#!/usr/bin/env python3 """ HTTP-сервер дашборда homelab: статика, /api/containers, прокси к Netdata. Порт: 19998 (по умолчанию). """ import json import os import subprocess import sys import urllib.request from http.server import HTTPServer, BaseHTTPRequestHandler from pathlib import Path PORT = int(os.environ.get("DASHBOARD_PORT", "19998")) NETDATA_URL = os.environ.get("NETDATA_URL", "http://127.0.0.1:19999") SCRIPT_DIR = Path(__file__).resolve().parent EXPORTER = SCRIPT_DIR / "dashboard-exporter.py" class DashboardHandler(BaseHTTPRequestHandler): def log_message(self, format, *args): pass # подавить вывод в консоль def send_json(self, data: dict, status: int = 200): self.send_response(status) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(json.dumps(data, ensure_ascii=False).encode("utf-8")) def send_html(self, html: bytes, status: int = 200): self.send_response(status) self.send_header("Content-Type", "text/html; charset=utf-8") self.send_header("Cache-Control", "no-cache") self.end_headers() self.wfile.write(html) def do_GET(self): path = self.path.split("?")[0].rstrip("/") or "/" if path == "/": self.serve_index() elif path == "/api/containers": self.serve_containers() elif path.startswith("/api/netdata"): self.proxy_netdata() else: self.send_error(404) def serve_index(self): html_file = SCRIPT_DIR / "index.html" if html_file.exists(): self.send_html(html_file.read_bytes()) else: self.send_error(404, "index.html not found") def serve_containers(self): try: r = subprocess.run( [sys.executable, str(EXPORTER)], capture_output=True, text=True, timeout=30, cwd=str(SCRIPT_DIR), ) if r.returncode != 0: self.send_json({"ok": False, "error": r.stderr or "exporter failed"}, 500) return data = json.loads(r.stdout) self.send_json(data) except subprocess.TimeoutExpired: self.send_json({"ok": False, "error": "timeout"}, 504) except json.JSONDecodeError as e: self.send_json({"ok": False, "error": str(e)}, 500) except Exception as e: self.send_json({"ok": False, "error": str(e)}, 500) def proxy_netdata(self): qs = self.path.split("?", 1)[1] if "?" in self.path else "" url = f"{NETDATA_URL}/api/v1/data?{qs}" if qs else f"{NETDATA_URL}/api/v1/data" try: req = urllib.request.Request(url) with urllib.request.urlopen(req, timeout=10) as resp: data = resp.read() self.send_response(200) self.send_header("Content-Type", resp.headers.get("Content-Type", "application/json")) self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() self.wfile.write(data) except Exception as e: self.send_json({"error": str(e)}, 502) def main(): server = HTTPServer(("0.0.0.0", PORT), DashboardHandler) print(f"Dashboard server on http://0.0.0.0:{PORT}", file=sys.stderr) try: server.serve_forever() except KeyboardInterrupt: pass if __name__ == "__main__": main()