From feb712e2de0ed215fcbcecd623573936c333ecc5 Mon Sep 17 00:00:00 2001 From: KingKDot Date: Mon, 13 Jul 2026 21:54:21 -0500 Subject: [PATCH] fix turbo on dashboard + optimize a few more things. --- Overlord-Server/public/assets/build.js | 7 +- Overlord-Server/public/assets/data.js | 1 + Overlord-Server/public/assets/file-share.js | 66 ++++--------------- Overlord-Server/public/assets/main.js | 1 + .../public/assets/stimulus/application.js | 10 +++ .../controllers/clipboard-controller.js | 33 ++++++++++ .../controllers/confirm-controller.js | 12 ++++ .../controllers/countdown-controller.js | 53 +++++++++++++++ .../stimulus/controllers/reveal-controller.js | 17 +++++ .../controllers/sessions-controller.js | 7 -- .../stimulus/controllers/toggle-controller.js | 11 ++++ .../public/assets/turbo-navigation.js | 1 + Overlord-Server/public/assets/ui.js | 23 ++++--- Overlord-Server/public/file-share.html | 24 +++++-- Overlord-Server/public/settings.html | 7 +- .../src/server/ui/sessions-view.ts | 4 +- 16 files changed, 194 insertions(+), 83 deletions(-) create mode 100644 Overlord-Server/public/assets/stimulus/controllers/clipboard-controller.js create mode 100644 Overlord-Server/public/assets/stimulus/controllers/confirm-controller.js create mode 100644 Overlord-Server/public/assets/stimulus/controllers/countdown-controller.js create mode 100644 Overlord-Server/public/assets/stimulus/controllers/reveal-controller.js create mode 100644 Overlord-Server/public/assets/stimulus/controllers/toggle-controller.js diff --git a/Overlord-Server/public/assets/build.js b/Overlord-Server/public/assets/build.js index a8d35e8..f14a6c8 100644 --- a/Overlord-Server/public/assets/build.js +++ b/Overlord-Server/public/assets/build.js @@ -2,7 +2,6 @@ import { createBuildProfileManager } from "./build-profile-manager.js"; import { createBuildHistoryManager, formatFileSize, - updateExpirationTimer, } from "./build-history-manager.js"; const form = document.getElementById("build-form"); @@ -2259,7 +2258,8 @@ function showBuildFiles(files, buildId, expiresAt) { const timer = document.createElement("span"); timer.id = "expiration-timer"; timer.className = "text-yellow-400 font-medium"; - timer.dataset.expires = String(expiresAt); + timer.dataset.controller = "countdown"; + timer.dataset.countdownExpiresAtValue = String(expiresAt); timer.textContent = "Calculating..."; right.appendChild(clockIcon); right.appendChild(expiresLabel); @@ -2270,9 +2270,6 @@ function showBuildFiles(files, buildId, expiresAt) { buildInfoDiv.appendChild(infoRow); buildFilesDiv.appendChild(buildInfoDiv); - updateExpirationTimer(timer, expiresAt); - setInterval(() => updateExpirationTimer(timer, expiresAt), 60000); - files.forEach((file) => { const fileDiv = document.createElement("div"); fileDiv.className = diff --git a/Overlord-Server/public/assets/data.js b/Overlord-Server/public/assets/data.js index 4983ef2..4dbd944 100644 --- a/Overlord-Server/public/assets/data.js +++ b/Overlord-Server/public/assets/data.js @@ -108,6 +108,7 @@ function moveClientCardImmediately(msg) { export function registerRenderer(fn) { render = fn; + state.lastDigest = ""; } function clientQueryParams(page = state.page) { diff --git a/Overlord-Server/public/assets/file-share.js b/Overlord-Server/public/assets/file-share.js index ca43d5f..041a9b4 100644 --- a/Overlord-Server/public/assets/file-share.js +++ b/Overlord-Server/public/assets/file-share.js @@ -23,12 +23,6 @@ uploadInfinite?.addEventListener("change", () => { }); if (uploadMaxDownloads) uploadMaxDownloads.disabled = true; -const passwordInfoBtn = document.getElementById("password-info-btn"); -const passwordInfoPanel = document.getElementById("password-info-panel"); -passwordInfoBtn?.addEventListener("click", () => { - passwordInfoPanel?.classList.toggle("hidden"); -}); - async function loadCurrentUser() { try { const res = await fetch("/api/auth/me"); @@ -113,12 +107,14 @@ async function loadFiles() {
${canManage ? ` ` : ""} @@ -137,57 +135,21 @@ async function loadFiles() { }) .join(""); - attachActionListeners(); } catch (err) { console.error("Load files error:", err); fileList.innerHTML = `Failed to load files`; } } -function attachActionListeners() { - fileList.addEventListener("click", (e) => { - const btn = e.target.closest(".action-btn"); - if (!btn) return; +fileList?.addEventListener("click", (e) => { + const btn = e.target.closest("[data-file-action]"); + if (!btn) return; - const action = btn.dataset.action; - const fileId = btn.dataset.fileId; - - switch (action) { - case "copy-link": - copyDownloadLink(fileId); - break; - case "edit": - openEditModal(btn); - break; - case "delete": - deleteFile(fileId, btn.dataset.filename); - break; - } - }); -} - -function copyDownloadLink(fileId) { - const url = getDownloadUrl(fileId); - navigator.clipboard.writeText(url).then(() => { - if (window.showToast) { - window.showToast("Download link copied to clipboard!", "success"); - } - }).catch(() => { - const input = document.createElement("input"); - input.value = url; - document.body.appendChild(input); - input.select(); - document.execCommand("copy"); - document.body.removeChild(input); - if (window.showToast) { - window.showToast("Download link copied!", "success"); - } - }); -} - -async function deleteFile(fileId, filename) { - if (!confirm(`Delete "${filename}"? This cannot be undone.`)) return; + if (btn.dataset.fileAction === "edit") openEditModal(btn); + if (btn.dataset.fileAction === "delete") deleteFile(btn.dataset.fileId); +}); +async function deleteFile(fileId) { try { const res = await fetch(`/api/file-share/${fileId}`, { method: "DELETE" }); if (res.ok) { diff --git a/Overlord-Server/public/assets/main.js b/Overlord-Server/public/assets/main.js index 67ad20a..96505b7 100644 --- a/Overlord-Server/public/assets/main.js +++ b/Overlord-Server/public/assets/main.js @@ -1141,6 +1141,7 @@ function initializeRenderer() { } }); initDashboardStats(); + renderCachedClients({ reorder: true, force: true }); setupDashboardThumbnailLoader(); refreshGroupFilter(); loadWithOptions(); diff --git a/Overlord-Server/public/assets/stimulus/application.js b/Overlord-Server/public/assets/stimulus/application.js index 182fbde..80109a0 100644 --- a/Overlord-Server/public/assets/stimulus/application.js +++ b/Overlord-Server/public/assets/stimulus/application.js @@ -1,9 +1,19 @@ import { Application } from "/vendor/hotwired/stimulus.js"; import SessionsController from "./controllers/sessions-controller.js"; +import ClipboardController from "./controllers/clipboard-controller.js"; +import ConfirmController from "./controllers/confirm-controller.js"; +import CountdownController from "./controllers/countdown-controller.js"; +import RevealController from "./controllers/reveal-controller.js"; +import ToggleController from "./controllers/toggle-controller.js"; const application = Application.start(); application.debug = false; application.register("sessions", SessionsController); +application.register("clipboard", ClipboardController); +application.register("confirm", ConfirmController); +application.register("countdown", CountdownController); +application.register("reveal", RevealController); +application.register("toggle", ToggleController); window.OverlordStimulus = application; diff --git a/Overlord-Server/public/assets/stimulus/controllers/clipboard-controller.js b/Overlord-Server/public/assets/stimulus/controllers/clipboard-controller.js new file mode 100644 index 0000000..1262b6b --- /dev/null +++ b/Overlord-Server/public/assets/stimulus/controllers/clipboard-controller.js @@ -0,0 +1,33 @@ +import { Controller } from "/vendor/hotwired/stimulus.js"; + +export default class extends Controller { + static values = { + text: String, + successMessage: { type: String, default: "Copied to clipboard" }, + }; + + async copy(event) { + event.preventDefault(); + if (!this.hasTextValue) return; + + try { + await navigator.clipboard.writeText(this.textValue); + } catch { + const input = document.createElement("textarea"); + input.value = this.textValue; + input.setAttribute("readonly", ""); + input.style.position = "fixed"; + input.style.opacity = "0"; + document.body.appendChild(input); + input.select(); + document.execCommand("copy"); + input.remove(); + } + + window.showToast?.(this.successMessageValue, "success"); + this.element.dispatchEvent(new CustomEvent("clipboard:copied", { + bubbles: true, + detail: { text: this.textValue }, + })); + } +} diff --git a/Overlord-Server/public/assets/stimulus/controllers/confirm-controller.js b/Overlord-Server/public/assets/stimulus/controllers/confirm-controller.js new file mode 100644 index 0000000..cab4a6a --- /dev/null +++ b/Overlord-Server/public/assets/stimulus/controllers/confirm-controller.js @@ -0,0 +1,12 @@ +import { Controller } from "/vendor/hotwired/stimulus.js"; + +export default class extends Controller { + static values = { message: String }; + + confirm(event) { + if (!this.hasMessageValue || window.confirm(this.messageValue)) return; + + event.preventDefault(); + event.stopImmediatePropagation(); + } +} diff --git a/Overlord-Server/public/assets/stimulus/controllers/countdown-controller.js b/Overlord-Server/public/assets/stimulus/controllers/countdown-controller.js new file mode 100644 index 0000000..a9630c6 --- /dev/null +++ b/Overlord-Server/public/assets/stimulus/controllers/countdown-controller.js @@ -0,0 +1,53 @@ +import { Controller } from "/vendor/hotwired/stimulus.js"; + +export default class extends Controller { + static values = { + expiresAt: Number, + interval: { type: Number, default: 60_000 }, + }; + + connect() { + this.update(); + if (this.expiresAtValue > Date.now()) { + this.clock = window.setInterval(() => this.update(), this.intervalValue); + } + } + + disconnect() { + window.clearInterval(this.clock); + } + + update() { + const remaining = this.expiresAtValue - Date.now(); + if (remaining <= 0) { + this.element.textContent = "Expired"; + this.setTone("text-red-400"); + window.clearInterval(this.clock); + return; + } + + const days = Math.floor(remaining / 86_400_000); + const hours = Math.floor((remaining % 86_400_000) / 3_600_000); + const minutes = Math.floor((remaining % 3_600_000) / 60_000); + this.element.textContent = days > 0 + ? `${days}d ${hours}h` + : hours > 0 + ? `${hours}h ${minutes}m` + : `${minutes}m`; + this.setTone(days >= 3 + ? "text-green-400" + : days >= 1 + ? "text-yellow-400" + : "text-orange-400"); + } + + setTone(tone) { + this.element.classList.remove( + "text-red-400", + "text-green-400", + "text-yellow-400", + "text-orange-400", + ); + this.element.classList.add(tone, "font-medium"); + } +} diff --git a/Overlord-Server/public/assets/stimulus/controllers/reveal-controller.js b/Overlord-Server/public/assets/stimulus/controllers/reveal-controller.js new file mode 100644 index 0000000..76f30bb --- /dev/null +++ b/Overlord-Server/public/assets/stimulus/controllers/reveal-controller.js @@ -0,0 +1,17 @@ +import { Controller } from "/vendor/hotwired/stimulus.js"; + +export default class extends Controller { + static targets = ["input", "icon"]; + + toggle(event) { + event.preventDefault(); + const revealing = this.inputTarget.type === "password"; + this.inputTarget.type = revealing ? "text" : "password"; + event.currentTarget.setAttribute("aria-pressed", String(revealing)); + event.currentTarget.setAttribute("aria-label", revealing ? "Hide value" : "Show value"); + if (this.hasIconTarget) { + this.iconTarget.classList.toggle("fa-eye", !revealing); + this.iconTarget.classList.toggle("fa-eye-slash", revealing); + } + } +} diff --git a/Overlord-Server/public/assets/stimulus/controllers/sessions-controller.js b/Overlord-Server/public/assets/stimulus/controllers/sessions-controller.js index 2eb1823..d1d411a 100644 --- a/Overlord-Server/public/assets/stimulus/controllers/sessions-controller.js +++ b/Overlord-Server/public/assets/stimulus/controllers/sessions-controller.js @@ -12,13 +12,6 @@ export default class extends Controller { window.clearInterval(this.clock); } - confirm(event) { - const message = event.currentTarget.dataset.confirmMessage; - if (message && !window.confirm(message)) { - event.preventDefault(); - } - } - updateTimes() { const now = Math.floor(Date.now() / 1000); for (const element of this.timeTargets) { diff --git a/Overlord-Server/public/assets/stimulus/controllers/toggle-controller.js b/Overlord-Server/public/assets/stimulus/controllers/toggle-controller.js new file mode 100644 index 0000000..094520f --- /dev/null +++ b/Overlord-Server/public/assets/stimulus/controllers/toggle-controller.js @@ -0,0 +1,11 @@ +import { Controller } from "/vendor/hotwired/stimulus.js"; + +export default class extends Controller { + static targets = ["content"]; + + toggle(event) { + event.preventDefault(); + const hidden = this.contentTarget.classList.toggle("hidden"); + event.currentTarget.setAttribute("aria-expanded", String(!hidden)); + } +} diff --git a/Overlord-Server/public/assets/turbo-navigation.js b/Overlord-Server/public/assets/turbo-navigation.js index 5656b98..b1f56f8 100644 --- a/Overlord-Server/public/assets/turbo-navigation.js +++ b/Overlord-Server/public/assets/turbo-navigation.js @@ -22,6 +22,7 @@ const persistentIds = [ "chat-bubble", "chat-panel", "cert-trust-banner", + "command-menu", ]; function normalizeUrl(value, base = window.location.href) { diff --git a/Overlord-Server/public/assets/ui.js b/Overlord-Server/public/assets/ui.js index f572618..cba2a31 100644 --- a/Overlord-Server/public/assets/ui.js +++ b/Overlord-Server/public/assets/ui.js @@ -1,3 +1,5 @@ +import { runWithoutPageTracking } from "./turbo-navigation.js"; + // ── Context menu data ───────────────────────────────────────────────────────── const MENU_GROUPS = [ { @@ -254,7 +256,6 @@ menuStyle.textContent = ` #ctx-main { border-radius: 8px 8px 0 0; } } `; -document.head.appendChild(menuStyle); // ── Build DOM ───────────────────────────────────────────────────────────────── function buildItemHTML(item) { @@ -282,9 +283,11 @@ const subPanelsHTML = const menu = document.createElement("div"); menu.id = "command-menu"; +menu.setAttribute("data-turbo-permanent", ""); menu.setAttribute("role", "menu"); menu.setAttribute("aria-hidden", "true"); menu.innerHTML = `
${mainRowsHTML}
${subPanelsHTML}
`; +menu.prepend(menuStyle); document.body.appendChild(menu); const ctxMain = menu.querySelector("#ctx-main"); @@ -363,32 +366,34 @@ function refitMobileMenu() { } ctxMain.querySelectorAll(".ctx-row").forEach(rowEl => { - rowEl.addEventListener("mouseenter", () => { + runWithoutPageTracking(() => rowEl.addEventListener("mouseenter", () => { if (isMobileMenu()) return; if (rowEl.disabled || rowEl.getAttribute("aria-disabled") === "true") return; const groupId = rowEl.dataset.groupToggle; if (groupId) showSubmenu(groupId, rowEl); - }); + })); }); // Debounced hide — prevents the 5px gap between panels from closing the submenu function scheduleHide() { if (!isMobileMenu()) _hideTimer = setTimeout(hideSubmenu, 150); } function cancelHide() { clearTimeout(_hideTimer); _hideTimer = null; } -ctxMain.addEventListener("mouseleave", scheduleHide); -ctxMain.addEventListener("mouseenter", cancelHide); -ctxSub.addEventListener("mouseenter", cancelHide); -ctxSub.addEventListener("mouseleave", scheduleHide); +runWithoutPageTracking(() => { + ctxMain.addEventListener("mouseleave", scheduleHide); + ctxMain.addEventListener("mouseenter", cancelHide); + ctxSub.addEventListener("mouseenter", cancelHide); + ctxSub.addEventListener("mouseleave", scheduleHide); +}); // Touch / mobile: tap to toggle group ctxMain.querySelectorAll(".ctx-row").forEach(rowEl => { - rowEl.addEventListener("click", (e) => { + runWithoutPageTracking(() => rowEl.addEventListener("click", (e) => { const groupId = rowEl.dataset.groupToggle; if (!groupId) return; if (rowEl.disabled || rowEl.getAttribute("aria-disabled") === "true") return; e.stopPropagation(); if (activeGroupId === groupId) { hideSubmenu(); } else { showSubmenu(groupId, rowEl); } - }); + })); }); const modal = document.createElement("div"); diff --git a/Overlord-Server/public/file-share.html b/Overlord-Server/public/file-share.html index f1800b3..428edb5 100644 --- a/Overlord-Server/public/file-share.html +++ b/Overlord-Server/public/file-share.html @@ -40,18 +40,23 @@ class="block w-full text-sm text-slate-300 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-medium file:bg-slate-800 file:text-slate-200 hover:file:bg-slate-700 cursor-pointer" />
-
+
-
- -