feat(versioning): implement UI and server version tracking with build date display in footer

This commit is contained in:
Supra4E8C
2025-11-20 18:35:22 +08:00
parent 23d8d20dbf
commit 6e0dec4567
7 changed files with 120 additions and 3 deletions

4
app.js
View File

@@ -42,6 +42,9 @@ class CLIProxyManager {
this.managementKey = ''; this.managementKey = '';
this.isConnected = false; this.isConnected = false;
this.isLoggedIn = false; this.isLoggedIn = false;
this.uiVersion = null;
this.serverVersion = null;
this.serverBuildDate = null;
// 配置缓存 - 改为分段缓存 // 配置缓存 - 改为分段缓存
this.configCache = {}; // 改为对象,按配置段缓存 this.configCache = {}; // 改为对象,按配置段缓存
@@ -129,6 +132,7 @@ class CLIProxyManager {
} }
init() { init() {
this.initUiVersion();
this.initializeTheme(); this.initializeTheme();
this.checkLoginStatus(); this.checkLoginStatus();
this.bindEvents(); this.bindEvents();

View File

@@ -508,7 +508,9 @@ const i18n = {
'sidebar.toggle_collapse': '收起侧边栏', 'sidebar.toggle_collapse': '收起侧边栏',
// 页脚 // 页脚
'footer.version': '版本', 'footer.api_version': 'CLI Proxy API 版本',
'footer.build_date': '构建时间',
'footer.version': '管理中心版本',
'footer.author': '作者' 'footer.author': '作者'
}, },
@@ -1013,7 +1015,9 @@ const i18n = {
'sidebar.toggle_collapse': 'Collapse sidebar', 'sidebar.toggle_collapse': 'Collapse sidebar',
// Footer // Footer
'footer.version': 'Version', 'footer.api_version': 'CLI Proxy API Version',
'footer.build_date': 'Build Time',
'footer.version': 'Management UI Version',
'footer.author': 'Author' 'footer.author': 'Author'
} }
}, },

View File

@@ -1003,7 +1003,14 @@
<!-- 版本信息 --> <!-- 版本信息 -->
<footer class="version-footer"> <footer class="version-footer">
<div class="version-info"> <div class="version-info">
<span data-i18n="footer.version">版本</span>: __VERSION__ <span data-i18n="footer.api_version">CLI Proxy API 版本</span>:
<span id="api-version">-</span>
<span class="separator"></span>
<span data-i18n="footer.build_date">构建时间</span>:
<span id="api-build-date">-</span>
<span class="separator"></span>
<span data-i18n="footer.version">管理中心版本</span>:
<span id="ui-version" data-ui-version="__VERSION__">-</span>
<span class="separator"></span> <span class="separator"></span>
<span data-i18n="footer.author">作者</span>: CLI Proxy API Team <span data-i18n="footer.author">作者</span>: CLI Proxy API Team
</div> </div>

View File

