Sanitized mirror from private repository - 2026-04-05 08:31:50 UTC
This commit is contained in:
82
dashboard/ui/components/activity-feed.tsx
Normal file
82
dashboard/ui/components/activity-feed.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
"use client";
|
||||
|
||||
import { useSSE } from "@/lib/use-sse";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import type { ActivityEvent } from "@/lib/types";
|
||||
|
||||
const typeColors: Record<string, string> = {
|
||||
stack_healthy: "bg-green-500 glow-green",
|
||||
backup_result: "bg-green-500 glow-green",
|
||||
email_classified: "bg-blue-500 glow-blue",
|
||||
receipt_extracted: "bg-amber-500 glow-amber",
|
||||
container_unhealthy: "bg-red-500 glow-red",
|
||||
};
|
||||
|
||||
function formatTime(ts: string) {
|
||||
try {
|
||||
return new Date(ts).toLocaleTimeString("en-US", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
} catch {
|
||||
return ts;
|
||||
}
|
||||
}
|
||||
|
||||
function eventMessage(event: ActivityEvent): string {
|
||||
if (event.raw) return event.raw;
|
||||
return `${event.type} from ${event.source}`;
|
||||
}
|
||||
|
||||
export function ActivityFeed() {
|
||||
const events = useSSE("/api/activity");
|
||||
|
||||
return (
|
||||
<Card className="col-span-full lg:col-span-3 overflow-hidden relative">
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
<CardTitle className="text-sm font-medium">Activity Feed</CardTitle>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="text-[10px] border-green-500/30 text-green-400 animate-live-pulse bg-green-500/5"
|
||||
>
|
||||
<span className="inline-block w-1.5 h-1.5 rounded-full bg-green-400 mr-1.5 glow-green" />
|
||||
LIVE
|
||||
</Badge>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ScrollArea className="h-[220px]">
|
||||
{events.length === 0 && (
|
||||
<p className="text-xs text-muted-foreground py-4 text-center">
|
||||
Waiting for events...
|
||||
</p>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
{events.map((event, i) => (
|
||||
<div
|
||||
key={`${event.timestamp}-${i}`}
|
||||
className="flex items-start gap-3 text-xs animate-slide-in rounded-lg px-2 py-1.5 transition-colors hover:bg-white/[0.03]"
|
||||
style={{ animationDelay: `${i * 30}ms` }}
|
||||
>
|
||||
<span
|
||||
className={`w-2.5 h-2.5 rounded-full mt-0.5 shrink-0 ${
|
||||
typeColors[event.type] ?? "bg-gray-500"
|
||||
}`}
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-foreground truncate">
|
||||
{eventMessage(event)}
|
||||
</p>
|
||||
<p className="text-muted-foreground/70">
|
||||
{formatTime(event.timestamp)} · {event.source}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user