// Machines tab - machine grid, health checks, worker management import { store } from "../core/state"; import { escapeHtml } from "../core/helpers"; import { icon } from "../design/icons"; import { setupMachinesModals, updateWorkerCount, machineHealthCheckAction, confirmMachineRemove, } from "../components/modal"; import * as api from "../core/api"; import type { FleetState, View, MachineInfo, JoinToken } from "../types/api"; function fetchMachinesTab(): void { api .fetchMachines() .then((machines) => { store.set("machinesCache", machines); renderMachinesTab(machines); }) .catch(() => {}); api .fetchJoinTokens() .then(({ tokens }) => { store.set("joinTokensCache", tokens); renderJoinTokens(tokens); }) .catch(() => {}); } function renderMachinesTab(machines: MachineInfo[]): void { const summaryEl = document.getElementById("machines-summary"); const gridEl = document.getElementById("machines-grid"); if (!summaryEl || !gridEl) return; summaryEl.innerHTML = renderMachineSummary(machines); if (machines.length === 0) { gridEl.innerHTML = `
${icon("server", 32)}

No machines registered

`; return; } gridEl.innerHTML = machines.map((m) => renderMachineCard(m)).join(""); // Wire up action buttons gridEl.querySelectorAll(".machine-action-btn").forEach((btn) => { btn.addEventListener("click", (e) => { e.stopPropagation(); const action = btn.getAttribute("data-machine-action"); const name = btn.getAttribute("data-machine-name") || ""; if (action === "check") machineHealthCheckAction(name); else if (action === "remove") confirmMachineRemove(name); }); }); // Wire up worker sliders gridEl.querySelectorAll(".workers-slider").forEach((slider) => { slider.addEventListener("input", (e) => { const el = e.target as HTMLInputElement; const name = el.getAttribute("data-machine-name") || ""; updateWorkerCount(name, el.value); }); }); } function renderMachineSummary(machines: MachineInfo[]): string { let totalMaxWorkers = 0; let totalActiveWorkers = 0; let onlineCount = 0; for (const m of machines) { totalMaxWorkers += m.max_workers || 0; totalActiveWorkers += m.active_workers || 0; if (m.status === "online") onlineCount++; } return ( `
${machines.length}
Total Machines
` + `
${onlineCount}
Online
` + `
${totalActiveWorkers} / ${totalMaxWorkers}
Active / Max Workers
` ); } function renderMachineCard(machine: MachineInfo): string { const name = machine.name || ""; const host = machine.host || "\u2014"; const role = machine.role || "worker"; const status = machine.status || "offline"; const maxWorkers = machine.max_workers || 4; const activeWorkers = machine.active_workers || 0; const health = machine.health || {}; const daemonRunning = health.daemon_running || false; const heartbeatCount = health.heartbeat_count || 0; const lastHbAge = health.last_heartbeat_s_ago; let lastHbText = "\u2014"; if (typeof lastHbAge === "number" && lastHbAge < 9999) { if (lastHbAge < 60) lastHbText = lastHbAge + "s ago"; else if (lastHbAge < 3600) lastHbText = Math.floor(lastHbAge / 60) + "m ago"; else lastHbText = Math.floor(lastHbAge / 3600) + "h ago"; } return ( `
` + `
` + `` + `${escapeHtml(name)}` + `${escapeHtml(role)}
` + `
${escapeHtml(host)}
` + `
` + `
Workers${activeWorkers} / ${maxWorkers}
` + `
` + `
` + `
Daemon` + `${daemonRunning ? "Running" : "Stopped"}
` + `
Heartbeats${heartbeatCount}
` + `
Last heartbeat${lastHbText}
` + `
` + `` + `
` ); } function renderJoinTokens(tokens: JoinToken[]): void { const section = document.getElementById("join-tokens-section"); const list = document.getElementById("join-tokens-list"); if (!section || !list) return; if (!tokens || tokens.length === 0) { section.style.display = "none"; return; } section.style.display = ""; let html = ""; for (const t of tokens) { const label = t.label || "Unlabeled"; const created = t.created_at ? new Date(t.created_at).toLocaleDateString() : "\u2014"; const used = t.used ? "Claimed" : "Active"; const usedClass = t.used ? "c-amber" : "c-green"; html += `
` + `${escapeHtml(label)}` + `${created}` + `${used}
`; } list.innerHTML = html; } export const machinesView: View = { init() { setupMachinesModals(); fetchMachinesTab(); }, render(_data: FleetState) { const cache = store.get("machinesCache"); if (cache) renderMachinesTab(cache); }, destroy() {}, };