@@ -58,6 +58,95 @@ export const connectionModule = {
this.updateLoginConnectionInfo(); this.updateLoginConnectionInfo();
}, },
// 读取并填充管理中心版本号(可能来自构建时注入或占位符)
initUiVersion() {
const uiVersion = this.readUiVersionFromDom();
this.uiVersion = uiVersion;
this.renderVersionInfo();
},
// 从 DOM 获取版本占位符,并处理空值、引号或未替换的占位符
readUiVersionFromDom() {
const el = document.getElementById('ui-version');
if (!el) return null;
const raw = (el.dataset && el.dataset.uiVersion) ? el.dataset.uiVersion : el.textContent;
if (typeof raw !== 'string') return null;
const cleaned = raw.replace(/^"+|"+$/g, '').trim();
if (!cleaned || cleaned === '__VERSION__') {
return null;
}
return cleaned;
},
// 根据响应头更新版本与构建时间
updateVersionFromHeaders(headers) {
if (!headers || typeof headers.get !== 'function') {
return;
}
const version = headers.get('X-CPA-VERSION');
const buildDate = headers.get('X-CPA-BUILD-DATE');
let updated = false;
if (version && version !== this.serverVersion) {
this.serverVersion = version;
updated = true;
}
if (buildDate && buildDate !== this.serverBuildDate) {
this.serverBuildDate = buildDate;
updated = true;
}
if (updated) {
this.renderVersionInfo();
}
},
// 渲染底栏的版本与构建时间
renderVersionInfo() {
const versionEl = document.getElementById('api-version');
const buildDateEl = document.getElementById('api-build-date');
const uiVersionEl = document.getElementById('ui-version');
if (versionEl) {
versionEl.textContent = this.serverVersion || '-';
}
if (buildDateEl) {
buildDateEl.textContent = this.serverBuildDate
? this.formatBuildDate(this.serverBuildDate)
: '-';
}
if (uiVersionEl) {
const domVersion = this.readUiVersionFromDom();
uiVersionEl.textContent = this.uiVersion || domVersion || 'v0.0.0-dev';
}
},
// 清空版本信息(例如登出时)
resetVersionInfo() {
this.serverVersion = null;
this.serverBuildDate = null;
this.renderVersionInfo();
},
// 格式化构建时间,优先使用界面语言对应的本地格式
formatBuildDate(buildDate) {
if (!buildDate) return '-';
const parsed = Date.parse(buildDate);
if (!Number.isNaN(parsed)) {
const locale = i18n?.currentLanguage || undefined;
return new Date(parsed).toLocaleString(locale);
}
return buildDate;
},
// API 请求方法 // API 请求方法
async makeRequest(endpoint, options = {}) { async makeRequest(endpoint, options = {}) {
const url = `${this.apiUrl}${endpoint}`; const url = `${this.apiUrl}${endpoint}`;
@@ -73,6 +162,8 @@ export const connectionModule = {
headers headers
}); });
this.updateVersionFromHeaders(response.headers);
if (!response.ok) { if (!response.ok) {
const errorData = await response.json().catch(() => ({})); const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `HTTP ${response.status}`); throw new Error(errorData.error || `HTTP ${response.status}`);

View File

@@ -732,6 +732,8 @@ export const authFilesModule = {
body: formData body: formData
}); });
this.updateVersionFromHeaders(response.headers);
if (!response.ok) { if (!response.ok) {
let errorMessage = `HTTP ${response.status}`; let errorMessage = `HTTP ${response.status}`;
try { try {
@@ -891,6 +893,8 @@ export const authFilesModule = {
} }
}); });
this.updateVersionFromHeaders(response.headers);
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP ${response.status}`); throw new Error(`HTTP ${response.status}`);
} }
@@ -1029,6 +1033,8 @@ export const authFilesModule = {
body: formData body: formData
}); });
this.updateVersionFromHeaders(response.headers);
if (!response.ok) { if (!response.ok) {
const errorData = await response.json().catch(() => ({})); const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `HTTP ${response.status}`); throw new Error(errorData.error || `HTTP ${response.status}`);

View File

@@ -160,6 +160,8 @@ export const configEditorModule = {
} }
}); });
this.updateVersionFromHeaders(response.headers);
if (!response.ok) { if (!response.ok) {
const errorText = await response.text().catch(() => ''); const errorText = await response.text().catch(() => '');
const message = errorText || `HTTP ${response.status}`; const message = errorText || `HTTP ${response.status}`;
@@ -235,6 +237,8 @@ export const configEditorModule = {
body: yamlText body: yamlText
}); });
this.updateVersionFromHeaders(response.headers);
if (!response.ok) { if (!response.ok) {
const contentType = response.headers.get('content-type') || ''; const contentType = response.headers.get('content-type') || '';
let errorText = ''; let errorText = '';

View File

@@ -100,6 +100,7 @@ export const loginModule = {
this.isConnected = false; this.isConnected = false;
this.clearCache(); this.clearCache();
this.stopStatusUpdateTimer(); this.stopStatusUpdateTimer();
this.resetVersionInfo();
localStorage.removeItem('isLoggedIn'); localStorage.removeItem('isLoggedIn');
secureStorage.removeItem('managementKey'); secureStorage.removeItem('managementKey');