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
Expand all
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
This diff is collapsed.
Click to expand it.
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