BREAKING CHANGE(core): Add multi-UPS support and group management; update CLI, configuration and documentation to support multiple UPS devices with group modes

This commit is contained in:
2025-03-28 16:19:43 +00:00
parent bd3042de25
commit 0e55f22dad
10 changed files with 2381 additions and 780 deletions

View File

@@ -14,7 +14,7 @@ export class NupstSystemd {
/** Template for the systemd service file */
private readonly serviceTemplate = `[Unit]
Description=Node.js UPS Shutdown Tool
Description=Node.js UPS Shutdown Tool for Multiple UPS Devices
After=network.target
[Service]
@@ -51,7 +51,7 @@ WantedBy=multi-user.target
const boxWidth = 50;
logger.logBoxTitle('Configuration Error', boxWidth);
logger.logBoxLine(`No configuration file found at ${configPath}`);
logger.logBoxLine("Please run 'nupst setup' first to create a configuration.");
logger.logBoxLine("Please run 'nupst add' first to create a UPS configuration.");
logger.logBoxEnd();
throw new Error('Configuration not found');
}
@@ -155,7 +155,7 @@ WantedBy=multi-user.target
}
await this.displayServiceStatus();
await this.displayUpsStatus();
await this.displayAllUpsStatus();
} catch (error) {
logger.error(`Failed to get status: ${error.message}`);
}
@@ -184,35 +184,38 @@ WantedBy=multi-user.target
}
/**
* Display the UPS status
* Display all UPS statuses
* @private
*/
private async displayUpsStatus(): Promise<void> {
private async displayAllUpsStatus(): Promise<void> {
try {
// Explicitly load the configuration first to ensure it's up-to-date
await this.daemon.loadConfig();
const config = this.daemon.getConfig();
const snmp = this.daemon.getNupstSnmp();
// Create a test config with appropriate timeout, similar to the test command
const snmpConfig = {
...config.snmp,
timeout: Math.min(config.snmp.timeout, 10000) // Use at most 10 seconds for status check
};
const boxWidth = 45;
logger.logBoxTitle('Connecting to UPS...', boxWidth);
logger.logBoxLine(`Host: ${config.snmp.host}:${config.snmp.port}`);
logger.logBoxLine(`UPS Model: ${config.snmp.upsModel || 'cyberpower'}`);
logger.logBoxEnd();
const status = await snmp.getUpsStatus(snmpConfig);
logger.logBoxTitle('UPS Status', boxWidth);
logger.logBoxLine(`Power Status: ${status.powerStatus}`);
logger.logBoxLine(`Battery Capacity: ${status.batteryCapacity}%`);
logger.logBoxLine(`Runtime Remaining: ${status.batteryRuntime} minutes`);
logger.logBoxEnd();
// 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`);
// Show status for each UPS
for (const ups of config.upsDevices) {
await this.displaySingleUpsStatus(ups, snmp);
}
} else if (config.snmp) {
// Legacy single UPS configuration
const legacyUps = {
id: 'default',
name: 'Default UPS',
snmp: config.snmp,
thresholds: config.thresholds,
groups: []
};
await this.displaySingleUpsStatus(legacyUps, snmp);
} else {
logger.error('No UPS devices found in configuration');
}
} catch (error) {
const boxWidth = 45;
logger.logBoxTitle('UPS Status', boxWidth);
@@ -220,6 +223,62 @@ WantedBy=multi-user.target
logger.logBoxEnd();
}
}
/**
* Display status of a single UPS
* @param ups UPS configuration
* @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 => {
const group = config.groups?.find(g => 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 = {
...ups.snmp,
timeout: Math.min(ups.snmp.timeout, 10000) // Use at most 10 seconds for status check
};
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`);
// 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 ? '⚠️' : '✓'
}`);
logger.logBoxEnd();
} catch (error) {
logger.logBoxTitle(`UPS Status: ${ups.name}`, boxWidth);
logger.logBoxLine(`Failed to retrieve UPS status: ${error.message}`);
logger.logBoxEnd();
}
}
/**
* Disable and uninstall the systemd service