Compare commits

...

3 Commits

Author SHA1 Message Date
bb87316dd3 fix(snmp): correct power status interpretation using OID set mappings
All checks were successful
CI / Type Check & Lint (push) Successful in 7s
CI / Build Test (Current Platform) (push) Successful in 5s
Release / build-and-release (push) Successful in 44s
CI / Build All Platforms (push) Successful in 49s
Move power status value interpretation from hardcoded logic to OID set configuration.
Each UPS model now defines its own value mappings (e.g., CyberPower: 2=online, 3=onBattery).

Fixes incorrect status display where UPS showed "On Battery" when actually online.

Changes:
- Add POWER_STATUS_VALUES to IOidSet interface
- Define value mappings for all UPS models (cyberpower, apc, eaton, tripplite, liebert)
- Refactor determinePowerStatus() to use OID set mappings instead of hardcoded values
- CyberPower now correctly interprets value 2 as online (was incorrectly onBattery)
2025-10-19 23:48:13 +00:00
d6e0a1a274 feat(cli): remove ALL ugly boxes from status output - now fully beautiful
All checks were successful
CI / Type Check & Lint (push) Successful in 6s
CI / Build Test (Current Platform) (push) Successful in 5s
Release / build-and-release (push) Successful in 44s
CI / Build All Platforms (push) Successful in 49s
Removed the last remaining ugly ASCII boxes:
- Version info box (┌─┐│└┘) that appeared at top
- Async version check box that ended randomly in middle
- Configuration error box

Now status output is 100% clean and beautiful with just colored text:

● Service: active (running)
  PID: 9120  Memory: 45.7M  CPU: 190ms

UPS Devices (2):
  ⚠ Test UPS (SNMP v1) - On Battery
    Battery: 100% ✓  Runtime: 48 min
    Host: 192.168.187.140:161

  ◯ Test UPS (SNMP v3) - Unknown
    Battery: 0% ⚠  Runtime: 0 min
    Host: 192.168.187.140:161

No boxes, just beautiful colored output with symbols!
Bumped to v4.1.0 to mark completion of beautiful CLI feature.
2025-10-19 23:01:25 +00:00
95fa4f8b0b fix(update): normalize version strings for correct comparison
All checks were successful
CI / Type Check & Lint (push) Successful in 5s
CI / Build Test (Current Platform) (push) Successful in 5s
Release / build-and-release (push) Successful in 46s
CI / Build All Platforms (push) Successful in 50s
The version check was comparing "4.0.8" (no prefix) with "v4.0.8"
(with prefix), causing it to always think an update was available.

