From 086b21dc2b385a58e6af9d8b023bebbaa5713fc5 Mon Sep 17 00:00:00 2001 From: Christian Mueller Date: Fri, 21 Aug 2026 08:12:42 +0000 Subject: [PATCH] feat: Zentrale per Durchwahl selbst besetzen Das Busylight im Empfangsbereich wird wechselnd besetzt. Mitarbeiter koennen sich jetzt selbst darauf setzen, ohne Admin-Passwort. - Neue Seite /zentrale (Durchwahl eingeben) und /zentrale/{durchwahl} als Direktlink fuer QR-Codes. Der Direktlink zeigt nur an und aendert nichts, damit Link-Vorschau und Prefetch niemanden versetzen. - Neue Tabelle home_assignments merkt das eigene Tuerschild, solange der Mitarbeiter in der Zentrale sitzt. Beim Verlassen wird es wiederhergestellt, das Zentral-Segment wird freigegeben. - Der Vorgaenger bleibt ohne Zuordnung - er ist nicht da. Sein gemerkter Heimatplatz bleibt erhalten, er kann jederzeit zurueckkehren. - Ist der Heimatplatz inzwischen belegt, wird die Rueckkehr abgelehnt und nichts geaendert. - Eine ausdrueckliche Admin-Zuordnung verwirft den gemerkten Heimatplatz. Ein reines Loeschen der Zuordnung nicht, sonst kaeme niemand mehr von der Zentrale zurueck. - Farbe wird sofort ans Geraet geschickt (scheduler.push_user_light). Das Polling allein reicht nicht: _process_user sendet nur bei einer Statusaenderung, und beim Platzwechsel bleibt der Status gleich. - Neue Settings selfservice_device_id und selfservice_segment, einstellbar unter Einstellungen. - Jeder Wechsel landet im Protokoll (zentrale / zentrale_frei). Die Durchwahl ist eine Kennung, kein Geheimnis - im README dokumentiert. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 66 +++++++++- app.py | 258 +++++++++++++++++++++++++++++++++++++++- database.py | 10 ++ scheduler.py | 61 ++++++++++ templates/settings.html | 37 +++++- templates/zentrale.html | 125 +++++++++++++++++++ 6 files changed, 554 insertions(+), 3 deletions(-) create mode 100644 templates/zentrale.html diff --git a/README.md b/README.md index fa1aff2..0d48406 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Jedes WLED-Geraet verfuegt ueber zwei Segmente (oben/unten), die jeweils einem B - **Telefon-Webhook** fuer Telefonanlage (callstart/callend auf Port 8081) - **Modulare Zeiterfassung** per CSV oder REST API - kompatibel mit jeder Zeiterfassung - **Webbasierte Azure-Authentifizierung** direkt im Browser (kein Terminal noetig) +- **Zentrale (Self-Service)** - wechselnde Besetzung des Empfangs-Busylights per Durchwahl, ohne Admin-Passwort - **Einstellungen** fuer Azure, Polling-Intervall und Zeiterfassung - **Protokoll** der letzten Statusaenderungen @@ -174,6 +175,59 @@ GET/POST http://SERVER:8081/{durchwahl}/callend - Anruf beendet (WLED -> zu - Waehrend eines aktiven Telefonats ueberschreibt das Teams-Polling die Farbe nicht - Die Telefon-Farbe (`phone_active`) ist unter **Farb-Regeln** konfigurierbar +## Zentrale (Self-Service) + +Ein Busylight im Empfangsbereich wird wechselnd besetzt. Damit die Mitarbeiter das +selbst umstellen koennen, gibt es eine eigene Seite, die **ohne Admin-Passwort** +erreichbar ist und die Durchwahl als Kennung nutzt. + +### Einrichtung + +1. Unter **Benutzer** die **Durchwahl** pro Mitarbeiter eintragen (dieselbe wie fuer den Telefon-Webhook) +2. Unter **Einstellungen > Zentrale (Self-Service)** das Busylight des Empfangs und das Segment auswaehlen +3. Die Seite `http://SERVER:8080/zentrale` an die Mitarbeiter geben + +### Bedienung + +``` +http://SERVER:8080/zentrale - Durchwahl eingeben und Zentrale uebernehmen +http://SERVER:8080/zentrale/{durchwahl} - Direktlink pro Person, z.B. als QR-Code +``` + +Der Direktlink zeigt nur den Stand an und aendert nichts - das Umstellen passiert +erst per Knopfdruck. + +### Verhalten + +- **Zentrale uebernehmen**: Der Mitarbeiter wird auf das Segment der Zentrale + gesetzt. Das eigene Tuerschild gibt er dabei ab, weil ein Mitarbeiter nur einer + Zuordnung gleichzeitig zugewiesen sein kann. Der alte Platz wird gemerkt und das + Segment ausgeschaltet. +- **Zurueck auf mein Tuerschild**: Der gemerkte Platz wird wiederhergestellt und die + Zentrale freigegeben (Farbe `abwesend`, Standard: aus). +- **Vorgaenger**: Wer die Zentrale abgibt, bleibt zunaechst ohne Zuordnung - er ist + ja nicht da. Sein gemerkter Heimatplatz bleibt erhalten, er kann jederzeit + zurueckkehren. +- **Belegter Heimatplatz**: Ist das eigene Tuerschild inzwischen jemand anderem + zugeordnet, wird die Rueckkehr abgelehnt und nichts geaendert. Dann muss der + Administrator die Zuordnung aufloesen. +- **Sofortiges Umschalten**: Die Farbe wird direkt ans Geraet geschickt. Das Polling + allein wuerde nicht reichen, weil es nur bei einer Statusaenderung sendet und der + Status des Mitarbeiters beim Platzwechsel unveraendert ist. +- **Protokoll**: Jeder Wechsel landet unter **Protokoll** (`zentrale` beim Uebernehmen, + `zentrale_frei` beim Freigeben). +- **Vorrang des Administrators**: Wird ein Mitarbeiter unter **Zuordnungen** neu + zugeordnet, verfaellt sein gemerkter Heimatplatz - die ausdrueckliche Zuordnung + gilt. Wird seine Zuordnung dort nur geloescht, bleibt der gemerkte Platz erhalten, + damit er von der Zentrale zurueckkehren kann. + +### Sicherheit + +Die Durchwahl ist eine Kennung, kein Geheimnis. Wer Port 8080 erreicht, kann jeden +Kollegen auf die Zentrale setzen. Fuer den internen Gebrauch ist das gewollt - die +Aenderung ist sofort sichtbar, im Protokoll nachvollziehbar und mit einem Klick +zurueckzunehmen. Die Seite gehoert deshalb nicht ins Internet. + ## Starten ### Manuell (zum Testen) @@ -238,7 +292,7 @@ busylight/ ├── docs/ # Dokumentation ├── static/ │ └── style.css # CSS Styles -└── templates/ # Jinja2 HTML-Templates +└── templates/ # Jinja2 HTML-Templates (u.a. zentrale.html) ``` ## Ports und Firewall @@ -274,5 +328,15 @@ busylight/ - Unter Tuerschilder den Status pruefen ("Alle jetzt pruefen") - Netzwerkverbindung pruefen: `curl http://HOSTNAME/json/info` +### Zentrale-Seite sagt "noch nicht eingerichtet" + +Unter **Einstellungen > Zentrale (Self-Service)** ist kein Busylight ausgewaehlt. +Ohne diese Einstellung weiss die Anwendung nicht, welches Geraet der Empfang ist. + +### Durchwahl wird auf der Zentrale-Seite nicht erkannt + +Die Durchwahl muss unter **Benutzer** exakt so eingetragen sein wie eingegeben, und +der Benutzer muss **aktiv** sein. Inaktive Benutzer werden bewusst abgewiesen. + ### Nach git pull aendert sich nichts - Service neustarten: `sudo systemctl restart busylight` diff --git a/app.py b/app.py index 0acc598..a48c6de 100644 --- a/app.py +++ b/app.py @@ -469,6 +469,10 @@ async def create_assignment( "INSERT OR REPLACE INTO assignments (user_azure_id, wled_device_id, segment) VALUES (?, ?, ?)", (user_azure_id, wled_device_id, segment), ) + # Eine ausdrueckliche Admin-Zuordnung setzt den fuer die Zentrale gemerkten + # Heimatplatz ausser Kraft, sonst wuerde die Rueckkehr auf einen veralteten + # Platz zeigen. + conn.execute("DELETE FROM home_assignments WHERE user_azure_id=?", (user_azure_id,)) conn.commit() conn.close() return RedirectResponse("/assignments", status_code=303) @@ -477,12 +481,258 @@ async def create_assignment( @app.post("/api/assignments/{assignment_id}/delete") async def delete_assignment(assignment_id: int): conn = database.get_db() + # Der fuer die Zentrale gemerkte Heimatplatz bleibt hier absichtlich stehen: + # wer von der Zentrale entfernt wird, soll noch zurueckkehren koennen. conn.execute("DELETE FROM assignments WHERE id=?", (assignment_id,)) conn.commit() conn.close() return RedirectResponse("/assignments", status_code=303) +# ── Zentrale (Self-Service per Durchwahl) ────────────────────── + +def _selfservice_target(conn): + """Konfiguriertes Zentral-Geraet + Segment, oder None wenn nicht eingerichtet.""" + raw_id = database.get_setting("selfservice_device_id", "") + if not raw_id: + return None + try: + device_id = int(raw_id) + except (TypeError, ValueError): + return None + + dev = conn.execute( + "SELECT id, hostname, name FROM wled_devices WHERE id = ?", (device_id,) + ).fetchone() + if not dev: + return None + + try: + segment = int(database.get_setting("selfservice_segment", "0")) + except (TypeError, ValueError): + segment = 0 + + return { + "id": dev["id"], + "hostname": dev["hostname"], + "name": dev["name"] or dev["hostname"], + "segment": segment, + } + + +def _user_by_extension(extension, conn): + """Aktiven Benutzer anhand der Telefon-Durchwahl finden.""" + ext = (extension or "").strip() + if not ext: + return None + return conn.execute(""" + SELECT azure_id, display_name, phone_extension + FROM users + WHERE active = 1 AND TRIM(phone_extension) = ? + """, (ext,)).fetchone() + + +def _zentrale_view(request, conn, target, extension="", user=None, error=None, message=None): + """Kontext fuer die Self-Service-Seite zusammenbauen.""" + occupant = None + if target: + occupant = conn.execute(""" + SELECT u.display_name + FROM assignments a JOIN users u ON u.azure_id = a.user_azure_id + WHERE a.wled_device_id = ? AND a.segment = ? + """, (target["id"], target["segment"])).fetchone() + + home = None + is_here = False + if user: + home = conn.execute(""" + SELECT h.segment, d.name, d.hostname + FROM home_assignments h JOIN wled_devices d ON d.id = h.wled_device_id + WHERE h.user_azure_id = ? + """, (user["azure_id"],)).fetchone() + if target: + is_here = conn.execute(""" + SELECT 1 FROM assignments + WHERE user_azure_id = ? AND wled_device_id = ? AND segment = ? + """, (user["azure_id"], target["id"], target["segment"])).fetchone() is not None + + return templates.TemplateResponse(request, "zentrale.html", { + "page": "zentrale", + "target": target, + "occupant": occupant["display_name"] if occupant else None, + "extension": extension, + "user": user, + "home": home, + "is_here": is_here, + "error": error, + "message": message, + }) + + +@app.get("/zentrale", response_class=HTMLResponse) +async def zentrale_page(request: Request): + """Self-Service-Seite fuer den Empfangsbereich. Absichtlich ohne Admin-Login.""" + conn = database.get_db() + try: + return _zentrale_view(request, conn, _selfservice_target(conn)) + finally: + conn.close() + + +@app.get("/zentrale/{extension}", response_class=HTMLResponse) +async def zentrale_page_for_extension(request: Request, extension: str): + """Direktlink pro Mitarbeiter (z.B. als QR-Code). Zeigt nur an, aendert nichts.""" + conn = database.get_db() + try: + target = _selfservice_target(conn) + user = _user_by_extension(extension, conn) + error = None if user else f"Durchwahl {extension} ist keinem aktiven Benutzer zugeordnet." + return _zentrale_view(request, conn, target, extension=extension, user=user, error=error) + finally: + conn.close() + + +@app.post("/api/zentrale/uebernehmen", response_class=HTMLResponse) +async def zentrale_takeover(request: Request, extension: str = Form(...)): + """Mitarbeiter setzt sich selbst auf das Busylight der Zentrale.""" + conn = database.get_db() + try: + target = _selfservice_target(conn) + if not target: + return _zentrale_view(request, conn, target, extension=extension, + error="Die Zentrale ist noch nicht eingerichtet. Bitte den Administrator informieren.") + + user = _user_by_extension(extension, conn) + if not user: + return _zentrale_view(request, conn, target, extension=extension, + error=f"Durchwahl {extension} ist keinem aktiven Benutzer zugeordnet.") + + # Sitzt der Benutzer schon dort? Dann nur das Licht nachziehen. + already = conn.execute(""" + SELECT 1 FROM assignments + WHERE user_azure_id = ? AND wled_device_id = ? AND segment = ? + """, (user["azure_id"], target["id"], target["segment"])).fetchone() + if already: + scheduler.push_user_light(user["azure_id"], user["display_name"], + target["hostname"], target["segment"], conn) + return _zentrale_view(request, conn, target, extension=extension, user=user, + message=f"{user['display_name']} ist bereits auf der Zentrale eingetragen.") + + # Bisheriger Platz des Benutzers wird als Heimat-Tuerschild gemerkt + own = conn.execute(""" + SELECT a.wled_device_id, a.segment, d.hostname, d.name + FROM assignments a JOIN wled_devices d ON d.id = a.wled_device_id + WHERE a.user_azure_id = ? + """, (user["azure_id"],)).fetchone() + + predecessor = conn.execute(""" + SELECT u.display_name + FROM assignments a JOIN users u ON u.azure_id = a.user_azure_id + WHERE a.wled_device_id = ? AND a.segment = ? + """, (target["id"], target["segment"])).fetchone() + + if own: + conn.execute(""" + INSERT OR REPLACE INTO home_assignments (user_azure_id, wled_device_id, segment) + VALUES (?, ?, ?) + """, (user["azure_id"], own["wled_device_id"], own["segment"])) + + # Zentral-Segment freimachen, eigene Zuordnung loesen, neu eintragen. + # Beide DELETEs sind noetig: UNIQUE(wled_device_id, segment) und UNIQUE(user_azure_id). + conn.execute("DELETE FROM assignments WHERE wled_device_id = ? AND segment = ?", + (target["id"], target["segment"])) + conn.execute("DELETE FROM assignments WHERE user_azure_id = ?", (user["azure_id"],)) + conn.execute(""" + INSERT INTO assignments (user_azure_id, wled_device_id, segment) VALUES (?, ?, ?) + """, (user["azure_id"], target["id"], target["segment"])) + conn.commit() + + # Licht sofort setzen - das Polling wuerde bei unveraendertem Status nichts senden + scheduler.push_user_light(user["azure_id"], user["display_name"], + target["hostname"], target["segment"], conn) + if own: + scheduler.clear_segment(own["hostname"], own["segment"], conn) + + logger.info("Zentrale uebernommen von %s (Durchwahl %s), Vorgaenger: %s", + user["display_name"], extension, + predecessor["display_name"] if predecessor else "-") + + msg = f"{user['display_name']} sitzt jetzt auf der Zentrale ({target['name']})." + if own: + msg += f" Das eigene Tuerschild ({own['name'] or own['hostname']}) ist bis zur Rueckkehr ausgeschaltet." + return _zentrale_view(request, conn, target, extension=extension, user=user, message=msg) + finally: + conn.close() + + +@app.post("/api/zentrale/verlassen", response_class=HTMLResponse) +async def zentrale_release(request: Request, extension: str = Form(...)): + """Mitarbeiter verlaesst die Zentrale und geht auf sein Heimat-Tuerschild zurueck.""" + conn = database.get_db() + try: + target = _selfservice_target(conn) + user = _user_by_extension(extension, conn) + if not user: + return _zentrale_view(request, conn, target, extension=extension, + error=f"Durchwahl {extension} ist keinem aktiven Benutzer zugeordnet.") + + was_here = bool(target) and conn.execute(""" + SELECT 1 FROM assignments + WHERE user_azure_id = ? AND wled_device_id = ? AND segment = ? + """, (user["azure_id"], target["id"], target["segment"])).fetchone() is not None + + home = conn.execute(""" + SELECT h.wled_device_id, h.segment, d.hostname, d.name + FROM home_assignments h JOIN wled_devices d ON d.id = h.wled_device_id + WHERE h.user_azure_id = ? + """, (user["azure_id"],)).fetchone() + + if not home: + if not was_here: + return _zentrale_view(request, conn, target, extension=extension, user=user, + error="Es ist kein Heimat-Tuerschild gespeichert und du sitzt nicht auf der Zentrale.") + # Nur von der Zentrale abmelden, es gibt keinen Platz zum Zurueckgehen + conn.execute("DELETE FROM assignments WHERE user_azure_id = ?", (user["azure_id"],)) + conn.commit() + scheduler.clear_segment(target["hostname"], target["segment"], conn) + logger.info("Zentrale verlassen von %s (kein Heimat-Tuerschild)", user["display_name"]) + return _zentrale_view(request, conn, target, extension=extension, user=user, + message=f"{user['display_name']} ist von der Zentrale abgemeldet. Ein eigenes Tuerschild war nicht gespeichert.") + + # Ist der Heimatplatz inzwischen von jemand anderem belegt? + blocker = conn.execute(""" + SELECT u.display_name + FROM assignments a JOIN users u ON u.azure_id = a.user_azure_id + WHERE a.wled_device_id = ? AND a.segment = ? AND a.user_azure_id != ? + """, (home["wled_device_id"], home["segment"], user["azure_id"])).fetchone() + if blocker: + return _zentrale_view(request, conn, target, extension=extension, user=user, + error=(f"Das eigene Tuerschild ({home['name'] or home['hostname']}, Segment {home['segment']}) " + f"ist inzwischen {blocker['display_name']} zugeordnet. " + "Bitte den Administrator ansprechen - es wurde nichts geaendert.")) + + conn.execute("DELETE FROM assignments WHERE user_azure_id = ?", (user["azure_id"],)) + conn.execute(""" + INSERT INTO assignments (user_azure_id, wled_device_id, segment) VALUES (?, ?, ?) + """, (user["azure_id"], home["wled_device_id"], home["segment"])) + conn.execute("DELETE FROM home_assignments WHERE user_azure_id = ?", (user["azure_id"],)) + conn.commit() + + scheduler.push_user_light(user["azure_id"], user["display_name"], + home["hostname"], home["segment"], conn) + if was_here: + scheduler.clear_segment(target["hostname"], target["segment"], conn) + + logger.info("Zentrale verlassen von %s, zurueck auf %s Segment %s", + user["display_name"], home["hostname"], home["segment"]) + + return _zentrale_view(request, conn, target, extension=extension, user=user, + message=(f"{user['display_name']} ist zurueck auf dem eigenen Tuerschild " + f"({home['name'] or home['hostname']}, Segment {home['segment']}).")) + finally: + conn.close() + + # ── Color Rules ──────────────────────────────────────────────────────────── @app.get("/colors", response_class=HTMLResponse) @@ -548,7 +798,12 @@ async def settings_page(request: Request): redir = _require_login(request) if redir: return redir settings = database.get_all_settings() - return templates.TemplateResponse(*_ctx(request, "settings", settings=settings)) + conn = database.get_db() + devices = conn.execute( + "SELECT id, hostname, name, segment_count FROM wled_devices ORDER BY name" + ).fetchall() + conn.close() + return templates.TemplateResponse(*_ctx(request, "settings", settings=settings, devices=devices)) @app.post("/api/settings") @@ -559,6 +814,7 @@ async def update_settings(request: Request): "azure_auth_tenant", "azure_scopes", "poll_interval", "attendance_csv_path", "attendance_status_anwesend", "attendance_status_abwesend", "attendance_status_homeoffice", + "selfservice_device_id", "selfservice_segment", ] for key in keys: value = form.get(key, "") diff --git a/database.py b/database.py index 797802d..ff8db14 100644 --- a/database.py +++ b/database.py @@ -49,6 +49,14 @@ def init_db(): UNIQUE(user_azure_id) ); + -- Heimat-Tuerschild: gemerkte Zuordnung, waehrend der Nutzer in der Zentrale sitzt + CREATE TABLE IF NOT EXISTS home_assignments ( + user_azure_id TEXT PRIMARY KEY REFERENCES users(azure_id) ON DELETE CASCADE, + wled_device_id INTEGER NOT NULL REFERENCES wled_devices(id) ON DELETE CASCADE, + segment INTEGER NOT NULL DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + CREATE TABLE IF NOT EXISTS color_rules ( status TEXT PRIMARY KEY, r INTEGER NOT NULL DEFAULT 0, @@ -140,6 +148,8 @@ def init_db(): "attendance_status_anwesend": "~~1~~", "attendance_status_abwesend": "~~0~~", "attendance_status_homeoffice": "~~2~~", + "selfservice_device_id": "", + "selfservice_segment": "0", } for key, value in defaults.items(): conn.execute( diff --git a/scheduler.py b/scheduler.py index f0939fb..7528c05 100644 --- a/scheduler.py +++ b/scheduler.py @@ -72,6 +72,67 @@ def get_color_for_status(status, conn): return {"r": 200, "g": 200, "b": 200, "effect": ""} +def current_color_for_user(azure_id, conn): + """Farbe fuer den zuletzt gespeicherten Status eines Users. + + Prioritaet wie im Polling und im Telefon-Webhook: Telefon > Teams > Zeiterfassung. + Wer laut Zeiterfassung nicht anwesend/homeoffice ist, bekommt die abwesend-Farbe. + """ + row = conn.execute(""" + SELECT last_attendance_status, last_teams_status, + COALESCE(phone_active, 0) as phone_active + FROM state WHERE user_azure_id = ? + """, (azure_id,)).fetchone() + + attendance = (row["last_attendance_status"] or "") if row else "" + teams = (row["last_teams_status"] or "") if row else "" + phone_active = row["phone_active"] if row else 0 + + if attendance not in ("anwesend", "homeoffice"): + return get_color_for_status("abwesend", conn) + if phone_active: + return get_color_for_status("phone_active", conn) + if attendance == "homeoffice": + return get_color_for_status("homeoffice", conn) + if teams: + return get_color_for_status(teams, conn) + return get_color_for_status("available", conn) + + +def push_user_light(azure_id, display_name, hostname, segment, conn, reason="zentrale"): + """Aktuelle Farbe eines Users sofort an WLED senden. + + Wird beim Platzwechsel gebraucht: _process_user schickt nur bei einer + Statusaenderung etwas ans Geraet, nach einem Wechsel ist der Status aber + unveraendert. Ohne diesen Push wuerde das Segment die Farbe des Vorgaengers + behalten, bis sich zufaellig etwas aendert. + """ + color = current_color_for_user(azure_id, conn) + url = build_wled_url( + hostname, color["r"], color["g"], color["b"], + segment=segment, effect=color.get("effect", ""), + ) + success, result_text = call_wled(url) + log_activity(azure_id, display_name, reason, "-", url, result_text) + if not success: + logger.warning("WLED Fehler beim Platzwechsel fuer %s: %s", display_name, result_text) + return success, result_text + + +def clear_segment(hostname, segment, conn, reason="zentrale_frei"): + """Segment auf die abwesend-Farbe setzen (Standard ist 0,0,0 = aus).""" + color = get_color_for_status("abwesend", conn) + url = build_wled_url( + hostname, color["r"], color["g"], color["b"], + segment=segment, effect=color.get("effect", ""), + ) + success, result_text = call_wled(url) + log_activity(None, "-", reason, "-", url, result_text) + if not success: + logger.warning("WLED Fehler beim Freigeben von %s Segment %s: %s", hostname, segment, result_text) + return success, result_text + + def poll_status(): """Main polling function - called periodically by APScheduler.""" global graph diff --git a/templates/settings.html b/templates/settings.html index 5189ebd..0f1a6c2 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -127,7 +127,7 @@ -
+
Zeiterfassung (CSV)
@@ -167,6 +167,41 @@
+ +
+
+
Zentrale (Self-Service)
+
+
+
+ + + Das Busylight im Empfangsbereich, das wechselnd besetzt wird. +
+
+ + + 0 = unten, 1 = oben +
+
+ + Self-Service-Seite
+ Mitarbeiter melden sich per Durchwahl an, ohne Admin-Passwort:
+ /zentrale
+ Direktlink bzw. QR-Code pro Person:
+ /zentrale/{durchwahl} +
+
+
+
diff --git a/templates/zentrale.html b/templates/zentrale.html new file mode 100644 index 0000000..e2a1cf2 --- /dev/null +++ b/templates/zentrale.html @@ -0,0 +1,125 @@ + + + + + + Zentrale - Busylight + + + + +
+
+
+
+
+
+ cynfo +
Zentrale besetzen
+
+ + {% if message %} +
+ {{ message }} +
+ {% endif %} + + {% if error %} +
+ {{ error }} +
+ {% endif %} + + {% if not target %} +
+ + + Die Zentrale ist noch nicht eingerichtet. Unter + Einstellungen > Zentrale muss ein Busylight + und ein Segment ausgewaehlt werden. + +
+ {% else %} + +
+
+
+ Busylight der Zentrale + {{ target.name }} + (Segment {{ target.segment }}) +
+
+
+ Aktuell besetzt von + {% if occupant %} + {{ occupant }} + {% else %} + niemand + {% endif %} +
+ + {% if user %} +
+ Durchwahl {{ user.phone_extension }} + {{ user.display_name }} +
+ + {% if is_here %} +
+ Du sitzt derzeit auf der Zentrale. +
+ {% endif %} + +
+ + +
+ +
+ + +
+ + {% if home %} +
+ + Gemerktes Tuerschild: {{ home.name or home.hostname }} (Segment {{ home.segment }}) + +
+ {% endif %} + + + {% else %} +
+
+ + + + Damit wird die eigene Person auf das Busylight der Zentrale gesetzt. + +
+ +
+ {% endif %} + {% endif %} + +
+ © cynfo GmbH +
+
+
+
+
+
+ +