feat(actions): implement action system for UPS state management with shutdown, webhook, and script actions
This commit is contained in:
@@ -77,10 +77,10 @@ export class UpsHandler {
|
||||
checkInterval: config.checkInterval,
|
||||
upsDevices: [{
|
||||
id: 'default',
|
||||
name: 'Default UPS',
|
||||
snmp: config.snmp,
|
||||
thresholds: config.thresholds,
|
||||
groups: [],
|
||||
name: 'Default UPS',
|
||||
snmp: config.snmp,
|
||||
groups: [],
|
||||
actions: [],
|
||||
}],
|
||||
groups: [],
|
||||
};
|
||||
@@ -117,6 +117,7 @@ export class UpsHandler {
|
||||
runtime: 20,
|
||||
},
|
||||
groups: [],
|
||||
actions: [],
|
||||
};
|
||||
|
||||
// Gather SNMP settings
|
||||
@@ -136,6 +137,9 @@ export class UpsHandler {
|
||||
await groupHandler.assignUpsToGroups(newUps, config.groups, prompt);
|
||||
}
|
||||
|
||||
// Gather action settings
|
||||
await this.gatherActionSettings(newUps.actions, prompt);
|
||||
|
||||
// Add the new UPS to the config
|
||||
config.upsDevices.push(newUps);
|
||||
|
||||
@@ -221,16 +225,16 @@ export class UpsHandler {
|
||||
// Convert old format to new format if needed
|
||||
if (!config.upsDevices) {
|
||||
// Initialize with the current config as the first UPS
|
||||
if (!config.snmp || !config.thresholds) {
|
||||
logger.error('Legacy configuration is missing required SNMP or threshold settings');
|
||||
if (!config.snmp) {
|
||||
logger.error('Legacy configuration is missing required SNMP settings');
|
||||
return;
|
||||
}
|
||||
config.upsDevices = [{
|
||||
id: 'default',
|
||||
name: 'Default UPS',
|
||||
snmp: config.snmp,
|
||||
thresholds: config.thresholds,
|
||||
groups: [],
|
||||
actions: [],
|
||||
}];
|
||||
config.groups = [];
|
||||
logger.log('Converting existing configuration to multi-UPS format.');
|
||||
@@ -265,9 +269,6 @@ export class UpsHandler {
|
||||
// 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);
|
||||
|
||||
@@ -279,6 +280,14 @@ export class UpsHandler {
|
||||
await groupHandler.assignUpsToGroups(upsToEdit, config.groups, prompt);
|
||||
}
|
||||
|
||||
// Initialize actions array if not exists
|
||||
if (!upsToEdit.actions) {
|
||||
upsToEdit.actions = [];
|
||||
}
|
||||
|
||||
// Edit action settings
|
||||
await this.gatherActionSettings(upsToEdit.actions, prompt);
|
||||
|
||||
// Save the configuration
|
||||
await this.nupst.getDaemon().saveConfig(config);
|
||||
|
||||
@@ -396,13 +405,12 @@ export class UpsHandler {
|
||||
logger.logBox('UPS Devices', [
|
||||
'Legacy single-UPS configuration detected.',
|
||||
'',
|
||||
...((!config.snmp || !config.thresholds)
|
||||
? ['Error: Configuration missing SNMP or threshold settings']
|
||||
...(!config.snmp
|
||||
? ['Error: Configuration missing SNMP settings']
|
||||
: [
|
||||
'Default UPS:',
|
||||
` Host: ${config.snmp.host}:${config.snmp.port}`,
|
||||
` Model: ${config.snmp.upsModel || 'cyberpower'}`,
|
||||
` Thresholds: ${config.thresholds.battery}% battery, ${config.thresholds.runtime} min runtime`,
|
||||
'',
|
||||
'Use "nupst ups add" to add more UPS devices and migrate',
|
||||
'to the multi-UPS configuration format.',
|
||||
@@ -506,9 +514,8 @@ export class UpsHandler {
|
||||
*/
|
||||
private displayTestConfig(config: any): void {
|
||||
// Check if this is a UPS device or full configuration
|
||||
const isUpsConfig = config.snmp && config.thresholds;
|
||||
const isUpsConfig = config.snmp;
|
||||
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
|
||||
@@ -919,6 +926,151 @@ export class UpsHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather action configuration settings
|
||||
* @param actions Actions array to configure
|
||||
* @param prompt Function to prompt for user input
|
||||
*/
|
||||
private async gatherActionSettings(
|
||||
actions: any[],
|
||||
prompt: (question: string) => Promise<string>,
|
||||
): Promise<void> {
|
||||
logger.log('');
|
||||
logger.info('Action Configuration (Optional):');
|
||||
logger.dim('Actions are triggered on power status changes and threshold violations.');
|
||||
logger.dim('Leave empty to use default shutdown behavior on threshold violations.');
|
||||
|
||||
const configureActions = await prompt('Configure custom actions? (y/N): ');
|
||||
if (configureActions.toLowerCase() !== 'y') {
|
||||
return; // Keep existing actions or use default
|
||||
}
|
||||
|
||||
// Clear existing actions
|
||||
actions.length = 0;
|
||||
|
||||
let addMore = true;
|
||||
while (addMore) {
|
||||
logger.log('');
|
||||
logger.info('Action Type:');
|
||||
logger.dim(' 1) Shutdown (system shutdown)');
|
||||
logger.dim(' 2) Webhook (HTTP notification)');
|
||||
logger.dim(' 3) Custom Script (run .sh file from /etc/nupst)');
|
||||
|
||||
const typeInput = await prompt('Select action type [1]: ');
|
||||
const typeValue = parseInt(typeInput, 10) || 1;
|
||||
|
||||
const action: any = {};
|
||||
|
||||
if (typeValue === 1) {
|
||||
// Shutdown action
|
||||
action.type = 'shutdown';
|
||||
|
||||
const delayInput = await prompt('Shutdown delay in minutes [5]: ');
|
||||
const delay = parseInt(delayInput, 10);
|
||||
if (delayInput.trim() && !isNaN(delay)) {
|
||||
action.shutdownDelay = delay;
|
||||
}
|
||||
} else if (typeValue === 2) {
|
||||
// Webhook action
|
||||
action.type = 'webhook';
|
||||
|
||||
const url = await prompt('Webhook URL: ');
|
||||
if (!url.trim()) {
|
||||
logger.warn('Webhook URL required, skipping action');
|
||||
continue;
|
||||
}
|
||||
action.webhookUrl = url.trim();
|
||||
|
||||
logger.log('');
|
||||
logger.info('HTTP Method:');
|
||||
logger.dim(' 1) POST (JSON body)');
|
||||
logger.dim(' 2) GET (query parameters)');
|
||||
const methodInput = await prompt('Select method [1]: ');
|
||||
action.webhookMethod = methodInput === '2' ? 'GET' : 'POST';
|
||||
|
||||
const timeoutInput = await prompt('Timeout in seconds [10]: ');
|
||||
const timeout = parseInt(timeoutInput, 10);
|
||||
if (timeoutInput.trim() && !isNaN(timeout)) {
|
||||
action.webhookTimeout = timeout * 1000; // Convert to ms
|
||||
}
|
||||
} else if (typeValue === 3) {
|
||||
// Script action
|
||||
action.type = 'script';
|
||||
|
||||
const scriptPath = await prompt('Script filename (in /etc/nupst/, must end with .sh): ');
|
||||
if (!scriptPath.trim() || !scriptPath.trim().endsWith('.sh')) {
|
||||
logger.warn('Script path must end with .sh, skipping action');
|
||||
continue;
|
||||
}
|
||||
action.scriptPath = scriptPath.trim();
|
||||
|
||||
const timeoutInput = await prompt('Script timeout in seconds [60]: ');
|
||||
const timeout = parseInt(timeoutInput, 10);
|
||||
if (timeoutInput.trim() && !isNaN(timeout)) {
|
||||
action.scriptTimeout = timeout * 1000; // Convert to ms
|
||||
}
|
||||
} else {
|
||||
logger.warn('Invalid action type, skipping');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Configure trigger mode (applies to all action types)
|
||||
logger.log('');
|
||||
logger.info('Trigger Mode:');
|
||||
logger.dim(' 1) Power changes + thresholds (default)');
|
||||
logger.dim(' 2) Only power status changes');
|
||||
logger.dim(' 3) Only threshold violations');
|
||||
logger.dim(' 4) Any change (every ~30s check)');
|
||||
const triggerInput = await prompt('Select trigger mode [1]: ');
|
||||
const triggerValue = parseInt(triggerInput, 10) || 1;
|
||||
|
||||
switch (triggerValue) {
|
||||
case 2:
|
||||
action.triggerMode = 'onlyPowerChanges';
|
||||
break;
|
||||
case 3:
|
||||
action.triggerMode = 'onlyThresholds';
|
||||
break;
|
||||
case 4:
|
||||
action.triggerMode = 'anyChange';
|
||||
break;
|
||||
default:
|
||||
action.triggerMode = 'powerChangesAndThresholds';
|
||||
}
|
||||
|
||||
// Configure thresholds if needed for onlyThresholds or powerChangesAndThresholds modes
|
||||
if (action.triggerMode === 'onlyThresholds' || action.triggerMode === 'powerChangesAndThresholds') {
|
||||
logger.log('');
|
||||
logger.info('Action Thresholds:');
|
||||
logger.dim('Action will trigger when battery or runtime falls below these values (while on battery)');
|
||||
|
||||
const batteryInput = await prompt('Battery threshold percentage [60]: ');
|
||||
const battery = parseInt(batteryInput, 10);
|
||||
const batteryThreshold = (batteryInput.trim() && !isNaN(battery)) ? battery : 60;
|
||||
|
||||
const runtimeInput = await prompt('Runtime threshold in minutes [20]: ');
|
||||
const runtime = parseInt(runtimeInput, 10);
|
||||
const runtimeThreshold = (runtimeInput.trim() && !isNaN(runtime)) ? runtime : 20;
|
||||
|
||||
action.thresholds = {
|
||||
battery: batteryThreshold,
|
||||
runtime: runtimeThreshold,
|
||||
};
|
||||
}
|
||||
|
||||
actions.push(action);
|
||||
logger.success(`${action.type.charAt(0).toUpperCase() + action.type.slice(1)} action added (mode: ${action.triggerMode || 'powerChangesAndThresholds'})`);
|
||||
|
||||
const more = await prompt('Add another action? (y/N): ');
|
||||
addMore = more.toLowerCase() === 'y';
|
||||
}
|
||||
|
||||
if (actions.length > 0) {
|
||||
logger.log('');
|
||||
logger.success(`${actions.length} action(s) configured`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display UPS configuration summary
|
||||
* @param ups UPS configuration
|
||||
|
Reference in New Issue
Block a user