/** * Task Commands - Pre-built command templates and execution * Handles SSH command execution and validation for individual tasks */ class TaskCommands { constructor() { this.commandTemplates = { // App Commands (✅ IMPLEMENTED) install: 'libreportal app install {appName} {config}', uninstall: 'libreportal app uninstall {appName}', restart: 'libreportal app restart {appName}', start: 'libreportal app start {appName}', stop: 'libreportal app stop {appName}', backup: 'libreportal app backup {appName}', status: 'libreportal app status {appName}', // Docker Compose Management (✅ IMPLEMENTED) up: 'libreportal app up {appName}', down: 'libreportal app down {appName}', reload: 'libreportal app reload {appName}', // System Commands (✅ IMPLEMENTED) system_status: 'libreportal system status', system_update: 'libreportal system update', system_reset: 'libreportal system reset', // Future Commands (❌ NOT YET IMPLEMENTED) // restore: 'libreportal app restore {appName} {backupId}', // update_config: 'libreportal config update {appName}', // system_info: 'libreportal system info', // system_disk: 'libreportal system disk', // system_memory: 'libreportal system memory' }; this.commandStatus = { // ✅ Available in CLI install: 'implemented', uninstall: 'implemented', restart: 'implemented', start: 'implemented', stop: 'implemented', backup: 'implemented', status: 'implemented', up: 'implemented', down: 'implemented', reload: 'implemented', system_status: 'implemented', system_update: 'implemented', system_reset: 'implemented', // ❌ Not yet implemented in CLI restore: 'not_implemented', update_config: 'not_implemented', system_info: 'not_implemented', system_disk: 'not_implemented', system_memory: 'not_implemented' }; } /** * Generate command from template with parameters */ generateCommand(type, params = {}) { const template = this.commandTemplates[type]; if (!template) { throw new Error(`Unknown command type: ${type}`); } let command = template; Object.keys(params).forEach(key => { const value = params[key] || ''; command = command.replace(`{${key}}`, value); }); // Clean up double spaces and trailing spaces command = command.replace(/\s+/g, ' ').trim(); return command; } /** * Check if command is implemented in CLI */ isCommandImplemented(type) { return this.commandStatus[type] === 'implemented'; } /** * Get command status */ getCommandStatus(type) { return this.commandStatus[type] || 'unknown'; } /** * Get only implemented commands */ getImplementedCommands() { return Object.keys(this.commandStatus).filter(cmd => this.commandStatus[cmd] === 'implemented'); } /** * Get all commands with their status */ getAllCommandsWithStatus() { return Object.keys(this.commandStatus).map(cmd => ({ command: cmd, template: this.commandTemplates[cmd], status: this.commandStatus[cmd] })); } /** * Validate command and check if implemented */ validateCommand(type, params) { // Check if command exists const template = this.commandTemplates[type]; if (!template) { throw new Error(`Unknown command type: ${type}`); } // Check if command is implemented if (!this.isCommandImplemented(type)) { throw new Error(`Command '${type}' is not yet implemented in the CLI system`); } const requiredParams = { install: ['appName'], // config is optional uninstall: ['appName'], restart: ['appName'], start: ['appName'], stop: ['appName'], backup: ['appName'], status: ['appName'], up: ['appName'], down: ['appName'], reload: ['appName'] }; const required = requiredParams[type] || []; const missing = required.filter(param => !params[param]); if (missing.length > 0) { throw new Error(`Missing required parameters: ${missing.join(', ')}`); } return true; } /** * Execute command via API */ async executeCommand(command, taskId) { try { //// // console.log(`🚀 Executing command: ${command}`); // Add task to local queue using generic endpoint const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const taskFileName = `${taskId}.json`; const taskFilePath = `tasks/queue/${taskFileName}`; // console.log('🔍 Creating task file:', taskFilePath); // console.log('🔍 Task ID:', taskId); const task = { id: taskId, type: 'install', app: this.extractAppName(command), command: command, config: null, status: 'queued', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; // console.log('🔍 Task object:', task); const response = await fetch('/write-file', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ path: taskFilePath, content: JSON.stringify(task, null, 2) }) }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); console.error(`❌ API Error Response:`, errorData); throw new Error(`Command execution failed: ${response.statusText} - ${errorData.error || 'Unknown error'}`); } const result = await response.json(); //// // console.log(`✅ Task queued successfully:`, result); return { success: true, output: `Task queued: ${command}`, taskId: result.taskId, exitCode: 0, queued: true }; } catch (error) { console.error(`❌ Command execution failed:`, error); throw new Error(`Command execution failed: ${error.message}`); } } /** * Extract app name from command */ extractAppName(command) { const match = command.match(/libreportal app (\w+) (.+)/); return match ? match[2] : 'unknown'; } /** * Execute command via file-based task system */ async executeCommand(command, taskId) { //// // console.log(`🔧 Creating task for command: ${command}`); try { // Create task file in queue const task = { id: taskId, command: command, status: 'queued', created: new Date().toISOString() }; // Extract app name from command for better task tracking const appNameMatch = command.match(/libreportal app (\w+) (\w+)/); if (appNameMatch) { task.app = appNameMatch[2]; task.type = appNameMatch[1]; } // Write task file to queue await this.createTaskFile(task); //// // console.log(`✅ Task created: ${taskId}`); return { success: true, taskId: taskId, message: 'Task queued for execution' }; } catch (error) { console.error(`❌ Failed to create task:`, error); throw new Error(`Task creation failed: ${error.message}`); } } /** * Create task file in queue directory */ async createTaskFile(task) { const taskFileName = `${task.id}.json`; const taskFilePath = `tasks/queue/${taskFileName}`; try { // Write task file using generic endpoint const response = await fetch('/write-file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: taskFilePath, content: JSON.stringify(task, null, 2) }) }); if (!response.ok) { throw new Error(`Failed to create task file: ${response.statusText}`); } return await response.json(); } catch (error) { // Fallback: create file directly (for development) console.warn('API not available, using fallback method'); await this.writeTaskFileDirectly(task); } } /** * Direct task file creation (fallback method) */ async writeTaskFileDirectly(task) { // This would be implemented server-side // For now, we'll simulate the task creation //// // console.log(`Task file created: ${task.id}.json`); return { success: true, taskId: task.id }; } /** * Get available command types */ getCommandTypes() { return Object.keys(this.commandTemplates); } /** * Get command template for a type */ getCommandTemplate(type) { return this.commandTemplates[type]; } /** * Get app data for icon and URL information */ getAppData(appName) { if (window.apps && Array.isArray(window.apps)) { const target = String(appName || '').toLowerCase(); const app = window.apps.find(app => { const appCommandName = (app.command || '').split(' ').pop(); return appCommandName.toLowerCase() === target; }); if (app) return app; } // Fallback: create minimal app data return { name: appName, icon: `icons/apps/${appName}.svg`, command: `libreportal app install ${appName}` }; } /** * Trigger enhanced notification with app icon and action button * * `customIcon` is forwarded to NotificationSystem.show so callers can * supply a task-type emoji (install ✅, backup 💾, etc.) for the leftmost * icon slot — same style every other task-related notification uses. */ triggerNotification(message, type = 'info', appName = null, appUrl = null, appIcon = null, customIcon = null) { if (window.notificationSystem && typeof window.notificationSystem.show === 'function') { // Use enhanced notification system window.notificationSystem.show(message, type, appName, appUrl, appIcon, customIcon); } else if (typeof ConfigShared !== 'undefined' && ConfigShared.showNotification) { // Fallback to basic notification ConfigShared.showNotification(message, type); } else { //// // console.log(`🔔 ${type.toUpperCase()}: ${message}`); } } } // Export for use window.TaskCommands = TaskCommands;