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
0e448297
Commit
0e448297
authored
Jan 13, 2026
by
yangjianbo
Browse files
Merge branch 'main' into dev
parents
9618cb56
93db889a
Changes
47
Show whitespace changes
Inline
Side-by-side
frontend/src/views/admin/ops/components/OpsErrorDetailsModal.vue
View file @
0e448297
...
@@ -174,8 +174,9 @@ watch(
...
@@ -174,8 +174,9 @@ watch(
<
template
>
<
template
>
<BaseDialog
:show=
"show"
:title=
"modalTitle"
width=
"full"
@
close=
"close"
>
<BaseDialog
:show=
"show"
:title=
"modalTitle"
width=
"full"
@
close=
"close"
>
<div
class=
"flex h-full min-h-0 flex-col"
>
<!-- Filters -->
<!-- Filters -->
<div
class=
"border-b border-gray-200 pb-4
mb-4
dark:border-dark-700"
>
<div
class=
"
mb-4 flex-shrink-0
border-b border-gray-200 pb-4 dark:border-dark-700"
>
<div
class=
"grid grid-cols-1 gap-4 lg:grid-cols-12"
>
<div
class=
"grid grid-cols-1 gap-4 lg:grid-cols-12"
>
<div
class=
"lg:col-span-5"
>
<div
class=
"lg:col-span-5"
>
<div
class=
"relative group"
>
<div
class=
"relative group"
>
...
@@ -225,10 +226,13 @@ watch(
...
@@ -225,10 +226,13 @@ watch(
</div>
</div>
<!-- Body -->
<!-- Body -->
<div
class=
"text-xs text-gray-500 dark:text-gray-400 mb-2"
>
<div
class=
"flex min-h-0 flex-1 flex-col"
>
<div
class=
"mb-2 flex-shrink-0 text-xs text-gray-500 dark:text-gray-400"
>
{{
t
(
'
admin.ops.errorDetails.total
'
)
}}
{{
total
}}
{{
t
(
'
admin.ops.errorDetails.total
'
)
}}
{{
total
}}
</div>
</div>
<OpsErrorLogTable
<OpsErrorLogTable
class=
"min-h-0 flex-1"
:rows=
"rows"
:rows=
"rows"
:total=
"total"
:total=
"total"
:loading=
"loading"
:loading=
"loading"
...
@@ -238,5 +242,7 @@ watch(
...
@@ -238,5 +242,7 @@ watch(
@
update:page=
"page = $event"
@
update:page=
"page = $event"
@
update:pageSize=
"pageSize = $event"
@
update:pageSize=
"pageSize = $event"
/>
/>
</div>
</div>
</BaseDialog>
</BaseDialog>
</
template
>
</
template
>
frontend/src/views/admin/ops/components/OpsErrorLogTable.vue
View file @
0e448297
<
template
>
<
template
>
<div>
<div
class=
"flex h-full min-h-0 flex-col"
>
<div
v-if=
"loading"
class=
"flex items-center justify-center py-10"
>
<div
v-if=
"loading"
class=
"flex
flex-1
items-center justify-center py-10"
>
<div
class=
"h-8 w-8 animate-spin rounded-full border-b-2 border-primary-600"
></div>
<div
class=
"h-8 w-8 animate-spin rounded-full border-b-2 border-primary-600"
></div>
</div>
</div>
<div
v-else
class=
"overflow-x-auto"
>
<div
v-else
class=
"flex min-h-0 flex-1 flex-col"
>
<div
class=
"min-h-0 flex-1 overflow-auto"
>
<table
class=
"min-w-full divide-y divide-gray-200 dark:divide-dark-700"
>
<table
class=
"min-w-full divide-y divide-gray-200 dark:divide-dark-700"
>
<thead
class=
"sticky top-0 z-10 bg-gray-50/50 dark:bg-dark-800/50"
>
<thead
class=
"sticky top-0 z-10 bg-gray-50/50 dark:bg-dark-800/50"
>
<tr>
<tr>
...
@@ -172,6 +173,7 @@
...
@@ -172,6 +173,7 @@
@
update:pageSize=
"emit('update:pageSize', $event)"
@
update:pageSize=
"emit('update:pageSize', $event)"
/>
/>
</div>
</div>
</div>
</
template
>
</
template
>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
...
...
frontend/src/views/admin/ops/components/OpsRequestDetailsModal.vue
View file @
0e448297
...
@@ -95,6 +95,7 @@ watch(
...
@@ -95,6 +95,7 @@ watch(
(
open
)
=>
{
(
open
)
=>
{
if
(
open
)
{
if
(
open
)
{
page
.
value
=
1
page
.
value
=
1
pageSize
.
value
=
20
fetchData
()
fetchData
()
}
}
}
}
...
@@ -150,7 +151,8 @@ const kindBadgeClass = (kind: string) => {
...
@@ -150,7 +151,8 @@ const kindBadgeClass = (kind: string) => {
<
template
>
<
template
>
<BaseDialog
:show=
"modelValue"
:title=
"props.preset.title || t('admin.ops.requestDetails.title')"
width=
"full"
@
close=
"close"
>
<BaseDialog
:show=
"modelValue"
:title=
"props.preset.title || t('admin.ops.requestDetails.title')"
width=
"full"
@
close=
"close"
>
<template
#default
>
<template
#default
>
<div
class=
"flex items-center justify-between mb-4"
>
<div
class=
"flex h-full min-h-0 flex-col"
>
<div
class=
"mb-4 flex flex-shrink-0 items-center justify-between"
>
<div
class=
"text-xs text-gray-500 dark:text-gray-400"
>
<div
class=
"text-xs text-gray-500 dark:text-gray-400"
>
{{
t
(
'
admin.ops.requestDetails.rangeLabel
'
,
{
range
:
rangeLabel
}
)
}}
{{
t
(
'
admin.ops.requestDetails.rangeLabel
'
,
{
range
:
rangeLabel
}
)
}}
<
/div
>
<
/div
>
...
@@ -164,7 +166,7 @@ const kindBadgeClass = (kind: string) => {
...
@@ -164,7 +166,7 @@ const kindBadgeClass = (kind: string) => {
<
/div
>
<
/div
>
<!--
Loading
-->
<!--
Loading
-->
<
div
v
-
if
=
"
loading
"
class
=
"
flex items-center justify-center py-16
"
>
<
div
v
-
if
=
"
loading
"
class
=
"
flex
flex-1
items-center justify-center py-16
"
>
<
div
class
=
"
flex flex-col items-center gap-3
"
>
<
div
class
=
"
flex flex-col items-center gap-3
"
>
<
svg
class
=
"
h-8 w-8 animate-spin text-blue-500
"
fill
=
"
none
"
viewBox
=
"
0 0 24 24
"
>
<
svg
class
=
"
h-8 w-8 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
>
<
circle
class
=
"
opacity-25
"
cx
=
"
12
"
cy
=
"
12
"
r
=
"
10
"
stroke
=
"
currentColor
"
stroke
-
width
=
"
4
"
><
/circle
>
...
@@ -179,16 +181,16 @@ const kindBadgeClass = (kind: string) => {
...
@@ -179,16 +181,16 @@ const kindBadgeClass = (kind: string) => {
<
/div
>
<
/div
>
<!--
Table
-->
<!--
Table
-->
<
div
v
-
else
>
<
div
v
-
else
class
=
"
flex min-h-0 flex-1 flex-col
"
>
<
div
v
-
if
=
"
items.length === 0
"
class
=
"
rounded-xl border border-dashed border-gray-200 p-10 text-center dark:border-dark-700
"
>
<
div
v
-
if
=
"
items.length === 0
"
class
=
"
rounded-xl border border-dashed border-gray-200 p-10 text-center dark:border-dark-700
"
>
<
div
class
=
"
text-sm font-medium text-gray-600 dark:text-gray-300
"
>
{{
t
(
'
admin.ops.requestDetails.empty
'
)
}}
<
/div
>
<
div
class
=
"
text-sm font-medium text-gray-600 dark:text-gray-300
"
>
{{
t
(
'
admin.ops.requestDetails.empty
'
)
}}
<
/div
>
<
div
class
=
"
mt-1 text-xs text-gray-400
"
>
{{
t
(
'
admin.ops.requestDetails.emptyHint
'
)
}}
<
/div
>
<
div
class
=
"
mt-1 text-xs text-gray-400
"
>
{{
t
(
'
admin.ops.requestDetails.emptyHint
'
)
}}
<
/div
>
<
/div
>
<
/div
>
<
div
v
-
else
class
=
"
overflow-hidden rounded-xl border border-gray-200 dark:border-dark-700
"
>
<
div
v
-
else
class
=
"
flex min-h-0 flex-1 flex-col
overflow-hidden rounded-xl border border-gray-200 dark:border-dark-700
"
>
<
div
class
=
"
overflow-
x-
auto
"
>
<
div
class
=
"
min-h-0 flex-1
overflow-auto
"
>
<
table
class
=
"
min-w-full divide-y divide-gray-200 dark:divide-dark-700
"
>
<
table
class
=
"
min-w-full divide-y divide-gray-200 dark:divide-dark-700
"
>
<
thead
class
=
"
bg-gray-50 dark:bg-dark-900
"
>
<
thead
class
=
"
sticky top-0 z-10
bg-gray-50 dark:bg-dark-900
"
>
<
tr
>
<
tr
>
<
th
class
=
"
px-4 py-3 text-left text-[11px] font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400
"
>
<
th
class
=
"
px-4 py-3 text-left text-[11px] font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400
"
>
{{
t
(
'
admin.ops.requestDetails.table.time
'
)
}}
{{
t
(
'
admin.ops.requestDetails.table.time
'
)
}}
...
@@ -276,6 +278,7 @@ const kindBadgeClass = (kind: string) => {
...
@@ -276,6 +278,7 @@ const kindBadgeClass = (kind: string) => {
/>
/>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/template
>
<
/template
>
<
/BaseDialog
>
<
/BaseDialog
>
<
/template
>
<
/template
>
frontend/src/views/admin/ops/components/OpsRuntimeSettingsCard.vue
View file @
0e448297
...
@@ -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 @
0e448297
...
@@ -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"
>
...
@@ -379,6 +487,48 @@ async function saveAllSettings() {
...
@@ -379,6 +487,48 @@ async function saveAllSettings() {
<Toggle
v-model=
"advancedSettings.aggregation.aggregation_enabled"
/>
<Toggle
v-model=
"advancedSettings.aggregation.aggregation_enabled"
/>
</div>
</div>
</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>
</div>
</details>
</details>
</div>
</div>
...
...
frontend/src/views/admin/ops/components/OpsThroughputTrendChart.vue
View file @
0e448297
...
@@ -19,6 +19,7 @@ interface Props {
...
@@ -19,6 +19,7 @@ interface Props {
timeRange
:
string
timeRange
:
string
byPlatform
?:
OpsThroughputPlatformBreakdownItem
[]
byPlatform
?:
OpsThroughputPlatformBreakdownItem
[]
topGroups
?:
OpsThroughputGroupBreakdownItem
[]
topGroups
?:
OpsThroughputGroupBreakdownItem
[]
fullscreen
?:
boolean
}
}
const
props
=
defineProps
<
Props
>
()
const
props
=
defineProps
<
Props
>
()
...
@@ -179,11 +180,12 @@ function downloadChart() {
...
@@ -179,11 +180,12 @@ function downloadChart() {
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"2"
d=
"M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"
/>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"2"
d=
"M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"
/>
</svg>
</svg>
{{
t
(
'
admin.ops.throughputTrend
'
)
}}
{{
t
(
'
admin.ops.throughputTrend
'
)
}}
<HelpTooltip
:content=
"t('admin.ops.tooltips.throughputTrend')"
/>
<HelpTooltip
v-if=
"!props.fullscreen"
:content=
"t('admin.ops.tooltips.throughputTrend')"
/>
</h3>
</h3>
<div
class=
"flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400"
>
<div
class=
"flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400"
>
<span
class=
"flex items-center gap-1"
><span
class=
"h-2 w-2 rounded-full bg-blue-500"
></span>
{{
t
(
'
admin.ops.qps
'
)
}}
</span>
<span
class=
"flex items-center gap-1"
><span
class=
"h-2 w-2 rounded-full bg-blue-500"
></span>
{{
t
(
'
admin.ops.qps
'
)
}}
</span>
<span
class=
"flex items-center gap-1"
><span
class=
"h-2 w-2 rounded-full bg-green-500"
></span>
{{
t
(
'
admin.ops.tpsK
'
)
}}
</span>
<span
class=
"flex items-center gap-1"
><span
class=
"h-2 w-2 rounded-full bg-green-500"
></span>
{{
t
(
'
admin.ops.tpsK
'
)
}}
</span>
<template
v-if=
"!props.fullscreen"
>
<button
<button
type=
"button"
type=
"button"
class=
"ml-2 inline-flex items-center rounded-lg border border-gray-200 bg-white px-2 py-1 text-[11px] font-semibold text-gray-600 hover:bg-gray-50 disabled:opacity-50 dark:border-dark-700 dark:bg-dark-900 dark:text-gray-300 dark:hover:bg-dark-800"
class=
"ml-2 inline-flex items-center rounded-lg border border-gray-200 bg-white px-2 py-1 text-[11px] font-semibold text-gray-600 hover:bg-gray-50 disabled:opacity-50 dark:border-dark-700 dark:bg-dark-900 dark:text-gray-300 dark:hover:bg-dark-800"
...
@@ -211,6 +213,7 @@ function downloadChart() {
...
@@ -211,6 +213,7 @@ function downloadChart() {
>
>
{{
t
(
'
admin.ops.charts.downloadChart
'
)
}}
{{
t
(
'
admin.ops.charts.downloadChart
'
)
}}
</button>
</button>
</
template
>
</div>
</div>
</div>
</div>
...
...
frontend/src/views/admin/ops/types.ts
View file @
0e448297
...
@@ -14,6 +14,7 @@ export type {
...
@@ -14,6 +14,7 @@ export type {
EmailNotificationConfig
,
EmailNotificationConfig
,
OpsDistributedLockSettings
,
OpsDistributedLockSettings
,
OpsAlertRuntimeSettings
,
OpsAlertRuntimeSettings
,
OpsMetricThresholds
,
OpsAdvancedSettings
,
OpsAdvancedSettings
,
OpsDataRetentionSettings
,
OpsDataRetentionSettings
,
OpsAggregationSettings
OpsAggregationSettings
...
...
Prev
1
2
3
Next
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