Neue Tools: tailscale.py (Netzwerk-Status) + grafana.py (Dashboard-Status)
This commit is contained in:
parent
4b8ee105de
commit
1a88c782d6
2 changed files with 140 additions and 0 deletions
73
homelab-ai-bot/tools/grafana.py
Normal file
73
homelab-ai-bot/tools/grafana.py
Normal file
|
|
@ -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,
|
||||
}
|
||||
67
homelab-ai-bot/tools/tailscale.py
Normal file
67
homelab-ai-bot/tools/tailscale.py
Normal file
|
|
@ -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,
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue