Migrate Harbor state and traffic history to SQLite
Build and Deploy Gateway / build-and-push (push) Successful in 1m22s
Build and Deploy Gateway / deploy (push) Successful in 16s

This commit is contained in:
2026-09-10 19:21:21 +03:00
parent ab14fc979e
commit 1ae23d848b
48 changed files with 1703 additions and 446 deletions
@@ -0,0 +1,33 @@
import type { IncomingMessage, ServerResponse } from 'node:http';
import { HarborError } from '../../../shared/errors.js';
import { assertTrafficHistorySnapshot, emptyTrafficHistory, parseTrafficHistoryQuery, type TrafficHistoryQuery } from '../../../shared/trafficHistory.js';
import { sendJson } from '../response.js';
export function createTrafficHistoryRoute({ readHistory, deviceInventory }: {
readHistory: ((query: TrafficHistoryQuery) => Promise<unknown>) | null;
deviceInventory?: { snapshot(): unknown } | null;
}) {
return {
async handle(req: IncomingMessage, res: ServerResponse) {
const url = new URL(req.url || '/', 'http://localhost');
if (url.pathname !== '/api/traffic/history') return false;
if (req.method !== 'GET') throw new HarborError('ENDPOINT_NOT_FOUND');
let query: TrafficHistoryQuery;
try { query = parseTrafficHistoryQuery(url.searchParams); }
catch (cause) { throw new HarborError('REQUEST_INVALID', { cause }); }
let snapshot;
try {
snapshot = readHistory ? assertTrafficHistorySnapshot(await readHistory(query)) : emptyTrafficHistory(query);
} catch {
snapshot = emptyTrafficHistory(query, 'stale');
snapshot.storage = { status: 'error', errorCode: 'TRAFFIC_HISTORY_UNAVAILABLE' };
snapshot.coverage.partial = true;
}
const inventory = deviceInventory?.snapshot() as { devices?: Array<{ id: string; alias?: string; hostname?: string; ip?: string }> } | undefined;
const labels = new Map((inventory?.devices || []).map((device) => [device.id, device.alias || device.hostname || device.ip]));
snapshot.origins = snapshot.origins.map((origin) => ({ ...origin, label: labels.get(origin.id) || origin.label }));
sendJson(res, 200, snapshot);
return true;
},
};
}