Merge pull request #257 from Edric-Li/feat/ops-fullscreen-scrollbar
feat(ops): 添加运维监控全屏模式 & 优化滚动条
This commit is contained in:
@@ -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',
|
||||||
|
|||||||
@@ -2088,6 +2088,9 @@ export default {
|
|||||||
'6h': '近6小时',
|
'6h': '近6小时',
|
||||||
'24h': '近24小时'
|
'24h': '近24小时'
|
||||||
},
|
},
|
||||||
|
fullscreen: {
|
||||||
|
enter: '进入全屏'
|
||||||
|
},
|
||||||
diagnosis: {
|
diagnosis: {
|
||||||
title: '智能诊断',
|
title: '智能诊断',
|
||||||
footer: '基于当前指标的自动诊断建议',
|
footer: '基于当前指标的自动诊断建议',
|
||||||
|
|||||||
@@ -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 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
::-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;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
*:hover::-webkit-scrollbar-thumb {
|
||||||
|
@apply bg-gray-300/50 dark:bg-dark-600/50;
|
||||||
|
}
|
||||||
|
|
||||||
|
*:hover::-webkit-scrollbar-thumb:hover {
|
||||||
@apply bg-gray-400 dark:bg-dark-500;
|
@apply bg-gray-400 dark:bg-dark-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<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 @@
|
|||||||
: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 @@
|
|||||||
@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 @@
|
|||||||
: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 @@
|
|||||||
<!-- 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>
|
||||||
</AppLayout>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -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(
|
|||||||
)
|
)
|
||||||
|
|
||||||
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() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('keydown', handleKeydown)
|
||||||
abortDashboardFetch()
|
abortDashboardFetch()
|
||||||
pauseAutoRefresh()
|
pauseAutoRefresh()
|
||||||
pauseCountdown()
|
pauseCountdown()
|
||||||
|
|||||||
@@ -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 {
|
|||||||
(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(() => {
|
|||||||
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() {
|
|||||||
</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() {
|
|||||||
{{ 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() {
|
|||||||
</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
|
<Select
|
||||||
:model-value="groupId"
|
:model-value="groupId"
|
||||||
:options="groupOptions"
|
:options="groupOptions"
|
||||||
class="w-full sm:w-[160px]"
|
class="w-full sm:w-[160px]"
|
||||||
@update:model-value="handleGroupChange"
|
@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
|
<Select
|
||||||
:model-value="timeRange"
|
:model-value="timeRange"
|
||||||
:options="timeRangeOptions"
|
:options="timeRangeOptions"
|
||||||
class="relative w-full sm:w-[150px]"
|
class="relative w-full sm:w-[150px]"
|
||||||
@update:model-value="handleTimeRangeChange"
|
@update:model-value="handleTimeRangeChange"
|
||||||
/>
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
v-if="false"
|
v-if="false"
|
||||||
@@ -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() {
|
|||||||
</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() {
|
|||||||
<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() {
|
|||||||
</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() {
|
|||||||
</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() {
|
|||||||
</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() {
|
|||||||
<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() {
|
|||||||
</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-4xl' : '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-4xl' : '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() {
|
|||||||
<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() {
|
|||||||
|
|
||||||
<!-- 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() {
|
|||||||
</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() {
|
|||||||
<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() {
|
|||||||
<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() {
|
|||||||
<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() {
|
|||||||
<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() {
|
|||||||
<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() {
|
|||||||
<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() {
|
|||||||
<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() {
|
|||||||
<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() {
|
|||||||
<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() {
|
|||||||
<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() {
|
|||||||
<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() {
|
|||||||
{{ 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>
|
||||||
|
|||||||
@@ -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() {
|
|||||||
<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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user