From 5d68958b03d11a8a4cd8ac415483b1955ae7861e Mon Sep 17 00:00:00 2001 From: tbphp Date: Wed, 9 Sep 2026 16:40:39 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat(monitor):=20=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E4=B8=8E=E7=94=A8=E9=87=8F=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E7=AD=9B=E9=80=89=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ui/AppDateTimeRangePicker.vue | 3 +- .../monitor/LogsAdvancedFilterDrawer.vue | 65 ++------ web/src/features/monitor/LogsFilterForm.vue | 143 +++++++++-------- web/src/features/monitor/LogsTab.vue | 115 ++++++++------ web/src/features/monitor/MonitorView.vue | 148 +++++++++++------- web/src/features/monitor/log-filters.ts | 98 +++++------- web/src/features/monitor/monitor-route.ts | 6 +- web/src/lib/time.ts | 3 +- 8 files changed, 284 insertions(+), 297 deletions(-) diff --git a/web/src/components/ui/AppDateTimeRangePicker.vue b/web/src/components/ui/AppDateTimeRangePicker.vue index e8ab2b198..fae34ed0d 100644 --- a/web/src/components/ui/AppDateTimeRangePicker.vue +++ b/web/src/components/ui/AppDateTimeRangePicker.vue @@ -26,7 +26,6 @@ const props = defineProps<{ fromError?: string toError?: string preset?: DateTimePreset - rollingEndOffsetMs?: number applyLabel?: string applyDisabled?: boolean }>() @@ -74,7 +73,7 @@ function updateLocalInput(field: 'from' | 'to', value: string): void { function selectShortcut(preset: DateTimePreset): void { const now = Math.floor(Date.now() / 1000) * 1000 - const range = resolveDateTimePreset(preset, now, props.rollingEndOffsetMs ?? 24 * 60 * 60 * 1000) + const range = resolveDateTimePreset(preset, now) emit('update:from', localDateTimeInput(range.from_ms)) emit('update:to', localDateTimeInput(range.to_ms)) emit('update:preset', preset) diff --git a/web/src/features/monitor/LogsAdvancedFilterDrawer.vue b/web/src/features/monitor/LogsAdvancedFilterDrawer.vue index fb9380274..7da6ca75f 100644 --- a/web/src/features/monitor/LogsAdvancedFilterDrawer.vue +++ b/web/src/features/monitor/LogsAdvancedFilterDrawer.vue @@ -1,13 +1,14 @@ @@ -20,7 +24,12 @@ withDefaults( + + + +
+

{{ t('monitor.logs.filters.sections.timing') }}

+
+
+
+ +
+

{{ t('monitor.logs.filters.sections.usage') }}

+
+ + + @@ -312,39 +376,8 @@ function update(field: keyof LogFilterDraft, value: string): void {
-

{{ t('monitor.logs.filters.sections.result') }}

+

{{ t('monitor.logs.filters.sections.cost') }}

- - - - - - @@ -369,39 +402,12 @@ function update(field: keyof LogFilterDraft, value: string): void { :model-value="draft.pricing_completeness" :label="t('monitor.logs.filters.completenessLabel')" :options="completenessOptions()" - size="compact" + size="sm" @update:model-value="update('pricing_completeness', $event)" /> - - - -
-
- -
-

{{ t('monitor.logs.filters.sections.ranges') }}

-
[ label: `${group.name} · #${group.id}`, })), ]) +const channelOptions = computed(() => { + const options = [{ value: '', label: t('monitor.logs.filters.anyChannel') }] + if ( + props.draft.channel_id && + !props.channels.some((channel) => channel.channel_id === props.draft.channel_id) + ) { + options.push({ value: props.draft.channel_id, label: props.draft.channel_id }) + } + return [ + ...options, + ...props.channels.map((channel) => ({ value: channel.channel_id, label: channel.name })), + ] +}) +const protocolOptions = computed(() => [ + { value: '', label: t('monitor.logs.filters.anyProtocol') }, + ...enabledDataProtocols.map((value) => ({ value, label: value })), +]) const statusOptions = computed(() => [ { value: '', label: t('monitor.logs.filters.anyStatus') }, ...(['success', 'error', 'incomplete', 'canceled'] as const).map((value) => ({ @@ -104,6 +122,36 @@ function applyAdvanced(): void { :disabled="groupsFailed" @update:model-value="update('group_id', $event)" /> + + + + + + - - - - {{ t('monitor.logs.filters.apply') }} {{ t('monitor.logs.filters.reset') }} @@ -176,8 +209,6 @@ function applyAdvanced(): void { :open="advancedOpen" :draft="draft" :errors="errors" - :channels="channels" - :channels-failed="channelsFailed" :self-scoped="selfScoped" @update-field="update" @update:open="emit('update:advancedOpen', $event)" @@ -258,20 +289,23 @@ function applyAdvanced(): void { width: 170px; } -.logs-filter__model, -.logs-filter__request-id { +.logs-filter__channel { + width: 150px; +} + +.logs-filter__protocol { + width: 168px; +} + +.logs-filter__model { display: block; width: auto; min-width: 130px; flex: 1 1 160px; } -.logs-filter__request-id { - flex-basis: 220px; -} - +.logs-filter__channel :deep(.app-text-input), .logs-filter__model :deep(.app-text-input), -.logs-filter__request-id :deep(.app-text-input), .logs-filter__access-key :deep(.app-text-input), .logs-filter__access-key :deep(.access-key-select), .logs-filter__access-key :deep(.app-button) { @@ -287,7 +321,9 @@ function applyAdvanced(): void { } .logs-filter__group :deep(.app-select__trigger), -.logs-filter__status :deep(.app-select__trigger) { +.logs-filter__status :deep(.app-select__trigger), +.logs-filter__channel :deep(.app-select__trigger), +.logs-filter__protocol :deep(.app-select__trigger) { width: 100%; } @@ -304,6 +340,7 @@ function applyAdvanced(): void { .logs-filter__row > :deep(.app-button), .logs-filter__row > :deep(.app-select__trigger), + .logs-filter__channel :deep(.app-select__trigger), .logs-filter__access-key :deep(.app-button) { min-height: var(--touch-target); } @@ -316,7 +353,8 @@ function applyAdvanced(): void { } .logs-filter__access-key, - .logs-filter__request-id, + .logs-filter__channel, + .logs-filter__protocol, .logs-filter__group, .logs-filter__model, .logs-filter__status { diff --git a/web/src/features/monitor/LogsTab.vue b/web/src/features/monitor/LogsTab.vue index 6c784bf9e..1229c5596 100644 --- a/web/src/features/monitor/LogsTab.vue +++ b/web/src/features/monitor/LogsTab.vue @@ -144,16 +144,11 @@ const filterSignature = computed(() => ]), ) const allAdvancedFilterKeys: readonly (keyof RequestLogFilters)[] = [ - 'channel_id', - 'credential_id', - 'upstream_model', - 'protocol', + 'request_id', 'stream', 'final_status_code', - 'usage_state', - 'cost_state', - 'pricing_completeness', - 'cache_present', + 'credential_id', + 'upstream_model', 'attempt_status_code', 'failure_category', 'error_code', @@ -164,10 +159,14 @@ const allAdvancedFilterKeys: readonly (keyof RequestLogFilters)[] = [ 'first_response_max_ms', 'duration_min_ms', 'duration_max_ms', + 'usage_state', + 'cache_present', 'input_tokens_min', 'input_tokens_max', 'output_tokens_min', 'output_tokens_max', + 'cost_state', + 'pricing_completeness', 'cost_min_nano_usd', 'cost_max_nano_usd', ] @@ -201,6 +200,9 @@ const appliedChips = computed(() => { }), }) } + if (filters.channel_id !== undefined) { + values.push({ key: 'channel_id', label: advancedChipLabel('channel_id', filters.channel_id) }) + } if (filters.status !== undefined) { values.push({ key: 'status', @@ -211,8 +213,8 @@ const appliedChips = computed(() => { } for (const key of [ 'access_key_id', + 'protocol', 'client_model', - 'request_id', ...advancedFilterKeys.value, ] as const) { const value = filters[key] diff --git a/web/src/features/monitor/MonitorView.vue b/web/src/features/monitor/MonitorView.vue index d0dca23ce..efb06fca7 100644 --- a/web/src/features/monitor/MonitorView.vue +++ b/web/src/features/monitor/MonitorView.vue @@ -277,7 +277,7 @@ function resetTimeDraft(): void { function applyCustomTime(): void { const { from, to } = timeValues.value if (from === undefined || to === undefined || to <= from) return - applyTimeRange(from, to, timeDraft.value.preset) + applyTimeRange(from, to) } function applyTimeRange(from: number, to: number, preset?: DateTimePreset): void { diff --git a/web/src/i18n/locales/en-US/monitor.ts b/web/src/i18n/locales/en-US/monitor.ts index d4adc3fff..af7bc939d 100644 --- a/web/src/i18n/locales/en-US/monitor.ts +++ b/web/src/i18n/locales/en-US/monitor.ts @@ -675,13 +675,15 @@ export default { lastRefreshed: 'Last successful refresh', remove: 'Remove filter {value}', advancedTitle: 'More filters', - advancedDescription: 'Filter by request, retry attempts, final result, and numeric ranges.', + advancedDescription: + 'Filter by request, upstream attempts, response timing, tokens, and cost.', closeAdvanced: 'Close more filters', sections: { - request: 'Request', - attempt: 'Retry attempts', - result: 'Final result', - ranges: 'Numeric ranges', + request: 'Request and response', + attempt: 'Upstream attempts and retries', + timing: 'Response timing', + usage: 'Tokens and cache', + cost: 'Cost and pricing', }, retryState: { retried: 'Retried', not_retried: 'Not retried' }, usageState: { diff --git a/web/src/i18n/locales/ja-JP/monitor.ts b/web/src/i18n/locales/ja-JP/monitor.ts index 93b180fce..eb76be674 100644 --- a/web/src/i18n/locales/ja-JP/monitor.ts +++ b/web/src/i18n/locales/ja-JP/monitor.ts @@ -674,13 +674,15 @@ export default { lastRefreshed: '最終成功更新', remove: 'フィルター {value} を削除', advancedTitle: 'その他のフィルター', - advancedDescription: 'リクエスト、再試行、最終結果、数値範囲で絞り込みます。', + advancedDescription: + 'リクエスト、アップストリームの試行、応答時間、トークン、コストで絞り込みます。', closeAdvanced: 'その他のフィルターを閉じる', sections: { - request: 'リクエスト', - attempt: '再試行', - result: '最終結果', - ranges: '数値範囲', + request: 'リクエストとレスポンス', + attempt: 'アップストリームの試行と再試行', + timing: '応答時間', + usage: 'トークンとキャッシュ', + cost: 'コストと料金計算', }, retryState: { retried: '再試行あり', not_retried: '再試行なし' }, usageState: { diff --git a/web/src/i18n/locales/zh-CN/monitor.ts b/web/src/i18n/locales/zh-CN/monitor.ts index ab711c40a..2dafb840d 100644 --- a/web/src/i18n/locales/zh-CN/monitor.ts +++ b/web/src/i18n/locales/zh-CN/monitor.ts @@ -653,13 +653,14 @@ export default { lastRefreshed: '最近一次成功刷新', remove: '移除筛选条件 {value}', advancedTitle: '更多筛选', - advancedDescription: '按请求、重试过程、最终结果和数值范围精确筛选。', + advancedDescription: '按请求、上游尝试、耗时、Token 与成本精确筛选。', closeAdvanced: '关闭更多筛选', sections: { - request: '请求', - attempt: '重试过程', - result: '最终结果', - ranges: '数值范围', + request: '请求与响应', + attempt: '上游尝试与重试', + timing: '响应耗时', + usage: 'Token 与缓存', + cost: '成本与计价', }, retryState: { retried: '发生过重试', From ab6f0f37934569556f7f0856b97ad2ae2afd4497 Mon Sep 17 00:00:00 2001 From: tbphp Date: Wed, 9 Sep 2026 17:30:09 +0800 Subject: [PATCH 5/6] =?UTF-8?q?fix(monitor):=20=E5=AF=B9=E9=BD=90=E8=AE=BF?= =?UTF-8?q?=E9=97=AE=E5=AF=86=E9=92=A5=E7=AD=9B=E9=80=89=E4=B8=8B=E6=8B=89?= =?UTF-8?q?=E6=A1=86=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/access-keys/AccessKeySelect.vue | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/web/src/features/access-keys/AccessKeySelect.vue b/web/src/features/access-keys/AccessKeySelect.vue index 1814dc389..9be6f094e 100644 --- a/web/src/features/access-keys/AccessKeySelect.vue +++ b/web/src/features/access-keys/AccessKeySelect.vue @@ -51,13 +51,14 @@ function choose(id?: number): void { {{ selectedLabel }} -
@@ -90,12 +91,34 @@ function choose(id?: number): void {