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)) {