1+ import { Card , CardContent , CardDescription , CardHeader , CardTitle } from "@/components/ui/card" ;
2+ import { Badge } from "@/components/ui/badge" ;
3+ import { Router , Network , ExternalLink , Users } from "lucide-react" ;
4+ import { Stats } from "@/hooks/useWebSocket" ;
5+
6+ interface TopListsCardsProps {
7+ stats : Stats | null ;
8+ }
9+
10+ export function TopListsCards ( { stats } : TopListsCardsProps ) {
11+ if ( ! stats ) {
12+ return (
13+ < div className = "grid gap-4 md:grid-cols-2 lg:grid-cols-4" >
14+ { [ ...Array ( 4 ) ] . map ( ( _ , i ) => (
15+ < Card key = { i } >
16+ < CardHeader className = "flex flex-row items-center justify-between space-y-0 pb-2" >
17+ < CardTitle className = "text-sm font-medium" > Loading...</ CardTitle >
18+ </ CardHeader >
19+ < CardContent >
20+ < div className = "h-32 bg-muted animate-pulse rounded" />
21+ </ CardContent >
22+ </ Card >
23+ ) ) }
24+ </ div >
25+ ) ;
26+ }
27+
28+ const topLists = [
29+ {
30+ title : "Top Routers" ,
31+ description : "Most active routers" ,
32+ icon : Router ,
33+ data : stats . topRouters || [ ] ,
34+ color : "text-purple-600" ,
35+ dataKey : "router"
36+ } ,
37+ {
38+ title : "Top Request Addresses" ,
39+ description : "Most requested addresses" ,
40+ icon : Network ,
41+ data : stats . topRequestAddrs || [ ] ,
42+ color : "text-cyan-600" ,
43+ dataKey : "addr"
44+ } ,
45+ {
46+ title : "Top Request Hosts" ,
47+ description : "Most requested hosts" ,
48+ icon : ExternalLink ,
49+ data : stats . topRequestHosts || [ ] ,
50+ color : "text-emerald-600" ,
51+ dataKey : "host"
52+ } ,
53+ {
54+ title : "Top Client IPs" ,
55+ description : "Most active IP addresses" ,
56+ icon : Users ,
57+ data : stats . topIPs || [ ] ,
58+ color : "text-orange-600" ,
59+ dataKey : "ip"
60+ }
61+ ] ;
62+
63+ return (
64+ < div className = "grid gap-4 md:grid-cols-2 lg:grid-cols-4" >
65+ { topLists . map ( ( list , index ) => {
66+ const Icon = list . icon ;
67+ return (
68+ < Card key = { index } >
69+ < CardHeader >
70+ < CardTitle className = "flex items-center gap-2" >
71+ < Icon className = { `h-5 w-5 ${ list . color } ` } />
72+ { list . title }
73+ </ CardTitle >
74+ < CardDescription > { list . description } </ CardDescription >
75+ </ CardHeader >
76+ < CardContent >
77+ < div className = "space-y-2" >
78+ { list . data . length === 0 ? (
79+ < div className = "text-center text-muted-foreground py-4" >
80+ No data available
81+ </ div >
82+ ) : (
83+ list . data . slice ( 0 , 5 ) . map ( ( item : any , itemIndex ) => {
84+ const percentage = stats ? ( ( item . count / stats . totalRequests ) * 100 ) . toFixed ( 1 ) : 0 ;
85+ const value = item [ list . dataKey ] ;
86+ const displayValue = value . length > 20 ? `${ value . substring ( 0 , 20 ) } ...` : value ;
87+
88+ return (
89+ < div key = { itemIndex } className = "flex items-center justify-between p-2 rounded-lg bg-muted/30" >
90+ < div className = "flex items-center gap-2 min-w-0 flex-1" >
91+ < div className = { `w-2 h-2 rounded-full bg-${ list . color . split ( '-' ) [ 1 ] } -${ 500 - itemIndex * 50 } ` }
92+ style = { { backgroundColor : `hsl(${ 220 - itemIndex * 20 } , 70%, ${ 60 - itemIndex * 5 } %)` } } />
93+ < span className = "font-mono text-xs truncate" title = { value } >
94+ { displayValue }
95+ </ span >
96+ </ div >
97+ < div className = "flex items-center gap-2 flex-shrink-0" >
98+ < Badge variant = "secondary" className = "text-xs" >
99+ { item . count . toLocaleString ( ) }
100+ </ Badge >
101+ < span className = "text-xs text-muted-foreground" >
102+ { percentage } %
103+ </ span >
104+ </ div >
105+ </ div >
106+ ) ;
107+ } )
108+ ) }
109+ </ div >
110+ </ CardContent >
111+ </ Card >
112+ ) ;
113+ } ) }
114+ </ div >
115+ ) ;
116+ }
0 commit comments