import { Link } from 'react-router-dom';
import type { CurrentUser, ResourceSample } from '@reforger-panel/shared';
import {
useConfiguration,
useResourceHistory,
useServerResources,
useServers,
} from '../api/hooks.js';
import { formatBytes, formatDuration } from '../lib/format.js';
import { Card, Spinner } from '../components/ui.js';
import { TimeSeriesChart } from '../components/charts.js';
import {
ConfigSummaryRows,
CurrentPlayersCard,
OpsHealthCard,
RecentActivityCard,
} from '../components/widgets.js';
export function OverviewPage({ user }: { user: CurrentUser }) {
const { data: serversData, isLoading } = useServers();
const server = serversData?.servers[0];
if (isLoading) return ;
if (!server) {
return (
No servers found. Run npm run db:seed{' '}
to create the training server.
);
}
return ;
}
function seriesOf(
samples: ResourceSample[] | undefined,
pick: (s: ResourceSample) => number,
): { t: number; v: number }[] {
return (samples ?? []).map((s) => ({ t: s.t, v: pick(s) }));
}
function Dashboard({ user, slug }: { user: CurrentUser; slug: string }) {
const { data: serversData } = useServers();
const server = serversData?.servers.find((s) => s.slug === slug);
const { data: resources } = useServerResources(slug);
const { data: config } = useConfiguration(slug);
const { data: history } = useResourceHistory(slug);
if (!server) return null;
const installedMods = config?.config.mods ?? [];
const samples = history?.samples;
const memoryLimit = resources?.memoryLimitBytes ?? samples?.at(-1)?.memoryLimitBytes ?? null;
const cpuLimit = resources?.cpuLimitPercent ?? samples?.at(-1)?.cpuLimitPercent ?? 100;
const diskUsed = resources?.diskBytes ?? null;
const diskLimit = resources?.diskLimitBytes ?? null;
const diskPercent = diskUsed !== null && diskLimit ? (diskUsed / diskLimit) * 100 : null;
return (
{resources ? `${resources.cpuPercent.toFixed(0)}%` : '—'}
{cpuLimit && cpuLimit !== 100 ? ` / ${cpuLimit}%` : ''}
s.cpuPercent),
color: 'var(--color-accent-400)',
},
]}
/>
{resources ? formatBytes(resources.memoryBytes) : '—'}
{memoryLimit ? ` / ${formatBytes(memoryLimit)}` : ''}
s.memoryBytes),
color: '#7dd3fc',
},
]}
/>
↓ {formatBytes(samples?.at(-1)?.networkRxRate ?? 0)}/s
↑ {formatBytes(samples?.at(-1)?.networkTxRate ?? 0)}/s
up{' '}
{resources && resources.uptimeMs > 0
? formatDuration(resources.uptimeMs / 1000)
: '—'}
s.networkRxRate),
color: 'var(--color-accent-400)',
label: 'rx',
},
{
points: seriesOf(samples, (s) => s.networkTxRate),
color: 'var(--color-warn-400)',
fill: false,
label: 'tx',
},
]}
/>
{diskUsed !== null ? formatBytes(diskUsed) : '—'}
{diskLimit ? ` / ${formatBytes(diskLimit)}` : ''}
{diskPercent !== null && (
90
? 'var(--color-danger-400)'
: diskPercent > 75
? 'var(--color-warn-400)'
: '#a3e635',
}}
/>
{diskPercent.toFixed(1)}% used
)}
View configuration
}
>
{config ? : }
Manage
}
>
{installedMods.length === 0 ? (
The server runs vanilla (no mods).
) : (
{installedMods.length} mod{installedMods.length === 1 ? '' : 's'} in config.json
{installedMods.slice(0, 5).map((mod) => (
-
{mod.name ?? mod.modId}
))}
{installedMods.length > 5 && (
- + {installedMods.length - 5} more
)}
)}
);
}