fix(ops): 修正卡片标题翻译
- 卡片标题显示"请求" - 卡片内部标签保持"请求数"
This commit is contained in:
@@ -2047,7 +2047,8 @@ export default {
|
|||||||
avg: 'avg',
|
avg: 'avg',
|
||||||
max: 'max',
|
max: 'max',
|
||||||
qps: 'QPS',
|
qps: 'QPS',
|
||||||
requests: '请求',
|
requests: '请求数',
|
||||||
|
requestsTitle: '请求',
|
||||||
upstream: '上游',
|
upstream: '上游',
|
||||||
client: '客户端',
|
client: '客户端',
|
||||||
system: '系统',
|
system: '系统',
|
||||||
|
|||||||
@@ -7,19 +7,15 @@ import HelpTooltip from '@/components/common/HelpTooltip.vue'
|
|||||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||||
import Icon from '@/components/icons/Icon.vue'
|
import Icon from '@/components/icons/Icon.vue'
|
||||||
import { adminAPI } from '@/api'
|
import { adminAPI } from '@/api'
|
||||||
import { opsAPI, type OpsDashboardOverview, type OpsWSStatus, type OpsMetricThresholds, type OpsRealtimeTrafficSummary } from '@/api/admin/ops'
|
import { opsAPI, type OpsDashboardOverview, type OpsMetricThresholds, type OpsRealtimeTrafficSummary } from '@/api/admin/ops'
|
||||||
import type { OpsRequestDetailsPreset } from './OpsRequestDetailsModal.vue'
|
import type { OpsRequestDetailsPreset } from './OpsRequestDetailsModal.vue'
|
||||||
|
import { useAdminSettingsStore } from '@/stores'
|
||||||
import { formatNumber } from '@/utils/format'
|
import { formatNumber } from '@/utils/format'
|
||||||
|
|
||||||
type RealtimeWindow = '1min' | '5min' | '30min' | '1h'
|
type RealtimeWindow = '1min' | '5min' | '30min' | '1h'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
overview?: OpsDashboardOverview | null
|
overview?: OpsDashboardOverview | null
|
||||||
wsStatus: OpsWSStatus
|
|
||||||
wsReconnectInMs?: number | null
|
|
||||||
wsHasData?: boolean
|
|
||||||
realTimeQps: number
|
|
||||||
realTimeTps: number
|
|
||||||
platform: string
|
platform: string
|
||||||
groupId: number | null
|
groupId: number | null
|
||||||
timeRange: string
|
timeRange: string
|
||||||
@@ -45,6 +41,7 @@ const props = defineProps<Props>()
|
|||||||
const emit = defineEmits<Emits>()
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
const adminSettingsStore = useAdminSettingsStore()
|
||||||
|
|
||||||
const realtimeWindow = ref<RealtimeWindow>('1min')
|
const realtimeWindow = ref<RealtimeWindow>('1min')
|
||||||
|
|
||||||
@@ -76,6 +73,8 @@ watch(
|
|||||||
() => {
|
() => {
|
||||||
// The realtime window must be inside the toolbar window; reset to keep UX predictable.
|
// The realtime window must be inside the toolbar window; reset to keep UX predictable.
|
||||||
realtimeWindow.value = '1min'
|
realtimeWindow.value = '1min'
|
||||||
|
// Keep realtime traffic consistent with toolbar changes even when the window is already 1min.
|
||||||
|
loadRealtimeTrafficSummary()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -218,11 +217,32 @@ const totalTokensLabel = computed(() => formatNumber(overview.value?.token_consu
|
|||||||
const realtimeTrafficSummary = ref<OpsRealtimeTrafficSummary | null>(null)
|
const realtimeTrafficSummary = ref<OpsRealtimeTrafficSummary | null>(null)
|
||||||
const realtimeTrafficLoading = ref(false)
|
const realtimeTrafficLoading = ref(false)
|
||||||
|
|
||||||
|
function makeZeroRealtimeTrafficSummary(): OpsRealtimeTrafficSummary {
|
||||||
|
const now = new Date().toISOString()
|
||||||
|
return {
|
||||||
|
window: realtimeWindow.value,
|
||||||
|
start_time: now,
|
||||||
|
end_time: now,
|
||||||
|
platform: props.platform,
|
||||||
|
group_id: props.groupId,
|
||||||
|
qps: { current: 0, peak: 0, avg: 0 },
|
||||||
|
tps: { current: 0, peak: 0, avg: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadRealtimeTrafficSummary() {
|
async function loadRealtimeTrafficSummary() {
|
||||||
if (realtimeTrafficLoading.value) return
|
if (realtimeTrafficLoading.value) return
|
||||||
|
if (!adminSettingsStore.opsRealtimeMonitoringEnabled) {
|
||||||
|
realtimeTrafficSummary.value = makeZeroRealtimeTrafficSummary()
|
||||||
|
return
|
||||||
|
}
|
||||||
realtimeTrafficLoading.value = true
|
realtimeTrafficLoading.value = true
|
||||||
try {
|
try {
|
||||||
realtimeTrafficSummary.value = await opsAPI.getRealtimeTrafficSummary(realtimeWindow.value, props.platform, props.groupId)
|
const res = await opsAPI.getRealtimeTrafficSummary(realtimeWindow.value, props.platform, props.groupId)
|
||||||
|
if (res && res.enabled === false) {
|
||||||
|
adminSettingsStore.setOpsRealtimeMonitoringEnabledLocal(false)
|
||||||
|
}
|
||||||
|
realtimeTrafficSummary.value = res?.summary ?? null
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[OpsDashboardHeader] Failed to load realtime traffic summary', err)
|
console.error('[OpsDashboardHeader] Failed to load realtime traffic summary', err)
|
||||||
realtimeTrafficSummary.value = null
|
realtimeTrafficSummary.value = null
|
||||||
@@ -247,9 +267,19 @@ const { pause: pauseRealtimeTrafficRefresh, resume: resumeRealtimeTrafficRefresh
|
|||||||
{ immediate: false }
|
{ immediate: false }
|
||||||
)
|
)
|
||||||
|
|
||||||
onMounted(() => {
|
watch(
|
||||||
resumeRealtimeTrafficRefresh()
|
() => adminSettingsStore.opsRealtimeMonitoringEnabled,
|
||||||
})
|
(enabled) => {
|
||||||
|
if (enabled) {
|
||||||
|
resumeRealtimeTrafficRefresh()
|
||||||
|
} else {
|
||||||
|
pauseRealtimeTrafficRefresh()
|
||||||
|
// Keep UI stable when realtime monitoring is turned off.
|
||||||
|
realtimeTrafficSummary.value = makeZeroRealtimeTrafficSummary()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
pauseRealtimeTrafficRefresh()
|
pauseRealtimeTrafficRefresh()
|
||||||
@@ -257,24 +287,12 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
const displayRealTimeQps = computed(() => {
|
const displayRealTimeQps = computed(() => {
|
||||||
const v = realtimeTrafficSummary.value?.qps?.current
|
const v = realtimeTrafficSummary.value?.qps?.current
|
||||||
if (typeof v === 'number' && Number.isFinite(v)) return v
|
return typeof v === 'number' && Number.isFinite(v) ? v : 0
|
||||||
|
|
||||||
const ov = overview.value
|
|
||||||
if (!ov) return 0
|
|
||||||
const useRealtime = props.wsStatus === 'connected' && !!props.wsHasData
|
|
||||||
const fallback = useRealtime ? props.realTimeQps : ov.qps?.current
|
|
||||||
return typeof fallback === 'number' && Number.isFinite(fallback) ? fallback : 0
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const displayRealTimeTps = computed(() => {
|
const displayRealTimeTps = computed(() => {
|
||||||
const v = realtimeTrafficSummary.value?.tps?.current
|
const v = realtimeTrafficSummary.value?.tps?.current
|
||||||
if (typeof v === 'number' && Number.isFinite(v)) return v
|
return typeof v === 'number' && Number.isFinite(v) ? v : 0
|
||||||
|
|
||||||
const ov = overview.value
|
|
||||||
if (!ov) return 0
|
|
||||||
const useRealtime = props.wsStatus === 'connected' && !!props.wsHasData
|
|
||||||
const fallback = useRealtime ? props.realTimeTps : ov.tps?.current
|
|
||||||
return typeof fallback === 'number' && Number.isFinite(fallback) ? fallback : 0
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const realtimeQpsPeakLabel = computed(() => {
|
const realtimeQpsPeakLabel = computed(() => {
|
||||||
@@ -343,7 +361,7 @@ const ttftMaxMs = computed(() => overview.value?.ttft?.max_ms ?? null)
|
|||||||
const isSystemIdle = computed(() => {
|
const isSystemIdle = computed(() => {
|
||||||
const ov = overview.value
|
const ov = overview.value
|
||||||
if (!ov) return true
|
if (!ov) return true
|
||||||
const qps = props.wsStatus === 'connected' && props.wsHasData ? props.realTimeQps : ov.qps?.current
|
const qps = ov.qps?.current
|
||||||
const errorRate = ov.error_rate ?? 0
|
const errorRate = ov.error_rate ?? 0
|
||||||
return (qps ?? 0) === 0 && errorRate === 0
|
return (qps ?? 0) === 0 && errorRate === 0
|
||||||
})
|
})
|
||||||
@@ -786,6 +804,11 @@ const showJobsDetails = ref(false)
|
|||||||
function openJobsDetails() {
|
function openJobsDetails() {
|
||||||
showJobsDetails.value = true
|
showJobsDetails.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleToolbarRefresh() {
|
||||||
|
loadRealtimeTrafficSummary()
|
||||||
|
emit('refresh')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -863,7 +886,7 @@ function openJobsDetails() {
|
|||||||
class="flex h-8 w-8 items-center justify-center rounded-lg bg-gray-100 text-gray-500 transition-colors hover:bg-gray-200 dark:bg-dark-700 dark:text-gray-400 dark:hover:bg-dark-600"
|
class="flex h-8 w-8 items-center justify-center rounded-lg bg-gray-100 text-gray-500 transition-colors hover:bg-gray-200 dark:bg-dark-700 dark:text-gray-400 dark:hover:bg-dark-600"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
:title="t('common.refresh')"
|
:title="t('common.refresh')"
|
||||||
@click="emit('refresh')"
|
@click="handleToolbarRefresh"
|
||||||
>
|
>
|
||||||
<svg class="h-4 w-4" :class="{ 'animate-spin': loading }" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg class="h-4 w-4" :class="{ 'animate-spin': loading }" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path
|
<path
|
||||||
@@ -1125,7 +1148,7 @@ function openJobsDetails() {
|
|||||||
<div class="rounded-2xl bg-gray-50 p-4 dark:bg-dark-900">
|
<div class="rounded-2xl bg-gray-50 p-4 dark:bg-dark-900">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<span class="text-[10px] font-bold uppercase text-gray-400">{{ t('admin.ops.requests') }}</span>
|
<span class="text-[10px] font-bold uppercase text-gray-400">{{ t('admin.ops.requestsTitle') }}</span>
|
||||||
<HelpTooltip :content="t('admin.ops.tooltips.totalRequests')" />
|
<HelpTooltip :content="t('admin.ops.tooltips.totalRequests')" />
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user