mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 11:11:16 -05:00
Add comprehensive monitoring dashboard scaffolding: - Extend queryKeys with monitoring keys - Create hooks: useCombinedAlerts, useRecentActivity, useDatabaseHealth, useModerationHealth - Build UI components: SystemHealthStatus, CriticalAlertsPanel, MonitoringQuickStats, RecentActivityTimeline, MonitoringNavCards - Create MonitoringOverview page and integrate routing (MonitoringOverview route) - Wire AdminSidebar to include Monitoring navigation - Introduce supporting routing and admin layout hooks/utilities - Align with TanStack Query patterns and plan for auto-refresh and optimistic updates
139 lines
4.2 KiB
TypeScript
139 lines
4.2 KiB
TypeScript
import { AlertTriangle, Database, ShieldAlert, XCircle } from 'lucide-react';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
import { formatDistanceToNow } from 'date-fns';
|
|
import { Link } from 'react-router-dom';
|
|
import type { ActivityEvent } from '@/hooks/admin/useRecentActivity';
|
|
|
|
interface RecentActivityTimelineProps {
|
|
activity?: ActivityEvent[];
|
|
isLoading: boolean;
|
|
}
|
|
|
|
export function RecentActivityTimeline({ activity, isLoading }: RecentActivityTimelineProps) {
|
|
if (isLoading) {
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Recent Activity</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-center text-muted-foreground py-8">Loading activity...</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
if (!activity || activity.length === 0) {
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Recent Activity (Last Hour)</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-center text-muted-foreground py-8">No recent activity</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
const getEventIcon = (event: ActivityEvent) => {
|
|
switch (event.type) {
|
|
case 'error':
|
|
return XCircle;
|
|
case 'approval':
|
|
return Database;
|
|
case 'alert':
|
|
return AlertTriangle;
|
|
}
|
|
};
|
|
|
|
const getEventColor = (event: ActivityEvent) => {
|
|
switch (event.type) {
|
|
case 'error':
|
|
return 'text-red-500';
|
|
case 'approval':
|
|
return 'text-orange-500';
|
|
case 'alert':
|
|
return 'text-yellow-500';
|
|
}
|
|
};
|
|
|
|
const getEventDescription = (event: ActivityEvent) => {
|
|
switch (event.type) {
|
|
case 'error':
|
|
return `${event.error_type} in ${event.endpoint}`;
|
|
case 'approval':
|
|
return `Approval failed: ${event.error_message}`;
|
|
case 'alert':
|
|
return event.message;
|
|
}
|
|
};
|
|
|
|
const getEventLink = (event: ActivityEvent) => {
|
|
switch (event.type) {
|
|
case 'error':
|
|
return `/admin/error-monitoring`;
|
|
case 'approval':
|
|
return `/admin/error-monitoring?tab=approvals`;
|
|
case 'alert':
|
|
return `/admin/error-monitoring`;
|
|
default:
|
|
return undefined;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<div className="flex items-center justify-between">
|
|
<CardTitle>Recent Activity (Last Hour)</CardTitle>
|
|
<Badge variant="outline">{activity.length} events</Badge>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<ScrollArea className="h-[400px] pr-4">
|
|
<div className="space-y-3">
|
|
{activity.map((event) => {
|
|
const Icon = getEventIcon(event);
|
|
const color = getEventColor(event);
|
|
const description = getEventDescription(event);
|
|
const link = getEventLink(event);
|
|
|
|
const content = (
|
|
<div
|
|
className={`flex items-start gap-3 p-3 rounded-lg border border-border transition-colors ${
|
|
link ? 'hover:bg-accent/50 cursor-pointer' : ''
|
|
}`}
|
|
>
|
|
<Icon className={`w-5 h-5 mt-0.5 flex-shrink-0 ${color}`} />
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-2 flex-wrap">
|
|
<Badge variant="outline" className="text-xs capitalize">
|
|
{event.type}
|
|
</Badge>
|
|
<span className="text-xs text-muted-foreground">
|
|
{formatDistanceToNow(new Date(event.created_at), { addSuffix: true })}
|
|
</span>
|
|
</div>
|
|
<p className="text-sm mt-1 break-words">{description}</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
return link ? (
|
|
<Link key={event.id} to={link}>
|
|
{content}
|
|
</Link>
|
|
) : (
|
|
<div key={event.id}>{content}</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</ScrollArea>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|