Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
d0b91a40
Commit
d0b91a40
authored
Jan 12, 2026
by
IanShaw027
Browse files
feat(ops): 添加指标阈值配置UI
- 在OpsSettingsDialog中添加指标阈值配置表单 - 在OpsRuntimeSettingsCard中添加阈值配置区域 - 添加阈值验证逻辑 - 更新国际化文本
parent
bd74bf79
Changes
3
Hide whitespace changes
Inline
Side-by-side
frontend/src/i18n/locales/zh.ts
View file @
d0b91a40
...
@@ -2018,7 +2018,7 @@ export default {
...
@@ -2018,7 +2018,7 @@ export default {
ready
:
'
就绪
'
,
ready
:
'
就绪
'
,
requestsTotal
:
'
请求(总计)
'
,
requestsTotal
:
'
请求(总计)
'
,
slaScope
:
'
SLA 范围:
'
,
slaScope
:
'
SLA 范围:
'
,
tokens
:
'
Token
'
,
tokens
:
'
Token
数
'
,
tps
:
'
TPS
'
,
tps
:
'
TPS
'
,
current
:
'
当前
'
,
current
:
'
当前
'
,
peak
:
'
峰值
'
,
peak
:
'
峰值
'
,
...
@@ -2047,7 +2047,7 @@ export default {
...
@@ -2047,7 +2047,7 @@ export default {
avg
:
'
avg
'
,
avg
:
'
avg
'
,
max
:
'
max
'
,
max
:
'
max
'
,
qps
:
'
QPS
'
,
qps
:
'
QPS
'
,
requests
:
'
请求
'
,
requests
:
'
请求
数
'
,
upstream
:
'
上游
'
,
upstream
:
'
上游
'
,
client
:
'
客户端
'
,
client
:
'
客户端
'
,
system
:
'
系统
'
,
system
:
'
系统
'
,
...
@@ -2465,6 +2465,18 @@ export default {
...
@@ -2465,6 +2465,18 @@ export default {
reportRecipients
:
'
评估报告接收邮箱
'
,
reportRecipients
:
'
评估报告接收邮箱
'
,
dailySummary
:
'
每日摘要
'
,
dailySummary
:
'
每日摘要
'
,
weeklySummary
:
'
每周摘要
'
,
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
:
'
高级设置
'
,
advancedSettings
:
'
高级设置
'
,
dataRetention
:
'
数据保留策略
'
,
dataRetention
:
'
数据保留策略
'
,
enableCleanup
:
'
启用数据清理
'
,
enableCleanup
:
'
启用数据清理
'
,
...
...
frontend/src/views/admin/ops/components/OpsRuntimeSettingsCard.vue
View file @
d0b91a40
...
@@ -45,6 +45,36 @@ function validateRuntimeSettings(settings: OpsAlertRuntimeSettings): ValidationR
...
@@ -45,6 +45,36 @@ function validateRuntimeSettings(settings: OpsAlertRuntimeSettings): ValidationR
errors
.
push
(
t
(
'
admin.ops.runtime.validation.evalIntervalRange
'
))
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
const
lock
=
settings
.
distributed_lock
if
(
lock
?.
enabled
)
{
if
(
lock
?.
enabled
)
{
if
(
!
lock
.
key
||
lock
.
key
.
trim
().
length
<
3
)
{
if
(
!
lock
.
key
||
lock
.
key
.
trim
().
length
<
3
)
{
...
@@ -130,6 +160,15 @@ function openAlertEditor() {
...
@@ -130,6 +160,15 @@ function openAlertEditor() {
if
(
!
Array
.
isArray
(
draftAlert
.
value
.
silencing
.
entries
))
{
if
(
!
Array
.
isArray
(
draftAlert
.
value
.
silencing
.
entries
))
{
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
showAlertEditor
.
value
=
true
...
@@ -295,6 +334,81 @@ onMounted(() => {
...
@@ -295,6 +334,81 @@ onMounted(() => {
<p
class=
"mt-1 text-xs text-gray-500"
>
{{
t
(
'
admin.ops.runtime.evalIntervalHint
'
)
}}
</p>
<p
class=
"mt-1 text-xs text-gray-500"
>
{{
t
(
'
admin.ops.runtime.evalIntervalHint
'
)
}}
</p>
</div>
</div>
<div
class=
"rounded-2xl bg-gray-50 p-4 dark:bg-dark-700/50"
>
<div
class=
"mb-2 text-sm font-semibold text-gray-900 dark:text-white"
>
指标阈值配置
</div>
<p
class=
"mb-4 text-xs text-gray-500 dark:text-gray-400"
>
配置各项指标的告警阈值。超出阈值的指标将在看板上以红色显示。
</p>
<div
class=
"grid grid-cols-1 gap-4 md:grid-cols-2"
>
<div>
<div
class=
"mb-1 text-xs font-medium text-gray-600 dark:text-gray-300"
>
SLA 最低值 (%)
</div>
<input
v-model.number=
"draftAlert.thresholds.sla_percent_min"
type=
"number"
min=
"0"
max=
"100"
step=
"0.1"
class=
"input"
placeholder=
"99.5"
/>
<p
class=
"mt-1 text-xs text-gray-500 dark:text-gray-400"
>
SLA 低于此值时将显示为红色
</p>
</div>
<div>
<div
class=
"mb-1 text-xs font-medium text-gray-600 dark:text-gray-300"
>
延迟 P99 最大值 (ms)
</div>
<input
v-model.number=
"draftAlert.thresholds.latency_p99_ms_max"
type=
"number"
min=
"0"
step=
"100"
class=
"input"
placeholder=
"2000"
/>
<p
class=
"mt-1 text-xs text-gray-500 dark:text-gray-400"
>
延迟 P99 高于此值时将显示为红色
</p>
</div>
<div>
<div
class=
"mb-1 text-xs font-medium text-gray-600 dark:text-gray-300"
>
TTFT P99 最大值 (ms)
</div>
<input
v-model.number=
"draftAlert.thresholds.ttft_p99_ms_max"
type=
"number"
min=
"0"
step=
"100"
class=
"input"
placeholder=
"500"
/>
<p
class=
"mt-1 text-xs text-gray-500 dark:text-gray-400"
>
TTFT P99 高于此值时将显示为红色
</p>
</div>
<div>
<div
class=
"mb-1 text-xs font-medium text-gray-600 dark:text-gray-300"
>
请求错误率最大值 (%)
</div>
<input
v-model.number=
"draftAlert.thresholds.request_error_rate_percent_max"
type=
"number"
min=
"0"
max=
"100"
step=
"0.1"
class=
"input"
placeholder=
"5"
/>
<p
class=
"mt-1 text-xs text-gray-500 dark:text-gray-400"
>
请求错误率高于此值时将显示为红色
</p>
</div>
<div>
<div
class=
"mb-1 text-xs font-medium text-gray-600 dark:text-gray-300"
>
上游错误率最大值 (%)
</div>
<input
v-model.number=
"draftAlert.thresholds.upstream_error_rate_percent_max"
type=
"number"
min=
"0"
max=
"100"
step=
"0.1"
class=
"input"
placeholder=
"5"
/>
<p
class=
"mt-1 text-xs text-gray-500 dark:text-gray-400"
>
上游错误率高于此值时将显示为红色
</p>
</div>
</div>
</div>
<div
class=
"rounded-2xl bg-gray-50 p-4 dark:bg-dark-700/50"
>
<div
class=
"rounded-2xl bg-gray-50 p-4 dark:bg-dark-700/50"
>
<div
class=
"mb-2 text-sm font-semibold text-gray-900 dark:text-white"
>
{{
t
(
'
admin.ops.runtime.silencing.title
'
)
}}
</div>
<div
class=
"mb-2 text-sm font-semibold text-gray-900 dark:text-white"
>
{{
t
(
'
admin.ops.runtime.silencing.title
'
)
}}
</div>
...
...
frontend/src/views/admin/ops/components/OpsSettingsDialog.vue
View file @
d0b91a40
...
@@ -6,7 +6,7 @@ import { opsAPI } from '@/api/admin/ops'
...
@@ -6,7 +6,7 @@ import { opsAPI } from '@/api/admin/ops'
import
BaseDialog
from
'
@/components/common/BaseDialog.vue
'
import
BaseDialog
from
'
@/components/common/BaseDialog.vue
'
import
Select
from
'
@/components/common/Select.vue
'
import
Select
from
'
@/components/common/Select.vue
'
import
Toggle
from
'
@/components/common/Toggle.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
{
t
}
=
useI18n
()
const
appStore
=
useAppStore
()
const
appStore
=
useAppStore
()
...
@@ -29,19 +29,38 @@ const runtimeSettings = ref<OpsAlertRuntimeSettings | null>(null)
...
@@ -29,19 +29,38 @@ const runtimeSettings = ref<OpsAlertRuntimeSettings | null>(null)
const
emailConfig
=
ref
<
EmailNotificationConfig
|
null
>
(
null
)
const
emailConfig
=
ref
<
EmailNotificationConfig
|
null
>
(
null
)
// 高级设置
// 高级设置
const
advancedSettings
=
ref
<
OpsAdvancedSettings
|
null
>
(
null
)
const
advancedSettings
=
ref
<
OpsAdvancedSettings
|
null
>
(
null
)
// 指标阈值配置
const
metricThresholds
=
ref
<
OpsMetricThresholds
>
({
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
()
{
async
function
loadAllSettings
()
{
loading
.
value
=
true
loading
.
value
=
true
try
{
try
{
const
[
runtime
,
email
,
advanced
]
=
await
Promise
.
all
([
const
[
runtime
,
email
,
advanced
,
thresholds
]
=
await
Promise
.
all
([
opsAPI
.
getAlertRuntimeSettings
(),
opsAPI
.
getAlertRuntimeSettings
(),
opsAPI
.
getEmailNotificationConfig
(),
opsAPI
.
getEmailNotificationConfig
(),
opsAPI
.
getAdvancedSettings
()
opsAPI
.
getAdvancedSettings
(),
opsAPI
.
getMetricThresholds
()
])
])
runtimeSettings
.
value
=
runtime
runtimeSettings
.
value
=
runtime
emailConfig
.
value
=
email
emailConfig
.
value
=
email
advancedSettings
.
value
=
advanced
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
)
{
}
catch
(
err
:
any
)
{
console
.
error
(
'
[OpsSettingsDialog] Failed to load settings
'
,
err
)
console
.
error
(
'
[OpsSettingsDialog] Failed to load settings
'
,
err
)
appStore
.
showError
(
err
?.
response
?.
data
?.
detail
||
t
(
'
admin.ops.settings.loadFailed
'
))
appStore
.
showError
(
err
?.
response
?.
data
?.
detail
||
t
(
'
admin.ops.settings.loadFailed
'
))
...
@@ -138,6 +157,23 @@ const validation = computed(() => {
...
@@ -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
}
return
{
valid
:
errors
.
length
===
0
,
errors
}
})
})
...
@@ -153,14 +189,15 @@ async function saveAllSettings() {
...
@@ -153,14 +189,15 @@ async function saveAllSettings() {
await
Promise
.
all
([
await
Promise
.
all
([
runtimeSettings
.
value
?
opsAPI
.
updateAlertRuntimeSettings
(
runtimeSettings
.
value
)
:
Promise
.
resolve
(),
runtimeSettings
.
value
?
opsAPI
.
updateAlertRuntimeSettings
(
runtimeSettings
.
value
)
:
Promise
.
resolve
(),
emailConfig
.
value
?
opsAPI
.
updateEmailNotificationConfig
(
emailConfig
.
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
'
))
appStore
.
showSuccess
(
t
(
'
admin.ops.settings.saveSuccess
'
))
emit
(
'
saved
'
)
emit
(
'
saved
'
)
emit
(
'
close
'
)
emit
(
'
close
'
)
}
catch
(
err
:
any
)
{
}
catch
(
err
:
any
)
{
console
.
error
(
'
[OpsSettingsDialog] Failed to save settings
'
,
err
)
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
{
}
finally
{
saving
.
value
=
false
saving
.
value
=
false
}
}
...
@@ -306,6 +343,77 @@ async function saveAllSettings() {
...
@@ -306,6 +343,77 @@ async function saveAllSettings() {
</div>
</div>
</div>
</div>
<!-- 指标阈值配置 -->
<div
class=
"rounded-2xl bg-gray-50 p-4 dark:bg-dark-700/50"
>
<h4
class=
"mb-3 text-sm font-semibold text-gray-900 dark:text-white"
>
{{
t
(
'
admin.ops.settings.metricThresholds
'
)
}}
</h4>
<p
class=
"mb-4 text-xs text-gray-500 dark:text-gray-400"
>
{{
t
(
'
admin.ops.settings.metricThresholdsHint
'
)
}}
</p>
<div
class=
"space-y-4"
>
<div>
<label
class=
"input-label"
>
{{
t
(
'
admin.ops.settings.slaMinPercent
'
)
}}
</label>
<input
v-model.number=
"metricThresholds.sla_percent_min"
type=
"number"
min=
"0"
max=
"100"
step=
"0.1"
class=
"input"
/>
<p
class=
"mt-1 text-xs text-gray-500"
>
{{
t
(
'
admin.ops.settings.slaMinPercentHint
'
)
}}
</p>
</div>
<div>
<label
class=
"input-label"
>
{{
t
(
'
admin.ops.settings.latencyP99MaxMs
'
)
}}
</label>
<input
v-model.number=
"metricThresholds.latency_p99_ms_max"
type=
"number"
min=
"0"
step=
"100"
class=
"input"
/>
<p
class=
"mt-1 text-xs text-gray-500"
>
{{
t
(
'
admin.ops.settings.latencyP99MaxMsHint
'
)
}}
</p>
</div>
<div>
<label
class=
"input-label"
>
{{
t
(
'
admin.ops.settings.ttftP99MaxMs
'
)
}}
</label>
<input
v-model.number=
"metricThresholds.ttft_p99_ms_max"
type=
"number"
min=
"0"
step=
"50"
class=
"input"
/>
<p
class=
"mt-1 text-xs text-gray-500"
>
{{
t
(
'
admin.ops.settings.ttftP99MaxMsHint
'
)
}}
</p>
</div>
<div>
<label
class=
"input-label"
>
{{
t
(
'
admin.ops.settings.requestErrorRateMaxPercent
'
)
}}
</label>
<input
v-model.number=
"metricThresholds.request_error_rate_percent_max"
type=
"number"
min=
"0"
max=
"100"
step=
"0.1"
class=
"input"
/>
<p
class=
"mt-1 text-xs text-gray-500"
>
{{
t
(
'
admin.ops.settings.requestErrorRateMaxPercentHint
'
)
}}
</p>
</div>
<div>
<label
class=
"input-label"
>
{{
t
(
'
admin.ops.settings.upstreamErrorRateMaxPercent
'
)
}}
</label>
<input
v-model.number=
"metricThresholds.upstream_error_rate_percent_max"
type=
"number"
min=
"0"
max=
"100"
step=
"0.1"
class=
"input"
/>
<p
class=
"mt-1 text-xs text-gray-500"
>
{{
t
(
'
admin.ops.settings.upstreamErrorRateMaxPercentHint
'
)
}}
</p>
</div>
</div>
</div>
<!-- 高级设置 -->
<!-- 高级设置 -->
<details
class=
"rounded-2xl bg-gray-50 dark:bg-dark-700/50"
>
<details
class=
"rounded-2xl bg-gray-50 dark:bg-dark-700/50"
>
<summary
class=
"cursor-pointer p-4 text-sm font-semibold text-gray-900 dark:text-white"
>
<summary
class=
"cursor-pointer p-4 text-sm font-semibold text-gray-900 dark:text-white"
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment