fix(cli(ups-handler), systemd): add type guards and null checks for UPS configs; improve SNMP handling and prompts; guard version display
This commit is contained in:
10
changelog.md
10
changelog.md
@@ -1,5 +1,15 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-01-29 - 5.2.1 - fix(cli(ups-handler), systemd)
|
||||||
|
add type guards and null checks for UPS configs; improve SNMP handling and prompts; guard version display
|
||||||
|
|
||||||
|
- Introduce a type guard ('id' in config && 'name' in config) to distinguish IUpsConfig from legacy INupstConfig and route fields (snmp, checkInterval, name, id) accordingly.
|
||||||
|
- displayTestConfig now handles missing SNMP by logging 'Not configured' and returning, computes checkInterval/upsName/upsId correctly, and uses groups only for true UPS configs.
|
||||||
|
- testConnection now safely derives snmpConfig for both config types, throws if SNMP is missing, and caps test timeout to 10s for probes.
|
||||||
|
- Clear auth/priv credentials by setting undefined (instead of empty strings) when disabling security levels to avoid invalid/empty string values.
|
||||||
|
- Expanded customOIDs to include OUTPUT_LOAD, OUTPUT_POWER, OUTPUT_VOLTAGE, OUTPUT_CURRENT with defaults; trim prompt input and document RFC 1628 fallbacks.
|
||||||
|
- systemd.displayVersionInfo: guard against missing nupst (silent return) and avoid errors when printing version info; use ignored catch variables for clarity.
|
||||||
|
|
||||||
## 2026-01-29 - 5.2.0 - feat(core)
|
## 2026-01-29 - 5.2.0 - feat(core)
|
||||||
Centralize timeouts/constants, add CLI prompt helpers, and introduce webhook/script actions with safety and SNMP refactors
|
Centralize timeouts/constants, add CLI prompt helpers, and introduce webhook/script actions with safety and SNMP refactors
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/nupst',
|
name: '@serve.zone/nupst',
|
||||||
version: '5.2.0',
|
version: '5.2.1',
|
||||||
description: 'Network UPS Shutdown Tool - Monitor SNMP-enabled UPS devices and orchestrate graceful system shutdowns during power emergencies'
|
description: 'Network UPS Shutdown Tool - Monitor SNMP-enabled UPS devices and orchestrate graceful system shutdowns during power emergencies'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -467,18 +467,27 @@ export class UpsHandler {
|
|||||||
* @param config Current configuration or individual UPS configuration
|
* @param config Current configuration or individual UPS configuration
|
||||||
*/
|
*/
|
||||||
private displayTestConfig(config: IUpsConfig | INupstConfig): void {
|
private displayTestConfig(config: IUpsConfig | INupstConfig): void {
|
||||||
// Check if this is a UPS device or full configuration
|
// Type guard: IUpsConfig has 'id' and 'name' at root level, INupstConfig doesn't
|
||||||
const isUpsConfig = config.snmp;
|
const isUpsConfig = 'id' in config && 'name' in config;
|
||||||
const snmpConfig = isUpsConfig ? config.snmp : config.snmp || {};
|
|
||||||
const checkInterval = config.checkInterval || 30000;
|
|
||||||
|
|
||||||
// Get UPS name and ID if available
|
// Get SNMP config and other values based on config type
|
||||||
const upsName = config.name ? config.name : 'Default UPS';
|
const snmpConfig: ISnmpConfig | undefined = isUpsConfig
|
||||||
const upsId = config.id ? config.id : 'default';
|
? (config as IUpsConfig).snmp
|
||||||
|
: (config as INupstConfig).snmp;
|
||||||
|
const checkInterval = isUpsConfig ? 30000 : (config as INupstConfig).checkInterval || 30000;
|
||||||
|
const upsName = isUpsConfig ? (config as IUpsConfig).name : 'Default UPS';
|
||||||
|
const upsId = isUpsConfig ? (config as IUpsConfig).id : 'default';
|
||||||
|
|
||||||
const boxWidth = 45;
|
const boxWidth = 45;
|
||||||
logger.logBoxTitle(`Testing Configuration: ${upsName}`, boxWidth);
|
logger.logBoxTitle(`Testing Configuration: ${upsName}`, boxWidth);
|
||||||
logger.logBoxLine(`UPS ID: ${upsId}`);
|
logger.logBoxLine(`UPS ID: ${upsId}`);
|
||||||
|
|
||||||
|
if (!snmpConfig) {
|
||||||
|
logger.logBoxLine('SNMP Settings: Not configured');
|
||||||
|
logger.logBoxEnd();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
logger.logBoxLine('SNMP Settings:');
|
logger.logBoxLine('SNMP Settings:');
|
||||||
logger.logBoxLine(` Host: ${snmpConfig.host}`);
|
logger.logBoxLine(` Host: ${snmpConfig.host}`);
|
||||||
logger.logBoxLine(` Port: ${snmpConfig.port}`);
|
logger.logBoxLine(` Port: ${snmpConfig.port}`);
|
||||||
@@ -514,9 +523,10 @@ export class UpsHandler {
|
|||||||
logger.logBoxLine(` Battery Runtime: ${snmpConfig.customOIDs.BATTERY_RUNTIME || 'Not set'}`);
|
logger.logBoxLine(` Battery Runtime: ${snmpConfig.customOIDs.BATTERY_RUNTIME || 'Not set'}`);
|
||||||
}
|
}
|
||||||
// Show group assignments if this is a UPS config
|
// Show group assignments if this is a UPS config
|
||||||
if (config.groups && Array.isArray(config.groups)) {
|
if (isUpsConfig) {
|
||||||
|
const groups = (config as IUpsConfig).groups;
|
||||||
logger.logBoxLine(
|
logger.logBoxLine(
|
||||||
`Group Assignments: ${config.groups.length === 0 ? 'None' : config.groups.join(', ')}`,
|
`Group Assignments: ${groups.length === 0 ? 'None' : groups.join(', ')}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,15 +539,23 @@ export class UpsHandler {
|
|||||||
* @param config Current UPS configuration or legacy config
|
* @param config Current UPS configuration or legacy config
|
||||||
*/
|
*/
|
||||||
private async testConnection(config: IUpsConfig | INupstConfig): Promise<void> {
|
private async testConnection(config: IUpsConfig | INupstConfig): Promise<void> {
|
||||||
const upsId = config.id || 'default';
|
// Type guard: IUpsConfig has 'id' and 'name' at root level
|
||||||
const upsName = config.name || 'Default UPS';
|
const isUpsConfig = 'id' in config && 'name' in config;
|
||||||
|
const upsId = isUpsConfig ? (config as IUpsConfig).id : 'default';
|
||||||
|
const upsName = isUpsConfig ? (config as IUpsConfig).name : 'Default UPS';
|
||||||
logger.log(`\nTesting connection to UPS: ${upsName} (${upsId})...`);
|
logger.log(`\nTesting connection to UPS: ${upsName} (${upsId})...`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create a test config with a short timeout
|
// Get SNMP config based on config type
|
||||||
const snmpConfig = config.snmp ? config.snmp : config.snmp;
|
const snmpConfig: ISnmpConfig | undefined = isUpsConfig
|
||||||
|
? (config as IUpsConfig).snmp
|
||||||
|
: (config as INupstConfig).snmp;
|
||||||
|
|
||||||
const testConfig = {
|
if (!snmpConfig) {
|
||||||
|
throw new Error('SNMP configuration not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const testConfig: ISnmpConfig = {
|
||||||
...snmpConfig,
|
...snmpConfig,
|
||||||
timeout: Math.min(snmpConfig.timeout, 10000), // Use at most 10 seconds for testing
|
timeout: Math.min(snmpConfig.timeout, 10000), // Use at most 10 seconds for testing
|
||||||
};
|
};
|
||||||
@@ -675,17 +693,17 @@ export class UpsHandler {
|
|||||||
if (secLevel === 1) {
|
if (secLevel === 1) {
|
||||||
snmpConfig.securityLevel = 'noAuthNoPriv';
|
snmpConfig.securityLevel = 'noAuthNoPriv';
|
||||||
// No auth, no priv - clear out authentication and privacy settings
|
// No auth, no priv - clear out authentication and privacy settings
|
||||||
snmpConfig.authProtocol = '';
|
snmpConfig.authProtocol = undefined;
|
||||||
snmpConfig.authKey = '';
|
snmpConfig.authKey = undefined;
|
||||||
snmpConfig.privProtocol = '';
|
snmpConfig.privProtocol = undefined;
|
||||||
snmpConfig.privKey = '';
|
snmpConfig.privKey = undefined;
|
||||||
// Set appropriate timeout for security level
|
// Set appropriate timeout for security level
|
||||||
snmpConfig.timeout = 5000; // 5 seconds for basic security
|
snmpConfig.timeout = 5000; // 5 seconds for basic security
|
||||||
} else if (secLevel === 2) {
|
} else if (secLevel === 2) {
|
||||||
snmpConfig.securityLevel = 'authNoPriv';
|
snmpConfig.securityLevel = 'authNoPriv';
|
||||||
// Auth, no priv - clear out privacy settings
|
// Auth, no priv - clear out privacy settings
|
||||||
snmpConfig.privProtocol = '';
|
snmpConfig.privProtocol = undefined;
|
||||||
snmpConfig.privKey = '';
|
snmpConfig.privKey = undefined;
|
||||||
// Set appropriate timeout for security level
|
// Set appropriate timeout for security level
|
||||||
snmpConfig.timeout = 10000; // 10 seconds for authentication
|
snmpConfig.timeout = 10000; // 10 seconds for authentication
|
||||||
} else {
|
} else {
|
||||||
@@ -825,16 +843,21 @@ export class UpsHandler {
|
|||||||
logger.info('Enter custom OIDs for your UPS:');
|
logger.info('Enter custom OIDs for your UPS:');
|
||||||
logger.dim('(Leave blank to use standard RFC 1628 OIDs as fallback)');
|
logger.dim('(Leave blank to use standard RFC 1628 OIDs as fallback)');
|
||||||
|
|
||||||
// Custom OIDs
|
// Custom OIDs - prompt for essential OIDs
|
||||||
const powerStatusOID = await prompt('Power Status OID: ');
|
const powerStatusOID = await prompt('Power Status OID: ');
|
||||||
const batteryCapacityOID = await prompt('Battery Capacity OID: ');
|
const batteryCapacityOID = await prompt('Battery Capacity OID: ');
|
||||||
const batteryRuntimeOID = await prompt('Battery Runtime OID: ');
|
const batteryRuntimeOID = await prompt('Battery Runtime OID: ');
|
||||||
|
|
||||||
// Create custom OIDs object
|
// Create custom OIDs object with all required fields
|
||||||
|
// Empty strings will use RFC 1628 fallback for non-essential OIDs
|
||||||
snmpConfig.customOIDs = {
|
snmpConfig.customOIDs = {
|
||||||
POWER_STATUS: powerStatusOID.trim(),
|
POWER_STATUS: powerStatusOID.trim(),
|
||||||
BATTERY_CAPACITY: batteryCapacityOID.trim(),
|
BATTERY_CAPACITY: batteryCapacityOID.trim(),
|
||||||
BATTERY_RUNTIME: batteryRuntimeOID.trim(),
|
BATTERY_RUNTIME: batteryRuntimeOID.trim(),
|
||||||
|
OUTPUT_LOAD: '',
|
||||||
|
OUTPUT_POWER: '',
|
||||||
|
OUTPUT_VOLTAGE: '',
|
||||||
|
OUTPUT_CURRENT: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,11 +142,14 @@ WantedBy=multi-user.target
|
|||||||
private async displayVersionInfo(): Promise<void> {
|
private async displayVersionInfo(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const nupst = this.daemon.getNupstSnmp().getNupst();
|
const nupst = this.daemon.getNupstSnmp().getNupst();
|
||||||
|
if (!nupst) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const version = nupst.getVersion();
|
const version = nupst.getVersion();
|
||||||
|
|
||||||
// Check for updates
|
// Check for updates
|
||||||
const updateAvailable = await nupst.checkForUpdates();
|
const updateAvailable = await nupst.checkForUpdates();
|
||||||
|
|
||||||
// Display version info
|
// Display version info
|
||||||
if (updateAvailable) {
|
if (updateAvailable) {
|
||||||
const updateStatus = nupst.getUpdateStatus();
|
const updateStatus = nupst.getUpdateStatus();
|
||||||
@@ -161,13 +164,15 @@ WantedBy=multi-user.target
|
|||||||
`${theme.dim('NUPST')} ${theme.dim('v' + version)} ${symbols.success} ${theme.success('Up to date')}`,
|
`${theme.dim('NUPST')} ${theme.dim('v' + version)} ${symbols.success} ${theme.success('Up to date')}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
// If version check fails, show at least the current version
|
// If version check fails, show at least the current version
|
||||||
try {
|
try {
|
||||||
const nupst = this.daemon.getNupstSnmp().getNupst();
|
const nupst = this.daemon.getNupstSnmp().getNupst();
|
||||||
const version = nupst.getVersion();
|
if (nupst) {
|
||||||
logger.log('');
|
const version = nupst.getVersion();
|
||||||
logger.log(`${theme.dim('NUPST')} ${theme.dim('v' + version)}`);
|
logger.log('');
|
||||||
|
logger.log(`${theme.dim('NUPST')} ${theme.dim('v' + version)}`);
|
||||||
|
}
|
||||||
} catch (_innerError) {
|
} catch (_innerError) {
|
||||||
// Silently fail if we can't even get the version
|
// Silently fail if we can't even get the version
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user