diff --git a/README.md b/README.md index cff80d3..0a37d80 100644 --- a/README.md +++ b/README.md @@ -205,11 +205,19 @@ erst per Knopfdruck. Segment ausgeschaltet. - **Zurueck auf mein Tuerschild**: Der gemerkte Platz wird wiederhergestellt und die Zentrale freigegeben (Farbe `abwesend`, Standard: aus). -- **Vorgaenger**: Wer die Zentrale abgibt, wird dabei automatisch auf sein eigenes - Tuerschild zurueckgesetzt. Er muss also nicht erst selbst "zurueck" klicken, damit - der Nachfolger uebernehmen kann. Ist sein Tuerschild inzwischen jemand anderem - zugeordnet oder war keines gemerkt, bleibt er ohne Zuordnung - die Meldung auf der - Seite sagt, welcher der beiden Faelle vorliegt. +- **Vorgaenger mit eigenem Tuerschild**: Wer die Zentrale abgibt, wird dabei + automatisch auf sein eigenes Tuerschild zurueckgesetzt. Er muss also nicht erst + selbst "zurueck" klicken, damit der Nachfolger uebernehmen kann. Ist sein Tuerschild + inzwischen jemand anderem zugeordnet, bleibt er ohne Zuordnung - die Meldung nennt + dann, wer dort sitzt. +- **Stamm-Besetzer der Zentrale**: Wer normalerweise auf der Zentrale sitzt und kein + eigenes Tuerschild hat, wird beim ersten Verdraengen als Stamm-Besetzer vermerkt. + Er kommt automatisch zurueck, sobald die Zentrale wieder freigegeben wird - egal + wie viele Vertretungen sich zwischenzeitlich abgewechselt haben. Sein Vermerk bleibt + dauerhaft stehen, denn die Zentrale ist sein normaler Platz und keine Vertretung. +- **Der Stamm-Besetzer selbst** braucht "Zurueck auf mein Tuerschild" nicht: sein + Platz ist die Zentrale. Klickt er es trotzdem, aendert sich nichts und die Seite + sagt es ihm. - **Belegter Heimatplatz**: Ist das eigene Tuerschild inzwischen jemand anderem zugeordnet, wird die Rueckkehr abgelehnt und nichts geaendert. Dann muss der Administrator die Zuordnung aufloesen. diff --git a/app.py b/app.py index f14b789..c839c09 100644 --- a/app.py +++ b/app.py @@ -569,6 +569,44 @@ def _zentrale_view(request, conn, target, extension="", user=None, error=None, m }) +def _zentrale_owner_row(conn, target): + """Heimateintrag, der auf das Zentral-Segment zeigt - der Stamm-Besetzer.""" + if not target: + return None + return conn.execute(""" + SELECT h.user_azure_id, u.display_name + FROM home_assignments h JOIN users u ON u.azure_id = h.user_azure_id + WHERE h.wled_device_id = ? AND h.segment = ? AND u.active = 1 + AND NOT EXISTS (SELECT 1 FROM assignments a WHERE a.user_azure_id = h.user_azure_id) + ORDER BY h.created_at, h.user_azure_id + LIMIT 1 + """, (target["id"], target["segment"])).fetchone() + + +def _restore_zentrale_owner(conn, target): + """Stamm-Besetzer zurueckholen, sobald das Zentral-Segment frei ist. + + Als Stamm-Besetzer gilt, fuer wen das Zentral-Segment als Heimatplatz + gemerkt ist. Sein Eintrag bleibt dabei absichtlich stehen: die Zentrale + ist sein normaler Platz, nicht eine Vertretung, aus der er zurueckkehrt. + """ + if not target: + return None + busy = conn.execute( + "SELECT 1 FROM assignments WHERE wled_device_id = ? AND segment = ?", + (target["id"], target["segment"]), + ).fetchone() + if busy: + return None + owner = _zentrale_owner_row(conn, target) + if not owner: + return None + conn.execute(""" + INSERT INTO assignments (user_azure_id, wled_device_id, segment) VALUES (?, ?, ?) + """, (owner["user_azure_id"], target["id"], target["segment"])) + return owner + + @app.get("/zentrale", response_class=HTMLResponse) async def zentrale_page(request: Request): """Self-Service-Seite fuer den Empfangsbereich. Absichtlich ohne Admin-Login.""" @@ -651,13 +689,23 @@ async def zentrale_takeover(request: Request, extension: str = Form(...)): # 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 FROM home_assignments h JOIN wled_devices d ON d.id = h.wled_device_id WHERE h.user_azure_id = ? """, (predecessor["azure_id"],)).fetchone() - if pred_home: + + is_owner = pred_home and (pred_home["wled_device_id"], pred_home["segment"]) \ + == (target["id"], target["segment"]) + + if is_owner: + # 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 + 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 = ? @@ -671,6 +719,19 @@ async def zentrale_takeover(request: Request, extension: str = Form(...)): conn.execute("DELETE FROM home_assignments WHERE user_azure_id = ?", (predecessor["azure_id"],)) pred_back = pred_home + else: + # Kein anderer Platz gemerkt: die Zentrale ist sein Stammplatz. + # Merken, damit er automatisch zurueckkommt, wenn sie frei wird. + claimed = conn.execute(""" + SELECT 1 FROM home_assignments + WHERE wled_device_id = ? AND segment = ? AND user_azure_id != ? + """, (target["id"], target["segment"], predecessor["azure_id"])).fetchone() + if not claimed: + conn.execute(""" + 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() @@ -694,6 +755,7 @@ async def zentrale_takeover(request: Request, extension: str = Form(...)): 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']})." @@ -705,6 +767,9 @@ async def zentrale_takeover(request: Request, extension: str = Form(...)): 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.") @@ -741,11 +806,30 @@ async def zentrale_release(request: Request, extension: str = Form(...)): 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"],)) + returning = _restore_zentrale_owner(conn, target) conn.commit() - scheduler.clear_segment(target["hostname"], target["segment"], conn) - logger.info("Zentrale verlassen von %s (kein Heimat-Tuerschild)", user["display_name"]) + if returning: + scheduler.push_user_light(returning["user_azure_id"], returning["display_name"], + 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", + 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) + + # Der Stamm-Besetzer der Zentrale hat keinen anderen Platz, auf den er + # zurueckgehen koennte - 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, - message=f"{user['display_name']} ist von der Zentrale abgemeldet. Ein eigenes Tuerschild war nicht gespeichert.") + error=("Die Zentrale ist dein eigener Platz, aktuell hat sie jemand anderes. " + "Mit \"Zentrale uebernehmen\" holst du sie zurueck.")) # Ist der Heimatplatz inzwischen von jemand anderem belegt? blocker = conn.execute(""" @@ -764,19 +848,29 @@ async def zentrale_release(request: Request, extension: str = Form(...)): 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"],)) + + # Die Zentrale ist jetzt frei - ihr Stamm-Besetzer kommt zurueck. + returning = _restore_zentrale_owner(conn, target) if was_here else None 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) + if returning: + scheduler.push_user_light(returning["user_azure_id"], returning["display_name"], + target["hostname"], target["segment"], conn) + else: + 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"]) + 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") - 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']}).")) + 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) finally: conn.close()