const ICON_VIEW = "⬇"; const ICON_MAINT = "↻"; const ICON_EVENT = "ϟ"; const ICON_PASS = "✔"; const ICON_FAIL = "✖"; let version = null; let latest_job = -1; async function api(x) { let r = await fetch(x); return r.json(); } function icon_progress() { // Use random braille glyphs to create a moving progress effect. let i = Math.floor(Math.random() * (0x28ff - 0x2801)) + 0x2801; return `&#${i};`; } function set_progress(element, x) { if (x !== null || element.className == "") { element.className = (x === null ? "progress" : ""); element.innerHTML = (x === null ? icon_progress() : x); } } function set_time(element) { let timestamp = element.dataset.timestamp; let seconds = Math.floor((Date.now() / 1000) - timestamp); let minutes = Math.floor(seconds / 60); let hours = Math.floor(minutes / 60); let days = Math.floor(hours / 24); if (seconds < 120) { element.innerText = `${seconds} seconds ago`; } else if (minutes < 120) { element.innerText = `${minutes} minutes ago`; } else if (hours < 48) { element.innerText = `${hours} hours ago`; } else { element.innerText = `${days} days ago`; } } function touch_job(list, job) { // New job, new
if (document.getElementById(`${job.job}`) === null) { let abv_hash = job.hash.substring(0, 16); let reason = (job.reason == "event" ? ICON_EVENT : ICON_MAINT); let element = document.createElement("div"); list.insertBefore(element, list.childNodes[0]); element.innerHTML = `
${ICON_VIEW} #${job.job} ${job.service} ${abv_hash} ${reason}

            
`; let time = document.getElementById(`${job.job}_time`); set_time(time); } // Update existing let box = document.getElementById(`${job.job}`); let result = document.getElementById(`${job.job}_result`); let log = document.getElementById(`${job.job}_log`); let cn = (job.reason == "remove" ? "standout" : job.result); box.className = `box ${cn}`; switch (job.result) { case "active": set_progress(result, null); break; case "fail": set_progress(result, ICON_FAIL); break; case "pass": set_progress(result, ICON_PASS); break; } log.innerText = job.log; } async function update_jobs() { let nav_progress = document.getElementById("nav_progress"); let no_server = document.getElementById("no_server"); let no_jobs = document.getElementById("no_jobs"); let list = document.getElementById("list"); let timeout = %($CYCHE_ACTIVE_UPDATE%); try { let status = await api("/api/status"); if (version === null) { version = status.version; } if (version != status.version) { location.reload(); } if (!status.active) { timeout = %($CYCHE_IDLE_UPDATE%); } set_progress(nav_progress, (status.active ? null : "")); for (let i = status.oldest; i <= status.newest; i++) { if (i >= latest_job) { let job = await api(`/api/job/${i}`); touch_job(list, job); latest_job = i; } } if (list.childElementCount > 0) { list.classList.remove("hidden"); no_jobs.classList.add("hidden"); } no_server.classList.add("hidden"); } catch (ex) { no_server.classList.remove("hidden"); console.log(ex); } setTimeout(update_jobs, timeout); } function update_times() { let elements = document.getElementsByClassName("time"); for (let e of elements) { set_time(e); } } function update_progress() { let elements = document.getElementsByClassName("progress"); for (let e of elements) { e.innerHTML = icon_progress(); } } update_jobs(); update_progress(); setInterval(update_times, 1000); setInterval(update_progress, %($CYCHE_PROGRESS_ANIM_SPEED%));