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
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user