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);
+ });
+ });
+});