diff --git a/ts/cli.ts b/ts/cli.ts index 7503226..b9c0b22 100644 --- a/ts/cli.ts +++ b/ts/cli.ts @@ -1,6 +1,6 @@ import { execSync } from 'node:child_process'; import { Nupst } from './nupst.ts'; -import { logger } from './logger.ts'; +import { logger, type ITableColumn } from './logger.ts'; import { theme, symbols } from './colors.ts'; /** @@ -303,154 +303,164 @@ export class NupstCli { try { await this.nupst.getDaemon().loadConfig(); } catch (_error) { - const errorBoxWidth = 45; - logger.logBoxTitle('Configuration Error', errorBoxWidth); - logger.logBoxLine('No configuration found.'); - logger.logBoxLine("Please run 'nupst setup' first to create a configuration."); - logger.logBoxEnd(); + logger.logBox('Configuration Error', [ + 'No configuration found.', + "Please run 'nupst ups add' first to create a configuration.", + ], 50, 'error'); return; } // Get current configuration const config = this.nupst.getDaemon().getConfig(); - const boxWidth = 50; - logger.logBoxTitle('NUPST Configuration', boxWidth); - // Check if multi-UPS config if (config.upsDevices && Array.isArray(config.upsDevices)) { - // Multi-UPS configuration - logger.logBoxLine(`UPS Devices: ${config.upsDevices.length}`); - logger.logBoxLine(`Groups: ${config.groups ? config.groups.length : 0}`); - logger.logBoxLine(`Check Interval: ${config.checkInterval / 1000} seconds`); - logger.logBoxLine(''); - logger.logBoxLine('Configuration File Location:'); - logger.logBoxLine(' /etc/nupst/config.json'); - logger.logBoxEnd(); + // === Multi-UPS Configuration === + + // Overview Box + logger.log(''); + logger.logBox('NUPST Configuration', [ + `UPS Devices: ${theme.highlight(String(config.upsDevices.length))}`, + `Groups: ${theme.highlight(String(config.groups ? config.groups.length : 0))}`, + `Check Interval: ${theme.info(String(config.checkInterval / 1000))} seconds`, + '', + theme.dim('Configuration File:'), + ` ${theme.path('/etc/nupst/config.json')}`, + ], 60, 'info'); - // Show UPS devices + // UPS Devices Table if (config.upsDevices.length > 0) { - logger.logBoxTitle('UPS Devices', boxWidth); - for (const ups of config.upsDevices) { - logger.logBoxLine(`${ups.name} (${ups.id}):`); - logger.logBoxLine(` Host: ${ups.snmp.host}:${ups.snmp.port}`); - logger.logBoxLine(` Model: ${ups.snmp.upsModel}`); - logger.logBoxLine( - ` Thresholds: ${ups.thresholds.battery}% battery, ${ups.thresholds.runtime} min runtime`, - ); - logger.logBoxLine( - ` Groups: ${ups.groups.length > 0 ? ups.groups.join(', ') : 'None'}`, - ); - logger.logBoxLine(''); - } - logger.logBoxEnd(); + const upsRows = config.upsDevices.map((ups) => ({ + name: ups.name, + id: theme.dim(ups.id), + host: `${ups.snmp.host}:${ups.snmp.port}`, + model: ups.snmp.upsModel || 'cyberpower', + thresholds: `${ups.thresholds.battery}% / ${ups.thresholds.runtime}min`, + groups: ups.groups.length > 0 ? ups.groups.join(', ') : theme.dim('None'), + })); + + const upsColumns: ITableColumn[] = [ + { header: 'Name', key: 'name', align: 'left', color: theme.highlight }, + { header: 'ID', key: 'id', align: 'left' }, + { header: 'Host:Port', key: 'host', align: 'left', color: theme.info }, + { header: 'Model', key: 'model', align: 'left' }, + { header: 'Battery/Runtime', key: 'thresholds', align: 'left' }, + { header: 'Groups', key: 'groups', align: 'left' }, + ]; + + logger.log(''); + logger.info(`UPS Devices (${config.upsDevices.length}):`); + logger.log(''); + logger.logTable(upsColumns, upsRows); } - // Show groups + // Groups Table if (config.groups && config.groups.length > 0) { - logger.logBoxTitle('UPS Groups', boxWidth); - for (const group of config.groups) { - logger.logBoxLine(`${group.name} (${group.id}):`); - logger.logBoxLine(` Mode: ${group.mode}`); - if (group.description) { - logger.logBoxLine(` Description: ${group.description}`); - } - - // List UPS devices in this group + const groupRows = config.groups.map((group) => { const upsInGroup = config.upsDevices.filter((ups) => ups.groups && ups.groups.includes(group.id) ); - logger.logBoxLine( - ` UPS Devices: ${ - upsInGroup.length > 0 ? upsInGroup.map((ups) => ups.name).join(', ') : 'None' - }`, - ); - logger.logBoxLine(''); - } - logger.logBoxEnd(); + return { + name: group.name, + id: theme.dim(group.id), + mode: group.mode, + upsCount: String(upsInGroup.length), + ups: upsInGroup.length > 0 + ? upsInGroup.map((ups) => ups.name).join(', ') + : theme.dim('None'), + description: group.description || theme.dim('—'), + }; + }); + + const groupColumns: ITableColumn[] = [ + { header: 'Name', key: 'name', align: 'left', color: theme.highlight }, + { header: 'ID', key: 'id', align: 'left' }, + { header: 'Mode', key: 'mode', align: 'left', color: theme.info }, + { header: 'UPS', key: 'upsCount', align: 'right' }, + { header: 'UPS Devices', key: 'ups', align: 'left' }, + { header: 'Description', key: 'description', align: 'left' }, + ]; + + logger.log(''); + logger.info(`UPS Groups (${config.groups.length}):`); + logger.log(''); + logger.logTable(groupColumns, groupRows); } } else { - // Legacy single UPS configuration - if (!config.snmp) { - logger.logBoxLine('Error: Legacy configuration missing SNMP settings'); - } else { - // SNMP Settings - logger.logBoxLine('SNMP Settings:'); - logger.logBoxLine(` Host: ${config.snmp.host}`); - logger.logBoxLine(` Port: ${config.snmp.port}`); - logger.logBoxLine(` Version: ${config.snmp.version}`); - logger.logBoxLine(` UPS Model: ${config.snmp.upsModel || 'cyberpower'}`); - - if (config.snmp.version === 1 || config.snmp.version === 2) { - logger.logBoxLine(` Community: ${config.snmp.community}`); - } else if (config.snmp.version === 3) { - logger.logBoxLine(` Security Level: ${config.snmp.securityLevel}`); - logger.logBoxLine(` Username: ${config.snmp.username}`); - - // Show auth and privacy details based on security level - if ( - config.snmp.securityLevel === 'authNoPriv' || - config.snmp.securityLevel === 'authPriv' - ) { - logger.logBoxLine(` Auth Protocol: ${config.snmp.authProtocol || 'None'}`); - } - - if (config.snmp.securityLevel === 'authPriv') { - logger.logBoxLine(` Privacy Protocol: ${config.snmp.privProtocol || 'None'}`); - } - - // Show timeout value - logger.logBoxLine(` Timeout: ${config.snmp.timeout / 1000} seconds`); - } - - // Show OIDs if custom model is selected - if (config.snmp.upsModel === 'custom' && config.snmp.customOIDs) { - logger.logBoxLine('Custom OIDs:'); - logger.logBoxLine( - ` Power Status: ${config.snmp.customOIDs.POWER_STATUS || 'Not set'}`, - ); - logger.logBoxLine( - ` Battery Capacity: ${config.snmp.customOIDs.BATTERY_CAPACITY || 'Not set'}`, - ); - logger.logBoxLine( - ` Battery Runtime: ${config.snmp.customOIDs.BATTERY_RUNTIME || 'Not set'}`, - ); - } + // === Legacy Single UPS Configuration === + + if (!config.snmp || !config.thresholds) { + logger.logBox('Configuration Error', [ + 'Error: Legacy configuration missing SNMP or threshold settings', + ], 60, 'error'); + return; } - // Thresholds - if (!config.thresholds) { - logger.logBoxLine('Error: Legacy configuration missing threshold settings'); - } else { - logger.logBoxLine('Thresholds:'); - logger.logBoxLine(` Battery: ${config.thresholds.battery}%`); - logger.logBoxLine(` Runtime: ${config.thresholds.runtime} minutes`); - } - logger.logBoxLine(`Check Interval: ${config.checkInterval / 1000} seconds`); - - // Configuration file location - logger.logBoxLine(''); - logger.logBoxLine('Configuration File Location:'); - logger.logBoxLine(' /etc/nupst/config.json'); - logger.logBoxLine(''); - logger.logBoxLine('Note: Using legacy single-UPS configuration format.'); - logger.logBoxLine('Consider using "nupst add" to migrate to multi-UPS format.'); - - logger.logBoxEnd(); + logger.log(''); + logger.logBox('NUPST Configuration (Legacy)', [ + theme.warning('Legacy single-UPS configuration format'), + '', + theme.dim('SNMP Settings:'), + ` Host: ${theme.info(config.snmp.host)}`, + ` Port: ${theme.info(String(config.snmp.port))}`, + ` Version: ${config.snmp.version}`, + ` UPS Model: ${config.snmp.upsModel || 'cyberpower'}`, + ...(config.snmp.version === 1 || config.snmp.version === 2 + ? [` Community: ${config.snmp.community}`] + : [] + ), + ...(config.snmp.version === 3 + ? [ + ` Security Level: ${config.snmp.securityLevel}`, + ` Username: ${config.snmp.username}`, + ...(config.snmp.securityLevel === 'authNoPriv' || config.snmp.securityLevel === 'authPriv' + ? [` Auth Protocol: ${config.snmp.authProtocol || 'None'}`] + : [] + ), + ...(config.snmp.securityLevel === 'authPriv' + ? [` Privacy Protocol: ${config.snmp.privProtocol || 'None'}`] + : [] + ), + ` Timeout: ${config.snmp.timeout / 1000} seconds`, + ] + : [] + ), + ...(config.snmp.upsModel === 'custom' && config.snmp.customOIDs + ? [ + theme.dim('Custom OIDs:'), + ` Power Status: ${config.snmp.customOIDs.POWER_STATUS || 'Not set'}`, + ` Battery Capacity: ${config.snmp.customOIDs.BATTERY_CAPACITY || 'Not set'}`, + ` Battery Runtime: ${config.snmp.customOIDs.BATTERY_RUNTIME || 'Not set'}`, + ] + : [] + ), + '', + theme.dim('Thresholds:'), + ` Battery: ${theme.highlight(String(config.thresholds.battery))}%`, + ` Runtime: ${theme.highlight(String(config.thresholds.runtime))} minutes`, + ` Check Interval: ${config.checkInterval / 1000} seconds`, + '', + theme.dim('Configuration File:'), + ` ${theme.path('/etc/nupst/config.json')}`, + '', + theme.warning('Note: Using legacy single-UPS configuration format.'), + `Consider using ${theme.command('nupst ups add')} to migrate to multi-UPS format.`, + ], 70, 'warning'); } - // Show service status + // Service Status try { const isActive = execSync('systemctl is-active nupst.service || true').toString().trim() === 'active'; const isEnabled = execSync('systemctl is-enabled nupst.service || true').toString().trim() === 'enabled'; - const statusBoxWidth = 45; - logger.logBoxTitle('Service Status', statusBoxWidth); - logger.logBoxLine(`Service Active: ${isActive ? 'Yes' : 'No'}`); - logger.logBoxLine(`Service Enabled: ${isEnabled ? 'Yes' : 'No'}`); - logger.logBoxEnd(); + logger.log(''); + logger.logBox('Service Status', [ + `Active: ${isActive ? theme.success('Yes') : theme.dim('No')}`, + `Enabled: ${isEnabled ? theme.success('Yes') : theme.dim('No')}`, + ], 50, isActive ? 'success' : 'default'); + logger.log(''); } catch (_error) { // Ignore errors checking service status } diff --git a/ts/cli/group-handler.ts b/ts/cli/group-handler.ts index 46cbbf6..4686e5b 100644 --- a/ts/cli/group-handler.ts +++ b/ts/cli/group-handler.ts @@ -1,6 +1,7 @@ import process from 'node:process'; import { Nupst } from '../nupst.ts'; -import { logger } from '../logger.ts'; +import { logger, type ITableColumn } from '../logger.ts'; +import { theme } from '../colors.ts'; import * as helpers from '../helpers/index.ts'; import { type IGroupConfig } from '../daemon.ts'; @@ -28,11 +29,10 @@ export class GroupHandler { try { await this.nupst.getDaemon().loadConfig(); } catch (error) { - const errorBoxWidth = 45; - logger.logBoxTitle('Configuration Error', errorBoxWidth); - logger.logBoxLine('No configuration found.'); - logger.logBoxLine("Please run 'nupst setup' first to create a configuration."); - logger.logBoxEnd(); + logger.logBox('Configuration Error', [ + 'No configuration found.', + "Please run 'nupst ups add' first to create a configuration.", + ], 50, 'error'); return; } @@ -41,43 +41,53 @@ export class GroupHandler { // Check if multi-UPS config if (!config.groups || !Array.isArray(config.groups)) { - // Legacy or missing groups configuration - const boxWidth = 45; - logger.logBoxTitle('UPS Groups', boxWidth); - logger.logBoxLine('No groups configured.'); - logger.logBoxLine('Use "nupst group add" to add a UPS group.'); - logger.logBoxEnd(); + logger.logBox('UPS Groups', [ + 'No groups configured.', + '', + `${theme.dim('Run')} ${theme.command('nupst group add')} ${theme.dim('to add a group')}`, + ], 50, 'info'); return; } - // Display group list - const boxWidth = 60; - logger.logBoxTitle('UPS Groups', boxWidth); - + // Display group list with modern table if (config.groups.length === 0) { - logger.logBoxLine('No UPS groups configured.'); - logger.logBoxLine('Use "nupst group add" to add a UPS group.'); - } else { - logger.logBoxLine(`Found ${config.groups.length} group(s)`); - logger.logBoxLine(''); - logger.logBoxLine('ID | Name | Mode | UPS Devices'); - logger.logBoxLine('-----------+----------------------+--------------+----------------'); - - for (const group of config.groups) { - const id = group.id.padEnd(10, ' ').substring(0, 10); - const name = (group.name || '').padEnd(20, ' ').substring(0, 20); - const mode = (group.mode || 'unknown').padEnd(12, ' ').substring(0, 12); - - // Count UPS devices in this group - const upsInGroup = config.upsDevices.filter((ups) => ups.groups.includes(group.id)); - const upsCount = upsInGroup.length; - const upsNames = upsInGroup.map((ups) => ups.name).join(', '); - - logger.logBoxLine(`${id} | ${name} | ${mode} | ${upsCount > 0 ? upsNames : 'None'}`); - } + logger.logBox('UPS Groups', [ + 'No UPS groups configured.', + '', + `${theme.dim('Run')} ${theme.command('nupst group add')} ${theme.dim('to add a group')}`, + ], 60, 'info'); + return; } - logger.logBoxEnd(); + // Prepare table data + const rows = config.groups.map((group) => { + // Count UPS devices in this group + const upsInGroup = config.upsDevices.filter((ups) => ups.groups.includes(group.id)); + const upsCount = upsInGroup.length; + const upsNames = upsInGroup.map((ups) => ups.name).join(', '); + + return { + id: group.id, + name: group.name || '', + mode: group.mode || 'unknown', + count: String(upsCount), + devices: upsCount > 0 ? upsNames : theme.dim('None'), + }; + }); + + const columns: ITableColumn[] = [ + { header: 'ID', key: 'id', align: 'left', color: theme.highlight }, + { header: 'Name', key: 'name', align: 'left' }, + { header: 'Mode', key: 'mode', align: 'left', color: theme.info }, + { header: 'UPS Count', key: 'count', align: 'right' }, + { header: 'UPS Devices', key: 'devices', align: 'left' }, + ]; + + logger.log(''); + logger.info(`UPS Groups (${config.groups.length}):`); + logger.log(''); + logger.logTable(columns, rows); + logger.log(''); } catch (error) { logger.error( `Failed to list UPS groups: ${error instanceof Error ? error.message : String(error)}`, diff --git a/ts/cli/ups-handler.ts b/ts/cli/ups-handler.ts index 1a4c801..602e7a0 100644 --- a/ts/cli/ups-handler.ts +++ b/ts/cli/ups-handler.ts @@ -1,7 +1,8 @@ import process from 'node:process'; import { execSync } from 'node:child_process'; import { Nupst } from '../nupst.ts'; -import { logger } from '../logger.ts'; +import { logger, type ITableColumn } from '../logger.ts'; +import { theme } from '../colors.ts'; import * as helpers from '../helpers/index.ts'; import type { TUpsModel } from '../snmp/types.ts'; import type { INupstConfig } from '../daemon.ts'; @@ -379,11 +380,10 @@ export class UpsHandler { try { await this.nupst.getDaemon().loadConfig(); } catch (error) { - const errorBoxWidth = 45; - logger.logBoxTitle('Configuration Error', errorBoxWidth); - logger.logBoxLine('No configuration found.'); - logger.logBoxLine("Please run 'nupst setup' first to create a configuration."); - logger.logBoxEnd(); + logger.logBox('Configuration Error', [ + 'No configuration found.', + "Please run 'nupst ups add' first to create a configuration.", + ], 50, 'error'); return; } @@ -393,58 +393,57 @@ export class UpsHandler { // Check if multi-UPS config if (!config.upsDevices || !Array.isArray(config.upsDevices)) { // Legacy single UPS configuration - const boxWidth = 45; - logger.logBoxTitle('UPS Devices', boxWidth); - logger.logBoxLine('Legacy single-UPS configuration detected.'); - if (!config.snmp || !config.thresholds) { - logger.logBoxLine(''); - logger.logBoxLine('Error: Configuration missing SNMP or threshold settings'); - logger.logBoxEnd(); - return; - } - logger.logBoxLine(''); - logger.logBoxLine('Default UPS:'); - logger.logBoxLine(` Host: ${config.snmp.host}:${config.snmp.port}`); - logger.logBoxLine(` Model: ${config.snmp.upsModel || 'cyberpower'}`); - logger.logBoxLine( - ` Thresholds: ${config.thresholds.battery}% battery, ${config.thresholds.runtime} min runtime`, - ); - logger.logBoxLine(''); - logger.logBoxLine('Use "nupst add" to add more UPS devices and migrate'); - logger.logBoxLine('to the multi-UPS configuration format.'); - logger.logBoxEnd(); + logger.logBox('UPS Devices', [ + 'Legacy single-UPS configuration detected.', + '', + ...((!config.snmp || !config.thresholds) + ? ['Error: Configuration missing SNMP or threshold settings'] + : [ + 'Default UPS:', + ` Host: ${config.snmp.host}:${config.snmp.port}`, + ` Model: ${config.snmp.upsModel || 'cyberpower'}`, + ` Thresholds: ${config.thresholds.battery}% battery, ${config.thresholds.runtime} min runtime`, + '', + 'Use "nupst ups add" to add more UPS devices and migrate', + 'to the multi-UPS configuration format.', + ] + ), + ], 60, 'warning'); return; } - // Display UPS list - const boxWidth = 60; - logger.logBoxTitle('UPS Devices', boxWidth); - + // Display UPS list with modern table if (config.upsDevices.length === 0) { - logger.logBoxLine('No UPS devices configured.'); - logger.logBoxLine('Use "nupst add" to add a UPS device.'); - } else { - logger.logBoxLine(`Found ${config.upsDevices.length} UPS device(s)`); - logger.logBoxLine(''); - logger.logBoxLine( - 'ID | Name | Host | Mode | Groups', - ); - logger.logBoxLine( - '-----------+----------------------+-----------------+--------------+----------------', - ); - - for (const ups of config.upsDevices) { - const id = ups.id.padEnd(10, ' ').substring(0, 10); - const name = (ups.name || '').padEnd(20, ' ').substring(0, 20); - const host = `${ups.snmp.host}:${ups.snmp.port}`.padEnd(15, ' ').substring(0, 15); - const model = (ups.snmp.upsModel || 'cyberpower').padEnd(12, ' ').substring(0, 12); - const groups = ups.groups.length > 0 ? ups.groups.join(', ') : 'None'; - - logger.logBoxLine(`${id} | ${name} | ${host} | ${model} | ${groups}`); - } + logger.logBox('UPS Devices', [ + 'No UPS devices configured.', + '', + `${theme.dim('Run')} ${theme.command('nupst ups add')} ${theme.dim('to add a device')}`, + ], 60, 'info'); + return; } - logger.logBoxEnd(); + // Prepare table data + const rows = config.upsDevices.map((ups) => ({ + id: ups.id, + name: ups.name || '', + host: `${ups.snmp.host}:${ups.snmp.port}`, + model: ups.snmp.upsModel || 'cyberpower', + groups: ups.groups.length > 0 ? ups.groups.join(', ') : theme.dim('None'), + })); + + const columns: ITableColumn[] = [ + { header: 'ID', key: 'id', align: 'left', color: theme.highlight }, + { header: 'Name', key: 'name', align: 'left' }, + { header: 'Host:Port', key: 'host', align: 'left', color: theme.info }, + { header: 'Model', key: 'model', align: 'left' }, + { header: 'Groups', key: 'groups', align: 'left' }, + ]; + + logger.log(''); + logger.info(`UPS Devices (${config.upsDevices.length}):`); + logger.log(''); + logger.logTable(columns, rows); + logger.log(''); } catch (error) { logger.error( `Failed to list UPS devices: ${error instanceof Error ? error.message : String(error)}`,