feat(gateway): add system.services and dashboard services grid

This commit is contained in:
William Valentin
2026-02-14 00:42:41 -08:00
parent 4f3810ba4c
commit 0493660e7d
9 changed files with 369 additions and 11 deletions
+41 -9
View File
@@ -65,8 +65,8 @@ function renderSkeleton(el) {
<div class="text-muted text-sm">Loading...</div>
</div>
<h2 class="section-title">Channels</h2>
<div id="ops-channels" class="channels-grid">
<h2 class="section-title">Services</h2>
<div id="ops-services" class="services-grid">
<div class="text-muted text-sm">Loading...</div>
</div>
`;
@@ -236,6 +236,38 @@ function updateChannels(channelsData) {
).join('');
}
function updateServices(servicesData) {
const el = document.getElementById('ops-services');
if (!el) {return;}
const services = servicesData?.services ?? [];
if (services.length === 0) {
el.innerHTML = '<div class="text-muted text-sm">No services configured</div>';
return;
}
el.innerHTML = services.map(svc => {
const typeIcon = svc.type === 'channel' ? '📡' : svc.type === 'automation' ? '⚙️' : '🔧';
const statusClass = svc.status === 'connected'
? 'connected'
: svc.status === 'configured'
? 'configured'
: svc.status === 'error'
? 'error'
: svc.status === 'not_configured'
? 'not-configured'
: 'disconnected';
const itemCount = svc.itemCount ? ` (${svc.itemCount})` : '';
return `<div class="service-card service-${statusClass}">
<span class="service-type-icon">${typeIcon}</span>
<span class="service-name">${escapeHtml(svc.name)}${itemCount}</span>
<span class="service-status">${escapeHtml(svc.status)}</span>
<span class="service-description text-muted text-xs">${escapeHtml(svc.description)}</span>
</div>`;
}).join('');
}
// ── Data fetching ───────────────────────────────────────────────
async function fetchFast(client) {
@@ -253,11 +285,11 @@ async function fetchFast(client) {
async function fetchSlow(client) {
try {
const [health, channels] = await Promise.all([
const [health, services] = await Promise.all([
client.call('system.health'),
client.call('system.channels'),
client.call('system.services'),
]);
return { health, channels };
return { health, services };
} catch {
return null;
}
@@ -287,7 +319,7 @@ async function loadDashboard(el, client) {
updateActiveRequests(fast.requestsData);
}
if (slow) {
updateChannels(slow.channels);
updateServices(slow.services);
}
// Fast refresh: 3 seconds for metrics, events, requests
@@ -302,13 +334,13 @@ async function loadDashboard(el, client) {
}
}, 3000);
// Slow refresh: 10 seconds for health, channels
// Slow refresh: 10 seconds for health, services
_slowTimer = setInterval(async () => {
const data = await fetchSlow(client);
if (data) {
_lastHealth = data.health;
updateCounters(_lastMetrics, _lastHealth);
updateChannels(data.channels);
updateCounters(_lastMetrics, data.health);
updateServices(data.services);
}
}, 10000);
}
+76
View File
@@ -771,6 +771,82 @@ header #status.status-ok {
text-transform: capitalize;
}
/* ── Services Grid ──────────────────────────────────────────── */
.services-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 12px;
margin-bottom: 32px;
}
.service-card {
background-color: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 12px;
display: flex;
flex-direction: column;
gap: 6px;
border-left: 3px solid var(--border);
}
.service-card.service-connected {
border-left-color: var(--success);
}
.service-card.service-configured {
border-left-color: var(--accent);
}
.service-card.service-error {
border-left-color: var(--error);
}
.service-card.service-not-configured {
border-left-color: var(--text-muted);
opacity: 0.6;
}
.service-type-icon {
font-size: var(--font-size-base);
flex-shrink: 0;
}
.service-name {
font-weight: 600;
color: var(--text-primary);
text-transform: capitalize;
}
.service-status {
font-size: var(--font-size-sm);
color: var(--text-muted);
text-transform: uppercase;
}
.service-card.service-connected .service-status {
color: var(--success);
}
.service-card.service-configured .service-status {
color: var(--accent);
}
.service-card.service-error .service-status {
color: var(--error);
}
.service-card.service-not-configured .service-status {
color: var(--text-muted);
font-style: italic;
}
.service-description {
font-size: var(--font-size-sm);
color: var(--text-muted);
}
/* ── Section Headers ────────────────────────────────────────── */
.section-title {