function showNotice(msg, isError) { const banner = document.getElementById("status-banner"); if (!banner) return; banner.textContent = msg; banner.style.display = "block"; banner.style.position = "fixed"; banner.style.top = "1.25rem"; banner.style.right = "1.25rem"; banner.style.zIndex = "99999"; banner.style.minWidth = "280px"; banner.style.maxWidth = "480px"; banner.style.padding = "0.85rem 1.25rem"; banner.style.borderRadius = "var(--radius-md)"; banner.style.boxShadow = "0 10px 25px -5px rgba(0, 0, 0, 0.35)"; banner.style.fontSize = "0.9rem"; banner.style.fontWeight = "600"; 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)"; if (banner._timeout) clearTimeout(banner._timeout); banner._timeout = setTimeout(() => { banner.style.display = "none"; }, 4000); } 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; // Slice 1: Application Registry Handlers function openCreateAppDrawer() { const drawer = document.getElementById("appDrawer"); if (!drawer) return; const title = document.getElementById("appDrawerTitle"); if (title) title.textContent = "Register New Subsidiary Application"; document.getElementById("editAppId").value = ""; document.getElementById("appName").value = ""; document.getElementById("appSpiffeId").value = ""; document.getElementById("appSpiffeId").readOnly = false; document.getElementById("appSpiffeId").style.opacity = "1"; document.getElementById("appDescription").value = ""; document.getElementById("appDomain").value = ""; document.getElementById("appIsPublic").checked = false; document.getElementById("appBypassPaths").value = ""; document.getElementById("appAllowedCidrs").value = ""; drawer.style.display = "block"; } globalThis.openCreateAppDrawer = openCreateAppDrawer; function openEditAppDrawer(appJson) { const drawer = document.getElementById("appDrawer"); if (!drawer) return; let app; try { app = typeof appJson === "string" ? JSON.parse(appJson) : appJson; } catch (e) { showNotice("Failed to parse application data.", true); return; } const title = document.getElementById("appDrawerTitle"); if (title) title.textContent = "Edit Application: " + app.name; document.getElementById("editAppId").value = app.id || ""; document.getElementById("appName").value = app.name || ""; document.getElementById("appSpiffeId").value = app.spiffe_id || ""; document.getElementById("appSpiffeId").readOnly = true; document.getElementById("appSpiffeId").style.opacity = "0.6"; document.getElementById("appDescription").value = app.description || ""; document.getElementById("appDomain").value = app.domain || ""; document.getElementById("appIsPublic").checked = app.is_public || false; document.getElementById("appBypassPaths").value = Array.isArray(app.bypass_paths) ? app.bypass_paths.join(",") : (app.bypass_paths || ""); document.getElementById("appAllowedCidrs").value = Array.isArray(app.allowed_cidrs) ? app.allowed_cidrs.join(",") : (app.allowed_cidrs || ""); drawer.style.display = "block"; } globalThis.openEditAppDrawer = openEditAppDrawer; function closeAppDrawer() { const drawer = document.getElementById("appDrawer"); if (drawer) drawer.style.display = "none"; } globalThis.closeAppDrawer = closeAppDrawer; async function handleSaveApp(e) { e.preventDefault(); const id = document.getElementById("editAppId").value; const name = document.getElementById("appName").value.trim(); const spiffeId = document.getElementById("appSpiffeId").value.trim(); const description = document.getElementById("appDescription").value.trim(); const domain = document.getElementById("appDomain").value.trim(); const isPublic = document.getElementById("appIsPublic").checked; const bypassPathsRaw = document.getElementById("appBypassPaths").value.trim(); const allowedCidrsRaw = document.getElementById("appAllowedCidrs").value .trim(); if (!name) { showNotice("Application Name is required", true); return; } if (!id && !spiffeId) { showNotice("SPIFFE ID is required", true); return; } const payload = { name, spiffe_id: spiffeId, description, domain, is_public: isPublic, bypass_paths: bypassPathsRaw ? bypassPathsRaw.split(",").map((s) => s.trim()).filter((s) => s) : [], allowed_cidrs: allowedCidrsRaw ? allowedCidrsRaw.split(",").map((s) => s.trim()).filter((s) => s) : [], }; const url = id ? `/api/admin/apps/${id}` : "/api/admin/apps"; const method = id ? "PUT" : "POST"; try { const res = await fetch(url, { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (res.ok) { showNotice( id ? "Application updated successfully!" : "Application registered successfully!", false, ); setTimeout(() => globalThis.location.reload(), 600); } else { const data = await res.json(); showNotice(data.error || "Failed to save application", true); } } catch (err) { showNotice("Network error saving application", true); } } globalThis.handleSaveApp = handleSaveApp; async function deleteApp(appId, appName) { if ( !confirm( "Permanently delete application '" + appName + "'? This will revoke all active grants.", ) ) return; try { const res = await fetch("/api/admin/apps/" + appId, { method: "DELETE" }); if (res.ok) { showNotice("Application deleted successfully", false); setTimeout(() => globalThis.location.reload(), 600); } else { const data = await res.json(); showNotice(data.error || "Failed to delete application", true); } } catch (err) { showNotice("Network error deleting application", true); } } globalThis.deleteApp = deleteApp;