diff --git a/app.py b/app.py index e08a2d0..0acc598 100644 --- a/app.py +++ b/app.py @@ -192,14 +192,14 @@ async def dashboard(request: Request): for seg_idx in range(dev["segment_count"]): row = conn.execute(""" SELECT u.display_name, u.azure_id, - s.last_aida_status, s.last_teams_status, + s.last_attendance_status, s.last_teams_status, COALESCE(s.phone_active, 0) as phone_active, - cr_aida.r as ar, cr_aida.g as ag, cr_aida.b as ab, + cr_att.r as ar, cr_att.g as ag, cr_att.b as ab, cr_teams.r as tr, cr_teams.g as tg, cr_teams.b as tb FROM assignments a JOIN users u ON u.azure_id = a.user_azure_id LEFT JOIN state s ON s.user_azure_id = u.azure_id - LEFT JOIN color_rules cr_aida ON cr_aida.status = s.last_aida_status + LEFT JOIN color_rules cr_att ON cr_att.status = s.last_attendance_status LEFT JOIN color_rules cr_teams ON cr_teams.status = s.last_teams_status WHERE a.wled_device_id = ? AND a.segment = ? """, (dev["id"], seg_idx)).fetchone() @@ -207,10 +207,9 @@ async def dashboard(request: Request): if row: phone = row["phone_active"] if phone: - # Phone active: show phone color pc = conn.execute("SELECT r,g,b FROM color_rules WHERE status='phone_active'").fetchone() r, g, b = (pc["r"], pc["g"], pc["b"]) if pc else (255, 0, 0) - elif row["last_aida_status"] == "anwesend" and row["tr"] is not None: + elif row["last_attendance_status"] == "anwesend" and row["tr"] is not None: r, g, b = row["tr"], row["tg"], row["tb"] elif row["ar"] is not None: r, g, b = row["ar"], row["ag"], row["ab"] @@ -219,7 +218,7 @@ async def dashboard(request: Request): segments.append({ "index": seg_idx, "user": row["display_name"], - "aida": row["last_aida_status"] or "-", + "attendance": row["last_attendance_status"] or "-", "teams": row["last_teams_status"] or "-", "phone": phone, "r": r, "g": g, "b": b, @@ -227,7 +226,7 @@ async def dashboard(request: Request): else: segments.append({ "index": seg_idx, "user": None, - "aida": "-", "teams": "-", + "attendance": "-", "teams": "-", "r": 128, "g": 128, "b": 128, }) @@ -558,8 +557,8 @@ async def update_settings(request: Request): keys = [ "azure_client_id", "azure_client_secret", "azure_tenant_id", "azure_auth_tenant", "azure_scopes", "poll_interval", - "aida_csv_path", "aida_status_anwesend", "aida_status_abwesend", - "aida_status_homeoffice", + "attendance_csv_path", "attendance_status_anwesend", "attendance_status_abwesend", + "attendance_status_homeoffice", ] for key in keys: value = form.get(key, "") @@ -607,7 +606,7 @@ async def log_page(request: Request): async def api_status(): conn = database.get_db() states = conn.execute(""" - SELECT u.display_name, u.azure_id, s.last_aida_status, s.last_teams_status, + SELECT u.display_name, u.azure_id, s.last_attendance_status, s.last_teams_status, s.updated_at, d.hostname, a.segment FROM state s JOIN users u ON u.azure_id = s.user_azure_id diff --git a/database.py b/database.py index 48e8161..797802d 100644 --- a/database.py +++ b/database.py @@ -59,7 +59,7 @@ def init_db(): CREATE TABLE IF NOT EXISTS state ( user_azure_id TEXT PRIMARY KEY REFERENCES users(azure_id) ON DELETE CASCADE, - last_aida_status TEXT DEFAULT '', + last_attendance_status TEXT DEFAULT '', last_teams_status TEXT DEFAULT '', phone_active INTEGER DEFAULT 0, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP @@ -70,7 +70,7 @@ def init_db(): timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, user_azure_id TEXT, user_name TEXT, - aida_status TEXT, + attendance_status TEXT, teams_status TEXT, wled_url TEXT, result TEXT @@ -107,6 +107,27 @@ def init_db(): except Exception: pass + # Migrate: rename aida columns to attendance + try: + conn.execute("ALTER TABLE state RENAME COLUMN last_aida_status TO last_attendance_status") + except Exception: + pass + try: + conn.execute("ALTER TABLE activity_log RENAME COLUMN aida_status TO attendance_status") + except Exception: + pass + # Migrate: rename aida settings keys + for old_key, new_key in [ + ("aida_csv_path", "attendance_csv_path"), + ("aida_status_anwesend", "attendance_status_anwesend"), + ("aida_status_abwesend", "attendance_status_abwesend"), + ("aida_status_homeoffice", "attendance_status_homeoffice"), + ]: + old_val = conn.execute("SELECT value FROM settings WHERE key=?", (old_key,)).fetchone() + if old_val: + conn.execute("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)", (new_key, old_val["value"])) + conn.execute("DELETE FROM settings WHERE key=?", (old_key,)) + defaults = { "azure_client_id": "", "azure_client_secret": "", @@ -115,10 +136,10 @@ def init_db(): "azure_scopes": "User.Read User.Read.All Presence.Read Presence.Read.All", "azure_auth_record": "", "poll_interval": "15", - "aida_csv_path": "", - "aida_status_anwesend": "~~1~~", - "aida_status_abwesend": "~~0~~", - "aida_status_homeoffice": "~~2~~", + "attendance_csv_path": "", + "attendance_status_anwesend": "~~1~~", + "attendance_status_abwesend": "~~0~~", + "attendance_status_homeoffice": "~~2~~", } for key, value in defaults.items(): conn.execute( @@ -179,14 +200,14 @@ def get_all_settings(): return {row["key"]: row["value"] for row in rows} -def log_activity(user_azure_id, user_name, aida_status, teams_status, wled_url, result): +def log_activity(user_azure_id, user_name, attendance_status, teams_status, wled_url, result): from datetime import datetime conn = get_db() conn.execute( """INSERT INTO activity_log - (timestamp, user_azure_id, user_name, aida_status, teams_status, wled_url, result) + (timestamp, user_azure_id, user_name, attendance_status, teams_status, wled_url, result) VALUES (?, ?, ?, ?, ?, ?, ?)""", - (datetime.now().strftime("%Y-%m-%d %H:%M:%S"), user_azure_id, user_name, aida_status, teams_status, wled_url, result), + (datetime.now().strftime("%Y-%m-%d %H:%M:%S"), user_azure_id, user_name, attendance_status, teams_status, wled_url, result), ) conn.execute( "DELETE FROM activity_log WHERE id NOT IN (SELECT id FROM activity_log ORDER BY id DESC LIMIT 500)" diff --git a/scheduler.py b/scheduler.py index 7176267..f0939fb 100644 --- a/scheduler.py +++ b/scheduler.py @@ -11,8 +11,8 @@ logger = logging.getLogger(__name__) graph = None -def read_aida_csv(csv_path): - """Read AIDA status CSV. Returns list of lines.""" +def read_attendance_csv(csv_path): + """Read attendance status CSV. Returns list of lines.""" if not csv_path or not os.path.exists(csv_path): return [] try: @@ -20,7 +20,7 @@ def read_aida_csv(csv_path): lines = f.read().splitlines() return lines except Exception as e: - logger.error("Fehler beim Lesen der AIDA CSV: %s", e) + logger.error("Fehler beim Lesen der Zeiterfassungs-CSV: %s", e) return [] @@ -29,8 +29,8 @@ def _extract_nr(value): return value.strip().strip("~") -def determine_aida_status(lines, personal_nr, settings): - """Determine AIDA status for a user based on their personal number.""" +def determine_attendance_status(lines, personal_nr, settings): + """Determine attendance status for a user based on their personal number.""" if not personal_nr or not lines: return None @@ -45,9 +45,9 @@ def determine_aida_status(lines, personal_nr, settings): line = matching[0] # Check status part (after comma or at end) - status_abwesend = _extract_nr(settings.get("aida_status_abwesend", "~~0~~")) - status_anwesend = _extract_nr(settings.get("aida_status_anwesend", "~~1~~")) - status_homeoffice = _extract_nr(settings.get("aida_status_homeoffice", "~~2~~")) + status_abwesend = _extract_nr(settings.get("attendance_status_abwesend", "~~0~~")) + status_anwesend = _extract_nr(settings.get("attendance_status_anwesend", "~~1~~")) + status_homeoffice = _extract_nr(settings.get("attendance_status_homeoffice", "~~2~~")) # Get the status value (last part after comma, stripped of ~) parts = line.split(",") @@ -90,7 +90,7 @@ def poll_status(): users_with_assignments = conn.execute(""" SELECT u.azure_id, u.display_name, u.personal_nr, a.segment, d.hostname, - s.last_aida_status, s.last_teams_status, + s.last_attendance_status, s.last_teams_status, COALESCE(s.phone_active, 0) as phone_active FROM users u JOIN assignments a ON a.user_azure_id = u.azure_id @@ -103,12 +103,12 @@ def poll_status(): logger.debug("Keine aktiven User mit Zuordnungen gefunden") return - # Read AIDA CSV (kann leer sein) - aida_lines = read_aida_csv(settings.get("aida_csv_path", "")) + # Read attendance CSV (kann leer sein) + attendance_lines = read_attendance_csv(settings.get("attendance_csv_path", "")) for user in users_with_assignments: try: - _process_user(user, aida_lines, settings, conn) + _process_user(user, attendance_lines, settings, conn) except Exception as e: logger.error( "Fehler bei User %s: %s", user["display_name"], e @@ -120,7 +120,7 @@ def poll_status(): conn.close() -def _process_user(user, aida_lines, settings, conn): +def _process_user(user, attendance_lines, settings, conn): """Process a single user: check status and update WLED if needed.""" global graph @@ -129,7 +129,7 @@ def _process_user(user, aida_lines, settings, conn): personal_nr = user["personal_nr"] hostname = user["hostname"] segment = user["segment"] - last_aida = user["last_aida_status"] or "" + last_attendance = user["last_attendance_status"] or "" last_teams = user["last_teams_status"] or "" phone_active = user["phone_active"] @@ -139,29 +139,25 @@ def _process_user(user, aida_lines, settings, conn): if presence and "activity" in presence: teams_status = "teams_" + presence["activity"].lower() - # AIDA-Status ermitteln - aida_status = determine_aida_status(aida_lines, personal_nr, settings) + # Anwesenheitsstatus ermitteln + attendance_status = determine_attendance_status(attendance_lines, personal_nr, settings) # Check if anything changed - if aida_status == last_aida and teams_status == last_teams: + if attendance_status == last_attendance and teams_status == last_teams: return # No change - # --- WLED nur steuern wenn AIDA-Status bekannt und kein aktives Telefonat --- - wled_changed = aida_status is not None and not phone_active and ( - aida_status != last_aida or - (aida_status == "anwesend" and teams_status != last_teams) + # --- WLED nur steuern wenn Anwesenheitsstatus bekannt und kein aktives Telefonat --- + wled_changed = attendance_status is not None and not phone_active and ( + attendance_status != last_attendance or + (attendance_status == "anwesend" and teams_status != last_teams) ) if wled_changed: - # Farbe bestimmen nach Originallogik: - # abwesend -> abwesend-Farbe - # homeoffice -> homeoffice-Farbe - # anwesend -> Teams-Farbe - if aida_status == "abwesend": + if attendance_status == "abwesend": color = get_color_for_status("abwesend", conn) - elif aida_status == "homeoffice": + elif attendance_status == "homeoffice": color = get_color_for_status("homeoffice", conn) - elif aida_status == "anwesend" and teams_status: + elif attendance_status == "anwesend" and teams_status: color = get_color_for_status(teams_status, conn) else: color = get_color_for_status("available", conn) @@ -177,26 +173,26 @@ def _process_user(user, aida_lines, settings, conn): log_activity( azure_id, display_name, - aida_status or "-", teams_status or "-", + attendance_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) + INSERT INTO state (user_azure_id, last_attendance_status, last_teams_status, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT(user_azure_id) - DO UPDATE SET last_aida_status=?, last_teams_status=?, updated_at=? + DO UPDATE SET last_attendance_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(), + attendance_status or last_attendance, teams_status, datetime.now(), + attendance_status or last_attendance, teams_status, datetime.now(), )) conn.commit() logger.info( - "%-40s aida=%-12s teams=%-20s wled=%s", - display_name, aida_status or "-", teams_status, + "%-40s zeiterfassung=%-12s teams=%-20s wled=%s", + display_name, attendance_status or "-", teams_status, "aktualisiert" if wled_changed else "nur Dashboard", ) diff --git a/templates/dashboard.html b/templates/dashboard.html index 6c704b3..95c4838 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -17,8 +17,7 @@ {% if not authenticated %}
python app.py --auth ausfuehren.
+ Azure ist nicht verbunden. Bitte unter Einstellungen konfigurieren und verbinden.
POST /api/attendance/{personal_nr}{"status": "anwesend"}
+