# dashboard/callbacks/overview_callbacks.py import sys sys.path.append('/workspace') import threading import dash import requests from dash import Input, Output, State, MATCH, html, dcc from flask import session from utils.db import get_session # Diese Funktion muss eine SQLAlchemy-Session liefern! from utils.mqtt_client import publish, start_loop from config import ENV import dash_bootstrap_components as dbc import os import time import pytz from datetime import datetime API_BASE_URL = os.getenv("API_BASE_URL", "http://192.168.43.100") mqtt_thread_started = False SCREENSHOT_DIR = "received-screenshots" def ensure_mqtt_running(): global mqtt_thread_started if not mqtt_thread_started: thread = threading.Thread(target=start_loop, daemon=True) thread.start() mqtt_thread_started = True def get_latest_screenshot(client_uuid): cache_buster = int(time.time()) # aktuelle Unix-Zeit in Sekunden # TODO: Hier genau im Produkitv-Modus die IPs testen! # Wenn API_BASE_URL auf "http" beginnt, absolute URL verwenden (z.B. im lokalen Dev) if API_BASE_URL.startswith("http"): return f"{API_BASE_URL}/screenshots/{client_uuid}?t={cache_buster}" # Sonst relative URL (nginx-Proxy übernimmt das Routing) return f"/screenshots/{client_uuid}?t={cache_buster}" def fetch_clients(): try: resp = requests.get(f"{API_BASE_URL}/api/clients") resp.raise_for_status() return resp.json() except Exception as e: print("Fehler beim Abrufen der Clients:", e) return [] @dash.callback( Output("clients-cards-container", "children"), Input("interval-update", "n_intervals") ) def update_clients(n): # ... Session-Handling wie gehabt ... ensure_mqtt_running() clients = fetch_clients() cards = [] for client in clients: uuid = client["uuid"] screenshot = get_latest_screenshot(uuid) last_alive_utc = client.get("last_alive") if last_alive_utc: try: # Unterstützt sowohl "2024-06-08T12:34:56Z" als auch "2024-06-08T12:34:56" if last_alive_utc.endswith("Z"): dt_utc = datetime.strptime(last_alive_utc, "%Y-%m-%dT%H:%M:%SZ") else: dt_utc = datetime.strptime(last_alive_utc, "%Y-%m-%dT%H:%M:%S") dt_utc = dt_utc.replace(tzinfo=pytz.UTC) # Lokale Zeitzone fest angeben, z.B. Europe/Berlin local_tz = pytz.timezone("Europe/Berlin") dt_local = dt_utc.astimezone(local_tz) last_alive_str = dt_local.strftime("%d.%m.%Y %H:%M:%S") except Exception: last_alive_str = last_alive_utc else: last_alive_str = "-" card = dbc.Card( [ dbc.CardHeader(client["location"]), dbc.CardBody([ html.Img( src=screenshot, style={ "width": "240px", "height": "135px", "object-fit": "cover", "display": "block", "margin-left": "auto", "margin-right": "auto" }, ), html.P(f"IP: {client['ip_address'] or '-'}", className="card-text"), html.P(f"Letzte Aktivität: {last_alive_str}", className="card-text"), dbc.ButtonGroup([ dbc.Button("Reload Page", color="primary", id={"type": "btn-reload", "index": uuid}, n_clicks=0), dbc.Button("Restart Client", color="danger", id={"type": "btn-restart", "index": uuid}, n_clicks=0), ], className="mt-2"), html.Div(id={"type": "restart-feedback", "index": uuid}), html.Div(id={"type": "reload-feedback", "index": uuid}), ]), ], className="mb-4", style={"width": "18rem"}, ) cards.append(dbc.Col(card, width=4)) return dbc.Row(cards) @dash.callback( Output({"type": "restart-feedback", "index": MATCH}, "children"), Input({"type": "btn-restart", "index": MATCH}, "n_clicks"), State({"type": "btn-restart", "index": MATCH}, "id") ) def on_restart(n_clicks, btn_id): if n_clicks and n_clicks > 0: cid = btn_id["index"] payload = '{"command": "restart"}' ok = publish(f"clients/{cid}/control", payload) return "Befehl gesendet." if ok else "Fehler beim Senden." return "" @dash.callback( Output({"type": "reload-feedback", "index": MATCH}, "children"), Input({"type": "btn-reload", "index": MATCH}, "n_clicks"), State({"type": "btn-reload", "index": MATCH}, "id") ) def on_reload(n_clicks, btn_id): if n_clicks and n_clicks > 0: cid = btn_id["index"] payload = '{"command": "reload"}' ok = publish(f"clients/{cid}/control", payload) return "Befehl gesendet." if ok else "Fehler beim Senden." return ""