Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
5.8 kB ยท 182 lines
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183import { fieldRefKind, toEntityId, toEntityIds,} from "@/data/entity-refs.client";import { type ComponentImpact, impactDisplay, isComponentImpactList,} from "@/data/status-report-updates.client";import { cn } from "@/lib/utils";
import { EntityRef, EntityRefList } from "./entity-ref/entity-ref";import { StatusBadge } from "./status-badge";
/** * Field-level diff row used by the audit-log "CHANGES" view and the * chat tool draft/result cards. * * - Provide both `before` and `after` for an update (renders both lines, * strikethrough on the removed value). * - Provide only `after` for a create (renders a single `+ value` line). * - Provide only `before` for a delete (renders a single `- value` line). * * Use `"before" in row` / `"after" in row` to distinguish "absent" from * "explicitly null" โ null/undefined render as `โ`. */export type ChangeRow = { field: string; before?: unknown; after?: unknown;};
export function ChangesTable({ changes }: { changes: ChangeRow[] }) { if (changes.length === 0) return null; return ( <div className="bg-background overflow-hidden rounded-md border text-sm"> {changes.map((change, idx) => ( <div key={change.field} className={cn( "grid grid-cols-1 md:grid-cols-4", idx > 0 && "border-t", )} > <div className="bg-muted/40 text-muted-foreground truncate border-r px-3 py-2 font-mono md:col-span-1"> {change.field} </div> <div className="grid min-w-0 md:col-span-3"> {"before" in change ? ( <DiffLine kind="removed" field={change.field} value={change.before} /> ) : null} {"after" in change ? ( <DiffLine kind="added" field={change.field} value={change.after} /> ) : null} </div> </div> ))} </div> );}
function DiffLine({ kind, field, value,}: { kind: "added" | "removed"; field: string; value: unknown;}) { const sign = kind === "added" ? "+" : "-"; return ( <div className="group flex items-start gap-2 px-3 py-2 font-mono has-[+[data-kind=added]]:pb-0" data-kind={kind} > <span className="text-muted-foreground group-data-[kind=added]:text-success group-data-[kind=removed]:text-destructive select-none"> {sign} </span> {/* Refs and badges are inline boxes; the removed strikethrough has to be set on them. */} <span className="group-data-[kind=added]:text-foreground group-data-[kind=removed]:text-muted-foreground min-w-0 break-all whitespace-pre-wrap group-data-[kind=removed]:line-through group-data-[kind=removed]:[&_[data-slot=badge]]:line-through group-data-[kind=removed]:[&_[data-slot=entity-ref]]:[text-decoration-line:underline_line-through]"> <DiffValue field={field} value={value} /> </span> </div> );}
function DiffValue({ field, value }: { field: string; value: unknown }) { const kind = fieldRefKind(field); if (kind === "componentImpacts") { if (isComponentImpactList(value) && value.length > 0) { return <ImpactList impacts={value} />; } } else if (kind) { // Strings stay text: a diff value of "4" is not a reference. const id = typeof value === "number" ? toEntityId(value) : undefined; if (id) return <EntityRef kind={kind} id={id} />; const ids = toEntityIds(value); if (ids) return <EntityRefList kind={kind} refs={ids} />; } return formatValue(value);}
function ImpactList({ impacts }: { impacts: ComponentImpact[] }) { return ( <span className="flex flex-col gap-1"> {impacts.map(({ pageComponentId, impact }) => { const display = impactDisplay(impact); return ( <span key={pageComponentId} className="flex items-center gap-2"> <EntityRef kind="pageComponent" id={pageComponentId} /> <span className="text-muted-foreground">โ</span> <StatusBadge plain variant={display.variant} className="font-mono"> {display.label} </StatusBadge> </span> ); })} </span> );}
const HIDDEN_FIELDS = new Set([ "workspaceId", "createdAt", "updatedAt", "deletedAt",]);
/** Update: rows from `changedFields`. Create: keys of `after`. Delete: keys of `before`. */export function buildAuditLogChangeRows(input: { before: Record<string, unknown> | null | undefined; after: Record<string, unknown> | null | undefined; changedFields: string[] | null | undefined;}): ChangeRow[] { const { before, after, changedFields } = input;
if (before && after) { if (!changedFields?.length) return []; return changedFields .filter((field) => !HIDDEN_FIELDS.has(field)) .map((field) => ({ field, before: before[field], after: after[field], })); }
if (after) { return Object.keys(after) .filter((field) => !HIDDEN_FIELDS.has(field)) .map((field) => ({ field, after: after[field] })); }
if (before) { return Object.keys(before) .filter((field) => !HIDDEN_FIELDS.has(field)) .map((field) => ({ field, before: before[field] })); }
return [];}
export function formatValue(value: unknown): string { if (value === null || value === undefined) return "โ"; if (value instanceof Date) return value.toISOString(); if (typeof value === "string") return value === "" ? "โ" : value; if (typeof value === "number" || typeof value === "boolean") { return String(value); } if (Array.isArray(value) && value.length === 0) return "โ"; return JSON.stringify(value);}