function showNotice(msg, isError) { const banner = document.getElementById("status-banner"); if (!banner) return; banner.textContent = msg; banner.style.display = "block"; banner.style.background = isError ? "var(--danger-bg)" : "var(--success-bg)"; banner.style.color = isError ? "var(--danger-text)" : "var(--success-text)"; banner.style.border = isError ? "1px solid var(--danger-border)" : "1px solid var(--success-border)"; setTimeout(() => { banner.style.display = "none"; }, 5000); } globalThis.showNotice = showNotice; function filterUsersList() { const queryInput = document.getElementById("userSearchInput"); if (!queryInput) return; const query = queryInput.value.toLowerCase().trim(); const rows = document.querySelectorAll(".user-row"); const cards = document.querySelectorAll(".user-card"); rows.forEach((r) => { const text = r.getAttribute("data-search") || ""; r.style.display = text.includes(query) ? "" : "none"; }); cards.forEach((c) => { const text = c.getAttribute("data-search") || ""; c.style.display = text.includes(query) ? "" : "none"; }); } globalThis.filterUsersList = filterUsersList; function filterAppsList() { const queryInput = document.getElementById("appSearchInput"); if (!queryInput) return; const query = queryInput.value.toLowerCase().trim(); const rows = document.querySelectorAll(".app-row"); const cards = document.querySelectorAll(".app-card"); rows.forEach((r) => { const text = r.getAttribute("data-search") || ""; r.style.display = text.includes(query) ? "" : "none"; }); cards.forEach((c) => { const text = c.getAttribute("data-search") || ""; c.style.display = text.includes(query) ? "" : "none"; }); } globalThis.filterAppsList = filterAppsList; function filterAuditLogs() { const queryInput = document.getElementById("auditSearchInput"); if (!queryInput) return; const query = queryInput.value.toLowerCase().trim(); const rows = document.querySelectorAll(".log-row"); const cards = document.querySelectorAll(".log-card"); rows.forEach((r) => { const text = r.getAttribute("data-search") || ""; r.style.display = text.includes(query) ? "" : "none"; }); cards.forEach((c) => { const text = c.getAttribute("data-search") || ""; c.style.display = text.includes(query) ? "" : "none"; }); } globalThis.filterAuditLogs = filterAuditLogs; async function updateStatus(userId, status, username) { if (!confirm("Set user @" + username + " to " + status.toUpperCase() + "?")) { return; } try { const res = await fetch("/api/admin/users/" + userId + "/status", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ status }), }); if (res.ok) { showNotice("User status updated to " + status, false); setTimeout(() => globalThis.location.reload(), 600); } else { const data = await res.json(); showNotice(data.error || "Failed to update status", true); } } catch (_err) { showNotice("Network error updating status", true); } } globalThis.updateStatus = updateStatus; async function handleUpdateProfile(e, userId) { e.preventDefault(); const displayNameInput = document.getElementById("displayNameInput"); const displayName = displayNameInput ? displayNameInput.value.trim() : ""; try { const res = await fetch("/api/admin/users/" + userId + "/profile", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ displayName }), }); const data = await res.json(); if (res.ok) { showNotice("User display name saved successfully!", false); setTimeout(() => globalThis.location.reload(), 600); } else { showNotice(data.error || "Failed to update display name", true); } } catch (_err) { showNotice("Network error updating display name", true); } } globalThis.handleUpdateProfile = handleUpdateProfile; async function handleGrantAccess(e, userId) { e.preventDefault(); const grantAppId = document.getElementById("grantAppId"); const grantRole = document.getElementById("grantRole"); if (!grantAppId || !grantRole) return; const appId = grantAppId.value; const role = grantRole.value; try { const res = await fetch("/api/admin/users/" + userId + "/grants", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ appId, role }), }); const data = await res.json(); if (res.ok) { showNotice("Application access granted successfully!", false); setTimeout(() => globalThis.location.reload(), 600); } else { showNotice(data.error || "Failed to update application grant", true); } } catch (_err) { showNotice("Network error updating grant", true); } } globalThis.handleGrantAccess = handleGrantAccess; async function revokeGrant(userId, appId, appName) { if (!confirm('Revoke access to "' + appName + '" for this user?')) return; try { const res = await fetch("/api/admin/users/" + userId + "/grants/" + appId, { method: "DELETE", }); if (res.ok) { showNotice("Access revoked", false); setTimeout(() => globalThis.location.reload(), 600); } else { const data = await res.json(); showNotice(data.error || "Failed to revoke grant", true); } } catch (_err) { showNotice("Network error revoking grant", true); } } globalThis.revokeGrant = revokeGrant; async function generateRecoveryLink(userId) { try { const res = await fetch("/api/admin/users/" + userId + "/recovery", { method: "POST", }); const data = await res.json(); if (res.ok) { const link = globalThis.location.origin + "/recovery?code=" + data.recoveryCode; const textEl = document.getElementById("recovery-link-text"); const containerEl = document.getElementById("recovery-link-container"); if (textEl) textEl.textContent = link; if (containerEl) containerEl.style.display = "block"; showNotice("Recovery link generated!", false); } else { showNotice(data.error || "Failed to generate link", true); } } catch (_err) { showNotice("Network error generating recovery link", true); } } globalThis.generateRecoveryLink = generateRecoveryLink; async function revokeSession(sessionId) { if (!confirm("Revoke this session?")) return; try { const res = await fetch("/api/admin/sessions/" + sessionId, { method: "DELETE", }); if (res.ok) { showNotice("Session revoked", false); setTimeout(() => globalThis.location.reload(), 600); } else { showNotice("Failed to revoke session", true); } } catch (_err) { showNotice("Network error revoking session", true); } } globalThis.revokeSession = revokeSession; async function revokeAllSessions(userId) { if ( !confirm( "Revoke ALL sessions for this user? They will be immediately logged out.", ) ) return; try { const res = await fetch("/api/admin/users/" + userId + "/sessions", { method: "DELETE", }); if (res.ok) { showNotice("All sessions revoked", false); setTimeout(() => globalThis.location.reload(), 600); } else { showNotice("Failed to revoke all sessions", true); } } catch (_err) { showNotice("Network error revoking sessions", true); } } globalThis.revokeAllSessions = revokeAllSessions; async function deletePasskey(userId, passkeyId) { if ( !confirm( "Permanently delete this device? The user will no longer be able to log in with it.", ) ) return; try { const res = await fetch( "/api/admin/users/" + userId + "/passkeys/" + passkeyId, { method: "DELETE" }, ); const data = await res.json(); if (res.ok) { showNotice("Passkey deleted", false); setTimeout(() => globalThis.location.reload(), 600); } else { showNotice(data.error || "Failed to delete passkey", true); } } catch (_err) { showNotice("Network error deleting passkey", true); } } globalThis.deletePasskey = deletePasskey; function updateRoleOptions(allRolesJson) { const grantAppIdEl = document.getElementById("grantAppId"); if (!grantAppIdEl) return; const appId = grantAppIdEl.value; const roleSelect = document.getElementById("grantRole"); if (!roleSelect) return; roleSelect.innerHTML = ""; const roles = typeof allRolesJson === "string" ? JSON.parse(allRolesJson) : (allRolesJson || []); const available = roles.filter((r) => !r.app_id || r.app_id === appId); if (available.length === 0) { const opt = document.createElement("option"); opt.value = "user"; opt.textContent = "user"; roleSelect.appendChild(opt); return; } available.forEach((r) => { const opt = document.createElement("option"); opt.value = r.name; opt.textContent = r.name + (r.app_id ? " (App Custom)" : " (Global)"); roleSelect.appendChild(opt); }); } globalThis.updateRoleOptions = updateRoleOptions;