// workflow-monitor.js — v0.35.0 Workflow Monitoring Dashboard
//
// Admin monitoring tab showing active instances, stage funnels,
// SLA status indicators, and stale instance detection.
export function mountMonitorTab(container, { basePath }) {
container.innerHTML = `
`;
let refreshTimer = null;
async function loadInstances() {
try {
const resp = await fetch(`${basePath}/api/v1/admin/workflows/monitor/instances`);
if (!resp.ok) throw new Error(resp.statusText);
const { data } = await resp.json();
renderInstances(data);
} catch (e) {
document.getElementById('monInstances').textContent = 'Failed to load: ' + e.message;
}
}
async function loadStale() {
try {
const resp = await fetch(`${basePath}/api/v1/admin/workflows/monitor/stale`);
if (!resp.ok) return;
const { data } = await resp.json();
renderStale(data);
} catch (_) {}
}
function renderInstances(instances) {
const el = document.getElementById('monInstances');
if (!instances.length) {
el.innerHTML = 'No active workflow instances.
';
return;
}
let html = '';
html += '';
html += '| Workflow | Stage | ';
html += 'Age | SLA | ';
html += '
';
for (const inst of instances) {
const age = formatDuration(inst.age_seconds);
const sla = renderSLA(inst);
html += ``;
html += `${esc(inst.workflow_name)} ${esc(inst.channel_title)} | `;
html += `${esc(inst.stage_name)} (${inst.current_stage}) | `;
html += `${age} | `;
html += `${sla} | `;
html += '
';
}
html += '
';
el.innerHTML = html;
}
function renderSLA(inst) {
if (!inst.sla_seconds) return '—';
if (inst.sla_breached) {
return 'BREACHED';
}
if (inst.sla_remaining_seconds != null) {
const remaining = inst.sla_remaining_seconds;
const pct = Math.max(0, remaining / inst.sla_seconds);
const color = pct > 0.5 ? 'var(--success,#2ecc71)' : pct > 0.2 ? 'var(--warning,#f39c12)' : 'var(--danger,#e74c3c)';
return `${formatDuration(remaining)}`;
}
return '—';
}
function renderStale(instances) {
const el = document.getElementById('monStale');
if (!instances.length) {
el.innerHTML = '';
return;
}
let html = 'Stale Instances (' + instances.length + ')
';
html += '';
for (const inst of instances) {
html += `- ${esc(inst.workflow_name)} — ${esc(inst.stage_name)} (${formatDuration(inst.age_seconds)} old)
`;
}
html += '
';
el.innerHTML = html;
}
function formatDuration(seconds) {
if (seconds < 60) return seconds + 's';
if (seconds < 3600) return Math.floor(seconds / 60) + 'm';
if (seconds < 86400) return Math.floor(seconds / 3600) + 'h ' + Math.floor((seconds % 3600) / 60) + 'm';
return Math.floor(seconds / 86400) + 'd ' + Math.floor((seconds % 86400) / 3600) + 'h';
}
function esc(s) {
const d = document.createElement('div');
d.textContent = s || '';
return d.innerHTML;
}
// Initial load
loadInstances();
loadStale();
// Auto-refresh every 30s
refreshTimer = setInterval(() => {
loadInstances();
loadStale();
}, 30000);
document.getElementById('monRefresh')?.addEventListener('click', () => {
loadInstances();
loadStale();
});
// Return cleanup function
return () => {
if (refreshTimer) clearInterval(refreshTimer);
};
}