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
bb1fd54d
Unverified
Commit
bb1fd54d
authored
Jan 13, 2026
by
Wesley Liddick
Committed by
GitHub
Jan 13, 2026
Browse files
Merge pull request #257 from Edric-Li/feat/ops-fullscreen-scrollbar
feat(ops): 添加运维监控全屏模式 & 优化滚动条
parents
3b71bc3d
d02e1db0
Changes
6
Hide whitespace changes
Inline
Side-by-side
frontend/src/i18n/locales/en.ts
View file @
bb1fd54d
...
@@ -1943,6 +1943,9 @@ export default {
...
@@ -1943,6 +1943,9 @@ export default {
'
6h
'
:
'
Last 6 hours
'
,
'
6h
'
:
'
Last 6 hours
'
,
'
24h
'
:
'
Last 24 hours
'
'
24h
'
:
'
Last 24 hours
'
},
},
fullscreen
:
{
enter
:
'
Enter Fullscreen
'
},
diagnosis
:
{
diagnosis
:
{
title
:
'
Smart Diagnosis
'
,
title
:
'
Smart Diagnosis
'
,
footer
:
'
Automated diagnostic suggestions based on current metrics
'
,
footer
:
'
Automated diagnostic suggestions based on current metrics
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
bb1fd54d
...
@@ -2088,6 +2088,9 @@ export default {
...
@@ -2088,6 +2088,9 @@ export default {
'
6h
'
:
'
近6小时
'
,
'
6h
'
:
'
近6小时
'
,
'
24h
'
:
'
近24小时
'
'
24h
'
:
'
近24小时
'
},
},
fullscreen
:
{
enter
:
'
进入全屏
'
},
diagnosis
:
{
diagnosis
:
{
title
:
'
智能诊断
'
,
title
:
'
智能诊断
'
,
footer
:
'
基于当前指标的自动诊断建议
'
,
footer
:
'
基于当前指标的自动诊断建议
'
,
...
...
frontend/src/style.css
View file @
bb1fd54d
...
@@ -19,7 +19,22 @@
...
@@ -19,7 +19,22 @@
@apply
min-h-screen;
@apply
min-h-screen;
}
}
/* 自定义滚动条 */
/* 自定义滚动条 - 默认隐藏,悬停或滚动时显示 */
*
{
scrollbar-width
:
thin
;
scrollbar-color
:
transparent
transparent
;
}
*
:hover
,
*
:focus-within
{
scrollbar-color
:
rgba
(
156
,
163
,
175
,
0.5
)
transparent
;
}
.dark
*
:hover
,
.dark
*
:focus-within
{
scrollbar-color
:
rgba
(
75
,
85
,
99
,
0.5
)
transparent
;
}
::-webkit-scrollbar
{
::-webkit-scrollbar
{
@apply
h-2
w-2;
@apply
h-2
w-2;
}
}
...
@@ -29,10 +44,15 @@
...
@@ -29,10 +44,15 @@
}
}
::-webkit-scrollbar-thumb
{
::-webkit-scrollbar-thumb
{
@apply
rounded-full
bg-gray-300
dark
:
bg-dark-600
;
@apply
rounded-full
bg-transparent;
transition
:
background-color
0.2s
ease
;
}
*
:hover::-webkit-scrollbar-thumb
{
@apply
bg-gray-300/50
dark
:
bg-dark-600
/
50
;
}
}
::-webkit-scrollbar-thumb:hover
{
*
:hover
::-webkit-scrollbar-thumb:hover
{
@apply
bg-gray-400
dark
:
bg-dark-500
;
@apply
bg-gray-400
dark
:
bg-dark-500
;
}
}
...
...
frontend/src/views/admin/ops/OpsDashboard.vue
View file @
bb1fd54d
<
template
>
<
template
>
<
AppLayout
>
<
component
:is=
"isFullscreen ? 'div' : AppLayout"
:class=
"isFullscreen ? 'flex min-h-screen flex-col justify-center bg-gray-50 dark:bg-dark-950' : ''"
>
<div
class=
"space-y-6 pb-12"
>
<div
:
class=
"
[isFullscreen ? 'p-4 md:p-6' : '', '
space-y-6 pb-12
']
"
>
<div
<div
v-if=
"errorMessage"
v-if=
"errorMessage"
class=
"rounded-2xl bg-red-50 p-4 text-sm text-red-600 dark:bg-red-900/20 dark:text-red-400"
class=
"rounded-2xl bg-red-50 p-4 text-sm text-red-600 dark:bg-red-900/20 dark:text-red-400"
...
@@ -22,6 +22,7 @@
...
@@ -22,6 +22,7 @@
:thresholds=
"metricThresholds"
:thresholds=
"metricThresholds"
:auto-refresh-enabled=
"autoRefreshEnabled"
:auto-refresh-enabled=
"autoRefreshEnabled"
:auto-refresh-countdown=
"autoRefreshCountdown"
:auto-refresh-countdown=
"autoRefreshCountdown"
:fullscreen=
"isFullscreen"
@
update:time-range=
"onTimeRangeChange"
@
update:time-range=
"onTimeRangeChange"
@
update:platform=
"onPlatformChange"
@
update:platform=
"onPlatformChange"
@
update:group=
"onGroupChange"
@
update:group=
"onGroupChange"
...
@@ -31,6 +32,8 @@
...
@@ -31,6 +32,8 @@
@
open-error-details=
"openErrorDetails"
@
open-error-details=
"openErrorDetails"
@
open-settings=
"showSettingsDialog = true"
@
open-settings=
"showSettingsDialog = true"
@
open-alert-rules=
"showAlertRulesCard = true"
@
open-alert-rules=
"showAlertRulesCard = true"
@
enter-fullscreen=
"enterFullscreen"
@
exit-fullscreen=
"exitFullscreen"
/>
/>
<!-- Row: Concurrency + Throughput -->
<!-- Row: Concurrency + Throughput -->
...
@@ -45,6 +48,7 @@
...
@@ -45,6 +48,7 @@
:top-groups=
"throughputTrend?.top_groups ?? []"
:top-groups=
"throughputTrend?.top_groups ?? []"
:loading=
"loadingTrend"
:loading=
"loadingTrend"
:time-range=
"timeRange"
:time-range=
"timeRange"
:fullscreen=
"isFullscreen"
@
select-platform=
"handleThroughputSelectPlatform"
@
select-platform=
"handleThroughputSelectPlatform"
@
select-group=
"handleThroughputSelectGroup"
@
select-group=
"handleThroughputSelectGroup"
@
open-details=
"handleOpenRequestDetails"
@
open-details=
"handleOpenRequestDetails"
...
@@ -72,36 +76,37 @@
...
@@ -72,36 +76,37 @@
<!-- Alert Events -->
<!-- Alert Events -->
<OpsAlertEventsCard
v-if=
"opsEnabled && !(loading && !hasLoadedOnce)"
/>
<OpsAlertEventsCard
v-if=
"opsEnabled && !(loading && !hasLoadedOnce)"
/>
<!-- Settings Dialog -->
<!-- Settings Dialog (hidden in fullscreen mode) -->
<OpsSettingsDialog
:show=
"showSettingsDialog"
@
close=
"showSettingsDialog = false"
@
saved=
"onSettingsSaved"
/>
<template
v-if=
"!isFullscreen"
>
<OpsSettingsDialog
:show=
"showSettingsDialog"
@
close=
"showSettingsDialog = false"
@
saved=
"onSettingsSaved"
/>
<!-- Alert Rules Dialog -->
<BaseDialog
:show=
"showAlertRulesCard"
:title=
"t('admin.ops.alertRules.title')"
width=
"extra-wide"
@
close=
"showAlertRulesCard = false"
>
<BaseDialog
:show=
"showAlertRulesCard"
:title=
"t('admin.ops.alertRules.title')"
width=
"extra-wide"
@
close=
"showAlertRulesCard = false"
>
<OpsAlertRulesCard
/>
<OpsAlertRulesCard
/>
</BaseDialog>
</BaseDialog>
<OpsErrorDetailsModal
<OpsErrorDetailsModal
:show=
"showErrorDetails"
:show=
"showErrorDetails"
:time-range=
"timeRange"
:time-range=
"timeRange"
:platform=
"platform"
:platform=
"platform"
:group-id=
"groupId"
:group-id=
"groupId"
:error-type=
"errorDetailsType"
:error-type=
"errorDetailsType"
@
update:show=
"showErrorDetails = $event"
@
update:show=
"showErrorDetails = $event"
@
openErrorDetail=
"openError"
@
openErrorDetail=
"openError"
/>
/>
<OpsErrorDetailModal
v-model:show=
"showErrorModal"
:error-id=
"selectedErrorId"
/>
<OpsErrorDetailModal
v-model:show=
"showErrorModal"
:error-id=
"selectedErrorId"
/>
<OpsRequestDetailsModal
<OpsRequestDetailsModal
v-model=
"showRequestDetails"
v-model=
"showRequestDetails"
:time-range=
"timeRange"
:time-range=
"timeRange"
:preset=
"requestDetailsPreset"
:preset=
"requestDetailsPreset"
:platform=
"platform"
:platform=
"platform"
:group-id=
"groupId"
:group-id=
"groupId"
@
openErrorDetail=
"openError"
@
openErrorDetail=
"openError"
/>
/>
</
template
>
</div>
</div>
</
AppLayou
t>
</
componen
t>
</template>
</template>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
...
@@ -163,12 +168,36 @@ const QUERY_KEYS = {
...
@@ -163,12 +168,36 @@ const QUERY_KEYS = {
timeRange
:
'
tr
'
,
timeRange
:
'
tr
'
,
platform
:
'
platform
'
,
platform
:
'
platform
'
,
groupId
:
'
group_id
'
,
groupId
:
'
group_id
'
,
queryMode
:
'
mode
'
queryMode
:
'
mode
'
,
fullscreen
:
'
fullscreen
'
}
as
const
}
as
const
const
isApplyingRouteQuery
=
ref
(
false
)
const
isApplyingRouteQuery
=
ref
(
false
)
const
isSyncingRouteQuery
=
ref
(
false
)
const
isSyncingRouteQuery
=
ref
(
false
)
// Fullscreen mode
const
isFullscreen
=
computed
(()
=>
{
const
val
=
route
.
query
[
QUERY_KEYS
.
fullscreen
]
return
val
===
'
1
'
||
val
===
'
true
'
})
function
exitFullscreen
()
{
const
nextQuery
=
{
...
route
.
query
}
delete
nextQuery
[
QUERY_KEYS
.
fullscreen
]
router
.
replace
({
query
:
nextQuery
})
}
function
enterFullscreen
()
{
const
nextQuery
=
{
...
route
.
query
,
[
QUERY_KEYS
.
fullscreen
]:
'
1
'
}
router
.
replace
({
query
:
nextQuery
})
}
function
handleKeydown
(
e
:
KeyboardEvent
)
{
if
(
e
.
key
===
'
Escape
'
&&
isFullscreen
.
value
)
{
exitFullscreen
()
}
}
let
dashboardFetchController
:
AbortController
|
null
=
null
let
dashboardFetchController
:
AbortController
|
null
=
null
let
dashboardFetchSeq
=
0
let
dashboardFetchSeq
=
0
...
@@ -603,6 +632,9 @@ watch(
...
@@ -603,6 +632,9 @@ watch(
)
)
onMounted
(
async
()
=>
{
onMounted
(
async
()
=>
{
// Fullscreen mode: listen for ESC key
window
.
addEventListener
(
'
keydown
'
,
handleKeydown
)
await
adminSettingsStore
.
fetch
()
await
adminSettingsStore
.
fetch
()
if
(
!
adminSettingsStore
.
opsMonitoringEnabled
)
{
if
(
!
adminSettingsStore
.
opsMonitoringEnabled
)
{
await
router
.
replace
(
'
/admin/settings
'
)
await
router
.
replace
(
'
/admin/settings
'
)
...
@@ -637,6 +669,7 @@ async function loadThresholds() {
...
@@ -637,6 +669,7 @@ async function loadThresholds() {
}
}
onUnmounted
(()
=>
{
onUnmounted
(()
=>
{
window
.
removeEventListener
(
'
keydown
'
,
handleKeydown
)
abortDashboardFetch
()
abortDashboardFetch
()
pauseAutoRefresh
()
pauseAutoRefresh
()
pauseCountdown
()
pauseCountdown
()
...
...
frontend/src/views/admin/ops/components/OpsDashboardHeader.vue
View file @
bb1fd54d
...
@@ -25,6 +25,7 @@ interface Props {
...
@@ -25,6 +25,7 @@ interface Props {
thresholds
?:
OpsMetricThresholds
|
null
// 阈值配置
thresholds
?:
OpsMetricThresholds
|
null
// 阈值配置
autoRefreshEnabled
?:
boolean
autoRefreshEnabled
?:
boolean
autoRefreshCountdown
?:
number
autoRefreshCountdown
?:
number
fullscreen
?:
boolean
}
}
interface
Emits
{
interface
Emits
{
...
@@ -37,6 +38,8 @@ interface Emits {
...
@@ -37,6 +38,8 @@ interface Emits {
(
e
:
'
openErrorDetails
'
,
kind
:
'
request
'
|
'
upstream
'
):
void
(
e
:
'
openErrorDetails
'
,
kind
:
'
request
'
|
'
upstream
'
):
void
(
e
:
'
openSettings
'
):
void
(
e
:
'
openSettings
'
):
void
(
e
:
'
openAlertRules
'
):
void
(
e
:
'
openAlertRules
'
):
void
(
e
:
'
enterFullscreen
'
):
void
(
e
:
'
exitFullscreen
'
):
void
}
}
const
props
=
defineProps
<
Props
>
()
const
props
=
defineProps
<
Props
>
()
...
@@ -391,15 +394,15 @@ const healthScoreClass = computed(() => {
...
@@ -391,15 +394,15 @@ const healthScoreClass = computed(() => {
return
'
text-red-500
'
return
'
text-red-500
'
})
})
const
circleSize
=
100
const
circleSize
=
computed
(()
=>
props
.
fullscreen
?
140
:
100
)
const
strokeWidth
=
8
const
strokeWidth
=
computed
(()
=>
props
.
fullscreen
?
10
:
8
)
const
radius
=
(
circleSize
-
strokeWidth
)
/
2
const
radius
=
computed
(()
=>
(
circleSize
.
value
-
strokeWidth
.
value
)
/
2
)
const
circumference
=
2
*
Math
.
PI
*
radius
const
circumference
=
computed
(()
=>
2
*
Math
.
PI
*
radius
.
value
)
const
dashOffset
=
computed
(()
=>
{
const
dashOffset
=
computed
(()
=>
{
if
(
isSystemIdle
.
value
)
return
0
if
(
isSystemIdle
.
value
)
return
0
if
(
healthScoreValue
.
value
==
null
)
return
0
if
(
healthScoreValue
.
value
==
null
)
return
0
const
score
=
Math
.
max
(
0
,
Math
.
min
(
100
,
healthScoreValue
.
value
))
const
score
=
Math
.
max
(
0
,
Math
.
min
(
100
,
healthScoreValue
.
value
))
return
circumference
-
(
score
/
100
)
*
circumference
return
circumference
.
value
-
(
score
/
100
)
*
circumference
.
value
})
})
interface
DiagnosisItem
{
interface
DiagnosisItem
{
...
@@ -814,7 +817,7 @@ function handleToolbarRefresh() {
...
@@ -814,7 +817,7 @@ function handleToolbarRefresh() {
</
script
>
</
script
>
<
template
>
<
template
>
<div
class=
"flex flex-col gap-4 rounded-3xl bg-white
p-6
shadow-sm ring-1 ring-gray-900/5 dark:bg-dark-800 dark:ring-dark-700"
>
<div
:
class=
"
['
flex flex-col gap-4 rounded-3xl bg-white shadow-sm ring-1 ring-gray-900/5 dark:bg-dark-800 dark:ring-dark-700
', props.fullscreen ? 'p-8' : 'p-6']
"
>
<!-- Top Toolbar -->
<!-- Top Toolbar -->
<div
class=
"flex flex-wrap items-center justify-between gap-4 border-b border-gray-100 pb-4 dark:border-dark-700"
>
<div
class=
"flex flex-wrap items-center justify-between gap-4 border-b border-gray-100 pb-4 dark:border-dark-700"
>
<div>
<div>
...
@@ -830,7 +833,7 @@ function handleToolbarRefresh() {
...
@@ -830,7 +833,7 @@ function handleToolbarRefresh() {
{{
t
(
'
admin.ops.title
'
)
}}
{{
t
(
'
admin.ops.title
'
)
}}
</h1>
</h1>
<div
class=
"mt-1 flex items-center gap-3 text-xs text-gray-500 dark:text-gray-400"
>
<div
v-if=
"!props.fullscreen"
class=
"mt-1 flex items-center gap-3 text-xs text-gray-500 dark:text-gray-400"
>
<span
class=
"flex items-center gap-1.5"
:title=
"props.loading ? t('admin.ops.loadingText') : t('admin.ops.ready')"
>
<span
class=
"flex items-center gap-1.5"
:title=
"props.loading ? t('admin.ops.loadingText') : t('admin.ops.ready')"
>
<span
class=
"relative flex h-2 w-2"
>
<span
class=
"relative flex h-2 w-2"
>
<span
class=
"relative inline-flex h-2 w-2 rounded-full"
:class=
"props.loading ? 'bg-gray-400' : 'bg-green-500'"
></span>
<span
class=
"relative inline-flex h-2 w-2 rounded-full"
:class=
"props.loading ? 'bg-gray-400' : 'bg-green-500'"
></span>
...
@@ -863,28 +866,30 @@ function handleToolbarRefresh() {
...
@@ -863,28 +866,30 @@ function handleToolbarRefresh() {
</div>
</div>
<div
class=
"flex flex-wrap items-center gap-3"
>
<div
class=
"flex flex-wrap items-center gap-3"
>
<Select
<
template
v-if=
"!props.fullscreen"
>
:model-value=
"platform"
<Select
:options=
"platformOptions"
:model-value=
"platform"
class=
"w-full sm:w-[140px]"
:options=
"platformOptions"
@
update:model-value=
"handlePlatformChange"
class=
"w-full sm:w-[140px]"
/>
@
update:model-value=
"handlePlatformChange"
/>
<Select
:model-value=
"groupId"
<Select
:options=
"groupOptions"
:model-value=
"groupId"
class=
"w-full sm:w-[160px]"
:options=
"groupOptions"
@
update:model-value=
"handleGroupChange"
class=
"w-full sm:w-[160px]"
/>
@
update:model-value=
"handleGroupChange"
/>
<div
class=
"mx-1 hidden h-4 w-[1px] bg-gray-200 dark:bg-dark-700 sm:block"
></div>
<div
class=
"mx-1 hidden h-4 w-[1px] bg-gray-200 dark:bg-dark-700 sm:block"
></div>
<Select
:model-value=
"timeRange"
<Select
:options=
"timeRangeOptions"
:model-value=
"timeRange"
class=
"relative w-full sm:w-[150px]"
:options=
"timeRangeOptions"
@
update:model-value=
"handleTimeRangeChange"
class=
"relative w-full sm:w-[150px]"
/>
@
update:model-value=
"handleTimeRangeChange"
/>
</
template
>
<Select
<Select
v-if=
"false"
v-if=
"false"
...
@@ -895,6 +900,7 @@ function handleToolbarRefresh() {
...
@@ -895,6 +900,7 @@ function handleToolbarRefresh() {
/>
/>
<button
<button
v-if=
"!props.fullscreen"
type=
"button"
type=
"button"
class=
"flex h-8 w-8 items-center justify-center rounded-lg bg-gray-100 text-gray-500 transition-colors hover:bg-gray-200 dark:bg-dark-700 dark:text-gray-400 dark:hover:bg-dark-600"
class=
"flex h-8 w-8 items-center justify-center rounded-lg bg-gray-100 text-gray-500 transition-colors hover:bg-gray-200 dark:bg-dark-700 dark:text-gray-400 dark:hover:bg-dark-600"
:disabled=
"loading"
:disabled=
"loading"
...
@@ -911,9 +917,11 @@ function handleToolbarRefresh() {
...
@@ -911,9 +917,11 @@ function handleToolbarRefresh() {
</svg>
</svg>
</button>
</button>
<div
class=
"mx-1 hidden h-4 w-[1px] bg-gray-200 dark:bg-dark-700 sm:block"
></div>
<div
v-if=
"!props.fullscreen"
class=
"mx-1 hidden h-4 w-[1px] bg-gray-200 dark:bg-dark-700 sm:block"
></div>
<!-- Alert Rules Button (hidden in fullscreen) -->
<button
<button
v-if=
"!props.fullscreen"
type=
"button"
type=
"button"
class=
"flex h-8 items-center gap-1.5 rounded-lg bg-blue-100 px-3 text-xs font-bold text-blue-700 transition-colors hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400 dark:hover:bg-blue-900/50"
class=
"flex h-8 items-center gap-1.5 rounded-lg bg-blue-100 px-3 text-xs font-bold text-blue-700 transition-colors hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400 dark:hover:bg-blue-900/50"
:title=
"t('admin.ops.alertRules.title')"
:title=
"t('admin.ops.alertRules.title')"
...
@@ -925,7 +933,9 @@ function handleToolbarRefresh() {
...
@@ -925,7 +933,9 @@ function handleToolbarRefresh() {
<span
class=
"hidden sm:inline"
>
{{ t('admin.ops.alertRules.manage') }}
</span>
<span
class=
"hidden sm:inline"
>
{{ t('admin.ops.alertRules.manage') }}
</span>
</button>
</button>
<!-- Settings Button (hidden in fullscreen) -->
<button
<button
v-if=
"!props.fullscreen"
type=
"button"
type=
"button"
class=
"flex h-8 items-center gap-1.5 rounded-lg bg-gray-100 px-3 text-xs font-bold text-gray-700 transition-colors hover:bg-gray-200 dark:bg-dark-700 dark:text-gray-300 dark:hover:bg-dark-600"
class=
"flex h-8 items-center gap-1.5 rounded-lg bg-gray-100 px-3 text-xs font-bold text-gray-700 transition-colors hover:bg-gray-200 dark:bg-dark-700 dark:text-gray-300 dark:hover:bg-dark-600"
:title=
"t('admin.ops.settings.title')"
:title=
"t('admin.ops.settings.title')"
...
@@ -937,13 +947,26 @@ function handleToolbarRefresh() {
...
@@ -937,13 +947,26 @@ function handleToolbarRefresh() {
</svg>
</svg>
<span
class=
"hidden sm:inline"
>
{{ t('common.settings') }}
</span>
<span
class=
"hidden sm:inline"
>
{{ t('common.settings') }}
</span>
</button>
</button>
<!-- Enter Fullscreen Button (hidden in fullscreen mode) -->
<button
v-if=
"!props.fullscreen"
type=
"button"
class=
"flex h-8 w-8 items-center justify-center rounded-lg bg-gray-100 text-gray-700 transition-colors hover:bg-gray-200 dark:bg-dark-700 dark:text-gray-300 dark:hover:bg-dark-600"
:title=
"t('admin.ops.fullscreen.enter')"
@
click=
"emit('enterFullscreen')"
>
<svg
class=
"h-4 w-4"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"2"
d=
"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
<div
v-if=
"overview"
class=
"grid grid-cols-1 gap-6 lg:grid-cols-12"
>
<div
v-if=
"overview"
class=
"grid grid-cols-1 gap-6 lg:grid-cols-12"
>
<!-- Left: Health + Realtime -->
<!-- Left: Health + Realtime -->
<div
class=
"rounded-2xl bg-gray-50
p-4
dark:bg-dark-900 lg:col-span-5"
>
<div
:
class=
"
['
rounded-2xl bg-gray-50 dark:bg-dark-900 lg:col-span-5
', props.fullscreen ? 'p-6' : 'p-4']
"
>
<div
class=
"grid grid-cols-1 gap-6 md:grid-cols-[200px_1fr] md:items-center"
>
<div
class=
"grid
h-full
grid-cols-1 gap-6 md:grid-cols-[200px_1fr] md:items-center"
>
<!-- 1) Health Score -->
<!-- 1) Health Score -->
<div
<div
class=
"group relative flex cursor-pointer flex-col items-center justify-center rounded-xl py-2 transition-all hover:bg-white/60 dark:hover:bg-dark-800/60 md:border-r md:border-gray-200 md:pr-6 dark:md:border-dark-700"
class=
"group relative flex cursor-pointer flex-col items-center justify-center rounded-xl py-2 transition-all hover:bg-white/60 dark:hover:bg-dark-800/60 md:border-r md:border-gray-200 md:pr-6 dark:md:border-dark-700"
...
@@ -1026,14 +1049,14 @@ function handleToolbarRefresh() {
...
@@ -1026,14 +1049,14 @@ function handleToolbarRefresh() {
</svg>
</svg>
<div
class=
"absolute flex flex-col items-center"
>
<div
class=
"absolute flex flex-col items-center"
>
<span
class=
"text-3xl
font-black
"
:class=
"
healthScoreClass"
>
<span
:
class=
"
[props.fullscreen ? 'text-5xl' : '
text-3xl
', '
font-black
',
healthScoreClass
]
"
>
{{ isSystemIdle ? t('admin.ops.idleStatus') : (overview.health_score ?? '--') }}
{{ isSystemIdle ? t('admin.ops.idleStatus') : (overview.health_score ?? '--') }}
</span>
</span>
<span
class=
"text-[10px]
font-bold uppercase tracking-wider text-gray-400"
>
{{ t('admin.ops.health') }}
</span>
<span
:
class=
"
[props.fullscreen ? 'text-xs' : '
text-[10px]
', '
font-bold uppercase tracking-wider text-gray-400
']
"
>
{{ t('admin.ops.health') }}
</span>
</div>
</div>
</div>
</div>
<div
class=
"mt-4 text-center"
>
<div
class=
"mt-4 text-center"
v-if=
"!props.fullscreen"
>
<div
class=
"flex items-center justify-center gap-1 text-xs font-medium text-gray-500"
>
<div
class=
"flex items-center justify-center gap-1 text-xs font-medium text-gray-500"
>
{{ t('admin.ops.healthCondition') }}
{{ t('admin.ops.healthCondition') }}
<HelpTooltip
:content=
"t('admin.ops.healthHelp')"
/>
<HelpTooltip
:content=
"t('admin.ops.healthHelp')"
/>
...
@@ -1051,7 +1074,7 @@ function handleToolbarRefresh() {
...
@@ -1051,7 +1074,7 @@ function handleToolbarRefresh() {
</div>
</div>
<!-- 2) Realtime Traffic -->
<!-- 2) Realtime Traffic -->
<div
class=
"flex flex-col justify-center py-2"
>
<div
class=
"flex
h-full
flex-col justify-center py-2"
>
<div
class=
"mb-3 flex flex-wrap items-center justify-between gap-2"
>
<div
class=
"mb-3 flex flex-wrap items-center justify-between gap-2"
>
<div
class=
"flex items-center gap-2"
>
<div
class=
"flex items-center gap-2"
>
<div
class=
"relative flex h-3 w-3 shrink-0"
>
<div
class=
"relative flex h-3 w-3 shrink-0"
>
...
@@ -1059,7 +1082,7 @@ function handleToolbarRefresh() {
...
@@ -1059,7 +1082,7 @@ function handleToolbarRefresh() {
<span
class=
"relative inline-flex h-3 w-3 rounded-full bg-blue-500"
></span>
<span
class=
"relative inline-flex h-3 w-3 rounded-full bg-blue-500"
></span>
</div>
</div>
<h3
class=
"text-xs font-bold uppercase tracking-wider text-gray-400"
>
{{ t('admin.ops.realtime.title') }}
</h3>
<h3
class=
"text-xs font-bold uppercase tracking-wider text-gray-400"
>
{{ t('admin.ops.realtime.title') }}
</h3>
<HelpTooltip
:content=
"t('admin.ops.tooltips.qps')"
/>
<HelpTooltip
v-if=
"!props.fullscreen"
:content=
"t('admin.ops.tooltips.qps')"
/>
</div>
</div>
<!-- Time Window Selector -->
<!-- Time Window Selector -->
...
@@ -1079,18 +1102,18 @@ function handleToolbarRefresh() {
...
@@ -1079,18 +1102,18 @@ function handleToolbarRefresh() {
</div>
</div>
</div>
</div>
<div
class=
"space-y-3"
>
<div
:
class=
"
props.fullscreen ? 'space-y-4' : '
space-y-3
'
"
>
<!-- Row 1: Current -->
<!-- Row 1: Current -->
<div>
<div>
<div
class=
"text-[10px]
font-bold uppercase text-gray-400"
>
{{ t('admin.ops.current') }}
</div>
<div
:
class=
"
[props.fullscreen ? 'text-xs' : '
text-[10px]
', '
font-bold uppercase text-gray-400
']
"
>
{{ t('admin.ops.current') }}
</div>
<div
class=
"mt-1 flex flex-wrap items-baseline gap-x-4 gap-y-2"
>
<div
class=
"mt-1 flex flex-wrap items-baseline gap-x-4 gap-y-2"
>
<div
class=
"flex items-baseline gap-1.5"
>
<div
class=
"flex items-baseline gap-1.5"
>
<span
class=
"text-xl
font-black text-gray-900 dark:text-white
sm:text-2xl
"
>
{{ displayRealTimeQps.toFixed(1) }}
</span>
<span
:
class=
"
[props.fullscreen ? '
text-
4
xl
' : 'text-xl sm:text-2xl', '
font-black text-gray-900 dark:text-white
']
"
>
{{ displayRealTimeQps.toFixed(1) }}
</span>
<span
class=
"text-xs
font-bold text-gray-500"
>
QPS
</span>
<span
:
class=
"
[props.fullscreen ? 'text-sm' : '
text-xs
', '
font-bold text-gray-500
']
"
>
QPS
</span>
</div>
</div>
<div
class=
"flex items-baseline gap-1.5"
>
<div
class=
"flex items-baseline gap-1.5"
>
<span
class=
"text-xl
font-black text-gray-900 dark:text-white
sm:text-2xl
"
>
{{ displayRealTimeTps.toFixed(1) }}
</span>
<span
:
class=
"
[props.fullscreen ? '
text-
4
xl
' : 'text-xl sm:text-2xl', '
font-black text-gray-900 dark:text-white
']
"
>
{{ displayRealTimeTps.toFixed(1) }}
</span>
<span
class=
"text-xs
font-bold text-gray-500"
>
TPS
</span>
<span
:
class=
"
[props.fullscreen ? 'text-sm' : '
text-xs
', '
font-bold text-gray-500
']
"
>
TPS
</span>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -1099,8 +1122,8 @@ function handleToolbarRefresh() {
...
@@ -1099,8 +1122,8 @@ function handleToolbarRefresh() {
<div
class=
"grid grid-cols-2 gap-3"
>
<div
class=
"grid grid-cols-2 gap-3"
>
<!-- Peak -->
<!-- Peak -->
<div>
<div>
<div
class=
"text-[10px]
font-bold uppercase text-gray-400"
>
{{ t('admin.ops.peak') }}
</div>
<div
:
class=
"
[props.fullscreen ? 'text-xs' : '
text-[10px]
', '
font-bold uppercase text-gray-400
']
"
>
{{ t('admin.ops.peak') }}
</div>
<div
class=
"mt-1 space-y-0.5
text-sm
font-medium text-gray-600 dark:text-gray-400"
>
<div
:
class=
"
[props.fullscreen ? 'text-base' : 'text-sm', '
mt-1 space-y-0.5 font-medium text-gray-600 dark:text-gray-400
']
"
>
<div
class=
"flex items-baseline gap-1.5"
>
<div
class=
"flex items-baseline gap-1.5"
>
<span
class=
"font-black text-gray-900 dark:text-white"
>
{{ realtimeQpsPeakLabel }}
</span>
<span
class=
"font-black text-gray-900 dark:text-white"
>
{{ realtimeQpsPeakLabel }}
</span>
<span
class=
"text-xs"
>
QPS
</span>
<span
class=
"text-xs"
>
QPS
</span>
...
@@ -1114,8 +1137,8 @@ function handleToolbarRefresh() {
...
@@ -1114,8 +1137,8 @@ function handleToolbarRefresh() {
<!-- Average -->
<!-- Average -->
<div>
<div>
<div
class=
"text-[10px]
font-bold uppercase text-gray-400"
>
{{ t('admin.ops.average') }}
</div>
<div
:
class=
"
[props.fullscreen ? 'text-xs' : '
text-[10px]
', '
font-bold uppercase text-gray-400
']
"
>
{{ t('admin.ops.average') }}
</div>
<div
class=
"mt-1 space-y-0.5
text-sm
font-medium text-gray-600 dark:text-gray-400"
>
<div
:
class=
"
[props.fullscreen ? 'text-base' : 'text-sm', '
mt-1 space-y-0.5 font-medium text-gray-600 dark:text-gray-400
']
"
>
<div
class=
"flex items-baseline gap-1.5"
>
<div
class=
"flex items-baseline gap-1.5"
>
<span
class=
"font-black text-gray-900 dark:text-white"
>
{{ realtimeQpsAvgLabel }}
</span>
<span
class=
"font-black text-gray-900 dark:text-white"
>
{{ realtimeQpsAvgLabel }}
</span>
<span
class=
"text-xs"
>
QPS
</span>
<span
class=
"text-xs"
>
QPS
</span>
...
@@ -1156,15 +1179,16 @@ function handleToolbarRefresh() {
...
@@ -1156,15 +1179,16 @@ function handleToolbarRefresh() {
</div>
</div>
<!-- Right: 6 cards (3 cols x 2 rows) -->
<!-- Right: 6 cards (3 cols x 2 rows) -->
<div
class=
"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:col-span-7 lg:grid-cols-3"
>
<div
class=
"grid
h-full
grid-cols-1
content-center
gap-4 sm:grid-cols-2 lg:col-span-7 lg:grid-cols-3"
>
<!-- Card 1: Requests -->
<!-- Card 1: Requests -->
<div
class=
"rounded-2xl bg-gray-50 p-4 dark:bg-dark-900"
>
<div
class=
"rounded-2xl bg-gray-50 p-4 dark:bg-dark-900"
>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"flex items-center gap-1"
>
<span
class=
"text-[10px] font-bold uppercase text-gray-400"
>
{{ t('admin.ops.requestsTitle') }}
</span>
<span
class=
"text-[10px] font-bold uppercase text-gray-400"
>
{{ t('admin.ops.requestsTitle') }}
</span>
<HelpTooltip
:content=
"t('admin.ops.tooltips.totalRequests')"
/>
<HelpTooltip
v-if=
"!props.fullscreen"
:content=
"t('admin.ops.tooltips.totalRequests')"
/>
</div>
</div>
<button
<button
v-if=
"!props.fullscreen"
class=
"text-[10px] font-bold text-blue-500 hover:underline"
class=
"text-[10px] font-bold text-blue-500 hover:underline"
type=
"button"
type=
"button"
@
click=
"openDetails({ title: t('admin.ops.requestDetails.title') })"
@
click=
"openDetails({ title: t('admin.ops.requestDetails.title') })"
...
@@ -1197,10 +1221,11 @@ function handleToolbarRefresh() {
...
@@ -1197,10 +1221,11 @@ function handleToolbarRefresh() {
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-2"
>
<div
class=
"flex items-center gap-2"
>
<span
class=
"text-[10px] font-bold uppercase text-gray-400"
>
SLA
</span>
<span
class=
"text-[10px] font-bold uppercase text-gray-400"
>
SLA
</span>
<HelpTooltip
:content=
"t('admin.ops.tooltips.sla')"
/>
<HelpTooltip
v-if=
"!props.fullscreen"
:content=
"t('admin.ops.tooltips.sla')"
/>
<span
class=
"h-1.5 w-1.5 rounded-full"
:class=
"isSLABelowThreshold(slaPercent) ? 'bg-red-500' : (slaPercent ?? 0) >= 99.5 ? 'bg-green-500' : 'bg-yellow-500'"
></span>
<span
class=
"h-1.5 w-1.5 rounded-full"
:class=
"isSLABelowThreshold(slaPercent) ? 'bg-red-500' : (slaPercent ?? 0) >= 99.5 ? 'bg-green-500' : 'bg-yellow-500'"
></span>
</div>
</div>
<button
<button
v-if=
"!props.fullscreen"
class=
"text-[10px] font-bold text-blue-500 hover:underline"
class=
"text-[10px] font-bold text-blue-500 hover:underline"
type=
"button"
type=
"button"
@
click=
"openDetails({ title: t('admin.ops.requestDetails.title'), kind: 'error' })"
@
click=
"openDetails({ title: t('admin.ops.requestDetails.title'), kind: 'error' })"
...
@@ -1227,9 +1252,10 @@ function handleToolbarRefresh() {
...
@@ -1227,9 +1252,10 @@ function handleToolbarRefresh() {
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"flex items-center gap-1"
>
<span
class=
"text-[10px] font-bold uppercase text-gray-400"
>
{{ t('admin.ops.latencyDuration') }}
</span>
<span
class=
"text-[10px] font-bold uppercase text-gray-400"
>
{{ t('admin.ops.latencyDuration') }}
</span>
<HelpTooltip
:content=
"t('admin.ops.tooltips.latency')"
/>
<HelpTooltip
v-if=
"!props.fullscreen"
:content=
"t('admin.ops.tooltips.latency')"
/>
</div>
</div>
<button
<button
v-if=
"!props.fullscreen"
class=
"text-[10px] font-bold text-blue-500 hover:underline"
class=
"text-[10px] font-bold text-blue-500 hover:underline"
type=
"button"
type=
"button"
@
click=
"openDetails({ title: t('admin.ops.latencyDuration'), sort: 'duration_desc', min_duration_ms: Math.max(Number(durationP99Ms ?? 0), 0) })"
@
click=
"openDetails({ title: t('admin.ops.latencyDuration'), sort: 'duration_desc', min_duration_ms: Math.max(Number(durationP99Ms ?? 0), 0) })"
...
@@ -1277,9 +1303,10 @@ function handleToolbarRefresh() {
...
@@ -1277,9 +1303,10 @@ function handleToolbarRefresh() {
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"flex items-center gap-1"
>
<span
class=
"text-[10px] font-bold uppercase text-gray-400"
>
TTFT
</span>
<span
class=
"text-[10px] font-bold uppercase text-gray-400"
>
TTFT
</span>
<HelpTooltip
:content=
"t('admin.ops.tooltips.ttft')"
/>
<HelpTooltip
v-if=
"!props.fullscreen"
:content=
"t('admin.ops.tooltips.ttft')"
/>
</div>
</div>
<button
<button
v-if=
"!props.fullscreen"
class=
"text-[10px] font-bold text-blue-500 hover:underline"
class=
"text-[10px] font-bold text-blue-500 hover:underline"
type=
"button"
type=
"button"
@
click=
"openDetails({ title: 'TTFT', sort: 'duration_desc' })"
@
click=
"openDetails({ title: 'TTFT', sort: 'duration_desc' })"
...
@@ -1327,9 +1354,9 @@ function handleToolbarRefresh() {
...
@@ -1327,9 +1354,9 @@ function handleToolbarRefresh() {
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"flex items-center gap-1"
>
<span
class=
"text-[10px] font-bold uppercase text-gray-400"
>
{{ t('admin.ops.requestErrors') }}
</span>
<span
class=
"text-[10px] font-bold uppercase text-gray-400"
>
{{ t('admin.ops.requestErrors') }}
</span>
<HelpTooltip
:content=
"t('admin.ops.tooltips.errors')"
/>
<HelpTooltip
v-if=
"!props.fullscreen"
:content=
"t('admin.ops.tooltips.errors')"
/>
</div>
</div>
<button
class=
"text-[10px] font-bold text-blue-500 hover:underline"
type=
"button"
@
click=
"openErrorDetails('request')"
>
<button
v-if=
"!props.fullscreen"
class=
"text-[10px] font-bold text-blue-500 hover:underline"
type=
"button"
@
click=
"openErrorDetails('request')"
>
{{ t('admin.ops.requestDetails.details') }}
{{ t('admin.ops.requestDetails.details') }}
</button>
</button>
</div>
</div>
...
@@ -1353,9 +1380,9 @@ function handleToolbarRefresh() {
...
@@ -1353,9 +1380,9 @@ function handleToolbarRefresh() {
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center justify-between"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"flex items-center gap-1"
>
<span
class=
"text-[10px] font-bold uppercase text-gray-400"
>
{{ t('admin.ops.upstreamErrors') }}
</span>
<span
class=
"text-[10px] font-bold uppercase text-gray-400"
>
{{ t('admin.ops.upstreamErrors') }}
</span>
<HelpTooltip
:content=
"t('admin.ops.tooltips.upstreamErrors')"
/>
<HelpTooltip
v-if=
"!props.fullscreen"
:content=
"t('admin.ops.tooltips.upstreamErrors')"
/>
</div>
</div>
<button
class=
"text-[10px] font-bold text-blue-500 hover:underline"
type=
"button"
@
click=
"openErrorDetails('upstream')"
>
<button
v-if=
"!props.fullscreen"
class=
"text-[10px] font-bold text-blue-500 hover:underline"
type=
"button"
@
click=
"openErrorDetails('upstream')"
>
{{ t('admin.ops.requestDetails.details') }}
{{ t('admin.ops.requestDetails.details') }}
</button>
</button>
</div>
</div>
...
@@ -1383,12 +1410,12 @@ function handleToolbarRefresh() {
...
@@ -1383,12 +1410,12 @@ function handleToolbarRefresh() {
<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"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"text-[10px] font-bold uppercase tracking-wider text-gray-400"
>
CPU
</div>
<div
class=
"text-[10px] font-bold uppercase tracking-wider text-gray-400"
>
CPU
</div>
<HelpTooltip
:content=
"t('admin.ops.tooltips.cpu')"
/>
<HelpTooltip
v-if=
"!props.fullscreen"
:content=
"t('admin.ops.tooltips.cpu')"
/>
</div>
</div>
<div
class=
"mt-1 text-lg font-black"
:class=
"cpuPercentClass"
>
<div
class=
"mt-1 text-lg font-black"
:class=
"cpuPercentClass"
>
{{ cpuPercentValue == null ? '-' : `${cpuPercentValue.toFixed(1)}%` }}
{{ cpuPercentValue == null ? '-' : `${cpuPercentValue.toFixed(1)}%` }}
</div>
</div>
<div
class=
"mt-1 text-[10px] text-gray-500 dark:text-gray-400"
>
<div
v-if=
"!props.fullscreen"
class=
"mt-1 text-[10px] text-gray-500 dark:text-gray-400"
>
{{ t('common.warning') }} 80% · {{ t('common.critical') }} 95%
{{ t('common.warning') }} 80% · {{ t('common.critical') }} 95%
</div>
</div>
</div>
</div>
...
@@ -1397,12 +1424,12 @@ function handleToolbarRefresh() {
...
@@ -1397,12 +1424,12 @@ function handleToolbarRefresh() {
<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"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"text-[10px] font-bold uppercase tracking-wider text-gray-400"
>
MEM
</div>
<div
class=
"text-[10px] font-bold uppercase tracking-wider text-gray-400"
>
MEM
</div>
<HelpTooltip
:content=
"t('admin.ops.tooltips.memory')"
/>
<HelpTooltip
v-if=
"!props.fullscreen"
:content=
"t('admin.ops.tooltips.memory')"
/>
</div>
</div>
<div
class=
"mt-1 text-lg font-black"
:class=
"memPercentClass"
>
<div
class=
"mt-1 text-lg font-black"
:class=
"memPercentClass"
>
{{ memPercentValue == null ? '-' : `${memPercentValue.toFixed(1)}%` }}
{{ memPercentValue == null ? '-' : `${memPercentValue.toFixed(1)}%` }}
</div>
</div>
<div
class=
"mt-1 text-[10px] text-gray-500 dark:text-gray-400"
>
<div
v-if=
"!props.fullscreen"
class=
"mt-1 text-[10px] text-gray-500 dark:text-gray-400"
>
{{
{{
systemMetrics?.memory_used_mb == null || systemMetrics?.memory_total_mb == null
systemMetrics?.memory_used_mb == null || systemMetrics?.memory_total_mb == null
? '-'
? '-'
...
@@ -1415,12 +1442,12 @@ function handleToolbarRefresh() {
...
@@ -1415,12 +1442,12 @@ function handleToolbarRefresh() {
<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"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"text-[10px] font-bold uppercase tracking-wider text-gray-400"
>
DB
</div>
<div
class=
"text-[10px] font-bold uppercase tracking-wider text-gray-400"
>
DB
</div>
<HelpTooltip
:content=
"t('admin.ops.tooltips.db')"
/>
<HelpTooltip
v-if=
"!props.fullscreen"
:content=
"t('admin.ops.tooltips.db')"
/>
</div>
</div>
<div
class=
"mt-1 text-lg font-black"
:class=
"dbMiddleClass"
>
<div
class=
"mt-1 text-lg font-black"
:class=
"dbMiddleClass"
>
{{ dbMiddleLabel }}
{{ dbMiddleLabel }}
</div>
</div>
<div
class=
"mt-1 text-[10px] text-gray-500 dark:text-gray-400"
>
<div
v-if=
"!props.fullscreen"
class=
"mt-1 text-[10px] text-gray-500 dark:text-gray-400"
>
{{ t('admin.ops.conns') }} {{ dbConnOpenValue ?? '-' }} / {{ dbMaxOpenConnsValue ?? '-' }}
{{ t('admin.ops.conns') }} {{ dbConnOpenValue ?? '-' }} / {{ dbMaxOpenConnsValue ?? '-' }}
· {{ t('admin.ops.active') }} {{ dbConnActiveValue ?? '-' }}
· {{ t('admin.ops.active') }} {{ dbConnActiveValue ?? '-' }}
· {{ t('admin.ops.idle') }} {{ dbConnIdleValue ?? '-' }}
· {{ t('admin.ops.idle') }} {{ dbConnIdleValue ?? '-' }}
...
@@ -1432,12 +1459,12 @@ function handleToolbarRefresh() {
...
@@ -1432,12 +1459,12 @@ function handleToolbarRefresh() {
<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"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"text-[10px] font-bold uppercase tracking-wider text-gray-400"
>
Redis
</div>
<div
class=
"text-[10px] font-bold uppercase tracking-wider text-gray-400"
>
Redis
</div>
<HelpTooltip
:content=
"t('admin.ops.tooltips.redis')"
/>
<HelpTooltip
v-if=
"!props.fullscreen"
:content=
"t('admin.ops.tooltips.redis')"
/>
</div>
</div>
<div
class=
"mt-1 text-lg font-black"
:class=
"redisMiddleClass"
>
<div
class=
"mt-1 text-lg font-black"
:class=
"redisMiddleClass"
>
{{ redisMiddleLabel }}
{{ redisMiddleLabel }}
</div>
</div>
<div
class=
"mt-1 text-[10px] text-gray-500 dark:text-gray-400"
>
<div
v-if=
"!props.fullscreen"
class=
"mt-1 text-[10px] text-gray-500 dark:text-gray-400"
>
{{ t('admin.ops.conns') }} {{ redisConnTotalValue ?? '-' }} / {{ redisPoolSizeValue ?? '-' }}
{{ t('admin.ops.conns') }} {{ redisConnTotalValue ?? '-' }} / {{ redisPoolSizeValue ?? '-' }}
<span
v-if=
"redisConnActiveValue != null"
>
· {{ t('admin.ops.active') }} {{ redisConnActiveValue }}
</span>
<span
v-if=
"redisConnActiveValue != null"
>
· {{ t('admin.ops.active') }} {{ redisConnActiveValue }}
</span>
<span
v-if=
"redisConnIdleValue != null"
>
· {{ t('admin.ops.idle') }} {{ redisConnIdleValue }}
</span>
<span
v-if=
"redisConnIdleValue != null"
>
· {{ t('admin.ops.idle') }} {{ redisConnIdleValue }}
</span>
...
@@ -1448,12 +1475,12 @@ function handleToolbarRefresh() {
...
@@ -1448,12 +1475,12 @@ function handleToolbarRefresh() {
<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"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"text-[10px] font-bold uppercase tracking-wider text-gray-400"
>
{{ t('admin.ops.goroutines') }}
</div>
<div
class=
"text-[10px] font-bold uppercase tracking-wider text-gray-400"
>
{{ t('admin.ops.goroutines') }}
</div>
<HelpTooltip
:content=
"t('admin.ops.tooltips.goroutines')"
/>
<HelpTooltip
v-if=
"!props.fullscreen"
:content=
"t('admin.ops.tooltips.goroutines')"
/>
</div>
</div>
<div
class=
"mt-1 text-lg font-black"
:class=
"goroutineStatusClass"
>
<div
class=
"mt-1 text-lg font-black"
:class=
"goroutineStatusClass"
>
{{ goroutineStatusLabel }}
{{ goroutineStatusLabel }}
</div>
</div>
<div
class=
"mt-1 text-[10px] text-gray-500 dark:text-gray-400"
>
<div
v-if=
"!props.fullscreen"
class=
"mt-1 text-[10px] text-gray-500 dark:text-gray-400"
>
{{ t('admin.ops.current') }}
<span
class=
"font-mono"
>
{{ goroutineCountValue ?? '-' }}
</span>
{{ t('admin.ops.current') }}
<span
class=
"font-mono"
>
{{ goroutineCountValue ?? '-' }}
</span>
· {{ t('common.warning') }}
<span
class=
"font-mono"
>
{{ goroutinesWarnThreshold }}
</span>
· {{ t('common.warning') }}
<span
class=
"font-mono"
>
{{ goroutinesWarnThreshold }}
</span>
· {{ t('common.critical') }}
<span
class=
"font-mono"
>
{{ goroutinesCriticalThreshold }}
</span>
· {{ t('common.critical') }}
<span
class=
"font-mono"
>
{{ goroutinesCriticalThreshold }}
</span>
...
@@ -1468,9 +1495,9 @@ function handleToolbarRefresh() {
...
@@ -1468,9 +1495,9 @@ function handleToolbarRefresh() {
<div
class=
"flex items-center justify-between gap-2"
>
<div
class=
"flex items-center justify-between gap-2"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"flex items-center gap-1"
>
<div
class=
"text-[10px] font-bold uppercase tracking-wider text-gray-400"
>
{{ t('admin.ops.jobs') }}
</div>
<div
class=
"text-[10px] font-bold uppercase tracking-wider text-gray-400"
>
{{ t('admin.ops.jobs') }}
</div>
<HelpTooltip
:content=
"t('admin.ops.tooltips.jobs')"
/>
<HelpTooltip
v-if=
"!props.fullscreen"
:content=
"t('admin.ops.tooltips.jobs')"
/>
</div>
</div>
<button
class=
"text-[10px] font-bold text-blue-500 hover:underline"
type=
"button"
@
click=
"openJobsDetails"
>
<button
v-if=
"!props.fullscreen"
class=
"text-[10px] font-bold text-blue-500 hover:underline"
type=
"button"
@
click=
"openJobsDetails"
>
{{ t('admin.ops.requestDetails.details') }}
{{ t('admin.ops.requestDetails.details') }}
</button>
</button>
</div>
</div>
...
@@ -1479,7 +1506,7 @@ function handleToolbarRefresh() {
...
@@ -1479,7 +1506,7 @@ function handleToolbarRefresh() {
{{ jobsStatusLabel }}
{{ jobsStatusLabel }}
</div>
</div>
<div
class=
"mt-1 text-[10px] text-gray-500 dark:text-gray-400"
>
<div
v-if=
"!props.fullscreen"
class=
"mt-1 text-[10px] text-gray-500 dark:text-gray-400"
>
{{ t('common.total') }}
<span
class=
"font-mono"
>
{{ jobHeartbeats.length }}
</span>
{{ t('common.total') }}
<span
class=
"font-mono"
>
{{ jobHeartbeats.length }}
</span>
· {{ t('common.warning') }}
<span
class=
"font-mono"
>
{{ jobsWarnCount }}
</span>
· {{ t('common.warning') }}
<span
class=
"font-mono"
>
{{ jobsWarnCount }}
</span>
</div>
</div>
...
...
frontend/src/views/admin/ops/components/OpsThroughputTrendChart.vue
View file @
bb1fd54d
...
@@ -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,38 +180,40 @@ function downloadChart() {
...
@@ -179,38 +180,40 @@ 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>
<button
<template
v-if=
"!props.fullscreen"
>
type=
"button"
<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"
type=
"button"
:disabled=
"state !== 'ready'"
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"
:title=
"t('admin.ops.requestDetails.title')"
:disabled=
"state !== 'ready'"
@
click=
"emit('openDetails')"
:title=
"t('admin.ops.requestDetails.title')"
>
@
click=
"emit('openDetails')"
{{
t
(
'
admin.ops.requestDetails.details
'
)
}}
>
</button>
{{
t
(
'
admin.ops.requestDetails.details
'
)
}}
<button
</button>
type=
"button"
<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"
type=
"button"
:disabled=
"state !== 'ready'"
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"
:title=
"t('admin.ops.charts.resetZoomHint')"
:disabled=
"state !== 'ready'"
@
click=
"resetZoom"
:title=
"t('admin.ops.charts.resetZoomHint')"
>
@
click=
"resetZoom"
{{
t
(
'
admin.ops.charts.resetZoom
'
)
}}
>
</button>
{{
t
(
'
admin.ops.charts.resetZoom
'
)
}}
<button
</button>
type=
"button"
<button
class=
"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"
type=
"button"
:disabled=
"state !== 'ready'"
class=
"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"
:title=
"t('admin.ops.charts.downloadChartHint')"
:disabled=
"state !== 'ready'"
@
click=
"downloadChart"
:title=
"t('admin.ops.charts.downloadChartHint')"
>
@
click=
"downloadChart"
{{
t
(
'
admin.ops.charts.downloadChart
'
)
}}
>
</button>
{{
t
(
'
admin.ops.charts.downloadChart
'
)
}}
</button>
</
template
>
</div>
</div>
</div>
</div>
...
...
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