feat(ui): 第22条 改名 L-UI + 第14/21条 + 第18/19/20条前端

- 第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条)零新增
This commit is contained in:
Muse
2026-10-22 13:30:57 +00:00
parent 2596880327
commit d46035708e
25 changed files with 270 additions and 74 deletions
+34 -15
View File
@@ -239,7 +239,7 @@ export default function NodeList({
{
title: t('pages.nodes.actions'),
align: 'center',
width: 190,
width: 150,
render: (_value, record) =>
record.transitive ? (
<Tooltip
@@ -264,13 +264,23 @@ export default function NodeList({
/>
</Tooltip>
{isUpdateEligible(record) && (
<Tooltip title={t('pages.nodes.updatePanel')}>
<Tooltip
title={t(
record.nodeType === 'agent'
? 'pages.nodes.updateAgent'
: 'pages.nodes.updatePanel',
)}
>
<Button
type="text"
size="small"
style={{ fontSize: 16 }}
icon={<CloudDownloadOutlined />}
aria-label={t('pages.nodes.updatePanel')}
aria-label={t(
record.nodeType === 'agent'
? 'pages.nodes.updateAgent'
: 'pages.nodes.updatePanel',
)}
onClick={() => onUpdateNode(record)}
/>
</Tooltip>
@@ -303,7 +313,7 @@ export default function NodeList({
title: t('pages.nodes.enable'),
dataIndex: 'enable',
align: 'center',
width: 80,
width: 64,
render: (_value, record) =>
record.transitive ? (
<span style={{ opacity: 0.4 }}>—</span>
@@ -362,17 +372,20 @@ export default function NodeList({
</span>
),
dataIndex: 'url',
width: 330,
ellipsis: true,
render: (_value, record) => {
if (record.nodeType === 'agent') {
// Agent nodes have no panel to link to: show the host IPs the
// agent reports with its heartbeat as plain text (P13).
// agent reports with its heartbeat as plain text on one line
// (P13, layout per item 21).
const ips = [record.ipv4, record.ipv6].filter(Boolean) as string[];
return (
<span className={showAddress ? 'address-visible' : 'address-hidden'}>
{ips.length > 0
? ips.map((ip) => (
<span key={ip} style={{ display: 'block' }}>
? ips.map((ip, i) => (
<span key={ip}>
{i > 0 && <span style={{ opacity: 0.45 }}> · </span>}
{ip}
</span>
))
@@ -436,10 +449,11 @@ export default function NodeList({
dataIndex: 'panelVersion',
align: 'center',
render: (_value, record) => {
// Agent rows carry an "agent <version>" label, not an upstream
// panel version: the update badge must never light up for them
// (the version comparator falls back to string inequality for
// unparseable versions, which would always differ) (P13).
// Agent rows carry the A-series display version ("A1.0.0"), not
// an upstream panel version: the update badge must never light
// up for them (the version comparator falls back to string
// inequality for unparseable versions, which would always
// differ) (P13, item 18).
const canUpdate =
record.nodeType !== 'agent' &&
isUpdateEligible(record) &&
@@ -474,7 +488,7 @@ export default function NodeList({
{
title: t('clients'),
align: 'center',
width: 180,
width: 150,
render: (_value, record) => (
<Space size={2}>
<Tag className="client-count-tag" style={{ margin: 0, padding: '0 2px' }}>
@@ -527,7 +541,7 @@ export default function NodeList({
title: t('pages.nodes.latency'),
dataIndex: 'latencyMs',
align: 'center',
width: 100,
width: 84,
render: (_value, record) =>
record.latencyMs && record.latencyMs > 0 ? `${record.latencyMs} ms` : '-',
},
@@ -535,7 +549,7 @@ export default function NodeList({
title: t('pages.nodes.lastHeartbeat'),
dataIndex: 'lastHeartbeat',
align: 'center',
width: 120,
width: 96,
render: (_value, record) => relativeTime(record.lastHeartbeat),
},
],
@@ -652,7 +666,12 @@ export default function NodeList({
key: 'update',
label: (
<>
<CloudDownloadOutlined /> {t('pages.nodes.updatePanel')}
<CloudDownloadOutlined />{' '}
{t(
record.nodeType === 'agent'
? 'pages.nodes.updateAgent'
: 'pages.nodes.updatePanel',
)}
</>
),
onClick: () => onUpdateNode(record),