- 第22条 L-UI:登录页品牌名、侧栏 brand-text(收起态 L)、抽屉品牌、 usePageTitle 默认标题(HTML 标题)、13 个语言文件里用户可见串中的 3X-UI 字样(更新说明×2、TG 版本行)一并改为 L-UI;仓库/二进制/ 服务名/路径/内部包名不动 - 第18条:formatPanelVersion 对 V/A 系显示版本原样返回(新增单测); PanelUpdateModal 与首页动作条改用 display* 字段判更新与显示 - 第14条:首页运行时间卡片删除 Xray 半边与分隔线,只留 OS - 第21条:节点表列宽 操作190→150/已启用80→64/客户端180→150/ 延迟100→84/心跳120→96、地址列 330;agent 行 IPv4·IPv6 单行 (浅点分隔、沿用可见性 class),传统行不变 - 第20条:agent 行云朵按钮与移动端菜单改用「更新 Agent」文案; NodesPage 对 agent 行用无渠道选择的简单确认(传统行不变); schemas 增 agentUpdateState/Error/Target - 第19条:节点编辑页(agent)拉 /panel/api/agent/install/:id 展示 完整安装命令(master 可编辑、含 token 时完整命令+复制按钮); 无 token 时展示无 token 命令+agent.json 路径提示,不报错不空白 - i18n 新增 4 键(updateAgent/updateAgentConfirmContent/ agentInstallCommand/agentTokenNotStored)补齐全部 13 个语言文件 (zh-CN 中文、其余英文),tsc 干净、panel-version 测试全过、 i18n 失败仍为既有 forwarding 键集问题(第10条)零新增
93 lines
3.1 KiB
TypeScript
93 lines
3.1 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>
|
|
{/* The lite master runs no local xray core, so the uptime card
|
|
shows the OS uptime only (the Xray half always read 0s). */}
|
|
<div>
|
|
<div className="ov-strip-sub">OS</div>
|
|
<div className="ov-strip-value">{TimeFormatter.formatSecond(status.uptime)}</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>
|
|
);
|
|
}
|