(function () { "use strict"; const view = document.body.dataset.view || ""; const canViewMiners = document.body.dataset.canViewMiners === "true"; function text(value, fallback) { if (value === null || value === undefined || String(value).trim() === "") { return fallback || "Not available"; } return String(value); } function statusName(value) { return text(value, "unknown").toLowerCase().replaceAll(" ", "_"); } function create(tag, options) { const element = document.createElement(tag); const values = options || {}; if (values.className) element.className = values.className; if (values.text !== undefined) element.textContent = values.text; if (values.href) element.href = values.href; return element; } function showState(name, message) { for (const state of document.querySelectorAll("[data-view-state]")) { state.hidden = state.dataset.viewState !== name; } const active = document.querySelector( '[data-view-state="' + name + '"]' ); const detail = active && active.querySelector("[data-state-detail]"); if (detail && message) detail.textContent = message; } async function api(path) { const controller = new AbortController(); const timeout = window.setTimeout(function () { controller.abort(); }, 15000); let response; try { response = await fetch(path, { credentials: "same-origin", headers: { Accept: "application/json" }, signal: controller.signal }); } catch (error) { if (error && error.name === "AbortError") { throw new Error( "MinerOps took too long to respond. Retry in a moment." ); } throw new Error( "MinerOps could not reach the backend. Check the connection and retry." ); } finally { window.clearTimeout(timeout); } if (response.status === 401) { window.location.replace( "/login?next=" + encodeURIComponent(window.location.pathname + window.location.search) ); throw new Error("Your session expired. Sign in again to continue."); } if (response.status === 403) { const denied = new Error( "Your account does not have permission to view this information." ); denied.permissionDenied = true; throw denied; } if (!response.ok) { throw new Error( "MinerOps could not load this information. Retry in a moment." ); } try { return await response.json(); } catch (error) { throw new Error( "MinerOps returned an invalid response. Retry in a moment." ); } } function setupShell() { const menu = document.getElementById("menuButton"); const nav = document.getElementById("primaryNav"); if (menu && nav) { menu.addEventListener("click", function () { const open = nav.dataset.open !== "true"; nav.dataset.open = String(open); menu.setAttribute("aria-expanded", String(open)); }); } const search = document.getElementById("globalSearchForm"); const searchInput = document.getElementById("globalSearchInput"); if (search && searchInput) { search.addEventListener("submit", function (event) { event.preventDefault(); const query = searchInput.value.trim(); const target = query ? "/assets/miners?query=" + encodeURIComponent(query) : "/assets/miners"; window.location.assign(target); }); } const logout = document.getElementById("logoutButton"); if (logout) { logout.addEventListener("click", async function () { logout.disabled = true; try { await fetch("/api/auth/logout", { method: "POST", credentials: "same-origin" }); } finally { window.location.replace("/login"); } }); } } function addCell(row, label, content) { const cell = create("td"); cell.dataset.label = label; if (content instanceof Node) cell.appendChild(content); else cell.textContent = text(content); row.appendChild(cell); } function status(value) { const label = create("span", { className: "status-text", text: text(value, "Unknown").replaceAll("_", " ") }); label.dataset.status = statusName(value); return label; } function dateTime(value) { if (!value) return "Not available"; const parsed = new Date(value); return Number.isNaN(parsed.valueOf()) ? String(value) : parsed.toLocaleString(); } async function loadRepairs() { const rows = document.getElementById("repairRows"); if (!rows) return; showState("loading"); try { const envelope = await api("/api/repairs/queue"); if ( !envelope || !envelope.data || typeof envelope.data !== "object" || !Array.isArray(envelope.data.items) ) { throw new Error( "MinerOps returned an invalid repair response." ); } const items = envelope.data.items; if (!items.every(function (item) { return item && typeof item === "object" && typeof item.miner_id === "string" && item.miner_id.trim() !== ""; })) { throw new Error( "MinerOps returned an incomplete repair response." ); } rows.replaceChildren(); if (!items.length) { showState("empty"); return; } for (const repair of items) { const row = create("tr"); const identity = create("div"); const minerLabel = text( repair.current_mac, text(repair.miner_id, "Unknown miner") ); if (canViewMiners) { identity.appendChild(create("a", { className: "object-link", text: minerLabel, href: "/miners/" + encodeURIComponent(repair.miner_id) + "/detail" })); } else { identity.appendChild(create("strong", { text: minerLabel })); } identity.appendChild(create("span", { className: "secondary", text: text(repair.current_ip, "No current IP") })); addCell(row, "Miner", identity); addCell(row, "Model", repair.model); addCell( row, "Repair state", status(repair.latest_event_status || repair.asset_state) ); addCell( row, "Reported issue", repair.reported_error || repair.latest_event_title || "No reported issue" ); addCell( row, "Last update", dateTime(repair.latest_event_at || repair.state_updated_at) ); rows.appendChild(row); } showState("ready"); } catch (error) { showState( error.permissionDenied ? "denied" : "error", error.message ); } } setupShell(); if (view === "repairs") loadRepairs(); })();