diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index 26334162..43aeee41 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -2048,6 +2048,7 @@ export default { lastRun: '最近运行', lastSuccess: '最近成功', lastError: '最近错误', + result: '结果', noData: '暂无数据', loadingText: '加载中...', ready: '就绪', diff --git a/frontend/src/views/admin/AccountsView.vue b/frontend/src/views/admin/AccountsView.vue index 42f38c74..fbf3ee0a 100644 --- a/frontend/src/views/admin/AccountsView.vue +++ b/frontend/src/views/admin/AccountsView.vue @@ -414,7 +414,17 @@ const handleScroll = () => { menu.show = false } -onMounted(async () => { load(); try { const [p, g] = await Promise.all([adminAPI.proxies.getAll(), adminAPI.groups.getAll()]); proxies.value = p; groups.value = g } catch (error) { console.error('Failed to load proxies/groups:', error) } window.addEventListener('scroll', handleScroll, true) }) +onMounted(async () => { + load() + try { + const [p, g] = await Promise.all([adminAPI.proxies.getAll(), adminAPI.groups.getAll()]) + proxies.value = p + groups.value = g + } catch (error) { + console.error('Failed to load proxies/groups:', error) + } + window.addEventListener('scroll', handleScroll, true) +}) onUnmounted(() => { window.removeEventListener('scroll', handleScroll, true) diff --git a/frontend/src/views/admin/ops/OpsDashboard.vue b/frontend/src/views/admin/ops/OpsDashboard.vue index 033ef1da..72cb2607 100644 --- a/frontend/src/views/admin/ops/OpsDashboard.vue +++ b/frontend/src/views/admin/ops/OpsDashboard.vue @@ -355,23 +355,21 @@ const autoRefreshCountdown = ref(0) // Used to trigger child component refreshes in a single shared cadence. const dashboardRefreshToken = ref(0) -// Auto refresh timer -const { pause: pauseAutoRefresh, resume: resumeAutoRefresh } = useIntervalFn( - () => { - if (autoRefreshEnabled.value && opsEnabled.value && !loading.value) { - fetchData() - } - }, - autoRefreshIntervalMs, - { immediate: false } -) - -// Countdown timer (updates every second) +// Countdown timer (drives auto refresh; updates every second) const { pause: pauseCountdown, resume: resumeCountdown } = useIntervalFn( () => { - if (autoRefreshEnabled.value && autoRefreshCountdown.value > 0) { - autoRefreshCountdown.value-- + if (!autoRefreshEnabled.value) return + if (!opsEnabled.value) return + if (loading.value) return + + if (autoRefreshCountdown.value <= 0) { + // Fetch immediately when the countdown reaches 0. + // fetchData() will reset the countdown to the full interval. + fetchData() + return } + + autoRefreshCountdown.value -= 1 }, 1000, { immediate: false } @@ -477,12 +475,19 @@ function buildApiParams() { group_id: groupId.value ?? undefined, mode: queryMode.value } - if (timeRange.value === 'custom' && customStartTime.value && customEndTime.value) { - params.start_time = customStartTime.value - params.end_time = customEndTime.value + + if (timeRange.value === 'custom') { + if (customStartTime.value && customEndTime.value) { + params.start_time = customStartTime.value + params.end_time = customEndTime.value + } else { + // Safety fallback: avoid sending time_range=custom (backend may not support it) + params.time_range = '1h' + } } else { params.time_range = timeRange.value } + return params } @@ -679,7 +684,6 @@ onMounted(async () => { // Start auto refresh if enabled if (autoRefreshEnabled.value) { - resumeAutoRefresh() resumeCountdown() } }) @@ -697,7 +701,6 @@ async function loadThresholds() { onUnmounted(() => { window.removeEventListener('keydown', handleKeydown) abortDashboardFetch() - pauseAutoRefresh() pauseCountdown() }) @@ -705,10 +708,8 @@ onUnmounted(() => { watch(autoRefreshEnabled, (enabled) => { if (enabled) { autoRefreshCountdown.value = Math.floor(autoRefreshIntervalMs.value / 1000) - resumeAutoRefresh() resumeCountdown() } else { - pauseAutoRefresh() pauseCountdown() autoRefreshCountdown.value = 0 } diff --git a/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue b/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue index b5791850..f2a7d787 100644 --- a/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue +++ b/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue @@ -191,8 +191,10 @@ function handleCustomTimeRangeConfirm() { if (!customStartTimeInput.value || !customEndTimeInput.value) return const startTime = new Date(customStartTimeInput.value).toISOString() const endTime = new Date(customEndTimeInput.value).toISOString() - emit('update:timeRange', 'custom') + // Emit custom time range first so the parent can build correct API params + // when it reacts to timeRange switching to "custom". emit('update:customTimeRange', startTime, endTime) + emit('update:timeRange', 'custom') showCustomTimeRangeDialog.value = false } @@ -221,8 +223,14 @@ function getSLAThresholdLevel(slaPercent: number | null): ThresholdLevel { if (slaPercent == null) return 'normal' const threshold = props.thresholds?.sla_percent_min if (threshold == null) return 'normal' + + // SLA is "higher is better": + // - below threshold => critical + // - within +0.1% buffer => warning + const warningBuffer = 0.1 + if (slaPercent < threshold) return 'critical' - if (slaPercent < threshold / 0.8) return 'warning' + if (slaPercent < threshold + warningBuffer) return 'warning' return 'normal' }