mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-03 11:20:50 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b9abdf9b1 | ||
|
|
a208a484ff |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -22,4 +22,6 @@ package-lock.json
|
|||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
CLAUDE.md
|
CLAUDE.md
|
||||||
AGENTS.md
|
.claude
|
||||||
|
AGENTS.md
|
||||||
|
.serena
|
||||||
297
app.js
297
app.js
@@ -26,6 +26,18 @@ class CLIProxyManager {
|
|||||||
// 主题管理
|
// 主题管理
|
||||||
this.currentTheme = 'light';
|
this.currentTheme = 'light';
|
||||||
|
|
||||||
|
// 配置文件编辑器状态
|
||||||
|
this.configYamlCache = '';
|
||||||
|
this.isConfigEditorDirty = false;
|
||||||
|
this.configEditorElements = {
|
||||||
|
textarea: null,
|
||||||
|
editorInstance: null,
|
||||||
|
saveBtn: null,
|
||||||
|
reloadBtn: null,
|
||||||
|
statusEl: null
|
||||||
|
};
|
||||||
|
this.lastConfigFetchUrl = null;
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +125,8 @@ class CLIProxyManager {
|
|||||||
this.setupNavigation();
|
this.setupNavigation();
|
||||||
this.setupLanguageSwitcher();
|
this.setupLanguageSwitcher();
|
||||||
this.setupThemeSwitcher();
|
this.setupThemeSwitcher();
|
||||||
|
this.setupConfigEditor();
|
||||||
|
this.updateConfigEditorAvailability();
|
||||||
// loadSettings 将在登录成功后调用
|
// loadSettings 将在登录成功后调用
|
||||||
this.updateLoginConnectionInfo();
|
this.updateLoginConnectionInfo();
|
||||||
// 检查主机名,如果不是 localhost 或 127.0.0.1,则隐藏 OAuth 登录框
|
// 检查主机名,如果不是 localhost 或 127.0.0.1,则隐藏 OAuth 登录框
|
||||||
@@ -840,6 +854,9 @@ class CLIProxyManager {
|
|||||||
// 如果点击的是日志查看页面,自动加载日志
|
// 如果点击的是日志查看页面,自动加载日志
|
||||||
if (sectionId === 'logs') {
|
if (sectionId === 'logs') {
|
||||||
this.refreshLogs(false);
|
this.refreshLogs(false);
|
||||||
|
} else if (sectionId === 'config-management') {
|
||||||
|
this.loadConfigFileEditor();
|
||||||
|
this.refreshConfigEditor();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -871,6 +888,272 @@ class CLIProxyManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化配置文件编辑器
|
||||||
|
setupConfigEditor() {
|
||||||
|
const textarea = document.getElementById('config-editor');
|
||||||
|
const saveBtn = document.getElementById('config-save-btn');
|
||||||
|
const reloadBtn = document.getElementById('config-reload-btn');
|
||||||
|
const statusEl = document.getElementById('config-editor-status');
|
||||||
|
|
||||||
|
this.configEditorElements = {
|
||||||
|
textarea,
|
||||||
|
editorInstance: null,
|
||||||
|
saveBtn,
|
||||||
|
reloadBtn,
|
||||||
|
statusEl
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!textarea || !saveBtn || !reloadBtn || !statusEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.CodeMirror) {
|
||||||
|
const editorInstance = window.CodeMirror.fromTextArea(textarea, {
|
||||||
|
mode: 'yaml',
|
||||||
|
theme: 'default',
|
||||||
|
lineNumbers: true,
|
||||||
|
indentUnit: 2,
|
||||||
|
tabSize: 2,
|
||||||
|
lineWrapping: true,
|
||||||
|
autoCloseBrackets: true,
|
||||||
|
extraKeys: {
|
||||||
|
'Ctrl-/': 'toggleComment',
|
||||||
|
'Cmd-/': 'toggleComment'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
editorInstance.setSize('100%', '100%');
|
||||||
|
editorInstance.on('change', () => {
|
||||||
|
this.isConfigEditorDirty = true;
|
||||||
|
this.updateConfigEditorStatus('info', i18n.t('config_management.status_dirty'));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.configEditorElements.editorInstance = editorInstance;
|
||||||
|
} else {
|
||||||
|
textarea.addEventListener('input', () => {
|
||||||
|
this.isConfigEditorDirty = true;
|
||||||
|
this.updateConfigEditorStatus('info', i18n.t('config_management.status_dirty'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
saveBtn.addEventListener('click', () => this.saveConfigFile());
|
||||||
|
reloadBtn.addEventListener('click', () => this.loadConfigFileEditor(true));
|
||||||
|
|
||||||
|
this.refreshConfigEditor();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新配置编辑器可用状态
|
||||||
|
updateConfigEditorAvailability() {
|
||||||
|
const { textarea, editorInstance, saveBtn, reloadBtn } = this.configEditorElements;
|
||||||
|
if ((!textarea && !editorInstance) || !saveBtn || !reloadBtn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const disabled = !this.isConnected;
|
||||||
|
if (editorInstance) {
|
||||||
|
editorInstance.setOption('readOnly', disabled ? 'nocursor' : false);
|
||||||
|
const wrapper = editorInstance.getWrapperElement();
|
||||||
|
if (wrapper) {
|
||||||
|
wrapper.classList.toggle('cm-readonly', disabled);
|
||||||
|
}
|
||||||
|
} else if (textarea) {
|
||||||
|
textarea.disabled = disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveBtn.disabled = disabled;
|
||||||
|
reloadBtn.disabled = disabled;
|
||||||
|
|
||||||
|
if (disabled) {
|
||||||
|
this.updateConfigEditorStatus('info', i18n.t('config_management.status_disconnected'));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refreshConfigEditor();
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshConfigEditor() {
|
||||||
|
const instance = this.configEditorElements && this.configEditorElements.editorInstance;
|
||||||
|
if (instance && typeof instance.refresh === 'function') {
|
||||||
|
setTimeout(() => instance.refresh(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新配置编辑器状态显示
|
||||||
|
updateConfigEditorStatus(type, message) {
|
||||||
|
const statusEl = (this.configEditorElements && this.configEditorElements.statusEl) || document.getElementById('config-editor-status');
|
||||||
|
if (!statusEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
statusEl.textContent = message;
|
||||||
|
statusEl.classList.remove('success', 'error');
|
||||||
|
|
||||||
|
if (type === 'success') {
|
||||||
|
statusEl.classList.add('success');
|
||||||
|
} else if (type === 'error') {
|
||||||
|
statusEl.classList.add('error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载配置文件内容
|
||||||
|
async loadConfigFileEditor(forceRefresh = false) {
|
||||||
|
const { textarea, editorInstance, reloadBtn } = this.configEditorElements;
|
||||||
|
if (!textarea && !editorInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isConnected) {
|
||||||
|
this.updateConfigEditorStatus('info', i18n.t('config_management.status_disconnected'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reloadBtn) {
|
||||||
|
reloadBtn.disabled = true;
|
||||||
|
}
|
||||||
|
this.updateConfigEditorStatus('info', i18n.t('config_management.status_loading'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const yamlText = await this.fetchConfigFile(forceRefresh);
|
||||||
|
|
||||||
|
if (editorInstance) {
|
||||||
|
editorInstance.setValue(yamlText || '');
|
||||||
|
if (typeof editorInstance.markClean === 'function') {
|
||||||
|
editorInstance.markClean();
|
||||||
|
}
|
||||||
|
} else if (textarea) {
|
||||||
|
textarea.value = yamlText || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isConfigEditorDirty = false;
|
||||||
|
this.updateConfigEditorStatus('success', i18n.t('config_management.status_loaded'));
|
||||||
|
this.refreshConfigEditor();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载配置文件失败:', error);
|
||||||
|
this.updateConfigEditorStatus('error', `${i18n.t('config_management.status_load_failed')}: ${error.message}`);
|
||||||
|
} finally {
|
||||||
|
if (reloadBtn) {
|
||||||
|
reloadBtn.disabled = !this.isConnected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取配置文件内容
|
||||||
|
async fetchConfigFile(forceRefresh = false) {
|
||||||
|
if (!forceRefresh && this.configYamlCache) {
|
||||||
|
return this.configYamlCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestUrl = '/config.yaml';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${this.apiUrl}${requestUrl}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${this.managementKey}`,
|
||||||
|
'Accept': 'application/yaml'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text().catch(() => '');
|
||||||
|
const message = errorText || `HTTP ${response.status}`;
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = response.headers.get('content-type') || '';
|
||||||
|
if (!/yaml/i.test(contentType)) {
|
||||||
|
throw new Error(i18n.t('config_management.error_yaml_not_supported'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = await response.text();
|
||||||
|
this.lastConfigFetchUrl = requestUrl;
|
||||||
|
this.configYamlCache = text;
|
||||||
|
return text;
|
||||||
|
} catch (error) {
|
||||||
|
throw error instanceof Error ? error : new Error(String(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存配置文件
|
||||||
|
async saveConfigFile() {
|
||||||
|
const { textarea, editorInstance, saveBtn, reloadBtn } = this.configEditorElements;
|
||||||
|
if ((!textarea && !editorInstance) || !saveBtn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isConnected) {
|
||||||
|
this.updateConfigEditorStatus('error', i18n.t('config_management.status_disconnected'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const yamlText = editorInstance ? editorInstance.getValue() : (textarea ? textarea.value : '');
|
||||||
|
|
||||||
|
saveBtn.disabled = true;
|
||||||
|
if (reloadBtn) {
|
||||||
|
reloadBtn.disabled = true;
|
||||||
|
}
|
||||||
|
this.updateConfigEditorStatus('info', i18n.t('config_management.status_saving'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
await this.writeConfigFile('/config.yaml', yamlText);
|
||||||
|
this.lastConfigFetchUrl = '/config.yaml';
|
||||||
|
this.configYamlCache = yamlText;
|
||||||
|
this.isConfigEditorDirty = false;
|
||||||
|
if (editorInstance && typeof editorInstance.markClean === 'function') {
|
||||||
|
editorInstance.markClean();
|
||||||
|
}
|
||||||
|
this.showNotification(i18n.t('config_management.save_success'), 'success');
|
||||||
|
this.updateConfigEditorStatus('success', i18n.t('config_management.status_saved'));
|
||||||
|
this.clearCache();
|
||||||
|
await this.loadAllData(true);
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = `${i18n.t('config_management.status_save_failed')}: ${error.message}`;
|
||||||
|
this.updateConfigEditorStatus('error', errorMessage);
|
||||||
|
this.showNotification(errorMessage, 'error');
|
||||||
|
this.isConfigEditorDirty = true;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
saveBtn.disabled = !this.isConnected;
|
||||||
|
if (reloadBtn) {
|
||||||
|
reloadBtn.disabled = !this.isConnected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入配置文件到指定端点
|
||||||
|
async writeConfigFile(endpoint, yamlText) {
|
||||||
|
const response = await fetch(`${this.apiUrl}${endpoint}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${this.managementKey}`,
|
||||||
|
'Content-Type': 'application/yaml',
|
||||||
|
'Accept': 'application/json, text/plain, */*'
|
||||||
|
},
|
||||||
|
body: yamlText
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const contentType = response.headers.get('content-type') || '';
|
||||||
|
let errorText = '';
|
||||||
|
if (contentType.includes('application/json')) {
|
||||||
|
const data = await response.json().catch(() => ({}));
|
||||||
|
errorText = data.message || data.error || '';
|
||||||
|
} else {
|
||||||
|
errorText = await response.text().catch(() => '');
|
||||||
|
}
|
||||||
|
throw new Error(errorText || `HTTP ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = response.headers.get('content-type') || '';
|
||||||
|
if (contentType.includes('application/json')) {
|
||||||
|
const data = await response.json().catch(() => null);
|
||||||
|
if (data && data.ok === false) {
|
||||||
|
throw new Error(data.message || data.error || 'Server rejected the update');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 切换语言
|
// 切换语言
|
||||||
toggleLanguage() {
|
toggleLanguage() {
|
||||||
const currentLang = i18n.currentLanguage;
|
const currentLang = i18n.currentLanguage;
|
||||||
@@ -1044,6 +1327,8 @@ class CLIProxyManager {
|
|||||||
|
|
||||||
lastUpdate.textContent = new Date().toLocaleString('zh-CN');
|
lastUpdate.textContent = new Date().toLocaleString('zh-CN');
|
||||||
|
|
||||||
|
this.updateConfigEditorAvailability();
|
||||||
|
|
||||||
// 更新连接信息显示
|
// 更新连接信息显示
|
||||||
this.updateConnectionInfo();
|
this.updateConnectionInfo();
|
||||||
}
|
}
|
||||||
@@ -1109,6 +1394,7 @@ class CLIProxyManager {
|
|||||||
clearCache() {
|
clearCache() {
|
||||||
this.configCache = null;
|
this.configCache = null;
|
||||||
this.cacheTimestamp = null;
|
this.cacheTimestamp = null;
|
||||||
|
this.configYamlCache = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启动状态更新定时器
|
// 启动状态更新定时器
|
||||||
@@ -1147,6 +1433,10 @@ class CLIProxyManager {
|
|||||||
// 使用统计需要单独加载
|
// 使用统计需要单独加载
|
||||||
await this.loadUsageStats();
|
await this.loadUsageStats();
|
||||||
|
|
||||||
|
// 加载配置文件编辑器内容
|
||||||
|
await this.loadConfigFileEditor(forceRefresh);
|
||||||
|
this.refreshConfigEditor();
|
||||||
|
|
||||||
console.log('配置加载完成,使用缓存:', !forceRefresh && this.isCacheValid());
|
console.log('配置加载完成,使用缓存:', !forceRefresh && this.isCacheValid());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载配置失败:', error);
|
console.error('加载配置失败:', error);
|
||||||
@@ -1241,6 +1531,9 @@ class CLIProxyManager {
|
|||||||
this.loadOpenAIProviders(),
|
this.loadOpenAIProviders(),
|
||||||
this.loadAuthFiles()
|
this.loadAuthFiles()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
await this.loadConfigFileEditor(true);
|
||||||
|
this.refreshConfigEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载调试设置
|
// 加载调试设置
|
||||||
@@ -1457,8 +1750,8 @@ class CLIProxyManager {
|
|||||||
// 增量加载:追加新日志
|
// 增量加载:追加新日志
|
||||||
this.appendLogs(response.lines, response['line-count'] || 0);
|
this.appendLogs(response.lines, response['line-count'] || 0);
|
||||||
} else if (!incremental && response.lines.length > 0) {
|
} else if (!incremental && response.lines.length > 0) {
|
||||||
// 全量加载:重新渲染
|
// 全量加载:重新渲染,默认滚动到底部显示最新日志
|
||||||
this.renderLogs(response.lines, response['line-count'] || response.lines.length, false);
|
this.renderLogs(response.lines, response['line-count'] || response.lines.length, true);
|
||||||
} else if (!incremental) {
|
} else if (!incremental) {
|
||||||
// 全量加载但没有日志
|
// 全量加载但没有日志
|
||||||
logsContent.innerHTML = '<div class="empty-state"><i class="fas fa-inbox"></i><p data-i18n="logs.empty_title">' +
|
logsContent.innerHTML = '<div class="empty-state"><i class="fas fa-inbox"></i><p data-i18n="logs.empty_title">' +
|
||||||
|
|||||||
38
i18n.js
38
i18n.js
@@ -82,6 +82,7 @@ const i18n = {
|
|||||||
'nav.ai_providers': 'AI 提供商',
|
'nav.ai_providers': 'AI 提供商',
|
||||||
'nav.auth_files': '认证文件',
|
'nav.auth_files': '认证文件',
|
||||||
'nav.usage_stats': '使用统计',
|
'nav.usage_stats': '使用统计',
|
||||||
|
'nav.config_management': '配置管理',
|
||||||
'nav.logs': '日志查看',
|
'nav.logs': '日志查看',
|
||||||
'nav.system_info': '系统信息',
|
'nav.system_info': '系统信息',
|
||||||
|
|
||||||
@@ -329,6 +330,24 @@ const i18n = {
|
|||||||
'logs.upgrade_required_title': '需要升级 CLI Proxy API',
|
'logs.upgrade_required_title': '需要升级 CLI Proxy API',
|
||||||
'logs.upgrade_required_desc': '当前服务器版本不支持日志查看功能,请升级到最新版本的 CLI Proxy API 以使用此功能。',
|
'logs.upgrade_required_desc': '当前服务器版本不支持日志查看功能,请升级到最新版本的 CLI Proxy API 以使用此功能。',
|
||||||
|
|
||||||
|
// 配置管理
|
||||||
|
'config_management.title': '配置管理',
|
||||||
|
'config_management.editor_title': '配置文件',
|
||||||
|
'config_management.reload': '重新加载',
|
||||||
|
'config_management.save': '保存',
|
||||||
|
'config_management.description': '查看并编辑服务器上的 config.yaml 配置文件。保存前请确认语法正确。',
|
||||||
|
'config_management.status_idle': '等待操作',
|
||||||
|
'config_management.status_loading': '加载配置中...',
|
||||||
|
'config_management.status_loaded': '配置已加载',
|
||||||
|
'config_management.status_dirty': '有未保存的更改',
|
||||||
|
'config_management.status_disconnected': '请先连接服务器以加载配置',
|
||||||
|
'config_management.status_load_failed': '加载失败',
|
||||||
|
'config_management.status_saving': '正在保存配置...',
|
||||||
|
'config_management.status_saved': '配置保存完成',
|
||||||
|
'config_management.status_save_failed': '保存失败',
|
||||||
|
'config_management.save_success': '配置已保存',
|
||||||
|
'config_management.error_yaml_not_supported': '服务器未返回 YAML 格式,请确认 /config.yaml 接口可用',
|
||||||
|
|
||||||
// 系统信息
|
// 系统信息
|
||||||
'system_info.title': '系统信息',
|
'system_info.title': '系统信息',
|
||||||
'system_info.connection_status_title': '连接状态',
|
'system_info.connection_status_title': '连接状态',
|
||||||
@@ -478,6 +497,7 @@ const i18n = {
|
|||||||
'nav.ai_providers': 'AI Providers',
|
'nav.ai_providers': 'AI Providers',
|
||||||
'nav.auth_files': 'Auth Files',
|
'nav.auth_files': 'Auth Files',
|
||||||
'nav.usage_stats': 'Usage Statistics',
|
'nav.usage_stats': 'Usage Statistics',
|
||||||
|
'nav.config_management': 'Config Management',
|
||||||
'nav.logs': 'Logs Viewer',
|
'nav.logs': 'Logs Viewer',
|
||||||
'nav.system_info': 'System Info',
|
'nav.system_info': 'System Info',
|
||||||
|
|
||||||
@@ -724,6 +744,24 @@ const i18n = {
|
|||||||
'logs.upgrade_required_title': 'Please Upgrade CLI Proxy API',
|
'logs.upgrade_required_title': 'Please Upgrade CLI Proxy API',
|
||||||
'logs.upgrade_required_desc': 'The current server version does not support the logs viewing feature. Please upgrade to the latest version of CLI Proxy API to use this feature.',
|
'logs.upgrade_required_desc': 'The current server version does not support the logs viewing feature. Please upgrade to the latest version of CLI Proxy API to use this feature.',
|
||||||
|
|
||||||
|
// Config management
|
||||||
|
'config_management.title': 'Config Management',
|
||||||
|
'config_management.editor_title': 'Configuration File',
|
||||||
|
'config_management.reload': 'Reload',
|
||||||
|
'config_management.save': 'Save',
|
||||||
|
'config_management.description': 'View and edit the server-side config.yaml file. Validate the syntax before saving.',
|
||||||
|
'config_management.status_idle': 'Waiting for action',
|
||||||
|
'config_management.status_loading': 'Loading configuration...',
|
||||||
|
'config_management.status_loaded': 'Configuration loaded',
|
||||||
|
'config_management.status_dirty': 'Unsaved changes',
|
||||||
|
'config_management.status_disconnected': 'Connect to the server to load the configuration',
|
||||||
|
'config_management.status_load_failed': 'Load failed',
|
||||||
|
'config_management.status_saving': 'Saving configuration...',
|
||||||
|
'config_management.status_saved': 'Configuration saved',
|
||||||
|
'config_management.status_save_failed': 'Save failed',
|
||||||
|
'config_management.save_success': 'Configuration saved successfully',
|
||||||
|
'config_management.error_yaml_not_supported': 'Server did not return YAML. Verify the /config.yaml endpoint is available.',
|
||||||
|
|
||||||
// System info
|
// System info
|
||||||
'system_info.title': 'System Information',
|
'system_info.title': 'System Information',
|
||||||
'system_info.connection_status_title': 'Connection Status',
|
'system_info.connection_status_title': 'Connection Status',
|
||||||
|
|||||||
37
index.html
37
index.html
@@ -7,7 +7,13 @@
|
|||||||
<title data-i18n="title.login">CLI Proxy API Management Center</title>
|
<title data-i18n="title.login">CLI Proxy API Management Center</title>
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/yaml/yaml.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/edit/closebrackets.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/comment/comment.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/js-yaml@4.1.0/dist/js-yaml.min.js"></script>
|
||||||
<script src="i18n.js"></script>
|
<script src="i18n.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -171,6 +177,9 @@
|
|||||||
<li data-tooltip="使用统计"><a href="#usage-stats" class="nav-item" data-section="usage-stats">
|
<li data-tooltip="使用统计"><a href="#usage-stats" class="nav-item" data-section="usage-stats">
|
||||||
<i class="fas fa-chart-line"></i> <span data-i18n="nav.usage_stats">使用统计</span>
|
<i class="fas fa-chart-line"></i> <span data-i18n="nav.usage_stats">使用统计</span>
|
||||||
</a></li>
|
</a></li>
|
||||||
|
<li data-tooltip="配置管理"><a href="#config-management" class="nav-item" data-section="config-management">
|
||||||
|
<i class="fas fa-cog"></i> <span data-i18n="nav.config_management">配置管理</span>
|
||||||
|
</a></li>
|
||||||
<li id="logs-nav-item" data-tooltip="日志查看" style="display: none;"><a href="#logs" class="nav-item" data-section="logs">
|
<li id="logs-nav-item" data-tooltip="日志查看" style="display: none;"><a href="#logs" class="nav-item" data-section="logs">
|
||||||
<i class="fas fa-scroll"></i> <span data-i18n="nav.logs">日志查看</span>
|
<i class="fas fa-scroll"></i> <span data-i18n="nav.logs">日志查看</span>
|
||||||
</a></li>
|
</a></li>
|
||||||
@@ -766,6 +775,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- 配置管理 -->
|
||||||
|
<section id="config-management" class="content-section">
|
||||||
|
<h2 data-i18n="config_management.title">配置管理</h2>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3><i class="fas fa-file-code"></i> <span data-i18n="config_management.editor_title">配置文件</span></h3>
|
||||||
|
<div class="editor-actions">
|
||||||
|
<button id="config-reload-btn" class="btn btn-secondary">
|
||||||
|
<i class="fas fa-sync-alt"></i> <span data-i18n="config_management.reload">重新加载</span>
|
||||||
|
</button>
|
||||||
|
<button id="config-save-btn" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save"></i> <span data-i18n="config_management.save">保存</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<p class="form-hint" data-i18n="config_management.description">查看并编辑服务器上的 config.yaml 配置文件。保存前请确认语法正确。</p>
|
||||||
|
<div class="yaml-editor-container">
|
||||||
|
<textarea id="config-editor" class="yaml-editor" spellcheck="false" placeholder="key: value"></textarea>
|
||||||
|
<div id="config-editor-status" class="editor-status" data-i18n="config_management.status_idle">等待操作</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- 系统信息 -->
|
<!-- 系统信息 -->
|
||||||
<section id="system-info" class="content-section">
|
<section id="system-info" class="content-section">
|
||||||
<h2 data-i18n="system_info.title">系统信息</h2>
|
<h2 data-i18n="system_info.title">系统信息</h2>
|
||||||
@@ -861,4 +896,4 @@
|
|||||||
<script src="app.js"></script>
|
<script src="app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
132
styles.css
132
styles.css
@@ -1278,6 +1278,133 @@ textarea::placeholder {
|
|||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 配置管理编辑器 */
|
||||||
|
.editor-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-actions .btn {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaml-editor-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-management .card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: calc(100vh - 360px);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-management .card-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-management .yaml-editor-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yaml-editor {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 360px;
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
overflow-y: auto;
|
||||||
|
font-family: 'SFMono-Regular', 'Consolas', 'Liberation Mono', 'Courier New', monospace;
|
||||||
|
line-height: 1.5;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-management .CodeMirror {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
font-family: 'SFMono-Regular', 'Consolas', 'Liberation Mono', 'Courier New', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-management .CodeMirror-scroll {
|
||||||
|
min-height: 0;
|
||||||
|
max-height: calc(100vh - 440px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-management .CodeMirror.cm-s-default {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-management .CodeMirror-gutters {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-right: 1px solid var(--border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-management .CodeMirror-linenumber {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-management .CodeMirror .CodeMirror-lines {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-management .CodeMirror .cm-comment {
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] #config-management .CodeMirror.cm-s-default {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] #config-management .CodeMirror-gutters {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-right: 1px solid var(--border-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] #config-management .CodeMirror .cm-comment {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
#config-management .CodeMirror.cm-readonly {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-status {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-quaternary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
min-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-status.success {
|
||||||
|
color: #059669;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-status.error {
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
.slider {
|
.slider {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -2924,4 +3051,7 @@ input:checked+.slider:before {
|
|||||||
.stat-badge i {
|
.stat-badge i {
|
||||||
font-size: 11px !important;
|
font-size: 11px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#config-management .CodeMirror .CodeMirror-lines {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user