init
This commit is contained in:
157
static/js/status.js
Normal file
157
static/js/status.js
Normal file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* static/js/status.js
|
||||
* Renders the server status and agent activity panels,
|
||||
* and the config settings panel.
|
||||
*/
|
||||
|
||||
import { state } from './state.js';
|
||||
import { api } from './api.js';
|
||||
import { toast } from './app.js';
|
||||
|
||||
// ── Time helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
function fmtTime(isoStr) {
|
||||
if (!isoStr) return '–';
|
||||
const d = new Date(isoStr);
|
||||
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
}
|
||||
|
||||
function fmtRelative(isoStr) {
|
||||
if (!isoStr) return '–';
|
||||
const d = new Date(isoStr);
|
||||
const diff = Math.floor((Date.now() - d.getTime()) / 1000);
|
||||
if (diff < 5) return 'just now';
|
||||
if (diff < 60) return `${diff}s ago`;
|
||||
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
|
||||
return `${Math.floor(diff / 3600)}h ago`;
|
||||
}
|
||||
|
||||
// ── Server online indicator (header) ─────────────────────────────────────
|
||||
|
||||
function updateHeaderLeds(serverOnline, status) {
|
||||
const serverLed = document.getElementById('led-server');
|
||||
const agentLed = document.getElementById('led-agent');
|
||||
|
||||
if (!serverLed || !agentLed) return;
|
||||
|
||||
// Don't overwrite reconnecting state – events.js sets that
|
||||
if (serverOnline && !state.get('sseReconnecting')) {
|
||||
serverLed.className = 'led led--green led--pulse';
|
||||
serverLed.querySelector('.led__label').textContent = 'Server Online';
|
||||
} else if (!serverOnline) {
|
||||
serverLed.className = 'led led--red';
|
||||
serverLed.querySelector('.led__label').textContent = 'Server Offline';
|
||||
}
|
||||
|
||||
if (status?.agent?.connected) {
|
||||
agentLed.className = 'led led--cyan led--pulse';
|
||||
agentLed.querySelector('.led__label').textContent = 'Agent Connected';
|
||||
} else {
|
||||
agentLed.className = 'led led--muted';
|
||||
agentLed.querySelector('.led__label').textContent = 'Agent Idle';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Status sidebar panel ──────────────────────────────────────────────────
|
||||
|
||||
function renderStatusPanel(status) {
|
||||
const el = document.getElementById('status-panel-body');
|
||||
if (!el || !status) return;
|
||||
|
||||
const agent = status.agent;
|
||||
const queue = status.queue;
|
||||
|
||||
el.innerHTML = `
|
||||
<div class="stat-row">
|
||||
<span class="stat-label">Server Up</span>
|
||||
<span class="stat-value">${fmtTime(status.server?.started_at)}</span>
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<span class="stat-label">Pending</span>
|
||||
<span class="stat-value stat-value--cyan">${queue.pending_count}</span>
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<span class="stat-label">Consumed</span>
|
||||
<span class="stat-value stat-value--amber">${queue.consumed_count}</span>
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<span class="stat-label">Agent</span>
|
||||
<span class="stat-value">${agent.agent_id ? escapeHtml(agent.agent_id) : '–'}</span>
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<span class="stat-label">Last Seen</span>
|
||||
<span class="stat-value" data-ts-rel="${agent.last_seen_at || ''}">${fmtRelative(agent.last_seen_at)}</span>
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<span class="stat-label">Last Fetch</span>
|
||||
<span class="stat-value" data-ts-rel="${agent.last_fetch_at || ''}">${fmtRelative(agent.last_fetch_at)}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/** Called by app.js on a timer to keep relative times fresh. */
|
||||
export function refreshStatusTimestamps() {
|
||||
document.querySelectorAll('[data-ts-rel]').forEach(el => {
|
||||
const iso = el.dataset.tsRel;
|
||||
if (iso) el.textContent = fmtRelative(iso);
|
||||
});
|
||||
}
|
||||
|
||||
export function initStatus() {
|
||||
state.subscribe('serverOnline', (online) => {
|
||||
updateHeaderLeds(online, state.get('status'));
|
||||
});
|
||||
|
||||
state.subscribe('status', (status) => {
|
||||
updateHeaderLeds(state.get('serverOnline'), status);
|
||||
renderStatusPanel(status);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Config panel ──────────────────────────────────────────────────────────
|
||||
|
||||
export function initConfig() {
|
||||
const form = document.getElementById('config-form');
|
||||
const waitInput = document.getElementById('cfg-wait');
|
||||
const emptyInput = document.getElementById('cfg-empty');
|
||||
const staleInput = document.getElementById('cfg-stale');
|
||||
const saveBtn = document.getElementById('cfg-save');
|
||||
|
||||
// Populate from state
|
||||
state.subscribe('config', (cfg) => {
|
||||
if (!cfg) return;
|
||||
waitInput.value = cfg.default_wait_seconds;
|
||||
emptyInput.value = cfg.default_empty_response;
|
||||
staleInput.value = cfg.agent_stale_after_seconds;
|
||||
});
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
saveBtn.disabled = true;
|
||||
const original = saveBtn.innerHTML;
|
||||
saveBtn.innerHTML = '<span class="spinner"></span>';
|
||||
|
||||
try {
|
||||
const cfg = await api.updateConfig({
|
||||
default_wait_seconds: parseInt(waitInput.value, 10) || 10,
|
||||
default_empty_response: emptyInput.value,
|
||||
agent_stale_after_seconds: parseInt(staleInput.value, 10) || 30,
|
||||
});
|
||||
state.set('config', cfg);
|
||||
toast('Settings saved', 'success');
|
||||
} catch (e) {
|
||||
toast(e.message, 'error');
|
||||
} finally {
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.innerHTML = original;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user