Dashboard 6
Pipeline overview dashboard: four stat cards for success, failure, skipped, and live activity, a clickable latest-runs bar chart with a run breakdown panel, and a recent-runs list. The active-now stat uses --accent-cool for live state; every other color is a design token, so it works in light and dark.
Preview
Installation
npx shadcn@latest add https://hirael.com/r/dashboard-06.jsonCode
components/blocks/dashboard-06.tsx
'use client';
import * as React from 'react';
import {
Activity,
AlertTriangle,
BarChart3,
CheckCircle2,
ChevronRight,
CircleSlash,
Clock,
GitBranch,
Hash,
} from 'lucide-react';
import { Bar, BarChart, Cell, XAxis, YAxis } from 'recharts';
import { cn } from '@/lib/utils';
import { Badge } from '@/registry/hirael/bases/radix/ui/badge';
import { Button } from '@/registry/hirael/bases/radix/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/registry/hirael/bases/radix/ui/card';
import { ChartContainer, ChartTooltip, type ChartConfig } from '@/registry/hirael/bases/radix/ui/chart';
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia } from '@/registry/hirael/bases/radix/ui/empty';
type RunState = 'success' | 'failure' | 'skipped' | 'running';
interface Run {
id: string;
number: number;
attempt: number;
pipeline: string;
branch: string;
state: RunState;
durationSec: number;
startedLabel: string;
steps: { name: string; state: RunState; durationLabel: string }[];
}
const STATE_TOKEN: Record<RunState, string> = {
success: 'var(--success)',
failure: 'var(--destructive)',
skipped: 'var(--muted-foreground)',
running: 'var(--accent-cool)',
};
const STATE_LABEL: Record<RunState, string> = {
success: 'Passed',
failure: 'Failed',
skipped: 'Skipped',
running: 'Running',
};
const LEGEND: { state: RunState; label: string }[] = [
{ state: 'success', label: 'Passed' },
{ state: 'failure', label: 'Failed' },
{ state: 'skipped', label: 'Skipped' },
{ state: 'running', label: 'Running' },
];
const RUNS: Run[] = [
{
id: 'r-118',
number: 118,
attempt: 1,
pipeline: 'Build & test',
branch: 'main',
state: 'success',
durationSec: 94,
startedLabel: '2 min ago',
steps: [
{ name: 'Install deps', state: 'success', durationLabel: '21s' },
{ name: 'Lint', state: 'success', durationLabel: '9s' },
{ name: 'Unit tests', state: 'success', durationLabel: '48s' },
{ name: 'Build', state: 'success', durationLabel: '16s' },
],
},
{
id: 'r-117',
number: 117,
attempt: 1,
pipeline: 'Deploy preview',
branch: 'feat/inline-edit',
state: 'running',
durationSec: 62,
startedLabel: '5 min ago',
steps: [
{ name: 'Install deps', state: 'success', durationLabel: '20s' },
{ name: 'Build', state: 'success', durationLabel: '18s' },
{ name: 'Upload', state: 'running', durationLabel: '—' },
],
},
{
id: 'r-116',
number: 116,
attempt: 2,
pipeline: 'Build & test',
branch: 'fix/date-range',
state: 'failure',
durationSec: 71,
startedLabel: '18 min ago',
steps: [
{ name: 'Install deps', state: 'success', durationLabel: '22s' },
{ name: 'Lint', state: 'success', durationLabel: '8s' },
{ name: 'Unit tests', state: 'failure', durationLabel: '41s' },
{ name: 'Build', state: 'skipped', durationLabel: '—' },
],
},
{
id: 'r-115',
number: 115,
attempt: 1,
pipeline: 'Nightly e2e',
branch: 'main',
state: 'success',
durationSec: 188,
startedLabel: '42 min ago',
steps: [
{ name: 'Provision', state: 'success', durationLabel: '31s' },
{ name: 'Browser tests', state: 'success', durationLabel: '2m 18s' },
{ name: 'Teardown', state: 'success', durationLabel: '19s' },
],
},
{
id: 'r-114',
number: 114,
attempt: 1,
pipeline: 'Build & test',
branch: 'chore/deps',
state: 'skipped',
durationSec: 4,
startedLabel: '1 hr ago',
steps: [{ name: 'Path filter', state: 'skipped', durationLabel: '4s' }],
},
{
id: 'r-113',
number: 113,
attempt: 1,
pipeline: 'Build & test',
branch: 'main',
state: 'success',
durationSec: 88,
startedLabel: '2 hr ago',
steps: [
{ name: 'Install deps', state: 'success', durationLabel: '20s' },
{ name: 'Unit tests', state: 'success', durationLabel: '52s' },
{ name: 'Build', state: 'success', durationLabel: '16s' },
],
},
{
id: 'r-112',
number: 112,
attempt: 1,
pipeline: 'Deploy production',
branch: 'main',
state: 'success',
durationSec: 142,
startedLabel: '3 hr ago',
steps: [
{ name: 'Build', state: 'success', durationLabel: '1m 04s' },
{ name: 'Migrate', state: 'success', durationLabel: '11s' },
{ name: 'Release', state: 'success', durationLabel: '27s' },
],
},
{
id: 'r-111',
number: 111,
attempt: 1,
pipeline: 'Build & test',
branch: 'feat/year-picker',
state: 'failure',
durationSec: 57,
startedLabel: '4 hr ago',
steps: [
{ name: 'Install deps', state: 'success', durationLabel: '21s' },
{ name: 'Lint', state: 'failure', durationLabel: '6s' },
{ name: 'Unit tests', state: 'skipped', durationLabel: '—' },
],
},
];
interface Stat {
key: string;
label: string;
value: string;
meta: string;
icon: React.ElementType;
accent: string;
live?: boolean;
}
const STATS: Stat[] = [
{
key: 'success',
label: 'Success rate',
value: '78%',
meta: '21 of 27 completed runs',
icon: CheckCircle2,
accent: 'var(--success)',
},
{
key: 'failure',
label: 'Failure rate',
value: '15%',
meta: '4 failures across 3 pipelines',
icon: AlertTriangle,
accent: 'var(--destructive)',
},
{
key: 'skipped',
label: 'Skipped',
value: '7%',
meta: '2 of 27 completed runs',
icon: CircleSlash,
accent: 'var(--muted-foreground)',
},
{
key: 'active',
label: 'Active now',
value: '1',
meta: 'Currently running or queued',
icon: Activity,
accent: 'var(--accent-cool)',
live: true,
},
];
const chartConfig = {
duration: { label: 'Duration' },
} satisfies ChartConfig;
const formatSeconds = (value: number) => {
if (value < 60) return `${value}s`;
const m = Math.floor(value / 60);
const s = value % 60;
return s ? `${m}m ${s}s` : `${m}m`;
};
const StateDot = ({ state }: { state: RunState }) => {
return (
<span
aria-hidden
className="inline-block size-2 shrink-0 rounded-full"
style={{ backgroundColor: STATE_TOKEN[state] }}
/>
);
};
const StatCard = ({ stat }: { stat: Stat }) => {
const Icon = stat.icon;
return (
<div data-slot="dashboard-stat" className="flex flex-col gap-2 rounded-lg border border-border bg-card p-4">
<div className="flex items-center gap-1.5 font-mono text-[10px] uppercase tracking-[0.12em] text-muted-foreground">
{stat.live ? (
<span className="relative flex size-2">
<span
className="absolute inline-flex size-full rounded-full opacity-70 motion-safe:animate-ping"
style={{ backgroundColor: stat.accent }}
/>
<span className="relative inline-flex size-2 rounded-full" style={{ backgroundColor: stat.accent }} />
</span>
) : (
<Icon className="size-3" style={{ color: stat.accent }} />
)}
{stat.label}
</div>
<span className="text-3xl font-semibold tabular-nums tracking-tight sm:text-4xl">{stat.value}</span>
<span className="text-[11px] text-muted-foreground">{stat.meta}</span>
</div>
);
};
const RunBreakdown = ({ run }: { run: Run | null }) => {
if (!run) {
return (
<Card data-slot="dashboard-breakdown" className="gap-1 self-stretch overflow-hidden py-0">
<CardHeader className="px-4 pt-3">
<div className="flex items-center gap-1 font-mono text-[11px] text-muted-foreground">
<Hash className="size-3 shrink-0" aria-hidden />
<span>—</span>
</div>
<CardTitle className="text-sm text-muted-foreground">No run selected</CardTitle>
</CardHeader>
<CardContent className="flex flex-1 items-center justify-center px-4 pb-4">
<Empty className="border-0">
<EmptyMedia variant="icon">
<BarChart3 />
</EmptyMedia>
<EmptyHeader>
<EmptyDescription>Pick a bar in the chart to inspect a run.</EmptyDescription>
</EmptyHeader>
</Empty>
</CardContent>
</Card>
);
}
return (
<Card data-slot="dashboard-breakdown" className="gap-2 self-stretch overflow-hidden py-0">
<CardHeader className="px-4 pt-3">
<div className="flex items-center gap-1.5 font-mono text-[11px] text-muted-foreground">
<span className="inline-flex items-center">
<Hash className="size-3 shrink-0" aria-hidden />
{run.number}
</span>
{run.attempt > 1 && <span>· attempt {run.attempt}</span>}
</div>
<CardTitle className="flex items-center justify-between gap-2">
<span className="line-clamp-1 text-sm">{run.pipeline}</span>
<Badge variant="outline" className="gap-1.5 text-[10px]" style={{ color: STATE_TOKEN[run.state] }}>
<StateDot state={run.state} />
{STATE_LABEL[run.state]}
</Badge>
</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-3 px-4 pb-4">
<dl className="grid grid-cols-2 gap-2 text-[11px]">
<div className="flex flex-col gap-0.5">
<dt className="text-muted-foreground">Branch</dt>
<dd className="inline-flex items-center gap-1 font-mono">
<GitBranch className="size-3 shrink-0" aria-hidden />
<span className="truncate">{run.branch}</span>
</dd>
</div>
<div className="flex flex-col gap-0.5">
<dt className="text-muted-foreground">Duration</dt>
<dd className="inline-flex items-center gap-1 font-mono tabular-nums">
<Clock className="size-3 shrink-0" aria-hidden />
{formatSeconds(run.durationSec)}
</dd>
</div>
</dl>
<div className="flex flex-col gap-1 border-t border-border pt-2">
<p className="font-mono text-[10px] uppercase tracking-[0.1em] text-muted-foreground">Steps</p>
<ul className="flex flex-col gap-1">
{run.steps.map((step) => (
<li key={step.name} className="flex items-center gap-2 text-[11px]">
<StateDot state={step.state} />
<span className="min-w-0 flex-1 truncate">{step.name}</span>
<span className="shrink-0 font-mono tabular-nums text-muted-foreground">{step.durationLabel}</span>
</li>
))}
</ul>
</div>
<Button type="button" size="sm" className="mt-auto gap-1 px-3 py-1.5 text-[11px]">
View full log
<ChevronRight className="size-3 rtl:rotate-180" aria-hidden />
</Button>
</CardContent>
</Card>
);
};
const Dashboard06 = () => {
const chartData = React.useMemo(
() =>
[...RUNS].reverse().map((run) => ({
run,
label: `#${run.number}`,
duration: run.durationSec,
state: run.state,
})),
[],
);
const [selectedId, setSelectedId] = React.useState<string>(RUNS[0].id);
const selectedRun = RUNS.find((r) => r.id === selectedId) ?? null;
return (
<section data-slot="dashboard" className="bg-background py-20 sm:py-28">
<div className="container flex w-full flex-col gap-6">
<div className="flex flex-col gap-1">
<span className="inline-flex w-fit items-center gap-1.5 font-mono text-[10px] uppercase tracking-[0.16em] text-muted-foreground">
<span className="size-1 rounded-full bg-foreground" />
Pipelines
</span>
<h2 className="font-serif text-4xl font-medium tracking-tight sm:text-5xl">Overview</h2>
<p className="max-w-xl text-sm text-muted-foreground">
Success rate, failures, and live activity across your pipelines, with the latest runs and what each one did.
</p>
</div>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
{STATS.map((stat) => (
<StatCard key={stat.key} stat={stat} />
))}
</div>
<div className="grid grid-cols-1 items-start gap-3 lg:grid-cols-[1fr_340px]">
<Card data-slot="dashboard-chart" className="py-0">
<CardHeader className="px-6 pt-4 pb-3">
<CardTitle>Latest runs</CardTitle>
<CardDescription>Duration of the last {chartData.length} runs. Pick a bar to inspect.</CardDescription>
</CardHeader>
<CardContent className="px-2 pb-4 sm:px-4">
<ChartContainer config={chartConfig} className="h-[200px] w-full">
<BarChart
data={chartData}
margin={{ left: 4, right: 4, top: 4, bottom: 0 }}
onClick={(e) => {
const payload = (e as { activePayload?: { payload?: unknown }[] }).activePayload?.[0]?.payload as
(typeof chartData)[number] | undefined;
if (payload?.run) setSelectedId(payload.run.id);
}}
>
<XAxis
dataKey="label"
tickLine={false}
axisLine={false}
tickMargin={8}
tick={{ fontSize: 10, fill: 'var(--muted-foreground)' }}
/>
<YAxis
tickLine={false}
axisLine={false}
tickMargin={4}
width={52}
tickFormatter={(v) => formatSeconds(v as number)}
tick={{ fontSize: 10, fill: 'var(--muted-foreground)' }}
/>
<ChartTooltip
cursor={{ fill: 'var(--muted)', opacity: 0.5 }}
content={({ active, payload }) => {
if (!active || !payload?.length) return null;
const { run, state } = payload[0].payload as (typeof chartData)[number];
return (
<div className="rounded-lg border border-border bg-popover px-3 py-2 text-xs text-popover-foreground shadow-md">
<p className="font-medium">{run.pipeline}</p>
<p className="font-mono text-muted-foreground">{run.branch}</p>
<div className="mt-1.5 space-y-0.5 text-[11px]">
<p>
<span className="text-muted-foreground">Status: </span>
{STATE_LABEL[state]}
</p>
<p>
<span className="text-muted-foreground">Duration: </span>
{formatSeconds(run.durationSec)}
</p>
<p>
<span className="text-muted-foreground">Started: </span>
{run.startedLabel}
</p>
</div>
</div>
);
}}
/>
<Bar dataKey="duration" radius={[4, 4, 0, 0]} maxBarSize={40} cursor="pointer">
{chartData.map((entry) => (
<Cell
key={entry.run.id}
fill={STATE_TOKEN[entry.state]}
opacity={selectedId === entry.run.id ? 1 : 0.45}
/>
))}
</Bar>
</BarChart>
</ChartContainer>
<div className="mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 px-2 text-[11px] text-muted-foreground">
{LEGEND.map((item) => (
<span key={item.state} className="flex items-center gap-1.5">
<span
className="inline-block size-2 rounded-sm"
style={{ backgroundColor: STATE_TOKEN[item.state] }}
/>
{item.label}
</span>
))}
</div>
</CardContent>
</Card>
<RunBreakdown run={selectedRun} />
</div>
<Card data-slot="dashboard-recent" className="py-0">
<CardHeader className="px-6 pt-4 pb-3">
<CardTitle>Recent runs</CardTitle>
<CardDescription>The last {RUNS.length} pipeline runs.</CardDescription>
</CardHeader>
<CardContent className="px-0 pb-2">
<ul className="flex flex-col">
{RUNS.map((run, i) => (
<li key={run.id}>
<button
type="button"
onClick={() => setSelectedId(run.id)}
aria-pressed={selectedId === run.id}
className={cn(
'grid w-full grid-cols-[auto_1fr_auto] items-center gap-x-3 gap-y-1 px-6 py-3 text-start transition-colors duration-150 ease-out hover:bg-muted/50',
i < RUNS.length - 1 && 'border-b border-border',
selectedId === run.id && 'bg-muted/40',
)}
>
<StateDot state={run.state} />
<div className="flex min-w-0 flex-col">
<span className="truncate text-sm font-medium">{run.pipeline}</span>
<span className="inline-flex items-center gap-1.5 font-mono text-[11px] text-muted-foreground">
<Hash className="size-3 shrink-0" aria-hidden />
{run.number}
<span className="inline-flex items-center gap-1">
<GitBranch className="size-3 shrink-0" aria-hidden />
{run.branch}
</span>
</span>
</div>
<div className="flex items-center gap-3">
<span className="font-mono text-xs tabular-nums text-muted-foreground">
{formatSeconds(run.durationSec)}
<span className="sr-only">, started {run.startedLabel}</span>
</span>
<span
className="hidden font-mono text-[10px] uppercase tracking-[0.08em] sm:inline"
style={{ color: STATE_TOKEN[run.state] }}
>
{STATE_LABEL[run.state]}
</span>
<ChevronRight className="size-4 text-muted-foreground rtl:rotate-180" aria-hidden />
</div>
</button>
</li>
))}
</ul>
</CardContent>
</Card>
</div>
</section>
);
};
export default Dashboard06;
Dependencies
shadcn registry
badgebuttoncardchartempty
npm
rechartslucide-react