summaryrefslogtreecommitdiffstats
path: root/dmt
diff options
context:
space:
mode:
authorMalfurious <m@lfurio.us>2023-11-11 21:50:47 -0500
committerMalfurious <m@lfurio.us>2023-11-15 23:11:08 -0500
commitb1c95b10f4d1a0d49c2b888dcf10872b4a474569 (patch)
treef94361d5c671755c4bb8c7def638ef40706950ac /dmt
parenta021cc6899c82b8af55013514dbc8c7b2a383fcd (diff)
downloadcychedelic-b1c95b10f4d1a0d49c2b888dcf10872b4a474569.tar.gz
cychedelic-b1c95b10f4d1a0d49c2b888dcf10872b4a474569.zip
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 <m@lfurio.us>
Diffstat (limited to 'dmt')
-rw-r--r--dmt/config.sh12
-rwxr-xr-xdmt/dmt4
-rw-r--r--dmt/html/jobs.html5
-rw-r--r--dmt/script.js154
4 files changed, 171 insertions, 4 deletions
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 @@
</div>
</div>
-<div id="latest_jobs"></div>
-
-<h1>Older Jobs</h1>
-<div id="older_jobs"></div>
+<div class="hidden" id="list"></div>
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 = "&#x2b07;";
+const ICON_MAINT = "&#x21bb;";
+const ICON_EVENT = "&#x03df;";
+const ICON_PASS = "&#x2714;";
+const ICON_FAIL = "&#x2716;";
+
+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 <div>
+ 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 = `
+ <div class="box" id="${job.job}">
+ <div class="box-title">
+ <a href="/api/log/${job.job}/%($CYCHE_LOG_FILENAME%)">
+ <span class="button">${ICON_VIEW}</span>
+ </a>
+ #${job.job}
+ ${job.service}
+ <small><i>${abv_hash}</i></small>
+
+ <span class="right">
+ <span class="time" id="${job.job}_time"
+ data-timestamp="${job.time}"></span>
+ <span id="${job.job}_result"></span>
+ ${reason}
+ </span>
+ </div>
+
+ <div class="box-text">
+ <pre id="${job.job}_log"></pre>
+ </div>
+ </div>`;
+
+ 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%));