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.
3.7 kB ยท 123 lines
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124import { cn } from "@/lib/utils";
/** * 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 truncate md:col-span-3"> {"before" in change ? ( <DiffLine kind="removed" value={change.before} /> ) : null} {"after" in change ? ( <DiffLine kind="added" value={change.after} /> ) : null} </div> </div> ))} </div> );}
function DiffLine({ kind, value,}: { kind: "added" | "removed"; 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> <span className="group-data-[kind=added]:text-foreground group-data-[kind=removed]:text-muted-foreground break-all whitespace-pre-wrap group-data-[kind=removed]:line-through"> {formatValue(value)} </span> </div> );}
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);}