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>
54 lines
1.6 KiB
TypeScript
54 lines
1.6 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { DraggableWindow } from './DraggableWindow';
|
|
|
|
interface Props { id: string; charName: string; zIndex: number; }
|
|
|
|
const PANELS = [
|
|
{ title: 'Kills per Hour', id: 1 },
|
|
{ title: 'Memory (MB)', id: 2 },
|
|
{ title: 'CPU (%)', id: 3 },
|
|
{ title: 'Mem Handles', id: 4 },
|
|
];
|
|
|
|
const TIME_RANGES = [
|
|
{ label: '1H', value: 'now-1h' },
|
|
{ label: '6H', value: 'now-6h' },
|
|
{ label: '24H', value: 'now-24h' },
|
|
{ label: '7D', value: 'now-7d' },
|
|
];
|
|
|
|
export const StatsWindow: React.FC<Props> = ({ id, charName, zIndex }) => {
|
|
const [timeRange, setTimeRange] = useState('now-24h');
|
|
|
|
const iframeUrl = (panelId: number) =>
|
|
`/grafana/d-solo/dereth-tracker/dereth-tracker-dashboard?panelId=${panelId}&var-character=${encodeURIComponent(charName)}&from=${timeRange}&to=now&theme=light`;
|
|
|
|
return (
|
|
<DraggableWindow id={id} title={`Stats: ${charName}`} zIndex={zIndex} width={750} height={480}>
|
|
<div className="ml-stats-controls">
|
|
{TIME_RANGES.map(r => (
|
|
<button
|
|
key={r.value}
|
|
className={`ml-stats-range-btn ${timeRange === r.value ? 'active' : ''}`}
|
|
onClick={() => setTimeRange(r.value)}
|
|
>
|
|
{r.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
<div className="ml-stats-grid">
|
|
{PANELS.map(p => (
|
|
<div key={p.id} className="ml-stats-panel">
|
|
<iframe
|
|
src={iframeUrl(p.id)}
|
|
width="100%"
|
|
height="100%"
|
|
frameBorder="0"
|
|
title={p.title}
|
|
/>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</DraggableWindow>
|
|
);
|
|
};
|