initial commit
This commit is contained in:
0
dashboard/callbacks/__init__.py
Normal file
0
dashboard/callbacks/__init__.py
Normal file
27
dashboard/callbacks/appoinments_callbacks.py
Normal file
27
dashboard/callbacks/appoinments_callbacks.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# dashboard/callbacks/appointments_callbacks.py
|
||||
import dash
|
||||
from dash import Input, Output, State, dcc
|
||||
from flask import session
|
||||
from utils.db import execute_query, execute_non_query
|
||||
|
||||
@dash.callback(
|
||||
Output("appointments-feedback", "children"),
|
||||
Input("btn-add-appointment", "n_clicks"),
|
||||
State("input-client-id", "value"),
|
||||
State("input-appointment-date", "date"),
|
||||
State("input-appointment-time", "value"),
|
||||
State("input-appointment-desc", "value"),
|
||||
prevent_initial_call=True
|
||||
)
|
||||
def add_appointment(n_clicks, client_id, date, time_str, desc):
|
||||
if "role" not in session:
|
||||
return dcc.Location(href="/login")
|
||||
if n_clicks and n_clicks > 0:
|
||||
datetime_str = f"{date} {time_str}:00"
|
||||
sql = """
|
||||
INSERT INTO appointments (client_id, start_datetime, description)
|
||||
VALUES (%s, %s, %s)
|
||||
"""
|
||||
rc = execute_non_query(sql, (client_id, datetime_str, desc))
|
||||
return "Erstellt." if rc else "Fehler beim Anlegen."
|
||||
return ""
|
||||
31
dashboard/callbacks/auth_callbacks.py
Normal file
31
dashboard/callbacks/auth_callbacks.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# dashboard/callbacks/auth_callbacks.py
|
||||
import dash
|
||||
from dash import Input, Output, State, dcc
|
||||
from flask import session
|
||||
from utils.auth import check_password, get_user_role
|
||||
from config import ENV
|
||||
from utils.db import execute_query
|
||||
|
||||
@dash.callback(
|
||||
Output("login-feedback", "children"),
|
||||
Output("header-right", "children"),
|
||||
Input("btn-login", "n_clicks"),
|
||||
State("input-user", "value"),
|
||||
State("input-pass", "value"),
|
||||
prevent_initial_call=True
|
||||
)
|
||||
def login_user(n_clicks, username, password):
|
||||
if ENV == "development":
|
||||
# Dev‐Bypass: setze immer Admin‐Session und leite weiter
|
||||
session["username"] = "dev_admin"
|
||||
session["role"] = "admin"
|
||||
return dcc.Location(href="/overview", id="redirect-dev"), None
|
||||
|
||||
# Produktions‐Login: User in DB suchen
|
||||
user = execute_query("SELECT username, pwd_hash, role FROM users WHERE username=%s", (username,), fetch_one=True)
|
||||
if user and check_password(password, user["pwd_hash"]):
|
||||
session["username"] = user["username"]
|
||||
session["role"] = user["role"]
|
||||
return dcc.Location(href="/overview", id="redirect-ok"), None
|
||||
else:
|
||||
return "Ungültige Zugangsdaten.", None
|
||||
104
dashboard/callbacks/overview_callbacks.py
Normal file
104
dashboard/callbacks/overview_callbacks.py
Normal file
@@ -0,0 +1,104 @@
|
||||
# dashboard/callbacks/overview_callbacks.py
|
||||
import sys
|
||||
print(sys.path)
|
||||
import threading
|
||||
import dash
|
||||
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
|
||||
from server.models import Client
|
||||
|
||||
mqtt_thread_started = False
|
||||
SCREENSHOT_DIR = "/workspace/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):
|
||||
prefix = f"client_{client_uuid}_"
|
||||
try:
|
||||
files = [f for f in os.listdir(SCREENSHOT_DIR) if f.startswith(prefix)]
|
||||
if not files:
|
||||
return "/assets/placeholder.png"
|
||||
latest = max(files, key=lambda x: os.path.getmtime(os.path.join(SCREENSHOT_DIR, x)))
|
||||
return f"/received-screenshots/{latest}"
|
||||
except Exception:
|
||||
return "/assets/placeholder.png"
|
||||
|
||||
@dash.callback(
|
||||
Output("clients-cards-container", "children"),
|
||||
Input("interval-update", "n_intervals")
|
||||
)
|
||||
def update_clients(n):
|
||||
# Auto-Login im Development-Modus
|
||||
if "role" not in session:
|
||||
if ENV == "development":
|
||||
session["role"] = "admin"
|
||||
else:
|
||||
return dcc.Location(id="redirect-login", href="/login")
|
||||
|
||||
ensure_mqtt_running()
|
||||
session_db = get_session()
|
||||
clients = session_db.query(Client).all()
|
||||
session_db.close()
|
||||
cards = []
|
||||
for client in clients:
|
||||
uuid = client.uuid
|
||||
screenshot = get_latest_screenshot(uuid)
|
||||
card = dbc.Card(
|
||||
[
|
||||
dbc.CardHeader(client.location or client.hardware_hash),
|
||||
dbc.CardBody([
|
||||
html.Img(
|
||||
src=screenshot,
|
||||
style={"width": "160px", "height": "90px", "object-fit": "cover"},
|
||||
),
|
||||
html.P(f"IP: {client.ip_address or '-'}", className="card-text"),
|
||||
html.P(f"Letzte Aktivität: {client.last_alive or '-'}", 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 ""
|
||||
20
dashboard/callbacks/settings_callbacks.py
Normal file
20
dashboard/callbacks/settings_callbacks.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# dashboard/callbacks/settings_callbacks.py
|
||||
import dash
|
||||
from dash import Input, Output, State, dcc
|
||||
from flask import session
|
||||
from utils.db import execute_query, execute_non_query
|
||||
|
||||
@dash.callback(
|
||||
Output("settings-feedback", "children"),
|
||||
Input("btn-save-settings", "n_clicks"),
|
||||
State("input-default-volume", "value"),
|
||||
prevent_initial_call=True
|
||||
)
|
||||
def save_settings(n_clicks, volume):
|
||||
if "role" not in session:
|
||||
return dcc.Location(href="/login")
|
||||
if n_clicks and n_clicks > 0:
|
||||
sql = "UPDATE global_settings SET value=%s WHERE key='default_volume'"
|
||||
rc = execute_non_query(sql, (volume,))
|
||||
return "Einstellungen gespeichert." if rc else "Speichern fehlgeschlagen."
|
||||
return ""
|
||||
31
dashboard/callbacks/ui_callbacks.py
Normal file
31
dashboard/callbacks/ui_callbacks.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# dashboard/callbacks/ui_callbacks.py
|
||||
|
||||
from dash import Input, Output, State, callback
|
||||
from components.sidebar import Sidebar
|
||||
|
||||
# 1) Toggle-Callback: Umschalten von collapsed = False ↔ True
|
||||
@callback(
|
||||
Output("sidebar-state", "data"),
|
||||
Input("btn-toggle-sidebar", "n_clicks"),
|
||||
State("sidebar-state", "data"),
|
||||
prevent_initial_call=True
|
||||
)
|
||||
def toggle_sidebar(n_clicks, state):
|
||||
# Wenn der Button geklickt wurde, invertiere den collapsed-Wert
|
||||
collapsed = state.get("collapsed", False)
|
||||
return {"collapsed": not collapsed}
|
||||
|
||||
|
||||
# 2) Render-Callback: Zeichnet die Sidebar neu und setzt die CSS-Klasse
|
||||
@callback(
|
||||
[Output("sidebar", "children"), Output("sidebar", "className")],
|
||||
Input("sidebar-state", "data")
|
||||
)
|
||||
def render_sidebar(state):
|
||||
collapsed = state.get("collapsed", False)
|
||||
sidebar_class = "sidebar collapsed" if collapsed else "sidebar"
|
||||
|
||||
# Sidebar() gibt jetzt nur den Inhalt zurück
|
||||
sidebar_content = Sidebar(collapsed=collapsed)
|
||||
|
||||
return sidebar_content, sidebar_class
|
||||
24
dashboard/callbacks/users_callbacks.py
Normal file
24
dashboard/callbacks/users_callbacks.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# dashboard/callbacks/users_callbacks.py
|
||||
import dash
|
||||
from dash import Input, Output, State, dcc
|
||||
from flask import session
|
||||
from utils.db import execute_query, execute_non_query
|
||||
from utils.auth import hash_password
|
||||
|
||||
@dash.callback(
|
||||
Output("users-feedback", "children"),
|
||||
Input("btn-new-user", "n_clicks"),
|
||||
State("input-new-username", "value"),
|
||||
State("input-new-password", "value"),
|
||||
State("input-new-role", "value"),
|
||||
prevent_initial_call=True
|
||||
)
|
||||
def create_user(n_clicks, uname, pwd, role):
|
||||
if session.get("role") != "admin":
|
||||
return "Keine Berechtigung."
|
||||
if n_clicks and n_clicks > 0:
|
||||
pwd_hash = hash_password(pwd)
|
||||
sql = "INSERT INTO users (username, pwd_hash, role) VALUES (%s, %s, %s)"
|
||||
rc = execute_non_query(sql, (uname, pwd_hash, role))
|
||||
return "Benutzer erstellt." if rc else "Fehler beim Erstellen."
|
||||
return ""
|
||||
Reference in New Issue
Block a user