Major type safety improvements throughout the codebase:
- Updated DEFAULT_CONFIG version to 4.2
- Replaced 'any' with proper types in systemd.ts:
  * displaySingleUpsStatus now uses IUpsConfig and NupstSnmp types
  * Fixed legacy config handling to use proper IUpsConfig format
  * Removed inline 'any' type annotations
- Replaced 'any' with proper types in daemon.ts:
  * emergencyUps now properly typed as { ups: IUpsConfig, status: ISnmpUpsStatus }
  * Exported IUpsStatus interface for reuse
  * Added ISnmpUpsStatus import to disambiguate from daemon's IUpsStatus
- Replaced 'any' with Record<string, unknown> in migration system:
  * Updated BaseMigration abstract class signatures
  * Updated MigrationRunner.run() signature
  * Updated migration-v4.0-to-v4.1.ts to use proper types
  * Migrations use Record<string, unknown> because they deal with
    unknown config schemas that are being upgraded
Benefits:
- TypeScript now catches type errors at compile time
- Would have caught the ups.thresholds bug earlier
- Better IDE autocomplete and type checking
- More maintainable and self-documenting code
		
	
		
			
				
	
	
		
			128 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			128 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { BaseMigration } from './base-migration.ts';
 | |
| import { logger } from '../logger.ts';
 | |
| 
 | |
| /**
 | |
|  * Migration from v4.0 to v4.1
 | |
|  *
 | |
|  * Major changes:
 | |
|  * 1. Moves thresholds from UPS level to action level
 | |
|  * 2. Creates default shutdown action for UPS devices that had thresholds
 | |
|  * 3. Adds empty actions array to UPS devices without actions
 | |
|  * 4. Adds empty actions array to groups
 | |
|  *
 | |
|  * Transforms v4.0 format (with UPS-level thresholds):
 | |
|  * {
 | |
|  *   version: "4.0",
 | |
|  *   upsDevices: [
 | |
|  *     {
 | |
|  *       id: "ups-1",
 | |
|  *       name: "UPS 1",
 | |
|  *       snmp: {...},
 | |
|  *       thresholds: { battery: 60, runtime: 20 },  // UPS-level
 | |
|  *       groups: []
 | |
|  *     }
 | |
|  *   ]
 | |
|  * }
 | |
|  *
 | |
|  * To v4.1 format (with action-level thresholds):
 | |
|  * {
 | |
|  *   version: "4.1",
 | |
|  *   upsDevices: [
 | |
|  *     {
 | |
|  *       id: "ups-1",
 | |
|  *       name: "UPS 1",
 | |
|  *       snmp: {...},
 | |
|  *       groups: [],
 | |
|  *       actions: [  // Thresholds moved here
 | |
|  *         {
 | |
|  *           type: "shutdown",
 | |
|  *           thresholds: { battery: 60, runtime: 20 },
 | |
|  *           triggerMode: "onlyThresholds",
 | |
|  *           shutdownDelay: 5
 | |
|  *         }
 | |
|  *       ]
 | |
|  *     }
 | |
|  *   ]
 | |
|  * }
 | |
|  */
 | |
| export class MigrationV4_0ToV4_1 extends BaseMigration {
 | |
|   readonly fromVersion = '4.0';
 | |
|   readonly toVersion = '4.1';
 | |
| 
 | |
|   async shouldRun(config: Record<string, unknown>): Promise<boolean> {
 | |
|     // 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 (Array.isArray(config.upsDevices) && config.upsDevices.length > 0) {
 | |
|       const firstDevice = config.upsDevices[0] as Record<string, unknown>;
 | |
|       // v4.0 has thresholds at UPS level, v4.1 has them in actions
 | |
|       return firstDevice.thresholds !== undefined;
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   async migrate(config: Record<string, unknown>): Promise<Record<string, unknown>> {
 | |
|     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 devices = (config.upsDevices as Array<Record<string, unknown>>) || [];
 | |
|     const migratedDevices = devices.map((device) => {
 | |
|       const migrated: Record<string, unknown> = {
 | |
|         id: device.id,
 | |
|         name: device.name,
 | |
|         snmp: device.snmp,
 | |
|         groups: device.groups || [],
 | |
|       };
 | |
| 
 | |
|       // If device has thresholds at UPS level, convert to shutdown action
 | |
|       const deviceThresholds = device.thresholds as { battery: number; runtime: number } | undefined;
 | |
|       if (deviceThresholds) {
 | |
|         migrated.actions = [
 | |
|           {
 | |
|             type: 'shutdown',
 | |
|             thresholds: {
 | |
|               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: ${deviceThresholds.battery}%, runtime: ${deviceThresholds.runtime}min)`,
 | |
|         );
 | |
|       } else {
 | |
|         // No thresholds, just add empty actions array
 | |
|         migrated.actions = device.actions || [];
 | |
|       }
 | |
| 
 | |
|       return migrated;
 | |
|     });
 | |
| 
 | |
|     // Add actions to groups
 | |
|     const groups = (config.groups as Array<Record<string, unknown>>) || [];
 | |
|     const migratedGroups = groups.map((group) => ({
 | |
|       ...group,
 | |
|       actions: group.actions || [],
 | |
|     }));
 | |
| 
 | |
|     const result = {
 | |
|       version: this.toVersion,
 | |
|       upsDevices: migratedDevices,
 | |
|       groups: migratedGroups,
 | |
|       checkInterval: config.checkInterval || 30000,
 | |
|     };
 | |
| 
 | |
|     logger.success(
 | |
|       `${this.getName()}: Migration complete (${migratedDevices.length} devices, ${migratedGroups.length} groups updated)`,
 | |
|     );
 | |
|     return result;
 | |
|   }
 | |
| }
 |