XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEServerManager

【Java】我的世界XFE服务器管理器

公开
关注 0 Fork 0 Star 0
UTF-8
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>
  );
}