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) <noreply@anthropic.com>
227 lines
7.8 KiB
Python
227 lines
7.8 KiB
Python
import sqlite3
|
|
import os
|
|
|
|
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "busylight.db")
|
|
|
|
|
|
def get_db():
|
|
conn = sqlite3.connect(DB_PATH, timeout=10)
|
|
conn.row_factory = sqlite3.Row
|
|
conn.execute("PRAGMA journal_mode=WAL")
|
|
conn.execute("PRAGMA foreign_keys=ON")
|
|
return conn
|
|
|
|
|
|
def init_db():
|
|
conn = get_db()
|
|
conn.executescript("""
|
|
CREATE TABLE IF NOT EXISTS settings (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL DEFAULT ''
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
azure_id TEXT PRIMARY KEY,
|
|
display_name TEXT NOT NULL DEFAULT '',
|
|
email TEXT DEFAULT '',
|
|
personal_nr TEXT DEFAULT '',
|
|
phone_extension TEXT DEFAULT '',
|
|
active INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS wled_devices (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
hostname TEXT NOT NULL UNIQUE,
|
|
name TEXT DEFAULT '',
|
|
segment_count INTEGER NOT NULL DEFAULT 2,
|
|
online INTEGER DEFAULT 0,
|
|
leds INTEGER DEFAULT 0,
|
|
version TEXT DEFAULT '',
|
|
last_check TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS assignments (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
user_azure_id TEXT NOT NULL 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,
|
|
UNIQUE(wled_device_id, segment),
|
|
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,
|
|
g INTEGER NOT NULL DEFAULT 0,
|
|
b INTEGER NOT NULL DEFAULT 0,
|
|
effect TEXT DEFAULT ''
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS state (
|
|
user_azure_id TEXT PRIMARY KEY REFERENCES users(azure_id) ON DELETE CASCADE,
|
|
last_attendance_status TEXT DEFAULT '',
|
|
last_teams_status TEXT DEFAULT '',
|
|
phone_active INTEGER DEFAULT 0,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS activity_log (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
user_azure_id TEXT,
|
|
user_name TEXT,
|
|
attendance_status TEXT,
|
|
teams_status TEXT,
|
|
wled_url TEXT,
|
|
result TEXT
|
|
);
|
|
""")
|
|
|
|
# Migrate: add columns if missing (for existing databases)
|
|
try:
|
|
conn.execute("ALTER TABLE wled_devices ADD COLUMN online INTEGER DEFAULT 0")
|
|
except Exception:
|
|
pass
|
|
try:
|
|
conn.execute("ALTER TABLE wled_devices ADD COLUMN leds INTEGER DEFAULT 0")
|
|
except Exception:
|
|
pass
|
|
try:
|
|
conn.execute("ALTER TABLE wled_devices ADD COLUMN version TEXT DEFAULT ''")
|
|
except Exception:
|
|
pass
|
|
try:
|
|
conn.execute("ALTER TABLE wled_devices ADD COLUMN last_check TIMESTAMP")
|
|
except Exception:
|
|
pass
|
|
try:
|
|
conn.execute("ALTER TABLE wled_devices ADD COLUMN mac TEXT DEFAULT ''")
|
|
except Exception:
|
|
pass
|
|
try:
|
|
conn.execute("ALTER TABLE users ADD COLUMN phone_extension TEXT DEFAULT ''")
|
|
except Exception:
|
|
pass
|
|
try:
|
|
conn.execute("ALTER TABLE state ADD COLUMN phone_active INTEGER DEFAULT 0")
|
|
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": "",
|
|
"azure_tenant_id": "",
|
|
"azure_auth_tenant": "common",
|
|
"azure_scopes": "User.Read User.Read.All Presence.Read Presence.Read.All",
|
|
"azure_auth_record": "",
|
|
"poll_interval": "15",
|
|
"attendance_csv_path": "",
|
|
"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(
|
|
"INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)",
|
|
(key, value),
|
|
)
|
|
|
|
default_colors = [
|
|
("abwesend", 0, 0, 0, ""),
|
|
("homeoffice", 255, 255, 0, ""),
|
|
("available", 0, 255, 0, ""),
|
|
("teams_available", 0, 255, 0, ""),
|
|
("teams_away", 0, 255, 0, ""),
|
|
("teams_busy", 255, 0, 0, ""),
|
|
("teams_donotdisturb", 255, 0, 255, ""),
|
|
("teams_inacall", 255, 0, 255, ""),
|
|
("teams_inameeting", 255, 0, 0, ""),
|
|
("teams_presenting", 200, 200, 200, ""),
|
|
("teams_inaconferencecall", 200, 200, 200, ""),
|
|
("teams_berightback", 0, 255, 0, ""),
|
|
("teams_offline", 0, 255, 0, ""),
|
|
("teams_inactive", 0, 255, 0, ""),
|
|
("teams_offwork", 0, 255, 0, ""),
|
|
("teams_outofoffice", 0, 255, 0, ""),
|
|
("phone_active", 255, 0, 0, ""),
|
|
]
|
|
for status, r, g, b, effect in default_colors:
|
|
conn.execute(
|
|
"INSERT OR IGNORE INTO color_rules (status, r, g, b, effect) VALUES (?, ?, ?, ?, ?)",
|
|
(status, r, g, b, effect),
|
|
)
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
|
|
def get_setting(key, default=""):
|
|
conn = get_db()
|
|
row = conn.execute("SELECT value FROM settings WHERE key = ?", (key,)).fetchone()
|
|
conn.close()
|
|
return row["value"] if row else default
|
|
|
|
|
|
def set_setting(key, value):
|
|
conn = get_db()
|
|
conn.execute(
|
|
"INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)",
|
|
(key, str(value)),
|
|
)
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
|
|
def get_all_settings():
|
|
conn = get_db()
|
|
rows = conn.execute("SELECT key, value FROM settings").fetchall()
|
|
conn.close()
|
|
return {row["key"]: row["value"] for row in rows}
|
|
|
|
|
|
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, attendance_status, teams_status, wled_url, result)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)""",
|
|
(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)"
|
|
)
|
|
conn.commit()
|
|
conn.close()
|