d73fb9c680
- 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
111 lines
3.0 KiB
TypeScript
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);
|
|
}
|
|
}
|
|
};
|