feat: compact dashboard + login system for admin pages

Dashboard:
- Much smaller cards (col-xl-2), compact badges
- Short labels (Buero, HO, DND, OoO, etc.)
- Optimized for small screens

Login:
- Password-protected admin pages (all except dashboard)
- Dashboard remains public (read-only status view)
- Login page with redirect back to requested page
- Session cookie (24h, regenerated on restart)
- Password set via Settings page (SHA256 hashed)
- Sidebar shows only Dashboard when not logged in
- Login/Logout link in sidebar footer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 13:48:58 +02:00
co-authored by Claude Opus 4.6
parent 5294e8ef50
commit 8a578fbe8d
6 changed files with 229 additions and 58 deletions
+9
View File
@@ -22,6 +22,7 @@
<i class="bi bi-speedometer2"></i> Dashboard
</a>
</li>
{% if logged_in %}
<li class="nav-item">
<a class="nav-link text-white {% if page == 'users' %}active{% endif %}" href="/users">
<i class="bi bi-people"></i> Benutzer
@@ -57,6 +58,7 @@
<i class="bi bi-journal-text"></i> Protokoll
</a>
</li>
{% endif %}
</ul>
<div class="p-3 border-top border-secondary">
{% if authenticated %}
@@ -64,6 +66,13 @@
{% else %}
<small class="text-danger"><i class="bi bi-x-circle"></i> Azure getrennt</small>
{% endif %}
<div class="mt-2">
{% if logged_in %}
<a href="/logout" class="text-light small"><i class="bi bi-box-arrow-left"></i> Abmelden</a>
{% else %}
<a href="/login" class="text-light small"><i class="bi bi-box-arrow-in-right"></i> Anmelden</a>
{% endif %}
</div>
</div>
</nav>
+39 -57
View File
@@ -2,99 +2,82 @@
{% block title %}Dashboard - Busylight{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="mb-0">Dashboard</h4>
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0">Dashboard</h5>
<div>
<form method="post" action="/api/poll" class="d-inline">
<button type="submit" class="btn btn-sm btn-outline-primary">
<i class="bi bi-arrow-clockwise"></i> Jetzt abfragen
<button type="submit" class="btn btn-sm btn-outline-primary py-0">
<i class="bi bi-arrow-clockwise"></i> Abfragen
</button>
</form>
<span class="text-muted ms-2" id="last-update"></span>
<small class="text-muted ms-2" id="last-update"></small>
</div>
</div>
{% if not authenticated %}
<div class="alert alert-warning">
<i class="bi bi-exclamation-triangle"></i>
Azure ist nicht verbunden. Bitte unter <a href="/settings">Einstellungen</a> konfigurieren und
<code>python app.py --auth</code> ausfuehren.
<div class="alert alert-warning py-2 mb-3">
<small><i class="bi bi-exclamation-triangle"></i> Azure nicht verbunden.
<a href="/settings">Einstellungen</a> pruefen.</small>
</div>
{% endif %}
{% if devices %}
<div class="row g-3">
<div class="row g-2">
{% for device in devices %}
<div class="col-md-6 col-lg-4 col-xl-3">
<div class="col-sm-6 col-md-4 col-lg-3 col-xl-2">
<div class="card busylight-card h-100">
<div class="card-header bg-white border-0 pb-0">
<div class="d-flex justify-content-between align-items-center">
<strong>{{ device.name }}</strong>
<small class="text-muted">{{ device.hostname }}</small>
<div class="card-body p-2">
<div class="d-flex justify-content-between align-items-center mb-1">
<strong class="small">{{ device.name }}</strong>
</div>
</div>
<div class="card-body pt-2">
{% for seg in device.segments|reverse %}
<div class="mb-2">
<small class="text-muted d-block mb-1">Segment {{ seg.index }}</small>
{% if seg.user %}
{% if seg.aida == '-' and seg.teams == '-' %}
<div class="segment-bar" style="background: #f0f0f0; border-color: #ccc;">
<span class="status-dot me-2" style="background: #ccc;"></span>
{{ seg.user }}
</div>
<small class="text-muted"><i class="bi bi-hourglass-split"></i> Warte auf Status...</small>
{% 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 }});">
<span class="status-dot me-2" style="background: rgb({{ seg.r }}, {{ seg.g }}, {{ seg.b }});"></span>
{{ seg.user }}
</div>
<div class="d-flex gap-2 mt-1">
{% if seg.user %}
{% if seg.aida == '-' and seg.teams == '-' %}
<div class="seg-compact" style="background: #f0f0f0; border-left: 3px solid #ccc;">
<span class="seg-name">{{ seg.user }}</span>
<small class="text-muted"><i class="bi bi-hourglass-split"></i></small>
</div>
{% else %}
<div class="seg-compact" style="background: rgba({{ seg.r }}, {{ seg.g }}, {{ seg.b }}, 0.15); border-left: 3px solid rgb({{ seg.r }}, {{ seg.g }}, {{ seg.b }});">
<span class="seg-name">{{ seg.user }}</span>
<div class="seg-badges">
{% if seg.aida == 'anwesend' %}
<span class="badge bg-success"><i class="bi bi-building"></i> Anwesend</span>
<span class="badge-sm bg-success">Buero</span>
{% elif seg.aida == 'abwesend' %}
<span class="badge bg-secondary"><i class="bi bi-door-open"></i> Abwesend</span>
<span class="badge-sm bg-secondary">Weg</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>
<span class="badge-sm bg-warning text-dark">HO</span>
{% endif %}
{% if seg.teams != '-' %}
{% if 'available' in seg.teams %}
<span class="badge bg-success"><i class="bi bi-microsoft-teams"></i> Verfuegbar</span>
<span class="badge-sm bg-success">Frei</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>
<span class="badge-sm bg-danger">Besetzt</span>
{% elif 'donotdisturb' in seg.teams %}
<span class="badge" style="background:#8b00ff;color:#fff"><i class="bi bi-microsoft-teams"></i> Nicht stoeren</span>
<span class="badge-sm" style="background:#8b00ff;color:#fff">DND</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>
<span class="badge-sm bg-warning text-dark">Weg</span>
{% elif 'presenting' in seg.teams %}
<span class="badge bg-danger"><i class="bi bi-microsoft-teams"></i> Praesentiert</span>
<span class="badge-sm bg-danger">Praes.</span>
{% elif 'offline' in seg.teams or 'inactive' in seg.teams or 'offwork' in seg.teams %}
<span class="badge-sm bg-secondary">Off</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>
<span class="badge-sm bg-secondary">OoO</span>
{% endif %}
{% endif %}
{% if seg.phone %}
<span class="badge bg-danger"><i class="bi bi-telephone-fill"></i> Telefoniert</span>
{% else %}
<span class="badge bg-light text-dark"><i class="bi bi-telephone"></i> Frei</span>
<span class="badge-sm bg-danger"><i class="bi bi-telephone-fill"></i></span>
{% endif %}
</div>
{% endif %}
{% else %}
<div class="segment-bar empty">
<i class="bi bi-dash-circle me-2"></i> Nicht zugeordnet
</div>
{% endif %}
{% else %}
<div class="seg-compact seg-empty">
<small class="text-muted">-- leer --</small>
</div>
{% endif %}
{% endfor %}
</div>
</div>
@@ -112,7 +95,6 @@
{% block scripts %}
<script>
// Auto-refresh every 10 seconds
setTimeout(() => location.reload(), 10000);
document.getElementById('last-update').textContent =
'Aktualisiert: ' + new Date().toLocaleTimeString('de-DE');
+48
View File
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Busylight</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<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-5">
<div class="col-md-4">
<div class="card shadow-sm">
<div class="card-body p-4">
<div class="text-center mb-4">
<i class="bi bi-lightbulb-fill text-warning display-4"></i>
<h4 class="mt-2">Busylight</h4>
<small class="text-muted">cynfo GmbH</small>
</div>
{% if error %}
<div class="alert alert-danger py-2">
<small><i class="bi bi-x-circle"></i> Falsches Passwort</small>
</div>
{% endif %}
<form method="post" action="/login">
<input type="hidden" name="next" value="{{ next }}">
<div class="mb-3">
<label class="form-label">Passwort</label>
<input type="password" name="password" class="form-control" autofocus required>
</div>
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-box-arrow-in-right"></i> Anmelden
</button>
</form>
<div class="text-center mt-3">
<a href="/" class="text-muted small">Zurueck zum Dashboard</a>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
+18
View File
@@ -77,6 +77,24 @@
</div>
</div>
<div class="card mb-4">
<div class="card-header">
<h6 class="mb-0"><i class="bi bi-shield-lock"></i> Zugangsschutz</h6>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label">Admin-Passwort</label>
<input type="password" name="admin_password" class="form-control"
placeholder="Neues Passwort eingeben (leer = nicht aendern)">
{% if settings.admin_password %}
<small class="text-success"><i class="bi bi-check-circle"></i> Passwort ist gesetzt</small>
{% else %}
<small class="text-warning"><i class="bi bi-exclamation-triangle"></i> Kein Passwort gesetzt - alle Seiten sind frei zugaenglich</small>
{% endif %}
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h6 class="mb-0"><i class="bi bi-file-earmark-spreadsheet"></i> AIDA Zeiterfassung</h6>