From 1a88c782d67db1a1001340ec2abaebc60b208eb8 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 16 Mar 2026 16:08:24 +0700 Subject: [PATCH] Neue Tools: tailscale.py (Netzwerk-Status) + grafana.py (Dashboard-Status) --- homelab-ai-bot/tools/grafana.py | 73 +++++++++++++++++++++++++++++++ homelab-ai-bot/tools/tailscale.py | 67 ++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 homelab-ai-bot/tools/grafana.py create mode 100644 homelab-ai-bot/tools/tailscale.py diff --git a/homelab-ai-bot/tools/grafana.py b/homelab-ai-bot/tools/grafana.py new file mode 100644 index 00000000..5d797eaf --- /dev/null +++ b/homelab-ai-bot/tools/grafana.py @@ -0,0 +1,73 @@ +"""Grafana Monitoring Dashboard Tool.""" + +import requests + +TOOLS = [ + { + "type": "function", + "function": { + "name": "get_grafana_status", + "description": "Grafana Monitoring-Status: Health, Dashboards, Datasources, Alerts. Nutze bei 'Grafana', 'Monitoring Dashboard', 'Alerts'.", + "parameters": {"type": "object", "properties": {}, "required": []}, + }, + }, +] + +GRAFANA_URL = "http://100.109.206.43:3000" +GRAFANA_USER = "admin" +GRAFANA_PASS = "astral66" + + +def _api(path): + try: + r = requests.get( + f"{GRAFANA_URL}{path}", + auth=(GRAFANA_USER, GRAFANA_PASS), + timeout=10, + ) + if r.ok: + return r.json() + return None + except Exception: + return None + + +def handle_get_grafana_status(**kw): + health = _api("/api/health") + if not health: + return "Grafana nicht erreichbar (http://100.109.206.43:3000)" + + lines = [f"Grafana v{health.get('version', '?')} — DB: {health.get('database', '?')}"] + lines.append(f"URL: https://grafana.orbitalo.net") + + datasources = _api("/api/datasources") or [] + lines.append(f"\nDatasources: {len(datasources)}") + if datasources: + for ds in datasources: + lines.append(f" {ds.get('name', '?')} ({ds.get('type', '?')}) — {'aktiv' if ds.get('access') else 'inaktiv'}") + else: + lines.append(" Keine konfiguriert — Prometheus muss noch als Datasource hinzugefuegt werden") + + dashboards = _api("/api/search?type=dash-db") or [] + lines.append(f"\nDashboards: {len(dashboards)}") + for db in dashboards[:10]: + lines.append(f" {db.get('title', '?')} (/{db.get('uri', '?')})") + if not dashboards: + lines.append(" Keine Dashboards vorhanden") + + alerts = _api("/api/alertmanager/grafana/api/v2/alerts") or [] + if alerts: + firing = [a for a in alerts if a.get("status", {}).get("state") == "active"] + lines.append(f"\nAlerts: {len(alerts)} gesamt, {len(firing)} aktiv") + for a in firing[:5]: + labels = a.get("labels", {}) + lines.append(f" {labels.get('alertname', '?')} — {labels.get('severity', '?')}") + else: + lines.append("\nAlerts: keine") + + return "\n".join(lines) + + +HANDLERS = { + "get_grafana_status": handle_get_grafana_status, +} diff --git a/homelab-ai-bot/tools/tailscale.py b/homelab-ai-bot/tools/tailscale.py new file mode 100644 index 00000000..10572b81 --- /dev/null +++ b/homelab-ai-bot/tools/tailscale.py @@ -0,0 +1,67 @@ +"""Tailscale Netzwerk-Status Tool.""" + +import json +import subprocess + +TOOLS = [ + { + "type": "function", + "function": { + "name": "get_tailscale_status", + "description": "Tailscale VPN Netzwerk-Status: Welche Geraete/Server sind online oder offline? Nutze bei 'ist Server X erreichbar', 'welche Geraete sind online', 'Netzwerk-Status', 'VPN'.", + "parameters": { + "type": "object", + "properties": { + "query": {"type": "string", "description": "Optional: Name filtern (z.B. 'pve', 'gold', 'wordpress')", "default": ""} + }, + "required": [], + }, + }, + }, +] + + +def handle_get_tailscale_status(query="", **kw): + try: + out = subprocess.check_output( + ["tailscale", "status", "--json"], text=True, timeout=10 + ) + except Exception as e: + return f"Tailscale nicht verfuegbar: {e}" + + d = json.loads(out) + peers = d.get("Peer", {}) + + devices = [] + for key, p in peers.items(): + name = p.get("HostName", "?") + ip = p.get("TailscaleIPs", ["?"])[0] + online = p.get("Online", False) + last = p.get("LastSeen", "")[:10] + devices.append({"name": name, "ip": ip, "online": online, "last": last}) + + if query: + q = query.lower() + devices = [d for d in devices if q in d["name"].lower() or q in d["ip"]] + + online = [d for d in devices if d["online"]] + offline = [d for d in devices if not d["online"]] + + lines = [f"Tailscale: {len(online)} online, {len(offline)} offline (von {len(devices)} Geraeten)"] + + if online: + lines.append(f"\n=== ONLINE ({len(online)}) ===") + for d in sorted(online, key=lambda x: x["name"]): + lines.append(f" {d['name']:28s} {d['ip']}") + + if offline: + lines.append(f"\n=== OFFLINE ({len(offline)}) ===") + for d in sorted(offline, key=lambda x: x["name"]): + lines.append(f" {d['name']:28s} {d['ip']:18s} zuletzt: {d['last']}") + + return "\n".join(lines) + + +HANDLERS = { + "get_tailscale_status": handle_get_tailscale_status, +}