import { Activity, Boxes, Clock3, Cpu, Database, Gauge, HardDrive, Radio, Users } from 'lucide-react';
import { useServer } from '../context/server-context';
import { useI18n } from '../lib/i18n';
import { MetricCard, PageHeader, Panel, ResourceState, TableShell, formatBytes, formatDate, formatDuration } from '../components/ui';
import { Sparkline } from '../components/sparkline';
function percent(value?: number): string {
if (value === undefined || !Number.isFinite(value)) return '—';
const normalized = value <= 1 ? value * 100 : value;
return `${normalized.toFixed(1)}%`;
}
export function OverviewPage() {
const { t, locale } = useI18n();
const { status, connection, refresh, lastError } = useServer();
const heapRatio = status?.heapUsedBytes !== undefined && status.heapMaxBytes
? status.heapUsedBytes / status.heapMaxBytes
: undefined;
const tpsTone = status?.tps === undefined ? undefined : status.tps >= 19 ? 'good' : status.tps >= 17 ? 'warn' : 'danger';
const msptTone = status?.mspt?.average === undefined ? undefined : status.mspt.average <= 45 ? 'good' : status.mspt.average <= 50 ? 'warn' : 'danger';
return (
<div className="page page--overview">
<PageHeader
eyebrow={status?.serverName}
title={t('overview.title')}
description={t('overview.description')}
actions={status && <span className="sample-time"><Radio size={14} />{t('overview.sampledAt')}: {formatDate(status.sampledAt, locale)}</span>}
/>
<ResourceState loading={connection === 'connecting' && !status} error={connection === 'offline' ? lastError : undefined} onRetry={() => void refresh()}>
<div className="metric-grid">
<MetricCard label={<><Gauge />{t('overview.tps')}</>} value={status?.tps?.toFixed(2) ?? '—'} tone={tpsTone} detail={status ? `${status.mspt?.jitter?.toFixed(2) ?? '—'} jitter` : undefined} />
<MetricCard label={<><Activity />{t('overview.mspt')}</>} value={status?.mspt?.average?.toFixed(2) ?? '—'} unit="ms" tone={msptTone} detail={status ? `p95 ${status.mspt?.p95?.toFixed(1) ?? '—'} · p99 ${status.mspt?.p99?.toFixed(1) ?? '—'}` : undefined} />
<MetricCard label={<><Users />{t('overview.players')}</>} value={status?.playersOnline ?? '—'} detail={status?.playersMax !== undefined ? `/ ${status.playersMax}` : undefined} />
<MetricCard label={<><Database />{t('overview.heap')}</>} value={formatBytes(status?.heapUsedBytes)} detail={status?.heapMaxBytes ? `/ ${formatBytes(status.heapMaxBytes)} · ${percent(heapRatio)}` : undefined} />
<MetricCard label={<><Cpu />{t('overview.cpu')}</>} value={percent(status?.cpuLoad)} />
<MetricCard label={<><Clock3 />{t('overview.uptime')}</>} value={formatDuration(status?.uptimeSeconds)} />
<MetricCard label={<><HardDrive />{t('overview.disk')}</>} value={formatBytes(status?.diskFreeBytes)} />
<MetricCard label={<><Radio />{t('overview.ping')}</>} value={status?.averagePingMs?.toFixed(0) ?? '—'} unit="ms" />
</div>
<div className="overview-grid">
<Panel title={t('overview.performance')} className="performance-panel">
<p className="panel-description">{t('overview.performanceHint')}</p>
<Sparkline points={status?.samples ?? []} field="mspt" label={t('overview.performance')} />
</Panel>
<Panel title={t('overview.serverIdentity')} className="identity-panel">
<dl className="definition-list">
<div><dt>Minecraft</dt><dd>{status?.minecraftVersion ?? '—'}</dd></div>
<div><dt>Forge</dt><dd>{status?.forgeVersion ?? '—'}</dd></div>
<div><dt>online-mode</dt><dd>{status?.onlineMode === undefined ? '—' : String(status.onlineMode)}</dd></div>
<div><dt>GC pause</dt><dd>{status?.gcPauseMs === undefined ? '—' : `${status.gcPauseMs.toFixed(1)} ms`}</dd></div>
</dl>
</Panel>
</div>
<Panel title={<><Boxes size={18} />{t('overview.dimensions')}</>}>
<ResourceState loading={false} empty={!status?.dimensions?.length}>
<TableShell>
<thead><tr><th>{t('overview.dimension')}</th><th>{t('overview.chunks')}</th><th>{t('overview.entities')}</th></tr></thead>
<tbody>{status?.dimensions?.map((dimension) => (
<tr key={dimension.id}><td><code>{dimension.id}</code></td><td>{dimension.loadedChunks.toLocaleString(locale)}</td><td>{dimension.entities.toLocaleString(locale)}</td></tr>
))}</tbody>
</TableShell>
</ResourceState>
</Panel>
</ResourceState>
</div>
);
}
import { Activity, Boxes, Clock3, Cpu, Database, Gauge, HardDrive, Radio, Users } from 'lucide-react';
import { useServer } from '../context/server-context';
import { useI18n } from '../lib/i18n';
import { MetricCard, PageHeader, Panel, ResourceState, TableShell, formatBytes, formatDate, formatDuration } from '../components/ui';
import { Sparkline } from '../components/sparkline';
function percent(value?: number): string {
if (value === undefined || !Number.isFinite(value)) return '—';
const normalized = value <= 1 ? value * 100 : value;
return `${normalized.toFixed(1)}%`;
}
export function OverviewPage() {
const { t, locale } = useI18n();
const { status, connection, refresh, lastError } = useServer();
const heapRatio = status?.heapUsedBytes !== undefined && status.heapMaxBytes
? status.heapUsedBytes / status.heapMaxBytes
: undefined;
const tpsTone = status?.tps === undefined ? undefined : status.tps >= 19 ? 'good' : status.tps >= 17 ? 'warn' : 'danger';
const msptTone = status?.mspt?.average === undefined ? undefined : status.mspt.average <= 45 ? 'good' : status.mspt.average <= 50 ? 'warn' : 'danger';
return (
<div className="page page--overview">
<PageHeader
eyebrow={status?.serverName}
title={t('overview.title')}
description={t('overview.description')}
actions={status && <span className="sample-time"><Radio size={14} />{t('overview.sampledAt')}: {formatDate(status.sampledAt, locale)}</span>}
/>
<ResourceState loading={connection === 'connecting' && !status} error={connection === 'offline' ? lastError : undefined} onRetry={() => void refresh()}>
<div className="metric-grid">
<MetricCard label={<><Gauge />{t('overview.tps')}</>} value={status?.tps?.toFixed(2) ?? '—'} tone={tpsTone} detail={status ? `${status.mspt?.jitter?.toFixed(2) ?? '—'} jitter` : undefined} />
<MetricCard label={<><Activity />{t('overview.mspt')}</>} value={status?.mspt?.average?.toFixed(2) ?? '—'} unit="ms" tone={msptTone} detail={status ? `p95 ${status.mspt?.p95?.toFixed(1) ?? '—'} · p99 ${status.mspt?.p99?.toFixed(1) ?? '—'}` : undefined} />
<MetricCard label={<><Users />{t('overview.players')}</>} value={status?.playersOnline ?? '—'} detail={status?.playersMax !== undefined ? `/ ${status.playersMax}` : undefined} />
<MetricCard label={<><Database />{t('overview.heap')}</>} value={formatBytes(status?.heapUsedBytes)} detail={status?.heapMaxBytes ? `/ ${formatBytes(status.heapMaxBytes)} · ${percent(heapRatio)}` : undefined} />
<MetricCard label={<><Cpu />{t('overview.cpu')}</>} value={percent(status?.cpuLoad)} />
<MetricCard label={<><Clock3 />{t('overview.uptime')}</>} value={formatDuration(status?.uptimeSeconds)} />
<MetricCard label={<><HardDrive />{t('overview.disk')}</>} value={formatBytes(status?.diskFreeBytes)} />
<MetricCard label={<><Radio />{t('overview.ping')}</>} value={status?.averagePingMs?.toFixed(0) ?? '—'} unit="ms" />
</div>
<div className="overview-grid">
<Panel title={t('overview.performance')} className="performance-panel">
<p className="panel-description">{t('overview.performanceHint')}</p>
<Sparkline points={status?.samples ?? []} field="mspt" label={t('overview.performance')} />
</Panel>
<Panel title={t('overview.serverIdentity')} className="identity-panel">
<dl className="definition-list">
<div><dt>Minecraft</dt><dd>{status?.minecraftVersion ?? '—'}</dd></div>
<div><dt>Forge</dt><dd>{status?.forgeVersion ?? '—'}</dd></div>
<div><dt>online-mode</dt><dd>{status?.onlineMode === undefined ? '—' : String(status.onlineMode)}</dd></div>
<div><dt>GC pause</dt><dd>{status?.gcPauseMs === undefined ? '—' : `${status.gcPauseMs.toFixed(1)} ms`}</dd></div>
</dl>
</Panel>
</div>
<Panel title={<><Boxes size={18} />{t('overview.dimensions')}</>}>
<ResourceState loading={false} empty={!status?.dimensions?.length}>
<TableShell>
<thead><tr><th>{t('overview.dimension')}</th><th>{t('overview.chunks')}</th><th>{t('overview.entities')}</th></tr></thead>
<tbody>{status?.dimensions?.map((dimension) => (
<tr key={dimension.id}><td><code>{dimension.id}</code></td><td>{dimension.loadedChunks.toLocaleString(locale)}</td><td>{dimension.entities.toLocaleString(locale)}</td></tr>
))}</tbody>
</TableShell>
</ResourceState>
</Panel>
</ResourceState>
</div>
);
}