refactor(ops): 优化阈值检查系统和布局

阈值检查系统优化:
- 引入三级阈值系统(normal/warning/critical)
- 统一阈值判断逻辑,支持警告和严重两个级别
- 移除硬编码的 TTFT 颜色判断,改用阈值配置
- 新增 getThresholdColorClass 统一颜色映射

布局优化:
- 优化详细指标在卡片内的响应式布局
- 改进宽屏下的卡片布局显示
- 优化指标数值的对齐和间距
This commit is contained in:
IanShaw027
2026-01-15 19:50:31 +08:00
parent 93b5b7474b
commit 38961ba10e
2 changed files with 68 additions and 56 deletions

View File

@@ -693,8 +693,8 @@ onMounted(async () => {
async function loadThresholds() {
try {
const settings = await opsAPI.getAlertRuntimeSettings()
metricThresholds.value = settings.thresholds || null
const thresholds = await opsAPI.getMetricThresholds()
metricThresholds.value = thresholds || null
} catch (err) {
console.warn('[OpsDashboard] Failed to load thresholds', err)
metricThresholds.value = null

View File

@@ -169,42 +169,54 @@ const updatedAtLabel = computed(() => {
return props.lastUpdated.toLocaleTimeString()
})
// --- Color coding for TTFT ---
function getTTFTColor(ms: number | null | undefined): string {
if (ms == null) return 'text-gray-900 dark:text-white'
if (ms < 500) return 'text-green-600 dark:text-green-400'
if (ms < 1000) return 'text-yellow-600 dark:text-yellow-400'
if (ms < 2000) return 'text-orange-600 dark:text-orange-400'
return 'text-red-600 dark:text-red-400'
}
// --- Threshold checking helpers ---
function isSLABelowThreshold(slaPercent: number | null): boolean {
if (slaPercent == null) return false
type ThresholdLevel = 'normal' | 'warning' | 'critical'
function getSLAThresholdLevel(slaPercent: number | null): ThresholdLevel {
if (slaPercent == null) return 'normal'
const threshold = props.thresholds?.sla_percent_min
if (threshold == null) return false
return slaPercent < threshold
if (threshold == null) return 'normal'
if (slaPercent < threshold) return 'critical'
if (slaPercent < threshold / 0.8) return 'warning'
return 'normal'
}
function isTTFTAboveThreshold(ttftP99Ms: number | null): boolean {
if (ttftP99Ms == null) return false
function getTTFTThresholdLevel(ttftMs: number | null): ThresholdLevel {
if (ttftMs == null) return 'normal'
const threshold = props.thresholds?.ttft_p99_ms_max
if (threshold == null) return false
return ttftP99Ms > threshold
if (threshold == null) return 'normal'
if (ttftMs >= threshold) return 'critical'
if (ttftMs >= threshold * 0.8) return 'warning'
return 'normal'
}
function isRequestErrorRateAboveThreshold(errorRatePercent: number | null): boolean {
if (errorRatePercent == null) return false
function getRequestErrorRateThresholdLevel(errorRatePercent: number | null): ThresholdLevel {
if (errorRatePercent == null) return 'normal'
const threshold = props.thresholds?.request_error_rate_percent_max
if (threshold == null) return false
return errorRatePercent > threshold
if (threshold == null) return 'normal'
if (errorRatePercent >= threshold) return 'critical'
if (errorRatePercent >= threshold * 0.8) return 'warning'
return 'normal'
}
function isUpstreamErrorRateAboveThreshold(upstreamErrorRatePercent: number | null): boolean {
if (upstreamErrorRatePercent == null) return false
function getUpstreamErrorRateThresholdLevel(upstreamErrorRatePercent: number | null): ThresholdLevel {
if (upstreamErrorRatePercent == null) return 'normal'
const threshold = props.thresholds?.upstream_error_rate_percent_max
if (threshold == null) return false
return upstreamErrorRatePercent > threshold
if (threshold == null) return 'normal'
if (upstreamErrorRatePercent >= threshold) return 'critical'
if (upstreamErrorRatePercent >= threshold * 0.8) return 'warning'
return 'normal'
}
function getThresholdColorClass(level: ThresholdLevel): string {
switch (level) {
case 'critical':
return 'text-red-600 dark:text-red-400'
case 'warning':
return 'text-yellow-600 dark:text-yellow-400'
default:
return 'text-green-600 dark:text-green-400'
}
}
// --- Realtime / Overview labels ---
@@ -1197,7 +1209,7 @@ function handleToolbarRefresh() {
<div class="flex items-center gap-2">
<span class="text-[10px] font-bold uppercase text-gray-400">{{ t('admin.ops.sla') }}</span>
<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="getSLAThresholdLevel(slaPercent) === 'critical' ? 'bg-red-500' : getSLAThresholdLevel(slaPercent) === 'warning' ? 'bg-yellow-500' : 'bg-green-500'"></span>
</div>
<button
v-if="!props.fullscreen"
@@ -1208,11 +1220,11 @@ function handleToolbarRefresh() {
{{ t('admin.ops.requestDetails.details') }}
</button>
</div>
<div class="mt-2 text-3xl font-black" :class="isSLABelowThreshold(slaPercent) ? 'text-red-600 dark:text-red-400' : 'text-gray-900 dark:text-white'">
<div class="mt-2 text-3xl font-black" :class="getThresholdColorClass(getSLAThresholdLevel(slaPercent))">
{{ slaPercent == null ? '-' : `${slaPercent.toFixed(3)}%` }}
</div>
<div class="mt-3 h-2 w-full overflow-hidden rounded-full bg-gray-200 dark:bg-dark-700">
<div class="h-full transition-all" :class="isSLABelowThreshold(slaPercent) ? 'bg-red-500' : 'bg-green-500'" :style="{ width: `${Math.max((slaPercent ?? 0) - 90, 0) * 10}%` }"></div>
<div class="h-full transition-all" :class="getSLAThresholdLevel(slaPercent) === 'critical' ? 'bg-red-500' : getSLAThresholdLevel(slaPercent) === 'warning' ? 'bg-yellow-500' : 'bg-green-500'" :style="{ width: `${Math.max((slaPercent ?? 0) - 90, 0) * 10}%` }"></div>
</div>
<div class="mt-3 text-xs">
<div class="flex justify-between">
@@ -1244,28 +1256,28 @@ function handleToolbarRefresh() {
</div>
<span class="text-xs font-bold text-gray-400">ms (P99)</span>
</div>
<div class="mt-3 flex flex-wrap gap-x-3 gap-y-1 text-xs">
<div class="flex min-w-[60px] items-baseline gap-1 whitespace-nowrap">
<span class="text-gray-500">{{ t('admin.ops.p95') }}</span>
<div class="mt-3 grid grid-cols-1 gap-x-3 gap-y-1 text-xs 2xl:grid-cols-2">
<div class="flex items-baseline gap-1 whitespace-nowrap">
<span class="text-gray-500">P95:</span>
<span class="font-bold text-gray-900 dark:text-white">{{ durationP95Ms ?? '-' }}</span>
<span class="text-gray-400">ms</span>
</div>
<div class="flex min-w-[60px] items-baseline gap-1 whitespace-nowrap">
<span class="text-gray-500">{{ t('admin.ops.p90') }}</span>
<div class="flex items-baseline gap-1 whitespace-nowrap">
<span class="text-gray-500">P90:</span>
<span class="font-bold text-gray-900 dark:text-white">{{ durationP90Ms ?? '-' }}</span>
<span class="text-gray-400">ms</span>
</div>
<div class="flex min-w-[60px] items-baseline gap-1 whitespace-nowrap">
<span class="text-gray-500">{{ t('admin.ops.p50') }}</span>
<div class="flex items-baseline gap-1 whitespace-nowrap">
<span class="text-gray-500">P50:</span>
<span class="font-bold text-gray-900 dark:text-white">{{ durationP50Ms ?? '-' }}</span>
<span class="text-gray-400">ms</span>
</div>
<div class="flex min-w-[60px] items-baseline gap-1 whitespace-nowrap">
<div class="flex items-baseline gap-1 whitespace-nowrap">
<span class="text-gray-500">Avg:</span>
<span class="font-bold text-gray-900 dark:text-white">{{ durationAvgMs ?? '-' }}</span>
<span class="text-gray-400">ms</span>
</div>
<div class="flex min-w-[60px] items-baseline gap-1 whitespace-nowrap">
<div class="flex items-baseline gap-1 whitespace-nowrap">
<span class="text-gray-500">Max:</span>
<span class="font-bold text-gray-900 dark:text-white">{{ durationMaxMs ?? '-' }}</span>
<span class="text-gray-400">ms</span>
@@ -1290,35 +1302,35 @@ function handleToolbarRefresh() {
</button>
</div>
<div class="mt-2 flex items-baseline gap-2">
<div class="text-3xl font-black" :class="isTTFTAboveThreshold(ttftP99Ms) ? 'text-red-600 dark:text-red-400' : getTTFTColor(ttftP99Ms)">
<div class="text-3xl font-black" :class="getThresholdColorClass(getTTFTThresholdLevel(ttftP99Ms))">
{{ ttftP99Ms ?? '-' }}
</div>
<span class="text-xs font-bold text-gray-400">ms (P99)</span>
</div>
<div class="mt-3 flex flex-wrap gap-x-3 gap-y-1 text-xs">
<div class="flex min-w-[60px] items-baseline gap-1 whitespace-nowrap">
<span class="text-gray-500">{{ t('admin.ops.p95') }}</span>
<span class="font-bold" :class="getTTFTColor(ttftP95Ms)">{{ ttftP95Ms ?? '-' }}</span>
<div class="mt-3 grid grid-cols-1 gap-x-3 gap-y-1 text-xs 2xl:grid-cols-2">
<div class="flex items-baseline gap-1 whitespace-nowrap">
<span class="text-gray-500">P95:</span>
<span class="font-bold" :class="getThresholdColorClass(getTTFTThresholdLevel(ttftP95Ms))">{{ ttftP95Ms ?? '-' }}</span>
<span class="text-gray-400">ms</span>
</div>
<div class="flex min-w-[60px] items-baseline gap-1 whitespace-nowrap">
<span class="text-gray-500">{{ t('admin.ops.p90') }}</span>
<span class="font-bold" :class="getTTFTColor(ttftP90Ms)">{{ ttftP90Ms ?? '-' }}</span>
<div class="flex items-baseline gap-1 whitespace-nowrap">
<span class="text-gray-500">P90:</span>
<span class="font-bold" :class="getThresholdColorClass(getTTFTThresholdLevel(ttftP90Ms))">{{ ttftP90Ms ?? '-' }}</span>
<span class="text-gray-400">ms</span>
</div>
<div class="flex min-w-[60px] items-baseline gap-1 whitespace-nowrap">
<span class="text-gray-500">{{ t('admin.ops.p50') }}</span>
<span class="font-bold" :class="getTTFTColor(ttftP50Ms)">{{ ttftP50Ms ?? '-' }}</span>
<div class="flex items-baseline gap-1 whitespace-nowrap">
<span class="text-gray-500">P50:</span>
<span class="font-bold" :class="getThresholdColorClass(getTTFTThresholdLevel(ttftP50Ms))">{{ ttftP50Ms ?? '-' }}</span>
<span class="text-gray-400">ms</span>
</div>
<div class="flex min-w-[60px] items-baseline gap-1 whitespace-nowrap">
<div class="flex items-baseline gap-1 whitespace-nowrap">
<span class="text-gray-500">Avg:</span>
<span class="font-bold" :class="getTTFTColor(ttftAvgMs)">{{ ttftAvgMs ?? '-' }}</span>
<span class="font-bold" :class="getThresholdColorClass(getTTFTThresholdLevel(ttftAvgMs))">{{ ttftAvgMs ?? '-' }}</span>
<span class="text-gray-400">ms</span>
</div>
<div class="flex min-w-[60px] items-baseline gap-1 whitespace-nowrap">
<div class="flex items-baseline gap-1 whitespace-nowrap">
<span class="text-gray-500">Max:</span>
<span class="font-bold" :class="getTTFTColor(ttftMaxMs)">{{ ttftMaxMs ?? '-' }}</span>
<span class="font-bold" :class="getThresholdColorClass(getTTFTThresholdLevel(ttftMaxMs))">{{ ttftMaxMs ?? '-' }}</span>
<span class="text-gray-400">ms</span>
</div>
</div>
@@ -1335,7 +1347,7 @@ function handleToolbarRefresh() {
{{ t('admin.ops.requestDetails.details') }}
</button>
</div>
<div class="mt-2 text-3xl font-black" :class="isRequestErrorRateAboveThreshold(errorRatePercent) ? 'text-red-600 dark:text-red-400' : (errorRatePercent ?? 0) > 5 ? 'text-red-500' : 'text-gray-900 dark:text-white'">
<div class="mt-2 text-3xl font-black" :class="getThresholdColorClass(getRequestErrorRateThresholdLevel(errorRatePercent))">
{{ errorRatePercent == null ? '-' : `${errorRatePercent.toFixed(2)}%` }}
</div>
<div class="mt-3 space-y-1 text-xs">
@@ -1361,7 +1373,7 @@ function handleToolbarRefresh() {
{{ t('admin.ops.requestDetails.details') }}
</button>
</div>
<div class="mt-2 text-3xl font-black" :class="isUpstreamErrorRateAboveThreshold(upstreamErrorRatePercent) ? 'text-red-600 dark:text-red-400' : (upstreamErrorRatePercent ?? 0) > 5 ? 'text-red-500' : 'text-gray-900 dark:text-white'">
<div class="mt-2 text-3xl font-black" :class="getThresholdColorClass(getUpstreamErrorRateThresholdLevel(upstreamErrorRatePercent))">
{{ upstreamErrorRatePercent == null ? '-' : `${upstreamErrorRatePercent.toFixed(2)}%` }}
</div>
<div class="mt-3 space-y-1 text-xs">