feat: add server-sent events for logging and activity tracking
- Implemented a custom tracing layer (`AppLogLayer`) to capture log events and broadcast them to SSE clients. - Created admin routes for streaming server logs and listing recent activity logs. - Added an activity log repository interface and SQLite implementation for persisting activity events. - Integrated activity logging into user authentication and channel CRUD operations. - Developed frontend components for displaying server logs and activity logs in the admin panel. - Enhanced the video player with a stats overlay for monitoring streaming metrics.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
"use client";
|
||||
|
||||
import type { ActivityEvent } from "@/lib/types";
|
||||
|
||||
const eventColors: Record<string, string> = {
|
||||
channel_created: "bg-green-900/40 text-green-400",
|
||||
channel_updated: "bg-blue-900/40 text-blue-400",
|
||||
channel_deleted: "bg-red-900/40 text-red-400",
|
||||
schedule_generated: "bg-violet-900/40 text-violet-400",
|
||||
user_login: "bg-zinc-800 text-zinc-400",
|
||||
};
|
||||
|
||||
function fmtTs(ts: string) {
|
||||
try {
|
||||
const d = new Date(ts);
|
||||
return d.toLocaleTimeString(undefined, { hour12: false });
|
||||
} catch {
|
||||
return ts;
|
||||
}
|
||||
}
|
||||
|
||||
interface ActivityLogPanelProps {
|
||||
events: ActivityEvent[];
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export function ActivityLogPanel({ events, isLoading }: ActivityLogPanelProps) {
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="border-b border-zinc-800 px-4 py-2.5">
|
||||
<span className="text-xs font-semibold uppercase tracking-widest text-violet-400">
|
||||
Activity
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-3">
|
||||
{isLoading && events.length === 0 ? (
|
||||
<p className="mt-8 text-center text-xs text-zinc-600">Loading…</p>
|
||||
) : events.length === 0 ? (
|
||||
<p className="mt-8 text-center text-xs text-zinc-600">No activity yet.</p>
|
||||
) : (
|
||||
<div className="flex flex-col gap-2">
|
||||
{events.map((event) => (
|
||||
<div
|
||||
key={event.id}
|
||||
className="rounded-md border border-zinc-800 bg-zinc-900 p-3"
|
||||
>
|
||||
<div className="mb-1 flex items-center gap-2">
|
||||
<span
|
||||
className={`rounded px-1.5 py-0.5 text-[10px] font-medium ${
|
||||
eventColors[event.event_type] ?? "bg-zinc-800 text-zinc-400"
|
||||
}`}
|
||||
>
|
||||
{event.event_type.replace(/_/g, " ")}
|
||||
</span>
|
||||
<span className="ml-auto font-mono text-[10px] text-zinc-600">
|
||||
{fmtTs(event.timestamp)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-zinc-300">{event.detail}</p>
|
||||
{event.channel_id && (
|
||||
<p className="mt-0.5 font-mono text-[10px] text-zinc-600">
|
||||
ch: {event.channel_id.slice(0, 8)}…
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user