diff --git a/ts/daemon.ts b/ts/daemon.ts index e5406eb..2deb114 100644 --- a/ts/daemon.ts +++ b/ts/daemon.ts @@ -4,7 +4,7 @@ import * as path from 'node:path'; import { exec, execFile } from 'node:child_process'; import { promisify } from 'node:util'; import { NupstSnmp } from './snmp/manager.ts'; -import type { ISnmpConfig } from './snmp/types.ts'; +import type { ISnmpConfig, IUpsStatus as ISnmpUpsStatus } from './snmp/types.ts'; import { logger, type ITableColumn } from './logger.ts'; import { MigrationRunner } from './migrations/index.ts'; import { theme, symbols, getBatteryColor, getRuntimeColor, formatPowerStatus } from './colors.ts'; @@ -76,7 +76,7 @@ export interface INupstConfig { /** * UPS status tracking interface */ -interface IUpsStatus { +export interface IUpsStatus { id: string; name: string; powerStatus: 'online' | 'onBattery' | 'unknown'; @@ -96,7 +96,7 @@ export class NupstDaemon { /** Default configuration */ private readonly DEFAULT_CONFIG: INupstConfig = { - version: '4.1', + version: '4.2', upsDevices: [ { id: 'default', @@ -171,11 +171,13 @@ export class NupstDaemon { const { config: migratedConfig, migrated } = await migrationRunner.run(parsedConfig); // Save migrated config back to disk if any migrations ran + // Cast to INupstConfig since migrations ensure the output is valid + const validConfig = migratedConfig as unknown as INupstConfig; if (migrated) { - this.config = migratedConfig; + this.config = validConfig; await this.saveConfig(this.config); } else { - this.config = migratedConfig; + this.config = validConfig; } return this.config; @@ -760,7 +762,7 @@ export class NupstDaemon { const rows: Array> = []; let emergencyDetected = false; - let emergencyUps: any = null; + let emergencyUps: { ups: IUpsConfig; status: ISnmpUpsStatus } | null = null; // Check all UPS devices for (const ups of this.config.upsDevices) { diff --git a/ts/migrations/base-migration.ts b/ts/migrations/base-migration.ts index 38ef72d..1bcfb08 100644 --- a/ts/migrations/base-migration.ts +++ b/ts/migrations/base-migration.ts @@ -28,18 +28,18 @@ export abstract class BaseMigration { /** * Check if this migration should run on the given config * - * @param config - Raw configuration object to check + * @param config - Raw configuration object to check (unknown schema for migrations) * @returns True if migration should run, false otherwise */ - abstract shouldRun(config: any): Promise; + abstract shouldRun(config: Record): Promise; /** * Perform the migration on the given config * - * @param config - Raw configuration object to migrate + * @param config - Raw configuration object to migrate (unknown schema for migrations) * @returns Migrated configuration object */ - abstract migrate(config: any): Promise; + abstract migrate(config: Record): Promise>; /** * Get human-readable name for this migration diff --git a/ts/migrations/migration-runner.ts b/ts/migrations/migration-runner.ts index e1ca4aa..16827d4 100644 --- a/ts/migrations/migration-runner.ts +++ b/ts/migrations/migration-runner.ts @@ -32,7 +32,9 @@ export class MigrationRunner { * @param config - Raw configuration object to migrate * @returns Migrated configuration and whether migrations ran */ - async run(config: any): Promise<{ config: any; migrated: boolean }> { + async run( + config: Record, + ): Promise<{ config: Record; migrated: boolean }> { let currentConfig = config; let anyMigrationsRan = false; diff --git a/ts/migrations/migration-v4.0-to-v4.1.ts b/ts/migrations/migration-v4.0-to-v4.1.ts index 7980bf4..33b6c2c 100644 --- a/ts/migrations/migration-v4.0-to-v4.1.ts +++ b/ts/migrations/migration-v4.0-to-v4.1.ts @@ -49,15 +49,15 @@ export class MigrationV4_0ToV4_1 extends BaseMigration { readonly fromVersion = '4.0'; readonly toVersion = '4.1'; - async shouldRun(config: any): Promise { + async shouldRun(config: Record): Promise { // Run if config is version 4.0 if (config.version === '4.0') { return true; } // Also run if config has upsDevices with thresholds at UPS level (v4.0 format) - if (config.upsDevices && config.upsDevices.length > 0) { - const firstDevice = config.upsDevices[0]; + if (Array.isArray(config.upsDevices) && config.upsDevices.length > 0) { + const firstDevice = config.upsDevices[0] as Record; // v4.0 has thresholds at UPS level, v4.1 has them in actions return firstDevice.thresholds !== undefined; } @@ -65,14 +65,15 @@ export class MigrationV4_0ToV4_1 extends BaseMigration { return false; } - async migrate(config: any): Promise { + async migrate(config: Record): Promise> { logger.info(`${this.getName()}: Migrating v4.0 config to v4.1 format...`); logger.dim(` - Moving thresholds from UPS level to action level`); logger.dim(` - Creating default shutdown actions from existing thresholds`); // Migrate UPS devices - const migratedDevices = (config.upsDevices || []).map((device: any) => { - const migrated: any = { + const devices = (config.upsDevices as Array>) || []; + const migratedDevices = devices.map((device) => { + const migrated: Record = { id: device.id, name: device.name, snmp: device.snmp, @@ -80,20 +81,21 @@ export class MigrationV4_0ToV4_1 extends BaseMigration { }; // If device has thresholds at UPS level, convert to shutdown action - if (device.thresholds) { + const deviceThresholds = device.thresholds as { battery: number; runtime: number } | undefined; + if (deviceThresholds) { migrated.actions = [ { type: 'shutdown', thresholds: { - battery: device.thresholds.battery, - runtime: device.thresholds.runtime, + battery: deviceThresholds.battery, + runtime: deviceThresholds.runtime, }, triggerMode: 'onlyThresholds', // Preserve old behavior (only on threshold violation) shutdownDelay: 5, // Default delay }, ]; logger.dim( - ` → ${device.name}: Created shutdown action (battery: ${device.thresholds.battery}%, runtime: ${device.thresholds.runtime}min)`, + ` → ${device.name}: Created shutdown action (battery: ${deviceThresholds.battery}%, runtime: ${deviceThresholds.runtime}min)`, ); } else { // No thresholds, just add empty actions array @@ -104,7 +106,8 @@ export class MigrationV4_0ToV4_1 extends BaseMigration { }); // Add actions to groups - const migratedGroups = (config.groups || []).map((group: any) => ({ + const groups = (config.groups as Array>) || []; + const migratedGroups = groups.map((group) => ({ ...group, actions: group.actions || [], })); diff --git a/ts/systemd.ts b/ts/systemd.ts index 9c2b3c3..ac3be90 100644 --- a/ts/systemd.ts +++ b/ts/systemd.ts @@ -1,7 +1,8 @@ import process from 'node:process'; import { promises as fs } from 'node:fs'; import { execSync } from 'node:child_process'; -import { NupstDaemon } from './daemon.ts'; +import { NupstDaemon, type IUpsConfig } from './daemon.ts'; +import { NupstSnmp } from './snmp/manager.ts'; import { logger } from './logger.ts'; import { theme, symbols, getBatteryColor, getRuntimeColor, formatPowerStatus } from './colors.ts'; @@ -277,14 +278,23 @@ WantedBy=multi-user.target await this.displaySingleUpsStatus(ups, snmp); } } else if (config.snmp) { - // Legacy single UPS configuration + // Legacy single UPS configuration (v1/v2 format) logger.info('UPS Devices (1):'); - const legacyUps = { + const legacyUps: IUpsConfig = { id: 'default', name: 'Default UPS', snmp: config.snmp, - thresholds: config.thresholds, groups: [], + actions: config.thresholds + ? [ + { + type: 'shutdown', + thresholds: config.thresholds, + triggerMode: 'onlyThresholds', + shutdownDelay: 5, + }, + ] + : [], }; await this.displaySingleUpsStatus(legacyUps, snmp); @@ -307,7 +317,7 @@ WantedBy=multi-user.target * @param ups UPS configuration * @param snmp SNMP manager */ - private async displaySingleUpsStatus(ups: any, snmp: any): Promise { + private async displaySingleUpsStatus(ups: IUpsConfig, snmp: NupstSnmp): Promise { try { // Create a test config with a short timeout const testConfig = { @@ -332,7 +342,7 @@ WantedBy=multi-user.target const batteryColor = getBatteryColor(status.batteryCapacity); // Get threshold from actions (if any action has thresholds defined) - const actionWithThresholds = ups.actions?.find((action: any) => action.thresholds); + const actionWithThresholds = ups.actions?.find((action) => action.thresholds); const batteryThreshold = actionWithThresholds?.thresholds?.battery; const batterySymbol = batteryThreshold !== undefined && status.batteryCapacity >= batteryThreshold ? symbols.success