Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
a7113d0387 | |||
61d4e9037a | |||
caced2718f |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@serve.zone/nupst",
|
||||
"version": "4.1.5",
|
||||
"version": "4.1.6",
|
||||
"exports": "./mod.ts",
|
||||
"tasks": {
|
||||
"dev": "deno run --allow-all mod.ts",
|
||||
|
202
ts/daemon.ts
202
ts/daemon.ts
@@ -5,8 +5,9 @@ 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 { logger } from './logger.ts';
|
||||
import { logger, type ITableColumn } from './logger.ts';
|
||||
import { MigrationRunner } from './migrations/index.ts';
|
||||
import { theme, symbols, getBatteryColor, getRuntimeColor, formatPowerStatus } from './colors.ts';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
const execFileAsync = promisify(execFile);
|
||||
@@ -310,29 +311,57 @@ export class NupstDaemon {
|
||||
* Log the loaded configuration settings
|
||||
*/
|
||||
private logConfigLoaded(): void {
|
||||
const boxWidth = 50;
|
||||
logger.logBoxTitle('Configuration Loaded', boxWidth);
|
||||
|
||||
if (this.config.upsDevices && this.config.upsDevices.length > 0) {
|
||||
logger.logBoxLine(`UPS Devices: ${this.config.upsDevices.length}`);
|
||||
for (const ups of this.config.upsDevices) {
|
||||
logger.logBoxLine(` - ${ups.name} (${ups.id}): ${ups.snmp.host}:${ups.snmp.port}`);
|
||||
}
|
||||
} else {
|
||||
logger.logBoxLine('No UPS devices configured');
|
||||
}
|
||||
|
||||
if (this.config.groups && this.config.groups.length > 0) {
|
||||
logger.logBoxLine(`Groups: ${this.config.groups.length}`);
|
||||
for (const group of this.config.groups) {
|
||||
logger.logBoxLine(` - ${group.name} (${group.id}): ${group.mode} mode`);
|
||||
}
|
||||
} else {
|
||||
logger.logBoxLine('No Groups configured');
|
||||
}
|
||||
|
||||
logger.log('');
|
||||
logger.logBoxTitle('Configuration Loaded', 70, 'success');
|
||||
logger.logBoxLine(`Check Interval: ${this.config.checkInterval / 1000} seconds`);
|
||||
logger.logBoxEnd();
|
||||
logger.log('');
|
||||
|
||||
// Display UPS devices in a table
|
||||
if (this.config.upsDevices && this.config.upsDevices.length > 0) {
|
||||
logger.info(`UPS Devices (${this.config.upsDevices.length}):`);
|
||||
|
||||
const upsColumns: Array<{ header: string; key: string; align?: 'left' | 'right'; color?: (val: string) => string }> = [
|
||||
{ header: 'Name', key: 'name', align: 'left', color: theme.highlight },
|
||||
{ header: 'ID', key: 'id', align: 'left', color: theme.dim },
|
||||
{ header: 'Host:Port', key: 'host', align: 'left', color: theme.info },
|
||||
{ header: 'Battery/Runtime', key: 'thresholds', align: 'left' },
|
||||
];
|
||||
|
||||
const upsRows: Array<Record<string, string>> = this.config.upsDevices.map((ups) => ({
|
||||
name: ups.name,
|
||||
id: ups.id,
|
||||
host: `${ups.snmp.host}:${ups.snmp.port}`,
|
||||
thresholds: `${ups.thresholds.battery}% / ${ups.thresholds.runtime} min`,
|
||||
}));
|
||||
|
||||
logger.logTable(upsColumns, upsRows);
|
||||
logger.log('');
|
||||
} else {
|
||||
logger.warn('No UPS devices configured');
|
||||
logger.log('');
|
||||
}
|
||||
|
||||
// Display groups in a table
|
||||
if (this.config.groups && this.config.groups.length > 0) {
|
||||
logger.info(`Groups (${this.config.groups.length}):`);
|
||||
|
||||
const groupColumns: Array<{ header: string; key: string; align?: 'left' | 'right'; color?: (val: string) => string }> = [
|
||||
{ header: 'Name', key: 'name', align: 'left', color: theme.highlight },
|
||||
{ header: 'ID', key: 'id', align: 'left', color: theme.dim },
|
||||
{ header: 'Mode', key: 'mode', align: 'left', color: theme.info },
|
||||
];
|
||||
|
||||
const groupRows: Array<Record<string, string>> = this.config.groups.map((group) => ({
|
||||
name: group.name,
|
||||
id: group.id,
|
||||
mode: group.mode,
|
||||
}));
|
||||
|
||||
logger.logTable(groupColumns, groupRows);
|
||||
logger.log('');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -428,9 +457,13 @@ export class NupstDaemon {
|
||||
|
||||
// Check if power status changed
|
||||
if (currentStatus && currentStatus.powerStatus !== status.powerStatus) {
|
||||
logger.logBoxTitle(`Power Status Change: ${ups.name}`, 50);
|
||||
logger.logBoxLine(`Status changed: ${currentStatus.powerStatus} → ${status.powerStatus}`);
|
||||
logger.log('');
|
||||
logger.logBoxTitle(`Power Status Change: ${ups.name}`, 60, 'warning');
|
||||
logger.logBoxLine(`Previous: ${formatPowerStatus(currentStatus.powerStatus)}`);
|
||||
logger.logBoxLine(`Current: ${formatPowerStatus(status.powerStatus)}`);
|
||||
logger.logBoxLine(`Time: ${new Date().toISOString()}`);
|
||||
logger.logBoxEnd();
|
||||
logger.log('');
|
||||
|
||||
updatedStatus.lastStatusChange = currentTime;
|
||||
}
|
||||
@@ -452,21 +485,38 @@ export class NupstDaemon {
|
||||
*/
|
||||
private logAllUpsStatus(): void {
|
||||
const timestamp = new Date().toISOString();
|
||||
const boxWidth = 60;
|
||||
logger.logBoxTitle('Periodic Status Update', boxWidth);
|
||||
logger.logBoxLine(`Timestamp: ${timestamp}`);
|
||||
logger.logBoxLine('');
|
||||
|
||||
logger.log('');
|
||||
logger.logBoxTitle('Periodic Status Update', 70, 'info');
|
||||
logger.logBoxLine(`Timestamp: ${timestamp}`);
|
||||
logger.logBoxEnd();
|
||||
logger.log('');
|
||||
|
||||
// Build table data
|
||||
const columns: Array<{ header: string; key: string; align?: 'left' | 'right'; color?: (val: string) => string }> = [
|
||||
{ header: 'UPS Name', key: 'name', align: 'left', color: theme.highlight },
|
||||
{ header: 'ID', key: 'id', align: 'left', color: theme.dim },
|
||||
{ header: 'Power Status', key: 'powerStatus', align: 'left' },
|
||||
{ header: 'Battery', key: 'battery', align: 'right' },
|
||||
{ header: 'Runtime', key: 'runtime', align: 'right' },
|
||||
];
|
||||
|
||||
const rows: Array<Record<string, string>> = [];
|
||||
for (const [id, status] of this.upsStatus.entries()) {
|
||||
logger.logBoxLine(`UPS: ${status.name} (${id})`);
|
||||
logger.logBoxLine(` Power Status: ${status.powerStatus}`);
|
||||
logger.logBoxLine(
|
||||
` Battery: ${status.batteryCapacity}% | Runtime: ${status.batteryRuntime} min`,
|
||||
);
|
||||
logger.logBoxLine('');
|
||||
const batteryColor = getBatteryColor(status.batteryCapacity);
|
||||
const runtimeColor = getRuntimeColor(status.batteryRuntime);
|
||||
|
||||
rows.push({
|
||||
name: status.name,
|
||||
id: id,
|
||||
powerStatus: formatPowerStatus(status.powerStatus),
|
||||
battery: batteryColor(status.batteryCapacity + '%'),
|
||||
runtime: runtimeColor(status.batteryRuntime + ' min'),
|
||||
});
|
||||
}
|
||||
|
||||
logger.logBoxEnd();
|
||||
logger.logTable(columns, rows);
|
||||
logger.log('');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -745,38 +795,61 @@ export class NupstDaemon {
|
||||
const MAX_MONITORING_TIME = 5 * 60 * 1000; // Max 5 minutes of monitoring
|
||||
const startTime = Date.now();
|
||||
|
||||
logger.log(
|
||||
`Emergency shutdown threshold: ${EMERGENCY_RUNTIME_THRESHOLD} minutes remaining battery runtime`,
|
||||
);
|
||||
logger.log('');
|
||||
logger.logBoxTitle('Shutdown Monitoring Active', 60, 'warning');
|
||||
logger.logBoxLine(`Emergency threshold: ${EMERGENCY_RUNTIME_THRESHOLD} minutes runtime`);
|
||||
logger.logBoxLine(`Check interval: ${CHECK_INTERVAL / 1000} seconds`);
|
||||
logger.logBoxLine(`Max monitoring time: ${MAX_MONITORING_TIME / 1000} seconds`);
|
||||
logger.logBoxEnd();
|
||||
logger.log('');
|
||||
|
||||
// Continue monitoring until max monitoring time is reached
|
||||
while (Date.now() - startTime < MAX_MONITORING_TIME) {
|
||||
try {
|
||||
logger.log('Checking UPS status during shutdown...');
|
||||
logger.info('Checking UPS status during shutdown...');
|
||||
|
||||
// Build table for UPS status during shutdown
|
||||
const columns: Array<{ header: string; key: string; align?: 'left' | 'right'; color?: (val: string) => string }> = [
|
||||
{ header: 'UPS Name', key: 'name', align: 'left', color: theme.highlight },
|
||||
{ header: 'Battery', key: 'battery', align: 'right' },
|
||||
{ header: 'Runtime', key: 'runtime', align: 'right' },
|
||||
{ header: 'Status', key: 'status', align: 'left' },
|
||||
];
|
||||
|
||||
const rows: Array<Record<string, string>> = [];
|
||||
let emergencyDetected = false;
|
||||
let emergencyUps: any = null;
|
||||
|
||||
// Check all UPS devices
|
||||
for (const ups of this.config.upsDevices) {
|
||||
try {
|
||||
const status = await this.snmp.getUpsStatus(ups.snmp);
|
||||
|
||||
logger.log(
|
||||
`UPS ${ups.name}: Battery ${status.batteryCapacity}%, Runtime: ${status.batteryRuntime} minutes`,
|
||||
);
|
||||
const batteryColor = getBatteryColor(status.batteryCapacity);
|
||||
const runtimeColor = getRuntimeColor(status.batteryRuntime);
|
||||
|
||||
// If any UPS battery runtime gets critically low, force immediate shutdown
|
||||
if (status.batteryRuntime < EMERGENCY_RUNTIME_THRESHOLD) {
|
||||
logger.logBoxTitle('EMERGENCY SHUTDOWN', 50);
|
||||
logger.logBoxLine(
|
||||
`UPS ${ups.name} runtime critically low: ${status.batteryRuntime} minutes`,
|
||||
);
|
||||
logger.logBoxLine('Forcing immediate shutdown!');
|
||||
logger.logBoxEnd();
|
||||
const isCritical = status.batteryRuntime < EMERGENCY_RUNTIME_THRESHOLD;
|
||||
|
||||
// Force immediate shutdown
|
||||
await this.forceImmediateShutdown();
|
||||
return;
|
||||
rows.push({
|
||||
name: ups.name,
|
||||
battery: batteryColor(status.batteryCapacity + '%'),
|
||||
runtime: runtimeColor(status.batteryRuntime + ' min'),
|
||||
status: isCritical ? theme.error('CRITICAL!') : theme.success('OK'),
|
||||
});
|
||||
|
||||
// If any UPS battery runtime gets critically low, flag for immediate shutdown
|
||||
if (isCritical && !emergencyDetected) {
|
||||
emergencyDetected = true;
|
||||
emergencyUps = { ups, status };
|
||||
}
|
||||
} catch (upsError) {
|
||||
rows.push({
|
||||
name: ups.name,
|
||||
battery: theme.error('N/A'),
|
||||
runtime: theme.error('N/A'),
|
||||
status: theme.error('ERROR'),
|
||||
});
|
||||
|
||||
logger.error(
|
||||
`Error checking UPS ${ups.name} during shutdown: ${
|
||||
upsError instanceof Error ? upsError.message : String(upsError)
|
||||
@@ -785,6 +858,27 @@ export class NupstDaemon {
|
||||
}
|
||||
}
|
||||
|
||||
// Display the table
|
||||
logger.logTable(columns, rows);
|
||||
logger.log('');
|
||||
|
||||
// If emergency detected, trigger immediate shutdown
|
||||
if (emergencyDetected && emergencyUps) {
|
||||
logger.log('');
|
||||
logger.logBoxTitle('EMERGENCY SHUTDOWN', 60, 'error');
|
||||
logger.logBoxLine(
|
||||
`UPS ${emergencyUps.ups.name} runtime critically low: ${emergencyUps.status.batteryRuntime} minutes`,
|
||||
);
|
||||
logger.logBoxLine(`Emergency threshold: ${EMERGENCY_RUNTIME_THRESHOLD} minutes`);
|
||||
logger.logBoxLine('Forcing immediate shutdown!');
|
||||
logger.logBoxEnd();
|
||||
logger.log('');
|
||||
|
||||
// Force immediate shutdown
|
||||
await this.forceImmediateShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait before checking again
|
||||
await this.sleep(CHECK_INTERVAL);
|
||||
} catch (error) {
|
||||
@@ -797,7 +891,9 @@ export class NupstDaemon {
|
||||
}
|
||||
}
|
||||
|
||||
logger.log('UPS monitoring during shutdown completed');
|
||||
logger.log('');
|
||||
logger.success('UPS monitoring during shutdown completed');
|
||||
logger.log('');
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user