fix: Zentrale-Seite zeigt den Zustand und den Weg zurueck
Zwei Handhabungsprobleme, die zusammengehoeren.
Auf /zentrale gab es ueberhaupt keinen Knopf zum Zurueckgeben - den fand
man nur ueber den Direktlink, den man nach einer Uebernahme in der Hand
hatte. Wer stattdessen /zentrale aufrief, konnte nur weiter uebernehmen
und sah nicht, dass erst zurueckgesetzt werden muss.
Ausserdem rendern die POST-Endpunkte ihr Ergebnis direkt. Man blieb also
auf /api/zentrale/uebernehmen stehen: eine Adresse, die beim Neuladen die
Aktion wiederholt und den Zustand nicht widerspiegelt.
Neu:
- Zwei Schritte. /zentrale fragt nur die Durchwahl ab und leitet auf
/zentrale/{durchwahl} um. Diese Zustandsseite ist die Adresse fuer den
Alltag, als Lesezeichen oder QR-Code, und ist gefahrlos neu ladbar.
- Die Knoepfe richten sich nach der Lage: frei -> besetzen, fremd besetzt
-> uebernehmen samt Hinweis was mit dieser Person passiert, selbst
besetzt -> zurueck auf mein Tuerschild, eigener Stammplatz -> kein
Knopf, nur der Hinweis dass nichts zu tun ist.
- Alle Aktionen antworten mit POST/Redirect/GET auf die Zustandsseite und
uebergeben nur einen kurzen Code fuer die Bestaetigung. Die Einzelheiten
- wer sitzt drauf, wer wartet, wer blockiert den Heimatplatz - leitet
die Seite bei jedem Aufruf frisch aus dem echten Zustand ab.
- Die allgemeine Seite nennt den aktuellen Besetzer und, falls vorhanden,
wer bei einer Freigabe automatisch zurueckkommt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -205,12 +205,30 @@ erreichbar ist und die Durchwahl als Kennung nutzt.
|
||||
### Bedienung
|
||||
|
||||
```
|
||||
http://SERVER:8080/zentrale - Durchwahl eingeben und Zentrale uebernehmen
|
||||
http://SERVER:8080/zentrale/{durchwahl} - Direktlink pro Person, z.B. als QR-Code
|
||||
http://SERVER:8080/zentrale - Durchwahl eingeben
|
||||
http://SERVER:8080/zentrale/{durchwahl} - Zustandsseite pro Person, als Lesezeichen
|
||||
oder QR-Code
|
||||
```
|
||||
|
||||
Der Direktlink zeigt nur den Stand an und aendert nichts - das Umstellen passiert
|
||||
erst per Knopfdruck.
|
||||
Die Bedienung laeuft in zwei Schritten. Auf `/zentrale` gibt man seine Durchwahl ein
|
||||
und landet auf der eigenen Zustandsseite `/zentrale/{durchwahl}`. Dort steht, wer
|
||||
gerade auf der Zentrale sitzt und welche Knoepfe zur Verfuegung stehen - geaendert
|
||||
wird erst per Klick.
|
||||
|
||||
Die Zustandsseite ist die eigentliche Adresse fuer den Alltag und als Lesezeichen
|
||||
gedacht. Sie zeigt immer den echten Zustand, passt die Knoepfe daran an und ist
|
||||
gefahrlos neu ladbar:
|
||||
|
||||
| Lage | Angebot |
|
||||
|------|---------|
|
||||
| Zentrale frei | **Zentrale besetzen** |
|
||||
| jemand anderes sitzt darauf | **Zentrale uebernehmen**, mit Hinweis was mit dieser Person passiert |
|
||||
| man sitzt selbst darauf | **Zurueck auf mein Tuerschild** |
|
||||
| es ist der eigene Stammplatz | kein Knopf, nur der Hinweis dass nichts zu tun ist |
|
||||
|
||||
Jede Aktion leitet auf diese Seite zurueck (POST/Redirect/GET). Man landet also nie
|
||||
auf einer Formular-Antwort, die beim Neuladen die Aktion wiederholen wuerde, und sieht
|
||||
nach jedem Klick den neuen Zustand samt Bestaetigung.
|
||||
|
||||
### Verhalten
|
||||
|
||||
@@ -353,6 +371,12 @@ busylight/
|
||||
- Unter Tuerschilder den Status pruefen ("Alle jetzt pruefen")
|
||||
- Netzwerkverbindung pruefen: `curl http://HOSTNAME/json/info`
|
||||
|
||||
### Auf /zentrale fehlt der Knopf zum Zurueckgeben
|
||||
|
||||
Der ist absichtlich erst auf der Zustandsseite `/zentrale/{durchwahl}` zu sehen, denn
|
||||
vorher weiss die Anwendung nicht, wer zurueckgeben will. Durchwahl eingeben, **Weiter**
|
||||
klicken - dann erscheint der passende Knopf.
|
||||
|
||||
### Zentrale-Seite sagt "noch nicht eingerichtet"
|
||||
|
||||
Unter **Einstellungen > Zentrale (Self-Service)** ist kein Busylight ausgewaehlt.
|
||||
|
||||
@@ -10,6 +10,7 @@ import shutil
|
||||
import sys
|
||||
from contextlib import asynccontextmanager
|
||||
from datetime import datetime
|
||||
from urllib.parse import quote
|
||||
|
||||
from fastapi import FastAPI, Request, Form, UploadFile, File, BackgroundTasks
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse, FileResponse, JSONResponse
|
||||
@@ -559,43 +560,6 @@ def _user_by_extension(extension, conn):
|
||||
""", (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,
|
||||
})
|
||||
|
||||
|
||||
def _zentrale_owner_row(conn, target):
|
||||
"""Heimateintrag, der auf das Zentral-Segment zeigt - der Stamm-Besetzer."""
|
||||
if not target:
|
||||
@@ -634,45 +598,122 @@ def _restore_zentrale_owner(conn, target):
|
||||
return owner
|
||||
|
||||
|
||||
def _zentrale_state(conn, target, user):
|
||||
"""Alles, was die Self-Service-Seite ueber die aktuelle Lage wissen muss.
|
||||
|
||||
Wird bei jedem GET frisch berechnet. Die Aktionen leiten deshalb nach dem
|
||||
Absenden auf /zentrale/{durchwahl} um: die Seite zeigt dann den echten
|
||||
Zustand und nicht das Ergebnis eines abgeschickten Formulars.
|
||||
"""
|
||||
st = {
|
||||
"occupant": None, "is_here": False, "home": None,
|
||||
"home_blocker": None, "is_owner": False, "owner_waiting": None,
|
||||
"occupant_is_owner": False,
|
||||
}
|
||||
if not target:
|
||||
return st
|
||||
|
||||
st["occupant"] = conn.execute("""
|
||||
SELECT u.azure_id, 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()
|
||||
|
||||
owner = _zentrale_owner_row(conn, target)
|
||||
if owner and (not st["occupant"] or owner["user_azure_id"] != st["occupant"]["azure_id"]):
|
||||
st["owner_waiting"] = owner["display_name"]
|
||||
|
||||
# Sitzt der aktuelle Besetzer auf seinem Stammplatz? Dann kommt er nach einer
|
||||
# Uebernahme von allein zurueck - das gehoert auf den Knopf geschrieben.
|
||||
if st["occupant"]:
|
||||
st["occupant_is_owner"] = conn.execute("""
|
||||
SELECT 1 FROM home_assignments
|
||||
WHERE user_azure_id = ? AND wled_device_id = ? AND segment = ?
|
||||
""", (st["occupant"]["azure_id"], target["id"], target["segment"])).fetchone() is not None
|
||||
|
||||
if not user:
|
||||
return st
|
||||
|
||||
st["is_here"] = bool(st["occupant"] and st["occupant"]["azure_id"] == user["azure_id"])
|
||||
|
||||
st["home"] = conn.execute("""
|
||||
SELECT h.wled_device_id, 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 st["home"]:
|
||||
st["is_owner"] = (st["home"]["wled_device_id"], st["home"]["segment"]) \
|
||||
== (target["id"], target["segment"])
|
||||
if not st["is_owner"]:
|
||||
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 != ?
|
||||
""", (st["home"]["wled_device_id"], st["home"]["segment"], user["azure_id"])).fetchone()
|
||||
st["home_blocker"] = blocker["display_name"] if blocker else None
|
||||
return st
|
||||
|
||||
|
||||
def _zentrale_page(request, conn, target, extension="", user=None, done=None):
|
||||
"""Self-Service-Seite rendern."""
|
||||
st = _zentrale_state(conn, target, user)
|
||||
return templates.TemplateResponse(request, "zentrale.html", {
|
||||
"page": "zentrale", "target": target, "extension": extension,
|
||||
"user": user, "done": done, **st,
|
||||
})
|
||||
|
||||
|
||||
def _zentrale_redirect(extension, done):
|
||||
"""Nach jeder Aktion zurueck auf die Zustandsseite (POST/Redirect/GET)."""
|
||||
ext = quote((extension or "").strip(), safe="")
|
||||
if not ext:
|
||||
return RedirectResponse(f"/zentrale?done={done}", status_code=303)
|
||||
return RedirectResponse(f"/zentrale/{ext}?done={done}", status_code=303)
|
||||
|
||||
|
||||
@app.get("/zentrale", response_class=HTMLResponse)
|
||||
async def zentrale_page(request: Request):
|
||||
"""Self-Service-Seite fuer den Empfangsbereich. Absichtlich ohne Admin-Login."""
|
||||
# Das Formular schickt die Durchwahl per GET - auf die Personenseite umleiten,
|
||||
# damit die Adresse merkbar und neu ladbar ist.
|
||||
typed = (request.query_params.get("durchwahl") or "").strip()
|
||||
if typed:
|
||||
return RedirectResponse(f"/zentrale/{quote(typed, safe='')}", status_code=303)
|
||||
|
||||
conn = database.get_db()
|
||||
try:
|
||||
return _zentrale_view(request, conn, _selfservice_target(conn))
|
||||
return _zentrale_page(request, conn, _selfservice_target(conn),
|
||||
done=request.query_params.get("done"))
|
||||
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."""
|
||||
"""Zustandsseite pro Mitarbeiter. Zeigt nur an, geaendert wird per Knopf."""
|
||||
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)
|
||||
done = request.query_params.get("done") if user else "err_unbekannt"
|
||||
return _zentrale_page(request, conn, target, extension=extension, user=user, done=done)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
@app.post("/api/zentrale/uebernehmen", response_class=HTMLResponse)
|
||||
async def zentrale_takeover(request: Request, extension: str = Form(...)):
|
||||
@app.post("/api/zentrale/uebernehmen")
|
||||
async def zentrale_takeover(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.")
|
||||
return _zentrale_redirect(extension, "err_nicht_eingerichtet")
|
||||
|
||||
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.")
|
||||
return _zentrale_redirect(extension, "err_unbekannt")
|
||||
|
||||
# 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 = ?
|
||||
@@ -680,8 +721,7 @@ async def zentrale_takeover(request: Request, extension: str = Form(...)):
|
||||
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.")
|
||||
return _zentrale_redirect(extension, "bereits")
|
||||
|
||||
# Bisheriger Platz des Benutzers wird als Heimat-Tuerschild gemerkt
|
||||
own = conn.execute("""
|
||||
@@ -715,8 +755,6 @@ async def zentrale_takeover(request: Request, extension: str = Form(...)):
|
||||
# er erst selbst "zurueck" klicken, bevor die Uebernahme fuer ihn stimmt - und
|
||||
# bis dahin haette er gar kein Licht.
|
||||
pred_back = None
|
||||
pred_blocked = None
|
||||
pred_waiting = False
|
||||
if predecessor and predecessor["azure_id"] != user["azure_id"]:
|
||||
pred_home = conn.execute("""
|
||||
SELECT h.wled_device_id, h.segment, d.hostname, d.name
|
||||
@@ -731,15 +769,13 @@ async def zentrale_takeover(request: Request, extension: str = Form(...)):
|
||||
# Sein Stammplatz IST die Zentrale, die jetzt der Nachfolger hat.
|
||||
# Der Heimateintrag bleibt stehen, damit er zurueckkommt, sobald
|
||||
# die Zentrale wieder frei wird.
|
||||
pred_waiting = True
|
||||
pass
|
||||
elif pred_home:
|
||||
taken = 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 = ?
|
||||
SELECT 1 FROM assignments
|
||||
WHERE wled_device_id = ? AND segment = ?
|
||||
""", (pred_home["wled_device_id"], pred_home["segment"])).fetchone()
|
||||
if taken:
|
||||
pred_blocked = taken["display_name"]
|
||||
else:
|
||||
if not taken:
|
||||
conn.execute("""
|
||||
INSERT INTO assignments (user_azure_id, wled_device_id, segment) VALUES (?, ?, ?)
|
||||
""", (predecessor["azure_id"], pred_home["wled_device_id"], pred_home["segment"]))
|
||||
@@ -758,7 +794,6 @@ async def zentrale_takeover(request: Request, extension: str = Form(...)):
|
||||
INSERT OR REPLACE INTO home_assignments (user_azure_id, wled_device_id, segment)
|
||||
VALUES (?, ?, ?)
|
||||
""", (predecessor["azure_id"], target["id"], target["segment"]))
|
||||
pred_waiting = True
|
||||
|
||||
conn.commit()
|
||||
|
||||
@@ -777,44 +812,23 @@ async def zentrale_takeover(request: Request, extension: str = Form(...)):
|
||||
scheduler.push_user_light(predecessor["azure_id"], predecessor["display_name"],
|
||||
pred_back["hostname"], pred_back["segment"], conn)
|
||||
|
||||
logger.info("Zentrale uebernommen von %s (Durchwahl %s), Vorgaenger: %s (%s)",
|
||||
logger.info("Zentrale uebernommen von %s (Durchwahl %s), Vorgaenger: %s",
|
||||
user["display_name"], extension,
|
||||
predecessor["display_name"] if predecessor else "-",
|
||||
"zurueck auf eigenem Tuerschild" if pred_back
|
||||
else f"Tuerschild belegt von {pred_blocked}" if pred_blocked
|
||||
else "wartet auf die Zentrale" if pred_waiting
|
||||
else "kein Tuerschild gemerkt")
|
||||
|
||||
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."
|
||||
if pred_back:
|
||||
msg += (f" {predecessor['display_name']} wurde auf das eigene Tuerschild "
|
||||
f"({pred_back['name'] or pred_back['hostname']}) zurueckgesetzt.")
|
||||
elif pred_blocked:
|
||||
msg += (f" {predecessor['display_name']} konnte nicht zurueckgesetzt werden, "
|
||||
f"weil das eigene Tuerschild inzwischen {pred_blocked} zugeordnet ist.")
|
||||
elif pred_waiting:
|
||||
msg += (f" {predecessor['display_name']} gehoert auf die Zentrale und kommt automatisch "
|
||||
"zurueck, sobald sie wieder freigegeben wird.")
|
||||
elif predecessor and predecessor["azure_id"] != user["azure_id"]:
|
||||
msg += (f" Fuer {predecessor['display_name']} war kein eigenes Tuerschild gemerkt, "
|
||||
"er ist jetzt ohne Zuordnung.")
|
||||
return _zentrale_view(request, conn, target, extension=extension, user=user, message=msg)
|
||||
predecessor["display_name"] if predecessor else "-")
|
||||
return _zentrale_redirect(extension, "uebernommen")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
@app.post("/api/zentrale/verlassen", response_class=HTMLResponse)
|
||||
async def zentrale_release(request: Request, extension: str = Form(...)):
|
||||
@app.post("/api/zentrale/verlassen")
|
||||
async def zentrale_release(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.")
|
||||
return _zentrale_redirect(extension, "err_unbekannt")
|
||||
|
||||
was_here = bool(target) and conn.execute("""
|
||||
SELECT 1 FROM assignments
|
||||
@@ -829,8 +843,7 @@ async def zentrale_release(request: Request, extension: str = Form(...)):
|
||||
|
||||
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.")
|
||||
return _zentrale_redirect(extension, "err_nichts_zu_tun")
|
||||
# Nur von der Zentrale abmelden, es gibt keinen Platz zum Zurueckgehen
|
||||
conn.execute("DELETE FROM assignments WHERE user_azure_id = ?", (user["azure_id"],))
|
||||
returning = _restore_zentrale_owner(conn, target)
|
||||
@@ -840,35 +853,21 @@ async def zentrale_release(request: Request, extension: str = Form(...)):
|
||||
target["hostname"], target["segment"], conn)
|
||||
else:
|
||||
scheduler.clear_segment(target["hostname"], target["segment"], conn)
|
||||
logger.info("Zentrale verlassen von %s (kein Heimat-Tuerschild), zurueck auf der Zentrale: %s",
|
||||
logger.info("Zentrale verlassen von %s (kein Heimat-Tuerschild), zurueck: %s",
|
||||
user["display_name"], returning["display_name"] if returning else "-")
|
||||
msg = f"{user['display_name']} ist von der Zentrale abgemeldet. Ein eigenes Tuerschild war nicht gespeichert."
|
||||
if returning:
|
||||
msg += f" {returning['display_name']} sitzt wieder auf der Zentrale."
|
||||
return _zentrale_view(request, conn, target, extension=extension, user=user, message=msg)
|
||||
return _zentrale_redirect(extension, "abgemeldet")
|
||||
|
||||
# Der Stamm-Besetzer der Zentrale hat keinen anderen Platz, auf den er
|
||||
# zurueckgehen koennte - sein Heimatplatz IST die Zentrale.
|
||||
# Der Stamm-Besetzer hat keinen anderen Platz - sein Heimatplatz IST die Zentrale
|
||||
if target and (home["wled_device_id"], home["segment"]) == (target["id"], target["segment"]):
|
||||
if was_here:
|
||||
return _zentrale_view(request, conn, target, extension=extension, user=user,
|
||||
message=("Die Zentrale ist dein eigener Platz - du sitzt schon dort. "
|
||||
"Es gibt nichts zurueckzusetzen."))
|
||||
return _zentrale_view(request, conn, target, extension=extension, user=user,
|
||||
error=("Die Zentrale ist dein eigener Platz, aktuell hat sie jemand anderes. "
|
||||
"Mit \"Zentrale uebernehmen\" holst du sie zurueck."))
|
||||
return _zentrale_redirect(extension, "stammplatz_hier" if was_here else "err_stammplatz_fremd")
|
||||
|
||||
# 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 != ?
|
||||
SELECT 1 FROM assignments
|
||||
WHERE wled_device_id = ? AND segment = ? AND 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."))
|
||||
return _zentrale_redirect(extension, "err_heimat_belegt")
|
||||
|
||||
conn.execute("DELETE FROM assignments WHERE user_azure_id = ?", (user["azure_id"],))
|
||||
conn.execute("""
|
||||
@@ -892,12 +891,7 @@ async def zentrale_release(request: Request, extension: str = Form(...)):
|
||||
logger.info("Zentrale verlassen von %s, zurueck auf %s Segment %s; Zentrale nun: %s",
|
||||
user["display_name"], home["hostname"], home["segment"],
|
||||
returning["display_name"] if returning else "frei")
|
||||
|
||||
msg = (f"{user['display_name']} ist zurueck auf dem eigenen Tuerschild "
|
||||
f"({home['name'] or home['hostname']}, Segment {home['segment']}).")
|
||||
if returning:
|
||||
msg += f" {returning['display_name']} sitzt wieder auf der Zentrale."
|
||||
return _zentrale_view(request, conn, target, extension=extension, user=user, message=msg)
|
||||
return _zentrale_redirect(extension, "zurueck")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
+192
-111
@@ -8,118 +8,199 @@
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center mt-4 mb-5">
|
||||
<div class="col-md-5">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<div class="text-center mb-4">
|
||||
<img src="{{ logo_url() }}" alt="Logo" style="height: 40px;" class="mb-2">
|
||||
<h5 class="mb-0">Zentrale besetzen</h5>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center mt-4 mb-5">
|
||||
<div class="col-md-5">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
|
||||
{% if message %}
|
||||
<div class="alert alert-success py-2">
|
||||
<small><i class="bi bi-check-circle"></i> {{ message }}</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if error %}
|
||||
<div class="alert alert-danger py-2">
|
||||
<small><i class="bi bi-exclamation-triangle"></i> {{ error }}</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not target %}
|
||||
<div class="alert alert-warning py-2 mb-0">
|
||||
<small>
|
||||
<i class="bi bi-gear"></i>
|
||||
Die Zentrale ist noch nicht eingerichtet. Unter
|
||||
<strong>Einstellungen > Zentrale</strong> muss ein Busylight
|
||||
und ein Segment ausgewaehlt werden.
|
||||
</small>
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
<div class="border rounded p-3 mb-4 bg-white">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<small class="text-muted d-block">Busylight der Zentrale</small>
|
||||
<strong>{{ target.name }}</strong>
|
||||
<small class="text-muted">(Segment {{ target.segment }})</small>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-2">
|
||||
<small class="text-muted d-block">Aktuell besetzt von</small>
|
||||
{% if occupant %}
|
||||
<strong class="text-success"><i class="bi bi-person-fill"></i> {{ occupant }}</strong>
|
||||
{% else %}
|
||||
<span class="text-muted"><i class="bi bi-person-dash"></i> niemand</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if user %}
|
||||
<div class="text-center mb-3">
|
||||
<small class="text-muted d-block">Durchwahl {{ user.phone_extension }}</small>
|
||||
<strong>{{ user.display_name }}</strong>
|
||||
</div>
|
||||
|
||||
{% if is_here %}
|
||||
<div class="alert alert-info py-2">
|
||||
<small><i class="bi bi-info-circle"></i> Du sitzt derzeit auf der Zentrale.</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="/api/zentrale/uebernehmen" class="mb-2">
|
||||
<input type="hidden" name="extension" value="{{ user.phone_extension }}">
|
||||
<button type="submit" class="btn btn-primary w-100" {% if is_here %}disabled{% endif %}>
|
||||
<i class="bi bi-box-arrow-in-right"></i> Zentrale uebernehmen
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="/api/zentrale/verlassen">
|
||||
<input type="hidden" name="extension" value="{{ user.phone_extension }}">
|
||||
<button type="submit" class="btn btn-outline-secondary w-100">
|
||||
<i class="bi bi-box-arrow-left"></i> Zurueck auf mein Tuerschild
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% if home %}
|
||||
<div class="text-center mt-3">
|
||||
<small class="text-muted">
|
||||
Gemerktes Tuerschild: {{ home.name or home.hostname }} (Segment {{ home.segment }})
|
||||
</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="text-center mt-3">
|
||||
<a href="/zentrale" class="text-muted small">Andere Durchwahl</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<form method="post" action="/api/zentrale/uebernehmen">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Eigene Telefon-Durchwahl</label>
|
||||
<input type="text" name="extension" class="form-control form-control-lg text-center"
|
||||
inputmode="numeric" autocomplete="off" autofocus required
|
||||
value="{{ extension }}" placeholder="z.B. 123">
|
||||
<small class="text-muted">
|
||||
Damit wird die eigene Person auf das Busylight der Zentrale gesetzt.
|
||||
</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
<i class="bi bi-box-arrow-in-right"></i> Zentrale uebernehmen
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<small class="text-secondary" style="font-size:0.65rem">© cynfo GmbH</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mb-4">
|
||||
<img src="{{ logo_url() }}" alt="Logo" style="height: 40px;" class="mb-2">
|
||||
<h5 class="mb-0">Zentrale besetzen</h5>
|
||||
</div>
|
||||
|
||||
{# ── Rueckmeldung der letzten Aktion ───────────────────────────────── #}
|
||||
{% if done == 'uebernommen' %}
|
||||
<div class="alert alert-success py-2"><small><i class="bi bi-check-circle"></i>
|
||||
Du sitzt jetzt auf der Zentrale.</small></div>
|
||||
{% elif done == 'bereits' %}
|
||||
<div class="alert alert-info py-2"><small><i class="bi bi-info-circle"></i>
|
||||
Du warst schon auf der Zentrale eingetragen. Das Licht wurde neu gesetzt.</small></div>
|
||||
{% elif done == 'zurueck' %}
|
||||
<div class="alert alert-success py-2"><small><i class="bi bi-check-circle"></i>
|
||||
Zurueck auf dem eigenen Tuerschild.</small></div>
|
||||
{% elif done == 'abgemeldet' %}
|
||||
<div class="alert alert-success py-2"><small><i class="bi bi-check-circle"></i>
|
||||
Von der Zentrale abgemeldet.</small></div>
|
||||
{% elif done == 'stammplatz_hier' %}
|
||||
<div class="alert alert-info py-2"><small><i class="bi bi-info-circle"></i>
|
||||
Die Zentrale ist dein eigener Platz - es gibt nichts zurueckzusetzen.</small></div>
|
||||
{% elif done == 'err_unbekannt' %}
|
||||
<div class="alert alert-danger py-2"><small><i class="bi bi-x-circle"></i>
|
||||
Die Durchwahl {{ extension }} ist keinem aktiven Benutzer zugeordnet.</small></div>
|
||||
{% elif done == 'err_nicht_eingerichtet' %}
|
||||
<div class="alert alert-warning py-2"><small><i class="bi bi-gear"></i>
|
||||
Die Zentrale ist noch nicht eingerichtet.</small></div>
|
||||
{% elif done == 'err_heimat_belegt' %}
|
||||
<div class="alert alert-danger py-2"><small><i class="bi bi-x-circle"></i>
|
||||
Dein eigenes Tuerschild ist inzwischen
|
||||
{%- if home_blocker %} {{ home_blocker }}{% else %} jemand anderem{% endif %} zugeordnet.
|
||||
Es wurde nichts geaendert - bitte den Administrator ansprechen.</small></div>
|
||||
{% elif done == 'err_nichts_zu_tun' %}
|
||||
<div class="alert alert-danger py-2"><small><i class="bi bi-x-circle"></i>
|
||||
Du sitzt nicht auf der Zentrale und es ist kein eigenes Tuerschild gemerkt.</small></div>
|
||||
{% elif done == 'err_stammplatz_fremd' %}
|
||||
<div class="alert alert-warning py-2"><small><i class="bi bi-exclamation-triangle"></i>
|
||||
Die Zentrale ist dein eigener Platz, aktuell hat sie jemand anderes.
|
||||
Mit "Zentrale uebernehmen" holst du sie zurueck.</small></div>
|
||||
{% endif %}
|
||||
|
||||
{% if not target %}
|
||||
<div class="alert alert-warning py-2 mb-0">
|
||||
<small><i class="bi bi-gear"></i>
|
||||
Die Zentrale ist noch nicht eingerichtet. Unter
|
||||
<strong>Einstellungen > Zentrale</strong> muss ein Busylight und ein
|
||||
Segment ausgewaehlt werden.</small>
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
{# ── Zustand der Zentrale, immer sichtbar ──────────────────────────── #}
|
||||
<div class="border rounded p-3 mb-3 bg-white">
|
||||
<small class="text-muted d-block">Busylight der Zentrale</small>
|
||||
<strong>{{ target.name }}</strong>
|
||||
<small class="text-muted">(Segment {{ target.segment }})</small>
|
||||
<hr class="my-2">
|
||||
{% if occupant %}
|
||||
<small class="text-muted d-block">Aktuell besetzt von</small>
|
||||
<strong class="{% if is_here %}text-primary{% else %}text-success{% endif %}">
|
||||
<i class="bi bi-person-fill"></i> {{ occupant.display_name }}
|
||||
{%- if is_here %} <span class="badge bg-primary">das bist du</span>{% endif %}
|
||||
</strong>
|
||||
{% if occupant_is_owner %}
|
||||
<div><small class="text-muted">Das ist der Stammplatz dieser Person.</small></div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<small class="text-muted d-block">Aktuell besetzt von</small>
|
||||
<span class="text-muted"><i class="bi bi-person-dash"></i> niemand - die Zentrale ist frei</span>
|
||||
{% endif %}
|
||||
{% if owner_waiting %}
|
||||
<div class="mt-2"><small class="text-muted">
|
||||
<i class="bi bi-hourglass-split"></i>
|
||||
{{ owner_waiting }} gehoert auf die Zentrale und kommt automatisch zurueck,
|
||||
sobald sie freigegeben wird.
|
||||
</small></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if not user %}
|
||||
{# ── Schritt 1: Wer bist du? ──────────────────────────────────── #}
|
||||
<form method="get" action="/zentrale">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Eigene Telefon-Durchwahl</label>
|
||||
<input type="text" name="durchwahl" class="form-control form-control-lg text-center"
|
||||
inputmode="numeric" autocomplete="off" autofocus required
|
||||
value="{{ extension }}" placeholder="z.B. 123">
|
||||
<small class="text-muted">
|
||||
Im naechsten Schritt siehst du, was du tun kannst - es wird noch nichts geaendert.
|
||||
</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
<i class="bi bi-arrow-right-circle"></i> Weiter
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
{# ── Schritt 2: Zustand und passende Aktionen ──────────────────── #}
|
||||
<div class="text-center mb-3">
|
||||
<small class="text-muted d-block">Durchwahl {{ user.phone_extension }}</small>
|
||||
<strong>{{ user.display_name }}</strong>
|
||||
</div>
|
||||
|
||||
{% if is_owner and is_here %}
|
||||
<div class="alert alert-info py-2">
|
||||
<small><i class="bi bi-house-check"></i>
|
||||
Die Zentrale ist dein eigener Platz und du sitzt darauf.
|
||||
Hier ist nichts zu tun - jemand anderes kann sie jederzeit uebernehmen,
|
||||
danach kommst du automatisch zurueck.</small>
|
||||
</div>
|
||||
|
||||
{% elif is_here %}
|
||||
<div class="alert alert-primary py-2">
|
||||
<small><i class="bi bi-person-check"></i>
|
||||
Du sitzt aktuell auf der Zentrale.
|
||||
{% if home %}
|
||||
Dein eigenes Tuerschild ({{ home.name or home.hostname }}, Segment {{ home.segment }})
|
||||
ist bis zur Rueckkehr ausgeschaltet.
|
||||
{% endif %}
|
||||
</small>
|
||||
</div>
|
||||
<form method="post" action="/api/zentrale/verlassen" class="mb-2">
|
||||
<input type="hidden" name="extension" value="{{ user.phone_extension }}">
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
<i class="bi bi-box-arrow-left"></i> Zurueck auf mein Tuerschild
|
||||
</button>
|
||||
</form>
|
||||
{% if home_blocker %}
|
||||
<div class="alert alert-warning py-2 mb-0">
|
||||
<small><i class="bi bi-exclamation-triangle"></i>
|
||||
Achtung: dein Tuerschild ist derzeit {{ home_blocker }} zugeordnet.
|
||||
Die Rueckkehr wird abgelehnt, solange das so ist.</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
<form method="post" action="/api/zentrale/uebernehmen" class="mb-2">
|
||||
<input type="hidden" name="extension" value="{{ user.phone_extension }}">
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
<i class="bi bi-box-arrow-in-right"></i>
|
||||
{% if occupant %}Zentrale uebernehmen{% else %}Zentrale besetzen{% endif %}
|
||||
</button>
|
||||
<small class="text-muted">
|
||||
{% if occupant and occupant_is_owner %}
|
||||
{{ occupant.display_name }} gehoert auf die Zentrale und kommt automatisch
|
||||
zurueck, sobald du sie wieder freigibst.
|
||||
{% elif occupant %}
|
||||
{{ occupant.display_name }} wird dabei auf das eigene Tuerschild zurueckgesetzt.
|
||||
{% else %}
|
||||
Die Zentrale ist frei.
|
||||
{% endif %}
|
||||
</small>
|
||||
</form>
|
||||
|
||||
{% if home %}
|
||||
<form method="post" action="/api/zentrale/verlassen">
|
||||
<button type="submit" class="btn btn-outline-secondary w-100 btn-sm">
|
||||
<i class="bi bi-box-arrow-left"></i> Zurueck auf mein Tuerschild
|
||||
</button>
|
||||
<input type="hidden" name="extension" value="{{ user.phone_extension }}">
|
||||
<small class="text-muted">
|
||||
Gemerkt: {{ home.name or home.hostname }}, Segment {{ home.segment }}.
|
||||
</small>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div class="text-center mt-3">
|
||||
<a href="/zentrale/{{ user.phone_extension }}" class="text-muted small me-3">
|
||||
<i class="bi bi-arrow-clockwise"></i> Aktualisieren
|
||||
</a>
|
||||
<a href="/zentrale" class="text-muted small">Andere Durchwahl</a>
|
||||
</div>
|
||||
<div class="text-center mt-2">
|
||||
<small class="text-secondary" style="font-size:0.7rem">
|
||||
Diese Seite als Lesezeichen: <code>/zentrale/{{ user.phone_extension }}</code>
|
||||
</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<small class="text-secondary" style="font-size:0.65rem">© cynfo GmbH</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user