feat(ui): redesign global logs as cockpit surface (#699)

Rework the Logs view into the cockpit language: a PageMasthead strip with
cyan rail, pulsing live dot, italic state word, and tracked-mono kicker;
a SignalRail of EVENTS/MIN (with rolling 60s sparkline), ERRORS, WARNINGS,
and CONTAINERS tiles; a segmented filter strip; a day-banded feed with
severity dots and whole-row tint on ERROR/WARN; and a floating glass chip
strip for pause/resume, clear, and download.

Decouple the live log stream from the Developer Mode toggle so SSE runs by
default for everyone, with polling as an invisible fallback when
EventSource is unavailable. Drop the Standard Log Polling Rate setting
and the global_logs_refresh key it persisted; Developer Mode still gates
Real-Time Metrics and Debug Diagnostics and nothing else.

Introduces the shared PageMasthead and SignalRail primitives for reuse
across other cockpit pages.
This commit is contained in:
Anso
2026-04-19 16:25:54 -04:00
committed by GitHub
parent ef4455f68d
commit b95442c8f7
13 changed files with 633 additions and 261 deletions
+126
View File
@@ -0,0 +1,126 @@
import type { ReactNode } from 'react';
import { cn } from '@/lib/utils';
export type MastheadTone = 'live' | 'idle' | 'warn' | 'error';
export interface MastheadMetadataItem {
label: string;
value: string;
tone?: 'value' | 'warn' | 'error' | 'subtitle';
}
export interface PageMastheadProps {
kicker: string;
state: string;
tone: MastheadTone;
pulsing?: boolean;
metadata?: MastheadMetadataItem[];
children?: ReactNode;
className?: string;
}
const toneConfig: Record<MastheadTone, {
dotClass: string;
stateTextClass: string;
tintClass: string;
}> = {
live: {
dotClass: 'bg-brand shadow-[0_0_0_3px_color-mix(in_oklch,var(--brand)_22%,transparent)]',
stateTextClass: 'text-stat-value',
tintClass: 'from-brand/[0.06] via-transparent to-transparent',
},
idle: {
dotClass: 'bg-stat-subtitle',
stateTextClass: 'text-stat-title',
tintClass: 'from-transparent via-transparent to-transparent',
},
warn: {
dotClass: 'bg-warning shadow-[0_0_0_3px_color-mix(in_oklch,var(--warning)_22%,transparent)]',
stateTextClass: 'text-warning',
tintClass: 'from-warning/[0.06] via-transparent to-transparent',
},
error: {
dotClass: 'bg-destructive shadow-[0_0_0_3px_color-mix(in_oklch,var(--destructive)_24%,transparent)]',
stateTextClass: 'text-destructive',
tintClass: 'from-destructive/[0.06] via-transparent to-transparent',
},
};
const metadataToneClass: Record<NonNullable<MastheadMetadataItem['tone']>, string> = {
value: 'text-stat-value',
warn: 'text-warning',
error: 'text-destructive',
subtitle: 'text-stat-subtitle',
};
export function PageMasthead({
kicker,
state,
tone,
pulsing = false,
metadata,
children,
className,
}: PageMastheadProps) {
const config = toneConfig[tone];
const shouldPulse = pulsing && (tone === 'live' || tone === 'warn');
return (
<div
className={cn(
'relative shrink-0 overflow-hidden border-b border-card-border bg-card',
className,
)}
>
<div className={cn('pointer-events-none absolute inset-0 bg-gradient-to-r', config.tintClass)} />
<div className="absolute inset-y-0 left-0 w-[3px] bg-brand" />
<div className="relative grid grid-cols-[1fr_auto] items-center gap-6 py-4 pl-7 pr-6">
<div className="flex min-w-0 items-center gap-4">
<span
aria-hidden="true"
className={cn(
'h-2.5 w-2.5 shrink-0 rounded-full',
config.dotClass,
shouldPulse && 'animate-[pulse_2.4s_ease-in-out_infinite]',
)}
/>
<div className="flex min-w-0 flex-col gap-1">
<span className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle">
{kicker}
</span>
<span className={cn('font-display italic text-2xl leading-none tracking-tight', config.stateTextClass)}>
{state}
</span>
</div>
{children ? <div className="ml-2 min-w-0">{children}</div> : null}
</div>
{metadata && metadata.length > 0 ? (
<div className="hidden items-stretch justify-end gap-0 md:flex">
{metadata.map((item, idx) => (
<div
key={item.label}
className={cn(
'flex flex-col gap-1 px-5',
idx > 0 && 'border-l border-border/60',
)}
>
<span className="font-mono text-[10px] uppercase tracking-[0.22em] text-stat-subtitle">
{item.label}
</span>
<span
className={cn(
'font-mono tabular-nums text-lg leading-none',
metadataToneClass[item.tone ?? 'value'],
)}
>
{item.value}
</span>
</div>
))}
</div>
) : null}
</div>
</div>
);
}
+71
View File
@@ -0,0 +1,71 @@
import { cn } from '@/lib/utils';
import { Sparkline } from '@/components/ui/sparkline';
export type SignalTone = 'value' | 'warn' | 'error' | 'subtitle';
export interface SignalTile {
kicker: string;
value: string;
tone?: SignalTone;
/** Optional series for a 64x20 sparkline stroked in brand cyan. */
spark?: number[];
}
interface SignalRailProps {
tiles: SignalTile[];
className?: string;
}
const toneClass: Record<SignalTone, string> = {
value: 'text-stat-value',
warn: 'text-warning',
error: 'text-destructive',
subtitle: 'text-stat-subtitle',
};
export function SignalRail({ tiles, className }: SignalRailProps) {
if (tiles.length === 0) return null;
return (
<div
className={cn(
'shrink-0 grid border-b border-card-border bg-card',
className,
)}
style={{ gridTemplateColumns: `repeat(${tiles.length}, minmax(0, 1fr))` }}
>
{tiles.map((tile, idx) => (
<div
key={tile.kicker}
className={cn(
'flex items-center justify-between gap-4 px-5 py-[var(--density-tile-y)]',
idx > 0 && 'border-l border-card-border',
)}
>
<div className="flex min-w-0 flex-col gap-1">
<span className="font-mono text-[10px] uppercase tracking-[0.22em] text-stat-subtitle">
{tile.kicker}
</span>
<span
className={cn(
'font-mono tabular-nums tracking-tight text-2xl leading-none',
toneClass[tile.tone ?? 'value'],
)}
>
{tile.value}
</span>
</div>
{tile.spark && tile.spark.length > 1 ? (
<div className="h-5 w-16 shrink-0 opacity-90">
<Sparkline
points={tile.spark}
stroke="var(--brand)"
fill="var(--brand)"
strokeWidth={1.25}
/>
</div>
) : null}
</div>
))}
</div>
);
}