feat(gateway): add system.services and dashboard services grid
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user