From b1c95b10f4d1a0d49c2b888dcf10872b4a474569 Mon Sep 17 00:00:00 2001 From: Malfurious Date: Sat, 11 Nov 2023 21:50:47 -0500 Subject: dmt: Implement initial functionality for jobs page Adds javascript to implement self-hydration for the main page. Several aspects of these features can be customized via new settings in config.sh. Signed-off-by: Malfurious --- dmt/config.sh | 12 +++++ dmt/dmt | 4 ++ dmt/html/jobs.html | 5 +- dmt/script.js | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 dmt/script.js diff --git a/dmt/config.sh b/dmt/config.sh index aa8a9c4..3df98e9 100644 --- a/dmt/config.sh +++ b/dmt/config.sh @@ -3,3 +3,15 @@ CYCHE_SITE_NAME="cychedelic" # Number of lines of text to show per job on the main page CYCHE_LOG_TAIL_LENGTH=25 + +# Job content refresh time while system is idle +CYCHE_IDLE_UPDATE=60000 # 1 minute + +# Job content refresh time while system is busy +CYCHE_ACTIVE_UPDATE=1000 # 1 second + +# Progress indicator animation speed (time per update) +CYCHE_PROGRESS_ANIM_SPEED=1000 # 1 second + +# Human-friendly filename template (mind the single-quotes!) +CYCHE_LOG_FILENAME='${job.job}_${abv_hash}_${job.service}.log' diff --git a/dmt/dmt b/dmt/dmt index c23e844..2a12bff 100755 --- a/dmt/dmt +++ b/dmt/dmt @@ -109,4 +109,8 @@ case ${route[0]} in "style.css") template "style.css" text/css ;; + + "script.js") + template "script.js" text/javascript + ;; esac diff --git a/dmt/html/jobs.html b/dmt/html/jobs.html index 72269f6..6ad138a 100644 --- a/dmt/html/jobs.html +++ b/dmt/html/jobs.html @@ -20,7 +20,4 @@ -
- -

Older Jobs

-
+ diff --git a/dmt/script.js b/dmt/script.js new file mode 100644 index 0000000..66e2d31 --- /dev/null +++ b/dmt/script.js @@ -0,0 +1,154 @@ +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" ? "remove" : 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_IDLE_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_ACTIVE_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%)); -- cgit v1.2.3