feat: show AIDA + Teams status badges in dashboard, keep original WLED logic

- WLED only controlled when AIDA status is known (original behavior:
  abwesend->off, homeoffice->yellow, anwesend->Teams color)
- Teams presence always queried and stored for dashboard display
- Dashboard shows colored badges for both AIDA and Teams status
- Works without AIDA CSV (Teams status still visible in dashboard)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Christian Mueller 2026-04-23 22:23:22 +02:00
parent 8d178eac6d
commit 5e9af4e268
2 changed files with 98 additions and 61 deletions

View File

@ -67,7 +67,7 @@ def poll_status():
for row in conn.execute("SELECT key, value FROM settings").fetchall(): for row in conn.execute("SELECT key, value FROM settings").fetchall():
settings[row["key"]] = row["value"] settings[row["key"]] = row["value"]
# Get all active users with assignments (auch ohne personal_nr) # Get all active users with assignments
users_with_assignments = conn.execute(""" users_with_assignments = conn.execute("""
SELECT u.azure_id, u.display_name, u.personal_nr, SELECT u.azure_id, u.display_name, u.personal_nr,
a.segment, d.hostname, a.segment, d.hostname,
@ -112,64 +112,69 @@ def _process_user(user, aida_lines, settings, conn):
last_aida = user["last_aida_status"] or "" last_aida = user["last_aida_status"] or ""
last_teams = user["last_teams_status"] or "" last_teams = user["last_teams_status"] or ""
# Determine AIDA status (kann None sein wenn keine CSV oder keine Personalnr) # Immer Teams-Status abfragen (fuer Dashboard-Anzeige)
aida_status = determine_aida_status(aida_lines, personal_nr, settings)
# Immer Teams-Status abfragen
teams_status = "" teams_status = ""
presence = graph.get_presence(azure_id) presence = graph.get_presence(azure_id)
if presence and "activity" in presence: if presence and "activity" in presence:
teams_status = "teams_" + presence["activity"].lower() teams_status = "teams_" + presence["activity"].lower()
# Wenn AIDA nicht verfuegbar, nur Teams-Status nutzen # AIDA-Status ermitteln
if aida_status is None: aida_status = determine_aida_status(aida_lines, personal_nr, settings)
aida_status = "anwesend" # Default: anwesend wenn AIDA nicht verfuegbar
# Check if status changed # Check if anything changed
if aida_status == last_aida and teams_status == last_teams: if aida_status == last_aida and teams_status == last_teams:
return # No change return # No change
# Determine color # --- WLED nur steuern wenn AIDA-Status bekannt ---
if aida_status == "abwesend": wled_changed = aida_status is not None and (
color = get_color_for_status("abwesend", conn) aida_status != last_aida or
elif aida_status == "homeoffice": (aida_status == "anwesend" and teams_status != last_teams)
color = get_color_for_status("homeoffice", conn)
elif teams_status:
color = get_color_for_status(teams_status, conn)
else:
color = get_color_for_status("available", conn)
# Build URL and call WLED
url = build_wled_url(
hostname, color["r"], color["g"], color["b"],
segment=segment, effect=color.get("effect", "")
) )
success, result_text = call_wled(url)
if success: if wled_changed:
conn.execute(""" # Farbe bestimmen nach Originallogik:
INSERT INTO state (user_azure_id, last_aida_status, last_teams_status, updated_at) # abwesend -> abwesend-Farbe
VALUES (?, ?, ?, ?) # homeoffice -> homeoffice-Farbe
ON CONFLICT(user_azure_id) # anwesend -> Teams-Farbe
DO UPDATE SET last_aida_status=?, last_teams_status=?, updated_at=? if aida_status == "abwesend":
""", ( color = get_color_for_status("abwesend", conn)
azure_id, aida_status, teams_status, datetime.now(), elif aida_status == "homeoffice":
aida_status, teams_status, datetime.now(), color = get_color_for_status("homeoffice", conn)
)) elif aida_status == "anwesend" and teams_status:
conn.commit() color = get_color_for_status(teams_status, conn)
logger.info( else:
"%-40s aida=%-12s teams=%-20s -> %s", color = get_color_for_status("available", conn)
display_name, aida_status, teams_status, result_text,
url = build_wled_url(
hostname, color["r"], color["g"], color["b"],
segment=segment, effect=color.get("effect", "")
) )
else: success, result_text = call_wled(url)
conn.execute("""
INSERT INTO state (user_azure_id, last_aida_status, last_teams_status, updated_at)
VALUES (?, '', '', ?)
ON CONFLICT(user_azure_id)
DO UPDATE SET last_aida_status='', last_teams_status='', updated_at=?
""", (azure_id, datetime.now(), datetime.now()))
conn.commit()
log_activity( if not success:
azure_id, display_name, aida_status, teams_status, url, result_text logger.warning("WLED Fehler fuer %s: %s", display_name, result_text)
log_activity(
azure_id, display_name,
aida_status or "-", teams_status or "-",
url, result_text,
)
# State immer aktualisieren (fuer Dashboard)
conn.execute("""
INSERT INTO state (user_azure_id, last_aida_status, last_teams_status, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(user_azure_id)
DO UPDATE SET last_aida_status=?, last_teams_status=?, updated_at=?
""", (
azure_id,
aida_status or last_aida, teams_status, datetime.now(),
aida_status or last_aida, teams_status, datetime.now(),
))
conn.commit()
logger.info(
"%-40s aida=%-12s teams=%-20s wled=%s",
display_name, aida_status or "-", teams_status,
"aktualisiert" if wled_changed else "nur Dashboard",
) )

View File

@ -38,19 +38,51 @@
<div class="mb-2"> <div class="mb-2">
<small class="text-muted d-block mb-1">Segment {{ seg.index }}</small> <small class="text-muted d-block mb-1">Segment {{ seg.index }}</small>
{% if seg.user %} {% if seg.user %}
{% if seg.aida == '-' %} {% if seg.aida == '-' and seg.teams == '-' %}
<div class="segment-bar" style="background: #f0f0f0; border-color: #ccc;"> <div class="segment-bar" style="background: #f0f0f0; border-color: #ccc;">
<span class="status-dot me-2" style="background: #ccc;"></span> <span class="status-dot me-2" style="background: #ccc;"></span>
{{ seg.user }} {{ seg.user }}
</div> </div>
<small class="text-muted"><i class="bi bi-hourglass-split"></i> Warte auf Status...</small> <small class="text-muted"><i class="bi bi-hourglass-split"></i> Warte auf Status...</small>
{% else %} {% else %}
<div class="segment-bar" style="background: rgba({{ seg.r }}, {{ seg.g }}, {{ seg.b }}, 0.3); border-color: rgb({{ seg.r }}, {{ seg.g }}, {{ seg.b }});"> <div class="segment-bar" style="background: rgba({{ seg.r }}, {{ seg.g }}, {{ seg.b }}, 0.3); border-color: rgb({{ seg.r }}, {{ seg.g }}, {{ seg.b }});">
<span class="status-dot me-2" style="background: rgb({{ seg.r }}, {{ seg.g }}, {{ seg.b }});"></span> <span class="status-dot me-2" style="background: rgb({{ seg.r }}, {{ seg.g }}, {{ seg.b }});"></span>
{{ seg.user }} {{ seg.user }}
</div> </div>
<small class="text-muted">{{ seg.aida }} / {{ seg.teams }}</small> <div class="d-flex gap-2 mt-1">
{% endif %} {% if seg.aida == 'anwesend' %}
<span class="badge bg-success"><i class="bi bi-building"></i> Anwesend</span>
{% elif seg.aida == 'abwesend' %}
<span class="badge bg-secondary"><i class="bi bi-door-open"></i> Abwesend</span>
{% elif seg.aida == 'homeoffice' %}
<span class="badge bg-warning text-dark"><i class="bi bi-house"></i> Homeoffice</span>
{% else %}
<span class="badge bg-light text-dark">{{ seg.aida }}</span>
{% endif %}
{% if seg.teams != '-' %}
{% if 'available' in seg.teams %}
<span class="badge bg-success"><i class="bi bi-microsoft-teams"></i> Verfuegbar</span>
{% elif 'busy' in seg.teams or 'inacall' in seg.teams or 'inameeting' in seg.teams %}
<span class="badge bg-danger"><i class="bi bi-microsoft-teams"></i> Beschaeftigt</span>
{% elif 'donotdisturb' in seg.teams %}
<span class="badge" style="background:#8b00ff;color:#fff"><i class="bi bi-microsoft-teams"></i> Nicht stoeren</span>
{% elif 'away' in seg.teams or 'berightback' in seg.teams %}
<span class="badge bg-warning text-dark"><i class="bi bi-microsoft-teams"></i> Abwesend</span>
{% elif 'offline' in seg.teams %}
<span class="badge bg-secondary"><i class="bi bi-microsoft-teams"></i> Offline</span>
{% elif 'presenting' in seg.teams %}
<span class="badge bg-danger"><i class="bi bi-microsoft-teams"></i> Praesentiert</span>
{% elif 'outofoffice' in seg.teams %}
<span class="badge bg-secondary"><i class="bi bi-microsoft-teams"></i> Nicht im Buero</span>
{% elif 'inactive' in seg.teams or 'offwork' in seg.teams %}
<span class="badge bg-secondary"><i class="bi bi-microsoft-teams"></i> Inaktiv</span>
{% else %}
<span class="badge bg-light text-dark"><i class="bi bi-microsoft-teams"></i> {{ seg.teams }}</span>
{% endif %}
{% endif %}
</div>
{% endif %}
{% else %} {% else %}
<div class="segment-bar empty"> <div class="segment-bar empty">
<i class="bi bi-dash-circle me-2"></i> Nicht zugeordnet <i class="bi bi-dash-circle me-2"></i> Nicht zugeordnet