Files
web-app-launcher/src/lib/server/integrations/nut/index.ts
T
alexei.dolgolyov d73fb9c680 feat(service-integrations): phases 3-8 — six service integrations
- NUT/UPS: TCP protocol client, battery/load gauges, runtime card, power alert banner
- Pi-hole: DNS stats summary, top blocked domains, query log, gravity status
- Portainer: Container summary/list, stack status via Docker API
- Gitea: Recent commits, open PRs, releases across repos
- Nginx Proxy Manager: Proxy hosts, SSL certificate expiry alerts, upstream status
- Authentik: User stats, login events feed, brute force detection alerts
- All integrations registered in registry with auto-discovery
2026-03-25 22:12:31 +03:00

111 lines
3.0 KiB
TypeScript

import type { Integration, IntegrationData, IntegrationEndpoint } from '../types.js';
import { nutAuthConfigSchema, type NutAuthConfig } from './schema.js';
import { listVariables, listUps } from './client.js';
import { toBatteryGauge, toLoadGauge, toRuntimeCard, toStatusAlert } from './transform.js';
import { wrapError } from '../base.js';
const endpoints: readonly IntegrationEndpoint[] = [
{
id: 'battery-status',
name: 'Battery Status',
description: 'Battery charge percentage',
renderer: 'gauge',
refreshInterval: 30
},
{
id: 'load',
name: 'UPS Load',
description: 'Current UPS load percentage',
renderer: 'gauge',
refreshInterval: 30
},
{
id: 'runtime',
name: 'Runtime Remaining',
description: 'Estimated battery runtime',
renderer: 'stat-card',
refreshInterval: 30
},
{
id: 'ups-status',
name: 'UPS Status',
description: 'Current UPS status with battery alert',
renderer: 'alert-banner',
refreshInterval: 15
}
];
async function getVarsMap(config: NutAuthConfig): Promise<Map<string, string>> {
const vars = await listVariables(config.nutHost, config.nutPort, config.upsName);
return new Map(vars.map((v) => [v.name, v.value]));
}
export const nutIntegration: Integration = {
id: 'nut',
name: 'NUT (UPS)',
icon: 'battery-charging',
description: 'Monitor UPS battery, load, and power status via NUT protocol',
authConfigSchema: nutAuthConfigSchema,
endpoints,
async testConnection(_appUrl: string, config: Record<string, unknown>) {
try {
const parsed = nutAuthConfigSchema.parse(config);
const upsList = await listUps(parsed.nutHost, parsed.nutPort);
if (upsList.length === 0) {
return { success: false, message: 'Connected but no UPS devices found' };
}
const found = upsList.includes(parsed.upsName);
return found
? { success: true, message: `Connected. UPS "${parsed.upsName}" found.` }
: {
success: false,
message: `Connected but UPS "${parsed.upsName}" not found. Available: ${upsList.join(', ')}`
};
} catch (err) {
return {
success: false,
message: err instanceof Error ? err.message : 'Connection failed'
};
}
},
async fetchData(
_appUrl: string,
config: Record<string, unknown>,
endpointId: string
): Promise<IntegrationData> {
try {
const parsed = nutAuthConfigSchema.parse(config);
const vars = await getVarsMap(parsed);
let data;
let renderer;
switch (endpointId) {
case 'battery-status':
data = toBatteryGauge(vars);
renderer = 'gauge' as const;
break;
case 'load':
data = toLoadGauge(vars);
renderer = 'gauge' as const;
break;
case 'runtime':
data = toRuntimeCard(vars);
renderer = 'stat-card' as const;
break;
case 'ups-status':
data = toStatusAlert(vars);
renderer = 'alert-banner' as const;
break;
default:
throw new Error(`Unknown endpoint: ${endpointId}`);
}
return { endpointId, renderer, data, fetchedAt: new Date().toISOString() };
} catch (err) {
throw wrapError('nut', endpointId, err);
}
}
};