From 7ff1a7da3639ed9c6a7c61877bb6fa371195c9ca Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Sun, 19 Oct 2025 21:50:31 +0000 Subject: [PATCH] feat(cli): replace ugly ASCII boxes with beautiful colored status output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced all ASCII box characters (┌─┐│└┘) with modern, clean colored output using the existing color theme and symbols. Changes in ts/systemd.ts: - displayServiceStatus(): Parse systemctl output and show key info with colored symbols (● for running, ○ for stopped) - displaySingleUpsStatus(): Clean output with battery/runtime colors - Green >60%, yellow 30-60%, red <30% for battery - Power status with colored symbols and text - Clean indented layout without boxes Example new output: ● Service: active (running) PID: 7606 Memory: 41.5M CPU: 279ms UPS Devices (2): ● Test UPS (SNMP v1) - Online Battery: 100% ✓ Runtime: 48 min Host: 192.168.187.140:161 Much cleaner and more readable than ASCII boxes! --- deno.json | 2 +- ts/systemd.ts | 148 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 89 insertions(+), 61 deletions(-) diff --git a/deno.json b/deno.json index b9f334d..7b5e73e 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@serve.zone/nupst", - "version": "4.0.5", + "version": "4.0.6", "exports": "./mod.ts", "tasks": { "dev": "deno run --allow-all mod.ts", diff --git a/ts/systemd.ts b/ts/systemd.ts index 0afa057..e61b088 100644 --- a/ts/systemd.ts +++ b/ts/systemd.ts @@ -3,6 +3,7 @@ import { promises as fs } from 'node:fs'; import { execSync } from 'node:child_process'; import { NupstDaemon } from './daemon.ts'; import { logger } from './logger.ts'; +import { theme, symbols, getBatteryColor, getRuntimeColor, formatPowerStatus } from './colors.ts'; /** * Class for managing systemd service @@ -171,18 +172,50 @@ WantedBy=multi-user.target private displayServiceStatus(): void { try { const serviceStatus = execSync('systemctl status nupst.service').toString(); - const boxWidth = 45; - logger.logBoxTitle('Service Status', boxWidth); - // Process each line of the status output - serviceStatus.split('\n').forEach((line) => { - logger.logBoxLine(line); - }); - logger.logBoxEnd(); + const lines = serviceStatus.split('\n'); + + // Parse key information from systemctl output + let isActive = false; + let pid = ''; + let memory = ''; + let cpu = ''; + + for (const line of lines) { + if (line.includes('Active:')) { + isActive = line.includes('active (running)'); + } else if (line.includes('Main PID:')) { + const match = line.match(/Main PID:\s+(\d+)/); + if (match) pid = match[1]; + } else if (line.includes('Memory:')) { + const match = line.match(/Memory:\s+([\d.]+[A-Z])/); + if (match) memory = match[1]; + } else if (line.includes('CPU:')) { + const match = line.match(/CPU:\s+([\d.]+(?:ms|s))/); + if (match) cpu = match[1]; + } + } + + // Display beautiful status + console.log(''); + if (isActive) { + console.log(`${symbols.running} ${theme.success('Service:')} ${theme.statusActive('active (running)')}`); + } else { + console.log(`${symbols.stopped} ${theme.dim('Service:')} ${theme.statusInactive('inactive')}`); + } + + if (pid || memory || cpu) { + const details = []; + if (pid) details.push(`PID: ${theme.dim(pid)}`); + if (memory) details.push(`Memory: ${theme.dim(memory)}`); + if (cpu) details.push(`CPU: ${theme.dim(cpu)}`); + console.log(` ${details.join(' ')}`); + } + console.log(''); + } catch (error) { - const boxWidth = 45; - logger.logBoxTitle('Service Status', boxWidth); - logger.logBoxLine('Service is not running'); - logger.logBoxEnd(); + console.log(''); + console.log(`${symbols.stopped} ${theme.dim('Service:')} ${theme.statusInactive('not installed')}`); + console.log(''); } } @@ -199,7 +232,7 @@ WantedBy=multi-user.target // Check if we have the new multi-UPS config format if (config.upsDevices && Array.isArray(config.upsDevices) && config.upsDevices.length > 0) { - logger.log(`Found ${config.upsDevices.length} UPS device(s) in configuration`); + console.log(theme.info(`UPS Devices (${config.upsDevices.length}):`)); // Show status for each UPS for (const ups of config.upsDevices) { @@ -207,6 +240,7 @@ WantedBy=multi-user.target } } else if (config.snmp) { // Legacy single UPS configuration + console.log(theme.info('UPS Devices (1):')); const legacyUps = { id: 'default', name: 'Default UPS', @@ -217,15 +251,16 @@ WantedBy=multi-user.target await this.displaySingleUpsStatus(legacyUps, snmp); } else { - logger.error('No UPS devices found in configuration'); + console.log(''); + console.log(`${symbols.warning} ${theme.warning('No UPS devices configured')}`); + console.log(` ${theme.dim('Run')} ${theme.command('nupst ups add')} ${theme.dim('to add a device')}`); + console.log(''); } } catch (error) { - const boxWidth = 45; - logger.logBoxTitle('UPS Status', boxWidth); - logger.logBoxLine( - `Failed to retrieve UPS status: ${error instanceof Error ? error.message : String(error)}`, - ); - logger.logBoxEnd(); + console.log(''); + console.log(`${symbols.error} ${theme.error('Failed to retrieve UPS status')}`); + console.log(` ${theme.dim(error instanceof Error ? error.message : String(error))}`); + console.log(''); } } @@ -235,24 +270,6 @@ WantedBy=multi-user.target * @param snmp SNMP manager */ private async displaySingleUpsStatus(ups: any, snmp: any): Promise { - const boxWidth = 45; - logger.logBoxTitle(`Connecting to UPS: ${ups.name}`, boxWidth); - logger.logBoxLine(`ID: ${ups.id}`); - logger.logBoxLine(`Host: ${ups.snmp.host}:${ups.snmp.port}`); - logger.logBoxLine(`UPS Model: ${ups.snmp.upsModel || 'cyberpower'}`); - - if (ups.groups && ups.groups.length > 0) { - // Get group names if available - const config = this.daemon.getConfig(); - const groupNames = ups.groups.map((groupId: string) => { - const group = config.groups?.find((g: { id: string }) => g.id === groupId); - return group ? group.name : groupId; - }); - logger.logBoxLine(`Groups: ${groupNames.join(', ')}`); - } - - logger.logBoxEnd(); - try { // Create a test config with a short timeout const testConfig = { @@ -262,32 +279,43 @@ WantedBy=multi-user.target const status = await snmp.getUpsStatus(testConfig); - logger.logBoxTitle(`UPS Status: ${ups.name}`, boxWidth); - logger.logBoxLine(`Power Status: ${status.powerStatus}`); - logger.logBoxLine(`Battery Capacity: ${status.batteryCapacity}%`); - logger.logBoxLine(`Runtime Remaining: ${status.batteryRuntime} minutes`); + // Determine status symbol based on power status + let statusSymbol = symbols.unknown; + if (status.powerStatus === 'online') { + statusSymbol = symbols.running; + } else if (status.powerStatus === 'onBattery') { + statusSymbol = symbols.warning; + } - // Show threshold status - logger.logBoxLine(''); - logger.logBoxLine('Thresholds:'); - logger.logBoxLine( - ` Battery: ${status.batteryCapacity}% / ${ups.thresholds.battery}% ${ - status.batteryCapacity < ups.thresholds.battery ? '⚠️' : '✓' - }`, - ); - logger.logBoxLine( - ` Runtime: ${status.batteryRuntime} min / ${ups.thresholds.runtime} min ${ - status.batteryRuntime < ups.thresholds.runtime ? '⚠️' : '✓' - }`, - ); + // Display UPS name and power status + console.log(` ${statusSymbol} ${theme.highlight(ups.name)} - ${formatPowerStatus(status.powerStatus)}`); + + // Display battery with color coding + const batteryColor = getBatteryColor(status.batteryCapacity); + const batterySymbol = status.batteryCapacity >= ups.thresholds.battery ? symbols.success : symbols.warning; + console.log(` Battery: ${batteryColor(status.batteryCapacity + '%')} ${batterySymbol} Runtime: ${getRuntimeColor(status.batteryRuntime)(status.batteryRuntime + ' min')}`); + + // Display host info + console.log(` ${theme.dim(`Host: ${ups.snmp.host}:${ups.snmp.port}`)}`); + + // Display groups if any + if (ups.groups && ups.groups.length > 0) { + const config = this.daemon.getConfig(); + const groupNames = ups.groups.map((groupId: string) => { + const group = config.groups?.find((g: { id: string }) => g.id === groupId); + return group ? group.name : groupId; + }); + console.log(` ${theme.dim(`Groups: ${groupNames.join(', ')}`)}`); + } + + console.log(''); - logger.logBoxEnd(); } catch (error) { - logger.logBoxTitle(`UPS Status: ${ups.name}`, boxWidth); - logger.logBoxLine( - `Failed to retrieve UPS status: ${error instanceof Error ? error.message : String(error)}`, - ); - logger.logBoxEnd(); + // Display error for this UPS + console.log(` ${symbols.error} ${theme.highlight(ups.name)} - ${theme.error('Connection failed')}`); + console.log(` ${theme.dim(error instanceof Error ? error.message : String(error))}`); + console.log(` ${theme.dim(`Host: ${ups.snmp.host}:${ups.snmp.port}`)}`); + console.log(''); } }