/* Eno.Health Console — governance views: Policies, Retention, Webhooks, Tenants */ const G = window.ENO; /* ======================================================================== */ /* POLICIES */ /* ======================================================================== */ function PoliciesView() { const [tab, setTab] = useState("doctype"); const tabs = [ { key: "doctype", label: "Document types", count: G.DOC_TYPE_POLICIES.length }, { key: "quotas", label: "Tenant quotas", count: G.QUOTA_POLICIES.length }, { key: "redaction", label: "Redaction", count: G.REDACTION_POLICIES.length }, ]; return h("div", { className: "view" }, h(PageHead, { title: "Policies", sub: "Govern what the pipeline accepts, how much, and how it scrubs PHI/PII" }), h(Tabs, { tabs, active: tab, onChange: setTab }), h("div", { className: "tabbody" }, tab === "doctype" && h(DocTypePolicies, null), tab === "quotas" && h(QuotaPolicies, null), tab === "redaction" && h(RedactionPolicies, null))); } function DocTypePolicies() { const toast = useToast(); const [rows, setRows] = useState(G.DOC_TYPE_POLICIES); const toggle = (mime) => { setRows((r) => r.map((p) => p.mime === mime ? { ...p, enabled: !p.enabled } : p)); toast("Policy updated · " + mime); }; const head = ["MIME type", "Extensions", "Max size", "Sniff", "Profile", "Scope", "Enabled"]; const body = rows.map((p) => h("tr", { key: p.mime }, h("td", null, h(Mono, null, p.mime)), h("td", null, h("span", { className: "tags" }, p.ext.split(", ").map((e) => h("span", { className: "tag", key: e }, "." + e)))), h("td", { className: "mono" }, p.maxMB, " MB"), h("td", null, p.sniff ? h(Pill, { tone: "ok", soft: true }, "strict") : h(Pill, { tone: "neutral", soft: true }, "off")), h("td", null, h(Mono, { className: "dim" }, p.profile)), h("td", null, p.tenant ? h(Mono, null, G.tenantName(p.tenant)) : h("span", { className: "dim" }, "global")), h("td", null, h(Toggle, { on: p.enabled, onChange: () => toggle(p.mime) })))); return h(Card, { pad: false, title: "Document type policies", subtitle: "MIME allow-list, size caps and content-sniff enforcement", actions: h(Button, { variant: "primary", size: "sm", icon: "plus", onClick: () => toast("New policy form (mock)") }, "New policy") }, h("div", { className: "table-wrap flush" }, h("table", { className: "table" }, h("thead", null, h("tr", null, head.map((c) => h("th", { key: c }, c)))), h("tbody", null, body)))); } function QuotaPolicies() { const toast = useToast(); return h("div", { className: "quota-grid" }, G.QUOTA_POLICIES.map((q) => h(Card, { key: q.scope + q.key, className: "quota-card", title: q.scope === "tier" ? "Tier · " + q.tier : G.tenantName(q.key), subtitle: q.scope === "tier" ? "Applies to all " + q.tier + " tenants" : "Tenant override · " + q.tier + " tier", actions: h(Button, { variant: "ghost", size: "sm", icon: "external", onClick: () => toast("Edit quota (mock)") }, "Edit") }, h("div", { className: "quota-rows" }, h(QuotaRow, { label: "Daily documents", used: q.used.dailyDocs, limit: q.dailyDocs, unit: "" }), h(QuotaRow, { label: "Monthly volume", used: q.used.monthlyGB, limit: q.monthlyGB, unit: " GB" }), h(QuotaRow, { label: "Concurrent uploads", used: q.used.concurrent, limit: q.concurrent, unit: "" })), h("div", { className: "quota-foot dim" }, "Per-user daily cap · ", h("strong", null, q.perUserDaily))))); } function QuotaRow({ label, used, limit, unit }) { const pct = Math.min(100, (used / limit) * 100); const tone = pct > 90 ? "bad" : pct > 70 ? "warn" : "ok"; return h("div", { className: "qrow" }, h("div", { className: "qrow-top" }, h("span", null, label), h("span", { className: "mono" }, fmtNum(used) + unit, h("span", { className: "dim" }, " / " + fmtNum(limit) + unit))), h(Bar, { value: used, max: limit, tone, height: 6 })); } function RedactionPolicies() { const toast = useToast(); const [rows, setRows] = useState(G.REDACTION_POLICIES); const activate = (i) => { setRows((r) => r.map((p, j) => j === i ? { ...p, active: !p.active } : (p.tenant === r[i].tenant && p.consumer === r[i].consumer ? { ...p, active: false } : p))); toast("Redaction policy activation updated"); }; const head = ["Tenant", "Consumer", "Version", "Engines", "Strategy", "Fail-closed", "Active"]; const body = rows.map((p, i) => h("tr", { key: i }, h("td", null, G.tenantName(p.tenant)), h("td", null, h(Mono, { className: "dim" }, p.consumer)), h("td", null, h(Mono, null, p.version)), h("td", null, h("span", { className: "tags" }, p.engines.map((e) => h("span", { className: "tag", key: e }, e)), p.extra.length ? h("span", { className: "tag tag-accent" }, "+" + p.extra.length + " PHI") : null)), h("td", null, h(Mono, { className: "dim" }, p.strategy)), h("td", null, p.failClosed ? h(Pill, { tone: "ok", soft: true }, "yes") : h(Pill, { tone: "bad", soft: true }, "no")), h("td", null, h(Toggle, { on: p.active, onChange: () => activate(i) })))); return h(Card, { pad: false, title: "Redaction policies", subtitle: "Per-tenant PHI/PII engines, strategies and fail-closed posture", actions: h(Button, { variant: "primary", size: "sm", icon: "plus", onClick: () => toast("New redaction policy (mock)") }, "New policy") }, h("div", { className: "table-wrap flush" }, h("table", { className: "table" }, h("thead", null, h("tr", null, head.map((c) => h("th", { key: c }, c)))), h("tbody", null, body)))); } /* ======================================================================== */ /* RETENTION & COMPLIANCE */ /* ======================================================================== */ function RetentionView() { const toast = useToast(); const [queue, setQueue] = useState(G.RETENTION_QUEUE); const [mode, setMode] = useState("observe"); const counts = { erasure: queue.filter((r) => r.type === "erasure").length, hold: queue.filter((r) => r.type === "legal_hold").length, review: queue.filter((r) => r.type === "review_due").length, }; const resolve = (id, action) => { setQueue((q) => q.filter((r) => r.id !== id)); toast(action + " · " + id); }; const TYPE_META = { erasure: { tone: "bad", icon: "trash", label: "Erasure request" }, legal_hold: { tone: "warn", icon: "lock", label: "Legal hold" }, review_due: { tone: "info", icon: "clock", label: "Review due" }, }; const head = ["Type", "Document", "Tenant", "Basis", "Requested by", "Age", "Action"]; const body = queue.map((r) => { const m = TYPE_META[r.type]; return h("tr", { key: r.id }, h("td", null, h("span", { className: "rtype" }, h("span", { className: `attn-icon tone-${m.tone}` }, h(Icon, { name: m.icon, size: 13 })), m.label)), h("td", null, h(Mono, null, r.id)), h("td", null, G.tenantName(r.tenant)), h("td", { className: "dim" }, r.basis), h("td", null, h(Mono, { className: "dim" }, r.actor)), h("td", { className: "dim" }, fmtAgo(r.requestedAt)), h("td", null, h("div", { className: "row-actions" }, r.type === "erasure" && h(Button, { variant: "danger", size: "sm", onClick: () => resolve(r.id, "Erasure executed") }, "Erase"), r.type === "legal_hold" && h(Button, { variant: "ghost", size: "sm", onClick: () => resolve(r.id, "Hold released") }, "Release"), r.type === "review_due" && h(Button, { variant: "default", size: "sm", onClick: () => resolve(r.id, "Retention extended") }, "Extend"), h(Button, { variant: "ghost", size: "sm", icon: "external", onClick: () => resolve(r.id, "Marked reviewed") }, "Review")))); }); return h("div", { className: "view" }, h(PageHead, { title: "Retention & compliance", sub: "Erasure requests, legal holds, and due retention reviews", right: h("div", { className: "mode-switch" }, h("span", { className: "dim" }, "retention-worker"), ["observe", "review", "erase"].map((m) => h("button", { key: m, className: "mode-btn" + (mode === m ? " on" : "") + (m === "erase" ? " danger" : ""), onClick: () => { setMode(m); toast("retention-worker mode → " + m, m === "erase" ? "bad" : "ok"); }, }, m))) }), h("div", { className: "stat-grid stat-grid-3" }, h(Card, { pad: false }, h(Stat, { label: "Pending erasure", value: counts.erasure, tone: "bad", icon: "trash" })), h(Card, { pad: false }, h(Stat, { label: "Active legal holds", value: counts.hold, tone: "warn", icon: "lock" })), h(Card, { pad: false }, h(Stat, { label: "Reviews due", value: counts.review, tone: "info", icon: "clock" }))), mode === "erase" && h("div", { className: "warn-banner" }, h(Icon, { name: "alert", size: 15 }), "Erase mode is destructive — the worker will delete object-storage copies for erasure-requested documents that are not under legal hold."), h(Card, { pad: false, title: "Disposition queue", subtitle: "Derived from document retention metadata · excludes legal-hold records from erasure" }, h("div", { className: "table-wrap flush" }, h("table", { className: "table" }, h("thead", null, h("tr", null, head.map((c) => h("th", { key: c }, c)))), h("tbody", null, body, !queue.length && h("tr", null, h("td", { colSpan: 7 }, h(Empty, { icon: "check", title: "Queue clear", sub: "No pending dispositions." })))))))); } /* ======================================================================== */ /* WEBHOOKS */ /* ======================================================================== */ function WebhooksView() { const toast = useToast(); const [hooks, setHooks] = useState(G.WEBHOOKS); const [modal, setModal] = useState(false); const [nu, setNu] = useState({ tenant: G.TENANTS[0].id, url: "", events: "document.redacted" }); const add = () => { if (!nu.url) { toast("Endpoint URL required", "bad"); return; } setHooks((x) => [{ id: "whk_" + Math.random().toString(16).slice(2, 6), tenant: nu.tenant, url: nu.url, events: [nu.events], active: true, deliveries: { ok: 0, failed: 0 }, lastDelivery: new Date(G.NOW) }, ...x]); setModal(false); setNu({ tenant: G.TENANTS[0].id, url: "", events: "document.redacted" }); toast("Webhook registered"); }; const del = (id) => { setHooks((x) => x.filter((w) => w.id !== id)); toast("Webhook removed"); }; const EVENTS = ["document.status.v1", "document.redacted", "document.redaction_failed", "ingest.start.v1", "ingest.failed.v1"]; return h("div", { className: "view" }, h(PageHead, { title: "Webhooks", sub: "Tenant status-event delivery endpoints and their delivery health", right: h(Button, { variant: "primary", icon: "plus", onClick: () => setModal(true) }, "Register endpoint") }), h("div", { className: "hook-grid" }, hooks.map((w) => { const rate = w.deliveries.ok + w.deliveries.failed > 0 ? (100 * w.deliveries.ok / (w.deliveries.ok + w.deliveries.failed)) : 100; return h(Card, { key: w.id, className: "hook-card" }, h("div", { className: "hook-head" }, h("div", null, h("div", { className: "hook-url mono" }, w.url), h("div", { className: "hook-tenant dim" }, G.tenantName(w.tenant), " · ", h(Mono, null, w.id))), w.active ? h(Pill, { tone: "ok" }, "active") : h(Pill, { tone: "neutral", soft: true }, "paused")), h("div", { className: "hook-events" }, w.events.map((e) => h("span", { className: "tag", key: e }, e))), h("div", { className: "hook-stats" }, h("div", { className: "hook-stat" }, h("span", { className: "hook-num ok-ink" }, fmtNum(w.deliveries.ok)), h("span", { className: "dim" }, "delivered")), h("div", { className: "hook-stat" }, h("span", { className: `hook-num ${w.deliveries.failed ? "bad-ink" : "dim"}` }, fmtNum(w.deliveries.failed)), h("span", { className: "dim" }, "failed")), h("div", { className: "hook-stat" }, h("span", { className: "hook-num" }, rate.toFixed(1) + "%"), h("span", { className: "dim" }, "success")), h("div", { className: "hook-stat" }, h("span", { className: "hook-num" }, fmtAgo(w.lastDelivery)), h("span", { className: "dim" }, "last"))), h("div", { className: "hook-foot" }, h(Button, { variant: "ghost", size: "sm", icon: "external", onClick: () => toast("Delivery history (mock)") }, "Deliveries"), h(Button, { variant: "danger-ghost", size: "sm", icon: "trash", onClick: () => del(w.id) }, "Remove"))); })), h(Modal, { open: modal, onClose: () => setModal(false), title: "Register webhook endpoint", footer: h(React.Fragment, null, h(Button, { variant: "ghost", onClick: () => setModal(false) }, "Cancel"), h(Button, { variant: "primary", onClick: add }, "Register")) }, h("div", { className: "form-stack" }, h(Field, { label: "Tenant" }, h(Select, { value: nu.tenant, onChange: (v) => setNu({ ...nu, tenant: v }), options: G.TENANTS.map((t) => ({ value: t.id, label: t.name })) })), h(Field, { label: "Endpoint URL" }, h("input", { className: "input", placeholder: "https://…", value: nu.url, onChange: (e) => setNu({ ...nu, url: e.target.value }) })), h(Field, { label: "Event" }, h(Select, { value: nu.events, onChange: (v) => setNu({ ...nu, events: v }), options: EVENTS }))))); } /* ======================================================================== */ /* TENANTS & USERS (scaffold for future identity/RBAC) */ /* ======================================================================== */ function TenantsView({ onOpenDoc }) { const [sel, setSel] = useState(null); return h("div", { className: "view" }, h(PageHead, { title: "Tenants & users", sub: "Organizations on the platform and the users who upload on their behalf" }), h("div", { className: "rbac-banner" }, h(Icon, { name: "shield", size: 15 }), h("div", null, h("strong", null, "Identity & RBAC module — in design."), " Today tenants and users are derived from ", h(Mono, null, "tenant_id"), " and ", h(Mono, null, "initiated_by_user_id"), " on documents. This surface is the home for first-class tenant onboarding, user roles, and SSO when the identity layer lands.")), h("div", { className: "split-tenants" }, h(Card, { pad: false, title: "Tenants", subtitle: G.TENANTS.length + " organizations" }, h("div", { className: "tenant-list" }, G.TENANTS.map((t) => h("button", { key: t.id, className: "tenant-row" + (sel === t.id ? " on" : ""), onClick: () => setSel(t.id) }, h(Avatar, { initials: t.name.split(" ").map((w) => w[0]).slice(0, 2).join(""), tone: t.status === "suspended" ? "bad" : "accent" }), h("div", { className: "tenant-main" }, h("div", { className: "tenant-name" }, t.name), h("div", { className: "tenant-sub dim" }, h(Mono, null, t.id), " · ", t.cell)), h("div", { className: "tenant-meta" }, h(Pill, { tone: t.tier === "genomics" ? "purple" : t.tier === "starter" ? "neutral" : "info", soft: true }, t.tier), t.status === "active" ? h(Pill, { tone: "ok", soft: true }, "active") : t.status === "trial" ? h(Pill, { tone: "warn", soft: true }, "trial") : h(Pill, { tone: "bad", soft: true }, "suspended")), h(Icon, { name: "chevronR", size: 15, className: "row-arrow" })))), ), h(TenantDetail, { tenantId: sel }))); } function TenantDetail({ tenantId }) { const toast = useToast(); if (!tenantId) return h(Card, { className: "tenant-detail" }, h(Empty, { icon: "tenants", title: "Select a tenant", sub: "Choose an organization to see its users and usage." })); const t = G.TENANTS.find((x) => x.id === tenantId); const users = G.USERS.filter((u) => u.tenant === tenantId); const docs = G.DOCUMENTS.filter((d) => d.tenant === tenantId); return h(Card, { className: "tenant-detail", pad: false, title: t.name, subtitle: t.contact + " · " + t.region, actions: h(Button, { variant: "ghost", size: "sm", icon: "external", onClick: () => toast("Tenant settings (mock)") }, "Settings") }, h("div", { className: "td-stats" }, h("div", { className: "td-stat" }, h("span", { className: "td-num" }, t.users), 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" }, t.tier), h("span", { className: "dim" }, "tier")), h("div", { className: "td-stat" }, h("span", { className: "td-num mono" }, t.cell), h("span", { className: "dim" }, "cell"))), h("div", { className: "td-section" }, h("div", { className: "td-section-head" }, "Users", h(Button, { variant: "ghost", size: "sm", icon: "plus", onClick: () => toast("Invite user (mock)") }, "Invite")), h("div", { className: "table-wrap flush" }, h("table", { className: "table table-tight" }, h("thead", null, h("tr", null, ["User", "Role", "MFA", "Last active"].map((c) => h("th", { key: c }, c)))), h("tbody", null, users.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, 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(G.ago(u.lastActive))))), !users.length && h("tr", null, h("td", { colSpan: 4 }, h("span", { className: "dim" }, "No users recorded.")))))))); } Object.assign(window, { PoliciesView, RetentionView, WebhooksView, TenantsView });