feat: overhaul Duriin autonomy console
This commit is contained in:
@@ -34,6 +34,72 @@ function escapeHtml(s) {
|
||||
}
|
||||
|
||||
|
||||
function formatNumber(value, compact = false) {
|
||||
const number = Number(value || 0);
|
||||
return new Intl.NumberFormat("en-GB", compact ? { notation: "compact", maximumFractionDigits: 1 } : {}).format(number);
|
||||
}
|
||||
|
||||
|
||||
function formatPercent(value, digits = 1) {
|
||||
if (value == null || !Number.isFinite(Number(value))) return "—";
|
||||
return `${(Number(value) * 100).toFixed(digits)}%`;
|
||||
}
|
||||
|
||||
|
||||
function formatRelative(value) {
|
||||
if (!value) return "—";
|
||||
const timestamp = new Date(value.endsWith && value.endsWith("Z") ? value : `${value}Z`).getTime();
|
||||
if (!Number.isFinite(timestamp)) return value;
|
||||
const seconds = Math.round((timestamp - Date.now()) / 1000);
|
||||
const units = [[86400, "day"], [3600, "hour"], [60, "minute"]];
|
||||
const formatter = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
||||
for (const [size, name] of units) {
|
||||
if (Math.abs(seconds) >= size) return formatter.format(Math.round(seconds / size), name);
|
||||
}
|
||||
return "just now";
|
||||
}
|
||||
|
||||
|
||||
function installChrome() {
|
||||
const header = document.querySelector("header.app-header");
|
||||
if (!header) return;
|
||||
const title = header.querySelector("h1");
|
||||
if (title) title.innerHTML = `<span class="brand-mark">D</span><span class="brand-copy">Duriin<span>Autonomous intelligence</span></span>`;
|
||||
|
||||
const nav = header.querySelector(".tabs");
|
||||
if (nav && !nav.querySelector('[href="/admin/autonomy"]')) {
|
||||
nav.insertAdjacentHTML("afterbegin", '<a href="/admin/autonomy">Autonomy</a>');
|
||||
}
|
||||
if (nav) {
|
||||
const path = location.pathname;
|
||||
nav.querySelectorAll("a").forEach(link => {
|
||||
const href = link.getAttribute("href");
|
||||
const active = href === "/admin/autonomy"
|
||||
? path === href
|
||||
: href === "/admin/ingest"
|
||||
? path.startsWith("/admin/ingest")
|
||||
: href === "/admin/intelligence"
|
||||
? path.startsWith("/admin/intelligence")
|
||||
: path === href;
|
||||
link.classList.toggle("active", active);
|
||||
});
|
||||
}
|
||||
|
||||
const status = document.createElement("div");
|
||||
status.className = "rail-status";
|
||||
status.innerHTML = `<div class="rail-status-label"><span class="rail-status-dot"></span><span id="rail-mode">Connecting</span></div><div class="rail-status-meta" id="rail-meta">Runtime status</div>`;
|
||||
header.appendChild(status);
|
||||
api("/admin/api/autonomy/overview").then(data => {
|
||||
const mode = String(data.mode || "offline").toUpperCase();
|
||||
document.getElementById("rail-mode").textContent = data.enabled ? `${mode} runtime` : "Runtime unavailable";
|
||||
document.getElementById("rail-meta").textContent = data.broker?.configured ? `${data.broker.name} · connected` : "Broker not configured";
|
||||
document.querySelector(".rail-status-dot")?.classList.toggle("live", data.enabled);
|
||||
}).catch(() => {
|
||||
document.getElementById("rail-mode").textContent = "Runtime unavailable";
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ── url query-param helpers ────────────────────────────────────────────────
|
||||
//
|
||||
// filters and sort state live in the url so reloads and shared links keep
|
||||
@@ -106,6 +172,7 @@ function wireOverlays() {
|
||||
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
installChrome();
|
||||
wireOverlays();
|
||||
loadGlobalStats();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user