Feed

Audit Log

Dense security audit log in a card: one timeline row per event with the actor in bold, a right-aligned time and a muted Geist Mono line of IP, key or device detail, plus an Export button.

Free, MIT licenseOpen in Browse
import {
  Timeline,
  TimelineContent,
  TimelineIndicator,
  TimelineItem,
  TimelineLabel,
  TimelineTitle,
} from "@/components/editorial/timeline"
import { Button } from "@/components/ui/button"
import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

// The copy: change text here, not in the markup.
const COPY = {
  title: "Audit log",
  body: "Security-relevant events on this workspace.",
  cta: "Export",
} as const

const EVENTS = [
  {
    actor: "Lena Orvik",
    action: "changed the billing email",
    meta: "lena@orbcore.dev · 193.0.12.4",
    time: "14:02",
  },
  {
    actor: "Tomas Vey",
    action: "created an API key",
    meta: "ok_live_…9f2a · 10.4.0.18",
    time: "13:47",
  },
  {
    actor: "Ines Halloran",
    action: "removed Elias Quill from Admins",
    meta: "193.0.12.9",
    time: "11:20",
  },
  {
    actor: "System",
    action: "rotated the signing secret",
    meta: "scheduled",
    time: "03:00",
  },
  {
    actor: "Kofi Brandt",
    action: "signed in with a new device",
    meta: "macOS · Chrome · Lisbon",
    time: "Yesterday 22:15",
  },
  {
    actor: "Mira Sato",
    action: "enabled two-factor sign-in",
    meta: "193.0.12.2",
    time: "Yesterday 09:31",
  },
] as const

// Dense: one line per event, the detail in a muted mono line under it. The
// actor is the bold name, not a mark; the dot only places the row in time.
export default function AuditLog() {
  return (
    <Card className="mx-auto w-full max-w-md">
      <CardHeader>
        <CardTitle>{COPY.title}</CardTitle>
        <CardDescription>{COPY.body}</CardDescription>
        <CardAction>
          <Button variant="outline" size="sm">
            {COPY.cta}
          </Button>
        </CardAction>
      </CardHeader>
      <CardContent>
        <Timeline className="[--timeline-lh:1.25rem]">
          {EVENTS.map((e) => (
            <TimelineItem key={e.action + e.time}>
              <TimelineIndicator className="border-0 [&_[data-slot=timeline-dot]]:bg-muted-foreground/60" />
              <TimelineContent className="pb-5">
                <div className="flex items-baseline justify-between gap-3">
                  <TimelineTitle className="text-sm/5 font-normal">
                    <span className="font-medium">{e.actor}</span> {e.action}
                  </TimelineTitle>
                  <TimelineLabel className="shrink-0 tabular-nums">
                    {e.time}
                  </TimelineLabel>
                </div>
                <p className="mt-0.5 font-mono text-xs text-muted-foreground">
                  {e.meta}
                </p>
              </TimelineContent>
            </TimelineItem>
          ))}
        </Timeline>
      </CardContent>
    </Card>
  )
}

Installation

pnpm dlx shadcn add @01orbital/audit-log
npx shadcn add @01orbital/audit-log
yarn shadcn add @01orbital/audit-log
bunx --bun shadcn add @01orbital/audit-log

Free under the MIT license. Add the @01orbital registry to components.json once (install guide), then run the command.

It lands at components/blocks/feed/audit-log/index.tsx, as source you own and edit.

Usage

import AuditLog from "@/components/blocks/feed/audit-log"

export default function Example() {
  return <AuditLog />
}

Dependencies

Registry pieces it installs with it: