- 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.
74 lines
2.4 KiB
TypeScript
74 lines
2.4 KiB
TypeScript
"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>
|
|
);
|
|
}
|