website-monitor/frontend/app/analytics/page.tsx

212 lines
11 KiB
TypeScript

'use client'
import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { monitorAPI } from '@/lib/api'
import { DashboardLayout } from '@/components/layout/dashboard-layout'
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
type TimeRange = '7d' | '30d' | '90d' | 'all'
export default function AnalyticsPage() {
const [timeRange, setTimeRange] = useState<TimeRange>('30d')
const { data, isLoading } = useQuery({
queryKey: ['monitors'],
queryFn: async () => {
const response = await monitorAPI.list()
return response.monitors
},
})
if (isLoading) {
return (
<DashboardLayout title="Analytics" description="Monitor performance and statistics">
<div className="flex items-center justify-center py-12">
<div className="flex flex-col items-center gap-3">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<p className="text-muted-foreground">Loading...</p>
</div>
</div>
</DashboardLayout>
)
}
const monitors = data || []
const totalMonitors = monitors.length
const activeMonitors = monitors.filter((m: any) => m.status === 'active').length
const errorMonitors = monitors.filter((m: any) => m.status === 'error').length
const avgFrequency = totalMonitors > 0
? Math.round(monitors.reduce((sum: number, m: any) => sum + m.frequency, 0) / totalMonitors)
: 0
// Calculate additional stats
const pausedMonitors = monitors.filter((m: any) => m.status === 'paused').length
const recentChanges = monitors.filter((m: any) => {
if (!m.last_change_at) return false
const changeDate = new Date(m.last_change_at)
const daysAgo = timeRange === '7d' ? 7 : timeRange === '30d' ? 30 : timeRange === '90d' ? 90 : 365
const cutoff = new Date(Date.now() - daysAgo * 24 * 60 * 60 * 1000)
return changeDate >= cutoff
}).length
return (
<DashboardLayout title="Analytics" description="Monitor performance and statistics">
{/* Time Range Selector */}
<div className="mb-6 flex flex-wrap gap-2">
{(['7d', '30d', '90d', 'all'] as const).map((range) => (
<Button
key={range}
variant={timeRange === range ? 'default' : 'outline'}
size="sm"
onClick={() => setTimeRange(range)}
>
{range === 'all' ? 'All Time' : range === '7d' ? 'Last 7 Days' : range === '30d' ? 'Last 30 Days' : 'Last 90 Days'}
</Button>
))}
</div>
{/* Stats Overview */}
<div className="mb-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-muted-foreground">Total Monitors</p>
<p className="text-3xl font-bold">{totalMonitors}</p>
</div>
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary/10">
<svg className="h-6 w-6 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-muted-foreground">Uptime Rate</p>
<p className="text-3xl font-bold text-green-600">
{totalMonitors > 0 ? Math.round((activeMonitors / totalMonitors) * 100) : 0}%
</p>
</div>
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-green-100">
<svg className="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-muted-foreground">Error Rate</p>
<p className="text-3xl font-bold text-red-600">
{totalMonitors > 0 ? Math.round((errorMonitors / totalMonitors) * 100) : 0}%
</p>
</div>
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-red-100">
<svg className="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-muted-foreground">Avg. Frequency</p>
<p className="text-3xl font-bold">{avgFrequency} min</p>
</div>
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-blue-100">
<svg className="h-6 w-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
</CardContent>
</Card>
</div>
{/* Charts Placeholder */}
<div className="grid gap-6 lg:grid-cols-2">
<Card>
<CardHeader>
<CardTitle>Monitor Status Distribution</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center justify-center py-8">
<div className="relative h-40 w-40">
<svg viewBox="0 0 100 100" className="h-full w-full -rotate-90">
<circle cx="50" cy="50" r="40" fill="none" stroke="hsl(var(--muted))" strokeWidth="12" />
<circle
cx="50"
cy="50"
r="40"
fill="none"
stroke="hsl(var(--success))"
strokeWidth="12"
strokeDasharray={`${(activeMonitors / (totalMonitors || 1)) * 251.2} 251.2`}
/>
</svg>
<div className="absolute inset-0 flex flex-col items-center justify-center">
<span className="text-2xl font-bold">{activeMonitors}</span>
<span className="text-xs text-muted-foreground">Active</span>
</div>
</div>
</div>
<div className="flex justify-center gap-6">
<div className="flex items-center gap-2">
<div className="h-3 w-3 rounded-full bg-green-500" />
<span className="text-sm">Active ({activeMonitors})</span>
</div>
<div className="flex items-center gap-2">
<div className="h-3 w-3 rounded-full bg-red-500" />
<span className="text-sm">Error ({errorMonitors})</span>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Check Frequency Distribution</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{[
{ label: '5 min', count: monitors.filter((m: any) => m.frequency === 5).length },
{ label: '30 min', count: monitors.filter((m: any) => m.frequency === 30).length },
{ label: '1 hour', count: monitors.filter((m: any) => m.frequency === 60).length },
{ label: '6 hours', count: monitors.filter((m: any) => m.frequency === 360).length },
{ label: '24 hours', count: monitors.filter((m: any) => m.frequency === 1440).length },
].map((item) => (
<div key={item.label} className="flex items-center gap-3">
<span className="w-16 text-sm text-muted-foreground">{item.label}</span>
<div className="flex-1 h-4 rounded-full bg-muted overflow-hidden">
<div
className="h-full bg-primary transition-all duration-500"
style={{ width: `${totalMonitors > 0 ? (item.count / totalMonitors) * 100 : 0}%` }}
/>
</div>
<span className="w-8 text-sm font-medium">{item.count}</span>
</div>
))}
</div>
</CardContent>
</Card>
</div>
</DashboardLayout>
)
}