2025-10-19 13:14:18 +00:00
|
|
|
import { execSync } from 'node:child_process';
|
2025-10-18 11:59:55 +00:00
|
|
|
import { Nupst } from './nupst.ts';
|
2025-10-20 01:30:57 +00:00
|
|
|
import { logger, type ITableColumn } from './logger.ts';
|
feat(cli): add beautiful colored output and fix daemon exit bug
Major improvements:
- Created color theme system (ts/colors.ts) with ANSI colors
- Enhanced logger with colors, table formatting, and styled boxes
- Fixed daemon exit bug - now stays running when no UPS configured
- Added config hot-reload with file watcher for live updates
- Beautified CLI help output with color-coded commands
- Added showcase test demonstrating all output features
- Fixed ANSI code handling for perfect table/box alignment
Features:
- Color-coded messages (success=green, error=red, warning=yellow, info=cyan)
- Status symbols (●○◐◯ for running/stopped/starting/unknown)
- Battery level colors (green>60%, yellow 30-60%, red<30%)
- Table formatting with auto-sizing and column alignment
- Styled boxes (success, error, warning, info styles)
- Hot-reload: daemon watches config file and reloads automatically
- Idle mode: daemon stays alive when no devices, checks periodically
Daemon improvements:
- No longer exits when no UPS devices configured
- Enters idle monitoring loop waiting for config
- File watcher detects config changes in real-time
- Auto-reloads and starts monitoring when devices added
- Logs warnings instead of errors for missing devices
Technical fixes:
- Strip ANSI codes when calculating text width for alignment
- Use visible length for padding calculations in tables and boxes
- Properly handle colored text in table cells and box lines
Breaking changes: None (backward compatible)
2025-10-19 15:08:30 +00:00
|
|
|
import { theme, symbols } from './colors.ts';
|
2025-03-25 09:06:23 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Class for handling CLI commands
|
|
|
|
* Provides interface between user commands and the application
|
|
|
|
*/
|
|
|
|
export class NupstCli {
|
|
|
|
private readonly nupst: Nupst;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new CLI handler
|
|
|
|
*/
|
|
|
|
constructor() {
|
|
|
|
this.nupst = new Nupst();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse command line arguments and execute the appropriate command
|
|
|
|
* @param args Command line arguments (process.argv)
|
|
|
|
*/
|
|
|
|
public async parseAndExecute(args: string[]): Promise<void> {
|
2025-10-18 12:36:35 +00:00
|
|
|
// Extract debug and version flags from any position
|
2025-03-25 09:06:23 +00:00
|
|
|
const debugOptions = this.extractDebugOptions(args);
|
|
|
|
if (debugOptions.debugMode) {
|
2025-03-26 22:38:24 +00:00
|
|
|
logger.log('Debug mode enabled');
|
2025-03-25 09:06:23 +00:00
|
|
|
// Enable debug mode in the SNMP client
|
|
|
|
this.nupst.getSnmp().enableDebug();
|
|
|
|
}
|
2025-03-26 17:49:50 +00:00
|
|
|
|
2025-10-18 12:36:35 +00:00
|
|
|
// Check for version flag
|
|
|
|
if (debugOptions.cleanedArgs.includes('--version') || debugOptions.cleanedArgs.includes('-v')) {
|
|
|
|
this.showVersion();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-25 09:06:23 +00:00
|
|
|
// Get the command (default to help if none provided)
|
2025-03-28 16:19:43 +00:00
|
|
|
const command = debugOptions.cleanedArgs[2] || 'help';
|
|
|
|
const commandArgs = debugOptions.cleanedArgs.slice(3);
|
2025-03-25 09:06:23 +00:00
|
|
|
|
|
|
|
// Route to the appropriate command handler
|
2025-03-28 16:19:43 +00:00
|
|
|
await this.executeCommand(command, commandArgs, debugOptions.debugMode);
|
2025-03-25 09:06:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Extract and remove debug options from args
|
|
|
|
* @param args Command line arguments
|
|
|
|
* @returns Object with debug flags and cleaned args
|
|
|
|
*/
|
|
|
|
private extractDebugOptions(args: string[]): { debugMode: boolean; cleanedArgs: string[] } {
|
|
|
|
const debugMode = args.includes('--debug') || args.includes('-d');
|
|
|
|
// Remove debug flags from args
|
2025-03-26 17:49:50 +00:00
|
|
|
const cleanedArgs = args.filter((arg) => arg !== '--debug' && arg !== '-d');
|
|
|
|
|
2025-03-25 09:06:23 +00:00
|
|
|
return { debugMode, cleanedArgs };
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Execute the command with the given arguments
|
|
|
|
* @param command Command to execute
|
2025-03-28 16:19:43 +00:00
|
|
|
* @param commandArgs Additional command arguments
|
2025-03-25 09:06:23 +00:00
|
|
|
* @param debugMode Whether debug mode is enabled
|
|
|
|
*/
|
2025-10-19 13:14:18 +00:00
|
|
|
private async executeCommand(
|
|
|
|
command: string,
|
|
|
|
commandArgs: string[],
|
|
|
|
debugMode: boolean,
|
|
|
|
): Promise<void> {
|
2025-03-28 22:12:01 +00:00
|
|
|
// Get access to the handlers
|
|
|
|
const upsHandler = this.nupst.getUpsHandler();
|
|
|
|
const groupHandler = this.nupst.getGroupHandler();
|
|
|
|
const serviceHandler = this.nupst.getServiceHandler();
|
2025-10-20 12:32:14 +00:00
|
|
|
const actionHandler = this.nupst.getActionHandler();
|
2025-10-18 12:36:35 +00:00
|
|
|
|
|
|
|
// Handle service subcommands
|
|
|
|
if (command === 'service') {
|
|
|
|
const subcommand = commandArgs[0] || 'status';
|
|
|
|
|
|
|
|
switch (subcommand) {
|
|
|
|
case 'enable':
|
|
|
|
await serviceHandler.enable();
|
|
|
|
break;
|
|
|
|
case 'disable':
|
|
|
|
await serviceHandler.disable();
|
|
|
|
break;
|
|
|
|
case 'start':
|
|
|
|
await serviceHandler.start();
|
|
|
|
break;
|
|
|
|
case 'stop':
|
|
|
|
await serviceHandler.stop();
|
|
|
|
break;
|
|
|
|
case 'restart':
|
|
|
|
await serviceHandler.stop();
|
2025-10-19 13:14:18 +00:00
|
|
|
await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait 2s
|
2025-10-18 12:36:35 +00:00
|
|
|
await serviceHandler.start();
|
|
|
|
break;
|
|
|
|
case 'status':
|
|
|
|
await serviceHandler.status();
|
|
|
|
break;
|
|
|
|
case 'logs':
|
|
|
|
await serviceHandler.logs();
|
|
|
|
break;
|
|
|
|
case 'start-daemon':
|
|
|
|
await serviceHandler.daemonStart(debugMode);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
this.showServiceHelp();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle UPS subcommands
|
|
|
|
if (command === 'ups') {
|
|
|
|
const subcommand = commandArgs[0] || 'list';
|
|
|
|
const subcommandArgs = commandArgs.slice(1);
|
|
|
|
|
|
|
|
switch (subcommand) {
|
|
|
|
case 'add':
|
|
|
|
await upsHandler.add();
|
|
|
|
break;
|
2025-10-18 21:07:57 +00:00
|
|
|
case 'edit': {
|
2025-10-18 12:36:35 +00:00
|
|
|
const upsId = subcommandArgs[0];
|
|
|
|
await upsHandler.edit(upsId);
|
|
|
|
break;
|
2025-10-18 21:07:57 +00:00
|
|
|
}
|
2025-10-18 12:36:35 +00:00
|
|
|
case 'remove':
|
|
|
|
case 'rm': // Alias
|
2025-10-18 21:07:57 +00:00
|
|
|
case 'delete': { // Backward compatibility
|
2025-10-18 12:36:35 +00:00
|
|
|
const upsIdToRemove = subcommandArgs[0];
|
|
|
|
if (!upsIdToRemove) {
|
|
|
|
logger.error('UPS ID is required for remove command');
|
|
|
|
this.showUpsHelp();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
await upsHandler.remove(upsIdToRemove);
|
|
|
|
break;
|
2025-10-18 21:07:57 +00:00
|
|
|
}
|
2025-10-18 12:36:35 +00:00
|
|
|
case 'list':
|
|
|
|
case 'ls': // Alias
|
|
|
|
await upsHandler.list();
|
|
|
|
break;
|
|
|
|
case 'test':
|
|
|
|
await upsHandler.test(debugMode);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
this.showUpsHelp();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-03-28 16:19:43 +00:00
|
|
|
// Handle group subcommands
|
|
|
|
if (command === 'group') {
|
|
|
|
const subcommand = commandArgs[0] || 'list';
|
|
|
|
const subcommandArgs = commandArgs.slice(1);
|
2025-10-18 12:36:35 +00:00
|
|
|
|
2025-03-28 16:19:43 +00:00
|
|
|
switch (subcommand) {
|
|
|
|
case 'add':
|
2025-03-28 22:12:01 +00:00
|
|
|
await groupHandler.add();
|
2025-03-28 16:19:43 +00:00
|
|
|
break;
|
2025-10-18 21:07:57 +00:00
|
|
|
case 'edit': {
|
2025-03-28 16:19:43 +00:00
|
|
|
const groupId = subcommandArgs[0];
|
|
|
|
if (!groupId) {
|
|
|
|
logger.error('Group ID is required for edit command');
|
|
|
|
this.showGroupHelp();
|
|
|
|
return;
|
|
|
|
}
|
2025-03-28 22:12:01 +00:00
|
|
|
await groupHandler.edit(groupId);
|
2025-03-28 16:19:43 +00:00
|
|
|
break;
|
2025-10-18 21:07:57 +00:00
|
|
|
}
|
2025-10-18 12:36:35 +00:00
|
|
|
case 'remove':
|
|
|
|
case 'rm': // Alias
|
2025-10-18 21:07:57 +00:00
|
|
|
case 'delete': { // Backward compatibility
|
2025-10-18 12:36:35 +00:00
|
|
|
const groupIdToRemove = subcommandArgs[0];
|
|
|
|
if (!groupIdToRemove) {
|
|
|
|
logger.error('Group ID is required for remove command');
|
2025-03-28 16:19:43 +00:00
|
|
|
this.showGroupHelp();
|
|
|
|
return;
|
|
|
|
}
|
2025-10-18 12:36:35 +00:00
|
|
|
await groupHandler.remove(groupIdToRemove);
|
2025-03-28 16:19:43 +00:00
|
|
|
break;
|
2025-10-18 21:07:57 +00:00
|
|
|
}
|
2025-03-28 16:19:43 +00:00
|
|
|
case 'list':
|
2025-10-18 12:36:35 +00:00
|
|
|
case 'ls': // Alias
|
2025-03-28 22:12:01 +00:00
|
|
|
await groupHandler.list();
|
2025-03-28 16:19:43 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
this.showGroupHelp();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-10-20 12:32:14 +00:00
|
|
|
// Handle action subcommands
|
|
|
|
if (command === 'action') {
|
|
|
|
const subcommand = commandArgs[0] || 'list';
|
|
|
|
const subcommandArgs = commandArgs.slice(1);
|
|
|
|
|
|
|
|
switch (subcommand) {
|
|
|
|
case 'add': {
|
|
|
|
const upsId = subcommandArgs[0];
|
|
|
|
await actionHandler.add(upsId);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'remove':
|
|
|
|
case 'rm': // Alias
|
|
|
|
case 'delete': { // Backward compatibility
|
|
|
|
const upsId = subcommandArgs[0];
|
|
|
|
const actionIndex = subcommandArgs[1];
|
|
|
|
await actionHandler.remove(upsId, actionIndex);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'list':
|
|
|
|
case 'ls': { // Alias
|
|
|
|
const upsId = subcommandArgs[0];
|
|
|
|
await actionHandler.list(upsId);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
this.showActionHelp();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-10-18 12:36:35 +00:00
|
|
|
// Handle config subcommand
|
|
|
|
if (command === 'config') {
|
|
|
|
const subcommand = commandArgs[0] || 'show';
|
|
|
|
|
|
|
|
switch (subcommand) {
|
|
|
|
case 'show':
|
|
|
|
case 'display':
|
|
|
|
await this.showConfig();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
await this.showConfig();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle top-level commands and backward compatibility
|
2025-03-25 09:06:23 +00:00
|
|
|
switch (command) {
|
2025-10-18 12:36:35 +00:00
|
|
|
// Backward compatibility - old UPS commands
|
2025-03-28 16:19:43 +00:00
|
|
|
case 'add':
|
2025-10-18 12:36:35 +00:00
|
|
|
logger.log("Note: 'nupst add' is deprecated. Use 'nupst ups add' instead.");
|
2025-03-28 22:12:01 +00:00
|
|
|
await upsHandler.add();
|
2025-03-28 16:19:43 +00:00
|
|
|
break;
|
|
|
|
case 'edit':
|
2025-10-18 12:36:35 +00:00
|
|
|
logger.log("Note: 'nupst edit' is deprecated. Use 'nupst ups edit' instead.");
|
|
|
|
await upsHandler.edit(commandArgs[0]);
|
2025-03-28 16:19:43 +00:00
|
|
|
break;
|
|
|
|
case 'delete':
|
2025-10-18 12:36:35 +00:00
|
|
|
logger.log("Note: 'nupst delete' is deprecated. Use 'nupst ups remove' instead.");
|
|
|
|
if (!commandArgs[0]) {
|
2025-03-28 16:19:43 +00:00
|
|
|
logger.error('UPS ID is required for delete command');
|
|
|
|
this.showHelp();
|
|
|
|
return;
|
|
|
|
}
|
2025-10-18 12:36:35 +00:00
|
|
|
await upsHandler.remove(commandArgs[0]);
|
2025-03-28 16:19:43 +00:00
|
|
|
break;
|
|
|
|
case 'list':
|
2025-10-18 12:36:35 +00:00
|
|
|
logger.log("Note: 'nupst list' is deprecated. Use 'nupst ups list' instead.");
|
2025-03-28 22:12:01 +00:00
|
|
|
await upsHandler.list();
|
2025-03-28 16:19:43 +00:00
|
|
|
break;
|
2025-10-18 12:36:35 +00:00
|
|
|
case 'test':
|
|
|
|
logger.log("Note: 'nupst test' is deprecated. Use 'nupst ups test' instead.");
|
|
|
|
await upsHandler.test(debugMode);
|
|
|
|
break;
|
|
|
|
case 'setup':
|
|
|
|
logger.log("Note: 'nupst setup' is deprecated. Use 'nupst ups edit' instead.");
|
2025-03-28 22:12:01 +00:00
|
|
|
await upsHandler.edit(undefined);
|
2025-03-28 16:19:43 +00:00
|
|
|
break;
|
|
|
|
|
2025-10-18 12:36:35 +00:00
|
|
|
// Backward compatibility - old service commands
|
2025-03-25 09:06:23 +00:00
|
|
|
case 'enable':
|
2025-10-18 12:36:35 +00:00
|
|
|
logger.log("Note: 'nupst enable' is deprecated. Use 'nupst service enable' instead.");
|
2025-03-28 22:12:01 +00:00
|
|
|
await serviceHandler.enable();
|
2025-03-25 09:06:23 +00:00
|
|
|
break;
|
2025-10-18 12:36:35 +00:00
|
|
|
case 'disable':
|
|
|
|
logger.log("Note: 'nupst disable' is deprecated. Use 'nupst service disable' instead.");
|
|
|
|
await serviceHandler.disable();
|
2025-03-25 09:06:23 +00:00
|
|
|
break;
|
2025-10-18 12:36:35 +00:00
|
|
|
case 'start':
|
|
|
|
logger.log("Note: 'nupst start' is deprecated. Use 'nupst service start' instead.");
|
|
|
|
await serviceHandler.start();
|
2025-03-25 09:06:23 +00:00
|
|
|
break;
|
|
|
|
case 'stop':
|
2025-10-18 12:36:35 +00:00
|
|
|
logger.log("Note: 'nupst stop' is deprecated. Use 'nupst service stop' instead.");
|
2025-03-28 22:12:01 +00:00
|
|
|
await serviceHandler.stop();
|
2025-03-25 09:06:23 +00:00
|
|
|
break;
|
|
|
|
case 'status':
|
2025-10-18 12:36:35 +00:00
|
|
|
logger.log("Note: 'nupst status' is deprecated. Use 'nupst service status' instead.");
|
2025-03-28 22:12:01 +00:00
|
|
|
await serviceHandler.status();
|
2025-03-25 09:06:23 +00:00
|
|
|
break;
|
2025-10-18 12:36:35 +00:00
|
|
|
case 'logs':
|
|
|
|
logger.log("Note: 'nupst logs' is deprecated. Use 'nupst service logs' instead.");
|
|
|
|
await serviceHandler.logs();
|
2025-03-25 09:06:23 +00:00
|
|
|
break;
|
2025-10-18 12:36:35 +00:00
|
|
|
case 'daemon-start':
|
2025-10-19 13:14:18 +00:00
|
|
|
logger.log(
|
|
|
|
"Note: 'nupst daemon-start' is deprecated. Use 'nupst service start-daemon' instead.",
|
|
|
|
);
|
2025-10-18 12:36:35 +00:00
|
|
|
await serviceHandler.daemonStart(debugMode);
|
2025-03-25 09:06:23 +00:00
|
|
|
break;
|
2025-03-26 17:49:50 +00:00
|
|
|
|
2025-10-18 12:36:35 +00:00
|
|
|
// Top-level commands (no changes)
|
2025-03-25 09:23:00 +00:00
|
|
|
case 'update':
|
2025-03-28 22:12:01 +00:00
|
|
|
await serviceHandler.update();
|
2025-03-25 09:23:00 +00:00
|
|
|
break;
|
2025-03-25 11:05:58 +00:00
|
|
|
case 'uninstall':
|
2025-03-28 22:12:01 +00:00
|
|
|
await serviceHandler.uninstall();
|
2025-03-25 11:05:58 +00:00
|
|
|
break;
|
2025-03-25 09:06:23 +00:00
|
|
|
case 'help':
|
2025-10-18 12:36:35 +00:00
|
|
|
case '--help':
|
|
|
|
case '-h':
|
|
|
|
this.showHelp();
|
|
|
|
break;
|
2025-03-25 09:06:23 +00:00
|
|
|
default:
|
2025-10-18 12:36:35 +00:00
|
|
|
logger.error(`Unknown command: ${command}`);
|
|
|
|
logger.log('');
|
2025-03-25 09:06:23 +00:00
|
|
|
this.showHelp();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-03-28 22:12:01 +00:00
|
|
|
* Display the current configuration
|
2025-03-25 09:06:23 +00:00
|
|
|
*/
|
2025-03-28 22:12:01 +00:00
|
|
|
private async showConfig(): Promise<void> {
|
2025-03-25 09:06:23 +00:00
|
|
|
try {
|
2025-03-28 22:12:01 +00:00
|
|
|
// Try to load configuration
|
2025-03-25 09:06:23 +00:00
|
|
|
try {
|
|
|
|
await this.nupst.getDaemon().loadConfig();
|
2025-10-18 21:07:57 +00:00
|
|
|
} catch (_error) {
|
2025-10-20 01:30:57 +00:00
|
|
|
logger.logBox('Configuration Error', [
|
|
|
|
'No configuration found.',
|
|
|
|
"Please run 'nupst ups add' first to create a configuration.",
|
|
|
|
], 50, 'error');
|
2025-03-25 09:06:23 +00:00
|
|
|
return;
|
|
|
|
}
|
2025-03-26 17:49:50 +00:00
|
|
|
|
2025-03-25 09:06:23 +00:00
|
|
|
// Get current configuration
|
|
|
|
const config = this.nupst.getDaemon().getConfig();
|
2025-03-26 17:49:50 +00:00
|
|
|
|
2025-03-28 22:12:01 +00:00
|
|
|
// Check if multi-UPS config
|
|
|
|
if (config.upsDevices && Array.isArray(config.upsDevices)) {
|
2025-10-20 01:30:57 +00:00
|
|
|
// === 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');
|
|
|
|
|
|
|
|
// UPS Devices Table
|
2025-03-28 22:12:01 +00:00
|
|
|
if (config.upsDevices.length > 0) {
|
2025-10-20 01:30:57 +00:00
|
|
|
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',
|
2025-10-20 12:03:14 +00:00
|
|
|
actions: `${(ups.actions || []).length} configured`,
|
2025-10-20 01:30:57 +00:00
|
|
|
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' },
|
2025-10-20 12:03:14 +00:00
|
|
|
{ header: 'Actions', key: 'actions', align: 'left' },
|
2025-10-20 01:30:57 +00:00
|
|
|
{ header: 'Groups', key: 'groups', align: 'left' },
|
|
|
|
];
|
|
|
|
|
|
|
|
logger.log('');
|
|
|
|
logger.info(`UPS Devices (${config.upsDevices.length}):`);
|
|
|
|
logger.log('');
|
|
|
|
logger.logTable(upsColumns, upsRows);
|
2025-03-28 22:12:01 +00:00
|
|
|
}
|
2025-03-26 17:49:50 +00:00
|
|
|
|
2025-10-20 01:30:57 +00:00
|
|
|
// Groups Table
|
2025-03-28 22:12:01 +00:00
|
|
|
if (config.groups && config.groups.length > 0) {
|
2025-10-20 01:30:57 +00:00
|
|
|
const groupRows = config.groups.map((group) => {
|
2025-10-19 13:14:18 +00:00
|
|
|
const upsInGroup = config.upsDevices.filter((ups) =>
|
|
|
|
ups.groups && ups.groups.includes(group.id)
|
|
|
|
);
|
2025-10-20 01:30:57 +00:00
|
|
|
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);
|
2025-03-25 09:23:00 +00:00
|
|
|
}
|
2025-03-28 22:12:01 +00:00
|
|
|
} else {
|
2025-10-20 01:30:57 +00:00
|
|
|
// === Legacy Single UPS Configuration ===
|
|
|
|
|
2025-10-20 12:03:14 +00:00
|
|
|
if (!config.snmp) {
|
2025-10-20 01:30:57 +00:00
|
|
|
logger.logBox('Configuration Error', [
|
2025-10-20 12:03:14 +00:00
|
|
|
'Error: Legacy configuration missing SNMP settings',
|
2025-10-20 01:30:57 +00:00
|
|
|
], 60, 'error');
|
|
|
|
return;
|
2025-10-18 21:07:57 +00:00
|
|
|
}
|
2025-03-28 16:19:43 +00:00
|
|
|
|
2025-10-20 01:30:57 +00:00
|
|
|
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'}`,
|
|
|
|
]
|
|
|
|
: []
|
|
|
|
),
|
|
|
|
'',
|
2025-10-20 12:03:14 +00:00
|
|
|
|
2025-10-20 01:30:57 +00:00
|
|
|
` 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');
|
2025-03-28 22:12:01 +00:00
|
|
|
}
|
2025-03-28 16:19:43 +00:00
|
|
|
|
2025-10-20 01:30:57 +00:00
|
|
|
// Service Status
|
2025-03-28 16:19:43 +00:00
|
|
|
try {
|
2025-03-28 22:12:01 +00:00
|
|
|
const isActive =
|
|
|
|
execSync('systemctl is-active nupst.service || true').toString().trim() === 'active';
|
|
|
|
const isEnabled =
|
|
|
|
execSync('systemctl is-enabled nupst.service || true').toString().trim() === 'enabled';
|
2025-03-28 16:19:43 +00:00
|
|
|
|
2025-10-20 01:30:57 +00:00
|
|
|
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('');
|
2025-10-18 21:07:57 +00:00
|
|
|
} catch (_error) {
|
2025-03-28 22:12:01 +00:00
|
|
|
// Ignore errors checking service status
|
2025-03-28 16:19:43 +00:00
|
|
|
}
|
|
|
|
} catch (error) {
|
2025-10-19 13:14:18 +00:00
|
|
|
logger.error(
|
|
|
|
`Failed to display configuration: ${
|
|
|
|
error instanceof Error ? error.message : String(error)
|
|
|
|
}`,
|
|
|
|
);
|
2025-03-28 16:19:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-10-18 12:36:35 +00:00
|
|
|
/**
|
|
|
|
* Display version information
|
|
|
|
*/
|
|
|
|
private showVersion(): void {
|
|
|
|
const version = this.nupst.getVersion();
|
|
|
|
logger.log(`NUPST version ${version}`);
|
2025-10-20 00:40:52 +00:00
|
|
|
logger.log('Network UPS Shutdown Tool (https://nupst.serve.zone)');
|
2025-10-18 12:36:35 +00:00
|
|
|
}
|
|
|
|
|
2025-03-28 16:19:43 +00:00
|
|
|
/**
|
2025-03-28 22:12:01 +00:00
|
|
|
* Display help message
|
2025-03-28 16:19:43 +00:00
|
|
|
*/
|
2025-03-28 22:12:01 +00:00
|
|
|
private showHelp(): void {
|
feat(cli): add beautiful colored output and fix daemon exit bug
Major improvements:
- Created color theme system (ts/colors.ts) with ANSI colors
- Enhanced logger with colors, table formatting, and styled boxes
- Fixed daemon exit bug - now stays running when no UPS configured
- Added config hot-reload with file watcher for live updates
- Beautified CLI help output with color-coded commands
- Added showcase test demonstrating all output features
- Fixed ANSI code handling for perfect table/box alignment
Features:
- Color-coded messages (success=green, error=red, warning=yellow, info=cyan)
- Status symbols (●○◐◯ for running/stopped/starting/unknown)
- Battery level colors (green>60%, yellow 30-60%, red<30%)
- Table formatting with auto-sizing and column alignment
- Styled boxes (success, error, warning, info styles)
- Hot-reload: daemon watches config file and reloads automatically
- Idle mode: daemon stays alive when no devices, checks periodically
Daemon improvements:
- No longer exits when no UPS devices configured
- Enters idle monitoring loop waiting for config
- File watcher detects config changes in real-time
- Auto-reloads and starts monitoring when devices added
- Logs warnings instead of errors for missing devices
Technical fixes:
- Strip ANSI codes when calculating text width for alignment
- Use visible length for padding calculations in tables and boxes
- Properly handle colored text in table cells and box lines
Breaking changes: None (backward compatible)
2025-10-19 15:08:30 +00:00
|
|
|
console.log('');
|
|
|
|
logger.highlight('NUPST - UPS Shutdown Tool');
|
|
|
|
logger.dim('Deno-powered UPS monitoring and shutdown automation');
|
|
|
|
console.log('');
|
|
|
|
|
|
|
|
// Usage section
|
|
|
|
logger.log(theme.info('Usage:'));
|
|
|
|
logger.log(` ${theme.command('nupst')} ${theme.dim('<command> [options]')}`);
|
|
|
|
console.log('');
|
|
|
|
|
|
|
|
// Main commands section
|
|
|
|
logger.log(theme.info('Commands:'));
|
|
|
|
this.printCommand('service <subcommand>', 'Manage systemd service');
|
|
|
|
this.printCommand('ups <subcommand>', 'Manage UPS devices');
|
|
|
|
this.printCommand('group <subcommand>', 'Manage UPS groups');
|
2025-10-20 12:32:14 +00:00
|
|
|
this.printCommand('action <subcommand>', 'Manage UPS actions');
|
feat(cli): add beautiful colored output and fix daemon exit bug
Major improvements:
- Created color theme system (ts/colors.ts) with ANSI colors
- Enhanced logger with colors, table formatting, and styled boxes
- Fixed daemon exit bug - now stays running when no UPS configured
- Added config hot-reload with file watcher for live updates
- Beautified CLI help output with color-coded commands
- Added showcase test demonstrating all output features
- Fixed ANSI code handling for perfect table/box alignment
Features:
- Color-coded messages (success=green, error=red, warning=yellow, info=cyan)
- Status symbols (●○◐◯ for running/stopped/starting/unknown)
- Battery level colors (green>60%, yellow 30-60%, red<30%)
- Table formatting with auto-sizing and column alignment
- Styled boxes (success, error, warning, info styles)
- Hot-reload: daemon watches config file and reloads automatically
- Idle mode: daemon stays alive when no devices, checks periodically
Daemon improvements:
- No longer exits when no UPS devices configured
- Enters idle monitoring loop waiting for config
- File watcher detects config changes in real-time
- Auto-reloads and starts monitoring when devices added
- Logs warnings instead of errors for missing devices
Technical fixes:
- Strip ANSI codes when calculating text width for alignment
- Use visible length for padding calculations in tables and boxes
- Properly handle colored text in table cells and box lines
Breaking changes: None (backward compatible)
2025-10-19 15:08:30 +00:00
|
|
|
this.printCommand('config [show]', 'Display current configuration');
|
|
|
|
this.printCommand('update', 'Update NUPST from repository', theme.dim('(requires root)'));
|
|
|
|
this.printCommand('uninstall', 'Completely remove NUPST', theme.dim('(requires root)'));
|
|
|
|
this.printCommand('help, --help, -h', 'Show this help message');
|
|
|
|
this.printCommand('--version, -v', 'Show version information');
|
|
|
|
console.log('');
|
|
|
|
|
|
|
|
// Service subcommands
|
|
|
|
logger.log(theme.info('Service Subcommands:'));
|
|
|
|
this.printCommand('nupst service enable', 'Install and enable systemd service', theme.dim('(requires root)'));
|
|
|
|
this.printCommand('nupst service disable', 'Stop and disable systemd service', theme.dim('(requires root)'));
|
|
|
|
this.printCommand('nupst service start', 'Start the systemd service');
|
|
|
|
this.printCommand('nupst service stop', 'Stop the systemd service');
|
|
|
|
this.printCommand('nupst service restart', 'Restart the systemd service');
|
|
|
|
this.printCommand('nupst service status', 'Show service and UPS status');
|
|
|
|
this.printCommand('nupst service logs', 'Show service logs in real-time');
|
|
|
|
this.printCommand('nupst service start-daemon', 'Start daemon process directly');
|
|
|
|
console.log('');
|
|
|
|
|
|
|
|
// UPS subcommands
|
|
|
|
logger.log(theme.info('UPS Subcommands:'));
|
|
|
|
this.printCommand('nupst ups add', 'Add a new UPS device');
|
|
|
|
this.printCommand('nupst ups edit [id]', 'Edit a UPS device (default if no ID)');
|
|
|
|
this.printCommand('nupst ups remove <id>', 'Remove a UPS device by ID');
|
|
|
|
this.printCommand('nupst ups list (or ls)', 'List all configured UPS devices');
|
|
|
|
this.printCommand('nupst ups test', 'Test UPS connections');
|
|
|
|
console.log('');
|
|
|
|
|
|
|
|
// Group subcommands
|
|
|
|
logger.log(theme.info('Group Subcommands:'));
|
|
|
|
this.printCommand('nupst group add', 'Add a new UPS group');
|
|
|
|
this.printCommand('nupst group edit <id>', 'Edit an existing UPS group');
|
|
|
|
this.printCommand('nupst group remove <id>', 'Remove a UPS group by ID');
|
|
|
|
this.printCommand('nupst group list (or ls)', 'List all UPS groups');
|
|
|
|
console.log('');
|
|
|
|
|
2025-10-20 12:32:14 +00:00
|
|
|
// Action subcommands
|
|
|
|
logger.log(theme.info('Action Subcommands:'));
|
|
|
|
this.printCommand('nupst action add <ups-id>', 'Add a new action to a UPS');
|
|
|
|
this.printCommand('nupst action remove <ups-id> <index>', 'Remove an action by index');
|
|
|
|
this.printCommand('nupst action list [ups-id]', 'List all actions (optionally for specific UPS)');
|
|
|
|
console.log('');
|
|
|
|
|
feat(cli): add beautiful colored output and fix daemon exit bug
Major improvements:
- Created color theme system (ts/colors.ts) with ANSI colors
- Enhanced logger with colors, table formatting, and styled boxes
- Fixed daemon exit bug - now stays running when no UPS configured
- Added config hot-reload with file watcher for live updates
- Beautified CLI help output with color-coded commands
- Added showcase test demonstrating all output features
- Fixed ANSI code handling for perfect table/box alignment
Features:
- Color-coded messages (success=green, error=red, warning=yellow, info=cyan)
- Status symbols (●○◐◯ for running/stopped/starting/unknown)
- Battery level colors (green>60%, yellow 30-60%, red<30%)
- Table formatting with auto-sizing and column alignment
- Styled boxes (success, error, warning, info styles)
- Hot-reload: daemon watches config file and reloads automatically
- Idle mode: daemon stays alive when no devices, checks periodically
Daemon improvements:
- No longer exits when no UPS devices configured
- Enters idle monitoring loop waiting for config
- File watcher detects config changes in real-time
- Auto-reloads and starts monitoring when devices added
- Logs warnings instead of errors for missing devices
Technical fixes:
- Strip ANSI codes when calculating text width for alignment
- Use visible length for padding calculations in tables and boxes
- Properly handle colored text in table cells and box lines
Breaking changes: None (backward compatible)
2025-10-19 15:08:30 +00:00
|
|
|
// Options
|
|
|
|
logger.log(theme.info('Options:'));
|
|
|
|
this.printCommand('--debug, -d', 'Enable debug mode for detailed SNMP logging');
|
|
|
|
logger.dim(' (Example: nupst ups test --debug)');
|
|
|
|
console.log('');
|
|
|
|
|
|
|
|
// Examples
|
|
|
|
logger.log(theme.info('Examples:'));
|
|
|
|
logger.dim(' nupst service enable # Install and start the service');
|
|
|
|
logger.dim(' nupst ups add # Add a new UPS interactively');
|
|
|
|
logger.dim(' nupst group list # Show all configured groups');
|
|
|
|
logger.dim(' nupst config # Display current configuration');
|
|
|
|
console.log('');
|
|
|
|
|
|
|
|
// Note about deprecated commands
|
|
|
|
logger.warn('Note: Old command format (e.g., \'nupst add\') still works but is deprecated.');
|
|
|
|
logger.dim(' Use the new format (e.g., \'nupst ups add\') going forward.');
|
|
|
|
console.log('');
|
|
|
|
}
|
2025-10-18 12:36:35 +00:00
|
|
|
|
feat(cli): add beautiful colored output and fix daemon exit bug
Major improvements:
- Created color theme system (ts/colors.ts) with ANSI colors
- Enhanced logger with colors, table formatting, and styled boxes
- Fixed daemon exit bug - now stays running when no UPS configured
- Added config hot-reload with file watcher for live updates
- Beautified CLI help output with color-coded commands
- Added showcase test demonstrating all output features
- Fixed ANSI code handling for perfect table/box alignment
Features:
- Color-coded messages (success=green, error=red, warning=yellow, info=cyan)
- Status symbols (●○◐◯ for running/stopped/starting/unknown)
- Battery level colors (green>60%, yellow 30-60%, red<30%)
- Table formatting with auto-sizing and column alignment
- Styled boxes (success, error, warning, info styles)
- Hot-reload: daemon watches config file and reloads automatically
- Idle mode: daemon stays alive when no devices, checks periodically
Daemon improvements:
- No longer exits when no UPS devices configured
- Enters idle monitoring loop waiting for config
- File watcher detects config changes in real-time
- Auto-reloads and starts monitoring when devices added
- Logs warnings instead of errors for missing devices
Technical fixes:
- Strip ANSI codes when calculating text width for alignment
- Use visible length for padding calculations in tables and boxes
- Properly handle colored text in table cells and box lines
Breaking changes: None (backward compatible)
2025-10-19 15:08:30 +00:00
|
|
|
/**
|
|
|
|
* Helper to print a command with description
|
|
|
|
*/
|
|
|
|
private printCommand(command: string, description: string, extra?: string): void {
|
|
|
|
const paddedCommand = command.padEnd(30);
|
|
|
|
logger.log(` ${theme.command(paddedCommand)} ${description}${extra ? ' ' + extra : ''}`);
|
2025-10-18 12:36:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Display help message for service commands
|
|
|
|
*/
|
|
|
|
private showServiceHelp(): void {
|
|
|
|
logger.log(`
|
|
|
|
NUPST - Service Management Commands
|
2025-03-26 17:49:50 +00:00
|
|
|
|
2025-03-28 22:12:01 +00:00
|
|
|
Usage:
|
2025-10-18 12:36:35 +00:00
|
|
|
nupst service <subcommand>
|
|
|
|
|
|
|
|
Subcommands:
|
|
|
|
enable - Install and enable the systemd service (requires root)
|
|
|
|
disable - Stop and disable the systemd service (requires root)
|
|
|
|
start - Start the systemd service
|
|
|
|
stop - Stop the systemd service
|
|
|
|
restart - Restart the systemd service
|
|
|
|
status - Show service status and UPS information
|
|
|
|
logs - Show service logs in real-time
|
|
|
|
start-daemon - Start the daemon process directly (for testing)
|
2025-03-26 17:49:50 +00:00
|
|
|
|
2025-03-28 22:12:01 +00:00
|
|
|
Options:
|
2025-10-18 12:36:35 +00:00
|
|
|
--debug, -d - Enable debug mode for detailed logging
|
|
|
|
`);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Display help message for UPS commands
|
|
|
|
*/
|
|
|
|
private showUpsHelp(): void {
|
|
|
|
logger.log(`
|
|
|
|
NUPST - UPS Management Commands
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
nupst ups <subcommand> [arguments]
|
|
|
|
|
|
|
|
Subcommands:
|
|
|
|
add - Add a new UPS device interactively
|
|
|
|
edit [id] - Edit a UPS device (edits default if no ID provided)
|
|
|
|
remove <id> - Remove a UPS device by ID (alias: rm)
|
|
|
|
list - List all configured UPS devices (alias: ls)
|
|
|
|
test - Test connections to all configured UPS devices
|
|
|
|
|
|
|
|
Options:
|
|
|
|
--debug, -d - Enable debug mode for detailed SNMP logging
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
nupst ups add - Add a new UPS device
|
|
|
|
nupst ups edit ups-1 - Edit UPS with ID 'ups-1'
|
|
|
|
nupst ups remove ups-1 - Remove UPS with ID 'ups-1'
|
|
|
|
nupst ups test --debug - Test all UPS connections with debug output
|
2025-03-28 22:12:01 +00:00
|
|
|
`);
|
2025-03-25 09:06:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-03-28 22:12:01 +00:00
|
|
|
* Display help message for group commands
|
2025-03-25 09:06:23 +00:00
|
|
|
*/
|
2025-03-28 22:12:01 +00:00
|
|
|
private showGroupHelp(): void {
|
|
|
|
logger.log(`
|
|
|
|
NUPST - Group Management Commands
|
2025-03-28 16:19:43 +00:00
|
|
|
|
2025-03-28 22:12:01 +00:00
|
|
|
Usage:
|
2025-10-18 12:36:35 +00:00
|
|
|
nupst group <subcommand> [arguments]
|
|
|
|
|
|
|
|
Subcommands:
|
|
|
|
add - Add a new UPS group interactively
|
|
|
|
edit <id> - Edit an existing UPS group
|
|
|
|
remove <id> - Remove a UPS group by ID (alias: rm)
|
|
|
|
list - List all UPS groups (alias: ls)
|
2025-03-26 17:49:50 +00:00
|
|
|
|
2025-03-28 22:12:01 +00:00
|
|
|
Options:
|
2025-10-18 12:36:35 +00:00
|
|
|
--debug, -d - Enable debug mode for detailed logging
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
nupst group add - Create a new group
|
|
|
|
nupst group edit dc-1 - Edit group with ID 'dc-1'
|
|
|
|
nupst group remove dc-1 - Remove group with ID 'dc-1'
|
2025-10-20 12:32:14 +00:00
|
|
|
`);
|
|
|
|
}
|
|
|
|
|
|
|
|
private showActionHelp(): void {
|
|
|
|
logger.log(`
|
|
|
|
NUPST - Action Management Commands
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
nupst action <subcommand> [arguments]
|
|
|
|
|
|
|
|
Subcommands:
|
|
|
|
add <ups-id> - Add a new action to a UPS interactively
|
|
|
|
remove <ups-id> <index> - Remove an action by index (alias: rm, delete)
|
|
|
|
list [ups-id] - List all actions (optionally for specific UPS) (alias: ls)
|
|
|
|
|
|
|
|
Options:
|
|
|
|
--debug, -d - Enable debug mode for detailed logging
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
nupst action list - List actions for all UPS devices
|
|
|
|
nupst action list default - List actions for UPS with ID 'default'
|
|
|
|
nupst action add default - Add a new action to UPS 'default'
|
|
|
|
nupst action remove default 0 - Remove action at index 0 from UPS 'default'
|
2025-03-28 22:12:01 +00:00
|
|
|
`);
|
2025-03-25 09:06:23 +00:00
|
|
|
}
|
2025-10-19 13:14:18 +00:00
|
|
|
}
|