Compare commits

...

10 Commits

Author SHA1 Message Date
Supra4E8C
89099b58ff update README 2025-10-26 14:56:54 +08:00
Supra4E8C
7509a1eddc 更新补全i18n国际化文本 2025-10-26 14:54:07 +08:00
hkfires
e92784f951 feat(ui): add auth file success/failure stats; expand list height 2025-10-21 21:59:08 +08:00
hkfires
d26695da76 feat(ui,keys): granular masking, stats match, legacy format
- Make maskApiKey progressive for short keys to reduce exposure
- When aggregating usage, fall back to masked key to match stored stats
- Support legacy provider config `api-keys` by mapping to `api-key-entries`
- Add scrolling for provider lists >5 and reset styles when empty
- Improves privacy, fixes mismatched stats display, and preserves compatibility
2025-10-21 21:08:09 +08:00
Supra4E8C
8964030ade 尝试修复bug 2025-10-21 12:31:22 +08:00
hkfires
0b9abdf9b1 fix(logs): auto-scroll to latest on full reload 2025-10-20 14:57:15 +08:00
hkfires
a208a484ff feat(config-ui): add YAML config editor with save/reload support 2025-10-19 22:40:08 +08:00
Supra4E8C
369cf52346 0.1.7 2025-10-18 17:04:29 +08:00
hkfires
dcfffc716b feat(ui): add in-app log viewer and file logging controls
- Add Logs section with refresh, download, and clear actions
- Implement auto-refresh toggle and incremental loading via timestamp
- Add “logging to file” setting; integrate with config and /logging-to-file API
- Auto-load logs when opening Logs; hide nav when logging disabled
- Escape HTML when rendering logs for safety
- Update i18n with strings for logs and settings
- Ignore CLAUDE.md and AGENTS.md in .gitignore

Why: Enable users to monitor and manage logs within the app, reduce overhead with incremental updates, and avoid committing local agent docs.
2025-10-16 12:03:15 +08:00
hkfires
7de5280824 feat(build): auto-inject release version into HTML output 2025-10-15 16:49:53 +08:00
9 changed files with 1634 additions and 148 deletions

View File

@@ -27,6 +27,8 @@ jobs:
- name: Build all-in-one HTML
run: npm run build
env:
VERSION: ${{ github.ref_name }}
- name: Prepare release assets
run: |

5
.gitignore vendored
View File

@@ -20,3 +20,8 @@ package-lock.json
# OS files
.DS_Store
Thumbs.db
CLAUDE.md
.claude
AGENTS.md
.serena

View File

@@ -10,7 +10,7 @@ Example URL:
https://remote.router-for.me/
Minimum required version: ≥ 6.0.0
Recommended version: ≥ 6.1.3
Recommended version: ≥ 6.2.32
Since version 6.0.19, the WebUI has been rolled into the main program. You can access it by going to `/management.html` on the external port after firing up the main project.

View File

@@ -9,7 +9,7 @@ https://remote.router-for.me/
最低可用版本 ≥ 6.0.0
推荐版本 ≥ 6.1.3
推荐版本 ≥ 6.2.32
自6.0.19起WebUI已经集成在主程序中 可以通过主项目开启的外部端口的`/management.html`访问

1059
app.js

File diff suppressed because it is too large Load Diff

View File

