Compare commits

..

5 Commits

Author SHA1 Message Date
d14ba1dd65 feat(status): display version and update status in nupst status command
All checks were successful
CI / Type Check & Lint (push) Successful in 5s
CI / Build Test (Current Platform) (push) Successful in 4s
Release / build-and-release (push) Successful in 46s
CI / Build All Platforms (push) Successful in 49s
- Add version display at the top of status output
- Check for available updates and notify user
- Show "Up to date" or "Update available" with version
- Display before service and UPS status information
- Improves user awareness of software version and updates

Bumps version to 4.1.4
2025-10-20 01:01:06 +00:00
7d595fa175 chore(release): bump version to 4.1.3
All checks were successful
CI / Type Check & Lint (push) Successful in 5s
CI / Build Test (Current Platform) (push) Successful in 4s
Release / build-and-release (push) Successful in 45s
CI / Build All Platforms (push) Successful in 51s
2025-10-20 00:40:56 +00:00
df417432b0 chore(branding): update description to 'Network UPS Shutdown Tool' 2025-10-20 00:40:52 +00:00
e5f1ebf343 chore(release): bump version to 4.1.2
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 46s
CI / Build All Platforms (push) Successful in 50s
2025-10-20 00:34:03 +00:00
3ff0dd7ac8 fix(cli): resolve process hang and improve output consistency
- Add process.stdin.destroy() after rl.close() in all interactive commands
  to properly release stdin and allow process to exit cleanly
- Replace raw console.log with logger methods throughout CLI handlers
- Convert manual box drawing to logger.logBox() in daemon.ts
- Standardize menu formatting with logger.info() and logger.dim()
- Improve migration output to only show when migrations actually run

