Files
foxhunt/web-dashboard/src/pages/RiskDashboard.tsx
jgrusewski 84877e5146 feat: add auth gRPC service + migrate dashboard from REST/WS to grpc-web
Backend (Rust):
- Create proto/auth.proto with Login and RefreshToken RPCs
- Implement AuthGrpcService in services/api with JWT issuance
- Register as unauthenticated service in main.rs (pre-auth)
- Update JWT issuer from foxhunt-api-gateway to foxhunt-api

Dashboard (TypeScript):
- Install @connectrpc/connect-web + @bufbuild/protobuf
- Generate TypeScript proto clients via buf (src/gen/)
- Create grpc.ts transport with JWT interceptor
- Rewrite all useApi hooks to typed gRPC calls
- Replace WebSocket with useGrpcStream server-streaming hooks
- Migrate all 6 pages + 6 components to grpc-web
- Delete api.ts, websocket.ts, useWebSocket.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:05:18 +01:00

113 lines
4.6 KiB
TypeScript

import { ComponentErrorBoundary } from '../components/common/ComponentErrorBoundary';
import { RiskGauge } from '../components/risk/RiskGauge';
import { DrawdownChart } from '../components/risk/DrawdownChart';
import { EmergencyControls } from '../components/risk/EmergencyControls';
import { useRiskMetrics } from '../hooks/useApi';
import { useRiskAlertsStream } from '../hooks/useGrpcStream';
import { RiskAlertSeverity } from '../gen/risk_pb';
export function RiskDashboard() {
const riskMetrics = useRiskMetrics();
const { messages: alerts } = useRiskAlertsStream();
const metrics = riskMetrics.data?.metrics;
return (
<div className="space-y-4">
{riskMetrics.isError && (
<div className="px-3 py-2 rounded border border-[var(--color-red)] bg-red-500/10 text-sm text-[var(--color-red)] flex items-center justify-between">
<span>Failed to load risk metrics: {riskMetrics.error.message}</span>
<button onClick={() => riskMetrics.refetch()} className="text-xs underline">Retry</button>
</div>
)}
{/* Top row: Risk gauges */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<div className="h-48 rounded border border-[var(--color-border)] bg-[var(--color-bg-card)]">
<ComponentErrorBoundary name="VaR Gauge">
<RiskGauge
label="VaR Utilization"
value={metrics?.portfolioVar1d ?? 0}
/>
</ComponentErrorBoundary>
</div>
<div className="h-48 rounded border border-[var(--color-border)] bg-[var(--color-bg-card)]">
<ComponentErrorBoundary name="Volatility Gauge">
<RiskGauge
label="Volatility"
value={metrics?.volatility ?? 0}
/>
</ComponentErrorBoundary>
</div>
<div className="h-48 rounded border border-[var(--color-border)] bg-[var(--color-bg-card)]">
<ComponentErrorBoundary name="Drawdown Gauge">
<RiskGauge
label="Drawdown"
value={Math.abs(metrics?.currentDrawdown ?? 0)}
thresholds={{ warn: 5, danger: 10 }}
max={20}
/>
</ComponentErrorBoundary>
</div>
</div>
{/* Middle: Drawdown chart */}
<div className="h-64 rounded border border-[var(--color-border)] bg-[var(--color-bg-card)]">
<ComponentErrorBoundary name="Drawdown Chart">
<DrawdownChart />
</ComponentErrorBoundary>
</div>
{/* Bottom row: Alerts + Emergency Controls */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="rounded border border-[var(--color-border)] bg-[var(--color-bg-card)]">
<div className="px-3 py-1.5 border-b border-[var(--color-border)]">
<span className="text-sm font-medium">Risk Alerts</span>
</div>
<div className="max-h-64 overflow-auto">
{alerts.length === 0 ? (
<div className="p-4 text-sm text-[var(--color-text-secondary)] text-center">
No active alerts
</div>
) : (
<div className="divide-y divide-[var(--color-border)]">
{alerts.slice(-20).reverse().map((alert, i) => {
const severityLabel =
alert.severity === RiskAlertSeverity.CRITICAL
? 'CRITICAL'
: alert.severity === RiskAlertSeverity.EMERGENCY
? 'EMERGENCY'
: alert.severity === RiskAlertSeverity.WARNING
? 'WARNING'
: 'INFO';
const isCritical =
alert.severity === RiskAlertSeverity.CRITICAL ||
alert.severity === RiskAlertSeverity.EMERGENCY;
return (
<div key={`alert-${alerts.length - i}`} className="px-3 py-2 text-xs">
<span
className={
isCritical
? 'text-[var(--color-red)]'
: 'text-[var(--color-yellow)]'
}
>
[{severityLabel}]
</span>{' '}
{alert.message}
</div>
);
})}
</div>
)}
</div>
</div>
<div className="rounded border border-[var(--color-border)] bg-[var(--color-bg-card)]">
<EmergencyControls />
</div>
</div>
</div>
);
}