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
92eafbc2
"vscode:/vscode.git/clone" did not exist on "48764e15a5fb460415c43ca77c6681dfd01bf480"
Commit
92eafbc2
authored
Jan 11, 2026
by
IanShaw027
Browse files
feat(ops): 优化运维监控界面组件功能和交互
parent
2548800c
Changes
3
Show whitespace changes
Inline
Side-by-side
frontend/src/views/admin/ops/components/OpsAlertRulesCard.vue
View file @
92eafbc2
...
@@ -5,6 +5,7 @@ import { useAppStore } from '@/stores/app'
...
@@ -5,6 +5,7 @@ import { useAppStore } from '@/stores/app'
import
BaseDialog
from
'
@/components/common/BaseDialog.vue
'
import
BaseDialog
from
'
@/components/common/BaseDialog.vue
'
import
ConfirmDialog
from
'
@/components/common/ConfirmDialog.vue
'
import
ConfirmDialog
from
'
@/components/common/ConfirmDialog.vue
'
import
Select
,
{
type
SelectOption
}
from
'
@/components/common/Select.vue
'
import
Select
,
{
type
SelectOption
}
from
'
@/components/common/Select.vue
'
import
{
adminAPI
}
from
'
@/api
'
import
{
opsAPI
}
from
'
@/api/admin/ops
'
import
{
opsAPI
}
from
'
@/api/admin/ops
'
import
type
{
AlertRule
,
MetricType
,
Operator
}
from
'
../types
'
import
type
{
AlertRule
,
MetricType
,
Operator
}
from
'
../types
'
import
type
{
OpsSeverity
}
from
'
@/api/admin/ops
'
import
type
{
OpsSeverity
}
from
'
@/api/admin/ops
'
...
@@ -31,6 +32,7 @@ async function load() {
...
@@ -31,6 +32,7 @@ async function load() {
onMounted
(()
=>
{
onMounted
(()
=>
{
load
()
load
()
loadGroups
()
})
})
const
sortedRules
=
computed
(()
=>
{
const
sortedRules
=
computed
(()
=>
{
...
@@ -44,29 +46,214 @@ const draft = ref<AlertRule | null>(null)
...
@@ -44,29 +46,214 @@ const draft = ref<AlertRule | null>(null)
type
MetricGroup
=
'
system
'
|
'
group
'
|
'
account
'
type
MetricGroup
=
'
system
'
|
'
group
'
|
'
account
'
interface
MetricDefinition
{
type
:
MetricType
group
:
MetricGroup
label
:
string
description
:
string
recommendedOperator
:
Operator
recommendedThreshold
:
number
unit
?:
string
}
const
groupMetricTypes
=
new
Set
<
MetricType
>
([
'
group_available_accounts
'
,
'
group_available_ratio
'
,
'
group_rate_limit_ratio
'
])
function
parsePositiveInt
(
value
:
unknown
):
number
|
null
{
if
(
value
==
null
)
return
null
if
(
typeof
value
===
'
boolean
'
)
return
null
const
n
=
typeof
value
===
'
number
'
?
value
:
Number
.
parseInt
(
String
(
value
),
10
)
return
Number
.
isFinite
(
n
)
&&
n
>
0
?
n
:
null
}
const
groupOptionsBase
=
ref
<
SelectOption
[]
>
([])
async
function
loadGroups
()
{
try
{
const
list
=
await
adminAPI
.
groups
.
getAll
()
groupOptionsBase
.
value
=
list
.
map
((
g
)
=>
({
value
:
g
.
id
,
label
:
g
.
name
}))
}
catch
(
err
)
{
console
.
error
(
'
[OpsAlertRulesCard] Failed to load groups
'
,
err
)
groupOptionsBase
.
value
=
[]
}
}
const
isGroupMetricSelected
=
computed
(()
=>
{
const
metricType
=
draft
.
value
?.
metric_type
return
metricType
?
groupMetricTypes
.
has
(
metricType
)
:
false
})
const
draftGroupId
=
computed
<
number
|
null
>
({
get
()
{
return
parsePositiveInt
(
draft
.
value
?.
filters
?.
group_id
)
},
set
(
value
)
{
if
(
!
draft
.
value
)
return
if
(
value
==
null
)
{
if
(
!
draft
.
value
.
filters
)
return
delete
draft
.
value
.
filters
.
group_id
if
(
Object
.
keys
(
draft
.
value
.
filters
).
length
===
0
)
{
delete
draft
.
value
.
filters
}
return
}
if
(
!
draft
.
value
.
filters
)
draft
.
value
.
filters
=
{}
draft
.
value
.
filters
.
group_id
=
value
}
})
const
groupOptions
=
computed
<
SelectOption
[]
>
(()
=>
{
if
(
isGroupMetricSelected
.
value
)
return
groupOptionsBase
.
value
return
[{
value
:
null
,
label
:
t
(
'
admin.ops.alertRules.form.allGroups
'
)
},
...
groupOptionsBase
.
value
]
})
const
metricDefinitions
=
computed
(()
=>
{
const
metricDefinitions
=
computed
(()
=>
{
return
[
return
[
// System-level metrics
// System-level metrics
{
type
:
'
success_rate
'
as
MetricType
,
group
:
'
system
'
as
const
,
label
:
t
(
'
admin.ops.alertRules.metrics.successRate
'
)
},
{
{
type
:
'
error_rate
'
as
MetricType
,
group
:
'
system
'
as
const
,
label
:
t
(
'
admin.ops.alertRules.metrics.errorRate
'
)
},
type
:
'
success_rate
'
,
{
type
:
'
upstream_error_rate
'
as
MetricType
,
group
:
'
system
'
as
const
,
label
:
t
(
'
admin.ops.alertRules.metrics.upstreamErrorRate
'
)
},
group
:
'
system
'
,
{
type
:
'
p95_latency_ms
'
as
MetricType
,
group
:
'
system
'
as
const
,
label
:
t
(
'
admin.ops.alertRules.metrics.p95
'
)
},
label
:
t
(
'
admin.ops.alertRules.metrics.successRate
'
),
{
type
:
'
p99_latency_ms
'
as
MetricType
,
group
:
'
system
'
as
const
,
label
:
t
(
'
admin.ops.alertRules.metrics.p99
'
)
},
description
:
t
(
'
admin.ops.alertRules.metricDescriptions.successRate
'
),
{
type
:
'
cpu_usage_percent
'
as
MetricType
,
group
:
'
system
'
as
const
,
label
:
t
(
'
admin.ops.alertRules.metrics.cpu
'
)
},
recommendedOperator
:
'
<
'
,
{
type
:
'
memory_usage_percent
'
as
MetricType
,
group
:
'
system
'
as
const
,
label
:
t
(
'
admin.ops.alertRules.metrics.memory
'
)
},
recommendedThreshold
:
99
,
{
type
:
'
concurrency_queue_depth
'
as
MetricType
,
group
:
'
system
'
as
const
,
label
:
t
(
'
admin.ops.alertRules.metrics.queueDepth
'
)
},
unit
:
'
%
'
},
{
type
:
'
error_rate
'
,
group
:
'
system
'
,
label
:
t
(
'
admin.ops.alertRules.metrics.errorRate
'
),
description
:
t
(
'
admin.ops.alertRules.metricDescriptions.errorRate
'
),
recommendedOperator
:
'
>
'
,
recommendedThreshold
:
1
,
unit
:
'
%
'
},
{
type
:
'
upstream_error_rate
'
,
group
:
'
system
'
,
label
:
t
(
'
admin.ops.alertRules.metrics.upstreamErrorRate
'
),
description
:
t
(
'
admin.ops.alertRules.metricDescriptions.upstreamErrorRate
'
),
recommendedOperator
:
'
>
'
,
recommendedThreshold
:
1
,
unit
:
'
%
'
},
{
type
:
'
p95_latency_ms
'
,
group
:
'
system
'
,
label
:
t
(
'
admin.ops.alertRules.metrics.p95
'
),
description
:
t
(
'
admin.ops.alertRules.metricDescriptions.p95
'
),
recommendedOperator
:
'
>
'
,
recommendedThreshold
:
1000
,
unit
:
'
ms
'
},
{
type
:
'
p99_latency_ms
'
,
group
:
'
system
'
,
label
:
t
(
'
admin.ops.alertRules.metrics.p99
'
),
description
:
t
(
'
admin.ops.alertRules.metricDescriptions.p99
'
),
recommendedOperator
:
'
>
'
,
recommendedThreshold
:
2000
,
unit
:
'
ms
'
},
{
type
:
'
cpu_usage_percent
'
,
group
:
'
system
'
,
label
:
t
(
'
admin.ops.alertRules.metrics.cpu
'
),
description
:
t
(
'
admin.ops.alertRules.metricDescriptions.cpu
'
),
recommendedOperator
:
'
>
'
,
recommendedThreshold
:
80
,
unit
:
'
%
'
},
{
type
:
'
memory_usage_percent
'
,
group
:
'
system
'
,
label
:
t
(
'
admin.ops.alertRules.metrics.memory
'
),
description
:
t
(
'
admin.ops.alertRules.metricDescriptions.memory
'
),
recommendedOperator
:
'
>
'
,
recommendedThreshold
:
80
,
unit
:
'
%
'
},
{
type
:
'
concurrency_queue_depth
'
,
group
:
'
system
'
,
label
:
t
(
'
admin.ops.alertRules.metrics.queueDepth
'
),
description
:
t
(
'
admin.ops.alertRules.metricDescriptions.queueDepth
'
),
recommendedOperator
:
'
>
'
,
recommendedThreshold
:
10
},
// Group-level metrics (requires group_id filter)
// Group-level metrics (requires group_id filter)
{
type
:
'
group_available_accounts
'
as
MetricType
,
group
:
'
group
'
as
const
,
label
:
t
(
'
admin.ops.alertRules.metrics.groupAvailableAccounts
'
)
},
{
{
type
:
'
group_available_ratio
'
as
MetricType
,
group
:
'
group
'
as
const
,
label
:
t
(
'
admin.ops.alertRules.metrics.groupAvailableRatio
'
)
},
type
:
'
group_available_accounts
'
,
{
type
:
'
group_rate_limit_ratio
'
as
MetricType
,
group
:
'
group
'
as
const
,
label
:
t
(
'
admin.ops.alertRules.metrics.groupRateLimitRatio
'
)
},
group
:
'
group
'
,
label
:
t
(
'
admin.ops.alertRules.metrics.groupAvailableAccounts
'
),
description
:
t
(
'
admin.ops.alertRules.metricDescriptions.groupAvailableAccounts
'
),
recommendedOperator
:
'
<
'
,
recommendedThreshold
:
1
},
{
type
:
'
group_available_ratio
'
,
group
:
'
group
'
,
label
:
t
(
'
admin.ops.alertRules.metrics.groupAvailableRatio
'
),
description
:
t
(
'
admin.ops.alertRules.metricDescriptions.groupAvailableRatio
'
),
recommendedOperator
:
'
<
'
,
recommendedThreshold
:
50
,
unit
:
'
%
'
},
{
type
:
'
group_rate_limit_ratio
'
,
group
:
'
group
'
,
label
:
t
(
'
admin.ops.alertRules.metrics.groupRateLimitRatio
'
),
description
:
t
(
'
admin.ops.alertRules.metricDescriptions.groupRateLimitRatio
'
),
recommendedOperator
:
'
>
'
,
recommendedThreshold
:
10
,
unit
:
'
%
'
},
// Account-level metrics
// Account-level metrics
{
type
:
'
account_rate_limited_count
'
as
MetricType
,
group
:
'
account
'
as
const
,
label
:
t
(
'
admin.ops.alertRules.metrics.accountRateLimitedCount
'
)
},
{
{
type
:
'
account_error_count
'
as
MetricType
,
group
:
'
account
'
as
const
,
label
:
t
(
'
admin.ops.alertRules.metrics.accountErrorCount
'
)
},
type
:
'
account_rate_limited_count
'
,
{
type
:
'
account_error_ratio
'
as
MetricType
,
group
:
'
account
'
as
const
,
label
:
t
(
'
admin.ops.alertRules.metrics.accountErrorRatio
'
)
},
group
:
'
account
'
,
{
type
:
'
overload_account_count
'
as
MetricType
,
group
:
'
account
'
as
const
,
label
:
t
(
'
admin.ops.alertRules.metrics.overloadAccountCount
'
)
}
label
:
t
(
'
admin.ops.alertRules.metrics.accountRateLimitedCount
'
),
]
satisfies
Array
<
{
type
:
MetricType
;
group
:
MetricGroup
;
label
:
string
}
>
description
:
t
(
'
admin.ops.alertRules.metricDescriptions.accountRateLimitedCount
'
),
recommendedOperator
:
'
>
'
,
recommendedThreshold
:
0
},
{
type
:
'
account_error_count
'
,
group
:
'
account
'
,
label
:
t
(
'
admin.ops.alertRules.metrics.accountErrorCount
'
),
description
:
t
(
'
admin.ops.alertRules.metricDescriptions.accountErrorCount
'
),
recommendedOperator
:
'
>
'
,
recommendedThreshold
:
0
},
{
type
:
'
account_error_ratio
'
,
group
:
'
account
'
,
label
:
t
(
'
admin.ops.alertRules.metrics.accountErrorRatio
'
),
description
:
t
(
'
admin.ops.alertRules.metricDescriptions.accountErrorRatio
'
),
recommendedOperator
:
'
>
'
,
recommendedThreshold
:
5
,
unit
:
'
%
'
},
{
type
:
'
overload_account_count
'
,
group
:
'
account
'
,
label
:
t
(
'
admin.ops.alertRules.metrics.overloadAccountCount
'
),
description
:
t
(
'
admin.ops.alertRules.metricDescriptions.overloadAccountCount
'
),
recommendedOperator
:
'
>
'
,
recommendedThreshold
:
0
}
]
satisfies
MetricDefinition
[]
})
const
selectedMetricDefinition
=
computed
(()
=>
{
const
metricType
=
draft
.
value
?.
metric_type
if
(
!
metricType
)
return
null
return
metricDefinitions
.
value
.
find
((
m
)
=>
m
.
type
===
metricType
)
??
null
})
})
const
metricOptions
=
computed
(()
=>
{
const
metricOptions
=
computed
(()
=>
{
...
@@ -137,6 +324,9 @@ const editorValidation = computed(() => {
...
@@ -137,6 +324,9 @@ const editorValidation = computed(() => {
if
(
!
r
)
return
{
valid
:
true
,
errors
}
if
(
!
r
)
return
{
valid
:
true
,
errors
}
if
(
!
r
.
name
||
!
r
.
name
.
trim
())
errors
.
push
(
t
(
'
admin.ops.alertRules.validation.nameRequired
'
))
if
(
!
r
.
name
||
!
r
.
name
.
trim
())
errors
.
push
(
t
(
'
admin.ops.alertRules.validation.nameRequired
'
))
if
(
!
r
.
metric_type
)
errors
.
push
(
t
(
'
admin.ops.alertRules.validation.metricRequired
'
))
if
(
!
r
.
metric_type
)
errors
.
push
(
t
(
'
admin.ops.alertRules.validation.metricRequired
'
))
if
(
groupMetricTypes
.
has
(
r
.
metric_type
)
&&
!
parsePositiveInt
(
r
.
filters
?.
group_id
))
{
errors
.
push
(
t
(
'
admin.ops.alertRules.validation.groupIdRequired
'
))
}
if
(
!
r
.
operator
)
errors
.
push
(
t
(
'
admin.ops.alertRules.validation.operatorRequired
'
))
if
(
!
r
.
operator
)
errors
.
push
(
t
(
'
admin.ops.alertRules.validation.operatorRequired
'
))
if
(
!
(
typeof
r
.
threshold
===
'
number
'
&&
Number
.
isFinite
(
r
.
threshold
)))
if
(
!
(
typeof
r
.
threshold
===
'
number
'
&&
Number
.
isFinite
(
r
.
threshold
)))
errors
.
push
(
t
(
'
admin.ops.alertRules.validation.thresholdRequired
'
))
errors
.
push
(
t
(
'
admin.ops.alertRules.validation.thresholdRequired
'
))
...
@@ -321,6 +511,18 @@ function cancelDelete() {
...
@@ -321,6 +511,18 @@ function cancelDelete() {
<div>
<div>
<label
class=
"input-label"
>
{{
t
(
'
admin.ops.alertRules.form.metric
'
)
}}
</label>
<label
class=
"input-label"
>
{{
t
(
'
admin.ops.alertRules.form.metric
'
)
}}
</label>
<Select
v-model=
"draft!.metric_type"
:options=
"metricOptions"
/>
<Select
v-model=
"draft!.metric_type"
:options=
"metricOptions"
/>
<div
v-if=
"selectedMetricDefinition"
class=
"mt-1 space-y-0.5 text-xs text-gray-500 dark:text-gray-400"
>
<p>
{{
selectedMetricDefinition
.
description
}}
</p>
<p>
{{
t
(
'
admin.ops.alertRules.hints.recommended
'
,
{
operator
:
selectedMetricDefinition
.
recommendedOperator
,
threshold
:
selectedMetricDefinition
.
recommendedThreshold
,
unit
:
selectedMetricDefinition
.
unit
||
''
}
)
}}
<
/p
>
<
/div
>
<
/div
>
<
/div
>
<
div
>
<
div
>
...
@@ -328,6 +530,23 @@ function cancelDelete() {
...
@@ -328,6 +530,23 @@ function cancelDelete() {
<
Select
v
-
model
=
"
draft!.operator
"
:
options
=
"
operatorOptions
"
/>
<
Select
v
-
model
=
"
draft!.operator
"
:
options
=
"
operatorOptions
"
/>
<
/div
>
<
/div
>
<
div
class
=
"
md:col-span-2
"
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.ops.alertRules.form.groupId
'
)
}}
<
span
v
-
if
=
"
isGroupMetricSelected
"
class
=
"
ml-1 text-red-500
"
>*<
/span
>
<
/label
>
<
Select
v
-
model
=
"
draftGroupId
"
:
options
=
"
groupOptions
"
searchable
:
placeholder
=
"
t('admin.ops.alertRules.form.groupPlaceholder')
"
:
error
=
"
isGroupMetricSelected && !draftGroupId
"
/>
<
p
class
=
"
mt-1 text-xs text-gray-500 dark:text-gray-400
"
>
{{
isGroupMetricSelected
?
t
(
'
admin.ops.alertRules.hints.groupRequired
'
)
:
t
(
'
admin.ops.alertRules.hints.groupOptional
'
)
}}
<
/p
>
<
/div
>
<
div
>
<
div
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.ops.alertRules.form.threshold
'
)
}}
<
/label
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.ops.alertRules.form.threshold
'
)
}}
<
/label
>
<
input
v
-
model
.
number
=
"
draft!.threshold
"
class
=
"
input
"
type
=
"
number
"
/>
<
input
v
-
model
.
number
=
"
draft!.threshold
"
class
=
"
input
"
type
=
"
number
"
/>
...
...
frontend/src/views/admin/ops/components/OpsDashboardHeader.vue
View file @
92eafbc2
...
@@ -239,47 +239,6 @@ const ttftP50Ms = computed(() => overview.value?.ttft?.p50_ms ?? null)
...
@@ -239,47 +239,6 @@ const ttftP50Ms = computed(() => overview.value?.ttft?.p50_ms ?? null)
const
ttftAvgMs
=
computed
(()
=>
overview
.
value
?.
ttft
?.
avg_ms
??
null
)
const
ttftAvgMs
=
computed
(()
=>
overview
.
value
?.
ttft
?.
avg_ms
??
null
)
const
ttftMaxMs
=
computed
(()
=>
overview
.
value
?.
ttft
?.
max_ms
??
null
)
const
ttftMaxMs
=
computed
(()
=>
overview
.
value
?.
ttft
?.
max_ms
??
null
)
// --- WebSocket status ---
const
wsStatusLabel
=
computed
(()
=>
{
switch
(
props
.
wsStatus
)
{
case
'
connected
'
:
return
t
(
'
admin.ops.realtime.connected
'
)
case
'
connecting
'
:
return
t
(
'
admin.ops.realtime.connecting
'
)
case
'
reconnecting
'
:
return
t
(
'
admin.ops.realtime.reconnecting
'
)
case
'
offline
'
:
return
t
(
'
admin.ops.realtime.offline
'
)
case
'
closed
'
:
default
:
return
t
(
'
admin.ops.realtime.closed
'
)
}
})
const
wsStatusDotClass
=
computed
(()
=>
{
switch
(
props
.
wsStatus
)
{
case
'
connected
'
:
return
'
bg-green-500
'
case
'
reconnecting
'
:
case
'
connecting
'
:
return
'
bg-yellow-500
'
case
'
offline
'
:
return
'
bg-orange-500
'
case
'
closed
'
:
default
:
return
'
bg-gray-400
'
}
})
const
wsReconnectHint
=
computed
(()
=>
{
if
(
props
.
wsStatus
!==
'
reconnecting
'
)
return
''
const
delayMs
=
props
.
wsReconnectInMs
??
null
if
(
typeof
delayMs
!==
'
number
'
||
!
Number
.
isFinite
(
delayMs
)
||
delayMs
<=
0
)
return
''
const
sec
=
Math
.
max
(
1
,
Math
.
ceil
(
delayMs
/
1000
))
return
t
(
'
admin.ops.realtime.reconnectIn
'
,
{
seconds
:
sec
})
})
// --- Health Score & Diagnosis (primary) ---
// --- Health Score & Diagnosis (primary) ---
const
isSystemIdle
=
computed
(()
=>
{
const
isSystemIdle
=
computed
(()
=>
{
...
@@ -662,19 +621,14 @@ function openJobsDetails() {
...
@@ -662,19 +621,14 @@ function openJobsDetails() {
<span>
·
</span>
<span>
·
</span>
<span>
{{
t
(
'
common.refresh
'
)
}}
:
{{
updatedAtLabel
}}
</span>
<span>
{{
t
(
'
common.refresh
'
)
}}
:
{{
updatedAtLabel
}}
</span>
<span>
·
</span>
<span
class=
"flex items-center gap-1.5"
>
<template
v-if=
"systemMetrics"
>
<span
class=
"relative flex h-2 w-2"
>
<span>
·
</span>
<span
<span>
v-if=
"wsStatus === 'connected'"
{{
t
(
'
admin.ops.collectedAt
'
)
}}
{{
formatTimeShort
(
systemMetrics
.
created_at
)
}}
class=
"absolute inline-flex h-full w-full animate-ping rounded-full bg-green-400 opacity-75"
(
{{
t
(
'
admin.ops.window
'
)
}}
{{
systemMetrics
.
window_minutes
}}
m)
></span>
<span
class=
"relative inline-flex h-2 w-2 rounded-full"
:class=
"wsStatusDotClass"
></span>
</span>
<span>
{{
wsStatusLabel
}}
</span>
<span
v-if=
"wsReconnectHint"
class=
"text-[11px] text-gray-400"
>
(
{{
wsReconnectHint
}}
)
</span>
</span>
</span>
</
template
>
</div>
</div>
</div>
</div>
...
@@ -1189,14 +1143,6 @@ function openJobsDetails() {
...
@@ -1189,14 +1143,6 @@ function openJobsDetails() {
<!-- Integrated: System health (cards) -->
<!-- Integrated: System health (cards) -->
<div
v-if=
"overview"
class=
"mt-2 border-t border-gray-100 pt-4 dark:border-dark-700"
>
<div
v-if=
"overview"
class=
"mt-2 border-t border-gray-100 pt-4 dark:border-dark-700"
>
<
div
class
=
"
mb-3 flex flex-wrap items-center justify-between gap-2 text-xs text-gray-500 dark:text-gray-400
"
>
<
span
v
-
if
=
"
systemMetrics
"
>
{{
t
(
'
admin.ops.collectedAt
'
)
}}
{{
formatTimeShort
(
systemMetrics
.
created_at
)
}}
({{
t
(
'
admin.ops.window
'
)
}}
{{
systemMetrics
.
window_minutes
}}
m
)
<
/span
>
<
span
v
-
else
>
{{
t
(
'
admin.ops.noSystemMetrics
'
)
}}
<
/span
>
<
/div
>
<div
class=
"grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-6"
>
<div
class=
"grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-6"
>
<!-- CPU -->
<!-- CPU -->
<div
class=
"rounded-xl bg-gray-50 p-3 dark:bg-dark-900"
>
<div
class=
"rounded-xl bg-gray-50 p-3 dark:bg-dark-900"
>
...
...
frontend/src/views/admin/ops/components/OpsRequestDetailsModal.vue
View file @
92eafbc2
...
@@ -150,11 +150,10 @@ const kindBadgeClass = (kind: string) => {
...
@@ -150,11 +150,10 @@ 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=
"text-xs text-gray-500 dark:text-gray-400 mb-4"
>
<div
class=
"flex items-center justify-between mb-4"
>
<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
>
<
div
class
=
"
flex justify-end mb-4
"
>
<
button
<
button
type
=
"
button
"
type
=
"
button
"
class
=
"
btn btn-secondary btn-sm
"
class
=
"
btn btn-secondary btn-sm
"
...
...
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