// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // The audit viewer (spec ยง9.4). It reads the platform's audit log for this deployment with the // deployment token, which carries `audit:read`, and shows only the events of this organization: // every write goes through one function that puts the organization's slug in the event's target, // and `isOwnedBy` is that rule read backwards (src/server/audit.ts). // // Administrators only, which the layout beside this page asks of the tenancy resolver // (src/server/guard.ts) before the response starts. import { orgGet } from "@conusai-kit/tenancy" import { ConusAIClient } from "@conusai-sdk/node-sdk" import type { ReactElement } from "react" import { webRuntime } from "../../../src/server/app" import { isOwnedBy } from "../../../src/server/audit" import { pageContext } from "../../../src/server/page" export const dynamic = "force-dynamic" interface AuditRow { id: number action: string outcome: string occurred_at: string resource_type?: string | null resource_id?: string | null } /** How many of this organization's events the viewer shows, and how far back it scans for them. */ const PAGE_LIMIT = 200 const MAX_PAGES = 25 /** * The platform's log is deployment-wide and pages newest-first with no server-side filter, so the * organization's rows are collected by walking pages until the viewer's own limit is filled or the * log ends -- a filter over the first page alone would show nothing once 200 newer events of other * organizations exist. */ async function organizationEvents(org: { slug: string; id: string }): Promise { const { env } = webRuntime() const platform = new ConusAIClient({ baseUrl: env.deployment.CONUSAI_API_URL, apiKey: env.deployment.CONUSAI_API_TOKEN, }) const rows: AuditRow[] = [] let cursor: string | null | undefined for (let page = 0; page < MAX_PAGES && rows.length < PAGE_LIMIT; page += 1) { const { data } = await platform.readEvents({ query: cursor ? { limit: PAGE_LIMIT, cursor } : { limit: PAGE_LIMIT }, throwOnError: true, }) for (const row of (data?.items ?? []) as AuditRow[]) { if (isOwnedBy(org, row.resource_id)) rows.push(row) } if (!data?.has_more || !data.next_cursor) break cursor = data.next_cursor } return rows } export default async function Audit({ params }: { params: Promise<{ org: string }> }): Promise { const { org } = await params const { caller, i18n } = await pageContext() // The layout already established membership; the id is what subscriber-written events carry. const organization = await caller.call(orgGet, { org }) const { observe } = webRuntime() const rows = await organizationEvents({ slug: org, id: organization.orgId }).catch((error: unknown) => { // The platform being unreachable is not the viewer's failure to explain to a customer; it is // ours to know about, so it is logged, and the page says there is nothing to show. observe.log.warn( `audit viewer: the platform's log could not be read: ${error instanceof Error ? error.name : "failed"}`, ) return [] }) return ( <>

{i18n.message("app.audit")}

{rows.length === 0 ? (

{i18n.message("app.auditEmpty")}

) : ( {rows.map((row) => ( ))}
{i18n.message("app.when")} {i18n.message("app.action")} {i18n.message("app.outcome")} {i18n.message("app.resource")}
{i18n.date(new Date(row.occurred_at))} {row.action} {row.outcome} {row.resource_type ?? ""}
)} ) }