diff --git a/web-dashboard/src/lib/api.ts b/web-dashboard/src/lib/api.ts index 9ec16ec78..c5a1c48e1 100644 --- a/web-dashboard/src/lib/api.ts +++ b/web-dashboard/src/lib/api.ts @@ -1,6 +1,8 @@ +import { clearTokens } from './auth'; + const API_BASE = '/api'; -/** Fetch wrapper that injects JWT Authorization header */ +/** Fetch wrapper that injects JWT Authorization header and handles 401 auto-logout */ async function apiFetch( path: string, options: RequestInit = {}, @@ -22,6 +24,13 @@ async function apiFetch( }); if (!res.ok) { + // Auto-logout on 401 (expired/invalid token) + if (res.status === 401) { + clearTokens(); + window.location.href = '/login'; + throw new Error('Session expired. Please log in again.'); + } + const body = await res.json().catch(() => ({ error: res.statusText })); throw new Error(body.error ?? `API error: ${res.status}`); } diff --git a/web-dashboard/src/lib/websocket.ts b/web-dashboard/src/lib/websocket.ts index 765b7efbc..df5abdd88 100644 --- a/web-dashboard/src/lib/websocket.ts +++ b/web-dashboard/src/lib/websocket.ts @@ -99,11 +99,12 @@ export class WsManager { private scheduleReconnect(): void { if (this.reconnectTimer) return; + const delay = this.backoff; + this.backoff = Math.min(this.backoff * 2, this.maxBackoff); this.reconnectTimer = setTimeout(() => { this.reconnectTimer = null; this.connect(); - this.backoff = Math.min(this.backoff * 2, this.maxBackoff); - }, this.backoff); + }, delay); } } diff --git a/web-dashboard/src/pages/MLDashboard.tsx b/web-dashboard/src/pages/MLDashboard.tsx index 1ff94418f..9d6155ff9 100644 --- a/web-dashboard/src/pages/MLDashboard.tsx +++ b/web-dashboard/src/pages/MLDashboard.tsx @@ -2,8 +2,16 @@ import { ModelCard } from '../components/ml/ModelCard'; import { EnsemblePanel } from '../components/ml/EnsemblePanel'; import { TrainingProgress } from '../components/ml/TrainingProgress'; import { useApiQuery } from '../hooks/useApi'; +import { useWebSocketTopic } from '../hooks/useWebSocket'; import type { MlPrediction, TrainingJob } from '../lib/types'; +interface RegimeData { + regime?: string; + volatility?: string; + confidence?: number; + duration?: string; +} + const MODELS = ['DQN', 'PPO', 'TFT', 'Mamba2'] as const; export function MLDashboard() { @@ -19,10 +27,24 @@ export function MLDashboard() { { refetchInterval: 5000 }, ); + const { messages: metricsMessages } = useWebSocketTopic('metrics'); + + // Extract regime data from most recent metrics message + const latestMetrics = metricsMessages.length > 0 + ? (metricsMessages.at(-1) as { data?: RegimeData })?.data + : undefined; + const preds = predictions.data ?? []; return (
+ {(predictions.isError || trainingJobs.isError) && ( +
+ Failed to load {predictions.isError ? 'predictions' : 'training jobs'}: {(predictions.error ?? trainingJobs.error)?.message} + +
+ )} + {/* Model Cards */}
{MODELS.map((model) => { @@ -55,19 +77,19 @@ export function MLDashboard() {
Current Regime - Trending + {latestMetrics?.regime ?? '--'}
Volatility - Medium + {latestMetrics?.volatility ?? '--'}
Regime Confidence - 72.3% + {latestMetrics?.confidence !== undefined ? `${(latestMetrics.confidence * 100).toFixed(1)}%` : '--'}
Regime Duration - 4h 23m + {latestMetrics?.duration ?? '--'}
diff --git a/web-dashboard/src/pages/PerformanceDashboard.tsx b/web-dashboard/src/pages/PerformanceDashboard.tsx index 9ce7565b9..8e6993e96 100644 --- a/web-dashboard/src/pages/PerformanceDashboard.tsx +++ b/web-dashboard/src/pages/PerformanceDashboard.tsx @@ -25,6 +25,13 @@ export function PerformanceDashboard() { return (
+ {(metrics.isError || pnlData.isError) && ( +
+ Failed to load {metrics.isError ? 'metrics' : 'P&L data'}: {(metrics.error ?? pnlData.error)?.message} + +
+ )} + {/* Top: Metric cards */}
diff --git a/web-dashboard/src/pages/RiskDashboard.tsx b/web-dashboard/src/pages/RiskDashboard.tsx index 83d1a86b1..4a75847f8 100644 --- a/web-dashboard/src/pages/RiskDashboard.tsx +++ b/web-dashboard/src/pages/RiskDashboard.tsx @@ -16,6 +16,13 @@ export function RiskDashboard() { return (
+ {riskMetrics.isError && ( +
+ Failed to load risk metrics: {riskMetrics.error.message} + +
+ )} + {/* Top row: Risk gauges */}
diff --git a/web-dashboard/src/pages/TradingDashboard.tsx b/web-dashboard/src/pages/TradingDashboard.tsx index 998b70954..68cceda41 100644 --- a/web-dashboard/src/pages/TradingDashboard.tsx +++ b/web-dashboard/src/pages/TradingDashboard.tsx @@ -38,6 +38,13 @@ export function TradingDashboard() { return (
+ {(positions.isError || orders.isError) && ( +
+ Failed to load {positions.isError ? 'positions' : 'orders'}: {(positions.error ?? orders.error)?.message} + +
+ )} + {/* Top row: Chart + Order Book */}