74 lines
2.3 KiB
TypeScript
74 lines
2.3 KiB
TypeScript
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
|
|
import {
|
|
assertLiveTrafficSnapshot,
|
|
type LiveTrafficSnapshot,
|
|
} from '../../../shared/liveTraffic.js';
|
|
import { HarborError } from '../../../shared/errors.js';
|
|
import { sendJson } from '../response.js';
|
|
|
|
interface LiveTrafficReader {
|
|
snapshot(): unknown | Promise<unknown>;
|
|
}
|
|
|
|
interface DeviceInventoryReader {
|
|
snapshot(): unknown;
|
|
}
|
|
|
|
function record(value: unknown): Record<string, unknown> {
|
|
return value && typeof value === 'object' && !Array.isArray(value)
|
|
? value as Record<string, unknown>
|
|
: {};
|
|
}
|
|
|
|
export function enrichLiveTrafficDeviceLabels(
|
|
snapshot: LiveTrafficSnapshot,
|
|
inventory: unknown,
|
|
): LiveTrafficSnapshot {
|
|
const devices = Array.isArray(record(inventory).devices)
|
|
? (record(inventory).devices as unknown[]).map(record)
|
|
: [];
|
|
const labels = new Map(devices.flatMap((device) => {
|
|
const id = String(device.id || '');
|
|
if (!/^dev_[a-f0-9]{16}$/.test(id)) return [];
|
|
const label = [device.alias, device.hostname, device.ip]
|
|
.find((value) => typeof value === 'string' && value.trim());
|
|
return label ? [[id, String(label).trim()] as const] : [];
|
|
}));
|
|
if (!labels.size) return snapshot;
|
|
return {
|
|
...snapshot,
|
|
connections: snapshot.connections.map((connection) => {
|
|
const label = connection.origin.kind === 'device' && connection.origin.id
|
|
? labels.get(connection.origin.id)
|
|
: null;
|
|
return label ? {
|
|
...connection,
|
|
origin: { ...connection.origin, label },
|
|
} : connection;
|
|
}),
|
|
};
|
|
}
|
|
|
|
export function createLiveTrafficRoute({
|
|
traffic,
|
|
deviceInventory = null,
|
|
}: {
|
|
traffic: LiveTrafficReader | null;
|
|
deviceInventory?: DeviceInventoryReader | null;
|
|
}) {
|
|
return {
|
|
async handle(req: IncomingMessage, res: ServerResponse) {
|
|
const pathname = new URL(req.url || '/', 'http://localhost').pathname;
|
|
if (pathname !== '/api/traffic/live') return false;
|
|
if (req.method !== 'GET' || !traffic) throw new HarborError('ENDPOINT_NOT_FOUND');
|
|
const snapshot = assertLiveTrafficSnapshot(await traffic.snapshot());
|
|
const enriched = deviceInventory
|
|
? enrichLiveTrafficDeviceLabels(snapshot, deviceInventory.snapshot())
|
|
: snapshot;
|
|
sendJson(res, 200, enriched);
|
|
return true;
|
|
},
|
|
};
|
|
}
|