Skip to content

Commit 5188bd7

Browse files
committed
return durations in us instead of ms for better granularity
1 parent c832169 commit 5188bd7

11 files changed

Lines changed: 264 additions & 83 deletions

File tree

dashboard/README.md

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ A modern, sleek web dashboard for the fetchr.sh HTTP proxy tool. Built with Next
1010
- **Dynamic Headers**: Add, remove, and toggle headers with real-time validation
1111
- **Request Body**: Support for JSON, XML, plain text, and other content types
1212
- **Auto Content-Type**: Automatically detects and sets appropriate content-type headers
13+
- **Auto-Refresh Integration**: Automatically refreshes statistics and history when requests are sent
14+
- **No-Cache Requests**: All requests include no-cache headers to ensure fresh data
1315

1416
### 📊 Response Viewer
1517
- **Real-time Responses**: View API responses with syntax highlighting
@@ -22,15 +24,33 @@ A modern, sleek web dashboard for the fetchr.sh HTTP proxy tool. Built with Next
2224
### 📚 Request History
2325
- **View Proxy Requests**: All requests that pass through the proxy appear in the left sidebar
2426
- **Real-time Updates**: History automatically refreshes with new proxy requests
27+
- **Auto-Refresh**: Automatically refreshes when requests are sent from the dashboard
28+
- **Manual Refresh**: Click the refresh button to manually update the history
2529
- **Replay Requests**: Click any history item to load it into the request builder
2630
- **Clear History**: Use the trash icon to clear all proxy request history
2731
- **External Links**: Click the external link icon to open URLs in new tabs
28-
- **Timing Details**: View detailed proxy overhead and upstream latency metrics
32+
- **Timing Details**: View detailed proxy overhead and upstream latency metrics with microsecond precision
33+
- **Fresh Data**: All history requests include cache-busting to ensure up-to-date information
2934

3035
**Note**: Only requests that pass through the fetchr.sh proxy server are tracked in the history. Requests made directly from the dashboard are not stored in the history.
3136

37+
### 📈 Request Statistics
38+
- **Real-time Metrics**: Live statistics for proxy requests with comprehensive data
39+
- **Auto-Refresh**: Statistics automatically refresh when requests are sent from the dashboard
40+
- **Manual Refresh**: Click the refresh button in the statistics panel header
41+
- **Request Counts**: Success and error counts with visual indicators
42+
- **Success Rate**: Color-coded success rate percentage
43+
- **High-Precision Timing**: Microsecond-precision performance metrics for accurate analysis
44+
- Average request duration
45+
- Average upstream latency
46+
- Average proxy overhead
47+
- **Data Transfer**: Total request and response sizes with formatted display
48+
- **Status Code Distribution**: Top status codes with counts
49+
- **HTTP Method Distribution**: Request method breakdown with counts
50+
- **Cache-Free Updates**: Statistics are fetched with no-cache headers for real-time accuracy
51+
3252
### 🔧 Proxy Integration
33-
- **Health Monitoring**: Real-time proxy server status monitoring
53+
- **Health Monitoring**: Real-time proxy server status monitoring with no-cache health checks
3454
- **Connection Status**: Visual indicators for proxy connectivity
3555
- **Auto-refresh**: Periodic health checks every 30 seconds
3656
- **Configuration Display**: Shows current proxy host and port
@@ -88,7 +108,7 @@ npm start
88108
- **Replay Requests**: Click any history item to load it into the request builder
89109
- **Clear History**: Use the trash icon to clear all proxy request history
90110
- **External Links**: Click the external link icon to open URLs in new tabs
91-
- **Timing Details**: View detailed proxy overhead and upstream latency metrics
111+
- **Timing Details**: View detailed proxy overhead and upstream latency metrics with microsecond precision
92112

93113
**Note**: Only requests that pass through the fetchr.sh proxy server are tracked in the history. Requests made directly from the dashboard are not stored in the history.
94114

@@ -211,17 +231,4 @@ When the fetchr.sh backend supports it, the dashboard will integrate with:
211231
- `GET /api/requests` - Fetch request history
212232
- `POST /api/requests` - Make requests through proxy
213233
- `GET /api/health` - Proxy health status
214-
- `GET /api/stats` - Request statistics
215-
- `POST /api/replay` - Replay captured requests
216-
217-
## Contributing
218-
219-
1. Fork the repository
220-
2. Create a feature branch
221-
3. Make your changes
222-
4. Run tests and linting
223-
5. Submit a pull request
224-
225-
## License
226-
227-
This project is part of the fetchr.sh tool suite. See the main project for license information.
234+
- `GET /api/stats`

dashboard/next.config.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,36 @@
11
import type { NextConfig } from "next";
22

