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:
@@ -324,3 +324,12 @@ body.drawer-open #scrim { opacity: 1; pointer-events: auto; }
|
||||
.dv-tile .dv-vendor { font-family: var(--font-ui); font-size: 10px; color: var(--muted); opacity: .7; }
|
||||
.dv-tile.flag { border-color: var(--bad); background: #1a1012; }
|
||||
.dv-tile.flag .dv-nm { color: var(--bad); }
|
||||
|
||||
/* ===== Discovered services + scan (Plan: DB-backed registry) ===== */
|
||||
.lb-scan { background: var(--accent-soft); border: 1px solid var(--accent-dim); color: var(--accent);
|
||||
border-radius: 5px; padding: 4px 12px; font-family: var(--font-ui); font-size: 11px; cursor: pointer; flex: none; }
|
||||
.lb-scan:hover { background: var(--accent-dim); color: var(--text); }
|
||||
.tile.disc { border-style: dashed; }
|
||||
.disc-add { margin-left: auto; width: 22px; height: 22px; border-radius: 50%; flex: none;
|
||||
border: 1px solid var(--accent-dim); background: transparent; color: var(--accent); font-size: 14px; line-height: 1; cursor: pointer; }
|
||||
.disc-add:hover { background: var(--accent); color: var(--bg); }
|
||||
|
||||
@@ -4,7 +4,36 @@ import { littleblueAvatar } from '../components/littleblue_avatar.js';
|
||||
import { serviceTile } from '../components/service_tile.js';
|
||||
|
||||
const TITLE = { agents: 'Agents', infrastructure: 'Infrastructure', media: 'Media', other: 'Other' };
|
||||
let host, timer;
|
||||
let host, timer, scanning = false;
|
||||
|
||||
async function promote(id) {
|
||||
try { await api.patch('/api/health/services/' + id, { enabled: true }); load(); } catch { /* */ }
|
||||
}
|
||||
function scan() {
|
||||
if (scanning) return;
|
||||
scanning = true; load(); // reflect "Scanning…"
|
||||
api.post('/api/health/discover', {}).catch(() => { /* */ });
|
||||
setTimeout(() => { scanning = false; load(); }, 30000); // LAN sweep ~25s
|
||||
}
|
||||
|
||||
// Owner-only; returns a section element or null (skipped for non-owner / none).
|
||||
async function discoveredSection() {
|
||||
let cand;
|
||||
try { cand = await api.get('/api/health/services/discovered'); } catch { return null; }
|
||||
if (!cand || !cand.length) return null;
|
||||
return el('div', { class: 'lb-section' },
|
||||
el('div', { class: 'lb-group' },
|
||||
el('span', { class: 'gname' }, 'Discovered'),
|
||||
el('span', { class: 'gcount' }, `${cand.length} new`),
|
||||
el('span', { class: 'line' })),
|
||||
el('div', { class: 'tiles' }, cand.map(c =>
|
||||
el('div', { class: 'tile disc' },
|
||||
el('div', { class: 'tile-main' },
|
||||
el('div', { class: 'tile-nm' }, c.name),
|
||||
el('div', { class: 'tile-host' }, c.url)),
|
||||
el('button', { class: 'disc-add', title: 'Add to the band', onclick: () => promote(c.id) }, '+')))));
|
||||
}
|
||||
|
||||
async function load() {
|
||||
if (!host) return;
|
||||
try {
|
||||
@@ -16,11 +45,15 @@ async function load() {
|
||||
el('span', { class: 'gcount' }, `${g.healthy}/${g.total} healthy`),
|
||||
el('span', { class: 'line' })),
|
||||
el('div', { class: 'tiles' }, g.services.map(serviceTile))));
|
||||
const disc = await discoveredSection();
|
||||
mount(host,
|
||||
el('div', { class: 'lbwrap' }, littleblueAvatar(),
|
||||
el('div', {}, el('div', { class: 'lb-name' }, 'Little Blue'),
|
||||
el('div', { class: 'lb-sub' }, 'Health & Uptime of the lab'))),
|
||||
sections);
|
||||
el('div', { style: { flex: 1 } },
|
||||
el('div', { class: 'lb-name' }, 'Little Blue'),
|
||||
el('div', { class: 'lb-sub' }, 'Health & Uptime of the lab')),
|
||||
el('button', { class: 'lb-scan', title: 'Scan the LAN for services', onclick: scan }, scanning ? 'Scanning…' : 'Scan')),
|
||||
sections,
|
||||
disc);
|
||||
} catch { mount(host, el('span', { class: 'muted' }, 'Health band unavailable')); }
|
||||
}
|
||||
export function renderHealthBand(el_) { host = el_; load(); timer = setInterval(load, 60000); }
|
||||
|
||||
Reference in New Issue
Block a user