refactor(ops): 优化阈值检查系统和布局
阈值检查系统优化: - 引入三级阈值系统(normal/warning/critical) - 统一阈值判断逻辑,支持警告和严重两个级别 - 移除硬编码的 TTFT 颜色判断,改用阈值配置 - 新增 getThresholdColorClass 统一颜色映射 布局优化: - 优化详细指标在卡片内的响应式布局 - 改进宽屏下的卡片布局显示 - 优化指标数值的对齐和间距
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user