From 76667454b3e83fe0425e5ffe298ca77c10f11df4 Mon Sep 17 00:00:00 2001 From: serversdown Date: Wed, 18 Mar 2026 18:51:54 +0000 Subject: [PATCH] feat: enhance agent status display with new classes and live refresh functionality --- templates/admin_watchers.html | 141 ++++++++++++++++++++++++++++------ 1 file changed, 119 insertions(+), 22 deletions(-) diff --git a/templates/admin_watchers.html b/templates/admin_watchers.html index 45cdccb..2fa418f 100644 --- a/templates/admin_watchers.html +++ b/templates/admin_watchers.html @@ -34,13 +34,13 @@
{% if agent.status == 'ok' %} - + {% elif agent.status == 'pending' %} - + {% elif agent.status in ('missing', 'error') %} - + {% else %} - + {% endif %}
@@ -60,15 +60,15 @@
{% if agent.status == 'ok' %} - OK + OK {% elif agent.status == 'pending' %} - Pending + Pending {% elif agent.status == 'missing' %} - Missing + Missing {% elif agent.status == 'error' %} - Error + Error {% else %} - Unknown + Unknown {% endif %} @@ -83,10 +83,10 @@
-
+
Last seen - + {% if agent.last_seen %} {{ agent.last_seen }} {% if agent.age_minutes is not none %} @@ -97,14 +97,12 @@ {% endif %}
- {% if agent.update_pending %} -
+
Update pending — will apply on next heartbeat
- {% endif %}
@@ -112,11 +110,16 @@
Log Tail - +
+ + +
- +
{% else %}
No log data received yet.
@@ -136,7 +139,8 @@ function triggerUpdate(agentId) { if (!confirm('Trigger update for ' + agentId + '?\n\nThe watcher will self-update on its next heartbeat cycle.')) return; - const btn = document.getElementById('btn-update-' + agentId.replace(/ /g, '-')); + const safeId = agentId.replace(/ /g, '-'); + const btn = document.getElementById('btn-update-' + safeId); btn.disabled = true; btn.textContent = 'Sending...'; @@ -151,7 +155,12 @@ function triggerUpdate(agentId) { btn.textContent = 'Update Queued'; btn.classList.remove('bg-seismo-orange', 'hover:bg-orange-600'); btn.classList.add('bg-green-600'); - setTimeout(() => location.reload(), 1500); + // Show the pending indicator immediately without a reload + const card = document.getElementById('agent-' + safeId); + if (card) { + const indicator = card.querySelector('.update-pending-indicator'); + if (indicator) indicator.classList.remove('hidden'); + } } else { btn.textContent = 'Error'; btn.classList.add('bg-red-600'); @@ -170,7 +179,95 @@ function toggleLog(agentId) { if (el) el.classList.toggle('hidden'); } -// Auto-refresh -setTimeout(() => location.reload(), 30000); +function expandLog(agentId) { + const el = document.getElementById('log-' + agentId); + const btn = document.getElementById('expand-' + agentId); + if (!el) return; + el.classList.remove('hidden'); + if (el.classList.contains('max-h-96')) { + el.classList.remove('max-h-96'); + el.style.maxHeight = 'none'; + if (btn) btn.textContent = 'Collapse'; + } else { + el.classList.add('max-h-96'); + el.style.maxHeight = ''; + if (btn) btn.textContent = 'Expand'; + } +} + +// Status colors for dot and badge by status value +const STATUS_DOT = { + ok: 'bg-green-500', + pending: 'bg-yellow-400', + missing: 'bg-red-500', + error: 'bg-red-500', +}; +const STATUS_BADGE_CLASSES = { + ok: 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300', + pending: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300', + missing: 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300', + error: 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300', +}; +const STATUS_BADGE_DEFAULT = 'bg-gray-100 text-gray-600 dark:bg-slate-700 dark:text-gray-400'; +const DOT_COLORS = ['bg-green-500', 'bg-yellow-400', 'bg-red-500', 'bg-gray-400']; +const BADGE_COLORS = [ + 'bg-green-100', 'text-green-700', 'dark:bg-green-900', 'dark:text-green-300', + 'bg-yellow-100', 'text-yellow-700', 'dark:bg-yellow-900', 'dark:text-yellow-300', + 'bg-red-100', 'text-red-700', 'dark:bg-red-900', 'dark:text-red-300', + 'bg-gray-100', 'text-gray-600', 'dark:bg-slate-700', 'dark:text-gray-400', +]; + +function patchAgent(card, agent) { + // Status dot + const dot = card.querySelector('.status-dot'); + if (dot) { + dot.classList.remove(...DOT_COLORS); + dot.classList.add(STATUS_DOT[agent.status] || 'bg-gray-400'); + } + + // Status badge + const badge = card.querySelector('.status-badge'); + if (badge) { + badge.classList.remove(...BADGE_COLORS); + const label = agent.status ? agent.status.charAt(0).toUpperCase() + agent.status.slice(1) : 'Unknown'; + badge.textContent = label === 'Ok' ? 'OK' : label; + const cls = STATUS_BADGE_CLASSES[agent.status] || STATUS_BADGE_DEFAULT; + badge.classList.add(...cls.split(' ')); + } + + // Last seen / age + const lastSeen = card.querySelector('.last-seen-value'); + if (lastSeen) { + if (agent.last_seen) { + const age = agent.age_minutes != null + ? ` (${agent.age_minutes}m ago)` + : ''; + lastSeen.innerHTML = agent.last_seen + age; + } else { + lastSeen.textContent = 'Never'; + } + } + + // Update pending indicator + const indicator = card.querySelector('.update-pending-indicator'); + if (indicator) { + indicator.classList.toggle('hidden', !agent.update_pending); + } +} + +function liveRefresh() { + fetch('/api/admin/watchers') + .then(r => r.json()) + .then(agents => { + agents.forEach(agent => { + const safeId = agent.id.replace(/ /g, '-'); + const card = document.getElementById('agent-' + safeId); + if (card) patchAgent(card, agent); + }); + }) + .catch(() => {}); // silently ignore fetch errors +} + +setInterval(liveRefresh, 30000); {% endblock %}