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
-
+
+
+
+
-
{{ agent.log_tail | log_tail_display }}
+
{{ agent.log_tail | log_tail_display }}
{% 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 %}