// UnitForge Editor JavaScript // Handles the unit file editor functionality class UnitFileEditor { constructor() { this.currentUnitType = 'service'; this.currentContent = ''; this.init(); } init() { this.setupEventListeners(); this.initializeEditor(); this.updateFilename(); } setupEventListeners() { // Unit type change const unitTypeSelect = document.getElementById('unitType'); if (unitTypeSelect) { unitTypeSelect.addEventListener('change', this.changeUnitType.bind(this)); } // Unit name change const unitNameInput = document.getElementById('unitName'); if (unitNameInput) { unitNameInput.addEventListener('input', this.updateFilename.bind(this)); } // Editor content change const editor = document.getElementById('editor'); if (editor) { editor.addEventListener('input', this.debounce(this.onEditorChange.bind(this), 300)); } // File upload const fileInput = document.getElementById('fileInput'); if (fileInput) { fileInput.addEventListener('change', this.handleFileUpload.bind(this)); } } initializeEditor() { this.generateBasicUnit(); } changeUnitType() { const unitType = document.getElementById('unitType').value; this.currentUnitType = unitType; // Show/hide type-specific fields this.toggleTypeFields(unitType); // Update filename this.updateFilename(); // Generate new basic unit this.generateBasicUnit(); } toggleTypeFields(unitType) { // Hide all type-specific fields const allFields = document.querySelectorAll('.unit-type-fields'); allFields.forEach(field => field.classList.add('d-none')); // Show relevant fields const targetFields = document.getElementById(`${unitType}Fields`); if (targetFields) { targetFields.classList.remove('d-none'); } } updateFilename() { const unitName = document.getElementById('unitName').value || 'myservice'; const unitType = document.getElementById('unitType').value; const filename = `${unitName}.${unitType}`; const filenameElement = document.getElementById('filename'); if (filenameElement) { filenameElement.textContent = filename; } } generateBasicUnit() { const unitType = this.currentUnitType; const unitName = document.getElementById('unitName').value || 'myservice'; const description = document.getElementById('unitDescription').value || 'My Service Description'; let content = `[Unit]\nDescription=${description}\n`; // Add common dependencies if (unitType === 'service' || unitType === 'timer') { content += 'After=network.target\n'; } content += '\n'; // Add type-specific sections switch (unitType) { case 'service': content += '[Service]\n'; content += 'Type=simple\n'; content += 'ExecStart=/usr/bin/myapp\n'; content += 'User=www-data\n'; content += 'Restart=on-failure\n'; break; case 'timer': content += '[Timer]\n'; content += 'OnCalendar=daily\n'; content += 'Persistent=true\n'; break; case 'socket': content += '[Socket]\n'; content += 'ListenStream=127.0.0.1:8080\n'; content += 'SocketUser=www-data\n'; break; case 'mount': content += '[Mount]\n'; content += 'What=/dev/disk/by-uuid/12345678-1234-1234-1234-123456789abc\n'; content += 'Where=/mnt/mydisk\n'; content += 'Type=ext4\n'; content += 'Options=defaults\n'; break; case 'target': content += '[Unit]\n'; content += 'Requires=multi-user.target\n'; break; case 'path': content += '[Path]\n'; content += 'PathExists=/var/spool/myapp\n'; content += 'Unit=myapp.service\n'; break; } // Add Install section for most types if (unitType !== 'target') { content += '\n[Install]\n'; if (unitType === 'timer') { content += 'WantedBy=timers.target\n'; } else if (unitType === 'socket') { content += 'WantedBy=sockets.target\n'; } else { content += 'WantedBy=multi-user.target\n'; } } this.setEditorContent(content); } setEditorContent(content) { const editor = document.getElementById('editor'); if (editor) { editor.value = content; this.currentContent = content; this.updateUnitInfo(); } } onEditorChange() { const editor = document.getElementById('editor'); if (editor) { this.currentContent = editor.value; this.updateUnitInfo(); } } updateUnitInfo() { // Update basic info display const lines = this.currentContent.split('\n'); const sections = this.countSections(lines); const infoType = document.getElementById('infoType'); const infoSections = document.getElementById('infoSections'); if (infoType) { infoType.textContent = this.currentUnitType; } if (infoSections) { infoSections.textContent = sections; } } countSections(lines) { let count = 0; for (const line of lines) { if (line.trim().match(/^\[.+\]$/)) { count++; } } return count; } updateField(section, key, value) { if (!value.trim()) return; const lines = this.currentContent.split('\n'); let newLines = []; let currentSection = ''; let foundSection = false; let foundKey = false; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const sectionMatch = line.match(/^\[(.+)\]$/); if (sectionMatch) { currentSection = sectionMatch[1]; foundSection = (currentSection === section); newLines.push(line); continue; } if (foundSection && line.includes('=')) { const [lineKey] = line.split('=', 2); if (lineKey.trim() === key) { newLines.push(`${key}=${value}`); foundKey = true; continue; } } newLines.push(line); } // If section or key wasn't found, add them if (!foundKey) { this.addKeyToSection(newLines, section, key, value); } this.setEditorContent(newLines.join('\n')); } addKeyToSection(lines, section, key, value) { let sectionIndex = -1; let nextSectionIndex = -1; // Find the target section for (let i = 0; i < lines.length; i++) { const sectionMatch = lines[i].match(/^\[(.+)\]$/); if (sectionMatch) { if (sectionMatch[1] === section) { sectionIndex = i; } else if (sectionIndex !== -1 && nextSectionIndex === -1) { nextSectionIndex = i; break; } } } if (sectionIndex === -1) { // Section doesn't exist, add it lines.push(''); lines.push(`[${section}]`); lines.push(`${key}=${value}`); } else { // Section exists, add key const insertIndex = nextSectionIndex === -1 ? lines.length : nextSectionIndex; lines.splice(insertIndex, 0, `${key}=${value}`); } } async validateUnit() { const content = this.currentContent; const filename = document.getElementById('filename').textContent; const resultsDiv = document.getElementById('validationResults'); if (!resultsDiv) return; // Show loading resultsDiv.innerHTML = `