From d0b91a40d4fc06fc50b96e6e3ef28742910d4fa2 Mon Sep 17 00:00:00 2001
From: IanShaw027 <131567472+IanShaw027@users.noreply.github.com>
Date: Mon, 12 Jan 2026 11:43:54 +0800
Subject: [PATCH] =?UTF-8?q?feat(ops):=20=E6=B7=BB=E5=8A=A0=E6=8C=87?=
=?UTF-8?q?=E6=A0=87=E9=98=88=E5=80=BC=E9=85=8D=E7=BD=AEUI?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在OpsSettingsDialog中添加指标阈值配置表单
- 在OpsRuntimeSettingsCard中添加阈值配置区域
- 添加阈值验证逻辑
- 更新国际化文本
---
frontend/src/i18n/locales/zh.ts | 16 ++-
.../ops/components/OpsRuntimeSettingsCard.vue | 114 +++++++++++++++++
.../ops/components/OpsSettingsDialog.vue | 118 +++++++++++++++++-
3 files changed, 241 insertions(+), 7 deletions(-)
diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts
index ecdcb13f..9ccd5678 100644
--- a/frontend/src/i18n/locales/zh.ts
+++ b/frontend/src/i18n/locales/zh.ts
@@ -2018,7 +2018,7 @@ export default {
ready: '就绪',
requestsTotal: '请求(总计)',
slaScope: 'SLA 范围:',
- tokens: 'Token',
+ tokens: 'Token数',
tps: 'TPS',
current: '当前',
peak: '峰值',
@@ -2047,7 +2047,7 @@ export default {
avg: 'avg',
max: 'max',
qps: 'QPS',
- requests: '请求',
+ requests: '请求数',
upstream: '上游',
client: '客户端',
system: '系统',
@@ -2465,6 +2465,18 @@ export default {
reportRecipients: '评估报告接收邮箱',
dailySummary: '每日摘要',
weeklySummary: '每周摘要',
+ metricThresholds: '指标阈值配置',
+ metricThresholdsHint: '配置各项指标的告警阈值,超出阈值时将以红色显示',
+ slaMinPercent: 'SLA最低百分比',
+ slaMinPercentHint: 'SLA低于此值时显示为红色(默认:99.5%)',
+ latencyP99MaxMs: '延迟P99最大值(毫秒)',
+ latencyP99MaxMsHint: '延迟P99高于此值时显示为红色(默认:2000ms)',
+ ttftP99MaxMs: 'TTFT P99最大值(毫秒)',
+ ttftP99MaxMsHint: 'TTFT P99高于此值时显示为红色(默认:500ms)',
+ requestErrorRateMaxPercent: '请求错误率最大值(%)',
+ requestErrorRateMaxPercentHint: '请求错误率高于此值时显示为红色(默认:5%)',
+ upstreamErrorRateMaxPercent: '上游错误率最大值(%)',
+ upstreamErrorRateMaxPercentHint: '上游错误率高于此值时显示为红色(默认:5%)',
advancedSettings: '高级设置',
dataRetention: '数据保留策略',
enableCleanup: '启用数据清理',
diff --git a/frontend/src/views/admin/ops/components/OpsRuntimeSettingsCard.vue b/frontend/src/views/admin/ops/components/OpsRuntimeSettingsCard.vue
index e9df347d..1dcab4b3 100644
--- a/frontend/src/views/admin/ops/components/OpsRuntimeSettingsCard.vue
+++ b/frontend/src/views/admin/ops/components/OpsRuntimeSettingsCard.vue
@@ -45,6 +45,36 @@ function validateRuntimeSettings(settings: OpsAlertRuntimeSettings): ValidationR
errors.push(t('admin.ops.runtime.validation.evalIntervalRange'))
}
+ // Thresholds validation
+ const thresholds = settings.thresholds
+ if (thresholds) {
+ if (thresholds.sla_percent_min != null) {
+ if (!Number.isFinite(thresholds.sla_percent_min) || thresholds.sla_percent_min < 0 || thresholds.sla_percent_min > 100) {
+ errors.push('SLA 最低值必须在 0-100 之间')
+ }
+ }
+ if (thresholds.latency_p99_ms_max != null) {
+ if (!Number.isFinite(thresholds.latency_p99_ms_max) || thresholds.latency_p99_ms_max < 0) {
+ errors.push('延迟 P99 最大值必须大于或等于 0')
+ }
+ }
+ if (thresholds.ttft_p99_ms_max != null) {
+ if (!Number.isFinite(thresholds.ttft_p99_ms_max) || thresholds.ttft_p99_ms_max < 0) {
+ errors.push('TTFT P99 最大值必须大于或等于 0')
+ }
+ }
+ if (thresholds.request_error_rate_percent_max != null) {
+ if (!Number.isFinite(thresholds.request_error_rate_percent_max) || thresholds.request_error_rate_percent_max < 0 || thresholds.request_error_rate_percent_max > 100) {
+ errors.push('请求错误率最大值必须在 0-100 之间')
+ }
+ }
+ if (thresholds.upstream_error_rate_percent_max != null) {
+ if (!Number.isFinite(thresholds.upstream_error_rate_percent_max) || thresholds.upstream_error_rate_percent_max < 0 || thresholds.upstream_error_rate_percent_max > 100) {
+ errors.push('上游错误率最大值必须在 0-100 之间')
+ }
+ }
+ }
+
const lock = settings.distributed_lock
if (lock?.enabled) {
if (!lock.key || lock.key.trim().length < 3) {
@@ -130,6 +160,15 @@ function openAlertEditor() {
if (!Array.isArray(draftAlert.value.silencing.entries)) {
draftAlert.value.silencing.entries = []
}
+ if (!draftAlert.value.thresholds) {
+ draftAlert.value.thresholds = {
+ sla_percent_min: 99.5,
+ latency_p99_ms_max: 2000,
+ ttft_p99_ms_max: 500,
+ request_error_rate_percent_max: 5,
+ upstream_error_rate_percent_max: 5
+ }
+ }
}
showAlertEditor.value = true
@@ -295,6 +334,81 @@ onMounted(() => {
{{ t('admin.ops.runtime.evalIntervalHint') }}
+
+
指标阈值配置
+
配置各项指标的告警阈值。超出阈值的指标将在看板上以红色显示。
+
+
+
+
SLA 最低值 (%)
+
+
SLA 低于此值时将显示为红色
+
+
+
+
延迟 P99 最大值 (ms)
+
+
延迟 P99 高于此值时将显示为红色
+
+
+
+
TTFT P99 最大值 (ms)
+
+
TTFT P99 高于此值时将显示为红色
+
+
+
+
请求错误率最大值 (%)
+
+
请求错误率高于此值时将显示为红色
+
+
+
+
上游错误率最大值 (%)
+
+
上游错误率高于此值时将显示为红色
+
+
+
+
{{ t('admin.ops.runtime.silencing.title') }}
diff --git a/frontend/src/views/admin/ops/components/OpsSettingsDialog.vue b/frontend/src/views/admin/ops/components/OpsSettingsDialog.vue
index 968c5081..0c9c4f81 100644
--- a/frontend/src/views/admin/ops/components/OpsSettingsDialog.vue
+++ b/frontend/src/views/admin/ops/components/OpsSettingsDialog.vue
@@ -6,7 +6,7 @@ import { opsAPI } from '@/api/admin/ops'
import BaseDialog from '@/components/common/BaseDialog.vue'
import Select from '@/components/common/Select.vue'
import Toggle from '@/components/common/Toggle.vue'
-import type { OpsAlertRuntimeSettings, EmailNotificationConfig, AlertSeverity, OpsAdvancedSettings } from '../types'
+import type { OpsAlertRuntimeSettings, EmailNotificationConfig, AlertSeverity, OpsAdvancedSettings, OpsMetricThresholds } from '../types'
const { t } = useI18n()
const appStore = useAppStore()
@@ -29,19 +29,38 @@ const runtimeSettings = ref
(null)
const emailConfig = ref(null)
// 高级设置
const advancedSettings = ref(null)
+// 指标阈值配置
+const metricThresholds = ref({
+ sla_percent_min: 99.5,
+ latency_p99_ms_max: 2000,
+ ttft_p99_ms_max: 500,
+ request_error_rate_percent_max: 5,
+ upstream_error_rate_percent_max: 5
+})
// 加载所有配置
async function loadAllSettings() {
loading.value = true
try {
- const [runtime, email, advanced] = await Promise.all([
+ const [runtime, email, advanced, thresholds] = await Promise.all([
opsAPI.getAlertRuntimeSettings(),
opsAPI.getEmailNotificationConfig(),
- opsAPI.getAdvancedSettings()
+ opsAPI.getAdvancedSettings(),
+ opsAPI.getMetricThresholds()
])
runtimeSettings.value = runtime
emailConfig.value = email
advancedSettings.value = advanced
+ // 如果后端返回了阈值,使用后端的值;否则保持默认值
+ if (thresholds && Object.keys(thresholds).length > 0) {
+ metricThresholds.value = {
+ sla_percent_min: thresholds.sla_percent_min ?? 99.5,
+ latency_p99_ms_max: thresholds.latency_p99_ms_max ?? 2000,
+ ttft_p99_ms_max: thresholds.ttft_p99_ms_max ?? 500,
+ request_error_rate_percent_max: thresholds.request_error_rate_percent_max ?? 5,
+ upstream_error_rate_percent_max: thresholds.upstream_error_rate_percent_max ?? 5
+ }
+ }
} catch (err: any) {
console.error('[OpsSettingsDialog] Failed to load settings', err)
appStore.showError(err?.response?.data?.detail || t('admin.ops.settings.loadFailed'))
@@ -138,6 +157,23 @@ const validation = computed(() => {
}
}
+ // 验证指标阈值
+ if (metricThresholds.value.sla_percent_min != null && (metricThresholds.value.sla_percent_min < 0 || metricThresholds.value.sla_percent_min > 100)) {
+ errors.push('SLA最低百分比必须在0-100之间')
+ }
+ if (metricThresholds.value.latency_p99_ms_max != null && metricThresholds.value.latency_p99_ms_max < 0) {
+ errors.push('延迟P99最大值必须大于等于0')
+ }
+ if (metricThresholds.value.ttft_p99_ms_max != null && metricThresholds.value.ttft_p99_ms_max < 0) {
+ errors.push('TTFT P99最大值必须大于等于0')
+ }
+ if (metricThresholds.value.request_error_rate_percent_max != null && (metricThresholds.value.request_error_rate_percent_max < 0 || metricThresholds.value.request_error_rate_percent_max > 100)) {
+ errors.push('请求错误率最大值必须在0-100之间')
+ }
+ if (metricThresholds.value.upstream_error_rate_percent_max != null && (metricThresholds.value.upstream_error_rate_percent_max < 0 || metricThresholds.value.upstream_error_rate_percent_max > 100)) {
+ errors.push('上游错误率最大值必须在0-100之间')
+ }
+
return { valid: errors.length === 0, errors }
})
@@ -153,14 +189,15 @@ async function saveAllSettings() {
await Promise.all([
runtimeSettings.value ? opsAPI.updateAlertRuntimeSettings(runtimeSettings.value) : Promise.resolve(),
emailConfig.value ? opsAPI.updateEmailNotificationConfig(emailConfig.value) : Promise.resolve(),
- advancedSettings.value ? opsAPI.updateAdvancedSettings(advancedSettings.value) : Promise.resolve()
+ advancedSettings.value ? opsAPI.updateAdvancedSettings(advancedSettings.value) : Promise.resolve(),
+ opsAPI.updateMetricThresholds(metricThresholds.value)
])
appStore.showSuccess(t('admin.ops.settings.saveSuccess'))
emit('saved')
emit('close')
} catch (err: any) {
console.error('[OpsSettingsDialog] Failed to save settings', err)
- appStore.showError(err?.response?.data?.detail || t('admin.ops.settings.saveFailed'))
+ appStore.showError(err?.response?.data?.message || err?.response?.data?.detail || t('admin.ops.settings.saveFailed'))
} finally {
saving.value = false
}
@@ -306,6 +343,77 @@ async function saveAllSettings() {
+
+
+
{{ t('admin.ops.settings.metricThresholds') }}
+
{{ t('admin.ops.settings.metricThresholdsHint') }}
+
+
+
+
+
+
{{ t('admin.ops.settings.slaMinPercentHint') }}
+
+
+
+
+
+
{{ t('admin.ops.settings.latencyP99MaxMsHint') }}
+
+
+
+
+
+
{{ t('admin.ops.settings.ttftP99MaxMsHint') }}
+
+
+
+
+
+
{{ t('admin.ops.settings.requestErrorRateMaxPercentHint') }}
+
+
+
+
+
+
{{ t('admin.ops.settings.upstreamErrorRateMaxPercentHint') }}
+
+
+
+