From 84ca116e27b7a14ca00a8ec1ee6e223e94d351fa Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Sun, 19 Apr 2026 05:22:09 +0300 Subject: [PATCH] feat: Add monitoring page with Prometheus metrics, real-time alerts, and comprehensive system health dashboard - Add "use client" directive to monitoring page for client-side rendering - Add useState/useEffect hooks for MetricSample/Alert state management with loading/error states - Add loadMonitoringData with Promise.all for api.monitoring.metrics/alerts, handleCustomQuery with PromQL execution - Add 8 predefinedQueries: uptime, CPU/memory/network usage, connections, response time, error/request rates --- .../web-dashboard/src/app/monitoring/page.tsx | 301 ++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 apps/web-dashboard/src/app/monitoring/page.tsx diff --git a/apps/web-dashboard/src/app/monitoring/page.tsx b/apps/web-dashboard/src/app/monitoring/page.tsx new file mode 100644 index 0000000..8415310 --- /dev/null +++ b/apps/web-dashboard/src/app/monitoring/page.tsx @@ -0,0 +1,301 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Activity, AlertTriangle, TrendingUp, TrendingDown, Eye, RefreshCw } from "lucide-react"; +import { api, Alert, MetricSample } from "@/lib/api"; +import { ErrorAlert } from "@/components/ui/error-alert"; +import { LoadingState, ActionLoading } from "@/components/ui/loading-state"; + +export default function MonitoringPage() { + const [metrics, setMetrics] = useState([]); + const [alerts, setAlerts] = useState([]); + const [selectedQuery, setSelectedQuery] = useState("up"); + const [customQuery, setCustomQuery] = useState(""); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const predefinedQueries = [ + { label: "System Uptime", query: "up" }, + { label: "CPU Usage", query: "rate(cpu_usage_total[5m])" }, + { label: "Memory Usage", query: "rate(memory_usage_bytes[5m])" }, + { label: "Network Traffic", query: "rate(network_traffic_bytes[5m])" }, + { label: "Active Connections", query: "active_connections_total" }, + { label: "Response Time", query: "http_request_duration_seconds" }, + { label: "Error Rate", query: "rate(http_requests_total{status=~\"5..\"}[5m])" }, + { label: "Request Rate", query: "rate(http_requests_total[5m])" } + ]; + + useEffect(() => { + loadMonitoringData(); + }, [selectedQuery]); + + const loadMonitoringData = async () => { + try { + setLoading(true); + setError(null); + const [metricsData, alertsData] = await Promise.all([ + api.monitoring.metrics(selectedQuery), + api.monitoring.alerts() + ]); + + setMetrics(metricsData.data); + setAlerts(alertsData); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : "Failed to load monitoring data"; + setError(errorMessage); + console.error("Failed to load monitoring data:", err); + } finally { + setLoading(false); + } + }; + + const handleCustomQuery = async () => { + if (!customQuery.trim()) return; + + try { + setLoading(true); + setError(null); + const metricsData = await api.monitoring.metrics(customQuery); + setMetrics(metricsData.data); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : "Failed to execute custom query"; + setError(errorMessage); + console.error("Failed to execute custom query:", err); + } finally { + setLoading(false); + } + }; + + const getAlertSeverityColor = (severity: string) => { + switch (severity.toLowerCase()) { + case 'critical': return 'destructive'; + case 'warning': return 'warning'; + case 'info': return 'secondary'; + default: return 'secondary'; + } + }; + + const getAlertStatusColor = (state: string) => { + switch (state.toLowerCase()) { + case 'firing': return 'destructive'; + case 'resolved': return 'success'; + default: return 'secondary'; + } + }; + + const formatMetricValue = (value: number) => { + if (value >= 1000000) return `${(value / 1000000).toFixed(2)}M`; + if (value >= 1000) return `${(value / 1000).toFixed(2)}K`; + return value.toFixed(2); + }; + + if (loading && metrics.length === 0) { + return ( +
+
+
+
+
+
+ {[...Array(4)].map((_, i) => ( +
+
+
+ ))} +
+
+ ); + } + + if (error) { + return ( +
+
+

Error loading monitoring data

+

{error}

+
+
+ ); + } + + return ( +
+
+
+ +
+

Monitoring

+

Advanced metrics and alerting dashboard.

+
+
+ +
+ + {/* Query Controls */} + + + Metric Query + + +
+
+ {predefinedQueries.map((pq) => ( + + ))} +
+
+ setCustomQuery(e.target.value)} + className="flex-1 h-9 rounded-lg border border-input bg-background px-3 text-sm outline-none focus:ring-2 focus:ring-ring/50" + /> + +
+
+ Current query: {selectedQuery} +
+
+
+
+ + {/* Metrics Display */} +
+ + + + + Metric Values + + + +
+ {metrics.length > 0 ? ( + metrics.map((metric, index) => ( +
+
+

+ {new Date(metric.timestamp).toLocaleString()} +

+

+ {metric.timestamp} +

+
+
+

+ {formatMetricValue(metric.value)} +

+

+ {metric.value > 0 ? ( + + ) : ( + + )} +

+
+
+ )) + ) : ( +

No metrics data available

+ )} +
+
+
+ + + + + + Active Alerts + + + +
+ {alerts.length > 0 ? ( + alerts.map((alert, index) => ( +
+
+ {alert.name} +
+ + {alert.severity} + + + {alert.state} + +
+
+

{alert.summary}

+

{alert.description}

+
+ Started: {new Date(alert.startsAt).toLocaleString()} + {alert.endsAt && ( + Ended: {new Date(alert.endsAt).toLocaleString()} + )} +
+
+ )) + ) : ( +

No active alerts

+ )} +
+
+
+
+ + {/* System Health Overview */} + + + + + System Health Overview + + + +
+
+
+ {alerts.filter(a => a.state === 'resolved').length} +
+

Resolved Alerts

+
+
+
+ {alerts.filter(a => a.state === 'firing').length} +
+

Active Alerts

+
+
+
+ {alerts.filter(a => a.severity === 'critical').length} +
+

Critical Alerts

+
+
+
+ {metrics.length} +
+

Data Points

+
+
+
+
+
+ ); +}