33
const nextConfig: NextConfig = {
4-
/* config options here */
4+
// Disable all caching to ensure fresh data
5+
experimental: {
6+
staleTimes: {
7+
dynamic: 0,
8+
static: 0,
9+
},
10+
},
11+
12+
// Add headers to prevent caching
13+
async headers() {
14+
return [
15+
{
16+
source: '/:path*',
17+
headers: [
18+
{
19+
key: 'Cache-Control',
20+
value: 'no-cache, no-store, must-revalidate',
21+
},
22+
{
23+
key: 'Pragma',
24+
value: 'no-cache',
25+
},
26+
{
27+
key: 'Expires',
28+
value: '0',
29+
},
30+
],
31+
},
32+
];
33+
},
534
};
635

736
export default nextConfig;

dashboard/src/app/page.tsx

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,27 @@ import { Header } from '../components/Header';
44
import { Sidebar } from '../components/Sidebar';
55
import { RequestBuilder } from '../components/RequestBuilder';
66
import { RequestStats } from '../components/RequestStats';
7+
import { RefreshProvider } from '../hooks/useRefreshContext';
78

89
export default function Home() {
910
return (
10-
<div className="h-screen flex flex-col">
11-
<Header />
12-
<div className="flex flex-1 overflow-hidden">
13-
<div className="flex flex-col w-80 border-r bg-background">
14-
<div className="flex-1 min-h-0">
15-
<Sidebar />
16-
</div>
17-
<div className="border-t">
18-
<RequestStats className="rounded-none border-0" />
11+
<RefreshProvider>
12+
<div className="h-screen flex flex-col">
13+
<Header />
14+
<div className="flex flex-1 overflow-hidden">
15+
<div className="flex flex-col w-80 border-r bg-background">
16+
<div className="flex-1 min-h-0">
17+
<Sidebar />
18+
</div>
19+
<div className="border-t">
20+
<RequestStats className="rounded-none border-0" />
21+
</div>
1922
</div>
23+
<main className="flex-1 p-6 overflow-auto">
24+
<RequestBuilder />
25+
</main>
2026
</div>
21-
<main className="flex-1 p-6 overflow-auto">
22-
<RequestBuilder />
23-
</main>
2427
</div>
25-
</div>
28+
</RefreshProvider>
2629
);
2730
}

dashboard/src/components/RequestBuilder.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Badge } from './ui/badge';
1212
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
1313
import { Plus, X, Send, RotateCcw, Copy } from 'lucide-react';
1414
import { useRequestStore } from '../hooks/useRequestStore';
15+
import { useRefresh } from '../hooks/useRefreshContext';
1516
import { apiService } from '../services/api';
1617
import { HttpMethod, ApiError } from '../types/api';
1718

@@ -38,6 +39,8 @@ export function RequestBuilder() {
3839
reset,
3940
} = useRequestStore();
4041

