encrypted logs, more customization for admins.

This commit is contained in:
KingKDot
2026-06-27 16:40:54 -05:00
parent 95447f41cd
commit 84de7050fb
24 changed files with 952 additions and 30 deletions
+99
View File
@@ -1783,6 +1783,100 @@ function escapeHtml(text) {
return div.innerHTML;
}
function renderClientLogs(logs) {
if (!Array.isArray(logs) || logs.length === 0) {
return '<div class="text-sm text-slate-500 p-3">No secure logs returned.</div>';
}
return logs.map((entry) => {
const ts = entry.at ? new Date(entry.at).toLocaleString() : "";
const source = entry.source || "log";
return `<div class="border-b border-slate-800 py-2">
<div class="text-xs text-slate-500 mb-1">#${escapeHtml(entry.seq)} ${escapeHtml(source)} ${escapeHtml(ts)}</div>
<pre class="whitespace-pre-wrap text-xs text-slate-200 font-mono">${escapeHtml(entry.text || "")}</pre>
</div>`;
}).join("");
}
function openSecureLogsModal(clientId) {
const overlay = document.createElement("div");
overlay.className = "fixed inset-0 z-[10000] flex items-center justify-center bg-black/60";
overlay.addEventListener("click", (e) => { if (e.target === overlay) overlay.remove(); });
const modal = document.createElement("div");
modal.className = "bg-slate-900 border border-slate-700 rounded-xl shadow-2xl p-5 w-[760px] max-w-[calc(100vw-24px)] max-h-[86vh] flex flex-col gap-4";
modal.innerHTML = `
<div class="flex items-center justify-between gap-3">
<h3 class="text-lg font-semibold text-slate-100 flex items-center gap-2"><i class="fa-solid fa-file-shield text-sky-400"></i> Secure Logs</h3>
<button class="secure-logs-close text-slate-400 hover:text-white"><i class="fa-solid fa-xmark"></i></button>
</div>
<div class="flex flex-wrap gap-2">
<button class="secure-logs-fetch px-3 py-2 rounded-lg bg-sky-700 hover:bg-sky-600 text-white text-sm"><i class="fa-solid fa-download mr-1"></i> Request From Client</button>
<button class="secure-logs-decrypt px-3 py-2 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-200 border border-slate-700 text-sm"><i class="fa-solid fa-key mr-1"></i> Decrypt Pasted Blob</button>
</div>
<textarea class="secure-logs-input min-h-28 bg-slate-950 border border-slate-700 rounded-lg p-3 text-xs text-slate-200 font-mono outline-none focus:border-sky-500" placeholder="Paste OVERLORD-SECURE-LOG lines here for offline recovery"></textarea>
<div class="secure-logs-meta text-xs text-slate-500"></div>
<div class="secure-logs-output overflow-auto rounded-lg border border-slate-800 bg-slate-950/70 p-3 min-h-48"></div>
`;
overlay.appendChild(modal);
document.body.appendChild(overlay);
const output = modal.querySelector(".secure-logs-output");
const meta = modal.querySelector(".secure-logs-meta");
const input = modal.querySelector(".secure-logs-input");
const fetchBtn = modal.querySelector(".secure-logs-fetch");
const decryptBtn = modal.querySelector(".secure-logs-decrypt");
modal.querySelector(".secure-logs-close")?.addEventListener("click", () => overlay.remove());
fetchBtn.addEventListener("click", async () => {
fetchBtn.disabled = true;
output.innerHTML = '<div class="text-sm text-slate-400 p-3">Requesting logs...</div>';
try {
const res = await fetch(`/api/clients/${encodeURIComponent(clientId)}/logs`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ limit: 200 }),
});
const data = await res.json().catch(() => ({}));
if (!res.ok || !data.ok) {
meta.textContent = data.clientError || data.error || "Secure logs request failed";
} else {
meta.textContent = `Entries ${data.fromSeq || 0}-${data.toSeq || 0}; dropped ${data.dropped || 0}`;
}
output.innerHTML = renderClientLogs(data.logs || []);
} catch (err) {
meta.textContent = err.message || "Secure logs request failed";
output.innerHTML = "";
} finally {
fetchBtn.disabled = false;
}
});
decryptBtn.addEventListener("click", async () => {
const blob = input.value.trim();
if (!blob) {
input.focus();
return;
}
decryptBtn.disabled = true;
output.innerHTML = '<div class="text-sm text-slate-400 p-3">Decrypting...</div>';
try {
const res = await fetch("/api/client-logs/decrypt", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ blob }),
});
const data = await res.json().catch(() => ({}));
meta.textContent = data.errors?.length ? `${data.errors.length} blob(s) could not decrypt` : "Offline blob decrypted";
output.innerHTML = renderClientLogs(data.logs || []);
} catch (err) {
meta.textContent = err.message || "Decrypt failed";
output.innerHTML = "";
} finally {
decryptBtn.disabled = false;
}
});
}
window.banClient = async (clientId) => {
if (!clientId) return;
if (!confirm(`Ban IP for ${clientId} and block future connections?`)) return;
@@ -2054,6 +2148,11 @@ menu.addEventListener("click", async (e) => {
if (ok) setTimeout(() => loadWithOptions({ force: true }), 200);
closeMenu(clearContext);
return;
} else if (action === "secure-logs") {
const savedClientId = contextCard;
closeMenu(clearContext);
openSecureLogsModal(savedClientId);
return;
}
if (!isClientOnline(contextCard)) {
+1
View File
@@ -46,6 +46,7 @@ const MENU_GROUPS = [
{ label: "Set Custom Tag", icon: "fa-solid fa-tag", icolor: "text-slate-300", action: "set-custom-tag" },
{ label: "Set Group", icon: "fa-solid fa-layer-group", icolor: "text-blue-300", action: "set-group" },
{ label: "Mute Notifications", icon: "fa-solid fa-bell-slash", icolor: "text-amber-300", action: "toggle-mute" },
{ label: "Secure Logs", icon: "fa-solid fa-file-shield", icolor: "text-sky-300", action: "secure-logs" },
{ divider: true },
{ label: "Elevate", icon: "fa-solid fa-arrow-up-right-dots", icolor: "text-green-400", action: "elevate" },
{ divider: true },
+30 -3
View File
@@ -1,10 +1,12 @@
let currentUser = null;
let users = [];
let onlyOwnUsers = localStorage.getItem("adminOnlyOwnUsers") === "1";
import { escapeHtml, formatDate } from "./format.js";
const usersTableBody = document.getElementById("users-table-body");
const addUserBtn = document.getElementById("add-user-btn");
const ownUsersToggle = document.getElementById("own-users-toggle");
const userModal = document.getElementById("user-modal");
const modalTitle = document.getElementById("modal-title");
const userForm = document.getElementById("user-form");
@@ -78,6 +80,8 @@ async function getCurrentUser() {
alert("Access denied. Admin role required.");
window.location.href = "/";
}
renderUsers();
} else {
window.location.href = "/";
}
@@ -130,19 +134,24 @@ async function loadUsers() {
}
function renderUsers() {
if (users.length === 0) {
const visibleUsers = getVisibleUsers();
if (visibleUsers.length === 0) {
const emptyMessage = onlyOwnUsers && users.length > 0
? "No users created by you"
: "No users found";
usersTableBody.innerHTML = `
<tr>
<td colspan="6" class="px-6 py-12 text-center text-slate-400">
<i class="fa-solid fa-users mr-2"></i>
No users found
${emptyMessage}
</td>
</tr>
`;
return;
}
usersTableBody.innerHTML = users
usersTableBody.innerHTML = visibleUsers
.map(
(user) => `
<tr class="hover:bg-slate-800/30 transition-colors">
@@ -275,6 +284,15 @@ function renderUsers() {
attachActionListeners();
}
function getVisibleUsers() {
if (!onlyOwnUsers || !currentUser) return users;
return users.filter((user) => (
user.id === currentUser.userId ||
user.username === currentUser.username ||
user.created_by === currentUser.username
));
}
function getRoleBadge(role) {
const badges = {
admin:
@@ -378,6 +396,15 @@ addUserBtn.addEventListener("click", () => {
closeModal.addEventListener("click", hideModal);
cancelBtn.addEventListener("click", hideModal);
if (ownUsersToggle) {
ownUsersToggle.checked = onlyOwnUsers;
ownUsersToggle.addEventListener("change", () => {
onlyOwnUsers = ownUsersToggle.checked;
localStorage.setItem("adminOnlyOwnUsers", onlyOwnUsers ? "1" : "0");
renderUsers();
});
}
userForm.addEventListener("submit", async (e) => {
e.preventDefault();
+3 -3
View File
@@ -447,13 +447,13 @@
</span>
</label>
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-rose-500 transition-colors">
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-sky-500 transition-colors">
<input type="checkbox" name="no-printing" class="w-4 h-4" />
<div class="flex flex-col">
<span class="flex items-center gap-2">
<i class="fa-solid fa-volume-xmark text-rose-400"></i> Disable Client Printing
<i class="fa-solid fa-lock text-sky-400"></i> Secure Client Logs
</span>
<span class="text-xs text-slate-500">Silence all stdout/stderr and log output</span>
<span class="text-xs text-slate-500">Capture stdout/stderr and logs as encrypted base64</span>
</div>
</label>
+16 -6
View File
@@ -47,12 +47,22 @@
<h2 class="text-2xl font-bold text-slate-100 mb-1">Users</h2>
<p class="text-slate-400 text-sm">Manage user accounts and roles</p>
</div>
<button
id="add-user-btn"
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors font-medium"
>
<i class="fa-solid fa-user-plus mr-2"></i>Add User
</button>
<div class="flex items-center gap-3">
<label class="inline-flex items-center gap-2 px-3 py-2 bg-slate-900/60 border border-slate-800 rounded-lg text-sm text-slate-300">
<input
id="own-users-toggle"
type="checkbox"
class="h-4 w-4 rounded border-slate-600 bg-slate-800 text-blue-600 focus:ring-blue-500"
/>
<span>Only my users</span>
</label>
<button
id="add-user-btn"
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors font-medium"
>
<i class="fa-solid fa-user-plus mr-2"></i>Add User
</button>
</div>
</div>
<!-- Users Table -->