Compare commits

..

1 Commits

Author SHA1 Message Date
7ff1a7da36 feat(cli): replace ugly ASCII boxes with beautiful colored status output
All checks were successful
CI / Type Check & Lint (push) Successful in 6s
CI / Build Test (Current Platform) (push) Successful in 5s
Release / build-and-release (push) Successful in 48s
CI / Build All Platforms (push) Successful in 52s
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!
2025-10-19 21:50:31 +00:00
2 changed files with 89 additions and 61 deletions

View File

@@ -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",

View File

@@ -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<void> {
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('');
}
}