feat(webui): Enhance dashboard, global styles, and settings module
## Dashboard Enhancements
- Add Request Volume trend chart with Chart.js line graph
- Support Family/Model display modes for aggregation levels
- Show Total/Today/1H usage statistics
- Hierarchical filter dropdown with Smart select (Top 5 by 24h usage)
- Persist chart preferences to localStorage
- Improve account health detection logic
- Core models (sonnet/opus/pro/flash) require >5% quota to be healthy
- Dynamic quota ring chart supporting any model family
- Unify table styles with standard-table class
## Global Style Refactoring
- Add CSS variable system for theming
- Space color scale (950/900/850/800/border)
- Neon accent colors (purple/green/cyan/yellow/red)
- Text hierarchy (main/dim/muted/bright)
- Chart palette (16 colors)
- Add unified component classes
- .view-container for consistent page layouts
- .section-header/.section-title/.section-desc
- .standard-table for table styling
- Update scrollbar, nav-item, progress-bar to use theme variables
## Settings Module Extensions
- Add model mapping column in Models tab
- Enhance model selectors with family color indicators
- Support horizontal scroll for tabs on narrow screens
- Add defaultCooldownMs and maxWaitBeforeErrorMs config options
## New Module
- Add src/modules/usage-stats.js for request tracking
- Track /v1/messages and /v1/chat/completions endpoints
- Hierarchical storage: { hour: { family: { model: count } } }
- Auto-save every minute, 30-day retention
- GET /api/stats/history endpoint for dashboard chart
## Backend Changes
- Add direct account manipulation helpers (bypass AccountManager)
- Add POST /api/config/password endpoint for WebUI password change
- Auto-reload AccountManager after account operations
- Use CSS variables in OAuth callback pages
## Other
- Update .gitignore for runtime data directory
- Add i18n keys for new UI elements (EN/zh_CN)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -56,7 +56,14 @@ document.addEventListener('alpine:init', () => {
|
||||
delete: "Delete",
|
||||
confirmDelete: "Are you sure you want to remove this account?",
|
||||
connectGoogle: "Connect Google Account",
|
||||
manualReload: "Reload from Disk",
|
||||
reauthenticated: "re-authenticated",
|
||||
added: "added",
|
||||
successfully: "successfully",
|
||||
failedToGetAuthUrl: "Failed to get auth URL",
|
||||
failedToStartOAuth: "Failed to start OAuth flow",
|
||||
family: "Family",
|
||||
model: "Model",
|
||||
activeSuffix: "Active",
|
||||
// Tabs
|
||||
tabInterface: "Interface",
|
||||
tabClaude: "Claude CLI",
|
||||
@@ -104,6 +111,68 @@ document.addEventListener('alpine:init', () => {
|
||||
authToken: "Auth Token",
|
||||
saveConfig: "Save to ~/.claude/settings.json",
|
||||
envVar: "Env",
|
||||
// New Keys
|
||||
systemName: "ANTIGRAVITY",
|
||||
systemDesc: "CLAUDE PROXY SYSTEM",
|
||||
connectGoogleDesc: "Connect a Google Workspace account to increase your API quota limit. The account will be used to proxy Claude requests via Antigravity.",
|
||||
useCliCommand: "Use CLI Command",
|
||||
close: "Close",
|
||||
requestVolume: "Request Volume",
|
||||
filter: "Filter",
|
||||
all: "All",
|
||||
none: "None",
|
||||
noDataTracked: "No data tracked yet",
|
||||
selectFamilies: "Select families to display",
|
||||
selectModels: "Select models to display",
|
||||
noLogsMatch: "No logs match filter",
|
||||
connecting: "CONNECTING",
|
||||
main: "Main",
|
||||
system: "System",
|
||||
refreshData: "Refresh Data",
|
||||
connectionLost: "Connection Lost",
|
||||
lastUpdated: "Last Updated",
|
||||
grepLogs: "grep logs...",
|
||||
noMatchingModels: "No matching models",
|
||||
typeToSearch: "Type to search or select...",
|
||||
or: "OR",
|
||||
refreshingAccount: "Refreshing {email}...",
|
||||
refreshedAccount: "Refreshed {email}",
|
||||
refreshFailed: "Refresh failed",
|
||||
accountToggled: "Account {email} {status}",
|
||||
toggleFailed: "Toggle failed",
|
||||
reauthenticating: "Re-authenticating {email}...",
|
||||
authUrlFailed: "Failed to get auth URL",
|
||||
deletedAccount: "Deleted {email}",
|
||||
deleteFailed: "Delete failed",
|
||||
accountsReloaded: "Accounts reloaded",
|
||||
reloadFailed: "Reload failed",
|
||||
claudeConfigSaved: "Claude configuration saved",
|
||||
saveConfigFailed: "Failed to save configuration",
|
||||
claudeActive: "Claude Active",
|
||||
claudeEmpty: "Claude Empty",
|
||||
geminiActive: "Gemini Active",
|
||||
geminiEmpty: "Gemini Empty",
|
||||
fix: "FIX",
|
||||
synced: "SYNCED",
|
||||
syncing: "SYNCING...",
|
||||
// Additional
|
||||
reloading: "Reloading...",
|
||||
reloaded: "Reloaded",
|
||||
lines: "lines",
|
||||
enabledSeeLogs: "Enabled (See Logs)",
|
||||
production: "Production",
|
||||
configSaved: "Configuration Saved",
|
||||
enterPassword: "Enter Web UI Password:",
|
||||
ready: "READY",
|
||||
familyClaude: "Claude",
|
||||
familyGemini: "Gemini",
|
||||
familyOther: "Other",
|
||||
enabledStatus: "enabled",
|
||||
disabledStatus: "disabled",
|
||||
logLevelInfo: "INFO",
|
||||
logLevelSuccess: "SUCCESS",
|
||||
logLevelWarn: "WARN",
|
||||
logLevelError: "ERR",
|
||||
},
|
||||
zh: {
|
||||
dashboard: "仪表盘",
|
||||
@@ -148,6 +217,14 @@ document.addEventListener('alpine:init', () => {
|
||||
delete: "删除",
|
||||
confirmDelete: "确定要移除此账号吗?",
|
||||
connectGoogle: "连接 Google 账号",
|
||||
reauthenticated: "已重新认证",
|
||||
added: "已添加",
|
||||
successfully: "成功",
|
||||
failedToGetAuthUrl: "获取认证链接失败",
|
||||
failedToStartOAuth: "启动 OAuth 流程失败",
|
||||
family: "系列",
|
||||
model: "模型",
|
||||
activeSuffix: "活跃",
|
||||
manualReload: "重新加载配置",
|
||||
// Tabs
|
||||
tabInterface: "界面设置",
|
||||
@@ -196,14 +273,82 @@ document.addEventListener('alpine:init', () => {
|
||||
authToken: "认证令牌",
|
||||
saveConfig: "保存到 ~/.claude/settings.json",
|
||||
envVar: "环境变量",
|
||||
// New Keys
|
||||
systemName: "ANTIGRAVITY",
|
||||
systemDesc: "CLAUDE 代理系统",
|
||||
connectGoogleDesc: "连接 Google Workspace 账号以增加 API 配额。该账号将用于通过 Antigravity 代理 Claude 请求。",
|
||||
useCliCommand: "使用命令行",
|
||||
close: "关闭",
|
||||
requestVolume: "请求量",
|
||||
filter: "筛选",
|
||||
all: "全选",
|
||||
none: "清空",
|
||||
noDataTracked: "暂无追踪数据",
|
||||
selectFamilies: "选择要显示的系列",
|
||||
selectModels: "选择要显示的模型",
|
||||
noLogsMatch: "没有符合过滤条件的日志",
|
||||
connecting: "正在连接",
|
||||
main: "主菜单",
|
||||
system: "系统",
|
||||
refreshData: "刷新数据",
|
||||
connectionLost: "连接已断开",
|
||||
lastUpdated: "最后更新",
|
||||
grepLogs: "过滤日志...",
|
||||
noMatchingModels: "没有匹配的模型",
|
||||
typeToSearch: "输入以搜索或选择...",
|
||||
or: "或",
|
||||
refreshingAccount: "正在刷新 {email}...",
|
||||
refreshedAccount: "已完成刷新 {email}",
|
||||
refreshFailed: "刷新失败",
|
||||
accountToggled: "账号 {email} 已{status}",
|
||||
toggleFailed: "切换失败",
|
||||
reauthenticating: "正在重新认证 {email}...",
|
||||
authUrlFailed: "获取认证链接失败",
|
||||
deletedAccount: "已删除 {email}",
|
||||
deleteFailed: "删除失败",
|
||||
accountsReloaded: "账号配置已重载",
|
||||
reloadFailed: "重载失败",
|
||||
claudeConfigSaved: "Claude 配置已保存",
|
||||
saveConfigFailed: "保存配置失败",
|
||||
claudeActive: "Claude 活跃",
|
||||
claudeEmpty: "Claude 耗尽",
|
||||
geminiActive: "Gemini 活跃",
|
||||
geminiEmpty: "Gemini 耗尽",
|
||||
fix: "修复",
|
||||
synced: "已同步",
|
||||
syncing: "正在同步...",
|
||||
// Additional
|
||||
reloading: "正在重载...",
|
||||
reloaded: "已重载",
|
||||
lines: "行",
|
||||
enabledSeeLogs: "已启用 (见日志)",
|
||||
production: "生产环境",
|
||||
configSaved: "配置已保存",
|
||||
enterPassword: "请输入 Web UI 密码:",
|
||||
ready: "就绪",
|
||||
familyClaude: "Claude 系列",
|
||||
familyGemini: "Gemini 系列",
|
||||
familyOther: "其他系列",
|
||||
enabledStatus: "已启用",
|
||||
disabledStatus: "已禁用",
|
||||
logLevelInfo: "信息",
|
||||
logLevelSuccess: "成功",
|
||||
logLevelWarn: "警告",
|
||||
logLevelError: "错误",
|
||||
}
|
||||
},
|
||||
|
||||
// Toast Messages
|
||||
toast: null,
|
||||
|
||||
t(key) {
|
||||
return this.translations[this.lang][key] || key;
|
||||
t(key, params = {}) {
|
||||
let str = this.translations[this.lang][key] || key;
|
||||
if (typeof str === 'string') {
|
||||
Object.keys(params).forEach(p => {
|
||||
str = str.replace(`{${p}}`, params[p]);
|
||||
});
|
||||
}
|
||||
return str;
|
||||
},
|
||||
|
||||
setLang(l) {
|
||||
|
||||
Reference in New Issue
Block a user