diff --git a/services/grafana/dashboards/logs.json b/services/grafana/dashboards/logs.json new file mode 100644 index 0000000..e8678eb --- /dev/null +++ b/services/grafana/dashboards/logs.json @@ -0,0 +1,85 @@ +{ + "uid": "logs-homelab", + "title": "Logs - Homelab", + "tags": ["loki", "logs", "homelab"], + "timezone": "browser", + "schemaVersion": 39, + "version": 1, + "refresh": "30s", + "templating": { + "list": [ + { + "name": "host", + "type": "query", + "datasource": {"type": "loki", "uid": "loki"}, + "query": "label_values(host)", + "refresh": 2, + "includeAll": true, + "multi": false, + "current": {"text": "All", "value": "$__all"} + }, + { + "name": "job", + "type": "query", + "datasource": {"type": "loki", "uid": "loki"}, + "query": "label_values(job)", + "refresh": 2, + "includeAll": true, + "multi": false, + "current": {"text": "All", "value": "$__all"} + }, + { + "name": "search", + "type": "textbox", + "current": {"text": "", "value": ""}, + "label": "Search" + } + ] + }, + "panels": [ + { + "id": 1, + "title": "Log Volume", + "type": "timeseries", + "gridPos": {"h": 6, "w": 24, "x": 0, "y": 0}, + "datasource": {"type": "loki", "uid": "loki"}, + "targets": [ + { + "expr": "sum by (host) (count_over_time({host=~\"$host\", job=~\"$job\"} |~ \"$search\" [1m]))", + "legendFormat": "{{host}}", + "refId": "A" + } + ], + "fieldConfig": { + "defaults": { + "unit": "short" + } + }, + "options": { + "legend": {"displayMode": "list", "placement": "bottom"} + } + }, + { + "id": 2, + "title": "Logs", + "type": "logs", + "gridPos": {"h": 18, "w": 24, "x": 0, "y": 6}, + "datasource": {"type": "loki", "uid": "loki"}, + "targets": [ + { + "expr": "{host=~\"$host\", job=~\"$job\"} |~ \"$search\"", + "refId": "A" + } + ], + "options": { + "showTime": true, + "showLabels": true, + "showCommonLabels": false, + "wrapLogMessage": true, + "prettifyLogMessage": false, + "enableLogDetails": true, + "sortOrder": "Descending" + } + } + ] +} diff --git a/services/grafana/dashboards/node-exporter.json b/services/grafana/dashboards/node-exporter.json new file mode 100644 index 0000000..5da2746 --- /dev/null +++ b/services/grafana/dashboards/node-exporter.json @@ -0,0 +1,208 @@ +{ + "uid": "node-exporter-homelab", + "title": "Node Exporter - Homelab", + "tags": ["node-exporter", "prometheus", "homelab"], + "timezone": "browser", + "schemaVersion": 39, + "version": 1, + "refresh": "30s", + "templating": { + "list": [ + { + "name": "instance", + "type": "query", + "datasource": {"type": "prometheus", "uid": "prometheus"}, + "query": "label_values(node_uname_info, instance)", + "refresh": 2, + "includeAll": false, + "multi": false, + "current": {} + } + ] + }, + "panels": [ + { + "id": 1, + "title": "CPU Usage", + "type": "timeseries", + "gridPos": {"h": 8, "w": 12, "x": 0, "y": 0}, + "datasource": {"type": "prometheus", "uid": "prometheus"}, + "targets": [ + { + "expr": "100 - (avg by(instance) (rate(node_cpu_seconds_total{mode=\"idle\", instance=~\"$instance\"}[5m])) * 100)", + "legendFormat": "CPU %", + "refId": "A" + } + ], + "fieldConfig": { + "defaults": { + "unit": "percent", + "min": 0, + "max": 100, + "thresholds": { + "mode": "absolute", + "steps": [ + {"color": "green", "value": null}, + {"color": "yellow", "value": 70}, + {"color": "red", "value": 90} + ] + } + } + } + }, + { + "id": 2, + "title": "Memory Usage", + "type": "timeseries", + "gridPos": {"h": 8, "w": 12, "x": 12, "y": 0}, + "datasource": {"type": "prometheus", "uid": "prometheus"}, + "targets": [ + { + "expr": "(1 - (node_memory_MemAvailable_bytes{instance=~\"$instance\"} / node_memory_MemTotal_bytes{instance=~\"$instance\"})) * 100", + "legendFormat": "Memory %", + "refId": "A" + } + ], + "fieldConfig": { + "defaults": { + "unit": "percent", + "min": 0, + "max": 100, + "thresholds": { + "mode": "absolute", + "steps": [ + {"color": "green", "value": null}, + {"color": "yellow", "value": 70}, + {"color": "red", "value": 90} + ] + } + } + } + }, + { + "id": 3, + "title": "Disk Usage", + "type": "gauge", + "gridPos": {"h": 8, "w": 8, "x": 0, "y": 8}, + "datasource": {"type": "prometheus", "uid": "prometheus"}, + "targets": [ + { + "expr": "100 - ((node_filesystem_avail_bytes{instance=~\"$instance\",mountpoint=\"/\",fstype!=\"rootfs\"} / node_filesystem_size_bytes{instance=~\"$instance\",mountpoint=\"/\",fstype!=\"rootfs\"}) * 100)", + "legendFormat": "Root /", + "refId": "A" + } + ], + "fieldConfig": { + "defaults": { + "unit": "percent", + "min": 0, + "max": 100, + "thresholds": { + "mode": "absolute", + "steps": [ + {"color": "green", "value": null}, + {"color": "yellow", "value": 70}, + {"color": "red", "value": 85} + ] + } + } + } + }, + { + "id": 4, + "title": "System Load", + "type": "timeseries", + "gridPos": {"h": 8, "w": 8, "x": 8, "y": 8}, + "datasource": {"type": "prometheus", "uid": "prometheus"}, + "targets": [ + { + "expr": "node_load1{instance=~\"$instance\"}", + "legendFormat": "1m", + "refId": "A" + }, + { + "expr": "node_load5{instance=~\"$instance\"}", + "legendFormat": "5m", + "refId": "B" + }, + { + "expr": "node_load15{instance=~\"$instance\"}", + "legendFormat": "15m", + "refId": "C" + } + ], + "fieldConfig": { + "defaults": { + "unit": "short" + } + } + }, + { + "id": 5, + "title": "Uptime", + "type": "stat", + "gridPos": {"h": 8, "w": 8, "x": 16, "y": 8}, + "datasource": {"type": "prometheus", "uid": "prometheus"}, + "targets": [ + { + "expr": "time() - node_boot_time_seconds{instance=~\"$instance\"}", + "legendFormat": "Uptime", + "refId": "A" + } + ], + "fieldConfig": { + "defaults": { + "unit": "s" + } + } + }, + { + "id": 6, + "title": "Network Traffic", + "type": "timeseries", + "gridPos": {"h": 8, "w": 12, "x": 0, "y": 16}, + "datasource": {"type": "prometheus", "uid": "prometheus"}, + "targets": [ + { + "expr": "rate(node_network_receive_bytes_total{instance=~\"$instance\",device!~\"lo|veth.*|br.*|docker.*\"}[5m])", + "legendFormat": "Receive {{device}}", + "refId": "A" + }, + { + "expr": "-rate(node_network_transmit_bytes_total{instance=~\"$instance\",device!~\"lo|veth.*|br.*|docker.*\"}[5m])", + "legendFormat": "Transmit {{device}}", + "refId": "B" + } + ], + "fieldConfig": { + "defaults": { + "unit": "Bps" + } + } + }, + { + "id": 7, + "title": "Disk I/O", + "type": "timeseries", + "gridPos": {"h": 8, "w": 12, "x": 12, "y": 16}, + "datasource": {"type": "prometheus", "uid": "prometheus"}, + "targets": [ + { + "expr": "rate(node_disk_read_bytes_total{instance=~\"$instance\",device!~\"dm-.*\"}[5m])", + "legendFormat": "Read {{device}}", + "refId": "A" + }, + { + "expr": "-rate(node_disk_written_bytes_total{instance=~\"$instance\",device!~\"dm-.*\"}[5m])", + "legendFormat": "Write {{device}}", + "refId": "B" + } + ], + "fieldConfig": { + "defaults": { + "unit": "Bps" + } + } + } + ] +} diff --git a/services/grafana/default.nix b/services/grafana/default.nix index ca22e8c..75413c1 100644 --- a/services/grafana/default.nix +++ b/services/grafana/default.nix @@ -28,8 +28,8 @@ email_attribute_path = "email"; login_attribute_path = "preferred_username"; name_attribute_path = "name"; - # Map admins group to Admin role, everyone else to Viewer - role_attribute_path = "contains(groups[*], 'admins') && 'Admin' || 'Viewer'"; + # Map admins group to Admin role, everyone else to Editor (for Explore access) + role_attribute_path = "contains(groups[*], 'admins') && 'Admin' || 'Editor'"; allow_sign_up = true; }; }; @@ -53,6 +53,19 @@ } ]; }; + + # Declarative dashboards + provision.dashboards.settings = { + apiVersion = 1; + providers = [ + { + name = "homelab"; + type = "file"; + options.path = ./dashboards; + disableDeletion = true; + } + ]; + }; }; # Vault secret for OAuth2 client secret