feat(ops): 添加自动刷新配置功能
功能特性: - 支持配置启用/禁用自动刷新 - 可配置刷新间隔(15秒/30秒/60秒) - 实时倒计时显示,用户可见下次刷新时间 - 手动刷新自动重置倒计时 - 页面卸载时自动清理定时器 用户体验: - 默认禁用,用户可根据需求开启 - 与现有 OpsConcurrencyCard 5秒刷新保持一致 - 倒计时带旋转动画,视觉反馈清晰 - 配置修改后立即生效,无需刷新页面 技术实现: - ops.ts: 添加 auto_refresh_enabled 和 auto_refresh_interval_seconds 配置 - OpsSettingsDialog.vue: 添加自动刷新配置界面 - OpsDashboard.vue: 实现主刷新逻辑和双定时器设计 - OpsDashboardHeader.vue: 倒计时显示组件 配置说明: - auto_refresh_enabled: 是否启用(默认 false) - auto_refresh_interval_seconds: 刷新间隔(默认 30 秒,范围 15-300 秒)
This commit is contained in:
@@ -734,6 +734,9 @@ export interface OpsAlertRuntimeSettings {
|
||||
export interface OpsAdvancedSettings {
|
||||
data_retention: OpsDataRetentionSettings
|
||||
aggregation: OpsAggregationSettings
|
||||
ignore_count_tokens_errors: boolean
|
||||
auto_refresh_enabled: boolean
|
||||
auto_refresh_interval_seconds: number
|
||||
}
|
||||
|
||||
export interface OpsDataRetentionSettings {
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
:loading="loading"
|
||||
:last-updated="lastUpdated"
|
||||
:thresholds="metricThresholds"
|
||||
:auto-refresh-enabled="autoRefreshEnabled"
|
||||
:auto-refresh-countdown="autoRefreshCountdown"
|
||||
@update:time-range="onTimeRangeChange"
|
||||
@update:platform="onPlatformChange"
|
||||
@update:group="onGroupChange"
|
||||
@@ -104,7 +106,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { useDebounceFn } from '@vueuse/core'
|
||||
import { useDebounceFn, useIntervalFn } from '@vueuse/core'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import AppLayout from '@/components/layout/AppLayout.vue'
|
||||
@@ -287,6 +289,45 @@ const requestDetailsPreset = ref<OpsRequestDetailsPreset>({
|
||||
const showSettingsDialog = ref(false)
|
||||
const showAlertRulesCard = ref(false)
|
||||
|
||||
// Auto refresh settings
|
||||
const autoRefreshEnabled = ref(false)
|
||||
const autoRefreshIntervalMs = ref(30000) // default 30 seconds
|
||||
const autoRefreshCountdown = ref(0)
|
||||
|
||||
// Auto refresh timer
|
||||
const { pause: pauseAutoRefresh, resume: resumeAutoRefresh, isActive: isAutoRefreshActive } = useIntervalFn(
|
||||
() => {
|
||||
if (autoRefreshEnabled.value && opsEnabled.value && !loading.value) {
|
||||
fetchData()
|
||||
}
|
||||
},
|
||||
autoRefreshIntervalMs,
|
||||
{ immediate: false }
|
||||
)
|
||||
|
||||
// Countdown timer (updates every second)
|
||||
const { pause: pauseCountdown, resume: resumeCountdown } = useIntervalFn(
|
||||
() => {
|
||||
if (autoRefreshEnabled.value && autoRefreshCountdown.value > 0) {
|
||||
autoRefreshCountdown.value--
|
||||
}
|
||||
},
|
||||
1000,
|
||||
{ immediate: false }
|
||||
)
|
||||
|
||||
// Load auto refresh settings from backend
|
||||
async function loadAutoRefreshSettings() {
|
||||
try {
|
||||
const settings = await opsAPI.getAdvancedSettings()
|
||||
autoRefreshEnabled.value = settings.auto_refresh_enabled
|
||||
autoRefreshIntervalMs.value = settings.auto_refresh_interval_seconds * 1000
|
||||
autoRefreshCountdown.value = settings.auto_refresh_interval_seconds
|
||||
} catch (err) {
|
||||
console.error('[OpsDashboard] Failed to load auto refresh settings', err)
|
||||
}
|
||||
}
|
||||
|
||||
function handleThroughputSelectPlatform(nextPlatform: string) {
|
||||
platform.value = nextPlatform || ''
|
||||
groupId.value = null
|
||||
@@ -510,6 +551,10 @@ async function fetchData() {
|
||||
])
|
||||
if (fetchSeq !== dashboardFetchSeq) return
|
||||
lastUpdated.value = new Date()
|
||||
// Reset auto refresh countdown after successful fetch
|
||||
if (autoRefreshEnabled.value) {
|
||||
autoRefreshCountdown.value = Math.floor(autoRefreshIntervalMs.value / 1000)
|
||||
}
|
||||
} catch (err) {
|
||||
if (!isOpsDisabledError(err)) {
|
||||
console.error('[ops] failed to fetch dashboard data', err)
|
||||
@@ -567,9 +612,18 @@ onMounted(async () => {
|
||||
// Load thresholds configuration
|
||||
loadThresholds()
|
||||
|
||||
// Load auto refresh settings
|
||||
await loadAutoRefreshSettings()
|
||||
|
||||
if (opsEnabled.value) {
|
||||
await fetchData()
|
||||
}
|
||||
|
||||
// Start auto refresh if enabled
|
||||
if (autoRefreshEnabled.value) {
|
||||
resumeAutoRefresh()
|
||||
resumeCountdown()
|
||||
}
|
||||
})
|
||||
|
||||
async function loadThresholds() {
|
||||
@@ -584,5 +638,27 @@ async function loadThresholds() {
|
||||
|
||||
onUnmounted(() => {
|
||||
abortDashboardFetch()
|
||||
pauseAutoRefresh()
|
||||
pauseCountdown()
|
||||
})
|
||||
|
||||
// Watch auto refresh settings changes
|
||||
watch(autoRefreshEnabled, (enabled) => {
|
||||
if (enabled) {
|
||||
autoRefreshCountdown.value = Math.floor(autoRefreshIntervalMs.value / 1000)
|
||||
resumeAutoRefresh()
|
||||
resumeCountdown()
|
||||
} else {
|
||||
pauseAutoRefresh()
|
||||
pauseCountdown()
|
||||
autoRefreshCountdown.value = 0
|
||||
}
|
||||
})
|
||||
|
||||
// Reload auto refresh settings after settings dialog is closed
|
||||
watch(showSettingsDialog, async (show) => {
|
||||
if (!show) {
|
||||
await loadAutoRefreshSettings()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -23,6 +23,8 @@ interface Props {
|
||||
loading: boolean
|
||||
lastUpdated: Date | null
|
||||
thresholds?: OpsMetricThresholds | null // 阈值配置
|
||||
autoRefreshEnabled?: boolean
|
||||
autoRefreshCountdown?: number
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
@@ -839,6 +841,17 @@ function handleToolbarRefresh() {
|
||||
<span>·</span>
|
||||
<span>{{ t('common.refresh') }}: {{ updatedAtLabel }}</span>
|
||||
|
||||
<template v-if="props.autoRefreshEnabled && props.autoRefreshCountdown !== undefined">
|
||||
<span>·</span>
|
||||
<span class="flex items-center gap-1">
|
||||
<svg class="h-3 w-3 animate-spin text-blue-500" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<span>自动刷新: {{ props.autoRefreshCountdown }}s</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template v-if="systemMetrics">
|
||||
<span>·</span>
|
||||
<span>
|
||||
|
||||
@@ -487,6 +487,48 @@ async function saveAllSettings() {
|
||||
<Toggle v-model="advancedSettings.aggregation.aggregation_enabled" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 错误过滤 -->
|
||||
<div class="space-y-3">
|
||||
<h5 class="text-xs font-semibold text-gray-700 dark:text-gray-300">错误过滤</h5>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">忽略 count_tokens 错误</label>
|
||||
<p class="mt-1 text-xs text-gray-500">
|
||||
启用后,count_tokens 请求的错误将不计入运维监控的统计和告警中(但仍会存储在数据库中)
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="advancedSettings.ignore_count_tokens_errors" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自动刷新 -->
|
||||
<div class="space-y-3">
|
||||
<h5 class="text-xs font-semibold text-gray-700 dark:text-gray-300">自动刷新</h5>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">启用自动刷新</label>
|
||||
<p class="mt-1 text-xs text-gray-500">
|
||||
自动刷新仪表板数据,启用后会定期拉取最新数据
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="advancedSettings.auto_refresh_enabled" />
|
||||
</div>
|
||||
|
||||
<div v-if="advancedSettings.auto_refresh_enabled">
|
||||
<label class="input-label">刷新间隔</label>
|
||||
<Select
|
||||
v-model="advancedSettings.auto_refresh_interval_seconds"
|
||||
:options="[
|
||||
{ value: 15, label: '15 秒' },
|
||||
{ value: 30, label: '30 秒' },
|
||||
{ value: 60, label: '60 秒' }
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user