Skip to content

Commit 73b5f69

Browse files
committed
Update LogTable.tsx
1 parent a6f277e commit 73b5f69

File tree

1 file changed

+178
-12
lines changed

1 file changed

+178
-12
lines changed

frontend/src/components/LogTable.tsx

Lines changed: 178 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,21 @@ import {
99
} from "@/components/ui/table";
1010
import { LogEntry } from "@/hooks/useWebSocket";
1111
import { format } from "date-fns";
12-
import { Globe, Server, Router, Network, ExternalLink } from "lucide-react";
12+
import { Globe, Server, Router, Network, ExternalLink, ChevronUp, ChevronDown, ChevronsUpDown } from "lucide-react";
1313
import { useEffect, useRef, useState } from "react";
1414

1515
interface LogTableProps {
1616
logs: LogEntry[];
1717
requestLogs: (params: { page: number, limit: number }) => void;
1818
}
1919

20+
type SortColumn = 'method' | 'status' | 'responseTime' | 'serviceName' | 'routerName' | 'requestAddr' | 'requestHost' | 'clientIP' | 'location';
21+
type SortDirection = 'asc' | 'desc' | null;
22+
2023
export function LogTable({ logs, requestLogs }: LogTableProps) {
2124
const [page, setPage] = useState(1);
25+
const [sortColumn, setSortColumn] = useState<SortColumn | null>(null);
26+
const [sortDirection, setSortDirection] = useState<SortDirection>(null);
2227
const observer = useRef<IntersectionObserver | null>(null);
2328
const loader = useRef<HTMLTableRowElement | null>(null);
2429

@@ -46,6 +51,95 @@ export function LogTable({ logs, requestLogs }: LogTableProps) {
4651
}
4752
}, [page, requestLogs]);
4853

54+
const handleSort = (column: SortColumn) => {
55+
if (sortColumn === column) {
56+
// Cycle through: asc -> desc -> none
57+
if (sortDirection === 'asc') {
58+
setSortDirection('desc');
59+
} else if (sortDirection === 'desc') {
60+
setSortDirection(null);
61+
setSortColumn(null);
62+
}
63+
} else {
64+
setSortColumn(column);
65+
setSortDirection('asc');
66+
}
67+
};
68+
69+
const getSortIcon = (column: SortColumn) => {
70+
if (sortColumn !== column) {
71+
return <ChevronsUpDown className="h-4 w-4 text-muted-foreground" />;
72+
}
73+
if (sortDirection === 'asc') {
74+
return <ChevronUp className="h-4 w-4 text-foreground" />;
75+
}
76+
if (sortDirection === 'desc') {
77+
return <ChevronDown className="h-4 w-4 text-foreground" />;
78+
}
79+
return <ChevronsUpDown className="h-4 w-4 text-muted-foreground" />;
80+
};
81+
82+
const sortedLogs = [...logs].sort((a, b) => {
83+
if (!sortColumn || !sortDirection) return 0;
84+
85+
let aValue: any;
86+
let bValue: any;
87+
88+
switch (sortColumn) {
89+
case 'method':
90+
aValue = a.method;
91+
bValue = b.method;
92+
break;
93+
case 'status':
94+
aValue = a.status;
95+
bValue = b.status;
96+
break;
97+
case 'responseTime':
98+
aValue = a.responseTime;
99+
bValue = b.responseTime;
100+
break;
101+
case 'serviceName':
102+
aValue = a.serviceName;
103+
bValue = b.serviceName;
104+
break;
105+
case 'routerName':
106+
aValue = a.routerName;
107+
bValue = b.routerName;
108+
break;
109+
case 'requestAddr':
110+
aValue = a.requestAddr || '';
111+
bValue = b.requestAddr || '';
112+
break;
113+
case 'requestHost':
114+
aValue = a.requestHost || '';
115+
bValue = b.requestHost || '';
116+
break;
117+
case 'clientIP':
118+
aValue = a.clientIP;
119+
bValue = b.clientIP;
120+
break;
121+
case 'location':
122+
aValue = a.country || '';
123+
bValue = b.country || '';
124+
break;
125+
default:
126+
return 0;
127+
}
128+
129+
// Handle different data types
130+
if (typeof aValue === 'number' && typeof bValue === 'number') {
131+
return sortDirection === 'asc' ? aValue - bValue : bValue - aValue;
132+
}
133+
134+
// String comparison
135+
const aStr = String(aValue).toLowerCase();
136+
const bStr = String(bValue).toLowerCase();
137+
138+
if (aStr < bStr) return sortDirection === 'asc' ? -1 : 1;
139+
if (aStr > bStr) return sortDirection === 'asc' ? 1 : -1;
140+
return 0;
141+
});
142+
49143

