140 lines
5.2 KiB
Python
140 lines
5.2 KiB
Python
# 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
|
|
|
|
print("overview_callbacks.py geladen")
|
|
|
|
API_BASE_URL = os.getenv("API_BASE_URL", "https://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:
|
|
verify_ssl = True if ENV == "production" else False
|
|
resp = requests.get(
|
|
f"{API_BASE_URL}/api/clients",
|
|
verify=verify_ssl
|
|
)
|
|
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 ""
|