Fixes issue where process would not exit after "Setup complete!" message
due to stdin keeping the event loop alive.
2025-10-20 00:32:06 +00:00
10 changed files with 139 additions and 80 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "@serve.zone/nupst", "name": "@serve.zone/nupst",
"version": "4.1.1", "version": "4.1.4",
"exports": "./mod.ts", "exports": "./mod.ts",
"tasks": { "tasks": {
"dev": "deno run --allow-all mod.ts", "dev": "deno run --allow-all mod.ts",

View File

@@ -6,5 +6,5 @@ import denoConfig from '../deno.json' with { type: 'json' };
export const commitinfo = { export const commitinfo = {
name: denoConfig.name, name: denoConfig.name,
version: denoConfig.version, version: denoConfig.version,
description: 'Deno-powered UPS monitoring tool for SNMP-enabled UPS devices', description: 'Network UPS Shutdown Tool (https://nupst.serve.zone)',
}; };

View File

@@ -469,7 +469,7 @@ export class NupstCli {
private showVersion(): void { private showVersion(): void {
const version = this.nupst.getVersion(); const version = this.nupst.getVersion();
logger.log(`NUPST version ${version}`); logger.log(`NUPST version ${version}`);
logger.log('Deno-powered UPS monitoring tool'); logger.log('Network UPS Shutdown Tool (https://nupst.serve.zone)');
} }
/** /**

View File

@@ -192,6 +192,7 @@ export class GroupHandler {
logger.log('\nGroup setup complete!'); logger.log('\nGroup setup complete!');
} finally { } finally {
rl.close(); rl.close();
process.stdin.destroy();
} }
} catch (error) { } catch (error) {
logger.error(`Add group error: ${error instanceof Error ? error.message : String(error)}`); logger.error(`Add group error: ${error instanceof Error ? error.message : String(error)}`);
@@ -309,6 +310,7 @@ export class GroupHandler {
logger.log('\nGroup edit complete!'); logger.log('\nGroup edit complete!');
} finally { } finally {
rl.close(); rl.close();
process.stdin.destroy();
} }
} catch (error) { } catch (error) {
logger.error(`Edit group error: ${error instanceof Error ? error.message : String(error)}`); logger.error(`Edit group error: ${error instanceof Error ? error.message : String(error)}`);
@@ -366,6 +368,7 @@ export class GroupHandler {
}); });
rl.close(); rl.close();
process.stdin.destroy();
if (confirm !== 'y' && confirm !== 'yes') { if (confirm !== 'y' && confirm !== 'yes') {
logger.log('Deletion cancelled.'); logger.log('Deletion cancelled.');

View File

@@ -213,9 +213,11 @@ export class ServiceHandler {
}); });
}; };
console.log('\nNUPST Uninstaller'); logger.log('');
console.log('==============='); logger.highlight('NUPST Uninstaller');
console.log('This will completely remove NUPST from your system.\n'); logger.dim('===============');
logger.log('This will completely remove NUPST from your system.');
logger.log('');
// Ask about removing configuration // Ask about removing configuration
const removeConfig = await prompt( const removeConfig = await prompt(
@@ -251,17 +253,20 @@ export class ServiceHandler {
} }
if (!uninstallScriptPath) { if (!uninstallScriptPath) {
console.error('Could not locate uninstall.sh script. Aborting uninstall.'); logger.error('Could not locate uninstall.sh script. Aborting uninstall.');
rl.close(); rl.close();
process.stdin.destroy();
process.exit(1); process.exit(1);
} }
} }
// Close readline before executing script // Close readline before executing script
rl.close(); rl.close();
process.stdin.destroy();
// Execute uninstall.sh with the appropriate option // Execute uninstall.sh with the appropriate option
console.log(`\nRunning uninstaller from ${uninstallScriptPath}...`); logger.log('');
logger.log(`Running uninstaller from ${uninstallScriptPath}...`);
// Pass the configuration removal option as an environment variable // Pass the configuration removal option as an environment variable
const env = { const env = {
@@ -277,7 +282,7 @@ export class ServiceHandler {
stdio: 'inherit', // Show output in the terminal stdio: 'inherit', // Show output in the terminal
}); });
} catch (error) { } catch (error) {
console.error(`Uninstall failed: ${error instanceof Error ? error.message : String(error)}`); logger.error(`Uninstall failed: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1); process.exit(1);
} }
} }

View File

@@ -47,6 +47,7 @@ export class UpsHandler {
await this.runAddProcess(prompt); await this.runAddProcess(prompt);
} finally { } finally {
rl.close(); rl.close();
process.stdin.destroy();
} }
} catch (error) { } catch (error) {
logger.error(`Add UPS error: ${error instanceof Error ? error.message : String(error)}`); logger.error(`Add UPS error: ${error instanceof Error ? error.message : String(error)}`);
@@ -178,6 +179,7 @@ export class UpsHandler {
await this.runEditProcess(upsId, prompt); await this.runEditProcess(upsId, prompt);
} finally { } finally {
rl.close(); rl.close();
process.stdin.destroy();
} }
} catch (error) { } catch (error) {
logger.error(`Edit UPS error: ${error instanceof Error ? error.message : String(error)}`); logger.error(`Edit UPS error: ${error instanceof Error ? error.message : String(error)}`);
@@ -344,6 +346,7 @@ export class UpsHandler {
}); });
rl.close(); rl.close();
process.stdin.destroy();
if (confirm !== 'y' && confirm !== 'yes') { if (confirm !== 'y' && confirm !== 'yes') {
logger.log('Deletion cancelled.'); logger.log('Deletion cancelled.');
@@ -667,10 +670,11 @@ export class UpsHandler {
// SNMP Version // SNMP Version
const defaultVersion = snmpConfig.version || 1; const defaultVersion = snmpConfig.version || 1;
console.log('\nSNMP Version:'); logger.log('');
console.log(' 1) SNMPv1'); logger.info('SNMP Version:');
console.log(' 2) SNMPv2c'); logger.dim(' 1) SNMPv1');
console.log(' 3) SNMPv3 (with security features)'); logger.dim(' 2) SNMPv2c');
logger.dim(' 3) SNMPv3 (with security features)');
const versionInput = await prompt(`Select SNMP version [${defaultVersion}]: `); const versionInput = await prompt(`Select SNMP version [${defaultVersion}]: `);
const version = parseInt(versionInput, 10); const version = parseInt(versionInput, 10);
snmpConfig.version = versionInput.trim() && (version === 1 || version === 2 || version === 3) snmpConfig.version = versionInput.trim() && (version === 1 || version === 2 || version === 3)
@@ -697,13 +701,15 @@ export class UpsHandler {
snmpConfig: any, snmpConfig: any,
prompt: (question: string) => Promise<string>, prompt: (question: string) => Promise<string>,
): Promise<void> { ): Promise<void> {
console.log('\nSNMPv3 Security Settings:'); logger.log('');
logger.info('SNMPv3 Security Settings:');
// Security Level // Security Level
console.log('\nSecurity Level:'); logger.log('');
console.log(' 1) noAuthNoPriv (No Authentication, No Privacy)'); logger.info('Security Level:');
console.log(' 2) authNoPriv (Authentication, No Privacy)'); logger.dim(' 1) noAuthNoPriv (No Authentication, No Privacy)');
console.log(' 3) authPriv (Authentication and Privacy)'); logger.dim(' 2) authNoPriv (Authentication, No Privacy)');
logger.dim(' 3) authPriv (Authentication and Privacy)');
const defaultSecLevel = snmpConfig.securityLevel const defaultSecLevel = snmpConfig.securityLevel
? snmpConfig.securityLevel === 'noAuthNoPriv' ? snmpConfig.securityLevel === 'noAuthNoPriv'
? 1 ? 1
@@ -752,8 +758,9 @@ export class UpsHandler {
// Allow customizing the timeout value // Allow customizing the timeout value
const defaultTimeout = snmpConfig.timeout / 1000; // Convert from ms to seconds for display const defaultTimeout = snmpConfig.timeout / 1000; // Convert from ms to seconds for display
console.log( logger.log('');
'\nSNMPv3 operations with authentication and privacy may require longer timeouts.', logger.info(
'SNMPv3 operations with authentication and privacy may require longer timeouts.',
); );
const timeoutInput = await prompt(`SNMP Timeout in seconds [${defaultTimeout}]: `); const timeoutInput = await prompt(`SNMP Timeout in seconds [${defaultTimeout}]: `);
const timeout = parseInt(timeoutInput, 10); const timeout = parseInt(timeoutInput, 10);
@@ -773,9 +780,10 @@ export class UpsHandler {
prompt: (question: string) => Promise<string>, prompt: (question: string) => Promise<string>,
): Promise<void> { ): Promise<void> {
// Authentication protocol // Authentication protocol
console.log('\nAuthentication Protocol:'); logger.log('');
console.log(' 1) MD5'); logger.info('Authentication Protocol:');
console.log(' 2) SHA'); logger.dim(' 1) MD5');
logger.dim(' 2) SHA');
const defaultAuthProtocol = snmpConfig.authProtocol === 'SHA' ? 2 : 1; const defaultAuthProtocol = snmpConfig.authProtocol === 'SHA' ? 2 : 1;
const authProtocolInput = await prompt( const authProtocolInput = await prompt(
`Select Authentication Protocol [${defaultAuthProtocol}]: `, `Select Authentication Protocol [${defaultAuthProtocol}]: `,
@@ -799,9 +807,10 @@ export class UpsHandler {
prompt: (question: string) => Promise<string>, prompt: (question: string) => Promise<string>,
): Promise<void> { ): Promise<void> {
// Privacy protocol // Privacy protocol
console.log('\nPrivacy Protocol:'); logger.log('');
console.log(' 1) DES'); logger.info('Privacy Protocol:');
console.log(' 2) AES'); logger.dim(' 1) DES');
logger.dim(' 2) AES');
const defaultPrivProtocol = snmpConfig.privProtocol === 'AES' ? 2 : 1; const defaultPrivProtocol = snmpConfig.privProtocol === 'AES' ? 2 : 1;
const privProtocolInput = await prompt(`Select Privacy Protocol [${defaultPrivProtocol}]: `); const privProtocolInput = await prompt(`Select Privacy Protocol [${defaultPrivProtocol}]: `);
const privProtocol = parseInt(privProtocolInput, 10) || defaultPrivProtocol; const privProtocol = parseInt(privProtocolInput, 10) || defaultPrivProtocol;
@@ -822,7 +831,8 @@ export class UpsHandler {
thresholds: any, thresholds: any,
prompt: (question: string) => Promise<string>, prompt: (question: string) => Promise<string>,
): Promise<void> { ): Promise<void> {
console.log('\nShutdown Thresholds:'); logger.log('');
logger.info('Shutdown Thresholds:');
// Battery threshold // Battery threshold
const defaultBatteryThreshold = thresholds.battery || 60; const defaultBatteryThreshold = thresholds.battery || 60;
@@ -854,13 +864,14 @@ export class UpsHandler {
snmpConfig: any, snmpConfig: any,
prompt: (question: string) => Promise<string>, prompt: (question: string) => Promise<string>,
): Promise<void> { ): Promise<void> {
console.log('\nUPS Model Selection:'); logger.log('');
console.log(' 1) CyberPower'); logger.info('UPS Model Selection:');
console.log(' 2) APC'); logger.dim(' 1) CyberPower');
console.log(' 3) Eaton'); logger.dim(' 2) APC');
console.log(' 4) TrippLite'); logger.dim(' 3) Eaton');
console.log(' 5) Liebert/Vertiv'); logger.dim(' 4) TrippLite');
console.log(' 6) Custom (Advanced)'); logger.dim(' 5) Liebert/Vertiv');
logger.dim(' 6) Custom (Advanced)');
const defaultModelValue = snmpConfig.upsModel === 'cyberpower' const defaultModelValue = snmpConfig.upsModel === 'cyberpower'
? 1 ? 1
@@ -891,8 +902,9 @@ export class UpsHandler {
snmpConfig.upsModel = 'liebert'; snmpConfig.upsModel = 'liebert';
} else if (modelValue === 6) { } else if (modelValue === 6) {
snmpConfig.upsModel = 'custom'; snmpConfig.upsModel = 'custom';
console.log('\nEnter custom OIDs for your UPS:'); logger.log('');
console.log('(Leave blank to use standard RFC 1628 OIDs as fallback)'); logger.info('Enter custom OIDs for your UPS:');
logger.dim('(Leave blank to use standard RFC 1628 OIDs as fallback)');
// Custom OIDs // Custom OIDs
const powerStatusOID = await prompt('Power Status OID: '); const powerStatusOID = await prompt('Power Status OID: ');

View File

@@ -207,11 +207,9 @@ export class NupstDaemon {
fs.writeFileSync(this.CONFIG_PATH, JSON.stringify(configToSave, null, 2)); fs.writeFileSync(this.CONFIG_PATH, JSON.stringify(configToSave, null, 2));
this.config = configToSave; this.config = configToSave;
console.log('┌─ Configuration Saved ─────────────────────┐'); logger.logBox('Configuration Saved', [`Location: ${this.CONFIG_PATH}`], 45, 'success');
console.log(`│ Location: ${this.CONFIG_PATH}`);
console.log('└──────────────────────────────────────────┘');
} catch (error) { } catch (error) {
console.error('Error saving configuration:', error); logger.error(`Error saving configuration: ${error}`);
} }
} }
@@ -219,10 +217,7 @@ export class NupstDaemon {
* Helper method to log configuration errors consistently * Helper method to log configuration errors consistently
*/ */
private logConfigError(message: string): void { private logConfigError(message: string): void {
console.error('┌─ Configuration Error ─────────────────────┐'); logger.logBox('Configuration Error', [message, "Please run 'nupst setup' first to create a configuration."], 45, 'error');
console.error(`${message}`);
console.error("│ Please run 'nupst setup' first to create a configuration.");
console.error('└───────────────────────────────────────────┘');
} }
/** /**

View File

@@ -34,12 +34,14 @@ export class MigrationRunner {
let currentConfig = config; let currentConfig = config;
let anyMigrationsRan = false; let anyMigrationsRan = false;
logger.dim('Checking for required config migrations...');
for (const migration of this.migrations) { for (const migration of this.migrations) {
const shouldRun = await migration.shouldRun(currentConfig); const shouldRun = await migration.shouldRun(currentConfig);
if (shouldRun) { if (shouldRun) {
// Only show "checking" message when we actually need to migrate
if (!anyMigrationsRan) {
logger.dim('Checking for required config migrations...');
}
logger.info(`Running ${migration.getName()}...`); logger.info(`Running ${migration.getName()}...`);
currentConfig = await migration.migrate(currentConfig); currentConfig = await migration.migrate(currentConfig);
anyMigrationsRan = true; anyMigrationsRan = true;
@@ -49,7 +51,7 @@ export class MigrationRunner {
if (anyMigrationsRan) { if (anyMigrationsRan) {
logger.success('Configuration migrations complete'); logger.success('Configuration migrations complete');
} else { } else {
logger.dim('No migrations needed'); logger.success('config format ok');
} }
return { return {

View File

@@ -50,11 +50,11 @@ WantedBy=multi-user.target
try { try {
await fs.access(configPath); await fs.access(configPath);
} catch (error) { } catch (error) {
console.log(''); logger.log('');
console.log(`${symbols.error} ${theme.error('No configuration found')}`); logger.error('No configuration found');
console.log(` ${theme.dim('Config file:')} ${configPath}`); logger.log(` ${theme.dim('Config file:')} ${configPath}`);
console.log(` ${theme.dim('Run')} ${theme.command('nupst ups add')} ${theme.dim('to create a configuration')}`); logger.log(` ${theme.dim('Run')} ${theme.command('nupst ups add')} ${theme.dim('to create a configuration')}`);
console.log(''); logger.log('');
throw new Error('Configuration not found'); throw new Error('Configuration not found');
} }
} }
@@ -134,6 +134,45 @@ WantedBy=multi-user.target
* Get status of the systemd service and UPS * Get status of the systemd service and UPS
* @param debugMode Whether to enable debug mode for SNMP * @param debugMode Whether to enable debug mode for SNMP
*/ */
/**
* Display version information and update status
* @private
*/
private async displayVersionInfo(): Promise<void> {
try {
const nupst = this.daemon.getNupstSnmp().getNupst();
const version = nupst.getVersion();
// Check for updates
const updateAvailable = await nupst.checkForUpdates();
// Display version info
if (updateAvailable) {
const updateStatus = nupst.getUpdateStatus();
logger.log('');
logger.log(
`${theme.dim('NUPST')} ${theme.dim('v' + version)} ${symbols.warning} ${theme.statusWarning(`Update available: v${updateStatus.latestVersion}`)}`,
);
logger.log(` ${theme.dim('Run')} ${theme.command('sudo nupst update')} ${theme.dim('to upgrade')}`);
} else {
logger.log('');
logger.log(
`${theme.dim('NUPST')} ${theme.dim('v' + version)} ${symbols.success} ${theme.success('Up to date')}`,
);
}
} catch (error) {
// If version check fails, show at least the current version
try {
const nupst = this.daemon.getNupstSnmp().getNupst();
const version = nupst.getVersion();
logger.log('');
logger.log(`${theme.dim('NUPST')} ${theme.dim('v' + version)}`);
} catch (_innerError) {
// Silently fail if we can't even get the version
}
}
}
public async getStatus(debugMode: boolean = false): Promise<void> { public async getStatus(debugMode: boolean = false): Promise<void> {
try { try {
// Enable debug mode if requested // Enable debug mode if requested
@@ -144,7 +183,10 @@ WantedBy=multi-user.target
this.daemon.getNupstSnmp().enableDebug(); this.daemon.getNupstSnmp().enableDebug();
} }
// Check if config exists first // Display version and update status first
await this.displayVersionInfo();
// Check if config exists
try { try {
await this.checkConfigExists(); await this.checkConfigExists();
} catch (error) { } catch (error) {
@@ -192,11 +234,11 @@ WantedBy=multi-user.target
} }
// Display beautiful status // Display beautiful status
console.log(''); logger.log('');
if (isActive) { if (isActive) {
console.log(`${symbols.running} ${theme.success('Service:')} ${theme.statusActive('active (running)')}`); logger.log(`${symbols.running} ${theme.success('Service:')} ${theme.statusActive('active (running)')}`);
} else { } else {
console.log(`${symbols.stopped} ${theme.dim('Service:')} ${theme.statusInactive('inactive')}`); logger.log(`${symbols.stopped} ${theme.dim('Service:')} ${theme.statusInactive('inactive')}`);
} }
if (pid || memory || cpu) { if (pid || memory || cpu) {
@@ -204,14 +246,14 @@ WantedBy=multi-user.target
if (pid) details.push(`PID: ${theme.dim(pid)}`); if (pid) details.push(`PID: ${theme.dim(pid)}`);
if (memory) details.push(`Memory: ${theme.dim(memory)}`); if (memory) details.push(`Memory: ${theme.dim(memory)}`);
if (cpu) details.push(`CPU: ${theme.dim(cpu)}`); if (cpu) details.push(`CPU: ${theme.dim(cpu)}`);
console.log(` ${details.join(' ')}`); logger.log(` ${details.join(' ')}`);
} }
console.log(''); logger.log('');
} catch (error) { } catch (error) {
console.log(''); logger.log('');
console.log(`${symbols.stopped} ${theme.dim('Service:')} ${theme.statusInactive('not installed')}`); logger.log(`${symbols.stopped} ${theme.dim('Service:')} ${theme.statusInactive('not installed')}`);
console.log(''); logger.log('');
} }
} }
@@ -228,7 +270,7 @@ WantedBy=multi-user.target
// Check if we have the new multi-UPS config format // Check if we have the new multi-UPS config format
if (config.upsDevices && Array.isArray(config.upsDevices) && config.upsDevices.length > 0) { if (config.upsDevices && Array.isArray(config.upsDevices) && config.upsDevices.length > 0) {
console.log(theme.info(`UPS Devices (${config.upsDevices.length}):`)); logger.info(`UPS Devices (${config.upsDevices.length}):`);
// Show status for each UPS // Show status for each UPS
for (const ups of config.upsDevices) { for (const ups of config.upsDevices) {
@@ -236,7 +278,7 @@ WantedBy=multi-user.target
} }
} else if (config.snmp) { } else if (config.snmp) {
// Legacy single UPS configuration // Legacy single UPS configuration
console.log(theme.info('UPS Devices (1):')); logger.info('UPS Devices (1):');
const legacyUps = { const legacyUps = {
id: 'default', id: 'default',
name: 'Default UPS', name: 'Default UPS',
@@ -247,16 +289,16 @@ WantedBy=multi-user.target
await this.displaySingleUpsStatus(legacyUps, snmp); await this.displaySingleUpsStatus(legacyUps, snmp);
} else { } else {
console.log(''); logger.log('');
console.log(`${symbols.warning} ${theme.warning('No UPS devices configured')}`); logger.warn('No UPS devices configured');
console.log(` ${theme.dim('Run')} ${theme.command('nupst ups add')} ${theme.dim('to add a device')}`); logger.log(` ${theme.dim('Run')} ${theme.command('nupst ups add')} ${theme.dim('to add a device')}`);
console.log(''); logger.log('');
} }
} catch (error) { } catch (error) {
console.log(''); logger.log('');
console.log(`${symbols.error} ${theme.error('Failed to retrieve UPS status')}`); logger.error('Failed to retrieve UPS status');
console.log(` ${theme.dim(error instanceof Error ? error.message : String(error))}`); logger.log(` ${theme.dim(error instanceof Error ? error.message : String(error))}`);
console.log(''); logger.log('');
} }
} }
@@ -284,15 +326,15 @@ WantedBy=multi-user.target
} }
// Display UPS name and power status // Display UPS name and power status
console.log(` ${statusSymbol} ${theme.highlight(ups.name)} - ${formatPowerStatus(status.powerStatus)}`); logger.log(` ${statusSymbol} ${theme.highlight(ups.name)} - ${formatPowerStatus(status.powerStatus)}`);
// Display battery with color coding // Display battery with color coding
const batteryColor = getBatteryColor(status.batteryCapacity); const batteryColor = getBatteryColor(status.batteryCapacity);
const batterySymbol = status.batteryCapacity >= ups.thresholds.battery ? symbols.success : symbols.warning; 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')}`); logger.log(` Battery: ${batteryColor(status.batteryCapacity + '%')} ${batterySymbol} Runtime: ${getRuntimeColor(status.batteryRuntime)(status.batteryRuntime + ' min')}`);
// Display host info // Display host info
console.log(` ${theme.dim(`Host: ${ups.snmp.host}:${ups.snmp.port}`)}`); logger.log(` ${theme.dim(`Host: ${ups.snmp.host}:${ups.snmp.port}`)}`);
// Display groups if any // Display groups if any
if (ups.groups && ups.groups.length > 0) { if (ups.groups && ups.groups.length > 0) {
@@ -301,17 +343,17 @@ WantedBy=multi-user.target
const group = config.groups?.find((g: { id: string }) => g.id === groupId); const group = config.groups?.find((g: { id: string }) => g.id === groupId);
return group ? group.name : groupId; return group ? group.name : groupId;
}); });
console.log(` ${theme.dim(`Groups: ${groupNames.join(', ')}`)}`); logger.log(` ${theme.dim(`Groups: ${groupNames.join(', ')}`)}`);
} }
console.log(''); logger.log('');
} catch (error) { } catch (error) {
// Display error for this UPS // Display error for this UPS
console.log(` ${symbols.error} ${theme.highlight(ups.name)} - ${theme.error('Connection failed')}`); logger.log(` ${symbols.error} ${theme.highlight(ups.name)} - ${theme.error('Connection failed')}`);
console.log(` ${theme.dim(error instanceof Error ? error.message : String(error))}`); logger.log(` ${theme.dim(error instanceof Error ? error.message : String(error))}`);
console.log(` ${theme.dim(`Host: ${ups.snmp.host}:${ups.snmp.port}`)}`); logger.log(` ${theme.dim(`Host: ${ups.snmp.host}:${ups.snmp.port}`)}`);
console.log(''); logger.log('');
} }
} }