From 5f1d19c4fb565fb2db420608f17a0f40e1a36c00 Mon Sep 17 00:00:00 2001 From: ayushcmdx Date: Thu, 15 Oct 2026 09:02:46 +0530 Subject: [PATCH 1/2] feat: implement CloudMonitor observability stack --- docker-compose.yml | 4 +- monitoring/alertmanager/alertmanager.yml | 21 + monitoring/alerts/rules.yml | 64 ++ .../dashboards/cloudMonitor_overview.json | 976 ++++++++++++++++++ .../provisioning/dashboards/dashboards.yml | 12 + .../provisioning/datasources/prometheus.yml | 10 + monitoring/prometheus/prometheus.yml | 47 + 7 files changed, 1133 insertions(+), 1 deletion(-) create mode 100644 monitoring/alertmanager/alertmanager.yml create mode 100644 monitoring/alerts/rules.yml create mode 100644 monitoring/grafana/dashboards/cloudMonitor_overview.json create mode 100644 monitoring/grafana/provisioning/dashboards/dashboards.yml create mode 100644 monitoring/grafana/provisioning/datasources/prometheus.yml create mode 100644 monitoring/prometheus/prometheus.yml diff --git a/docker-compose.yml b/docker-compose.yml index 8f3d815..756aa1d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,7 +59,8 @@ services: - '--docker=' - '--containerd=/var/run/containerd/containerd.sock' - '--containerd-namespace=moby' - - '--housekeeping_interval=5s' + - '--disable_metrics=disk,tcp,udp,percpu' + - '--housekeeping_interval=10s' networks: - cloudmonitor-net @@ -130,6 +131,7 @@ services: start_period: 5s frontend: + profiles: ["frontend"] image: cloudmonitor-frontend:latest build: context: ./frontend diff --git a/monitoring/alertmanager/alertmanager.yml b/monitoring/alertmanager/alertmanager.yml new file mode 100644 index 0000000..dca2fbe --- /dev/null +++ b/monitoring/alertmanager/alertmanager.yml @@ -0,0 +1,21 @@ +global: + resolve_timeout: 1m + +route: + group_by: ['alertname', 'severity', 'component'] + group_wait: 5s + group_interval: 10s + repeat_interval: 1h + receiver: 'cloudmonitor-default-log' + routes: + - match: + severity: critical + receiver: 'cloudmonitor-critical-receiver' + continue: true + +receivers: + - name: 'cloudmonitor-default-log' + webhook_configs: [] + + - name: 'cloudmonitor-critical-receiver' + webhook_configs: [] diff --git a/monitoring/alerts/rules.yml b/monitoring/alerts/rules.yml new file mode 100644 index 0000000..20693f8 --- /dev/null +++ b/monitoring/alerts/rules.yml @@ -0,0 +1,64 @@ +groups: + - name: cloudMonitor_infrastructure_alerts + rules: + - alert: HostHighCpuUtilization + expr: (100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[1m])) * 100)) > 85 + for: 1m + labels: + severity: warning + component: infrastructure + annotations: + summary: "Host CPU utilization exceeds 85%" + description: "Host instance {{ $labels.instance }} CPU utilization is currently at {{ $value }}%." + + - alert: HostHighMemoryUtilization + expr: ((1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100) > 85 + for: 1m + labels: + severity: warning + component: infrastructure + annotations: + summary: "Host memory utilization exceeds 85%" + description: "Host memory utilization on {{ $labels.instance }} is currently at {{ $value }}%." + + - name: cloudMonitor_application_alerts + rules: + - alert: CloudMonitorApplicationDown + expr: up{job="cloudmonitor-backend"} == 0 + for: 15s + labels: + severity: critical + component: application + annotations: + summary: "CloudMonitor backend API instance is DOWN" + description: "The CloudMonitor backend service target has been unreachable for more than 15 seconds." + + - alert: CloudMonitorHealthDegraded + expr: cloudmonitor_app_health_status == 0 + for: 15s + labels: + severity: critical + component: application + annotations: + summary: "CloudMonitor application health status reported degraded" + description: "The application self-diagnostic probe has reported unhealthy (gauge=0)." + + - alert: HighHttpErrorRate + expr: sum(rate(cloudmonitor_http_requests_total{status_code=~"5.."}[1m])) > 0.05 + for: 30s + labels: + severity: warning + component: application + annotations: + summary: "Elevated 5xx HTTP error rate detected" + description: "CloudMonitor backend is returning elevated 5xx server errors at {{ $value }} errors/sec." + + - alert: HighRequestLatency + expr: histogram_quantile(0.95, sum(rate(cloudmonitor_http_request_duration_seconds_bucket[1m])) by (le)) > 0.5 + for: 1m + labels: + severity: warning + component: application + annotations: + summary: "P95 HTTP response latency exceeds 500ms SLO target" + description: "95% of API requests are taking longer than 500ms (current P95: {{ $value }}s)." diff --git a/monitoring/grafana/dashboards/cloudMonitor_overview.json b/monitoring/grafana/dashboards/cloudMonitor_overview.json new file mode 100644 index 0000000..a4f621c --- /dev/null +++ b/monitoring/grafana/dashboards/cloudMonitor_overview.json @@ -0,0 +1,976 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 100, + "title": "Application Health & SLO Status", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "0": { + "color": "red", + "index": 1, + "text": "DEGRADED" + }, + "1": { + "color": "green", + "index": 0, + "text": "HEALTHY" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 1 + }, + "id": 1, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "cloudmonitor_app_health_status", + "instant": true, + "range": false, + "refId": "A" + } + ], + "title": "App Health Status", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "0": { + "color": "red", + "index": 1, + "text": "DOWN" + }, + "1": { + "color": "green", + "index": 0, + "text": "UP" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 1 + }, + "id": 2, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "up{job=\"cloudmonitor-backend\"}", + "instant": true, + "range": false, + "refId": "A" + } + ], + "title": "Backend Service Target", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + }, + { + "color": "orange", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 1 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "cloudmonitor_active_requests", + "instant": false, + "range": true, + "refId": "A" + } + ], + "title": "Active In-Flight Requests", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 1 + }, + { + "color": "red", + "value": 5 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 1 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "cloudmonitor_simulated_errors_total", + "instant": true, + "range": false, + "refId": "A" + } + ], + "title": "Simulated Faults Injected", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 5 + }, + "id": 101, + "title": "Traffic & Latency Performance", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "line", + "lineInterpolation": "smooth", + "lineWidth": 2, + "fillOpacity": 15 + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 6 + }, + "id": 5, + "options": { + "legend": { + "calcs": ["mean", "lastNotNull", "max"], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "sum by (status_code) (rate(cloudmonitor_http_requests_total[1m]))", + "legendFormat": "HTTP {{status_code}}", + "range": true, + "refId": "A" + } + ], + "title": "HTTP Request Rate by Status Code", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "line", + "lineInterpolation": "smooth", + "lineWidth": 2, + "fillOpacity": 10 + }, + "unit": "s", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 0.5 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 6 + }, + "id": 6, + "options": { + "legend": { + "calcs": ["mean", "lastNotNull", "max"], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum by (le) (rate(cloudmonitor_http_request_duration_seconds_bucket[1m])))", + "legendFormat": "P50 Latency", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.90, sum by (le) (rate(cloudmonitor_http_request_duration_seconds_bucket[1m])))", + "legendFormat": "P90 Latency", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum by (le) (rate(cloudmonitor_http_request_duration_seconds_bucket[1m])))", + "legendFormat": "P95 Latency (SLO 500ms)", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by (le) (rate(cloudmonitor_http_request_duration_seconds_bucket[1m])))", + "legendFormat": "P99 Latency", + "range": true, + "refId": "D" + } + ], + "title": "API Response Latency Percentiles (SLO Target: 500ms)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "line", + "lineInterpolation": "smooth", + "lineWidth": 2, + "fillOpacity": 20 + }, + "unit": "reqps", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 0.05 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 14 + }, + "id": 7, + "options": { + "legend": { + "calcs": ["mean", "lastNotNull", "max"], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(cloudmonitor_http_requests_total{status_code=~\"5..\"}[1m]))", + "legendFormat": "5xx Error Rate", + "range": true, + "refId": "A" + } + ], + "title": "HTTP 5xx Server Error Rate (Alert Threshold > 0.05 req/s)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "line", + "lineInterpolation": "smooth", + "lineWidth": 2, + "fillOpacity": 15 + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 14 + }, + "id": 8, + "options": { + "legend": { + "calcs": ["mean", "lastNotNull"], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "sum by (endpoint, method) (rate(cloudmonitor_http_requests_total[1m]))", + "legendFormat": "{{method}} {{endpoint}}", + "range": true, + "refId": "A" + } + ], + "title": "Throughput by API Endpoint", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 22 + }, + "id": 102, + "title": "Infrastructure & Host Health", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 70 + }, + { + "color": "red", + "value": 85 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 23 + }, + "id": 9, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "100 - (avg by (instance) (rate(node_cpu_seconds_total{mode=\"idle\"}[1m])) * 100)", + "instant": true, + "range": false, + "refId": "A" + } + ], + "title": "Host CPU Utilization", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 70 + }, + { + "color": "red", + "value": 85 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 23 + }, + "id": 10, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "((1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100)", + "instant": true, + "range": false, + "refId": "A" + } + ], + "title": "Host Memory Utilization", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "line", + "lineInterpolation": "smooth", + "lineWidth": 2, + "fillOpacity": 10 + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 12, + "y": 23 + }, + "id": 11, + "options": { + "legend": { + "calcs": ["lastNotNull", "max"], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "sum by (image) (rate(container_cpu_usage_seconds_total{image=~\".+\"}[1m]))", + "legendFormat": "{{image}}", + "range": true, + "refId": "A" + } + ], + "title": "Docker Containers CPU Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "line", + "lineInterpolation": "smooth", + "lineWidth": 2, + "fillOpacity": 10 + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 23 + }, + "id": 12, + "options": { + "legend": { + "calcs": ["lastNotNull", "max"], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "sum by (image) (container_memory_usage_bytes{image=~\".+\"})", + "legendFormat": "{{image}}", + "range": true, + "refId": "A" + } + ], + "title": "Docker Containers Memory Usage", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 103, + "title": "Target Scrape Health", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [ + { + "options": { + "0": { + "color": "red", + "index": 1, + "text": "DOWN" + }, + "1": { + "color": "green", + "index": 0, + "text": "UP" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 30 + }, + "id": 13, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": ["sum"], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "up", + "format": "table", + "instant": true, + "range": false, + "refId": "A" + } + ], + "title": "Scrape Target Status Overview", + "type": "table" + } + ], + "refresh": "5s", + "schemaVersion": 39, + "tags": [ + "cloud-monitor", + "prometheus", + "fastapi" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m" + ] + }, + "timezone": "browser", + "title": "Cloud Monitor - System Overview", + "uid": "cloudmonitor-overview", + "version": 1 +} diff --git a/monitoring/grafana/provisioning/dashboards/dashboards.yml b/monitoring/grafana/provisioning/dashboards/dashboards.yml new file mode 100644 index 0000000..f3aa9f8 --- /dev/null +++ b/monitoring/grafana/provisioning/dashboards/dashboards.yml @@ -0,0 +1,12 @@ +apiVersion: 1 + +providers: + - name: 'CloudMonitor Dashboards' + orgId: 1 + folder: 'CloudMonitor' + type: file + disableDeletion: false + updateIntervalSeconds: 10 + allowUiUpdates: true + options: + path: /etc/grafana/dashboards diff --git a/monitoring/grafana/provisioning/datasources/prometheus.yml b/monitoring/grafana/provisioning/datasources/prometheus.yml new file mode 100644 index 0000000..9e3c7b4 --- /dev/null +++ b/monitoring/grafana/provisioning/datasources/prometheus.yml @@ -0,0 +1,10 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + uid: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true + editable: false diff --git a/monitoring/prometheus/prometheus.yml b/monitoring/prometheus/prometheus.yml new file mode 100644 index 0000000..e1e2d31 --- /dev/null +++ b/monitoring/prometheus/prometheus.yml @@ -0,0 +1,47 @@ +global: + scrape_interval: 15s + evaluation_interval: 15s + scrape_timeout: 10s + external_labels: + monitor: 'cloudmonitor-monitor' + environment: 'local' + +alerting: + alertmanagers: + - static_configs: + - targets: ['alertmanager:9093'] + +rule_files: + - '/etc/prometheus/alerts/*.yml' + +scrape_configs: + - job_name: 'prometheus' + static_configs: + - targets: ['localhost:9090'] + labels: + service: 'prometheus' + + - job_name: 'node-exporter' + static_configs: + - targets: ['node-exporter:9100'] + labels: + service: 'node-exporter' + + - job_name: 'cadvisor' + static_configs: + - targets: ['cadvisor:8080'] + labels: + service: 'cadvisor' + + - job_name: 'cloudmonitor-backend' + metrics_path: '/metrics' + static_configs: + - targets: ['backend:8000'] + labels: + service: 'cloudmonitor-backend' + + - job_name: 'alertmanager' + static_configs: + - targets: ['alertmanager:9093'] + labels: + service: 'alertmanager' From 28e2a0b0f7ab9349d74dbaab2c6b1b088294d836 Mon Sep 17 00:00:00 2001 From: Parveen-collab Date: Sat, 7 Nov 2026 13:10:22 +0530 Subject: [PATCH 2/2] feat(auth): add password visibility toggle button component# --- frontend/src/components/AuthScreen.jsx | 37 ++++++++++++---- .../src/components/PasswordToggleButton.jsx | 27 ++++++++++++ frontend/src/index.css | 43 +++++++++++++++++-- 3 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 frontend/src/components/PasswordToggleButton.jsx diff --git a/frontend/src/components/AuthScreen.jsx b/frontend/src/components/AuthScreen.jsx index 92db79b..aff1ef9 100644 --- a/frontend/src/components/AuthScreen.jsx +++ b/frontend/src/components/AuthScreen.jsx @@ -1,16 +1,26 @@ import React, { useState } from 'react'; import { Activity, Lock, Mail, ArrowRight, ShieldCheck, CheckCircle2, AlertCircle, Sun, Moon, Info } from 'lucide-react'; import { api } from '../api'; +import PasswordToggleButton from './PasswordToggleButton'; export default function AuthScreen({ onAuthSuccess, sessionExpired = false, currentTheme, onToggleTheme }) { const [mode, setMode] = useState('login'); // 'login' | 'register' const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); + const [showPassword, setShowPassword] = useState(false); + const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(sessionExpired ? 'Your session has expired. Please sign in again.' : null); const [successMsg, setSuccessMsg] = useState(null); + const switchMode = (newMode) => { + setMode(newMode); + setError(null); + setShowPassword(false); + setShowConfirmPassword(false); + }; + const handleSubmit = async (e) => { e.preventDefault(); setError(null); @@ -97,14 +107,14 @@ export default function AuthScreen({ onAuthSuccess, sessionExpired = false, curr @@ -155,15 +165,20 @@ export default function AuthScreen({ onAuthSuccess, sessionExpired = false, curr setPassword(e.target.value)} disabled={loading} /> + setShowPassword((prev) => !prev)} + disabled={loading} + /> {mode === 'register' && ( At least 8 characters required. @@ -179,15 +194,21 @@ export default function AuthScreen({ onAuthSuccess, sessionExpired = false, curr setConfirmPassword(e.target.value)} disabled={loading} /> + setShowConfirmPassword((prev) => !prev)} + label="confirm password" + disabled={loading} + /> )} @@ -210,7 +231,7 @@ export default function AuthScreen({ onAuthSuccess, sessionExpired = false, curr @@ -221,7 +242,7 @@ export default function AuthScreen({ onAuthSuccess, sessionExpired = false, curr diff --git a/frontend/src/components/PasswordToggleButton.jsx b/frontend/src/components/PasswordToggleButton.jsx new file mode 100644 index 0000000..53d696c --- /dev/null +++ b/frontend/src/components/PasswordToggleButton.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Eye, EyeOff } from 'lucide-react'; + +export default function PasswordToggleButton({ + isVisible, + onToggle, + disabled = false, + label = 'password', + size = 18, + className = '', +}) { + const actionText = isVisible ? `Hide ${label}` : `Show ${label}`; + + return ( + + ); +} + diff --git a/frontend/src/index.css b/frontend/src/index.css index da32e56..9d85c2a 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -68,7 +68,9 @@ } /* Base Resets */ -*, *::before, *::after { +*, +*::before, +*::after { box-sizing: border-box; margin: 0; padding: 0; @@ -657,15 +659,19 @@ body { transform: translateX(-100%); width: 240px !important; } + .app-sidebar.mobile-open { transform: translateX(0); } + .app-main { margin-left: 0 !important; } + .kpi-grid { grid-template-columns: 1fr; } + .content-body { padding: 16px; } @@ -677,7 +683,9 @@ body { /* Keyframe for loading spinners */ @keyframes spin { - to { transform: rotate(360deg); } + to { + transform: rotate(360deg); + } } .spin-anim { @@ -815,6 +823,35 @@ body { box-shadow: 0 0 0 3px rgba(34, 184, 176, 0.2); } +.auth-input.has-toggle { + padding-right: 42px; +} + +.password-toggle-btn { + position: absolute; + right: 10px; + background: transparent; + border: none; + color: var(--text-subtle); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + padding: 6px; + border-radius: 6px; + transition: color 0.15s ease, background-color 0.15s ease; +} + +.password-toggle-btn:hover:not(:disabled) { + color: var(--text-main); + background-color: rgba(120, 159, 180, 0.15); +} + +.password-toggle-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + .auth-submit-btn { width: 100%; padding: 13px; @@ -1384,4 +1421,4 @@ body { background: var(--bg-card-solid); border: 1px solid var(--border-glass); color: var(--text-main); -} +} \ No newline at end of file