986 lines
34 KiB
TypeScript
986 lines
34 KiB
TypeScript
import { execSync } from 'child_process';
|
|
import { Nupst } from '../nupst.js';
|
|
import { logger } from '../logger.js';
|
|
import * as helpers from '../helpers/index.js';
|
|
|
|
/**
|
|
* Class for handling UPS-related CLI commands
|
|
* Provides interface for managing UPS devices
|
|
*/
|
|
export class UpsHandler {
|
|
private readonly nupst: Nupst;
|
|
|
|
/**
|
|
* Create a new UPS handler
|
|
* @param nupst Reference to the main Nupst instance
|
|
*/
|
|
constructor(nupst: Nupst) {
|
|
this.nupst = nupst;
|
|
}
|
|
|
|
/**
|
|
* Add a new UPS configuration
|
|
*/
|
|
public async add(): Promise<void> {
|
|
try {
|
|
// Import readline module for user input
|
|
const readline = await import('readline');
|
|
|
|
const rl = readline.createInterface({
|
|
input: process.stdin,
|
|
output: process.stdout,
|
|
});
|
|
|
|
// Helper function to prompt for input
|
|
const prompt = (question: string): Promise<string> => {
|
|
return new Promise((resolve) => {
|
|
rl.question(question, (answer: string) => {
|
|
resolve(answer);
|
|
});
|
|
});
|
|
};
|
|
|
|
try {
|
|
await this.runAddProcess(prompt);
|
|
} finally {
|
|
rl.close();
|
|
}
|
|
} catch (error) {
|
|
logger.error(`Add UPS error: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run the interactive process to add a new UPS
|
|
* @param prompt Function to prompt for user input
|
|
*/
|
|
public async runAddProcess(prompt: (question: string) => Promise<string>): Promise<void> {
|
|
logger.log('\nNUPST Add UPS');
|
|
logger.log('=============\n');
|
|
logger.log('This will guide you through configuring a new UPS.\n');
|
|
|
|
// Try to load existing config if available
|
|
let config;
|
|
try {
|
|
await this.nupst.getDaemon().loadConfig();
|
|
config = this.nupst.getDaemon().getConfig();
|
|
|
|
// Convert old format to new format if needed
|
|
if (!config.upsDevices) {
|
|
// Initialize with the current config as the first UPS
|
|
config = {
|
|
checkInterval: config.checkInterval,
|
|
upsDevices: [{
|
|
id: 'default',
|
|
name: 'Default UPS',
|
|
snmp: config.snmp,
|
|
thresholds: config.thresholds,
|
|
groups: []
|
|
}],
|
|
groups: []
|
|
};
|
|
logger.log('Converting existing configuration to multi-UPS format.');
|
|
}
|
|
} catch (error) {
|
|
// If config doesn't exist, initialize with empty config
|
|
config = {
|
|
checkInterval: 30000, // Default check interval
|
|
upsDevices: [],
|
|
groups: []
|
|
};
|
|
logger.log('No existing configuration found. Creating a new configuration.');
|
|
}
|
|
|
|
// Get UPS ID and name
|
|
const upsId = helpers.shortId();
|
|
const name = await prompt('UPS Name: ');
|
|
|
|
// Create a new UPS configuration object with defaults
|
|
const newUps = {
|
|
id: upsId,
|
|
name: name || `UPS-${upsId}`,
|
|
snmp: {
|
|
host: '127.0.0.1',
|
|
port: 161,
|
|
community: 'public',
|
|
version: 1,
|
|
timeout: 5000,
|
|
upsModel: 'cyberpower'
|
|
},
|
|
thresholds: {
|
|
battery: 60,
|
|
runtime: 20
|
|
},
|
|
groups: []
|
|
};
|
|
|
|
// Gather SNMP settings
|
|
await this.gatherSnmpSettings(newUps.snmp, prompt);
|
|
|
|
// Gather threshold settings
|
|
await this.gatherThresholdSettings(newUps.thresholds, prompt);
|
|
|
|
// Gather UPS model settings
|
|
await this.gatherUpsModelSettings(newUps.snmp, prompt);
|
|
|
|
// Get access to GroupHandler for group assignments
|
|
const groupHandler = this.nupst.getGroupHandler();
|
|
|
|
// Assign to groups if any exist
|
|
if (config.groups && config.groups.length > 0) {
|
|
await groupHandler.assignUpsToGroups(newUps, config.groups, prompt);
|
|
}
|
|
|
|
// Add the new UPS to the config
|
|
config.upsDevices.push(newUps);
|
|
|
|
// Save the configuration
|
|
await this.nupst.getDaemon().saveConfig(config);
|
|
|
|
this.displayUpsConfigSummary(newUps);
|
|
|
|
// Test the connection if requested
|
|
await this.optionallyTestConnection(newUps.snmp, prompt);
|
|
|
|
// Check if service is running and restart it if needed
|
|
await this.restartServiceIfRunning();
|
|
|
|
logger.log('\nSetup complete!');
|
|
}
|
|
|
|
/**
|
|
* Edit an existing UPS configuration
|
|
* @param upsId ID of the UPS to edit (undefined for default UPS)
|
|
*/
|
|
public async edit(upsId?: string): Promise<void> {
|
|
try {
|
|
// Import readline module for user input
|
|
const readline = await import('readline');
|
|
|
|
const rl = readline.createInterface({
|
|
input: process.stdin,
|
|
output: process.stdout,
|
|
});
|
|
|
|
// Helper function to prompt for input
|
|
const prompt = (question: string): Promise<string> => {
|
|
return new Promise((resolve) => {
|
|
rl.question(question, (answer: string) => {
|
|
resolve(answer);
|
|
});
|
|
});
|
|
};
|
|
|
|
try {
|
|
await this.runEditProcess(upsId, prompt);
|
|
} finally {
|
|
rl.close();
|
|
}
|
|
} catch (error) {
|
|
logger.error(`Edit UPS error: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run the interactive process to edit a UPS
|
|
* @param upsId ID of the UPS to edit (undefined for default UPS)
|
|
* @param prompt Function to prompt for user input
|
|
*/
|
|
public async runEditProcess(upsId: string | undefined, prompt: (question: string) => Promise<string>): Promise<void> {
|
|
logger.log('\nNUPST Edit UPS');
|
|
logger.log('=============\n');
|
|
|
|
// Try to load existing config
|
|
try {
|
|
await this.nupst.getDaemon().loadConfig();
|
|
} catch (error) {
|
|
if (!upsId) {
|
|
// For default UPS (no ID specified), run setup if no config exists
|
|
logger.log('No existing configuration found. Running setup for new UPS.');
|
|
await this.runAddProcess(prompt);
|
|
return;
|
|
} else {
|
|
// For specific UPS ID, error if config doesn't exist
|
|
logger.error('No configuration found. Please run "nupst setup" first.');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Get the config
|
|
const config = this.nupst.getDaemon().getConfig();
|
|
|
|
// Convert old format to new format if needed
|
|
if (!config.upsDevices) {
|
|
// Initialize with the current config as the first UPS
|
|
config.upsDevices = [{
|
|
id: 'default',
|
|
name: 'Default UPS',
|
|
snmp: config.snmp,
|
|
thresholds: config.thresholds,
|
|
groups: []
|
|
}];
|
|
config.groups = [];
|
|
logger.log('Converting existing configuration to multi-UPS format.');
|
|
}
|
|
|
|
// Find the UPS to edit
|
|
let upsToEdit;
|
|
if (upsId) {
|
|
// Find specific UPS by ID
|
|
upsToEdit = config.upsDevices.find(ups => ups.id === upsId);
|
|
if (!upsToEdit) {
|
|
logger.error(`UPS with ID "${upsId}" not found.`);
|
|
return;
|
|
}
|
|
logger.log(`Editing UPS: ${upsToEdit.name} (${upsToEdit.id})\n`);
|
|
} else {
|
|
// For backward compatibility, edit the first UPS if no ID specified
|
|
if (config.upsDevices.length === 0) {
|
|
logger.error('No UPS devices configured. Please run "nupst add" to add a UPS.');
|
|
return;
|
|
}
|
|
upsToEdit = config.upsDevices[0];
|
|
logger.log(`Editing default UPS: ${upsToEdit.name} (${upsToEdit.id})\n`);
|
|
}
|
|
|
|
// Allow editing UPS name
|
|
const newName = await prompt(`UPS Name [${upsToEdit.name}]: `);
|
|
if (newName.trim()) {
|
|
upsToEdit.name = newName;
|
|
}
|
|
|
|
// Edit SNMP settings
|
|
await this.gatherSnmpSettings(upsToEdit.snmp, prompt);
|
|
|
|
// Edit threshold settings
|
|
await this.gatherThresholdSettings(upsToEdit.thresholds, prompt);
|
|
|
|
// Edit UPS model settings
|
|
await this.gatherUpsModelSettings(upsToEdit.snmp, prompt);
|
|
|
|
// Get access to GroupHandler for group assignments
|
|
const groupHandler = this.nupst.getGroupHandler();
|
|
|
|
// Edit group assignments
|
|
if (config.groups && config.groups.length > 0) {
|
|
await groupHandler.assignUpsToGroups(upsToEdit, config.groups, prompt);
|
|
}
|
|
|
|
// Save the configuration
|
|
await this.nupst.getDaemon().saveConfig(config);
|
|
|
|
this.displayUpsConfigSummary(upsToEdit);
|
|
|
|
// Test the connection if requested
|
|
await this.optionallyTestConnection(upsToEdit.snmp, prompt);
|
|
|
|
// Check if service is running and restart it if needed
|
|
await this.restartServiceIfRunning();
|
|
|
|
logger.log('\nEdit complete!');
|
|
}
|
|
|
|
/**
|
|
* Delete a UPS by ID
|
|
* @param upsId ID of the UPS to delete
|
|
*/
|
|
public async delete(upsId: string): Promise<void> {
|
|
try {
|
|
// Try to load configuration
|
|
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();
|
|
return;
|
|
}
|
|
|
|
// Get current configuration
|
|
const config = this.nupst.getDaemon().getConfig();
|
|
|
|
// Check if multi-UPS config
|
|
if (!config.upsDevices || !Array.isArray(config.upsDevices)) {
|
|
logger.error('Legacy single-UPS configuration detected. Cannot delete UPS.');
|
|
logger.log('Use "nupst add" to migrate to multi-UPS configuration format first.');
|
|
return;
|
|
}
|
|
|
|
// Find the UPS to delete
|
|
const upsIndex = config.upsDevices.findIndex(ups => ups.id === upsId);
|
|
if (upsIndex === -1) {
|
|
logger.error(`UPS with ID "${upsId}" not found.`);
|
|
return;
|
|
}
|
|
|
|
const upsToDelete = config.upsDevices[upsIndex];
|
|
|
|
// Get confirmation before deleting
|
|
const readline = await import('readline');
|
|
const rl = readline.createInterface({
|
|
input: process.stdin,
|
|
output: process.stdout,
|
|
});
|
|
|
|
const confirm = await new Promise<string>(resolve => {
|
|
rl.question(`Are you sure you want to delete UPS "${upsToDelete.name}" (${upsId})? [y/N]: `, answer => {
|
|
resolve(answer.toLowerCase());
|
|
});
|
|
});
|
|
|
|
rl.close();
|
|
|
|
if (confirm !== 'y' && confirm !== 'yes') {
|
|
logger.log('Deletion cancelled.');
|
|
return;
|
|
}
|
|
|
|
// Remove the UPS from the array
|
|
config.upsDevices.splice(upsIndex, 1);
|
|
|
|
// Save the configuration
|
|
await this.nupst.getDaemon().saveConfig(config);
|
|
|
|
logger.log(`UPS "${upsToDelete.name}" (${upsId}) has been deleted.`);
|
|
|
|
// Check if service is running and restart it if needed
|
|
await this.restartServiceIfRunning();
|
|
} catch (error) {
|
|
logger.error(`Failed to delete UPS: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List all configured UPS devices
|
|
*/
|
|
public async list(): Promise<void> {
|
|
try {
|
|
// Try to load configuration
|
|
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();
|
|
return;
|
|
}
|
|
|
|
// Get current configuration
|
|
const config = this.nupst.getDaemon().getConfig();
|
|
|
|
// 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.');
|
|
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();
|
|
return;
|
|
}
|
|
|
|
// Display UPS list
|
|
const boxWidth = 60;
|
|
logger.logBoxTitle('UPS Devices', boxWidth);
|
|
|
|
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.logBoxEnd();
|
|
} catch (error) {
|
|
logger.error(`Failed to list UPS devices: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test the current configuration by connecting to the UPS
|
|
* @param debugMode Whether to enable debug mode
|
|
*/
|
|
public async test(debugMode: boolean = false): Promise<void> {
|
|
try {
|
|
// Debug mode is now handled in parseAndExecute
|
|
if (debugMode) {
|
|
const boxWidth = 45;
|
|
logger.logBoxTitle('Debug Mode', boxWidth);
|
|
logger.logBoxLine('SNMP debugging enabled - detailed logs will be shown');
|
|
logger.logBoxEnd();
|
|
}
|
|
|
|
// Try to load the configuration
|
|
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();
|
|
return;
|
|
}
|
|
|
|
// Get current configuration
|
|
const config = this.nupst.getDaemon().getConfig();
|
|
|
|
// Handle new multi-UPS configuration format
|
|
if (config.upsDevices && config.upsDevices.length > 0) {
|
|
logger.log(`Found ${config.upsDevices.length} UPS devices in configuration.`);
|
|
|
|
for (let i = 0; i < config.upsDevices.length; i++) {
|
|
const ups = config.upsDevices[i];
|
|
logger.log(`\nTesting UPS: ${ups.name} (${ups.id})`);
|
|
this.displayTestConfig(ups);
|
|
await this.testConnection(ups);
|
|
}
|
|
} else {
|
|
// Legacy configuration format
|
|
this.displayTestConfig(config);
|
|
await this.testConnection(config);
|
|
}
|
|
} catch (error) {
|
|
logger.error(`Test failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display the configuration for testing
|
|
* @param config Current configuration or individual UPS configuration
|
|
*/
|
|
private displayTestConfig(config: any): void {
|
|
// Check if this is a UPS device or full configuration
|
|
const isUpsConfig = config.snmp && config.thresholds;
|
|
const snmpConfig = isUpsConfig ? config.snmp : config.snmp || {};
|
|
const thresholds = isUpsConfig ? config.thresholds : config.thresholds || {};
|
|
const checkInterval = config.checkInterval || 30000;
|
|
|
|
// Get UPS name and ID if available
|
|
const upsName = config.name ? config.name : 'Default UPS';
|
|
const upsId = config.id ? config.id : 'default';
|
|
|
|
const boxWidth = 45;
|
|
logger.logBoxTitle(`Testing Configuration: ${upsName}`, boxWidth);
|
|
logger.logBoxLine(`UPS ID: ${upsId}`);
|
|
logger.logBoxLine('SNMP Settings:');
|
|
logger.logBoxLine(` Host: ${snmpConfig.host}`);
|
|
logger.logBoxLine(` Port: ${snmpConfig.port}`);
|
|
logger.logBoxLine(` Version: ${snmpConfig.version}`);
|
|
logger.logBoxLine(` UPS Model: ${snmpConfig.upsModel || 'cyberpower'}`);
|
|
|
|
if (snmpConfig.version === 1 || snmpConfig.version === 2) {
|
|
logger.logBoxLine(` Community: ${snmpConfig.community}`);
|
|
} else if (snmpConfig.version === 3) {
|
|
logger.logBoxLine(` Security Level: ${snmpConfig.securityLevel}`);
|
|
logger.logBoxLine(` Username: ${snmpConfig.username}`);
|
|
|
|
// Show auth and privacy details based on security level
|
|
if (snmpConfig.securityLevel === 'authNoPriv' || snmpConfig.securityLevel === 'authPriv') {
|
|
logger.logBoxLine(` Auth Protocol: ${snmpConfig.authProtocol || 'None'}`);
|
|
}
|
|
|
|
if (snmpConfig.securityLevel === 'authPriv') {
|
|
logger.logBoxLine(` Privacy Protocol: ${snmpConfig.privProtocol || 'None'}`);
|
|
}
|
|
|
|
// Show timeout value
|
|
logger.logBoxLine(` Timeout: ${snmpConfig.timeout / 1000} seconds`);
|
|
}
|
|
|
|
// Show OIDs if custom model is selected
|
|
if (snmpConfig.upsModel === 'custom' && snmpConfig.customOIDs) {
|
|
logger.logBoxLine('Custom OIDs:');
|
|
logger.logBoxLine(` Power Status: ${snmpConfig.customOIDs.POWER_STATUS || 'Not set'}`);
|
|
logger.logBoxLine(` Battery Capacity: ${snmpConfig.customOIDs.BATTERY_CAPACITY || 'Not set'}`);
|
|
logger.logBoxLine(` Battery Runtime: ${snmpConfig.customOIDs.BATTERY_RUNTIME || 'Not set'}`);
|
|
}
|
|
logger.logBoxLine('Thresholds:');
|
|
logger.logBoxLine(` Battery: ${thresholds.battery}%`);
|
|
logger.logBoxLine(` Runtime: ${thresholds.runtime} minutes`);
|
|
|
|
// Show group assignments if this is a UPS config
|
|
if (config.groups && Array.isArray(config.groups)) {
|
|
logger.logBoxLine(`Group Assignments: ${config.groups.length === 0 ? 'None' : config.groups.join(', ')}`);
|
|
}
|
|
|
|
logger.logBoxLine(`Check Interval: ${checkInterval / 1000} seconds`);
|
|
logger.logBoxEnd();
|
|
}
|
|
|
|
/**
|
|
* Test connection to the UPS
|
|
* @param config Current UPS configuration or legacy config
|
|
*/
|
|
private async testConnection(config: any): Promise<void> {
|
|
const upsId = config.id || 'default';
|
|
const upsName = config.name || 'Default UPS';
|
|
logger.log(`\nTesting connection to UPS: ${upsName} (${upsId})...`);
|
|
|
|
try {
|
|
// Create a test config with a short timeout
|
|
const snmpConfig = config.snmp ? config.snmp : config.snmp;
|
|
const thresholds = config.thresholds ? config.thresholds : config.thresholds;
|
|
|
|
const testConfig = {
|
|
...snmpConfig,
|
|
timeout: Math.min(snmpConfig.timeout, 10000), // Use at most 10 seconds for testing
|
|
};
|
|
|
|
const status = await this.nupst.getSnmp().getUpsStatus(testConfig);
|
|
|
|
const boxWidth = 45;
|
|
logger.logBoxTitle(`Connection Successful: ${upsName}`, boxWidth);
|
|
logger.logBoxLine('UPS Status:');
|
|
logger.logBoxLine(` Power Status: ${status.powerStatus}`);
|
|
logger.logBoxLine(` Battery Capacity: ${status.batteryCapacity}%`);
|
|
logger.logBoxLine(` Runtime Remaining: ${status.batteryRuntime} minutes`);
|
|
logger.logBoxEnd();
|
|
|
|
// Check status against thresholds if on battery
|
|
if (status.powerStatus === 'onBattery') {
|
|
this.analyzeThresholds(status, thresholds);
|
|
}
|
|
} catch (error) {
|
|
const errorBoxWidth = 45;
|
|
logger.logBoxTitle(`Connection Failed: ${upsName}`, errorBoxWidth);
|
|
logger.logBoxLine(`Error: ${error.message}`);
|
|
logger.logBoxEnd();
|
|
logger.log("\nPlease check your settings and run 'nupst edit' to reconfigure this UPS.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Analyze UPS status against thresholds
|
|
* @param status UPS status
|
|
* @param thresholds Threshold configuration
|
|
*/
|
|
private analyzeThresholds(status: any, thresholds: any): void {
|
|
const boxWidth = 45;
|
|
logger.logBoxTitle('Threshold Analysis', boxWidth);
|
|
|
|
if (status.batteryCapacity < thresholds.battery) {
|
|
logger.logBoxLine('⚠️ WARNING: Battery capacity below threshold');
|
|
logger.logBoxLine(
|
|
` Current: ${status.batteryCapacity}% | Threshold: ${thresholds.battery}%`
|
|
);
|
|
logger.logBoxLine(' System would initiate shutdown');
|
|
} else {
|
|
logger.logBoxLine('✓ Battery capacity above threshold');
|
|
logger.logBoxLine(
|
|
` Current: ${status.batteryCapacity}% | Threshold: ${thresholds.battery}%`
|
|
);
|
|
}
|
|
|
|
if (status.batteryRuntime < thresholds.runtime) {
|
|
logger.logBoxLine('⚠️ WARNING: Runtime below threshold');
|
|
logger.logBoxLine(
|
|
` Current: ${status.batteryRuntime} min | Threshold: ${thresholds.runtime} min`
|
|
);
|
|
logger.logBoxLine(' System would initiate shutdown');
|
|
} else {
|
|
logger.logBoxLine('✓ Runtime above threshold');
|
|
logger.logBoxLine(
|
|
` Current: ${status.batteryRuntime} min | Threshold: ${thresholds.runtime} min`
|
|
);
|
|
}
|
|
|
|
logger.logBoxEnd();
|
|
}
|
|
|
|
/**
|
|
* Gather SNMP settings
|
|
* @param snmpConfig SNMP configuration object to update
|
|
* @param prompt Function to prompt for user input
|
|
*/
|
|
private async gatherSnmpSettings(
|
|
snmpConfig: any,
|
|
prompt: (question: string) => Promise<string>
|
|
): Promise<void> {
|
|
// SNMP IP Address
|
|
const defaultHost = snmpConfig.host || '127.0.0.1';
|
|
const host = await prompt(`UPS IP Address [${defaultHost}]: `);
|
|
snmpConfig.host = host.trim() || defaultHost;
|
|
|
|
// SNMP Port
|
|
const defaultPort = snmpConfig.port || 161;
|
|
const portInput = await prompt(`SNMP Port [${defaultPort}]: `);
|
|
const port = parseInt(portInput, 10);
|
|
snmpConfig.port = portInput.trim() && !isNaN(port) ? port : defaultPort;
|
|
|
|
// SNMP Version
|
|
const defaultVersion = snmpConfig.version || 1;
|
|
console.log('\nSNMP Version:');
|
|
console.log(' 1) SNMPv1');
|
|
console.log(' 2) SNMPv2c');
|
|
console.log(' 3) SNMPv3 (with security features)');
|
|
const versionInput = await prompt(`Select SNMP version [${defaultVersion}]: `);
|
|
const version = parseInt(versionInput, 10);
|
|
snmpConfig.version =
|
|
versionInput.trim() && (version === 1 || version === 2 || version === 3)
|
|
? version
|
|
: defaultVersion;
|
|
|
|
if (snmpConfig.version === 1 || snmpConfig.version === 2) {
|
|
// SNMP Community String (for v1/v2c)
|
|
const defaultCommunity = snmpConfig.community || 'public';
|
|
const community = await prompt(`SNMP Community String [${defaultCommunity}]: `);
|
|
snmpConfig.community = community.trim() || defaultCommunity;
|
|
} else if (snmpConfig.version === 3) {
|
|
// SNMP v3 settings
|
|
await this.gatherSnmpV3Settings(snmpConfig, prompt);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gather SNMPv3 specific settings
|
|
* @param snmpConfig SNMP configuration object to update
|
|
* @param prompt Function to prompt for user input
|
|
*/
|
|
private async gatherSnmpV3Settings(
|
|
snmpConfig: any,
|
|
prompt: (question: string) => Promise<string>
|
|
): Promise<void> {
|
|
console.log('\nSNMPv3 Security Settings:');
|
|
|
|
// Security Level
|
|
console.log('\nSecurity Level:');
|
|
console.log(' 1) noAuthNoPriv (No Authentication, No Privacy)');
|
|
console.log(' 2) authNoPriv (Authentication, No Privacy)');
|
|
console.log(' 3) authPriv (Authentication and Privacy)');
|
|
const defaultSecLevel = snmpConfig.securityLevel
|
|
? snmpConfig.securityLevel === 'noAuthNoPriv'
|
|
? 1
|
|
: snmpConfig.securityLevel === 'authNoPriv'
|
|
? 2
|
|
: 3
|
|
: 3;
|
|
const secLevelInput = await prompt(`Select Security Level [${defaultSecLevel}]: `);
|
|
const secLevel = parseInt(secLevelInput, 10) || defaultSecLevel;
|
|
|
|
if (secLevel === 1) {
|
|
snmpConfig.securityLevel = 'noAuthNoPriv';
|
|
// No auth, no priv - clear out authentication and privacy settings
|
|
snmpConfig.authProtocol = '';
|
|
snmpConfig.authKey = '';
|
|
snmpConfig.privProtocol = '';
|
|
snmpConfig.privKey = '';
|
|
// Set appropriate timeout for security level
|
|
snmpConfig.timeout = 5000; // 5 seconds for basic security
|
|
} else if (secLevel === 2) {
|
|
snmpConfig.securityLevel = 'authNoPriv';
|
|
// Auth, no priv - clear out privacy settings
|
|
snmpConfig.privProtocol = '';
|
|
snmpConfig.privKey = '';
|
|
// Set appropriate timeout for security level
|
|
snmpConfig.timeout = 10000; // 10 seconds for authentication
|
|
} else {
|
|
snmpConfig.securityLevel = 'authPriv';
|
|
// Set appropriate timeout for security level
|
|
snmpConfig.timeout = 15000; // 15 seconds for full encryption
|
|
}
|
|
|
|
// Username
|
|
const defaultUsername = snmpConfig.username || '';
|
|
const username = await prompt(`SNMPv3 Username [${defaultUsername}]: `);
|
|
snmpConfig.username = username.trim() || defaultUsername;
|
|
|
|
if (secLevel >= 2) {
|
|
// Authentication settings
|
|
await this.gatherAuthenticationSettings(snmpConfig, prompt);
|
|
|
|
if (secLevel === 3) {
|
|
// Privacy settings
|
|
await this.gatherPrivacySettings(snmpConfig, prompt);
|
|
}
|
|
|
|
// Allow customizing the timeout value
|
|
const defaultTimeout = snmpConfig.timeout / 1000; // Convert from ms to seconds for display
|
|
console.log(
|
|
'\nSNMPv3 operations with authentication and privacy may require longer timeouts.'
|
|
);
|
|
const timeoutInput = await prompt(`SNMP Timeout in seconds [${defaultTimeout}]: `);
|
|
const timeout = parseInt(timeoutInput, 10);
|
|
if (timeoutInput.trim() && !isNaN(timeout)) {
|
|
snmpConfig.timeout = timeout * 1000; // Convert to ms
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gather authentication settings for SNMPv3
|
|
* @param snmpConfig SNMP configuration object to update
|
|
* @param prompt Function to prompt for user input
|
|
*/
|
|
private async gatherAuthenticationSettings(
|
|
snmpConfig: any,
|
|
prompt: (question: string) => Promise<string>
|
|
): Promise<void> {
|
|
// Authentication protocol
|
|
console.log('\nAuthentication Protocol:');
|
|
console.log(' 1) MD5');
|
|
console.log(' 2) SHA');
|
|
const defaultAuthProtocol = snmpConfig.authProtocol === 'SHA' ? 2 : 1;
|
|
const authProtocolInput = await prompt(
|
|
`Select Authentication Protocol [${defaultAuthProtocol}]: `
|
|
);
|
|
const authProtocol = parseInt(authProtocolInput, 10) || defaultAuthProtocol;
|
|
snmpConfig.authProtocol = authProtocol === 2 ? 'SHA' : 'MD5';
|
|
|
|
// Authentication Key/Password
|
|
const defaultAuthKey = snmpConfig.authKey || '';
|
|
const authKey = await prompt(`Authentication Password ${defaultAuthKey ? '[*****]' : ''}: `);
|
|
snmpConfig.authKey = authKey.trim() || defaultAuthKey;
|
|
}
|
|
|
|
/**
|
|
* Gather privacy settings for SNMPv3
|
|
* @param snmpConfig SNMP configuration object to update
|
|
* @param prompt Function to prompt for user input
|
|
*/
|
|
private async gatherPrivacySettings(
|
|
snmpConfig: any,
|
|
prompt: (question: string) => Promise<string>
|
|
): Promise<void> {
|
|
// Privacy protocol
|
|
console.log('\nPrivacy Protocol:');
|
|
console.log(' 1) DES');
|
|
console.log(' 2) AES');
|
|
const defaultPrivProtocol = snmpConfig.privProtocol === 'AES' ? 2 : 1;
|
|
const privProtocolInput = await prompt(`Select Privacy Protocol [${defaultPrivProtocol}]: `);
|
|
const privProtocol = parseInt(privProtocolInput, 10) || defaultPrivProtocol;
|
|
snmpConfig.privProtocol = privProtocol === 2 ? 'AES' : 'DES';
|
|
|
|
// Privacy Key/Password
|
|
const defaultPrivKey = snmpConfig.privKey || '';
|
|
const privKey = await prompt(`Privacy Password ${defaultPrivKey ? '[*****]' : ''}: `);
|
|
snmpConfig.privKey = privKey.trim() || defaultPrivKey;
|
|
}
|
|
|
|
/**
|
|
* Gather threshold settings
|
|
* @param thresholds Thresholds configuration object to update
|
|
* @param prompt Function to prompt for user input
|
|
*/
|
|
private async gatherThresholdSettings(
|
|
thresholds: any,
|
|
prompt: (question: string) => Promise<string>
|
|
): Promise<void> {
|
|
console.log('\nShutdown Thresholds:');
|
|
|
|
// Battery threshold
|
|
const defaultBatteryThreshold = thresholds.battery || 60;
|
|
const batteryThresholdInput = await prompt(
|
|
`Battery percentage threshold [${defaultBatteryThreshold}%]: `
|
|
);
|
|
const batteryThreshold = parseInt(batteryThresholdInput, 10);
|
|
thresholds.battery =
|
|
batteryThresholdInput.trim() && !isNaN(batteryThreshold)
|
|
? batteryThreshold
|
|
: defaultBatteryThreshold;
|
|
|
|
// Runtime threshold
|
|
const defaultRuntimeThreshold = thresholds.runtime || 20;
|
|
const runtimeThresholdInput = await prompt(
|
|
`Runtime minutes threshold [${defaultRuntimeThreshold} minutes]: `
|
|
);
|
|
const runtimeThreshold = parseInt(runtimeThresholdInput, 10);
|
|
thresholds.runtime =
|
|
runtimeThresholdInput.trim() && !isNaN(runtimeThreshold)
|
|
? runtimeThreshold
|
|
: defaultRuntimeThreshold;
|
|
}
|
|
|
|
/**
|
|
* Gather UPS model settings
|
|
* @param snmpConfig SNMP configuration object to update
|
|
* @param prompt Function to prompt for user input
|
|
*/
|
|
private async gatherUpsModelSettings(
|
|
snmpConfig: any,
|
|
prompt: (question: string) => Promise<string>
|
|
): Promise<void> {
|
|
console.log('\nUPS Model Selection:');
|
|
console.log(' 1) CyberPower');
|
|
console.log(' 2) APC');
|
|
console.log(' 3) Eaton');
|
|
console.log(' 4) TrippLite');
|
|
console.log(' 5) Liebert/Vertiv');
|
|
console.log(' 6) Custom (Advanced)');
|
|
|
|
const defaultModelValue =
|
|
snmpConfig.upsModel === 'cyberpower'
|
|
? 1
|
|
: snmpConfig.upsModel === 'apc'
|
|
? 2
|
|
: snmpConfig.upsModel === 'eaton'
|
|
? 3
|
|
: snmpConfig.upsModel === 'tripplite'
|
|
? 4
|
|
: snmpConfig.upsModel === 'liebert'
|
|
? 5
|
|
: snmpConfig.upsModel === 'custom'
|
|
? 6
|
|
: 1;
|
|
|
|
const modelInput = await prompt(`Select UPS model [${defaultModelValue}]: `);
|
|
const modelValue = parseInt(modelInput, 10) || defaultModelValue;
|
|
|
|
if (modelValue === 1) {
|
|
snmpConfig.upsModel = 'cyberpower';
|
|
} else if (modelValue === 2) {
|
|
snmpConfig.upsModel = 'apc';
|
|
} else if (modelValue === 3) {
|
|
snmpConfig.upsModel = 'eaton';
|
|
} else if (modelValue === 4) {
|
|
snmpConfig.upsModel = 'tripplite';
|
|
} else if (modelValue === 5) {
|
|
snmpConfig.upsModel = 'liebert';
|
|
} else if (modelValue === 6) {
|
|
snmpConfig.upsModel = 'custom';
|
|
console.log('\nEnter custom OIDs for your UPS:');
|
|
console.log('(Leave blank to use standard RFC 1628 OIDs as fallback)');
|
|
|
|
// Custom OIDs
|
|
const powerStatusOID = await prompt('Power Status OID: ');
|
|
const batteryCapacityOID = await prompt('Battery Capacity OID: ');
|
|
const batteryRuntimeOID = await prompt('Battery Runtime OID: ');
|
|
|
|
// Create custom OIDs object
|
|
snmpConfig.customOIDs = {
|
|
POWER_STATUS: powerStatusOID.trim(),
|
|
BATTERY_CAPACITY: batteryCapacityOID.trim(),
|
|
BATTERY_RUNTIME: batteryRuntimeOID.trim(),
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display UPS configuration summary
|
|
* @param ups UPS configuration
|
|
*/
|
|
private displayUpsConfigSummary(ups: any): void {
|
|
const boxWidth = 45;
|
|
logger.log('');
|
|
logger.logBoxTitle(`UPS Configuration: ${ups.name}`, boxWidth);
|
|
logger.logBoxLine(`UPS ID: ${ups.id}`);
|
|
logger.logBoxLine(`SNMP Host: ${ups.snmp.host}:${ups.snmp.port}`);
|
|
logger.logBoxLine(`SNMP Version: ${ups.snmp.version}`);
|
|
logger.logBoxLine(`UPS Model: ${ups.snmp.upsModel}`);
|
|
logger.logBoxLine(
|
|
`Thresholds: ${ups.thresholds.battery}% battery, ${ups.thresholds.runtime} min runtime`
|
|
);
|
|
if (ups.groups && ups.groups.length > 0) {
|
|
logger.logBoxLine(`Groups: ${ups.groups.join(', ')}`);
|
|
} else {
|
|
logger.logBoxLine('Groups: None');
|
|
}
|
|
logger.logBoxEnd();
|
|
logger.log('');
|
|
}
|
|
|
|
/**
|
|
* Optionally test connection to UPS
|
|
* @param snmpConfig SNMP configuration to test
|
|
* @param prompt Function to prompt for user input
|
|
*/
|
|
private async optionallyTestConnection(
|
|
snmpConfig: any,
|
|
prompt: (question: string) => Promise<string>
|
|
): Promise<void> {
|
|
const testConnection = await prompt(
|
|
'Would you like to test the connection to your UPS? (y/N): '
|
|
);
|
|
if (testConnection.toLowerCase() === 'y') {
|
|
logger.log('\nTesting connection to UPS...');
|
|
try {
|
|
// Create a test config with a short timeout
|
|
const testConfig = {
|
|
...snmpConfig,
|
|
timeout: Math.min(snmpConfig.timeout, 10000), // Use at most 10 seconds for testing
|
|
};
|
|
|
|
const status = await this.nupst.getSnmp().getUpsStatus(testConfig);
|
|
const boxWidth = 45;
|
|
logger.log('');
|
|
logger.logBoxTitle('Connection Successful!', boxWidth);
|
|
logger.logBoxLine('UPS Status:');
|
|
logger.logBoxLine(`✓ Power Status: ${status.powerStatus}`);
|
|
logger.logBoxLine(`✓ Battery Capacity: ${status.batteryCapacity}%`);
|
|
logger.logBoxLine(`✓ Runtime Remaining: ${status.batteryRuntime} minutes`);
|
|
logger.logBoxEnd();
|
|
} catch (error) {
|
|
const errorBoxWidth = 45;
|
|
logger.log('');
|
|
logger.logBoxTitle('Connection Failed!', errorBoxWidth);
|
|
logger.logBoxLine(`Error: ${error.message}`);
|
|
logger.logBoxEnd();
|
|
logger.log('\nPlease check your settings and try again.');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the systemd service is running and restart it if it is
|
|
* This is useful after configuration changes
|
|
*/
|
|
public async restartServiceIfRunning(): Promise<void> {
|
|
try {
|
|
// Check if the service is active
|
|
const isActive =
|
|
execSync('systemctl is-active nupst.service || true').toString().trim() === 'active';
|
|
|
|
if (isActive) {
|
|
// Service is running, restart it
|
|
const boxWidth = 45;
|
|
logger.logBoxTitle('Service Update', boxWidth);
|
|
logger.logBoxLine('Configuration has changed.');
|
|
logger.logBoxLine('Restarting NUPST service to apply changes...');
|
|
|
|
try {
|
|
if (process.getuid && process.getuid() === 0) {
|
|
// We have root access, restart directly
|
|
execSync('systemctl restart nupst.service');
|
|
logger.logBoxLine('Service restarted successfully.');
|
|
} else {
|
|
// No root access, show instructions
|
|
logger.logBoxLine('Please restart the service with:');
|
|
logger.logBoxLine(' sudo systemctl restart nupst.service');
|
|
}
|
|
} catch (error) {
|
|
logger.logBoxLine(`Error restarting service: ${error.message}`);
|
|
logger.logBoxLine('You may need to restart the service manually:');
|
|
logger.logBoxLine(' sudo systemctl restart nupst.service');
|
|
}
|
|
|
|
logger.logBoxEnd();
|
|
}
|
|
} catch (error) {
|
|
// Ignore errors checking service status
|
|
}
|
|
}
|
|
} |