mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-09 21:00:58 +00:00
c3fa73d5a0
Replace the ten-small-cards overview with an action bar, four vitals
tiles carrying 72-sample sparklines seeded from /server/history, a
two-series throughput chart, a TCP/UDP connections chart, and a
grouped system strip (uptime xray|os, panel ram|threads, ip
addresses). StatusCard and XrayStatusCard are deleted; every modal
stays reachable from the action bar, the Xray error message moves
into a tooltip on the state pill, and the panel version text keeps
opening the update modal (the dev-channel switch lives there) even
when no update is available. Live values sit beside the
upload/download and tcp/udp legends, a health sentence appears only
when a vital crosses the shared warn/crit thresholds now exported
from models/status, and load average is left to System History.
The sidebar becomes an auto-collapsed 72px icon rail that expands as
an overlay on hover: rail width, brand-row height and menu paddings
are pinned so nothing shifts during the transition, the collapsed-menu
tooltips are disabled, hover state survives the per-page sidebar
remounts (with a matches(':hover') resync), and the manual collapse
trigger is gone.
Sparkline gains rgb()/rgba() support in its fill gradient, a
showLegend prop so pages stop reaching into its internals, and loses
a dependency-less repaint effect that doubled canvas paints. Chart
tooltips show clock time via the new TimeFormatter.formatClock;
accents come from theme tokens instead of status.cpu.color. Verified
by screenshot at 390/800/1150/1280/1400/1600px in light and dark,
en and fa-IR, plus programmatic geometry checks on the sidebar.
Locale files gain 8 keys and lose 9 dead ones across all 13
languages.
98 lines
3.3 KiB
TypeScript
98 lines
3.3 KiB
TypeScript
import { useTranslation } from 'react-i18next';
|
|
import { Card, Tooltip } from 'antd';
|
|
import {
|
|
ClockCircleOutlined,
|
|
DatabaseOutlined,
|
|
EyeInvisibleOutlined,
|
|
EyeOutlined,
|
|
GlobalOutlined,
|
|
} from '@ant-design/icons';
|
|
|
|
import { SizeFormatter, TimeFormatter } from '@/utils';
|
|
import { activateOnKey } from '@/utils/a11y';
|
|
import type { Status } from '@/models/status';
|
|
|
|
interface SystemStripProps {
|
|
status: Status;
|
|
showIp: boolean;
|
|
onToggleIp: () => void;
|
|
}
|
|
|
|
export default function SystemStrip({ status, showIp, onToggleIp }: SystemStripProps) {
|
|
const { t } = useTranslation();
|
|
|
|
return (
|
|
<Card hoverable styles={{ body: { padding: 0 } }}>
|
|
<div className="ov-strip-grid">
|
|
<div className="ov-strip-cell">
|
|
<div className="ov-kicker ov-kicker-icon">
|
|
<ClockCircleOutlined />
|
|
{t('pages.index.uptime')}
|
|
</div>
|
|
<div className="ov-strip-split">
|
|
<div>
|
|
<div className="ov-strip-sub">Xray</div>
|
|
<div className="ov-strip-value">{TimeFormatter.formatSecond(status.appStats.uptime)}</div>
|
|
</div>
|
|
<span className="ov-strip-split-sep" />
|
|
<div>
|
|
<div className="ov-strip-sub">OS</div>
|
|
<div className="ov-strip-value">{TimeFormatter.formatSecond(status.uptime)}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="ov-strip-cell">
|
|
<div className="ov-kicker ov-kicker-icon">
|
|
<DatabaseOutlined />
|
|
{t('pages.index.panel')}
|
|
</div>
|
|
<div className="ov-strip-split">
|
|
<div>
|
|
<div className="ov-strip-sub">{t('pages.index.memory')}</div>
|
|
<div className="ov-strip-value">{SizeFormatter.sizeFormat(status.appStats.mem)}</div>
|
|
</div>
|
|
<span className="ov-strip-split-sep" />
|
|
<div>
|
|
<div className="ov-strip-sub">{t('pages.index.threads')}</div>
|
|
<div className="ov-strip-value">{status.appStats.threads}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="ov-strip-cell">
|
|
<div className="ov-kicker ov-kicker-icon">
|
|
<GlobalOutlined />
|
|
{t('pages.index.ipAddresses')}
|
|
<Tooltip title={t('pages.index.toggleIpVisibility')}>
|
|
{showIp ? (
|
|
<EyeOutlined
|
|
className="ip-toggle-icon"
|
|
role="button"
|
|
tabIndex={0}
|
|
aria-label={t('pages.index.toggleIpVisibility')}
|
|
onClick={onToggleIp}
|
|
onKeyDown={activateOnKey(onToggleIp)}
|
|
/>
|
|
) : (
|
|
<EyeInvisibleOutlined
|
|
className="ip-toggle-icon"
|
|
role="button"
|
|
tabIndex={0}
|
|
aria-label={t('pages.index.toggleIpVisibility')}
|
|
onClick={onToggleIp}
|
|
onKeyDown={activateOnKey(onToggleIp)}
|
|
/>
|
|
)}
|
|
</Tooltip>
|
|
</div>
|
|
<div className={`ov-ip${showIp ? '' : ' ip-hidden'}`}>
|
|
<div className="ov-mono">{status.publicIP.ipv4}</div>
|
|
<div className="ov-mono ov-ip-v6">{status.publicIP.ipv6}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
);
|
|
}
|