fix(monitor-ui): localize the "quota" placeholder model across monitor views

纯配额模式的主模型存储值是占位符 "quota"(数据源是关联账号而非模型),
此前在管理列表主模型列、用户端监控卡片、用户端详情弹窗、运行结果弹窗
四处被当作真实模型名直接渲染,读起来像假模型。

新增 useChannelMonitorFormat.formatMonitorModel:占位符替换为本地化
「配额」标签(复用既有 monitorCommon.checkMode.quota,零新增 i18n 键),
quota_probe/probe 的真实模型名不受影响;纯匹配逻辑(如 15d/30d 可用率
按模型名回查 detail)仍用原始值。

问题定位备注:另一反馈「设置页看不到 channel_monitor_show_quota 开关」
经查为部署镜像过旧(开关随 #5761 于 08-18 合入,反馈环境容器构建于
08-15),上游代码中开关完整存在,更新镜像即可,无需改码。
This commit is contained in:
Randark
2026-08-18 10:35:57 +00:00
parent c9effc4560
commit 2c250bfd73
7 changed files with 112 additions and 8 deletions
@@ -1,7 +1,8 @@
<template>
<div class="flex flex-col gap-0.5">
<div class="flex items-center gap-2">
<span class="text-sm text-gray-900 dark:text-gray-100">{{ row.primary_model }}</span>
<!-- 纯配额模式主模型是占位符 "quota"(数据源是账号不是模型),展示层替换为本地化标签 -->
<span class="text-sm text-gray-900 dark:text-gray-100">{{ formatMonitorModel(row.primary_model) }}</span>
<HelpTooltip>
<template #trigger>
<span
@@ -13,7 +14,7 @@
</template>
<div class="space-y-2">
<div class="text-xs font-semibold text-gray-100">
{{ row.primary_model }}
{{ formatMonitorModel(row.primary_model) }}
<span
class="ml-1 inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-medium"
:class="statusBadgeClass(row.primary_status)"
@@ -72,5 +73,5 @@ defineProps<{
}>()
const { t } = useI18n()
const { statusLabel, statusBadgeClass, formatLatency } = useChannelMonitorFormat()
const { statusLabel, statusBadgeClass, formatLatency, formatMonitorModel } = useChannelMonitorFormat()
</script>
@@ -12,7 +12,7 @@
class="flex items-center justify-between rounded-lg border border-gray-200 px-3 py-2 text-sm dark:border-dark-600"
>
<div class="flex flex-col">
<span class="font-medium text-gray-900 dark:text-white">{{ r.model }}</span>
<span class="font-medium text-gray-900 dark:text-white">{{ formatMonitorModel(r.model) }}</span>
<span v-if="r.message" class="text-xs text-gray-500 dark:text-gray-400">{{ r.message }}</span>
<MonitorQuotaView :snapshot="r.quota" class="mt-1" />
</div>
@@ -54,5 +54,5 @@ defineEmits<{
}>()
const { t } = useI18n()
const { statusLabel, statusBadgeClass, formatLatency } = useChannelMonitorFormat()
const { statusLabel, statusBadgeClass, formatLatency, formatMonitorModel } = useChannelMonitorFormat()
</script>
@@ -0,0 +1,73 @@
import { mount } from '@vue/test-utils'
import { describe, expect, it, vi } from 'vitest'
import type { ChannelMonitor } from '@/api/admin/channelMonitor'
import MonitorPrimaryModelCell from '../MonitorPrimaryModelCell.vue'
// 占位符 "quota" 是纯配额模式主模型的存储值(数据源是账号不是模型),
// 展示层必须替换为本地化标签,不得作为假模型名透出。
vi.mock('vue-i18n', async () => {
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
return {
...actual,
useI18n: () => ({ t: (key: string) => key }),
}
})
function makeRow(overrides: Partial<ChannelMonitor> = {}): ChannelMonitor {
return {
id: 1,
name: 'kimi-coding',
provider: 'kimi',
api_mode: 'chat_completions',
endpoint: '',
api_key_masked: '',
primary_model: 'quota',
extra_models: [],
group_name: '',
enabled: true,
interval_seconds: 60,
jitter_seconds: 0,
last_checked_at: null,
created_by: 1,
created_at: '2026-08-18T00:00:00Z',
updated_at: '2026-08-18T00:00:00Z',
primary_status: 'operational',
primary_latency_ms: null,
availability_7d: 100,
extra_models_status: [],
template_id: null,
extra_headers: {},
body_override_mode: 'off',
body_override: null,
check_mode: 'quota',
account_id: 5,
...overrides,
}
}
function mountCell(row: ChannelMonitor) {
return mount(MonitorPrimaryModelCell, {
props: { row },
global: {
stubs: { MonitorQuotaView: true, HelpTooltip: false },
},
})
}
describe('MonitorPrimaryModelCell placeholder model display', () => {
it('replaces the quota placeholder with the localized mode label', () => {
const wrapper = mountCell(makeRow())
expect(wrapper.text()).toContain('monitorCommon.checkMode.quota')
})
it('keeps the real model for quota_probe and probe rows', () => {
const wrapper = mountCell(makeRow({
check_mode: 'quota_probe',
primary_model: 'claude-sonnet-4-5',
}))
expect(wrapper.text()).toContain('claude-sonnet-4-5')
expect(wrapper.text()).not.toContain('monitorCommon.checkMode.quota')
})
})
@@ -30,7 +30,7 @@
:key="m.model"
class="border-b border-gray-100 dark:border-dark-800"
>
<td class="py-2 pr-3 font-medium text-gray-900 dark:text-gray-100">{{ m.model }}</td>
<td class="py-2 pr-3 font-medium text-gray-900 dark:text-gray-100">{{ formatMonitorModel(m.model) }}</td>
<td class="py-2 pr-3">
<span
class="inline-flex items-center rounded-full px-2 py-0.5 text-[11px]"
@@ -83,7 +83,7 @@ defineEmits<{
const { t } = useI18n()
const appStore = useAppStore()
const { statusLabel, statusBadgeClass, formatLatency, formatPercent } = useChannelMonitorFormat()
const { statusLabel, statusBadgeClass, formatLatency, formatPercent, formatMonitorModel } = useChannelMonitorFormat()
const detail = ref<UserMonitorDetail | null>(null)
const loading = ref(false)
@@ -88,4 +88,16 @@ describe('MonitorCard quota snapshot visibility', () => {
const wrapper = mountCard(makeItem())
expect(wrapper.find('[data-testid="monitor-quota-view"]').exists()).toBe(false)
})
// 占位符 "quota" 是主模型存储值,不得作为假模型名直接透出到用户端。
it('shows the localized quota label instead of the raw placeholder model', () => {
const wrapper = mountCard(makeItem())
expect(wrapper.text()).toContain('monitorCommon.checkMode.quota')
})
it('keeps the real model name for probe monitors', () => {
const wrapper = mountCard(makeItem({ primary_model: 'claude-sonnet-4-5' }))
expect(wrapper.text()).toContain('claude-sonnet-4-5')
expect(wrapper.text()).not.toContain('monitorCommon.checkMode.quota')
})
})
@@ -23,8 +23,9 @@
>
{{ providerLabel(item.provider) }}
</span>
<!-- 纯配额模式主模型是占位符 "quota",展示层替换为本地化「配额」标签 -->
<span class="font-mono text-xs truncate text-gray-500 dark:text-gray-400">
{{ item.primary_model }}
{{ formatMonitorModel(item.primary_model) }}
</span>
<span
v-if="item.group_name"
@@ -120,6 +121,7 @@ const {
providerLabel,
providerBadgeClass,
formatLatency,
formatMonitorModel,
} = useChannelMonitorFormat()
const providerTintClass = computed(() =>
@@ -79,6 +79,21 @@ export function useChannelMonitorFormat() {
return m || '-'
}
/**
* Display label for a monitor's primary model. Pure-quota monitors carry the
* literal placeholder "quota" (the probe target is an account, not a model),
* which must not leak into the UI as a fake model name — render the
* localized mode label instead. quota_probe keeps a real model name.
*/
const QUOTA_MODEL_PLACEHOLDER = 'quota'
function formatMonitorModel(model: string): string {
if (model === QUOTA_MODEL_PLACEHOLDER) {
return t('monitorCommon.checkMode.quota')
}
return model
}
function providerBadgeClass(p: Provider | string): string {
switch (p) {
case PROVIDER_OPENAI:
@@ -200,6 +215,7 @@ export function useChannelMonitorFormat() {
statusBadgeClass,
providerLabel,
checkModeLabel,
formatMonitorModel,
providerBadgeClass,
checkModeBadgeClass,
providerPickerClass,