Now both versions are normalized to have the "v" prefix before
comparison, so "Already up to date!" works correctly.
2025-10-19 22:56:35 +00:00
6 changed files with 69 additions and 52 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "@serve.zone/nupst", "name": "@serve.zone/nupst",
"version": "4.0.8", "version": "4.1.1",
"exports": "./mod.ts", "exports": "./mod.ts",
"tasks": { "tasks": {
"dev": "deno run --allow-all mod.ts", "dev": "deno run --allow-all mod.ts",

View File

@@ -145,12 +145,16 @@ export class ServiceHandler {
const release = JSON.parse(response); const release = JSON.parse(response);
const latestVersion = release.tag_name; // e.g., "v4.0.7" const latestVersion = release.tag_name; // e.g., "v4.0.7"
logger.dim(`Current version: ${currentVersion}`); // Normalize versions for comparison (ensure both have "v" prefix)
logger.dim(`Latest version: ${latestVersion}`); const normalizedCurrent = currentVersion.startsWith('v') ? currentVersion : `v${currentVersion}`;
const normalizedLatest = latestVersion.startsWith('v') ? latestVersion : `v${latestVersion}`;
logger.dim(`Current version: ${normalizedCurrent}`);
logger.dim(`Latest version: ${normalizedLatest}`);
console.log(''); console.log('');
// Compare versions (both are in format "v4.0.7") // Compare normalized versions
if (currentVersion === latestVersion) { if (normalizedCurrent === normalizedLatest) {
logger.success('Already up to date!'); logger.success('Already up to date!');
console.log(''); console.log('');
return; return;

View File

@@ -525,6 +525,7 @@ export class NupstSnmp {
/** /**
* Determine power status based on UPS model and raw value * Determine power status based on UPS model and raw value
* Uses the value mappings defined in the OID sets
* @param upsModel UPS model * @param upsModel UPS model
* @param powerStatusValue Raw power status value * @param powerStatusValue Raw power status value
* @returns Standardized power status * @returns Standardized power status
@@ -533,39 +534,28 @@ export class NupstSnmp {
upsModel: TUpsModel | undefined, upsModel: TUpsModel | undefined,
powerStatusValue: number, powerStatusValue: number,
): 'online' | 'onBattery' | 'unknown' { ): 'online' | 'onBattery' | 'unknown' {
if (upsModel === 'cyberpower') { // Get the OID set for this UPS model
// CyberPower RMCARD205: upsBaseOutputStatus values if (upsModel && upsModel !== 'custom') {
// 2=onLine, 3=onBattery, 4=onBoost, 5=onSleep, 6=off, etc. const oidSet = UpsOidSets.getOidSet(upsModel);
if (powerStatusValue === 2) {
return 'online'; // Use the value mappings if available
} else if (powerStatusValue === 3) { if (oidSet.POWER_STATUS_VALUES) {
return 'onBattery'; if (powerStatusValue === oidSet.POWER_STATUS_VALUES.online) {
} return 'online';
} else if (upsModel === 'eaton') { } else if (powerStatusValue === oidSet.POWER_STATUS_VALUES.onBattery) {
// Eaton UPS: xupsOutputSource values return 'onBattery';
// 3=normal/mains, 5=battery, etc. }
if (powerStatusValue === 3) {
return 'online';
} else if (powerStatusValue === 5) {
return 'onBattery';
}
} else if (upsModel === 'apc') {
// APC UPS: upsBasicOutputStatus values
// 2=online, 3=onBattery, etc.
if (powerStatusValue === 2) {
return 'online';
} else if (powerStatusValue === 3) {
return 'onBattery';
}
} else {
// Default interpretation for other UPS models
if (powerStatusValue === 1) {
return 'online';
} else if (powerStatusValue === 2) {
return 'onBattery';
} }
} }
// Fallback for custom or undefined models (RFC 1628 standard)
// upsOutputSource: 3=normal (mains), 5=battery
if (powerStatusValue === 3) {
return 'online';
} else if (powerStatusValue === 5) {
return 'onBattery';
}
return 'unknown'; return 'unknown';
} }

View File

@@ -11,37 +11,57 @@ export class UpsOidSets {
private static readonly UPS_OID_SETS: Record<TUpsModel, IOidSet> = { private static readonly UPS_OID_SETS: Record<TUpsModel, IOidSet> = {
// Cyberpower OIDs for RMCARD205 (based on CyberPower_MIB_v2.11) // Cyberpower OIDs for RMCARD205 (based on CyberPower_MIB_v2.11)
cyberpower: { cyberpower: {
POWER_STATUS: '1.3.6.1.4.1.3808.1.1.1.4.1.1.0', // upsBaseOutputStatus (2=online, 3=on battery) POWER_STATUS: '1.3.6.1.4.1.3808.1.1.1.4.1.1.0', // upsBaseOutputStatus
BATTERY_CAPACITY: '1.3.6.1.4.1.3808.1.1.1.2.2.1.0', // upsAdvanceBatteryCapacity (percentage) BATTERY_CAPACITY: '1.3.6.1.4.1.3808.1.1.1.2.2.1.0', // upsAdvanceBatteryCapacity (percentage)
BATTERY_RUNTIME: '1.3.6.1.4.1.3808.1.1.1.2.2.4.0', // upsAdvanceBatteryRunTimeRemaining (TimeTicks) BATTERY_RUNTIME: '1.3.6.1.4.1.3808.1.1.1.2.2.4.0', // upsAdvanceBatteryRunTimeRemaining (TimeTicks)
POWER_STATUS_VALUES: {
online: 2, // upsBaseOutputStatus: 2=onLine
onBattery: 3, // upsBaseOutputStatus: 3=onBattery
},
}, },
// APC OIDs // APC OIDs
apc: { apc: {
POWER_STATUS: '1.3.6.1.4.1.318.1.1.1.4.1.1.0', // Power status (1=online, 2=on battery) POWER_STATUS: '1.3.6.1.4.1.318.1.1.1.4.1.1.0', // upsBasicOutputStatus
BATTERY_CAPACITY: '1.3.6.1.4.1.318.1.1.1.2.2.1.0', // Battery capacity in percentage BATTERY_CAPACITY: '1.3.6.1.4.1.318.1.1.1.2.2.1.0', // Battery capacity in percentage
BATTERY_RUNTIME: '1.3.6.1.4.1.318.1.1.1.2.2.3.0', // Remaining runtime in minutes BATTERY_RUNTIME: '1.3.6.1.4.1.318.1.1.1.2.2.3.0', // Remaining runtime in minutes
POWER_STATUS_VALUES: {
online: 2, // upsBasicOutputStatus: 2=onLine
onBattery: 3, // upsBasicOutputStatus: 3=onBattery
},
}, },
// Eaton OIDs // Eaton OIDs
eaton: { eaton: {
POWER_STATUS: '1.3.6.1.4.1.534.1.4.4.0', // xupsOutputSource (3=normal/mains, 5=battery) POWER_STATUS: '1.3.6.1.4.1.534.1.4.4.0', // xupsOutputSource
BATTERY_CAPACITY: '1.3.6.1.4.1.534.1.2.4.0', // xupsBatCapacity (percentage) BATTERY_CAPACITY: '1.3.6.1.4.1.534.1.2.4.0', // xupsBatCapacity (percentage)
BATTERY_RUNTIME: '1.3.6.1.4.1.534.1.2.1.0', // xupsBatTimeRemaining (seconds) BATTERY_RUNTIME: '1.3.6.1.4.1.534.1.2.1.0', // xupsBatTimeRemaining (seconds)
POWER_STATUS_VALUES: {
online: 3, // xupsOutputSource: 3=normal (mains power)
onBattery: 5, // xupsOutputSource: 5=battery
},
}, },
// TrippLite OIDs // TrippLite OIDs
tripplite: { tripplite: {
POWER_STATUS: '1.3.6.1.4.1.850.1.1.3.1.1.1.0', // Power status POWER_STATUS: '1.3.6.1.4.1.850.1.1.3.1.1.1.0', // tlUpsOutputSource
BATTERY_CAPACITY: '1.3.6.1.4.1.850.1.1.3.2.4.1.0', // Battery capacity in percentage BATTERY_CAPACITY: '1.3.6.1.4.1.850.1.1.3.2.4.1.0', // Battery capacity in percentage
BATTERY_RUNTIME: '1.3.6.1.4.1.850.1.1.3.2.2.1.0', // Remaining runtime in minutes BATTERY_RUNTIME: '1.3.6.1.4.1.850.1.1.3.2.2.1.0', // Remaining runtime in minutes
POWER_STATUS_VALUES: {
online: 2, // tlUpsOutputSource: 2=normal (mains power)
onBattery: 3, // tlUpsOutputSource: 3=onBattery
},
}, },
// Liebert/Vertiv OIDs // Liebert/Vertiv OIDs
liebert: { liebert: {
POWER_STATUS: '1.3.6.1.4.1.476.1.42.3.9.20.1.20.1.2.1.2.1', // Power status POWER_STATUS: '1.3.6.1.4.1.476.1.42.3.9.20.1.20.1.2.1.2.1', // lgpPwrOutputSource
BATTERY_CAPACITY: '1.3.6.1.4.1.476.1.42.3.9.20.1.20.1.2.1.4.1', // Battery capacity in percentage BATTERY_CAPACITY: '1.3.6.1.4.1.476.1.42.3.9.20.1.20.1.2.1.4.1', // Battery capacity in percentage
BATTERY_RUNTIME: '1.3.6.1.4.1.476.1.42.3.9.20.1.20.1.2.1.5.1', // Remaining runtime in minutes BATTERY_RUNTIME: '1.3.6.1.4.1.476.1.42.3.9.20.1.20.1.2.1.5.1', // Remaining runtime in minutes
POWER_STATUS_VALUES: {
online: 2, // lgpPwrOutputSource: 2=normal (mains power)
onBattery: 3, // lgpPwrOutputSource: 3=onBattery
},
}, },
// Custom OIDs (to be provided by the user) // Custom OIDs (to be provided by the user)

View File

@@ -28,6 +28,13 @@ export interface IOidSet {
BATTERY_CAPACITY: string; BATTERY_CAPACITY: string;
/** OID for battery runtime */ /** OID for battery runtime */
BATTERY_RUNTIME: string; BATTERY_RUNTIME: string;
/** Power status value mappings */
POWER_STATUS_VALUES?: {
/** SNMP value that indicates UPS is online (on AC power) */
online: number;
/** SNMP value that indicates UPS is on battery */
onBattery: number;
};
} }
/** /**

View File

@@ -50,11 +50,11 @@ WantedBy=multi-user.target
try { try {
await fs.access(configPath); await fs.access(configPath);
} catch (error) { } catch (error) {
const boxWidth = 50; console.log('');
logger.logBoxTitle('Configuration Error', boxWidth); console.log(`${symbols.error} ${theme.error('No configuration found')}`);
logger.logBoxLine(`No configuration file found at ${configPath}`); console.log(` ${theme.dim('Config file:')} ${configPath}`);
logger.logBoxLine("Please run 'nupst add' first to create a UPS configuration."); console.log(` ${theme.dim('Run')} ${theme.command('nupst ups add')} ${theme.dim('to create a configuration')}`);
logger.logBoxEnd(); console.log('');
throw new Error('Configuration not found'); throw new Error('Configuration not found');
} }
} }
@@ -138,16 +138,12 @@ WantedBy=multi-user.target
try { try {
// Enable debug mode if requested // Enable debug mode if requested
if (debugMode) { if (debugMode) {
const boxWidth = 45; console.log('');
logger.logBoxTitle('Debug Mode', boxWidth); logger.info('Debug Mode: SNMP debugging enabled');
logger.logBoxLine('SNMP debugging enabled - detailed logs will be shown'); console.log('');
logger.logBoxEnd();
this.daemon.getNupstSnmp().enableDebug(); this.daemon.getNupstSnmp().enableDebug();
} }
// Display version information
this.daemon.getNupstSnmp().getNupst().logVersionInfo();
// Check if config exists first // Check if config exists first
try { try {
await this.checkConfigExists(); await this.checkConfigExists();