- 第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条)零新增
45 lines
1.9 KiB
TypeScript
45 lines
1.9 KiB
TypeScript
// Mirror of web/service/panel.go isNewerVersion: parse a vMAJOR.MINOR.PATCH tag
|
|
// and report whether `latest` is ahead of `current`. When either side isn't a
|
|
// clean three-part numeric tag, fall back to a normalized string inequality —
|
|
// the same heuristic the Go side uses so the node "update available" badge
|
|
// agrees with what the server would decide.
|
|
function parseVersionParts(version: string): [number, number, number] | null {
|
|
const parts = version.trim().replace(/^v/, '').split('.');
|
|
if (parts.length !== 3) return null;
|
|
const out: number[] = [];
|
|
for (const part of parts) {
|
|
if (!/^\d+$/.test(part)) return null;
|
|
out.push(Number(part));
|
|
}
|
|
return [out[0], out[1], out[2]];
|
|
}
|
|
|
|
// Format a panel version for display. Dev builds report a "dev+<commit>"
|
|
// identity (see config.GetPanelVersion); show those — and any other
|
|
// non-numeric label — verbatim. Semantic versions get a single normalized "v"
|
|
// prefix, so a raw "v3.4.0" tag and a bare "3.4.0" both render as "v3.4.0"
|
|
// instead of doubling up to "vv3.4.0".
|
|
export function formatPanelVersion(version: string | undefined | null): string {
|
|
const v = (version || '').trim();
|
|
if (!v) return '';
|
|
// This fork's display versions (master "V1.0.0", agent "A1.0.0") are
|
|
// shown exactly as written — never re-prefixed or re-cased.
|
|
if (/^[VA]\d+\.\d+\.\d+$/.test(v)) return v;
|
|
const normalized = v.replace(/^v/i, '');
|
|
return /^\d/.test(normalized) ? `v${normalized}` : v;
|
|
}
|
|
|
|
export function isPanelUpdateAvailable(latest: string, current: string): boolean {
|
|
if (!latest || !current) return false;
|
|
const a = parseVersionParts(latest);
|
|
const b = parseVersionParts(current);
|
|
if (!a || !b) {
|
|
return latest.trim().replace(/^v/, '') !== current.trim().replace(/^v/, '');
|
|
}
|
|
for (let i = 0; i < 3; i++) {
|
|
if (a[i] > b[i]) return true;
|
|
if (a[i] < b[i]) return false;
|
|
}
|
|
return false;
|
|
}
|