gitea
This commit is contained in:
211
frontend/app/analytics/page.tsx
Normal file
211
frontend/app/analytics/page.tsx
Normal file
@@ -0,0 +1,211 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user