feat(core): Centralize timeouts/constants, add CLI prompt helpers, and introduce webhook/script actions with safety and SNMP refactors
This commit is contained in:
@@ -4,6 +4,7 @@ import { logger, type ITableColumn } from '../logger.ts';
|
||||
import { theme, symbols } from '../colors.ts';
|
||||
import type { IActionConfig } from '../actions/base-action.ts';
|
||||
import type { IUpsConfig, IGroupConfig } from '../daemon.ts';
|
||||
import * as helpers from '../helpers/index.ts';
|
||||
|
||||
/**
|
||||
* Class for handling action-related CLI commands
|
||||
@@ -57,21 +58,7 @@ export class ActionHandler {
|
||||
const targetType = ups ? 'UPS' : 'Group';
|
||||
const targetName = ups ? ups.name : group!.name;
|
||||
|
||||
const readline = await import('node:readline');
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
const prompt = (question: string): Promise<string> => {
|
||||
return new Promise((resolve) => {
|
||||
rl.question(question, (answer: string) => {
|
||||
resolve(answer);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
await helpers.withPrompt(async (prompt) => {
|
||||
logger.log('');
|
||||
logger.info(`Add Action to ${targetType} ${theme.highlight(targetName)}`);
|
||||
logger.log('');
|
||||
@@ -154,9 +141,7 @@ export class ActionHandler {
|
||||
logger.success(`Action added to ${targetType} ${targetName}`);
|
||||
logger.log(` ${theme.dim('Changes saved and will be applied automatically')}`);
|
||||
logger.log('');
|
||||
} finally {
|
||||
rl.close();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed to add action: ${error instanceof Error ? error.message : String(error)}`,
|
||||
|
||||
@@ -25,26 +25,9 @@ export class FeatureHandler {
|
||||
*/
|
||||
public async configureHttpServer(): Promise<void> {
|
||||
try {
|
||||
const readline = await import('node:readline');
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
const prompt = (question: string): Promise<string> => {
|
||||
return new Promise((resolve) => {
|
||||
rl.question(question, (answer: string) => {
|
||||
resolve(answer);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
await helpers.withPrompt(async (prompt) => {
|
||||
await this.runHttpServerConfig(prompt);
|
||||
} finally {
|
||||
rl.close();
|
||||
process.stdin.destroy();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`HTTP Server config error: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
@@ -186,17 +169,9 @@ export class FeatureHandler {
|
||||
|
||||
if (isActive) {
|
||||
logger.log('');
|
||||
const readline = await import('node:readline');
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
const answer = await new Promise<string>((resolve) => {
|
||||
rl.question('Service is running. Restart to apply changes? (Y/n): ', resolve);
|
||||
});
|
||||
|
||||
rl.close();
|
||||
const { prompt, close } = await helpers.createPrompt();
|
||||
const answer = await prompt('Service is running. Restart to apply changes? (Y/n): ');
|
||||
close();
|
||||
|
||||
if (!answer || answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
||||
logger.info('Restarting service...');
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Nupst } from '../nupst.ts';
|
||||
import { logger, type ITableColumn } from '../logger.ts';
|
||||
import { theme } from '../colors.ts';
|
||||
import * as helpers from '../helpers/index.ts';
|
||||
import { type IGroupConfig } from '../daemon.ts';
|
||||
import type { IGroupConfig, IUpsConfig, INupstConfig } from '../daemon.ts';
|
||||
|
||||
/**
|
||||
* Class for handling group-related CLI commands
|
||||
@@ -100,24 +100,7 @@ export class GroupHandler {
|
||||
*/
|
||||
public async add(): Promise<void> {
|
||||
try {
|
||||
// Import readline module for user input
|
||||
const readline = await import('node: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 helpers.withPrompt(async (prompt) => {
|
||||
// Try to load configuration
|
||||
try {
|
||||
await this.nupst.getDaemon().loadConfig();
|
||||
@@ -200,10 +183,7 @@ export class GroupHandler {
|
||||
this.nupst.getUpsHandler().restartServiceIfRunning();
|
||||
|
||||
logger.log('\nGroup setup complete!');
|
||||
} finally {
|
||||
rl.close();
|
||||
process.stdin.destroy();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Add group error: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
@@ -215,24 +195,7 @@ export class GroupHandler {
|
||||
*/
|
||||
public async edit(groupId: string): Promise<void> {
|
||||
try {
|
||||
// Import readline module for user input
|
||||
const readline = await import('node: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 helpers.withPrompt(async (prompt) => {
|
||||
// Try to load configuration
|
||||
try {
|
||||
await this.nupst.getDaemon().loadConfig();
|
||||
@@ -318,10 +281,7 @@ export class GroupHandler {
|
||||
this.nupst.getUpsHandler().restartServiceIfRunning();
|
||||
|
||||
logger.log('\nGroup edit complete!');
|
||||
} finally {
|
||||
rl.close();
|
||||
process.stdin.destroy();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Edit group error: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
@@ -362,23 +322,11 @@ export class GroupHandler {
|
||||
const groupToDelete = config.groups[groupIndex];
|
||||
|
||||
// Get confirmation before deleting
|
||||
const readline = await import('node: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 group "${groupToDelete.name}" (${groupId})? [y/N]: `,
|
||||
(answer) => {
|
||||
resolve(answer.toLowerCase());
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
rl.close();
|
||||
process.stdin.destroy();
|
||||
const { prompt, close } = await helpers.createPrompt();
|
||||
const confirm = (await prompt(
|
||||
`Are you sure you want to delete group "${groupToDelete.name}" (${groupId})? [y/N]: `,
|
||||
)).toLowerCase();
|
||||
close();
|
||||
|
||||
if (confirm !== 'y' && confirm !== 'yes') {
|
||||
logger.log('Deletion cancelled.');
|
||||
@@ -419,8 +367,8 @@ export class GroupHandler {
|
||||
* @param prompt Function to prompt for user input
|
||||
*/
|
||||
public async assignUpsToGroups(
|
||||
ups: any,
|
||||
groups: any[],
|
||||
ups: IUpsConfig,
|
||||
groups: IGroupConfig[],
|
||||
prompt: (question: string) => Promise<string>,
|
||||
): Promise<void> {
|
||||
// Initialize groups array if it doesn't exist
|
||||
@@ -514,7 +462,7 @@ export class GroupHandler {
|
||||
*/
|
||||
public async assignUpsToGroup(
|
||||
groupId: string,
|
||||
config: any,
|
||||
config: INupstConfig,
|
||||
prompt: (question: string) => Promise<string>,
|
||||
): Promise<void> {
|
||||
if (!config.upsDevices || config.upsDevices.length === 0) {
|
||||
@@ -522,7 +470,7 @@ export class GroupHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
const group = config.groups.find((g: { id: string }) => g.id === groupId);
|
||||
const group = config.groups.find((g) => g.id === groupId);
|
||||
if (!group) {
|
||||
logger.error(`Group with ID "${groupId}" not found.`);
|
||||
return;
|
||||
@@ -530,7 +478,7 @@ export class GroupHandler {
|
||||
|
||||
// Show current assignments
|
||||
logger.log(`\nUPS devices in group "${group.name}" (${group.id}):`);
|
||||
const upsInGroup = config.upsDevices.filter((ups: { groups?: string[] }) =>
|
||||
const upsInGroup = config.upsDevices.filter((ups) =>
|
||||
ups.groups && ups.groups.includes(groupId)
|
||||
);
|
||||
if (upsInGroup.length === 0) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import process from 'node:process';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { Nupst } from '../nupst.ts';
|
||||
import { logger } from '../logger.ts';
|
||||
import * as helpers from '../helpers/index.ts';
|
||||
|
||||
/**
|
||||
* Class for handling service-related CLI commands
|
||||
@@ -196,22 +197,7 @@ export class ServiceHandler {
|
||||
this.checkRootAccess('This command must be run as root.');
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
};
|
||||
const { prompt, close } = await helpers.createPrompt();
|
||||
|
||||
logger.log('');
|
||||
logger.highlight('NUPST Uninstaller');
|
||||
@@ -254,15 +240,13 @@ export class ServiceHandler {
|
||||
|
||||
if (!uninstallScriptPath) {
|
||||
logger.error('Could not locate uninstall.sh script. Aborting uninstall.');
|
||||
rl.close();
|
||||
process.stdin.destroy();
|
||||
close();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Close readline before executing script
|
||||
rl.close();
|
||||
process.stdin.destroy();
|
||||
// Close prompt before executing script
|
||||
close();
|
||||
|
||||
// Execute uninstall.sh with the appropriate option
|
||||
logger.log('');
|
||||
|
||||
@@ -4,8 +4,17 @@ import { Nupst } from '../nupst.ts';
|
||||
import { logger, type ITableColumn } from '../logger.ts';
|
||||
import { theme } from '../colors.ts';
|
||||
import * as helpers from '../helpers/index.ts';
|
||||
import type { TUpsModel } from '../snmp/types.ts';
|
||||
import type { INupstConfig } from '../daemon.ts';
|
||||
import type { ISnmpConfig, TUpsModel, IUpsStatus as ISnmpUpsStatus } from '../snmp/types.ts';
|
||||
import type { INupstConfig, IUpsConfig, IUpsStatus } from '../daemon.ts';
|
||||
import type { IActionConfig } from '../actions/base-action.ts';
|
||||
|
||||
/**
|
||||
* Thresholds configuration for CLI display
|
||||
*/
|
||||
interface IThresholds {
|
||||
battery: number;
|
||||
runtime: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for handling UPS-related CLI commands
|
||||
@@ -27,29 +36,9 @@ export class UpsHandler {
|
||||
*/
|
||||
public async add(): Promise<void> {
|
||||
try {
|
||||
// Import readline module for user input
|
||||
const readline = await import('node: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 helpers.withPrompt(async (prompt) => {
|
||||
await this.runAddProcess(prompt);
|
||||
} finally {
|
||||
rl.close();
|
||||
process.stdin.destroy();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Add UPS error: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
@@ -160,29 +149,9 @@ export class UpsHandler {
|
||||
*/
|
||||
public async edit(upsId?: string): Promise<void> {
|
||||
try {
|
||||
// Import readline module for user input
|
||||
const readline = await import('node: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 helpers.withPrompt(async (prompt) => {
|
||||
await this.runEditProcess(upsId, prompt);
|
||||
} finally {
|
||||
rl.close();
|
||||
process.stdin.destroy();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Edit UPS error: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
@@ -337,23 +306,11 @@ export class UpsHandler {
|
||||
const upsToDelete = config.upsDevices[upsIndex];
|
||||
|
||||
// Get confirmation before deleting
|
||||
const readline = await import('node: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();
|
||||
process.stdin.destroy();
|
||||
const { prompt, close } = await helpers.createPrompt();
|
||||
const confirm = (await prompt(
|
||||
`Are you sure you want to delete UPS "${upsToDelete.name}" (${upsId})? [y/N]: `,
|
||||
)).toLowerCase();
|
||||
close();
|
||||
|
||||
if (confirm !== 'y' && confirm !== 'yes') {
|
||||
logger.log('Deletion cancelled.');
|
||||
@@ -509,7 +466,7 @@ export class UpsHandler {
|
||||
* Display the configuration for testing
|
||||
* @param config Current configuration or individual UPS configuration
|
||||
*/
|
||||
private displayTestConfig(config: any): void {
|
||||
private displayTestConfig(config: IUpsConfig | INupstConfig): void {
|
||||
// Check if this is a UPS device or full configuration
|
||||
const isUpsConfig = config.snmp;
|
||||
const snmpConfig = isUpsConfig ? config.snmp : config.snmp || {};
|
||||
@@ -571,7 +528,7 @@ export class UpsHandler {
|
||||
* Test connection to the UPS
|
||||
* @param config Current UPS configuration or legacy config
|
||||
*/
|
||||
private async testConnection(config: any): Promise<void> {
|
||||
private async testConnection(config: IUpsConfig | INupstConfig): Promise<void> {
|
||||
const upsId = config.id || 'default';
|
||||
const upsName = config.name || 'Default UPS';
|
||||
logger.log(`\nTesting connection to UPS: ${upsName} (${upsId})...`);
|
||||
@@ -610,7 +567,7 @@ export class UpsHandler {
|
||||
* @param status UPS status
|
||||
* @param thresholds Threshold configuration
|
||||
*/
|
||||
private analyzeThresholds(status: any, thresholds: any): void {
|
||||
private analyzeThresholds(status: ISnmpUpsStatus, thresholds: IThresholds): void {
|
||||
const boxWidth = 45;
|
||||
logger.logBoxTitle('Threshold Analysis', boxWidth);
|
||||
|
||||
@@ -649,7 +606,7 @@ export class UpsHandler {
|
||||
* @param prompt Function to prompt for user input
|
||||
*/
|
||||
private async gatherSnmpSettings(
|
||||
snmpConfig: any,
|
||||
snmpConfig: Partial<ISnmpConfig>,
|
||||
prompt: (question: string) => Promise<string>,
|
||||
): Promise<void> {
|
||||
// SNMP IP Address
|
||||
@@ -693,7 +650,7 @@ export class UpsHandler {
|
||||
* @param prompt Function to prompt for user input
|
||||
*/
|
||||
private async gatherSnmpV3Settings(
|
||||
snmpConfig: any,
|
||||
snmpConfig: Partial<ISnmpConfig>,
|
||||
prompt: (question: string) => Promise<string>,
|
||||
): Promise<void> {
|
||||
logger.log('');
|
||||
@@ -771,7 +728,7 @@ export class UpsHandler {
|
||||
* @param prompt Function to prompt for user input
|
||||
*/
|
||||
private async gatherAuthenticationSettings(
|
||||
snmpConfig: any,
|
||||
snmpConfig: Partial<ISnmpConfig>,
|
||||
prompt: (question: string) => Promise<string>,
|
||||
): Promise<void> {
|
||||
// Authentication protocol
|
||||
@@ -798,7 +755,7 @@ export class UpsHandler {
|
||||
* @param prompt Function to prompt for user input
|
||||
*/
|
||||
private async gatherPrivacySettings(
|
||||
snmpConfig: any,
|
||||
snmpConfig: Partial<ISnmpConfig>,
|
||||
prompt: (question: string) => Promise<string>,
|
||||
): Promise<void> {
|
||||
// Privacy protocol
|
||||
@@ -823,7 +780,7 @@ export class UpsHandler {
|
||||
* @param prompt Function to prompt for user input
|
||||
*/
|
||||
private async gatherUpsModelSettings(
|
||||
snmpConfig: any,
|
||||
snmpConfig: Partial<ISnmpConfig>,
|
||||
prompt: (question: string) => Promise<string>,
|
||||
): Promise<void> {
|
||||
logger.log('');
|
||||
@@ -888,7 +845,7 @@ export class UpsHandler {
|
||||
* @param prompt Function to prompt for user input
|
||||
*/
|
||||
private async gatherActionSettings(
|
||||
actions: any[],
|
||||
actions: IActionConfig[],
|
||||
prompt: (question: string) => Promise<string>,
|
||||
): Promise<void> {
|
||||
logger.log('');
|
||||
@@ -915,7 +872,7 @@ export class UpsHandler {
|
||||
const typeInput = await prompt('Select action type [1]: ');
|
||||
const typeValue = parseInt(typeInput, 10) || 1;
|
||||
|
||||
const action: any = {};
|
||||
const action: Partial<IActionConfig> = {};
|
||||
|
||||
if (typeValue === 1) {
|
||||
// Shutdown action
|
||||
@@ -1014,8 +971,8 @@ export class UpsHandler {
|
||||
};
|
||||
}
|
||||
|
||||
actions.push(action);
|
||||
logger.success(`${action.type.charAt(0).toUpperCase() + action.type.slice(1)} action added (mode: ${action.triggerMode || 'powerChangesAndThresholds'})`);
|
||||
actions.push(action as IActionConfig);
|
||||
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';
|
||||
@@ -1031,7 +988,7 @@ export class UpsHandler {
|
||||
* Display UPS configuration summary
|
||||
* @param ups UPS configuration
|
||||
*/
|
||||
private displayUpsConfigSummary(ups: any): void {
|
||||
private displayUpsConfigSummary(ups: IUpsConfig): void {
|
||||
const boxWidth = 45;
|
||||
logger.log('');
|
||||
logger.logBoxTitle(`UPS Configuration: ${ups.name}`, boxWidth);
|
||||
@@ -1055,7 +1012,7 @@ export class UpsHandler {
|
||||
* @param prompt Function to prompt for user input
|
||||
*/
|
||||
private async optionallyTestConnection(
|
||||
snmpConfig: any,
|
||||
snmpConfig: ISnmpConfig,
|
||||
prompt: (question: string) => Promise<string>,
|
||||
): Promise<void> {
|
||||
const testConnection = await prompt(
|
||||
|
||||
Reference in New Issue
Block a user