diff --git a/frontend/src/views/admin/ops/OpsDashboard.vue b/frontend/src/views/admin/ops/OpsDashboard.vue index ff2a434d..d33f0f64 100644 --- a/frontend/src/views/admin/ops/OpsDashboard.vue +++ b/frontend/src/views/admin/ops/OpsDashboard.vue @@ -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 diff --git a/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue b/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue index 2d52b6e8..8e868bba 100644 --- a/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue +++ b/frontend/src/views/admin/ops/components/OpsDashboardHeader.vue @@ -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() {
{{ t('admin.ops.sla') }} - +
-
+
{{ ttftP99Ms ?? '-' }}
ms (P99)
-
-
- {{ t('admin.ops.p95') }} - {{ ttftP95Ms ?? '-' }} +
+
+ P95: + {{ ttftP95Ms ?? '-' }} ms
-
- {{ t('admin.ops.p90') }} - {{ ttftP90Ms ?? '-' }} +
+ P90: + {{ ttftP90Ms ?? '-' }} ms
-
- {{ t('admin.ops.p50') }} - {{ ttftP50Ms ?? '-' }} +
+ P50: + {{ ttftP50Ms ?? '-' }} ms
-
+
Avg: - {{ ttftAvgMs ?? '-' }} + {{ ttftAvgMs ?? '-' }} ms
-
+
Max: - {{ ttftMaxMs ?? '-' }} + {{ ttftMaxMs ?? '-' }} ms
@@ -1335,7 +1347,7 @@ function handleToolbarRefresh() { {{ t('admin.ops.requestDetails.details') }}
-
+
{{ errorRatePercent == null ? '-' : `${errorRatePercent.toFixed(2)}%` }}
@@ -1361,7 +1373,7 @@ function handleToolbarRefresh() { {{ t('admin.ops.requestDetails.details') }}
-
+
{{ upstreamErrorRatePercent == null ? '-' : `${upstreamErrorRatePercent.toFixed(2)}%` }}