UNPKG

@aerocorp/cli

Version:

AeroCorp CLI 5.1.0 - Future-Proofed Enterprise Infrastructure with Live Preview, Tunneling & Advanced DevOps

232 lines (192 loc) • 6.89 kB
import axios from 'axios'; import chalk from 'chalk'; import ora from 'ora'; import { AuthService } from './auth'; export class LogService { private authService = new AuthService(); async getLogs(appName: string, options: any) { if (!this.authService.isAuthenticated()) { throw new Error('Not authenticated. Run "aerocorp login" first.'); } const spinner = ora(`Fetching logs for ${appName}...`).start(); try { const coolifyUrl = this.authService.getCoolifyUrl(); const headers = this.authService.getAuthHeaders(); // Find the application const app = await this.findApplication(coolifyUrl, headers, appName); if (!app) { spinner.fail(`Application '${appName}' not found`); throw new Error(`Application '${appName}' not found`); } // Get logs const logsResponse = await axios.get( `${coolifyUrl}/api/v1/applications/${app.id}/logs`, { headers, params: { lines: options.tail || 100, follow: options.follow || false } } ); spinner.succeed(`Logs fetched for ${appName}`); // Display logs this.displayLogs(appName, logsResponse.data, options); // If follow mode, set up streaming if (options.follow) { await this.followLogs(coolifyUrl, headers, app.id, appName); } } catch (error) { spinner.fail(`Failed to fetch logs for ${appName}`); if (error.response?.status === 404) { throw new Error(`Application '${appName}' not found or no logs available`); } else { throw new Error(`API Error: ${error.response?.data?.message || error.message}`); } } } private async findApplication(coolifyUrl: string, headers: any, appName: string): Promise<any> { try { // Get all projects const projectsResponse = await axios.get(`${coolifyUrl}/api/v1/projects`, { headers }); const projects = projectsResponse.data; // Search for application in all projects for (const project of projects) { try { const appsResponse = await axios.get( `${coolifyUrl}/api/v1/projects/${project.id}/applications`, { headers } ); const app = appsResponse.data.find((app: any) => app.name === appName || app.name.includes(appName) ); if (app) { return app; } } catch (error) { // Continue searching in other projects continue; } } return null; } catch (error) { throw new Error(`Failed to search for application: ${error.message}`); } } private displayLogs(appName: string, logs: any, options: any) { console.log(chalk.cyan(`\nšŸ“‹ Logs for ${appName}`)); console.log(chalk.cyan('='.repeat(50))); if (!logs || (Array.isArray(logs) && logs.length === 0)) { console.log(chalk.yellow('⚠ No logs available')); return; } // Handle different log formats let logEntries: any[] = []; if (typeof logs === 'string') { logEntries = logs.split('\n').filter(line => line.trim()); } else if (Array.isArray(logs)) { logEntries = logs; } else if (logs.logs) { logEntries = Array.isArray(logs.logs) ? logs.logs : logs.logs.split('\n'); } logEntries.forEach((logEntry, index) => { let logLine: string; let timestamp: string = ''; let level: string = ''; let message: string = ''; if (typeof logEntry === 'string') { logLine = logEntry; // Try to parse timestamp and level const timestampMatch = logLine.match(/^(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2})/); if (timestampMatch) { timestamp = timestampMatch[1]; logLine = logLine.substring(timestampMatch[0].length).trim(); } const levelMatch = logLine.match(/^(ERROR|WARN|INFO|DEBUG|TRACE)/i); if (levelMatch) { level = levelMatch[1].toUpperCase(); message = logLine.substring(levelMatch[0].length).trim(); } else { message = logLine; } } else { timestamp = logEntry.timestamp || logEntry.time || ''; level = logEntry.level || logEntry.severity || ''; message = logEntry.message || logEntry.msg || JSON.stringify(logEntry); } // Format output let output = ''; if (timestamp) { output += chalk.gray(`[${timestamp}] `); } if (level) { const levelColor = this.getLevelColor(level); output += (chalk as any)[levelColor](`${level.padEnd(5)} `); } output += message; console.log(output); }); console.log(chalk.cyan(`\nšŸ“Š Showing ${logEntries.length} log entries`)); } private getLevelColor(level: string): string { switch (level.toUpperCase()) { case 'ERROR': return 'red'; case 'WARN': case 'WARNING': return 'yellow'; case 'INFO': return 'blue'; case 'DEBUG': return 'magenta'; case 'TRACE': return 'gray'; default: return 'white'; } } private async followLogs(coolifyUrl: string, headers: any, appId: string, appName: string) { console.log(chalk.blue(`\nšŸ‘ļø Following logs for ${appName} (Press Ctrl+C to stop)...`)); let lastLogCount = 0; const followInterval = setInterval(async () => { try { const logsResponse = await axios.get( `${coolifyUrl}/api/v1/applications/${appId}/logs`, { headers, params: { lines: 50 } } ); let logEntries: any[] = []; const logs = logsResponse.data; if (typeof logs === 'string') { logEntries = logs.split('\n').filter(line => line.trim()); } else if (Array.isArray(logs)) { logEntries = logs; } else if (logs.logs) { logEntries = Array.isArray(logs.logs) ? logs.logs : logs.logs.split('\n'); } // Only show new logs if (logEntries.length > lastLogCount) { const newLogs = logEntries.slice(lastLogCount); newLogs.forEach(logEntry => { let message = typeof logEntry === 'string' ? logEntry : logEntry.message || JSON.stringify(logEntry); console.log(chalk.gray(`[${new Date().toISOString()}] `) + message); }); lastLogCount = logEntries.length; } } catch (error) { console.error(chalk.red(`Error fetching logs: ${error.message}`)); } }, 2000); // Poll every 2 seconds // Handle Ctrl+C process.on('SIGINT', () => { clearInterval(followInterval); console.log(chalk.yellow('\nšŸ‘‹ Stopped following logs')); process.exit(0); }); } }