UNPKG

docker-pilot

Version:

A powerful, scalable Docker CLI library for managing containerized applications of any size

418 lines (417 loc) β€’ 17.9 kB
"use strict"; /** * Status Command - Show status of services */ Object.defineProperty(exports, "__esModule", { value: true }); exports.StatusCommand = void 0; const BaseCommand_1 = require("./BaseCommand"); const child_process_1 = require("child_process"); const util_1 = require("util"); const execAsync = (0, util_1.promisify)(child_process_1.exec); class StatusCommand extends BaseCommand_1.BaseCommand { constructor(context) { super('status', 'Show status of all services or a specific service', 'docker-pilot status [service-name] [options]', context); } async execute(args, _options) { const { args: parsedArgs, options: parsedOptions } = this.parseOptions(args); const serviceName = parsedArgs[0]; try { if (!(await this.checkDockerAvailable())) { return this.createErrorResult(this.i18n.t('cmd.docker_not_available')); } this.logger.info(this.i18n.t('cmd.status.loading')); const { result: statusData, executionTime } = await this.measureExecutionTime(async () => { return await this.getServicesStatus(serviceName); }); // Always show status output this.showServiceStatus(statusData, parsedOptions); // Return result with detailed output for interactive menu const outputSummary = this.createStatusSummary(statusData); return this.createSuccessResult(outputSummary, executionTime); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; this.logger.error(this.i18n.t('cmd.status.failed', { error: errorMessage })); return this.createErrorResult(errorMessage); } } /** * Get real status of services using docker compose ps */ async getServicesStatus(serviceName) { try { // Use compose file from context const composeFile = this.context.composeFile; const composeCmd = composeFile ? `docker compose -f "${composeFile}"` : 'docker compose'; const command = serviceName ? `${composeCmd} ps ${serviceName} --format json` : `${composeCmd} ps --format json`; const result = await this.execDockerCommand(command); if (!result.stdout.trim()) { return await this.getServicesStatusFallback(serviceName); } // Parse JSON output from docker compose ps const lines = result.stdout.trim().split('\n'); const services = []; for (const line of lines) { if (!line.trim()) continue; try { const serviceData = JSON.parse(line); services.push({ name: serviceData.Service || serviceData.Name || 'unknown', state: this.normalizeState(serviceData.State || serviceData.Status || 'unknown'), health: this.getHealthFromStatus(serviceData.Health || serviceData.Status || ''), uptime: serviceData.RunningFor || '', ports: serviceData.Publishers ? serviceData.Publishers.map((p) => `${p.PublishedPort}:${p.TargetPort}`) : [], image: serviceData.Image || '', created: serviceData.CreatedAt || '' }); } catch (parseError) { // Fallback to parsing plain text output services.push(this.parseServiceFromPlainText(line)); } } return services; } catch (error) { // If JSON format fails, try with table format and parse manually return await this.getServicesStatusFallback(serviceName); } } /** * Fallback method using table format */ async getServicesStatusFallback(serviceName) { try { // Use compose file from context const composeFile = this.context.composeFile; const composeCmd = composeFile ? `docker compose -f "${composeFile}"` : 'docker compose'; // Try docker compose ps first const composeCommand = serviceName ? `${composeCmd} ps ${serviceName}` : `${composeCmd} ps`; let result = await this.execDockerCommand(composeCommand); if (!result.stdout.trim()) { // If docker compose ps doesn't work, try docker ps directly const dockerCommand = serviceName ? `docker ps --filter "name=${serviceName}" --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}\t{{.CreatedAt}}"` : 'docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}\t{{.CreatedAt}}"'; result = await this.execDockerCommand(dockerCommand); if (!result.stdout.trim()) { console.log('[DEBUG] No containers found via docker ps'); return []; } return this.parseDockerPsOutput(result.stdout); } return this.parseTableOutput(result.stdout); } catch (error) { console.log(`[DEBUG] Failed to get services status: ${error instanceof Error ? error.message : 'Unknown error'}`); return []; } } /** * Parse table output from docker compose ps */ parseTableOutput(output) { const lines = output.trim().split('\n'); const services = []; // Skip header line(s) const dataLines = lines.filter(line => !line.includes('NAME') && !line.includes('SERVICE') && line.trim() !== '' && !line.startsWith('-')); for (const line of dataLines) { const parts = line.trim().split(/\s+/); if (parts.length >= 3) { const name = parts[0] || 'unknown'; const image = parts[1] || ''; const created = parts[3] || ''; const status = parts.slice(4).join(' ') || 'unknown'; services.push({ name: name.replace(/^.*_/, ''), // Remove project prefix state: this.normalizeState(status), health: this.getHealthFromStatus(status), image, created, uptime: this.extractUptime(status) }); } } return services; } /** * Parse output from docker ps command */ parseDockerPsOutput(output) { const lines = output.trim().split('\n'); const services = []; // Skip header line const dataLines = lines.filter(line => !line.includes('NAMES') && !line.includes('IMAGE') && line.trim() !== '' && !line.startsWith('-')); for (const line of dataLines) { const parts = line.trim().split('\t'); if (parts.length >= 3) { const name = parts[0] || 'unknown'; const image = parts[1] || ''; const status = parts[2] || 'unknown'; const ports = parts[3] || ''; const created = parts[4] || ''; // Extract port mappings const portMappings = []; if (ports) { const portMatches = ports.match(/(\d+:\d+)/g); if (portMatches) { portMappings.push(...portMatches); } } services.push({ name: name.replace(/^.*_/, ''), // Remove project prefix if any state: this.normalizeState(status), health: this.getHealthFromStatus(status), image, created, uptime: this.extractUptime(status), ports: portMappings }); } } return services; } /** * Parse service info from plain text line */ parseServiceFromPlainText(line) { const parts = line.trim().split(/\s+/); return { name: parts[0] || 'unknown', state: this.normalizeState(parts.slice(1).join(' ')), health: this.getHealthFromStatus(parts.slice(1).join(' ')) }; } /** * Normalize service state */ normalizeState(status) { const lowerStatus = status.toLowerCase(); if (lowerStatus.includes('up') || lowerStatus.includes('running')) return 'running'; if (lowerStatus.includes('exit') || lowerStatus.includes('stopped')) return 'stopped'; if (lowerStatus.includes('restarting')) return 'restarting'; if (lowerStatus.includes('paused')) return 'paused'; if (lowerStatus.includes('dead')) return 'dead'; return 'unknown'; } /** * Extract health status from docker status */ getHealthFromStatus(status) { const lowerStatus = status.toLowerCase(); if (lowerStatus.includes('healthy')) return 'healthy'; if (lowerStatus.includes('unhealthy')) return 'unhealthy'; if (lowerStatus.includes('starting')) return 'starting'; return 'none'; } /** * Extract uptime from status string */ extractUptime(status) { const uptimeMatch = status.match(/Up\s+([^,\s]+(?:\s+[^,\s]+)*)/i); return uptimeMatch?.[1] || ''; } /** * Execute Docker command with proper error handling */ async execDockerCommand(command) { try { const result = await execAsync(command, { cwd: this.context.workingDirectory }); return result; } catch (error) { // Some docker commands return non-zero exit codes but still have useful output if (error.stdout) { return { stdout: error.stdout, stderr: error.stderr || '' }; } throw error; } } showExamples() { this.logger.info(` Examples: docker-pilot status # Show status of all services docker-pilot status backend # Show status of backend service docker-pilot status --detailed # Show detailed status information docker-pilot status --json # Output status in JSON format Status Options: --detailed Show detailed information (ports, uptime, image) --json Output status in JSON format --refresh=N Auto-refresh every N seconds `); } /** * Show service status with real data */ showServiceStatus(servicesData, options) { this.logger.newLine(); this.logger.info(this.i18n.t('cmd.status.title')); this.logger.separator('-', 50); if (servicesData.length === 0) { this.logger.info(this.i18n.t('cmd.status.no_services')); this.logger.info('\nπŸ’‘ Tip: Make sure you have a docker-compose.yml file and services are defined.'); return; } // Group services by state for better visualization const runningServices = servicesData.filter(s => s.state === 'running'); const stoppedServices = servicesData.filter(s => s.state === 'stopped'); const otherServices = servicesData.filter(s => s.state !== 'running' && s.state !== 'stopped'); // Show summary const totalServices = servicesData.length; const runningCount = runningServices.length; const stoppedCount = stoppedServices.length; this.logger.info(`πŸ“Š Services Summary: ${totalServices} total, ${runningCount} running, ${stoppedCount} stopped`); this.logger.newLine(); // Show running services first if (runningServices.length > 0) { this.logger.info('🟒 Running Services:'); runningServices.forEach(service => this.displayService(service, options)); } // Show stopped services if (stoppedServices.length > 0) { this.logger.info('πŸ”΄ Stopped Services:'); stoppedServices.forEach(service => this.displayService(service, options)); } // Show other states if (otherServices.length > 0) { this.logger.info('βšͺ Other Services:'); otherServices.forEach(service => this.displayService(service, options)); } if (options?.['json']) { this.logger.info('\nπŸ“„ JSON Output:'); this.logger.info(JSON.stringify(servicesData, null, 2)); } // Show helpful tips if (stoppedCount > 0) { this.logger.info('\nπŸ’‘ Tip: Use "docker-pilot start" to start stopped services.'); } } /** * Create a text summary of the status for interactive menu display */ createStatusSummary(servicesData) { if (servicesData.length === 0) { return 'πŸ“­ No services found. Make sure you have a docker-compose.yml file with defined services.'; } const runningCount = servicesData.filter(s => s.state === 'running').length; const stoppedCount = servicesData.filter(s => s.state === 'stopped').length; const totalCount = servicesData.length; let summary = `πŸ“Š Services Summary: ${totalCount} total, ${runningCount} running, ${stoppedCount} stopped\n\n`; // Group services by state const runningServices = servicesData.filter(s => s.state === 'running'); const stoppedServices = servicesData.filter(s => s.state === 'stopped'); const otherServices = servicesData.filter(s => s.state !== 'running' && s.state !== 'stopped'); if (runningServices.length > 0) { summary += '🟒 Running Services:\n'; runningServices.forEach(service => { summary += ` βœ… ${service.name} (${service.state})`; if (service.ports && service.ports.length > 0) { const mainPort = service.ports[0]; if (mainPort) { const portNumber = mainPort.split(':')[0]; const url = service.name.includes('redis') || service.name.includes('db') || service.name.includes('mysql') || service.name.includes('postgres') ? `${service.name}://localhost:${portNumber}` : `http://localhost:${portNumber}`; summary += ` - ${url}`; } } summary += '\n'; }); summary += '\n'; } if (stoppedServices.length > 0) { summary += 'πŸ”΄ Stopped Services:\n'; stoppedServices.forEach(service => { summary += ` ⏹️ ${service.name} (${service.state})\n`; }); summary += '\n'; } if (otherServices.length > 0) { summary += 'βšͺ Other Services:\n'; otherServices.forEach(service => { const icon = this.getStatusIcon(service.state); summary += ` ${icon} ${service.name} (${service.state})\n`; }); summary += '\n'; } if (stoppedCount > 0) { summary += 'πŸ’‘ Tip: Use "docker-pilot start" to start stopped services.\n'; } return summary.trim(); } /** * Display individual service information */ displayService(service, options) { const statusIcon = this.getStatusIcon(service.state); const healthIcon = this.getHealthIcon(service.health); this.logger.info(` ${statusIcon} ${service.name}`); this.logger.info(` State: ${service.state} ${healthIcon} ${service.health}`); if (options?.['detailed']) { if (service.image) { this.logger.info(` Image: ${service.image}`); } if (service.uptime) { this.logger.info(` Uptime: ${service.uptime}`); } if (service.ports && service.ports.length > 0) { this.logger.info(` Ports: ${service.ports.join(', ')}`); } if (service.created) { this.logger.info(` Created: ${service.created}`); } } else { // Show basic port info for running services if (service.state === 'running' && service.ports && service.ports.length > 0) { const mainPort = service.ports[0]; if (mainPort) { const portNumber = mainPort.split(':')[0]; const url = service.name.includes('redis') || service.name.includes('db') || service.name.includes('mysql') || service.name.includes('postgres') ? `${service.name}://localhost:${portNumber}` : `http://localhost:${portNumber}`; this.logger.info(` URL: ${url}`); } } } this.logger.newLine(); } getStatusIcon(state) { const icons = { running: 'βœ…', stopped: '⏹️', restarting: 'πŸ”„', paused: '⏸️', dead: 'πŸ’€', unknown: '❓' }; return icons[state] || icons['unknown']; } getHealthIcon(health) { const icons = { healthy: 'πŸ’š', unhealthy: '❀️', starting: 'πŸ’›', none: 'βšͺ' }; return icons[health] || icons['none']; } } exports.StatusCommand = StatusCommand; //# sourceMappingURL=StatusCommand.js.map