Skip to content

Commit f1c4539

Browse files
Merge pull request #2 from hhftechnology/dev
Dev
2 parents 656b3ec + 1a40fc2 commit f1c4539

File tree

6 files changed

+358
-48
lines changed

6 files changed

+358
-48
lines changed

.github/workflows/build-and-publish.yml

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -65,36 +65,3 @@ jobs:
6565
cache-from: type=gha
6666
cache-to: type=gha,mode=max
6767

68-
test:
69-
runs-on: ubuntu-latest
70-
needs: build-and-push
71-
if: github.event_name == 'pull_request'
72-
73-
steps:
74-
- name: Checkout repository
75-
uses: actions/checkout@v4
76-
77-
- name: Create test log file
78-
run: |
79-
mkdir -p logs
80-
echo '{"time":"2024-01-20T10:00:00Z","ServiceName":"test-service","RequestMethod":"GET","RequestPath":"/api/test","DownstreamStatus":200,"Duration":0.05}' > logs/traefik.log
81-
82-
- name: Start services
83-
run: |
84-
echo "TRAEFIK_LOG_PATH=./logs" > .env
85-
docker-compose up -d
86-
87-
- name: Wait for services
88-
run: sleep 10
89-
90-
- name: Test backend health
91-
run: |
92-
curl -f http://localhost:3001/health || exit 1
93-
94-
- name: Test frontend
95-
run: |
96-
curl -f http://localhost:3000 || exit 1
97-
98-
- name: Show logs if failed
99-
if: failure()
100-
run: docker-compose logs

backend/src/logParser.js

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ export class LogParser extends EventEmitter {
2121
requests2xx: 0,
2222
requestsPerSecond: 0,
2323
topIPs: {},
24-
countries: {}
24+
countries: {},
25+
topRouters: {},
26+
topRequestAddrs: {},
27+
topRequestHosts: {}
2528
};
2629
this.lastTimestamp = Date.now();
2730
this.requestsInLastSecond = 0;
@@ -134,6 +137,8 @@ export class LogParser extends EventEmitter {
134137
serviceName: log.ServiceName || 'unknown',
135138
routerName: log.RouterName || 'unknown',
136139
host: log.RequestHost || '',
140+
requestAddr: log.RequestAddr || '', // New field
141+
requestHost: log.RequestHost || '', // Explicit field for RequestHost
137142
userAgent: log['request_User-Agent'] || '',
138143
size: parseInt(log.DownstreamContentSize || 0),
139144
country: null,
@@ -197,6 +202,18 @@ export class LogParser extends EventEmitter {
197202
this.stats.topIPs[log.clientIP] = (this.stats.topIPs[log.clientIP] || 0) + 1;
198203
}
199204

205+
if (log.routerName && log.routerName !== 'unknown') {
206+
this.stats.topRouters[log.routerName] = (this.stats.topRouters[log.routerName] || 0) + 1;
207+
}
208+
209+
if (log.requestAddr && log.requestAddr !== '') {
210+
this.stats.topRequestAddrs[log.requestAddr] = (this.stats.topRequestAddrs[log.requestAddr] || 0) + 1;
211+
}
212+
213+
if (log.requestHost && log.requestHost !== '') {
214+
this.stats.topRequestHosts[log.requestHost] = (this.stats.topRequestHosts[log.requestHost] || 0) + 1;
215+
}
216+
200217
if (log.country && log.countryCode) {
201218
const key = `${log.countryCode}|${log.country}`;
202219
this.stats.countries[key] = (this.stats.countries[key] || 0) + 1;
@@ -228,10 +245,28 @@ export class LogParser extends EventEmitter {
228245
return { country: name, countryCode: code, count };
229246
});
230247

248+
const topRouters = Object.entries(this.stats.topRouters)
249+
.sort(([, a], [, b]) => b - a)
250+
.slice(0, 10)
251+
.map(([router, count]) => ({ router, count }));
252+
253+
const topRequestAddrs = Object.entries(this.stats.topRequestAddrs)
254+
.sort(([, a], [, b]) => b - a)
255+
.slice(0, 10)
256+
.map(([addr, count]) => ({ addr, count }));
257+
258+
const topRequestHosts = Object.entries(this.stats.topRequestHosts)
259+
.sort(([, a], [, b]) => b - a)
260+
.slice(0, 10)
261+
.map(([host, count]) => ({ host, count }));
262+
231263
return {
232264
...this.stats,
233265
topIPs,
234266
topCountries,
267+
topRouters,
268+
topRequestAddrs,
269+
topRequestHosts,
235270
avgResponseTime: Math.round(this.stats.avgResponseTime * 100) / 100
236271
};
237272
}

