feat: 新增自用模式下的 API 密钥统计功能#4959
Conversation
|
Caution Review failedFailed to post review comments WalkthroughAdds API key consumption statistics: backend aggregation and flag, two HTTP endpoints (admin/self) gated by self-use mode and flag, frontend types/API and KeyCharts UI, chart processing, admin toggle and settings wiring, tests, and translations. ChangesAPI Key Consumption Statistics
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (2)
web/default/src/i18n/locales/en.json (1)
342-344: ⚡ Quick winUse hierarchical i18n keys for the new API-key analytics strings.
These new entries should follow semantic hierarchical keys (for example,
dashboard.apiKeyStats.title,dashboard.apiKeyStats.ranking, etc.) instead of full-sentence/source-text keys, to keep i18n key naming consistent and maintainable.As per coding guidelines, "Use hierarchical and semantically clear translation key names such as
dashboard.overview.titleand maintain naming consistency".Also applies to: 1374-1374, 3632-3632, 4033-4033, 4326-4326
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/default/src/i18n/locales/en.json` around lines 342 - 344, Replace the flat translation keys "API Key Analytics", "API Key Consumption Ranking", and "API Key Consumption Trend" with hierarchical, semantic keys (for example dashboard.apiKeyStats.title, dashboard.apiKeyStats.ranking, and dashboard.apiKeyStats.trend) in en.json and update any code that consumes those keys to use the new names; also apply the same hierarchical naming convention to the other mentioned occurrences (lines referenced: 1374, 3632, 4033, 4326) so all API-key analytics entries follow dashboard.apiKeyStats.*.web/default/src/i18n/locales/zh.json (1)
342-344: 🏗️ Heavy liftUse hierarchical i18n keys for new API-key analytics entries.
These newly added keys use phrase-as-key style and break the required naming convention. Please migrate them to semantic namespaces (for example,
dashboard.apiKey.analytics.title,dashboard.apiKey.ranking.title,settings.dashboard.apiKeyStats.enable) and keep aliases only if backward compatibility is needed.As per coding guidelines, "Use hierarchical and semantically clear translation key names such as
dashboard.overview.titleand maintain naming consistency".Also applies to: 1374-1374, 3632-3632, 4033-4033, 4326-4326
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/default/src/i18n/locales/zh.json` around lines 342 - 344, The three new phrase-as-key entries ("API Key Analytics", "API Key Consumption Ranking", "API Key Consumption Trend") violate the project's hierarchical i18n convention; replace them with semantic namespace keys such as dashboard.apiKey.analytics.title, dashboard.apiKey.ranking.title, and dashboard.apiKey.trend.title in zh.json, move their Chinese translations under those new keys, and optionally keep the original phrase keys as short-lived aliases mapping to the new keys if backward compatibility is required (apply the same migration pattern to the other mentioned occurrences).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@controller/log.go`:
- Around line 154-167: The endpoint handlers (e.g., GetLogStatsByToken and the
similar handler around lines 170-184 that call model.GetLogStatsByToken) must
enforce the ApiKeyStatsEnabled feature flag (and self-use mode rules) before
performing the query; add a guard at the top of each handler that checks
config.ApiKeyStatsEnabled (or the appropriate self-use check) and return an HTTP
403/404 JSON response (using common.ApiError or c.JSON) when disabled, avoiding
the call to model.GetLogStatsByToken so stats are not exposed via direct API
calls.
In `@model/log_token_stat.go`:
- Around line 19-43: GetLogStatsByToken currently allows unbounded full-history
scans when startTime and endTime are both zero; change the function to guard by
requiring a bounded time range: if startTime and endTime are both zero, set a
sensible default window (e.g., endTime = now, startTime = now - 24h) and enforce
a maximum allowed range (e.g., cap range to 30 days by adjusting startTime if
endTime-startTime exceeds maxRange); apply these normalized startTime/endTime
values before building the LOG_DB query so the resulting query (used by
GetLogStatsByToken and the query variable) is always time-bounded.
In `@web/default/src/features/dashboard/components/keys/key-charts.tsx`:
- Around line 45-48: The code keys per-API-key data by token_name which
collapses distinct keys with the same label; update the mapping functions (e.g.,
mapToQuotaData and any other mappers that currently use token_name as the unique
key) to use a stable identifier like token_id (or a composite like
`${token_id}:${token_name}`) for uniqueness — set model_name/series key to
`key-${item.token_id ?? 'unknown'}` (and keep token_name as a separate display
label field if needed) so Top-N, trends and totals no longer merge different
keys with identical names.
In `@web/default/src/i18n/locales/ja.json`:
- Line 3632: The Japanese translation for the string "Show API key consumption
statistics tab in the dashboard. Only available in self-use mode." uses the
Chinese term "自用モード"; update the value in ja.json to use a proper Japanese term
such as "セルフユースモード" (or "セルフユース") so the entry reads:
ダッシュボードにAPIキー消費統計タブを表示します。セルフユースモードでのみ利用できます。, keeping the rest of the sentence
unchanged and ensuring UTF-8 encoding.
In `@web/default/src/i18n/locales/ru.json`:
- Line 4326: Update the Russian translation value for the key "View API key
consumption statistics and charts" in ru.json to include the missing "and
charts" portion; replace the current value "Просмотр статистики потребления
API-ключей" with a complete translation such as "Просмотр статистики потребления
API-ключей и диаграмм" (or another appropriate Russian phrasing that includes
both statistics and charts).
In `@web/default/src/i18n/locales/vi.json`:
- Line 4033: The vi locale entry for the key "Top Keys" is still in English;
update the value for the "Top Keys" key in web/default/src/i18n/locales/vi.json
to a proper Vietnamese translation (e.g., "Các khóa hàng đầu" or another
context-appropriate phrase) so the UI is fully localized for the vi locale.
---
Nitpick comments:
In `@web/default/src/i18n/locales/en.json`:
- Around line 342-344: Replace the flat translation keys "API Key Analytics",
"API Key Consumption Ranking", and "API Key Consumption Trend" with
hierarchical, semantic keys (for example dashboard.apiKeyStats.title,
dashboard.apiKeyStats.ranking, and dashboard.apiKeyStats.trend) in en.json and
update any code that consumes those keys to use the new names; also apply the
same hierarchical naming convention to the other mentioned occurrences (lines
referenced: 1374, 3632, 4033, 4326) so all API-key analytics entries follow
dashboard.apiKeyStats.*.
In `@web/default/src/i18n/locales/zh.json`:
- Around line 342-344: The three new phrase-as-key entries ("API Key Analytics",
"API Key Consumption Ranking", "API Key Consumption Trend") violate the
project's hierarchical i18n convention; replace them with semantic namespace
keys such as dashboard.apiKey.analytics.title, dashboard.apiKey.ranking.title,
and dashboard.apiKey.trend.title in zh.json, move their Chinese translations
under those new keys, and optionally keep the original phrase keys as
short-lived aliases mapping to the new keys if backward compatibility is
required (apply the same migration pattern to the other mentioned occurrences).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 1f837b07-96e6-4760-93b8-910b502c2224
📒 Files selected for processing (27)
common/constants.gocontroller/log.gocontroller/misc.gomodel/log_token_stat.gomodel/option.gorouter/api-router.goweb/default/src/components/ui/sidebar.tsxweb/default/src/features/auth/types.tsweb/default/src/features/dashboard/api.tsweb/default/src/features/dashboard/components/keys/key-charts.tsxweb/default/src/features/dashboard/components/models/model-charts.tsxweb/default/src/features/dashboard/index.tsxweb/default/src/features/dashboard/lib/charts.tsweb/default/src/features/dashboard/lib/index.tsweb/default/src/features/dashboard/section-registry.tsxweb/default/src/features/dashboard/types.tsweb/default/src/features/system-settings/content/dashboard-section.tsxweb/default/src/features/system-settings/content/index.tsxweb/default/src/features/system-settings/content/section-registry.tsxweb/default/src/features/system-settings/hooks/use-update-option.tsweb/default/src/features/system-settings/types.tsweb/default/src/i18n/locales/en.jsonweb/default/src/i18n/locales/fr.jsonweb/default/src/i18n/locales/ja.jsonweb/default/src/i18n/locales/ru.jsonweb/default/src/i18n/locales/vi.jsonweb/default/src/i18n/locales/zh.json
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
controller/log_token_stat_test.go (1)
3-26: ⚡ Quick winSerialize global flag overrides in tests.
withApiKeyStatsSettingsmutates package-level globals; adding a mutex prevents cross-test interference if these tests (or neighboring ones) are parallelized later.Suggested patch
import ( "net/http" "net/http/httptest" + "sync" "testing" @@ "github.com/stretchr/testify/require" ) +var apiKeyStatsSettingsMu sync.Mutex + func withApiKeyStatsSettings(t *testing.T, selfUseModeEnabled, apiKeyStatsEnabled bool) { t.Helper() + apiKeyStatsSettingsMu.Lock() originalSelfUseModeEnabled := operation_setting.SelfUseModeEnabled originalApiKeyStatsEnabled := common.ApiKeyStatsEnabled operation_setting.SelfUseModeEnabled = selfUseModeEnabled common.ApiKeyStatsEnabled = apiKeyStatsEnabled t.Cleanup(func() { operation_setting.SelfUseModeEnabled = originalSelfUseModeEnabled common.ApiKeyStatsEnabled = originalApiKeyStatsEnabled + apiKeyStatsSettingsMu.Unlock() }) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@controller/log_token_stat_test.go` around lines 3 - 26, withApiKeyStatsSettings currently mutates package-level globals (operation_setting.SelfUseModeEnabled and common.ApiKeyStatsEnabled) without synchronization; add a package-level mutex (e.g., var apiKeyStatsMu sync.Mutex) and acquire it at the start of withApiKeyStatsSettings, then release it in the t.Cleanup closure (unlock inside the cleanup after restoring original values) so the global overrides are serialized across tests and cannot race when tests run in parallel.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@web/default/src/features/dashboard/components/keys/key-charts.tsx`:
- Around line 186-236: These toggle buttons do not expose their selected state
to assistive tech; update each button rendering for the presets,
TIME_GRANULARITY_OPTIONS and TOP_KEY_LIMIT_OPTIONS to include an aria-pressed
attribute based on the current state (use selectedRange for the preset buttons,
timeGranularity for the granularity buttons, and topKeyLimit for the top-key
buttons) so that handleRangeChange, handleGranularityChange and setTopKeyLimit
still work as-is but screen readers will know which option is active.
---
Nitpick comments:
In `@controller/log_token_stat_test.go`:
- Around line 3-26: withApiKeyStatsSettings currently mutates package-level
globals (operation_setting.SelfUseModeEnabled and common.ApiKeyStatsEnabled)
without synchronization; add a package-level mutex (e.g., var apiKeyStatsMu
sync.Mutex) and acquire it at the start of withApiKeyStatsSettings, then release
it in the t.Cleanup closure (unlock inside the cleanup after restoring original
values) so the global overrides are serialized across tests and cannot race when
tests run in parallel.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4fc5e288-1453-4d10-8fe7-0b186a69966b
📒 Files selected for processing (9)
controller/log.gocontroller/log_token_stat_test.gomodel/log_token_stat.gomodel/log_token_stat_test.goweb/default/src/features/dashboard/components/keys/key-charts.tsxweb/default/src/features/dashboard/lib/charts.tsweb/default/src/i18n/locales/ja.jsonweb/default/src/i18n/locales/ru.jsonweb/default/src/i18n/locales/vi.json
🚧 Files skipped from review as they are similar to previous changes (6)
- controller/log.go
- web/default/src/features/dashboard/lib/charts.ts
- model/log_token_stat.go
- web/default/src/i18n/locales/ja.json
- web/default/src/i18n/locales/ru.json
- web/default/src/i18n/locales/vi.json
| <button | ||
| key={preset.days} | ||
| type='button' | ||
| onClick={() => handleRangeChange(preset.days)} | ||
| className={`rounded-md px-2.5 py-1 text-xs font-medium transition-colors ${ | ||
| selectedRange === preset.days | ||
| ? 'bg-primary text-primary-foreground shadow-sm' | ||
| : 'text-muted-foreground hover:bg-muted hover:text-foreground' | ||
| }`} | ||
| > | ||
| {t(preset.label)} | ||
| </button> | ||
| ))} | ||
| </div> | ||
|
|
||
| {/* Time granularity */} | ||
| <div className='flex shrink-0 items-center gap-1.5 rounded-lg border p-0.5'> | ||
| {TIME_GRANULARITY_OPTIONS.map((opt) => ( | ||
| <button | ||
| key={opt.value} | ||
| type='button' | ||
| onClick={() => handleGranularityChange(opt.value as TimeGranularity)} | ||
| className={`rounded-md px-2.5 py-1 text-xs font-medium transition-colors ${ | ||
| timeGranularity === opt.value | ||
| ? 'bg-primary text-primary-foreground shadow-sm' | ||
| : 'text-muted-foreground hover:bg-muted hover:text-foreground' | ||
| }`} | ||
| > | ||
| {t(opt.label)} | ||
| </button> | ||
| ))} | ||
| </div> | ||
|
|
||
| <div className='flex shrink-0 items-center gap-1.5 rounded-lg border p-0.5'> | ||
| <span className='text-muted-foreground px-2 text-xs font-medium'> | ||
| {t('Top Keys')} | ||
| </span> | ||
| {TOP_KEY_LIMIT_OPTIONS.map((limit) => ( | ||
| <button | ||
| key={limit} | ||
| type='button' | ||
| onClick={() => setTopKeyLimit(limit)} | ||
| className={`rounded-md px-2.5 py-1 text-xs font-medium transition-colors ${ | ||
| topKeyLimit === limit | ||
| ? 'bg-primary text-primary-foreground shadow-sm' | ||
| : 'text-muted-foreground hover:bg-muted hover:text-foreground' | ||
| }`} | ||
| > | ||
| {t('Top {{count}}', { count: limit })} | ||
| </button> | ||
| ))} |
There was a problem hiding this comment.
Expose selected state on toggle buttons with aria-pressed.
These button groups behave like toggles; without aria-pressed, screen readers can’t reliably announce which option is active.
Suggested patch
{TIME_RANGE_PRESETS.map((preset) => (
<button
key={preset.days}
type='button'
+ aria-pressed={selectedRange === preset.days}
onClick={() => handleRangeChange(preset.days)}
className={`rounded-md px-2.5 py-1 text-xs font-medium transition-colors ${
@@
{TIME_GRANULARITY_OPTIONS.map((opt) => (
<button
key={opt.value}
type='button'
+ aria-pressed={timeGranularity === opt.value}
onClick={() => handleGranularityChange(opt.value as TimeGranularity)}
className={`rounded-md px-2.5 py-1 text-xs font-medium transition-colors ${
@@
{TOP_KEY_LIMIT_OPTIONS.map((limit) => (
<button
key={limit}
type='button'
+ aria-pressed={topKeyLimit === limit}
onClick={() => setTopKeyLimit(limit)}
className={`rounded-md px-2.5 py-1 text-xs font-medium transition-colors ${🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/default/src/features/dashboard/components/keys/key-charts.tsx` around
lines 186 - 236, These toggle buttons do not expose their selected state to
assistive tech; update each button rendering for the presets,
TIME_GRANULARITY_OPTIONS and TOP_KEY_LIMIT_OPTIONS to include an aria-pressed
attribute based on the current state (use selectedRange for the preset buttons,
timeGranularity for the granularity buttons, and topKeyLimit for the top-key
buttons) so that handleRangeChange, handleGranularityChange and setTopKeyLimit
still work as-is but screen readers will know which option is active.
# Conflicts: # controller/misc.go
📝 变更描述 / Description
新增自用模式下的 API 密钥统计功能
在「数据看板」中新增「API 密钥统计」功能,展示各 API Key 的相关统计图表,方便自用模式下多API用户的数据展示。
核心逻辑:直接对
logs表按token_id/token_name进行小时级聚合,无需新建表或字段。复用了前端现有的
ConsumptionDistributionChart和ModelCharts组件,通过将token_name映射到model_name字段实现数据复用。新添加的功能默认关闭,需同时满足两个条件才可见:
目的是防止非自用模式下,过多的API导致影响使用。
修复侧边栏视觉问题
使用过程中,发现侧边栏选中项与相邻项的背景会因圆角相接而粘连。
将
SidebarMenu的gap-0改为gap-0.5后,消除了该问题。🚀 变更类型 / Type of change
✅ 提交前检查项 / Checklist
go build、bun build、docker build --no-cache均通过,并使用生产数据库备份手动验证了接口返回与图表渲染📸 运行证明 / Proof of Work
API 密钥统计页:

数据仪表盘控制开关:

粘连修复效果:

Summary by CodeRabbit