From 14ea136fbbad447d1eb668fcc09aadb75de88ee3 Mon Sep 17 00:00:00 2001 From: Artur Borecki Date: Thu, 12 Mar 2026 20:33:38 +0100 Subject: [PATCH] feat(panel.js): move js from inline to separate script --- src/judas_server/web/static/js/panel.js | 163 ++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 src/judas_server/web/static/js/panel.js diff --git a/src/judas_server/web/static/js/panel.js b/src/judas_server/web/static/js/panel.js new file mode 100644 index 0000000..4e11461 --- /dev/null +++ b/src/judas_server/web/static/js/panel.js @@ -0,0 +1,163 @@ +$(document).ready(function () { + const socket = io(); + + const showNotify = (message) => { + $("#notify").stop().fadeIn(); + $("#notify").text(message); + setTimeout(() => { + $("#notify").fadeOut(); + }, 3000); + }; + + const loadClientDetails = (clientId) => { + fetch(`/client/${clientId}`) + .then((response) => response.text()) + .then((html) => { + $("#content").html(html); + }) + .catch((error) => { + console.error("Error fetching client details:", error); + }); + }; + + // load client_details for the client specified in the URL + const urlParams = new URLSearchParams(window.location.search); + const clientId = urlParams.get("client"); + if (clientId) { + loadClientDetails(clientId); + $(`#client-list a[href="?client=${clientId}"]`).addClass("active"); + } + + $("#notify").hide(); + + socket.on("connect", () => { + console.log("Connected to server"); + $("#no-connection-message").hide(); + showNotify("Connected to server"); + }); + + socket.on("disconnect", () => { + console.log("Disconnected from server"); + $("#no-connection-message").show(); + showNotify("Disconnected from server"); + }); + + socket.on("update_data", (data) => { + const clientList = $("#client-list"); + const existingItems = {}; + + // Index current
  • by clientId + clientList.children("li").each(function () { + const a = $(this).find("a"); + if (a.length) { + const clientId = a.attr("href").substring(1); + existingItems[clientId] = $(this); + } + }); + + // Track which clientIds are still present + const seen = new Set(); + + Object.entries(data).forEach(([clientId, client]) => { + seen.add(clientId); + + // Build icon + const iconElement = document.createElement("i"); + switch (client.status) { + case "online": + iconElement.className = "fi fi-sr-play text-ctp-green"; + break; + case "offline": + iconElement.className = "fi fi-sr-stop text-ctp-red"; + break; + case "pending": + iconElement.className = "fi fi-sr-pending text-ctp-yellow"; + break; + case "stale": + iconElement.className = "fi fi-sr-skull text-ctp-text"; + break; + default: + iconElement.className = "fi fi-rr-question text-ctp-gray"; + } + + // Time since last seen + const lastSeen = new Date(client.last_seen * 1000); + const now = new Date(); + const timeSinceLastSeen = Math.floor((now - lastSeen) / 1000); + const days = Math.floor(timeSinceLastSeen / 86400); + const hours = Math.floor((timeSinceLastSeen % 86400) / 3600); + const minutes = Math.floor((timeSinceLastSeen % 3600) / 60); + const seconds = timeSinceLastSeen % 60; + let timeSinceLastSeenText = ""; + if (days > 0) timeSinceLastSeenText += `${days}d `; + if (hours > 0) timeSinceLastSeenText += `${hours}h `; + if (minutes > 0) timeSinceLastSeenText += `${minutes}m `; + if (seconds > 0) timeSinceLastSeenText += `${seconds}s`; + + timeSinceLastSeenText = timeSinceLastSeenText.trim() || "0s"; + + let statusText = `${client.id} (${timeSinceLastSeenText})`; + + // check if
  • exists + if (existingItems[clientId]) { + // update if needed + const li = existingItems[clientId]; + const a = li.find("a"); + if (a.text() !== statusText) { + a.text(statusText); + } + + li.attr( + "title", + `Status: ${client.status}\nLast Seen: ${lastSeen.toISOString()}`, + ); + + // update icon + const icon = li.find("i")[0]; + if (icon) { + icon.className = iconElement.className; + } + } else { + // add new
  • + const li = $("
  • "); + li.append(iconElement); + const a = $("") + .text(statusText) + .attr("href", `?client=${clientId}`); + + li.attr( + "title", + `Status: ${client.status}\nLast Seen: ${lastSeen.toISOString()}`, + ); + + li.append(a); + clientList.append(li); + } + }); + + // Remove
  • for clients no longer present + Object.keys(existingItems).forEach((clientId) => { + if (!seen.has(clientId)) { + existingItems[clientId].remove(); + } + }); + + // Re-bind click handlers + $("#client-list li > a") + .off("click") + .on("click", function (e) { + let clientId = $(this).attr("href").substring(1); + // this is client=clientId + + clientId = clientId.replace("client=", ""); + + loadClientDetails(clientId); + $("#client-list a").removeClass("bg-ctp-surface0"); + $(this).addClass("bg-ctp-surface0"); + e.preventDefault(); + + let newUrl = `${window.location.pathname}?client=${clientId}`; + window.history.pushState({ path: newUrl }, "", newUrl); + }); + }); +});