fix(v2): comprehensive bug fix round — all reported issues

1. Server stats: now shows player count, latency (rounded), uptime hours
2. Rares/Kills counters: fixed API response fields (all_time/total)
3. Chat send: wired socket.send with v1 envelope { player_name, command }
4. Stats button: opens Grafana iframe grid (4 panels, time range selector)
5. Char button: opens character window with attributes/skills/vitals from
   /character-stats/{name} API, structured display with sections
6. Inventory button: full inventory window with equipment table (material,
   set, imbue, AL, dmg, work, tink) + pack contents pill grid + filter
7. Radar button: opens radar window, sends start/stop commands via socket
8. Sidebar links: added Inventory Search, Suitbuilder, Player Debug
9. Color palette: expanded from 30 to 60 distinct colors matching v1
10. Window types properly routed: stats- prefix → Grafana, char- → character
    data, inv- → inventory, radar- → radar with socket commands

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-12 18:31:06 +02:00
parent de7b547349
commit b77450b6eb
19 changed files with 529 additions and 223 deletions

View file

@ -13,6 +13,7 @@ export interface DashboardState {
totalKills: number;
recentRares: RareMessage[];
chatMessages: Map<string, Array<{ text: string; color?: number; timestamp: string }>>;
socketRef: React.RefObject<WebSocket | null>;
}
export function useLiveData(): DashboardState {
@ -63,7 +64,7 @@ export function useLiveData(): DashboardState {
}
}, [updateChar]);
useWebSocket(handleWS);
const socketRef = useWebSocket(handleWS);
// HTTP polls as fallback/initial load
useEffect(() => {
@ -131,8 +132,8 @@ export function useLiveData(): DashboardState {
const fetch = async () => {
try {
const [rares, kills] = await Promise.all([getTotalRares(), getTotalKills()]);
setTotalRares(rares.total_rares ?? rares.count ?? 0);
setTotalKills(kills.total_kills ?? kills.count ?? 0);
setTotalRares((rares as any).all_time ?? 0);
setTotalKills((kills as any).total ?? 0);
} catch { /* ignore */ }
};
fetch();
@ -140,5 +141,5 @@ export function useLiveData(): DashboardState {
return () => clearInterval(id);
}, []);
return { characters, serverHealth, totalRares, totalKills, recentRares, chatMessages };
return { characters, serverHealth, totalRares, totalKills, recentRares, chatMessages, socketRef };
}

View file

@ -1,11 +1,25 @@
import { useRef, useCallback } from 'react';
// Matches v1 script.js PALETTE — 30 accessible colors
// Matches v1 script.js PALETTE — 60 distinct high-contrast colors
const PALETTE = [
'#1f77b4','#ff7f0e','#2ca02c','#d62728','#9467bd','#8c564b','#e377c2','#7f7f7f',
'#bcbd22','#17becf','#aec7e8','#ffbb78','#98df8a','#ff9896','#c5b0d5','#c49c94',
'#f7b6d2','#c7c7c7','#dbdb8d','#9edae5','#393b79','#637939','#8c6d31','#843c39',
'#7b4173','#5254a3','#6b6ecf','#9c9ede','#d6616b','#ce6dbd',
// Original colorblind-friendly (10)
'#1f77b4','#ff7f0e','#2ca02c','#d62728','#9467bd',
'#8c564b','#e377c2','#7f7f7f','#bcbd22','#17becf',
// Extended high-contrast (10)
'#ff4444','#44ff44','#4444ff','#ffff44','#ff44ff',
'#44ffff','#ff8844','#88ff44','#4488ff','#ff4488',
// Darker variants (10)
'#cc3333','#33cc33','#3333cc','#cccc33','#cc33cc',
'#33cccc','#cc6633','#66cc33','#3366cc','#cc3366',
// Brighter variants (10)
'#ff6666','#66ff66','#6666ff','#ffff66','#ff66ff',
'#66ffff','#ffaa66','#aaff66','#66aaff','#ff66aa',
// Additional distinct (10)
'#990099','#009900','#000099','#990000','#009999',
'#999900','#aa5500','#55aa00','#0055aa','#aa0055',
// Light pastels (10)
'#ffaaaa','#aaffaa','#aaaaff','#ffffaa','#ffaaff',
'#aaffff','#ffccaa','#ccffaa','#aaccff','#ffaacc',
];
function hashColor(name: string): string {

View file

@ -4,7 +4,7 @@ import type { WSMessage } from '../types';
type MessageHandler = (msg: WSMessage) => void;
export function useWebSocket(onMessage: MessageHandler) {
export function useWebSocket(onMessage: MessageHandler): React.RefObject<WebSocket | null> {
const wsRef = useRef<WebSocket | null>(null);
const reconnectTimer = useRef<number>(0);
const onMessageRef = useRef(onMessage);
@ -41,4 +41,6 @@ export function useWebSocket(onMessage: MessageHandler) {
wsRef.current = null;
};
}, [connect]);
return wsRef;
}