frontend/src/components/Dashboard.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useWebSocket } from "@/hooks/useWebSocket";
22
import { StatsCards } from "./StatsCards";
33
import { LogTable } from "./LogTable";
44
import { GeoMap } from "./GeoMap";
5+
import { TopListsCards } from "./TopListsCards";
56
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
67
import { Badge } from "@/components/ui/badge";
78
import { Activity, AlertCircle, Server } from "lucide-react";
@@ -114,6 +115,8 @@ export function Dashboard() {
114115
</Card>
115116
</div>
116117

118+
<TopListsCards stats={stats} />
119+
117120
<GeoMap stats={stats} />
118121

119122
<Card>
@@ -130,4 +133,4 @@ export function Dashboard() {
130133
</Card>
131134
</div>
132135
);
133-
}
136+
}

frontend/src/components/LogTable.tsx

Lines changed: 197 additions & 13 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 } 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,26 +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>Client IP</TableHead>
93-
<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>
94262
<TableHead>Size</TableHead>
95263
</TableRow>
96264
</TableHeader>
97265
<TableBody>
98-
{logs.length === 0 ? (
266+
{sortedLogs.length === 0 ? (
99267
<TableRow>
100-
<TableCell colSpan={10} className="h-24 text-center text-muted-foreground">
268+
<TableCell colSpan={12} className="h-24 text-center text-muted-foreground">
101269
No logs found. Waiting for incoming requests...
102270
</TableCell>
103271
</TableRow>
104272
) : (
105-
logs.map((log) => {
273+
sortedLogs.map((log) => {
106274
const responseTime = formatResponseTime(log.responseTime);
107275
return (
108276
<TableRow key={log.id}>
@@ -139,6 +307,22 @@ export function LogTable({ logs, requestLogs }: LogTableProps) {
139307
<span className="text-xs">{log.routerName}</span>
140308
</div>
141309
</TableCell>
310+
<TableCell>
311+
<div className="flex items-center gap-1">
312+
<Network className="h-3 w-3 text-muted-foreground" />
313+
<span className="text-xs font-mono max-w-32 truncate" title={log.requestAddr}>
314+
{log.requestAddr || '-'}
315+
</span>
316+
</div>
317+
</TableCell>
318+
<TableCell>
319+
<div className="flex items-center gap-1">
320+
<ExternalLink className="h-3 w-3 text-muted-foreground" />
321+
<span className="text-xs font-mono max-w-32 truncate" title={log.requestHost}>
322+
{log.requestHost || '-'}
323+
</span>
324+
</div>
325+
</TableCell>
142326
<TableCell className="font-mono text-xs">
143327
{log.clientIP}
144328
</TableCell>
@@ -160,12 +344,12 @@ export function LogTable({ logs, requestLogs }: LogTableProps) {
160344
})
161345
)}
162346
<TableRow ref={loader}>
163-
<TableCell colSpan={10} className="text-center text-muted-foreground">
347+
<TableCell colSpan={12} className="text-center text-muted-foreground">
164348
Loading more logs...
165349
</TableCell>
166350
</TableRow>
167351
</TableBody>
168352
</Table>
169353
</div>
170354
);
171-
}
355+
}

0 commit comments

Comments
 (0)