@@ -49,6 +49,35 @@ function escapeForStyle(content) {
return content.replace(/<\/(style)/gi, '<\\/$1');
}
function getVersion() {
// 1. 优先从环境变量获取GitHub Actions 会设置)
if (process.env.VERSION) {
return process.env.VERSION;
}
// 2. 尝试从 git tag 获取
try {
const { execSync } = require('child_process');
const gitTag = execSync('git describe --tags --exact-match 2>/dev/null || git describe --tags 2>/dev/null || echo ""', { encoding: 'utf8' }).trim();
if (gitTag) {
return gitTag;
}
} catch (err) {
console.warn('无法从 git 获取版本号');
}
// 3. 回退到 package.json
try {
const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'));
return 'v' + packageJson.version;
} catch (err) {
console.warn('无法从 package.json 读取版本号');
}
// 4. 最后使用默认值
return 'v0.0.0-dev';
}
function ensureDistDir() {
if (fs.existsSync(distDir)) {
fs.rmSync(distDir, { recursive: true, force: true });
@@ -83,6 +112,11 @@ function build() {
const i18n = escapeForScript(readFile(sourceFiles.i18n));
const app = escapeForScript(readFile(sourceFiles.app));
// 获取版本号并替换
const version = getVersion();
console.log(`使用版本号: ${version}`);
html = html.replace(/__VERSION__/g, version);
html = html.replace(
'<link rel="stylesheet" href="styles.css">',
`<style>

134
i18n.js
View File

@@ -38,6 +38,8 @@ const i18n = {
'common.base_url': '地址',
'common.proxy_url': '代理',
'common.alias': '别名',
'common.failure': '失败',
'common.unknown_error': '未知错误',
// 页面标题
'title.main': 'CLI Proxy API Management Center',
@@ -82,6 +84,8 @@ const i18n = {
'nav.ai_providers': 'AI 提供商',
'nav.auth_files': '认证文件',
'nav.usage_stats': '使用统计',
'nav.config_management': '配置管理',
'nav.logs': '日志查看',
'nav.system_info': '系统信息',
// 基础设置
@@ -101,6 +105,8 @@ const i18n = {
'basic_settings.quota_switch_preview': '切换到预览模型',
'basic_settings.usage_statistics_title': '使用统计',
'basic_settings.usage_statistics_enable': '启用使用统计',
'basic_settings.logging_title': '日志记录',
'basic_settings.logging_to_file_enable': '启用日志记录到文件',
// API 密钥管理
'api_keys.title': 'API 密钥管理',
@@ -271,6 +277,7 @@ const i18n = {
'auth_login.qwen_oauth_status_error': '认证失败:',
'auth_login.qwen_oauth_start_error': '启动 Qwen OAuth 失败:',
'auth_login.qwen_oauth_polling_error': '检查认证状态失败:',
'auth_login.missing_state': '无法获取认证状态参数',
// iFlow OAuth
'auth_login.iflow_oauth_title': 'iFlow OAuth',
@@ -304,6 +311,48 @@ const i18n = {
'usage_stats.tokens_count': 'Token数量',
'usage_stats.models': '模型统计',
'usage_stats.success_rate': '成功率',
'stats.success': '成功',
'stats.failure': '失败',
// 日志查看
'logs.title': '日志查看',
'logs.refresh_button': '刷新日志',
'logs.clear_button': '清空日志',
'logs.download_button': '下载日志',
'logs.empty_title': '暂无日志记录',
'logs.empty_desc': '当启用"日志记录到文件"功能后,日志将显示在这里',
'logs.log_content': '日志内容',
'logs.loading': '正在加载日志...',
'logs.load_error': '加载日志失败',
'logs.clear_confirm': '确定要清空所有日志吗?此操作不可恢复!',
'logs.clear_success': '日志已清空',
'logs.download_success': '日志下载成功',
'logs.auto_refresh': '自动刷新',
'logs.auto_refresh_enabled': '自动刷新已开启',
'logs.auto_refresh_disabled': '自动刷新已关闭',
'logs.lines': '行',
'logs.removed': '已删除',
'logs.upgrade_required_title': '需要升级 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 接口可用',
'config_management.editor_placeholder': 'key: value',
// 系统信息
'system_info.title': '系统信息',
@@ -324,6 +373,7 @@ const i18n = {
'notification.quota_switch_project_updated': '项目切换设置已更新',
'notification.quota_switch_preview_updated': '预览模型切换设置已更新',
'notification.usage_statistics_updated': '使用统计设置已更新',
'notification.logging_to_file_updated': '日志记录设置已更新',
'notification.api_key_added': 'API密钥添加成功',
'notification.api_key_updated': 'API密钥更新成功',
'notification.api_key_deleted': 'API密钥删除成功',
@@ -358,6 +408,7 @@ const i18n = {
'notification.gemini_api_key': 'Gemini API密钥',
'notification.codex_api_key': 'Codex API密钥',
'notification.claude_api_key': 'Claude API密钥',
'notification.link_copied': '链接已复制到剪贴板',
// 语言切换
'language.switch': '语言',
@@ -372,6 +423,10 @@ const i18n = {
'theme.switch_to_dark': '切换到暗色模式',
'theme.auto': '跟随系统',
// 侧边栏
'sidebar.toggle_expand': '展开侧边栏',
'sidebar.toggle_collapse': '收起侧边栏',
// 页脚
'footer.version': '版本',
'footer.author': '作者'
@@ -409,6 +464,8 @@ const i18n = {
'common.base_url': 'Address',
'common.proxy_url': 'Proxy',
'common.alias': 'Alias',
'common.failure': 'Failure',
'common.unknown_error': 'Unknown error',
// Page titles
'title.main': 'CLI Proxy API Management Center',
@@ -453,6 +510,8 @@ const i18n = {
'nav.ai_providers': 'AI Providers',
'nav.auth_files': 'Auth Files',
'nav.usage_stats': 'Usage Statistics',
'nav.config_management': 'Config Management',
'nav.logs': 'Logs Viewer',
'nav.system_info': 'System Info',
// Basic settings
@@ -472,6 +531,8 @@ const i18n = {
'basic_settings.quota_switch_preview': 'Switch to Preview Model',
'basic_settings.usage_statistics_title': 'Usage Statistics',
'basic_settings.usage_statistics_enable': 'Enable usage statistics',
'basic_settings.logging_title': 'Logging',
'basic_settings.logging_to_file_enable': 'Enable logging to file',
// API Keys management
'api_keys.title': 'API Keys Management',
@@ -641,6 +702,7 @@ const i18n = {
'auth_login.qwen_oauth_status_error': 'Authentication failed:',
'auth_login.qwen_oauth_start_error': 'Failed to start Qwen OAuth:',
'auth_login.qwen_oauth_polling_error': 'Failed to check authentication status:',
'auth_login.missing_state': 'Unable to retrieve authentication state parameter',
// iFlow OAuth
'auth_login.iflow_oauth_title': 'iFlow OAuth',
@@ -674,6 +736,48 @@ const i18n = {
'usage_stats.tokens_count': 'Token Count',
'usage_stats.models': 'Model Statistics',
'usage_stats.success_rate': 'Success Rate',
'stats.success': 'Success',
'stats.failure': 'Failure',
// Logs viewer
'logs.title': 'Logs Viewer',
'logs.refresh_button': 'Refresh Logs',
'logs.clear_button': 'Clear Logs',
'logs.download_button': 'Download Logs',
'logs.empty_title': 'No Logs Available',
'logs.empty_desc': 'When "Enable logging to file" is enabled, logs will be displayed here',
'logs.log_content': 'Log Content',
'logs.loading': 'Loading logs...',
'logs.load_error': 'Failed to load logs',
'logs.clear_confirm': 'Are you sure you want to clear all logs? This action cannot be undone!',
'logs.clear_success': 'Logs cleared successfully',
'logs.download_success': 'Logs downloaded successfully',
'logs.auto_refresh': 'Auto Refresh',
'logs.auto_refresh_enabled': 'Auto refresh enabled',
'logs.auto_refresh_disabled': 'Auto refresh disabled',
'logs.lines': 'lines',
'logs.removed': 'Removed',
'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.',
// 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.',
'config_management.editor_placeholder': 'key: value',
// System info
'system_info.title': 'System Information',
@@ -694,6 +798,7 @@ const i18n = {
'notification.quota_switch_project_updated': 'Project switch settings updated',
'notification.quota_switch_preview_updated': 'Preview model switch settings updated',
'notification.usage_statistics_updated': 'Usage statistics settings updated',
'notification.logging_to_file_updated': 'Logging settings updated',
'notification.api_key_added': 'API key added successfully',
'notification.api_key_updated': 'API key updated successfully',
'notification.api_key_deleted': 'API key deleted successfully',
@@ -728,6 +833,7 @@ const i18n = {
'notification.gemini_api_key': 'Gemini API key',
'notification.codex_api_key': 'Codex API key',
'notification.claude_api_key': 'Claude API key',
'notification.link_copied': 'Link copied to clipboard',
// Language switch
'language.switch': 'Language',
@@ -742,6 +848,10 @@ const i18n = {
'theme.switch_to_dark': 'Switch to dark mode',
'theme.auto': 'Follow system',
// Sidebar
'sidebar.toggle_expand': 'Expand sidebar',
'sidebar.toggle_collapse': 'Collapse sidebar',
// Footer
'footer.version': 'Version',
'footer.author': 'Author'
@@ -791,6 +901,30 @@ const i18n = {
}
});
// 更新所有包含 data-i18n-placeholder 的输入框占位符
document.querySelectorAll('[data-i18n-placeholder]').forEach(element => {
const key = element.getAttribute('data-i18n-placeholder');
element.placeholder = this.t(key);
});
// 更新 data-i18n-title
document.querySelectorAll('[data-i18n-title]').forEach(element => {
const key = element.getAttribute('data-i18n-title');
element.title = this.t(key);
});
// 更新 data-i18n-tooltip
document.querySelectorAll('[data-i18n-tooltip]').forEach(element => {
const key = element.getAttribute('data-i18n-tooltip');
element.setAttribute('data-tooltip', this.t(key));
});
// 更新 data-i18n-text常用于按钮或标签
document.querySelectorAll('[data-i18n-text]').forEach(element => {
const key = element.getAttribute('data-i18n-text');
element.textContent = this.t(key);
});
// 更新所有带有 data-i18n-html 属性的元素支持HTML
document.querySelectorAll('[data-i18n-html]').forEach(element => {
const key = element.getAttribute('data-i18n-html');

View File

@@ -7,7 +7,13 @@
<title data-i18n="title.login">CLI Proxy API Management Center</title>
<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/codemirror/5.65.16/codemirror.min.css">
<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>
</head>
@@ -71,8 +77,7 @@
<div class="form-group">
<label for="login-api-base" data-i18n="login.custom_connection_label">自定义连接地址:</label>
<div class="input-group">
<input type="text" id="login-api-base" data-i18n="login.custom_connection_placeholder"
placeholder="例如: https://example.com:8317">
<input type="text" id="login-api-base" data-i18n-placeholder="login.custom_connection_placeholder">
<button type="button" id="login-reset-api-base"
class="btn btn-secondary connection-reset-btn">
<i class="fas fa-location-arrow"></i>
@@ -85,8 +90,7 @@
<div class="form-group">
<label for="login-management-key" data-i18n="login.management_key_label">管理密钥:</label>
<div class="input-group">
<input type="password" id="login-management-key"
data-i18n="login.management_key_placeholder" placeholder="请输入管理密钥" required>
<input type="password" id="login-management-key" data-i18n-placeholder="login.management_key_placeholder" required>
<button type="button" class="btn btn-secondary toggle-key-visibility">
<i class="fas fa-eye"></i>
</button>
@@ -117,7 +121,7 @@
<button class="mobile-menu-btn" id="mobile-menu-btn">
<i class="fas fa-bars"></i>
</button>
<button class="sidebar-toggle-btn-desktop" id="sidebar-toggle-btn-desktop" title="收起/展开侧边栏">
<button class="sidebar-toggle-btn-desktop" id="sidebar-toggle-btn-desktop" data-i18n-title="sidebar.toggle_collapse">
<i class="fas fa-bars"></i>
</button>
<div class="top-navbar-brand">
@@ -155,23 +159,29 @@
<nav class="sidebar" id="sidebar">
<!-- 导航菜单 -->
<ul class="nav-menu">
<li data-tooltip="基础设置"><a href="#basic-settings" class="nav-item active"
<li data-i18n-tooltip="nav.basic_settings"><a href="#basic-settings" class="nav-item active"
data-section="basic-settings">
<i class="fas fa-sliders-h"></i> <span data-i18n="nav.basic_settings">基础设置</span>
</a></li>
<li data-tooltip="API 密钥"><a href="#api-keys" class="nav-item" data-section="api-keys">
<li data-i18n-tooltip="nav.api_keys"><a href="#api-keys" class="nav-item" data-section="api-keys">
<i class="fas fa-key"></i> <span data-i18n="nav.api_keys">API 密钥</span>
</a></li>
<li data-tooltip="AI 提供商"><a href="#ai-providers" class="nav-item" data-section="ai-providers">
<li data-i18n-tooltip="nav.ai_providers"><a href="#ai-providers" class="nav-item" data-section="ai-providers">
<i class="fas fa-robot"></i> <span data-i18n="nav.ai_providers">AI 提供商</span>
</a></li>
<li data-tooltip="认证文件"><a href="#auth-files" class="nav-item" data-section="auth-files">
<li data-i18n-tooltip="nav.auth_files"><a href="#auth-files" class="nav-item" data-section="auth-files">
<i class="fas fa-file-alt"></i> <span data-i18n="nav.auth_files">认证文件</span>
</a></li>
<li data-tooltip="使用统计"><a href="#usage-stats" class="nav-item" data-section="usage-stats">
<li data-i18n-tooltip="nav.usage_stats"><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>
</a></li>
<li data-tooltip="系统信息"><a href="#system-info" class="nav-item" data-section="system-info">
<li data-i18n-tooltip="nav.config_management"><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-i18n-tooltip="nav.logs" style="display: none;"><a href="#logs" class="nav-item" data-section="logs">
<i class="fas fa-scroll"></i> <span data-i18n="nav.logs">日志查看</span>
</a></li>
<li data-i18n-tooltip="nav.system_info"><a href="#system-info" class="nav-item" data-section="system-info">
<i class="fas fa-info-circle"></i> <span data-i18n="nav.system_info">系统信息</span>
</a></li>
</ul>
@@ -218,9 +228,7 @@
<label for="proxy-url" data-i18n="basic_settings.proxy_url_label">代理
URL:</label>
<div class="input-group">
<input type="text" id="proxy-url"
data-i18n="basic_settings.proxy_url_placeholder"
placeholder="例如: socks5://user:pass@127.0.0.1:1080/">
<input type="text" id="proxy-url" data-i18n-placeholder="basic_settings.proxy_url_placeholder">
<button id="update-proxy" class="btn btn-primary"
data-i18n="basic_settings.proxy_update">更新</button>
<button id="clear-proxy" class="btn btn-danger"
@@ -293,6 +301,24 @@
</div>
</div>
<!-- 日志记录设置 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-file-alt"></i> <span
data-i18n="basic_settings.logging_title">日志记录</span></h3>
</div>
<div class="card-content">
<div class="toggle-group">
<label class="toggle-switch">
<input type="checkbox" id="logging-to-file-toggle">
<span class="slider"></span>
</label>
<span class="toggle-label"
data-i18n="basic_settings.logging_to_file_enable">启用日志记录到文件</span>
</div>
</div>
</div>
</section>
<!-- API 密钥管理 -->
@@ -503,8 +529,7 @@
data-i18n="auth_login.gemini_cli_project_id_label">Google Cloud 项目 ID
(可选):</label>
<input type="text" id="gemini-cli-project-id"
data-i18n="auth_login.gemini_cli_project_id_placeholder"
placeholder="输入 Google Cloud 项目 ID (可选)">
data-i18n-placeholder="auth_login.gemini_cli_project_id_placeholder">
<div class="form-hint" data-i18n="auth_login.gemini_cli_project_id_hint">
如果指定了项目 ID将使用该项目的认证信息。
</div>
@@ -601,6 +626,40 @@
</div>
</section>
<!-- 日志查看 -->
<section id="logs" class="content-section">
<h2 data-i18n="logs.title">日志查看</h2>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-scroll"></i> <span data-i18n="logs.log_content">日志内容</span></h3>
<div class="header-actions">
<div class="toggle-group" style="margin-right: 15px;">
<label class="toggle-switch" style="margin-right: 5px;">
<input type="checkbox" id="logs-auto-refresh-toggle">
<span class="slider"></span>
</label>
<span class="toggle-label" data-i18n="logs.auto_refresh" style="font-size: 0.9em;">自动刷新</span>
</div>
<button id="refresh-logs" class="btn btn-primary">
<i class="fas fa-sync-alt"></i> <span data-i18n="logs.refresh_button">刷新日志</span>
</button>
<button id="download-logs" class="btn btn-secondary">
<i class="fas fa-download"></i> <span data-i18n="logs.download_button">下载日志</span>
</button>
<button id="clear-logs" class="btn btn-danger">
<i class="fas fa-trash"></i> <span data-i18n="logs.clear_button">清空日志</span>
</button>
</div>
</div>
<div class="card-content">
<div id="logs-content" class="logs-container">
<div class="loading-placeholder" data-i18n="logs.loading">正在加载日志...</div>
</div>
</div>
</div>
</section>
<!-- 使用统计 -->
<section id="usage-stats" class="content-section">
<h2 data-i18n="usage_stats.title">使用统计</h2>
@@ -711,6 +770,32 @@
</div>
</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" data-i18n="config_management.editor_placeholder"></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">
<h2 data-i18n="system_info.title">系统信息</h2>
@@ -730,13 +815,6 @@
</div>
<div class="info-value" id="display-api-url">-</div>
</div>
<div class="info-item">
<div class="info-label">
<i class="fas fa-key"></i>
<span data-i18n="connection.management_key">管理密钥:</span>
</div>
<div class="info-value" id="display-management-key">-</div>
</div>
<div class="info-item">
<div class="info-label">
<i class="fas fa-circle"></i>
@@ -785,7 +863,7 @@
<!-- 版本信息 -->
<footer class="version-footer">
<div class="version-info">
<span data-i18n="footer.version">版本</span>: v0.1.3
<span data-i18n="footer.version">版本</span>: __VERSION__
<span class="separator"></span>
<span data-i18n="footer.author">作者</span>: CLI Proxy API Team
</div>

View File

@@ -1278,6 +1278,133 @@ textarea::placeholder {
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 {
position: absolute;
cursor: pointer;
@@ -1318,13 +1445,21 @@ input:checked+.slider:before {
}
/* 列表样式 */
.key-list,
.provider-list,
.file-list {
.key-list {
max-height: 400px;
overflow-y: auto;
}
.file-list {
/* 认证文件列表填满页面,保留版本信息空间 */
max-height: calc(100vh - 300px); /* 减去导航栏、padding和版本信息的高度 */
overflow-y: auto;
}
.provider-list {
/* 默认不限制高度,动态设置 */
}
.key-item,
.provider-item,
.file-item {
@@ -2665,3 +2800,266 @@ input:checked+.slider:before {
font-size: 12px;
}
}
/* 日志查看样式 */
.logs-container {
background: var(--bg-tertiary);
border-radius: 8px;
padding: 16px;
min-height: 500px;
max-height: calc(100vh - 400px);
overflow-y: auto;
font-family: 'Monaco', 'Menlo', 'Consolas', 'Ubuntu Mono', monospace;
}
.logs-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border-primary);
font-size: 13px;
color: var(--text-tertiary);
}
.logs-info span {
display: flex;
align-items: center;
gap: 6px;
}
.logs-text {
background: var(--bg-secondary);
border: 1px solid var(--border-primary);
border-radius: 6px;
padding: 16px;
font-size: 12px;
line-height: 1.6;
color: var(--text-secondary);
white-space: pre-wrap;
word-wrap: break-word;
max-height: calc(100vh - 480px);
min-height: 450px;
overflow-y: auto;
margin: 0;
}
.logs-text::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.logs-text::-webkit-scrollbar-track {
background: var(--bg-tertiary);
border-radius: 4px;
}
.logs-text::-webkit-scrollbar-thumb {
background: var(--border-secondary);
border-radius: 4px;
}
.logs-text::-webkit-scrollbar-thumb:hover {
background: var(--text-quaternary);
}
/* 空状态和错误状态 */
.logs-container .empty-state,
.logs-container .error-state,
.logs-container .upgrade-notice {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 300px;
text-align: center;
color: var(--text-tertiary);
}
.logs-container .empty-state i,
.logs-container .error-state i,
.logs-container .upgrade-notice i {
font-size: 48px;
margin-bottom: 16px;
color: var(--border-secondary);
}
.logs-container .error-state i {
color: var(--error-text);
}
.logs-container .upgrade-notice i {
color: var(--primary-color);
}
.logs-container .empty-state p,
.logs-container .error-state p,
.logs-container .upgrade-notice p {
margin: 4px 0;
font-size: 14px;
}
.logs-container .empty-state p:first-of-type,
.logs-container .error-state p:first-of-type {
font-weight: 600;
color: var(--text-secondary);
font-size: 16px;
}
.logs-container .upgrade-notice h3 {
font-weight: 600;
color: var(--text-secondary);
font-size: 18px;
margin-bottom: 8px;
}
.logs-container .upgrade-notice p {
color: var(--text-tertiary);
max-width: 500px;
line-height: 1.6;
}
/* 日志页面头部操作区域 */
#logs .card-header .header-actions {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
#logs .card-header .header-actions .toggle-group {
margin-bottom: 0;
}
/* 响应式设计 - 日志查看 */
@media (max-width: 768px) {
.logs-container {
min-height: 300px;
max-height: calc(100vh - 350px);
padding: 12px;
}
.logs-text {
font-size: 11px;
padding: 12px;
max-height: calc(100vh - 430px);
min-height: 250px;
}
.logs-info {
font-size: 12px;
}
#logs .card-header .header-actions {
width: 100%;
justify-content: flex-start;
}
#logs .card-header .header-actions .btn {
flex: 1;
min-width: 0;
}
#logs .card-header .header-actions .btn span {
display: none;
}
#logs .card-header .header-actions .toggle-group {
width: 100%;
justify-content: space-between;
}
}
/* 暗色主题适配 */
[data-theme="dark"] .logs-text {
background: rgba(15, 23, 42, 0.5);
border-color: var(--border-primary);
color: var(--text-tertiary);
}
[data-theme="dark"] .logs-container {
background: rgba(15, 23, 42, 0.3);
}
/* ===== AI提供商统计徽章样式 ===== */
/* 统计信息容器 */
.item-stats {
display: flex;
gap: 8px;
margin-top: 10px;
flex-wrap: wrap;
}
/* 统计徽章基础样式 */
.stat-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
transition: all 0.2s ease;
}
/* 成功统计徽章 */
.stat-badge.stat-success {
background-color: var(--success-bg);
color: var(--success-text);
border: 1px solid var(--success-border);
}
.stat-badge.stat-success i {
font-size: 13px;
}
/* 失败统计徽章 */
.stat-badge.stat-failure {
background-color: var(--error-bg);
color: var(--error-text);
border: 1px solid var(--error-border);
}
.stat-badge.stat-failure i {
font-size: 13px;
}
/* 悬停效果 */
.stat-badge:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
/* 暗色主题适配 */
[data-theme="dark"] .stat-badge.stat-success {
background-color: rgba(6, 78, 59, 0.3);
color: #6ee7b7;
border-color: rgba(5, 150, 105, 0.5);
}
[data-theme="dark"] .stat-badge.stat-failure {
background-color: rgba(153, 27, 27, 0.3);
color: #fca5a5;
border-color: rgba(220, 38, 38, 0.5);
}
/* 响应式设计 */
@media (max-width: 768px) {
.item-stats {
gap: 6px;
margin-top: 8px;
}
.stat-badge {
padding: 3px 8px;
font-size: 11px;
}
.stat-badge i {
font-size: 11px !important;
}
}
#config-management .CodeMirror .CodeMirror-lines {
padding: 12px;
}