42+
const { triggerRefresh } = useRefresh();
43+
4144
const handleSendRequest = async () => {
4245
if (!url.trim()) {
4346
setError('URL is required');
@@ -66,6 +69,9 @@ export function RequestBuilder() {
6669

6770
const apiResponse = await apiService.makeRequest(requestConfig);
6871
setResponse(apiResponse);
72+
73+
// Trigger refresh of statistics and history after successful request
74+
triggerRefresh();
6975
} catch (err: unknown) {
7076
const error = err as ApiError;
7177
const errorMessage = error?.body || error?.message || 'Request failed';

dashboard/src/components/RequestStats.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Button } from './ui/button';
77
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
88
import { BarChart3, CheckCircle, XCircle, RefreshCw } from 'lucide-react';
99
import { apiService, RequestStats as RequestStatsType } from '../services/api';
10+
import { useRefresh } from '../hooks/useRefreshContext';
1011

1112
interface RequestStatsProps {
1213
className?: string;
@@ -16,6 +17,7 @@ export function RequestStats({ className }: RequestStatsProps) {
1617
const [stats, setStats] = useState<RequestStatsType | null>(null);
1718
const [isLoading, setIsLoading] = useState(false);
1819
const [error, setError] = useState<string | null>(null);
20+
const { onRefresh } = useRefresh();
1921

2022
const fetchStats = async () => {
2123
setIsLoading(true);
@@ -35,6 +37,12 @@ export function RequestStats({ className }: RequestStatsProps) {
3537
fetchStats();
3638
}, []);
3739

40+
// Listen for refresh events from other components
41+
useEffect(() => {
42+
const cleanup = onRefresh(fetchStats);
43+
return cleanup;
44+
}, [onRefresh]);
45+
3846
const formatBytes = (bytes: number): string => {
3947
if (bytes === 0) return '0 B';
4048
const k = 1024;
@@ -125,15 +133,15 @@ export function RequestStats({ className }: RequestStatsProps) {
125133
<div className="space-y-2">
126134
<div className="flex items-center justify-between">
127135
<span className="text-xs text-muted-foreground">Avg Duration</span>
128-
<span className="text-xs font-medium">{stats.avg_duration_ms?.toFixed(1) || 0}ms</span>
136+
<span className="text-xs font-medium">{(stats.avg_duration_us / 1000).toFixed(1)}ms</span>
129137
</div>
130138
<div className="flex items-center justify-between">
131139
<span className="text-xs text-muted-foreground">Avg Upstream</span>
132-
<span className="text-xs font-medium">{stats.avg_upstream_latency_ms?.toFixed(1) || 0}ms</span>
140+
<span className="text-xs font-medium">{(stats.avg_upstream_latency_us / 1000).toFixed(1)}ms</span>
133141
</div>
134142
<div className="flex items-center justify-between">
135143
<span className="text-xs text-muted-foreground">Avg Proxy</span>
136-
<span className="text-xs font-medium">{stats.avg_proxy_overhead_ms?.toFixed(1) || 0}ms</span>
144+
<span className="text-xs font-medium">{(stats.avg_proxy_overhead_us / 1000).toFixed(1)}ms</span>
137145
</div>
138146
</div>
139147

dashboard/src/components/Sidebar.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { ScrollArea } from './ui/scroll-area';
88
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
99
import { History, Trash2, ExternalLink, Server, RefreshCw } from 'lucide-react';
1010
import { useRequestStore } from '../hooks/useRequestStore';
11+
import { useRefresh } from '../hooks/useRefreshContext';
1112
import { apiService, BackendRequestRecord } from '../services/api';
1213
import { formatDistanceToNow } from 'date-fns';
1314
import { HttpMethod } from '../types/api';
@@ -20,6 +21,7 @@ export function Sidebar() {
2021
setBody,
2122
} = useRequestStore();
2223

24+
const { onRefresh } = useRefresh();
2325
const [backendHistory, setBackendHistory] = useState<BackendRequestRecord[]>([]);
2426
const [isLoadingBackend, setIsLoadingBackend] = useState(false);
2527
const [lastRefresh, setLastRefresh] = useState<Date | null>(null);
@@ -53,6 +55,12 @@ export function Sidebar() {
5355
fetchBackendHistory();
5456
}, []);
5557

58+
// Listen for refresh events from other components
59+
useEffect(() => {
60+
const cleanup = onRefresh(fetchBackendHistory);
61+
return cleanup;
62+
}, [onRefresh]);
63+
5664
const loadBackendHistoryItem = (item: BackendRequestRecord) => {
5765
setMethod(item.method as HttpMethod);
5866
setUrl(item.url);
@@ -214,13 +222,13 @@ export function Sidebar() {
214222
<TooltipProvider>
215223
<Tooltip>
216224
<TooltipTrigger asChild>
217-
<span>{item.total_duration_ms}ms</span>
225+
<span>{(item.total_duration_us / 1000).toFixed(1)}ms</span>
218226
</TooltipTrigger>
219227
<TooltipContent>
220228
<div className="text-xs">
221-
<div>Total: {item.total_duration_ms}ms</div>
222-
<div>Upstream: {item.upstream_latency_ms}ms</div>
223-
<div>Proxy: {item.proxy_overhead_ms}ms</div>
229+
<div>Total: {(item.total_duration_us / 1000).toFixed(1)}ms</div>
230+
<div>Upstream: {(item.upstream_latency_us / 1000).toFixed(1)}ms</div>
231+
<div>Proxy: {(item.proxy_overhead_us / 1000).toFixed(1)}ms</div>
224232
</div>
225233
</TooltipContent>
226234
</Tooltip>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use client';
2+
3+
import React, { createContext, useContext, useCallback } from 'react';
4+
5+
interface RefreshContextType {
6+
triggerRefresh: () => void;
7+
onRefresh: (callback: () => void) => () => void;
8+
}
9+
10+
const RefreshContext = createContext<RefreshContextType | undefined>(undefined);
11+
12+
export function RefreshProvider({ children }: { children: React.ReactNode }) {
13+
const callbacks = React.useRef<Set<() => void>>(new Set());
14+
15+
const triggerRefresh = useCallback(() => {
16+
callbacks.current.forEach(callback => callback());
17+
}, []);
18+
19+
const onRefresh = useCallback((callback: () => void) => {
20+
callbacks.current.add(callback);
21+
22+
// Return cleanup function
23+
return () => {
24+
callbacks.current.delete(callback);
25+
};
26+
}, []);
27+
28+
return (
29+
<RefreshContext.Provider value={{ triggerRefresh, onRefresh }}>
30+
{children}
31+
</RefreshContext.Provider>
32+
);
33+
}
34+
35+
export function useRefresh() {
36+
const context = useContext(RefreshContext);
37+
if (context === undefined) {
38+
throw new Error('useRefresh must be used within a RefreshProvider');
39+
}
40+
return context;
41+
}

0 commit comments

Comments
 (0)