feat: 2.0.0-alpha.11 — DB-backed service registry + LAN auto-discovery
- monitored_services table (mig 015) replaces config/services.json (now a boot seed) - owner CRUD over /api/health/services; GET is DB-backed; cron+worker read the DB - discover.lan worker: pure-Node TCP sweep + HTTP-title probe -> disabled 'discovered' candidates (never clobbers curated entries); POST /api/health/discover + GET .../discovered - dashboard: Scan button + Discovered(N) section with one-click promote Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,22 +1,18 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import * as repo from '../db/repos/monitored_services.js';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const CONFIG = path.join(__dirname, '../../config/services.json');
|
||||
const SEED_FILE = path.join(__dirname, '../../config/services.json');
|
||||
export const CATEGORY_ORDER = ['agents', 'infrastructure', 'media', 'other'];
|
||||
|
||||
let cache = null;
|
||||
export function load() {
|
||||
if (!cache) cache = JSON.parse(readFileSync(CONFIG, 'utf8'));
|
||||
return cache;
|
||||
}
|
||||
export function _reset() { cache = null; } // tests
|
||||
|
||||
// Icon slug: explicit `icon`, else slugified name. Pure.
|
||||
export function iconSlug(svc) {
|
||||
return (svc.icon || svc.name).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
|
||||
}
|
||||
|
||||
// Group services by category in CATEGORY_ORDER (unknown categories last). Pure.
|
||||
export function grouped(services) {
|
||||
const map = new Map();
|
||||
for (const s of services) {
|
||||
@@ -28,3 +24,18 @@ export function grouped(services) {
|
||||
.filter(c => map.has(c))
|
||||
.map(category => ({ category, services: map.get(category) }));
|
||||
}
|
||||
|
||||
// One-time bootstrap: if the registry table is empty, populate it from the
|
||||
// version-controlled config/services.json seed. Idempotent (no-op once seeded).
|
||||
export async function seedFromConfig() {
|
||||
if ((await repo.count()) > 0) return 0;
|
||||
let seed;
|
||||
try { seed = JSON.parse(readFileSync(SEED_FILE, 'utf8')); }
|
||||
catch { return 0; }
|
||||
let n = 0;
|
||||
for (const s of seed) {
|
||||
try { await repo.create({ ...s, source: 'manual', enabled: true }); n++; }
|
||||
catch { /* skip a bad/duplicate seed row */ }
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user