feat: v2 dashboard — React + Vite parallel implementation
New modern dashboard at /v2 running alongside the existing UI at /. Same backend, same APIs, same WebSocket — zero backend changes. Stack: React 19 + Vite + TypeScript + Recharts Source: frontend/ — build output: static/v2/ Phase 1 delivers: - Character overview cards in a responsive CSS Grid - Live HP/Stamina/Mana bars via WebSocket vitals - Kills/hr, total kills, deaths, session uptime - VTank state badge (Combat/Nav/Idle) - Location coordinates - Click to expand: combat stats, prismatic count, CPU/RAM - Global stats header: active chars, total kills, total rares, server health - WebSocket hook with auto-reconnect - HTTP poll fallback for initial load + server health - Mobile responsive (single column on narrow screens) - Dark theme matching the MosswartOverlord palette Build: cd frontend && npm run build Access: /v2 (served by existing NoCacheStaticFiles mount) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ee30ad2636
commit
e58c05c895
24 changed files with 3213 additions and 0 deletions
15
frontend/src/api/client.ts
Normal file
15
frontend/src/api/client.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// In production the browser hits /api/* and Nginx strips the prefix.
|
||||
// In dev, Vite's proxy does the same stripping.
|
||||
// So we always use /api/ as prefix — works both environments.
|
||||
const API_BASE = '/api';
|
||||
|
||||
export async function apiFetch<T>(path: string): Promise<T> {
|
||||
const res = await fetch(`${API_BASE}${path}`, { credentials: 'include' });
|
||||
if (!res.ok) throw new Error(`API ${path}: ${res.status}`);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export function wsUrl(): string {
|
||||
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
return `${proto}//${location.host}/api/ws/live`;
|
||||
}
|
||||
22
frontend/src/api/endpoints.ts
Normal file
22
frontend/src/api/endpoints.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { apiFetch } from './client';
|
||||
import type { TelemetrySnapshot, CombatStatsMessage, ServerHealth } from '../types';
|
||||
|
||||
interface LiveResponse {
|
||||
players: TelemetrySnapshot[];
|
||||
}
|
||||
|
||||
interface CombatStatsResponse {
|
||||
stats: CombatStatsMessage[];
|
||||
}
|
||||
|
||||
interface CountResponse {
|
||||
total_rares?: number;
|
||||
total_kills?: number;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
export const getLive = () => apiFetch<LiveResponse>('/live');
|
||||
export const getCombatStats = () => apiFetch<CombatStatsResponse>('/combat-stats');
|
||||
export const getServerHealth = () => apiFetch<ServerHealth>('/server-health');
|
||||
export const getTotalRares = () => apiFetch<CountResponse>('/total-rares');
|
||||
export const getTotalKills = () => apiFetch<CountResponse>('/total-kills');
|
||||
Loading…
Add table
Add a link
Reference in a new issue