Info Tab und MFA
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
<!-- Aktionen: Standort-Dropdown, +Button, Theme, Logout -->
|
||||
<div class="navbar-nav flex-row order-md-last top-actions">
|
||||
<!-- Standort-Auswahl -->
|
||||
{% if current_user.is_authenticated %}
|
||||
<div class="dropdown me-2">
|
||||
<a class="btn btn-outline-secondary" href="#" data-bs-toggle="dropdown">
|
||||
<i class="ti ti-building me-1"></i>{{ current_site | capitalize }} <i class="ti ti-chevron-down"></i>
|
||||
@@ -34,6 +35,7 @@
|
||||
onclick="var n=prompt('Name des neuen Standorts:'); if(n&&n.trim()) location.href='/add-site?name='+encodeURIComponent(n.trim().toLowerCase());"
|
||||
title="Neuen Standort anlegen"
|
||||
style="display:inline-flex;align-items:center;justify-content:center;min-width:2.5rem;min-height:2.5rem;padding:0;"><i class="ti ti-plus"></i></a>
|
||||
{% endif %}
|
||||
|
||||
<!-- Dark-Mode-Toggle (persistiert in localStorage("signage-theme")) -->
|
||||
<div>
|
||||
@@ -50,6 +52,7 @@
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<a class="dropdown-item" href="/change-password"><i class="ti ti-key me-1"></i>Passwort ändern</a>
|
||||
<a class="dropdown-item" href="/mfa/setup"><i class="ti ti-shield me-1"></i>MFA einrichten{% if current_user.mfa_enabled %} <span class="badge bg-green text-green-fg ms-1" style="font-size:0.6rem;">Aktiv</span>{% endif %}</a>
|
||||
<a class="dropdown-item" href="/admin/help"><i class="ti ti-help me-1"></i>Hilfe</a>
|
||||
{% if current_user.is_admin %}
|
||||
<a class="dropdown-item" href="/admin/dashboard"><i class="ti ti-dashboard me-1"></i>Admin Dashboard</a>
|
||||
|
||||
@@ -176,7 +176,8 @@
|
||||
</span>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
{% if screen_status[screen] == "online" %}
|
||||
<button type="button" class="btn btn-sm btn-success" onclick="return false;">Online</button>
|
||||
<button type="button" class="btn btn-sm btn-success" onclick="return false;"
|
||||
title="Browser: {{ client_info[screen].get('ua', '?')[:60] }} Auflösung: {{ client_info[screen].get('resolution', '?') }} IP: {{ client_info[screen].get('ip', '?') }} Zuletzt: {{ client_info[screen].get('last_seen', '?') }}">Online</button>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-sm btn-danger" onclick="return false;">Offline</button>
|
||||
{% endif %}
|
||||
@@ -188,7 +189,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card-Body: Tabs (Playlist | Einstellungen | Aktionen | Digital Voice Agent | Medien) -->
|
||||
<!-- Card-Body: Tabs (Playlist | Einstellungen | Aktionen | Digital Voice Agent | Medien | Info) -->
|
||||
<div class="card-body">
|
||||
|
||||
<ul class="nav nav-tabs" data-bs-toggle="tabs" role="tablist">
|
||||
@@ -217,6 +218,11 @@
|
||||
<i class="ti ti-upload me-1"></i>Medien
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a href="#info-{{ screen }}" class="nav-link" data-bs-toggle="tab" role="tab">
|
||||
<i class="ti ti-info-circle me-1"></i>Info
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content mt-3">
|
||||
@@ -473,6 +479,35 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- ═══ Tab: Info (Client-Informationen) ═══ -->
|
||||
<div class="tab-pane" id="info-{{ screen }}" role="tabpanel">
|
||||
<h3>Client-Informationen</h3>
|
||||
{% if client_info[screen] %}
|
||||
<table class="table table-sm">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th style="width:140px">IP-Adresse</th>
|
||||
<td><code>{{ client_info[screen].get('ip', '') }}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Browser</th>
|
||||
<td style="word-break:break-all"><small>{{ client_info[screen].get('ua', '') }}</small></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Auflösung</th>
|
||||
<td>{{ client_info[screen].get('resolution', '') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Zuletzt gesehen</th>
|
||||
<td>{{ client_info[screen].get('last_seen', '') }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="text-muted">Noch keine Client-Daten vorhanden. Der Player muss mindestens einmal die Playlist aufgerufen haben.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div> <!-- /tab-content -->
|
||||
|
||||
</div> <!-- /card-body -->
|
||||
|
||||
@@ -41,9 +41,36 @@
|
||||
|
||||
<div class="page-wrapper">
|
||||
<div class="container-xl mt-4">
|
||||
<div class="row g-4">
|
||||
<!-- Sidebar Navigation -->
|
||||
<div class="col-md-3">
|
||||
<div class="card" style="position:sticky;top:1rem;">
|
||||
<div class="card-header"><h4 class="card-title" style="font-size:0.9rem;"><i class="ti ti-menu-2 me-1"></i>Themen</h4></div>
|
||||
<div class="card-body p-2">
|
||||
<nav class="nav flex-column">
|
||||
<a class="nav-link py-1 px-2 small" href="#help-standort"><i class="ti ti-building-community me-1"></i>Standort</a>
|
||||
<a class="nav-link py-1 px-2 small" href="#help-screen"><i class="ti ti-devices me-1"></i>Screen</a>
|
||||
<a class="nav-link py-1 px-2 small" href="#help-playlist"><i class="ti ti-list me-1"></i>Playlist</a>
|
||||
<a class="nav-link py-1 px-2 small" href="#help-priority"><i class="ti ti-player-play me-1"></i>Priority</a>
|
||||
<a class="nav-link py-1 px-2 small" href="#help-actions"><i class="ti ti-external-link me-1"></i>Action Button</a>
|
||||
<a class="nav-link py-1 px-2 small" href="#help-voice"><i class="ti ti-robot me-1"></i>Voice Agent</a>
|
||||
<a class="nav-link py-1 px-2 small" href="#help-einstellungen"><i class="ti ti-settings me-1"></i>Einstellungen</a>
|
||||
<a class="nav-link py-1 px-2 small" href="#help-info"><i class="ti ti-info-circle me-1"></i>Info-Tab</a>
|
||||
<a class="nav-link py-1 px-2 small" href="#help-willkommen"><i class="ti ti-users me-1"></i>Willkommen</a>
|
||||
<a class="nav-link py-1 px-2 small" href="#help-userverwaltung"><i class="ti ti-shield me-1"></i>User</a>
|
||||
<a class="nav-link py-1 px-2 small" href="#help-dashboard"><i class="ti ti-dashboard me-1"></i>Dashboard</a>
|
||||
<a class="nav-link py-1 px-2 small" href="#help-darkmode"><i class="ti ti-moon me-1"></i>Dark Mode</a>
|
||||
<a class="nav-link py-1 px-2 small" href="#help-passwort"><i class="ti ti-key me-1"></i>Passwort</a>
|
||||
<a class="nav-link py-1 px-2 small" href="#help-mfa"><i class="ti ti-shield-lock me-1"></i>MFA</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Main Content -->
|
||||
<div class="col-md-9">
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><h3 class="card-title"><i class="ti ti-building-community me-1"></i>Standort anlegen</h3></div>
|
||||
<div class="card-header"><h3 class="card-title" id="help-standort"><i class="ti ti-building-community me-1"></i>Standort anlegen</h3></div>
|
||||
<div class="card-body">
|
||||
<p>Ein Standort (Site) gruppiert mehrere Screens, z. B. <code>stuttgart</code> oder <code>karlsruhe</code>.</p>
|
||||
<ol>
|
||||
@@ -56,7 +83,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><h3 class="card-title"><i class="ti ti-devices me-1"></i>Screen hinzufügen</h3></div>
|
||||
<div class="card-header"><h3 class="card-title" id="help-screen"><i class="ti ti-devices me-1"></i>Screen hinzufügen</h3></div>
|
||||
<div class="card-body">
|
||||
<p>Ein Screen ist ein einzelner Player (z. B. <code>lobby</code>, <code>casino</code>), der eine Playlist abspielt.</p>
|
||||
<ol>
|
||||
@@ -69,7 +96,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><h3 class="card-title"><i class="ti ti-list me-1"></i>Playlist verwalten</h3></div>
|
||||
<div class="card-header"><h3 class="card-title" id="help-playlist"><i class="ti ti-list me-1"></i>Playlist verwalten</h3></div>
|
||||
<div class="card-body">
|
||||
<p>Jeder Screen hat eine eigene Playlist mit Medien und URLs.</p>
|
||||
<ul>
|
||||
@@ -84,7 +111,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><h3 class="card-title"><i class="ti ti-player-play me-1"></i>Priority-Playlist</h3></div>
|
||||
<div class="card-header"><h3 class="card-title" id="help-priority"><i class="ti ti-player-play me-1"></i>Priority-Playlist</h3></div>
|
||||
<div class="card-body">
|
||||
<p>Die Priority-Playlist ist <strong>global</strong> und wird auf <em>allen</em> Playern eines Standorts eingeblendet – zwischen den normalen Playlist-Einträgen.</p>
|
||||
<ul>
|
||||
@@ -96,7 +123,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><h3 class="card-title"><i class="ti ti-external-link me-1"></i>Action Button (Custom-URL)</h3></div>
|
||||
<div class="card-header"><h3 class="card-title" id="help-actions"><i class="ti ti-external-link me-1"></i>Action Button (Custom-URL)</h3></div>
|
||||
<div class="card-body">
|
||||
<p>Der Action Button ist ein konfigurierbarer Button im Player, der eine frei wählbare URL öffnet.</p>
|
||||
<ul>
|
||||
@@ -116,7 +143,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><h3 class="card-title"><i class="ti ti-robot me-1"></i>Digital Voice Agent</h3></div>
|
||||
<div class="card-header"><h3 class="card-title" id="help-voice"><i class="ti ti-robot me-1"></i>Digital Voice Agent</h3></div>
|
||||
<div class="card-body">
|
||||
<p>Der Digital Voice Agent zeigt einen Button im Player, der eine Typewriter-Animation mit wechselnden mehrsprachigen Texten anzeigt.</p>
|
||||
<ul>
|
||||
@@ -132,7 +159,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><h3 class="card-title"><i class="ti ti-settings me-1"></i>Screen-Einstellungen</h3></div>
|
||||
<div class="card-header"><h3 class="card-title" id="help-einstellungen"><i class="ti ti-settings me-1"></i>Screen-Einstellungen</h3></div>
|
||||
<div class="card-body">
|
||||
<p>Im Tab <strong>Einstellungen</strong> können pro Screen konfiguriert werden:</p>
|
||||
<ul>
|
||||
@@ -145,7 +172,21 @@
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><h3 class="card-title"><i class="ti ti-users me-1"></i>Willkommensseite</h3></div>
|
||||
<div class="card-header"><h3 class="card-title" id="help-info"><i class="ti ti-info-circle me-1"></i>Info-Tab im Screen</h3></div>
|
||||
<div class="card-body">
|
||||
<p>Jeder Screen hat einen <strong>Info-Tab</strong> (Tab 6), der detaillierte Client-Informationen zum aktuell verbundenen Player anzeigt:</p>
|
||||
<ul>
|
||||
<li><strong>IP-Adresse</strong> des Players</li>
|
||||
<li><strong>Browser</strong> (User-Agent) des Players</li>
|
||||
<li><strong>Bildschirmauflösung</strong> (Breite × Höhe)</li>
|
||||
<li><strong>Zuletzt gesehen</strong> – Zeitstempel des letzten Heartbeats</li>
|
||||
</ul>
|
||||
<p class="text-muted small mb-0"><i class="ti ti-info-circle me-1"></i>Die Daten werden alle 5 Sekunden vom Player gesendet und im Heartbeat-JSON gespeichert. Der Online-Status (grüner/roter Button im Screen-Header) basiert auf dem gleichen Mechanismus.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><h3 class="card-title" id="help-willkommen"><i class="ti ti-users me-1"></i>Willkommensseite</h3></div>
|
||||
<div class="card-body">
|
||||
<p>Auf der Willkommensseite können bis zu 3 Kundennamen eingegeben werden. Die Logos werden automatisch via OpenAI + Brandfetch gesucht.</p>
|
||||
<ul>
|
||||
@@ -159,7 +200,7 @@
|
||||
|
||||
{% if current_user.is_admin %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><h3 class="card-title"><i class="ti ti-shield me-1"></i>User-Verwaltung (Admin)</h3></div>
|
||||
<div class="card-header"><h3 class="card-title" id="help-userverwaltung"><i class="ti ti-shield me-1"></i>User-Verwaltung (Admin)</h3></div>
|
||||
<div class="card-body">
|
||||
<p>Admins können über <code>/admin/users</code> die User-Verwaltung aufrufen.</p>
|
||||
<ul>
|
||||
@@ -179,7 +220,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><h3 class="card-title"><i class="ti ti-dashboard me-1"></i>Admin Dashboard</h3></div>
|
||||
<div class="card-header"><h3 class="card-title" id="help-dashboard"><i class="ti ti-dashboard me-1"></i>Admin Dashboard</h3></div>
|
||||
<div class="card-body">
|
||||
<p>Das Admin Dashboard (<code>/admin/dashboard</code>) bietet eine Übersicht über das gesamte System:</p>
|
||||
<ul>
|
||||
@@ -193,24 +234,40 @@
|
||||
{% endif %}
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><h3 class="card-title"><i class="ti ti-moon me-1"></i>Dark Mode</h3></div>
|
||||
<div class="card-header"><h3 class="card-title" id="help-darkmode"><i class="ti ti-moon me-1"></i>Dark Mode</h3></div>
|
||||
<div class="card-body">
|
||||
<p>Der Dark Mode kann über den Mond-Button im Header umgeschaltet werden. Die Einstellung wird im Browser gespeichert (localStorage) und bleibt auch nach Seitenwechsel erhalten.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><h3 class="card-title"><i class="ti ti-key me-1"></i>Passwort ändern</h3></div>
|
||||
<div class="card-header"><h3 class="card-title" id="help-passwort"><i class="ti ti-key me-1"></i>Passwort ändern</h3></div>
|
||||
<div class="card-body">
|
||||
<p>Das Passwort kann über das User-Menü (oben rechts) → <strong>Passwort ändern</strong> geändert werden. Nach einem Admin-Reset muss der User beim ersten Login ein neues Passwort vergeben.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mb-3">
|
||||
<div class="card-header"><h3 class="card-title" id="help-mfa"><i class="ti ti-shield-lock me-1"></i>MFA (Multi-Faktor-Authentifizierung)</h3></div>
|
||||
<div class="card-body">
|
||||
<p>MFA bietet eine optionale zweite Sicherheitsstufe per TOTP (Google Authenticator, Authy).</p>
|
||||
<ul>
|
||||
<li><strong>Aktivierung:</strong> Über das User-Menü → <strong>MFA einrichten</strong> → QR-Code mit einer Authenticator-App scannen → 6-stelligen Code eingeben → Bestätigen</li>
|
||||
<li><strong>Recovery-Codes:</strong> Nach erfolgreicher Aktivierung werden 8 Recovery-Codes (einmalig) angezeigt. Jeder Code kann nur einmal verwendet werden. Nach Einsatz eines Recovery-Codes muss MFA neu eingerichtet werden.</li>
|
||||
<li><strong>Login mit MFA:</strong> Nach Eingabe von E-Mail + Passwort erscheint ein zweites Formular für den TOTP-Code. Erst nach erfolgreichem Code wird der Zugriff gewährt.</li>
|
||||
<li><strong>Deaktivierung:</strong> Über <code>/mfa/setup</code> → Button "MFA deaktivieren" → aktuelles Passwort eingeben</li>
|
||||
<li><strong>MFA-Status:</strong> In der Userliste (<code>/admin/users</code>) wird der MFA-Status als grünes "Aktiv"-Badge oder "–" angezeigt.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- /col-md-9 main -->
|
||||
</div> <!-- /row -->
|
||||
|
||||
</div> <!-- /container-xl -->
|
||||
</div> <!-- /page-wrapper -->
|
||||
|
||||
{% include "_footer.html" %}
|
||||
</div>
|
||||
|
||||
</div> <!-- /page -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,50 +2,60 @@
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>CANCOM Simple Signage Admin</title>
|
||||
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||
<link href="https://unpkg.com/@tabler/core@1.0.0-beta20/dist/css/tabler.min.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: #F4F6F8;
|
||||
color: #212121;
|
||||
}
|
||||
.btn-primary {
|
||||
background-color: #DA002D;
|
||||
border-color: #DA002D;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background-color: #B00024;
|
||||
border-color: #B00024;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet"
|
||||
href="https://unpkg.com/@tabler/core@1.0.0-beta20/dist/css/tabler.min.css">
|
||||
<link rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css">
|
||||
{% include "_styles.html" %}
|
||||
<script>
|
||||
(() => {
|
||||
const stored = window.localStorage.getItem("signage-theme");
|
||||
const theme = stored === "dark" || stored === "light" ? stored : "light";
|
||||
document.documentElement.setAttribute("data-bs-theme", theme);
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
|
||||
<!-- ─── Login-Seite (zentriert) ─── -->
|
||||
<body class="d-flex align-items-center justify-content-center" style="height:100vh;">
|
||||
<div class="card card-md">
|
||||
<div class="card-body">
|
||||
<h2 class="text-center mb-4">Signage Admin</h2>
|
||||
{% include "_header.html" %}
|
||||
|
||||
{% if error %}
|
||||
<div class="alert alert-danger text-center" role="alert">
|
||||
{{ error }}
|
||||
<div class="page-wrapper d-flex align-items-center justify-content-center">
|
||||
<div class="container-xl">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-5">
|
||||
<div class="card card-md">
|
||||
<div class="card-body">
|
||||
<h2 class="text-center mb-4">Signage Admin</h2>
|
||||
|
||||
{% if error %}
|
||||
<div class="alert alert-danger text-center" role="alert">
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">E-Mail / Benutzername</label>
|
||||
<input class="form-control" name="username" placeholder="admin" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Passwort</label>
|
||||
<input type="password" class="form-control" name="password" required>
|
||||
</div>
|
||||
<button class="btn btn-primary w-100">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">E-Mail / Benutzername</label>
|
||||
<input class="form-control" name="username" placeholder="admin" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Passwort</label>
|
||||
<input type="password" class="form-control" name="password" required>
|
||||
</div>
|
||||
<button class="btn btn-primary w-100">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include "_footer.html" %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
64
templates/mfa_recovery.html
Normal file
64
templates/mfa_recovery.html
Normal file
@@ -0,0 +1,64 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>MFA eingerichtet – CANCOM Signage</title>
|
||||
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const stored = window.localStorage.getItem("signage-theme");
|
||||
const theme = stored === "dark" || stored === "light" ? stored : "light";
|
||||
document.documentElement.setAttribute("data-bs-theme", theme);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/@tabler/core@1.0.0-beta20/dist/css/tabler.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css">
|
||||
<script defer src="https://unpkg.com/@tabler/core@1.0.0-beta20/dist/js/tabler.min.js"></script>
|
||||
|
||||
{% include '_styles.html' %}
|
||||
</head>
|
||||
<body>
|
||||
|
||||
{% include '_header.html' %}
|
||||
|
||||
<div class="page-wrapper">
|
||||
<div class="page-body" style="min-height:calc(100vh - 120px);display:flex;align-items:center;justify-content:center;">
|
||||
<div class="container-xl">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8 col-xl-6">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
<h2 class="mb-2">✅ MFA aktiviert</h2>
|
||||
<p class="text-muted mb-3">
|
||||
Speichere diese Recovery-Codes an einem sicheren Ort.<br>
|
||||
Jeder Code kann <strong>einmal</strong> verwendet werden.
|
||||
</p>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<strong>⚠️ Wichtig:</strong> Diese Codes werden nur <strong>einmal</strong> angezeigt.
|
||||
</div>
|
||||
|
||||
<div class="my-4 d-flex flex-wrap justify-content-center gap-2">
|
||||
{% for code in codes %}
|
||||
<kbd style="font-size:1.1rem;padding:8px 16px;border-radius:6px;user-select:all;cursor:pointer;" onclick="navigator.clipboard.writeText(this.textContent);this.textContent='✓ Kopiert!';setTimeout(()=>{this.textContent='{{ code }}';},1500);">{{ code }}</kbd>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<p class="text-muted small mb-4">Klicke auf einen Code, um ihn zu kopieren.</p>
|
||||
<a href="/admin" class="btn btn-primary">← Zum Admin</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include '_footer.html' %}
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
101
templates/mfa_setup.html
Normal file
101
templates/mfa_setup.html
Normal file
@@ -0,0 +1,101 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>MFA einrichten – CANCOM Signage</title>
|
||||
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const stored = window.localStorage.getItem("signage-theme");
|
||||
const theme = stored === "dark" || stored === "light" ? stored : "light";
|
||||
document.documentElement.setAttribute("data-bs-theme", theme);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/@tabler/core@1.0.0-beta20/dist/css/tabler.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css">
|
||||
<script defer src="https://unpkg.com/@tabler/core@1.0.0-beta20/dist/js/tabler.min.js"></script>
|
||||
|
||||
{% include '_styles.html' %}
|
||||
</head>
|
||||
<body>
|
||||
|
||||
{% include '_header.html' %}
|
||||
|
||||
<div class="page-wrapper">
|
||||
<div class="page-body" style="min-height:calc(100vh - 120px);display:flex;align-items:center;justify-content:center;">
|
||||
<div class="container-xl">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8 col-xl-6">
|
||||
|
||||
{% if recovered %}
|
||||
<div class="alert alert-warning">
|
||||
<strong>Hinweis:</strong> Du hast einen Recovery-Code verwendet. Bitte richte MFA neu ein.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if disabled %}
|
||||
<div class="alert alert-info">MFA wurde deaktiviert.</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
|
||||
{% if current_user.mfa_enabled %}
|
||||
<h2 class="mb-2">✅ MFA ist aktiv</h2>
|
||||
<p class="text-muted mb-4">Dein Konto ist durch Zwei-Faktor-Authentifizierung geschützt.</p>
|
||||
<p class="text-muted small mb-2">Möchtest du MFA deaktivieren? Gib dein Passwort ein:</p>
|
||||
<form method="post" action="/mfa/disable" class="row justify-content-center">
|
||||
<div class="col-auto">
|
||||
<div class="input-group">
|
||||
<input type="password" name="password" class="form-control" placeholder="Passwort" required>
|
||||
<button class="btn btn-danger" type="submit">Deaktivieren</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<h2 class="mb-2">MFA einrichten</h2>
|
||||
<p class="text-muted mb-4">Scanne den QR-Code mit deiner Authenticator-App (z. B. Google Authenticator, Authy).</p>
|
||||
|
||||
{% if error %}
|
||||
<div class="alert alert-danger" role="alert">{{ error }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if qr_data %}
|
||||
<div class="mb-4">
|
||||
<img src="data:image/png;base64,{{ qr_data }}" alt="QR-Code"
|
||||
style="width:220px;height:220px;border:1px solid #ddd;border-radius:8px;">
|
||||
</div>
|
||||
<p class="text-muted small">Alternativ: Secret manuell eingeben</p>
|
||||
<div class="mb-4">
|
||||
<kbd style="font-size:1.1rem;padding:6px 14px;border-radius:6px;user-select:all;">{{ secret }}</kbd>
|
||||
</div>
|
||||
<hr class="my-4" style="max-width:300px;margin-left:auto;margin-right:auto;">
|
||||
<h4>Code verifizieren</h4>
|
||||
<p class="text-muted small mb-3">Gib den 6-stelligen Code aus der App ein, um MFA zu aktivieren.</p>
|
||||
<form method="post" class="row justify-content-center">
|
||||
<div class="col-auto">
|
||||
<div class="d-flex gap-2">
|
||||
<input type="text" name="code" class="form-control form-control-lg text-center"
|
||||
placeholder="000000" inputmode="numeric" maxlength="6"
|
||||
pattern="[0-9]{6}" required autofocus style="max-width:160px;">
|
||||
<button class="btn btn-success" type="submit">Aktivieren</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include '_footer.html' %}
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
70
templates/mfa_verify.html
Normal file
70
templates/mfa_verify.html
Normal file
@@ -0,0 +1,70 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Sicherheitscode eingeben - CANCOM Signage</title>
|
||||
<link rel="stylesheet"
|
||||
href="https://unpkg.com/@tabler/core@1.0.0-beta20/dist/css/tabler.min.css">
|
||||
<link rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css">
|
||||
{% include "_styles.html" %}
|
||||
<script>
|
||||
(() => {
|
||||
const stored = window.localStorage.getItem("signage-theme");
|
||||
const theme = stored === "dark" || stored === "light" ? stored : "light";
|
||||
document.documentElement.setAttribute("data-bs-theme", theme);
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
|
||||
{% include "_header.html" %}
|
||||
|
||||
<div class="page-wrapper d-flex align-items-center justify-content-center">
|
||||
<div class="container-xl">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-5">
|
||||
<div class="card card-md">
|
||||
<div class="card-body">
|
||||
<h2 class="text-center mb-2">Sicherheitscode</h2>
|
||||
<p class="text-center text-muted mb-4">Gib den 6-stelligen Code aus deiner Authenticator-App ein.</p>
|
||||
|
||||
{% if error %}
|
||||
<div class="alert alert-danger text-center" role="alert">{{ error }}</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
<input type="text" name="code" class="form-control form-control-lg text-center"
|
||||
placeholder="000000" inputmode="numeric" autocomplete="one-time-code"
|
||||
maxlength="6" pattern="[0-9]{6}" required autofocus>
|
||||
</div>
|
||||
<button class="btn btn-primary w-100 mb-2" type="submit">Bestätigen</button>
|
||||
</form>
|
||||
|
||||
<p class="text-center text-muted mt-3 small">
|
||||
<a href="#" onclick="document.getElementById('recovery-form').style.display='block';this.style.display='none';return false;">
|
||||
Code verloren?
|
||||
</a>
|
||||
</p>
|
||||
<form id="recovery-form" method="post" style="display:none;">
|
||||
<input type="hidden" name="action" value="recovery">
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Recovery-Code eingeben</label>
|
||||
<input type="text" name="code" class="form-control" placeholder="ABCD-123">
|
||||
</div>
|
||||
<button class="btn btn-warning w-100" type="submit">Mit Recovery-Code anmelden</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include "_footer.html" %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -488,7 +488,9 @@ let lastHash = null;
|
||||
|
||||
async function checkForUpdates() {
|
||||
try {
|
||||
const res = await fetch(`/playlist/${site}/${screen}/hash`, { cache: "no-store" });
|
||||
const w = window.screen ? window.screen.width : '?';
|
||||
const h = window.screen ? window.screen.height : '?';
|
||||
const res = await fetch(`/playlist/${site}/${screen}/hash?w=${w}&h=${h}`, { cache: "no-store" });
|
||||
const data = await res.json();
|
||||
const hash = data.hash;
|
||||
const serverStayOnFirst = data.stay_on_first;
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
<th>Rolle</th>
|
||||
<th>Standorte</th>
|
||||
<th>Abteilung</th>
|
||||
<th>MFA</th>
|
||||
<th>Status</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
@@ -106,6 +107,13 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-muted small">{{ u.department }}</td>
|
||||
<td>
|
||||
{% if u.mfa_enabled %}
|
||||
<span class="badge bg-green text-green-fg">Aktiv</span>
|
||||
{% else %}
|
||||
<span class="text-muted">–</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if u.must_change_password %}
|
||||
<span class="badge bg-warning text-white">Passwort-Änderung erforderlich</span>
|
||||
|
||||
Reference in New Issue
Block a user