50144
const getStatusBadgeVariant = (status: number) => {
51145
if (status >= 200 && status < 300) return "success";
@@ -83,28 +177,100 @@ export function LogTable({ logs, requestLogs }: LogTableProps) {
83177
<TableHeader>
84178
<TableRow>
85179
<TableHead>Time</TableHead>
86-
<TableHead>Method</TableHead>
180+
<TableHead
181+
className="cursor-pointer select-none hover:bg-muted/50 transition-colors"
182+
onClick={() => handleSort('method')}
183+
>
184+
<div className="flex items-center gap-1">
185+
Method
186+
{getSortIcon('method')}
187+
</div>
188+
</TableHead>
87189
<TableHead>Path</TableHead>
88-
<TableHead>Status</TableHead>
89-
<TableHead>Response Time</TableHead>
90-
<TableHead>Service</TableHead>
91-
<TableHead>Router</TableHead>
92-
<TableHead>Request Addr</TableHead>
93-
<TableHead>Request Host</TableHead>
94-
<TableHead>Client IP</TableHead>
95-
<TableHead>Location</TableHead>
190+
<TableHead
191+
className="cursor-pointer select-none hover:bg-muted/50 transition-colors"
192+
onClick={() => handleSort('status')}
193+
>
194+
<div className="flex items-center gap-1">
195+
Status
196+
{getSortIcon('status')}
197+
</div>
198+
</TableHead>
199+
<TableHead
200+
className="cursor-pointer select-none hover:bg-muted/50 transition-colors"
201+
onClick={() => handleSort('responseTime')}
202+
>
203+
<div className="flex items-center gap-1">
204+
Response Time
205+
{getSortIcon('responseTime')}
206+
</div>
207+
</TableHead>
208+
<TableHead
209+
className="cursor-pointer select-none hover:bg-muted/50 transition-colors"
210+
onClick={() => handleSort('serviceName')}
211+
>
212+
<div className="flex items-center gap-1">
213+
Service
214+
{getSortIcon('serviceName')}
215+
</div>
216+
</TableHead>
217+
<TableHead
218+
className="cursor-pointer select-none hover:bg-muted/50 transition-colors"
219+
onClick={() => handleSort('routerName')}
220+
>
221+
<div className="flex items-center gap-1">
222+
Router
223+
{getSortIcon('routerName')}
224+
</div>
225+
</TableHead>
226+
<TableHead
227+
className="cursor-pointer select-none hover:bg-muted/50 transition-colors"
228+
onClick={() => handleSort('requestAddr')}
229+
>
230+
<div className="flex items-center gap-1">
231+
Request Addr
232+
{getSortIcon('requestAddr')}
233+
</div>
234+
</TableHead>
235+
<TableHead
236+
className="cursor-pointer select-none hover:bg-muted/50 transition-colors"
237+
onClick={() => handleSort('requestHost')}
238+
>
239+
<div className="flex items-center gap-1">
240+
Request Host
241+
{getSortIcon('requestHost')}
242+
</div>
243+
</TableHead>
244+
<TableHead
245+
className="cursor-pointer select-none hover:bg-muted/50 transition-colors"
246+
onClick={() => handleSort('clientIP')}
247+
>
248+
<div className="flex items-center gap-1">
249+
Client IP
250+
{getSortIcon('clientIP')}
251+
</div>
252+
</TableHead>
253+
<TableHead
254+
className="cursor-pointer select-none hover:bg-muted/50 transition-colors"
255+
onClick={() => handleSort('location')}
256+
>
257+
<div className="flex items-center gap-1">
258+
Location
259+
{getSortIcon('location')}
260+
</div>
261+
</TableHead>
96262
<TableHead>Size</TableHead>
97263
</TableRow>
98264
</TableHeader>
99265
<TableBody>
100-
{logs.length === 0 ? (
266+
{sortedLogs.length === 0 ? (
101267
<TableRow>
102268
<TableCell colSpan={12} className="h-24 text-center text-muted-foreground">
103269
No logs found. Waiting for incoming requests...
104270
</TableCell>
105271
</TableRow>
106272
) : (
107-
logs.map((log) => {
273+
sortedLogs.map((log) => {
108274
const responseTime = formatResponseTime(log.responseTime);
109275
return (
110276
<TableRow key={log.id}>

0 commit comments

Comments
 (0)