/* Eno.Health Console — scope-aware administration: top-bar switcher, cell-level (Global) views, and tenant-level views. Reuses globals from views_govern.jsx: TdGeneral, TdBilling, TdUsers, orgTypeLabel, statusPill, tierTone, mRow, tInitials, hl, _MIN/_HOUR/_DAY. */ const T = window.ENO; const GLOBAL_SCOPE = "__global__"; /* ---- scope / cell helpers ----------------------------------------------- */ const cellOf = (id) => T.CELLS.find((c) => c.id === id) || T.CELLS[0]; const tenantsInCell = (cellId) => T.TENANTS.filter((t) => t.cell === cellId); const usersInCell = (cellId) => { const ids = new Set(tenantsInCell(cellId).map((t) => t.id)); return T.USERS.filter((u) => ids.has(u.tenant)); }; const docsInCell = (cellId) => { const ids = new Set(tenantsInCell(cellId).map((t) => t.id)); return T.DOCUMENTS.filter((d) => ids.has(d.tenant)); }; const mimeShort = (m) => (m || "").split("/").pop().replace("vnd.openxmlformats-officedocument.wordprocessingml.document", "docx").toUpperCase(); /* ======================================================================== */ /* TOP-BAR SCOPE SWITCHER */ /* ======================================================================== */ function ScopeSwitcher({ scope, tenants, onPick, onAdd }) { const [open, setOpen] = useState(false); const [q, setQ] = useState(""); const ref = useRef(); useEffect(() => { const f = (e) => ref.current && !ref.current.contains(e.target) && setOpen(false); window.addEventListener("click", f); return () => window.removeEventListener("click", f); }, []); useEffect(() => { if (open) setQ(""); }, [open]); const isGlobal = scope === GLOBAL_SCOPE; const current = isGlobal ? null : tenants.find((t) => t.id === scope); const query = q.trim().toLowerCase(); const matches = tenants.filter((t) => !query || t.name.toLowerCase().includes(query) || t.id.toLowerCase().includes(query)); const globalMatch = !query || "global".includes(query); return h("div", { className: "scopesw", ref }, h("button", { className: "scopesw-btn", onClick: (e) => { e.stopPropagation(); setOpen((o) => !o); } }, h("span", { className: "scopesw-ic " + (isGlobal ? "global" : "tenant") }, isGlobal ? h(Icon, { name: "globe", size: 16 }) : tInitials(current ? current.name : "?")), h("span", { className: "scopesw-txt" }, h("div", { className: "scopesw-kind" }, isGlobal ? "Scope · Global" : "Tenant"), h("div", { className: "scopesw-label" }, isGlobal ? "All tenants" : (current ? current.name : "Unknown"))), h(Icon, { name: "chevron", size: 15, className: "dim" })), open && h("div", { className: "scopesw-menu" }, h("div", { className: "scopesw-search" }, h(Icon, { name: "search", size: 15 }), h("input", { placeholder: "Search tenants…", value: q, autoFocus: true, onChange: (e) => setQ(e.target.value) })), h("div", { className: "scopesw-list" }, globalMatch && h("button", { className: "scopesw-item" + (isGlobal ? " on" : ""), onClick: () => { onPick(GLOBAL_SCOPE); setOpen(false); } }, h("span", { className: "scopesw-ic global" }, h(Icon, { name: "globe", size: 16 })), h("div", { className: "scopesw-item-main" }, h("div", { className: "scopesw-item-name" }, "Global"), h("div", { className: "scopesw-item-sub" }, "Cell-wide settings · all tenants")), isGlobal && h(Icon, { name: "check", size: 15 })), matches.length > 0 && h("div", { className: "scopesw-head" }, "Tenants"), matches.map((t) => h("button", { key: t.id, className: "scopesw-item" + (scope === t.id ? " on" : ""), onClick: () => { onPick(t.id); setOpen(false); } }, h(Avatar, { initials: tInitials(t.name), tone: t.status === "suspended" ? "bad" : "accent", size: 28 }), h("div", { className: "scopesw-item-main" }, h("div", { className: "scopesw-item-name" }, hl(t.name, q.trim())), h("div", { className: "scopesw-item-sub mono" }, t.id, " · ", t.cell)), scope === t.id && h(Icon, { name: "check", size: 15 }))), !matches.length && !globalMatch && h("div", { className: "scopesw-none" }, "No tenants match “", q.trim(), "”")), h("button", { className: "scopesw-add", onClick: () => { setOpen(false); onAdd(); } }, h(Icon, { name: "plus", size: 16 }), h("span", null, "Add new tenant")))); } /* ======================================================================== */ /* SHARED */ /* ======================================================================== */ function CellTag({ cell }) { return h("span", { className: "cell-scope-tag" }, h("span", { className: "cm-flag", style: { fontSize: "13px" } }, cell.flag), h(Mono, null, cell.id), " · all tenants"); } function ScopeNote({ tenant }) { if (!tenant) return h("div", { className: "scope-banner global" }, h("span", { className: "sb-ic" }, h(Icon, { name: "globe", size: 16 })), h("div", { className: "sb-main" }, h("strong", null, "Global defaults."), " You are editing the platform-wide defaults that every tenant inherits. Select a tenant in the top bar to override these for one organization.")); return h("div", { className: "scope-banner tenant" }, h("span", { className: "sb-ic" }, h(Icon, { name: "building", size: 16 })), h("div", { className: "sb-main" }, h("strong", null, "Editing " + tenant.name + "."), " Values shown inherit from the Global defaults unless this tenant overrides them.")); } function OvBadge({ overridden }) { return overridden ? h("span", { className: "ovbadge ovbadge-ovr" }, "Overridden") : h("span", { className: "ovbadge ovbadge-inh" }, "Inherited"); } function ScopedSetting({ label, hint, overridden, onOverride, onRevert, globalText, control }) { return h("div", { className: "scoped-row" + (overridden ? "" : " inh") }, h("div", { className: "scoped-info" }, h("div", { className: "scoped-label" }, label, h(OvBadge, { overridden })), hint && h("div", { className: "scoped-hint" }, hint)), h("div", null, h("div", { className: "scoped-ctl" }, control), h("div", { className: "scoped-foot" }, h("span", { className: "scoped-default" }, "Global: ", globalText), overridden ? h("button", { className: "revert-link", onClick: onRevert }, h(Icon, { name: "refresh", size: 12 }), "Revert to global") : h("button", { className: "override-link", onClick: onOverride }, h(Icon, { name: "edit", size: 12 }), "Override")))); } /* ======================================================================== */ /* CELL (GLOBAL) VIEWS */ /* ======================================================================== */ function CellSecurityView({ cell }) { const toast = useToast(); const [base, setBase] = useState({ mfa: true, deviceVerify: true, anomaly: true, geo: "eu-only", tls: "1.3" }); const [ips, setIps] = useState([ { cidr: "185.220.101.0/24", note: "Tor exit nodes" }, { cidr: "45.155.205.0/24", note: "Abusive scraping (2026-05)" }, ]); const [newIp, setNewIp] = useState(""); const addIp = () => { if (!newIp.trim()) return; setIps((x) => [{ cidr: newIp.trim(), note: "Manual block" }, ...x]); setNewIp(""); toast("IP range blocked for " + cell.id); }; const set = (k, v) => { setBase((b) => ({ ...b, [k]: v })); toast("Cell security baseline updated"); }; return h("div", { className: "view" }, h(PageHead, { title: "Security", sub: "Cell-wide security baseline — applies to every tenant in this cell", right: h(CellTag, { cell }) }), h("div", { className: "scope-banner global" }, h("span", { className: "sb-ic" }, h(Icon, { name: "shield", size: 16 })), h("div", { className: "sb-main" }, h("strong", null, "Global / cell scope."), " These are the platform defaults tenants inherit. Select a tenant in the top bar to view or override per-tenant security.")), h(Card, { title: "Access baseline", subtitle: "Defaults inherited by all tenants in " + cell.id }, h("div", { className: "scoped-list" }, h("div", { className: "scoped-row" }, h("div", { className: "scoped-info" }, h("div", { className: "scoped-label" }, "Minimum TLS version"), h("div", { className: "scoped-hint" }, "Edge rejects handshakes below this version")), h("div", { className: "scoped-ctl" }, h(Mono, null, "TLS " + base.tls))), h("div", { className: "scoped-row" }, h("div", { className: "scoped-info" }, h("div", { className: "scoped-label" }, "Enforce MFA baseline"), h("div", { className: "scoped-hint" }, "Require multi-factor auth for all tenant users")), h("div", { className: "scoped-ctl" }, h(Toggle, { on: base.mfa, onChange: (v) => set("mfa", v) }))), h("div", { className: "scoped-row" }, h("div", { className: "scoped-info" }, h("div", { className: "scoped-label" }, "New-device verification"), h("div", { className: "scoped-hint" }, "Email challenge on first sign-in from an unseen device")), h("div", { className: "scoped-ctl" }, h(Toggle, { on: base.deviceVerify, onChange: (v) => set("deviceVerify", v) }))), h("div", { className: "scoped-row" }, h("div", { className: "scoped-info" }, h("div", { className: "scoped-label" }, "Anomalous-access detection"), h("div", { className: "scoped-hint" }, "Flag impossible travel & credential-stuffing patterns")), h("div", { className: "scoped-ctl" }, h(Toggle, { on: base.anomaly, onChange: (v) => set("anomaly", v) }))), h("div", { className: "scoped-row" }, h("div", { className: "scoped-info" }, h("div", { className: "scoped-label" }, "Geo-restriction"), h("div", { className: "scoped-hint" }, "Limit access to approved jurisdictions")), h("div", { className: "scoped-ctl" }, h(Select, { value: base.geo, onChange: (v) => set("geo", v), options: [{ value: "eu-only", label: "EU/EEA only" }, { value: "eu-uk", label: "EU/EEA + UK" }, { value: "global", label: "No restriction" }] }))))), h(Card, { title: "Blocked IP ranges", subtitle: ips.length + " ranges denied across " + cell.id }, h("div", { className: "iplist" }, ips.map((ip, i) => h("div", { className: "iprow", key: ip.cidr + i }, h("div", { className: "iprow-main" }, h(Icon, { name: "lock", size: 14, className: "dim" }), h(Mono, null, ip.cidr), h("span", { className: "iprow-note" }, ip.note)), h("button", { className: "link-rm", title: "Remove", onClick: () => setIps((x) => x.filter((_, j) => j !== i)) }, h(Icon, { name: "x", size: 15 })))), !ips.length && h("div", { className: "dim small", style: { padding: "6px 2px" } }, "No ranges blocked.")), h("div", { className: "ip-add" }, h("input", { className: "input mono", placeholder: "e.g. 203.0.113.0/24", value: newIp, onChange: (e) => setNewIp(e.target.value), onKeyDown: (e) => e.key === "Enter" && addIp() }), h(Button, { variant: "default", icon: "plus", onClick: addIp }, "Block range")))); } function CellAuditView({ cell }) { const toast = useToast(); const [q, setQ] = useState(""); const ten = tenantsInCell(cell.id); const usrs = usersInCell(cell.id); const uName = (i) => usrs[i] ? usrs[i].name : "system"; const tName = (i) => ten[i] ? ten[i].name : (ten[0] ? ten[0].name : "—"); const events = [ { ev: "User signed in", actor: uName(0), tenant: ten[0] && ten[0].name, age: 6 * _MIN, tone: "info" }, { ev: "Document redacted", actor: uName(1 % Math.max(usrs.length, 1)), tenant: tName(0), age: 21 * _MIN, tone: "ok" }, { ev: "Redaction policy overridden", actor: uName(0), tenant: tName(1), age: 2 * _HOUR, tone: "warn" }, { ev: "Blocked IP — access denied", actor: "edge", tenant: "—", age: 3 * _HOUR + 40 * _MIN, tone: "bad" }, { ev: "User invited", actor: uName(0), tenant: tName(0), age: 8 * _HOUR, tone: "info" }, { ev: "Data export completed", actor: uName(2 % Math.max(usrs.length, 1)), tenant: tName(1), age: 1 * _DAY, tone: "purple" }, { ev: "MFA enrolled", actor: uName(1 % Math.max(usrs.length, 1)), tenant: tName(0), age: 2 * _DAY, tone: "ok" }, { ev: "API key rotated", actor: "system", tenant: "—", age: 4 * _DAY, tone: "neutral" }, ]; const rows = events.filter((e) => !q.trim() || (e.ev + " " + e.actor + " " + (e.tenant || "")).toLowerCase().includes(q.trim().toLowerCase())); return h("div", { className: "view" }, h(PageHead, { title: "Audit & Logging", sub: "Cell-wide activity across all tenants in this cell", right: h(CellTag, { cell }) }), h("div", { className: "scope-banner global" }, h("span", { className: "sb-ic" }, h(Icon, { name: "clock", size: 16 })), h("div", { className: "sb-main" }, h("strong", null, "Immutable audit trail."), " Every privileged action across the ", ten.length, " tenants in ", h(Mono, null, cell.id), " is logged and retained for 400 days.")), h(Card, { pad: false, title: "Recent activity", subtitle: "Across " + ten.length + " tenants · " + usrs.length + " users", actions: h(Button, { variant: "ghost", size: "sm", icon: "download", onClick: () => toast("Export audit log (mock)") }, "Export") }, h("div", { className: "toolbar" }, h("div", { className: "search" }, h(Icon, { name: "search", size: 16 }), h("input", { className: "search-input", placeholder: "Filter events, actors, tenants…", value: q, onChange: (e) => setQ(e.target.value) })), h("span", { className: "toolbar-count" }, rows.length, " events")), h("div", { className: "table-wrap flush" }, h("table", { className: "table table-tight" }, h("thead", null, h("tr", null, ["Event", "Tenant", "Actor", "When"].map((c) => h("th", { key: c }, c)))), h("tbody", null, rows.map((e, i) => h("tr", { key: i }, h("td", null, h("span", { className: "rtype" }, h("span", { className: "pill-dot", style: { background: `var(--${e.tone === "neutral" ? "muted" : e.tone})` } }), e.ev)), h("td", null, e.tenant || "—"), h("td", { className: "dim" }, e.actor), h("td", { className: "dim" }, fmtAgo(T.ago(e.age))))), !rows.length && h("tr", null, h("td", { colSpan: 4 }, h("span", { className: "dim" }, "No matching events.")))))))); } function DataExplorerView({ cell, onOpenDoc }) { const [q, setQ] = useState(""); const [ten, setTen] = useState("all"); const all = docsInCell(cell.id); const inCellTenants = tenantsInCell(cell.id); const rows = all.filter((d) => (ten === "all" || d.tenant === ten) && (!q.trim() || (d.file + " " + d.id + " " + T.tenantName(d.tenant)).toLowerCase().includes(q.trim().toLowerCase()))); return h("div", { className: "view" }, h(PageHead, { title: "Data Explorer", sub: "Browse documents across every tenant in this cell", right: h(CellTag, { cell }) }), h("div", { className: "scope-banner global" }, h("span", { className: "sb-ic" }, h(Icon, { name: "database", size: 16 })), h("div", { className: "sb-main" }, h("strong", null, "Cross-tenant view."), " Showing ", all.length, " documents from ", inCellTenants.length, " tenants in ", h(Mono, null, cell.id), ". Access is logged to the audit trail.")), h(Card, { pad: false, title: "Documents", subtitle: rows.length + " of " + all.length + " documents" }, h("div", { className: "toolbar" }, h("div", { className: "search" }, h(Icon, { name: "search", size: 16 }), h("input", { className: "search-input", placeholder: "Search file, ID or tenant…", value: q, onChange: (e) => setQ(e.target.value) })), h(Select, { value: ten, onChange: setTen, options: [{ value: "all", label: "All tenants" }].concat(inCellTenants.map((t) => ({ value: t.id, label: t.name }))) })), h("div", { className: "table-wrap flush" }, h("table", { className: "table table-tight" }, h("thead", null, h("tr", null, ["Document", "Tenant", "Type", "Status", "Size", "Age"].map((c) => h("th", { key: c }, c)))), h("tbody", null, rows.map((d) => h("tr", { key: d.id, className: "row-click", onClick: () => onOpenDoc && onOpenDoc(d) }, h("td", null, h("div", { className: "cell-doc" }, h("span", { className: "cell-file" }, d.file), h(Mono, { className: "cell-id" }, d.id))), h("td", null, h("span", { className: "cell-tenant" }, T.tenantName(d.tenant))), h("td", null, h("span", { className: "tag" }, mimeShort(d.mime))), h("td", null, h(StatusPill, { status: d.status })), h("td", { className: "ta-r mono" }, fmtBytes(d.size)), h("td", { className: "dim" }, fmtAgo(T.ago(d.createdMin * _MIN))))), !rows.length && h("tr", null, h("td", { colSpan: 6 }, h(Empty, { icon: "search", title: "No documents", sub: "No documents match in this cell." })))))))); } function CellUsersView({ cell }) { const toast = useToast(); const [q, setQ] = useState(""); const all = usersInCell(cell.id); const rows = all.filter((u) => !q.trim() || (u.name + " " + u.email + " " + T.tenantName(u.tenant)).toLowerCase().includes(q.trim().toLowerCase())); return h("div", { className: "view" }, h(PageHead, { title: "Users", sub: "Every user across all tenants allocated to this cell", right: h(CellTag, { cell }) }), h(Card, { pad: false, title: "Users", subtitle: rows.length + " users across " + tenantsInCell(cell.id).length + " tenants", actions: h(Button, { variant: "ghost", size: "sm", icon: "download", onClick: () => toast("Export users (mock)") }, "Export") }, h("div", { className: "toolbar" }, h("div", { className: "search" }, h(Icon, { name: "search", size: 16 }), h("input", { className: "search-input", placeholder: "Search name, email or tenant…", value: q, onChange: (e) => setQ(e.target.value) })), h("span", { className: "toolbar-count" }, rows.length, " users")), h("div", { className: "table-wrap flush" }, h("table", { className: "table table-tight" }, h("thead", null, h("tr", null, ["User", "Tenant", "Role", "MFA", "Last active"].map((c) => h("th", { key: c }, c)))), h("tbody", null, rows.map((u) => h("tr", { key: u.id }, h("td", null, h("div", { className: "user-cell" }, h(Avatar, { initials: u.name.split(" ").map((w) => w[0]).join(""), size: 26 }), h("div", null, h("div", null, u.name), h("div", { className: "dim mono small" }, u.email)))), h("td", null, T.tenantName(u.tenant)), h("td", null, h(Pill, { tone: u.role === "Owner" || u.role === "Admin" ? "accent" : "neutral", soft: true }, u.role)), h("td", null, u.mfa ? h(Pill, { tone: "ok", soft: true }, "on") : h(Pill, { tone: "warn", soft: true }, "off")), h("td", { className: "dim" }, fmtAgo(T.ago(u.lastActive))))), !rows.length && h("tr", null, h("td", { colSpan: 5 }, h(Empty, { icon: "tenants", title: "No users", sub: "No users match in this cell." })))))))); } /* ======================================================================== */ /* TENANT VIEWS */ /* ======================================================================== */ function TenantGeneralView({ tenant, onUpdate }) { const toast = useToast(); if (!tenant) return null; const users = T.USERS.filter((u) => u.tenant === tenant.id); const docs = T.DOCUMENTS.filter((d) => d.tenant === tenant.id); return h("div", { className: "view" }, h(PageHead, { title: "General", sub: tenant.name + " · organization profile" }), h(Card, { pad: false }, h("div", { className: "td-stats" }, h("div", { className: "td-stat" }, h("span", { className: "td-num" }, users.length), h("span", { className: "dim" }, "users")), h("div", { className: "td-stat" }, h("span", { className: "td-num" }, docs.length), h("span", { className: "dim" }, "docs (mock)")), h("div", { className: "td-stat" }, h("span", { className: "td-num", style: { fontSize: "15px" } }, orgTypeLabel(tenant.orgType) || tenant.tier), h("span", { className: "dim" }, "org type")), h("div", { className: "td-stat" }, h("span", { className: "td-num mono" }, tenant.cell), h("span", { className: "dim" }, "cell"))), h("div", { className: "td-section" }, h(TdGeneral, { t: tenant, toast, onUpdate })))); } function TenantBillingView({ tenant }) { const toast = useToast(); if (!tenant) return null; return h("div", { className: "view" }, h(PageHead, { title: "Billing & Invoicing", sub: tenant.name }), h(Card, { pad: false }, h("div", { className: "td-section" }, h(TdBilling, { t: tenant, toast })))); } function TenantUsersView({ tenant }) { const toast = useToast(); if (!tenant) return null; const users = T.USERS.filter((u) => u.tenant === tenant.id); return h("div", { className: "view" }, h(PageHead, { title: "Users", sub: tenant.name + " · users & roles" }), h(Card, { pad: false }, h("div", { className: "td-section" }, h(TdUsers, { users, toast })))); } function TenantSecurityView({ tenant }) { const toast = useToast(); const GLOBAL = { mfa: true, sso: false, session: "30", rotation: "90", ip: "" }; const [ov, setOv] = useState({}); useEffect(() => { setOv({}); }, [tenant && tenant.id]); if (!tenant) return null; const isOv = (k) => Object.prototype.hasOwnProperty.call(ov, k); const eff = (k) => isOv(k) ? ov[k] : GLOBAL[k]; const override = (k) => { setOv((o) => ({ ...o, [k]: GLOBAL[k] })); toast("Override created for this tenant"); }; const setVal = (k, v) => setOv((o) => ({ ...o, [k]: v })); const revert = (k) => { setOv((o) => { const n = { ...o }; delete n[k]; return n; }); toast("Reverted to global default"); }; const count = Object.keys(ov).length; const rotationOpts = [{ value: "30", label: "30 days" }, { value: "60", label: "60 days" }, { value: "90", label: "90 days" }, { value: "none", label: "No rotation" }]; const sessionOpts = [{ value: "15", label: "15 min" }, { value: "30", label: "30 min" }, { value: "60", label: "60 min" }, { value: "120", label: "2 hours" }]; return h("div", { className: "view" }, h(PageHead, { title: "Security", sub: tenant.name + " · access & authentication" }), h("div", { className: "scope-banner tenant" }, h("span", { className: "sb-ic" }, h(Icon, { name: "shield", size: 16 })), h("div", { className: "sb-main" }, h("strong", null, "Editing " + tenant.name + "."), " Settings ", h("strong", null, "inherit"), " the Global cell defaults unless you override them. ", count ? count + " setting" + (count === 1 ? "" : "s") + " overridden." : "Nothing overridden yet.")), h(Card, { title: "Authentication & access", subtitle: "Per-tenant overrides of the cell security baseline" }, h("div", { className: "scoped-list" }, h(ScopedSetting, { label: "MFA enforcement", hint: "Require multi-factor auth for this tenant's users", overridden: isOv("mfa"), onOverride: () => override("mfa"), onRevert: () => revert("mfa"), globalText: GLOBAL.mfa ? "Required" : "Optional", control: h(Toggle, { on: eff("mfa"), onChange: (v) => setVal("mfa", v) }) }), h(ScopedSetting, { label: "SSO / SAML", hint: "Federate sign-in with the tenant's identity provider", overridden: isOv("sso"), onOverride: () => override("sso"), onRevert: () => revert("sso"), globalText: GLOBAL.sso ? "Enabled" : "Disabled", control: h(Toggle, { on: eff("sso"), onChange: (v) => setVal("sso", v) }) }), h(ScopedSetting, { label: "Session timeout", hint: "Idle time before users must re-authenticate", overridden: isOv("session"), onOverride: () => override("session"), onRevert: () => revert("session"), globalText: GLOBAL.session + " min", control: h(Select, { value: eff("session"), disabled: !isOv("session"), onChange: (v) => setVal("session", v), options: sessionOpts }) }), h(ScopedSetting, { label: "Password rotation", hint: "Maximum password age before a forced reset", overridden: isOv("rotation"), onOverride: () => override("rotation"), onRevert: () => revert("rotation"), globalText: GLOBAL.rotation === "none" ? "No rotation" : GLOBAL.rotation + " days", control: h(Select, { value: eff("rotation"), disabled: !isOv("rotation"), onChange: (v) => setVal("rotation", v), options: rotationOpts }) }), h(ScopedSetting, { label: "IP allowlist", hint: "Restrict access to specific CIDR ranges (comma-separated)", overridden: isOv("ip"), onOverride: () => override("ip"), onRevert: () => revert("ip"), globalText: GLOBAL.ip || "None", control: h("input", { className: "input mono", placeholder: "e.g. 10.0.0.0/8", style: { minWidth: "180px" }, value: eff("ip"), disabled: !isOv("ip"), onChange: (e) => setVal("ip", e.target.value) }) })))); } /* ======================================================================== */ /* DANGER ZONE */ /* ======================================================================== */ function ConfirmClearModal({ action, scopeLabel, onClose, onConfirm }) { const [text, setText] = useState(""); useEffect(() => { setText(""); }, [action && action.key]); if (!action) return null; const ok = text.trim() === "DELETE ALL"; return h(Modal, { open: true, onClose, title: action.title, width: 460, footer: h("div", { className: "drawer-actions" }, h(Button, { variant: "ghost", onClick: onClose }, "Cancel"), h(Button, { variant: "danger", icon: "trash", disabled: !ok, onClick: () => onConfirm(action) }, action.title)) }, h("div", { className: "mform" }, h("div", { className: "dz-modal-warn" }, h(Icon, { name: "alert", size: 16 }), h("div", null, h("strong", null, "This is irreversible."), " ", action.warn, " ", scopeLabel, ". This action is logged to the audit trail.")), h(Field, { label: "Type DELETE ALL to confirm" }, h("input", { className: "input mono", placeholder: "DELETE ALL", autoFocus: true, value: text, onChange: (e) => setText(e.target.value), onKeyDown: (e) => { if (e.key === "Enter" && ok) onConfirm(action); } })), h("div", { className: "mform-note" }, h(Icon, { name: "lock", size: 14 }), h("span", null, "Confirmation must match ", h("span", { className: "dz-confirm-code" }, "DELETE ALL"), " exactly.")))); } function DangerZoneView({ scope, cell, tenant, onDeleteTenant }) { const toast = useToast(); const [action, setAction] = useState(null); const isGlobal = scope === GLOBAL_SCOPE; const where = isGlobal ? "across " + cell.id + " (all tenants in this cell)" : "for " + (tenant ? tenant.name : "this tenant"); const scopeLabel = isGlobal ? "for every tenant in " + cell.id : "for " + (tenant ? tenant.name : "this tenant"); const actions = [ { key: "documents", title: "Clear Documents", icon: "documents", desc: "Permanently delete all ingested documents and their pipeline state " + where + ".", warn: "All documents and redaction records will be erased" }, { key: "mappings", title: "Clear Mappings", icon: "hash", desc: "Remove all terminology and LOINC ↔ biomarker mappings " + where + ".", warn: "All reference-data mappings will be erased" }, { key: "wearable", title: "Clear Wearable Data", icon: "activity", desc: "Delete all device streams and wearable readings " + where + ".", warn: "All wearable device data and readings will be erased" }, ]; if (!isGlobal && tenant) actions.push({ key: "tenant", title: "Delete Tenant", icon: "building", desc: "Permanently delete " + tenant.name + " — including its users, documents, settings and overrides.", warn: "The entire tenant and all of its data will be erased" }); const confirm = (a) => { setAction(null); if (a.key === "tenant") { const name = tenant ? tenant.name : "Tenant"; onDeleteTenant && onDeleteTenant(tenant.id); toast(name + " deleted", "bad"); return; } toast(a.title.toLowerCase() + " — done (mock)", "bad"); }; return h("div", { className: "view" }, h(PageHead, { title: "Danger Zone", sub: "Irreversible bulk deletions " + (isGlobal ? "· cell-wide" : "· " + (tenant ? tenant.name : "")), right: isGlobal ? h(CellTag, { cell }) : null }), h("div", { className: "warn-banner" }, h(Icon, { name: "alert", size: 15 }), h("span", null, "These actions permanently destroy data ", where, " and cannot be undone. Each requires typing ", h("span", { className: "dz-confirm-code" }, "DELETE ALL"), " to confirm.")), h(Card, { title: "Destructive operations", subtitle: "Restricted to platform administrators · every action is audited" }, h("div", { className: "dz-list" }, actions.map((a) => h("div", { className: "dz-item", key: a.key }, h("div", { className: "dz-ic" }, h(Icon, { name: a.icon, size: 19 })), h("div", { className: "dz-main" }, h("div", { className: "dz-title" }, a.title), h("div", { className: "dz-desc" }, a.desc)), h(Button, { variant: "danger", icon: "trash", onClick: () => setAction(a) }, a.title))))), h(ConfirmClearModal, { action, scopeLabel, onClose: () => setAction(null), onConfirm: confirm })); } Object.assign(window, { GLOBAL_SCOPE, ScopeSwitcher, ScopeNote, DangerZoneView, CellSecurityView, CellAuditView, DataExplorerView, CellUsersView, TenantGeneralView, TenantSecurityView, TenantBillingView, TenantUsersView, });