Session-Timeout nach 5min.
Dieser Commit ist enthalten in:
@@ -41,7 +41,10 @@
|
|||||||
"Bash(docker exec:*)",
|
"Bash(docker exec:*)",
|
||||||
"Bash(rm:*)",
|
"Bash(rm:*)",
|
||||||
"Bash(mv:*)",
|
"Bash(mv:*)",
|
||||||
"Bash(docker-compose restart:*)"
|
"Bash(docker-compose restart:*)",
|
||||||
|
"Bash(find:*)",
|
||||||
|
"Bash(docker network:*)",
|
||||||
|
"Bash(curl:*)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
|
|||||||
116
JOURNAL.md
116
JOURNAL.md
@@ -687,4 +687,118 @@ Die Session-Daten werden erst gefüllt, wenn der License Server API implementier
|
|||||||
**Hinweis für Produktion:**
|
**Hinweis für Produktion:**
|
||||||
- CAPTCHA-Keys müssen in .env konfiguriert werden
|
- CAPTCHA-Keys müssen in .env konfiguriert werden
|
||||||
- E-Mail-Server für Benachrichtigungen einrichten
|
- E-Mail-Server für Benachrichtigungen einrichten
|
||||||
- Rate-Limits können über Konstanten angepasst werden
|
- Rate-Limits können über Konstanten angepasst werden
|
||||||
|
|
||||||
|
### 2025-06-07 - Session-Timeout mit Live-Timer implementiert
|
||||||
|
- 5 Minuten Inaktivitäts-Timeout mit visueller Countdown-Anzeige
|
||||||
|
- Automatische Session-Verlängerung bei Benutzeraktivität
|
||||||
|
|
||||||
|
**Implementierte Features:**
|
||||||
|
1. **Session-Timeout Backend:**
|
||||||
|
- Flask Session-Timeout auf 5 Minuten konfiguriert
|
||||||
|
- Heartbeat-Endpoint für Keep-Alive
|
||||||
|
- Automatisches Session-Update bei jeder Aktion
|
||||||
|
|
||||||
|
2. **Live-Timer in der Navbar:**
|
||||||
|
- Countdown von 5:00 bis 0:00
|
||||||
|
- Position: Zwischen Logo und Username
|
||||||
|
- Farbwechsel nach verbleibender Zeit:
|
||||||
|
- Grün: > 2 Minuten
|
||||||
|
- Gelb: 1-2 Minuten
|
||||||
|
- Rot: < 1 Minute
|
||||||
|
- Blinkend: < 30 Sekunden
|
||||||
|
|
||||||
|
3. **Benutzerinteraktion:**
|
||||||
|
- Timer wird bei jeder Aktivität zurückgesetzt
|
||||||
|
- Tracking von: Klicks, Tastatureingaben, Mausbewegungen
|
||||||
|
- Automatischer Heartbeat bei Aktivität
|
||||||
|
- Warnung bei < 1 Minute mit "Session verlängern" Button
|
||||||
|
|
||||||
|
4. **Base-Template System:**
|
||||||
|
- Neue base.html als Basis für alle Admin-Seiten
|
||||||
|
- Alle Templates (außer login.html) nutzen jetzt base.html
|
||||||
|
- Einheitliches Layout und Timer auf allen Seiten
|
||||||
|
|
||||||
|
**Neue/Geänderte Dateien:**
|
||||||
|
- v2_adminpanel/app.py (Session-Konfiguration, Heartbeat-Endpoint)
|
||||||
|
- v2_adminpanel/templates/base.html (neu - Base-Template mit Timer)
|
||||||
|
- Alle anderen Templates aktualisiert für Template-Vererbung
|
||||||
|
|
||||||
|
**Technische Details:**
|
||||||
|
- JavaScript-basierter Countdown-Timer
|
||||||
|
- AJAX-Heartbeat alle 5 Sekunden bei Aktivität
|
||||||
|
- LocalStorage für Tab-Synchronisation möglich
|
||||||
|
- Automatischer Logout bei 0:00
|
||||||
|
- Fetch-Interceptor für automatische Session-Verlängerung
|
||||||
|
|
||||||
|
**Sicherheitsverbesserung:**
|
||||||
|
- Automatischer Logout nach 5 Minuten Inaktivität
|
||||||
|
- Verhindert vergessene Sessions
|
||||||
|
- Visuelles Feedback für Session-Status
|
||||||
|
|
||||||
|
### 2025-06-07 - Session-Timeout Bug behoben
|
||||||
|
- Problem: Session-Timeout funktionierte nicht korrekt - Session blieb länger als 5 Minuten aktiv
|
||||||
|
- Ursache: login_required Decorator aktualisierte last_activity bei JEDEM Request
|
||||||
|
|
||||||
|
**Durchgeführte Änderungen:**
|
||||||
|
1. **login_required Decorator (app.py):**
|
||||||
|
- Prüft jetzt ob Session abgelaufen ist (5 Minuten seit last_activity)
|
||||||
|
- Aktualisiert last_activity NICHT mehr automatisch
|
||||||
|
- Führt AUTO_LOGOUT mit Audit-Log bei Timeout durch
|
||||||
|
- Speichert Username vor session.clear() für korrektes Logging
|
||||||
|
|
||||||
|
2. **Heartbeat-Endpoint (app.py):**
|
||||||
|
- Geändert zu POST-only Endpoint
|
||||||
|
- Aktualisiert explizit last_activity wenn aufgerufen
|
||||||
|
- Wird nur bei aktiver Benutzerinteraktion aufgerufen
|
||||||
|
|
||||||
|
3. **Frontend Timer (base.html):**
|
||||||
|
- Heartbeat wird als POST Request gesendet
|
||||||
|
- trackActivity() ruft extendSession() ohne vorheriges resetTimer() auf
|
||||||
|
- Timer wird erst nach erfolgreichem Heartbeat zurückgesetzt
|
||||||
|
- AJAX Interceptor ignoriert Heartbeat-Requests
|
||||||
|
|
||||||
|
4. **Audit-Log Erweiterung:**
|
||||||
|
- Neue Aktion AUTO_LOGOUT hinzugefügt
|
||||||
|
- Orange Farbcodierung (#fd7e14)
|
||||||
|
- Zeigt Grund des Timeouts im Audit-Log
|
||||||
|
|
||||||
|
**Ergebnis:**
|
||||||
|
- ✅ Session läuft nach exakt 5 Minuten Inaktivität ab
|
||||||
|
- ✅ Benutzeraktivität verlängert Session korrekt
|
||||||
|
- ✅ AUTO_LOGOUT wird im Audit-Log protokolliert
|
||||||
|
- ✅ Visueller Timer zeigt verbleibende Zeit
|
||||||
|
|
||||||
|
### 2025-06-07 - Session-Timeout weitere Verbesserungen
|
||||||
|
- Zusätzliche Fixes nach Test-Feedback implementiert
|
||||||
|
|
||||||
|
**Weitere durchgeführte Änderungen:**
|
||||||
|
1. **Fehlender Import behoben:**
|
||||||
|
- `flash` zu Flask-Imports hinzugefügt für Timeout-Warnmeldungen
|
||||||
|
|
||||||
|
2. **Session-Cookie-Konfiguration erweitert (app.py):**
|
||||||
|
- SESSION_COOKIE_HTTPONLY = True (Sicherheit gegen XSS)
|
||||||
|
- SESSION_COOKIE_SECURE = False (intern HTTP, extern HTTPS via Nginx)
|
||||||
|
- SESSION_COOKIE_SAMESITE = 'Lax' (CSRF-Schutz)
|
||||||
|
- SESSION_COOKIE_NAME = 'admin_session' (eindeutiger Name)
|
||||||
|
- SESSION_REFRESH_EACH_REQUEST = False (verhindert automatische Verlängerung)
|
||||||
|
|
||||||
|
3. **Session-Handling verbessert:**
|
||||||
|
- Entfernt: session.permanent = True aus login_required decorator
|
||||||
|
- Hinzugefügt: session.modified = True im Heartbeat für explizites Speichern
|
||||||
|
- Debug-Logging für Session-Timeout-Prüfung hinzugefügt
|
||||||
|
|
||||||
|
4. **Nginx-Konfiguration:**
|
||||||
|
- Bereits korrekt konfiguriert für Heartbeat-Weiterleitung
|
||||||
|
- Proxy-Headers für korrekte IP-Weitergabe
|
||||||
|
|
||||||
|
**Technische Details:**
|
||||||
|
- Flask-Session mit Filesystem-Backend nutzt jetzt korrekte Cookie-Einstellungen
|
||||||
|
- Session-Cookie wird nicht mehr automatisch bei jedem Request verlängert
|
||||||
|
- Explizite Session-Modifikation nur bei Heartbeat-Requests
|
||||||
|
- Debug-Logs zeigen Zeit seit letzter Aktivität für Troubleshooting
|
||||||
|
|
||||||
|
**Status:**
|
||||||
|
- ✅ Session-Timeout-Mechanismus vollständig implementiert
|
||||||
|
- ✅ Debug-Logging für Session-Überwachung aktiv
|
||||||
|
- ✅ Cookie-Sicherheitseinstellungen optimiert
|
||||||
5
v2/cookies.txt
Normale Datei
5
v2/cookies.txt
Normale Datei
@@ -0,0 +1,5 @@
|
|||||||
|
# Netscape HTTP Cookie File
|
||||||
|
# https://curl.se/docs/http-cookies.html
|
||||||
|
# This file was generated by libcurl! Edit at your own risk.
|
||||||
|
|
||||||
|
#HttpOnly_localhost FALSE / FALSE 1749327638 admin_session hlywIUxTA0lRA4PyUOO2OrC5YNt7-2__FEVhP7H_Jac
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import psycopg2
|
import psycopg2
|
||||||
from psycopg2.extras import Json
|
from psycopg2.extras import Json
|
||||||
from flask import Flask, render_template, request, redirect, session, url_for, send_file, jsonify
|
from flask import Flask, render_template, request, redirect, session, url_for, send_file, jsonify, flash
|
||||||
from flask_session import Session
|
from flask_session import Session
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
@@ -25,6 +25,13 @@ app.config['SECRET_KEY'] = os.urandom(24)
|
|||||||
app.config['SESSION_TYPE'] = 'filesystem'
|
app.config['SESSION_TYPE'] = 'filesystem'
|
||||||
app.config['JSON_AS_ASCII'] = False # JSON-Ausgabe mit UTF-8
|
app.config['JSON_AS_ASCII'] = False # JSON-Ausgabe mit UTF-8
|
||||||
app.config['JSONIFY_MIMETYPE'] = 'application/json; charset=utf-8'
|
app.config['JSONIFY_MIMETYPE'] = 'application/json; charset=utf-8'
|
||||||
|
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(minutes=5) # 5 Minuten Session-Timeout
|
||||||
|
app.config['SESSION_COOKIE_HTTPONLY'] = True
|
||||||
|
app.config['SESSION_COOKIE_SECURE'] = False # Wird auf True gesetzt wenn HTTPS (intern läuft HTTP)
|
||||||
|
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
|
||||||
|
app.config['SESSION_COOKIE_NAME'] = 'admin_session'
|
||||||
|
# WICHTIG: Session-Cookie soll auch nach 5 Minuten ablaufen
|
||||||
|
app.config['SESSION_REFRESH_EACH_REQUEST'] = False
|
||||||
Session(app)
|
Session(app)
|
||||||
|
|
||||||
# Backup-Konfiguration
|
# Backup-Konfiguration
|
||||||
@@ -57,6 +64,32 @@ def login_required(f):
|
|||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
if 'logged_in' not in session:
|
if 'logged_in' not in session:
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
# Prüfe ob Session abgelaufen ist
|
||||||
|
if 'last_activity' in session:
|
||||||
|
last_activity = datetime.fromisoformat(session['last_activity'])
|
||||||
|
time_since_activity = datetime.now() - last_activity
|
||||||
|
|
||||||
|
# Debug-Logging
|
||||||
|
app.logger.info(f"Session check for {session.get('username', 'unknown')}: "
|
||||||
|
f"Last activity: {last_activity}, "
|
||||||
|
f"Time since: {time_since_activity.total_seconds()} seconds")
|
||||||
|
|
||||||
|
if time_since_activity > timedelta(minutes=5):
|
||||||
|
# Session abgelaufen - Logout
|
||||||
|
username = session.get('username', 'unbekannt')
|
||||||
|
app.logger.info(f"Session timeout for user {username} - auto logout")
|
||||||
|
# Audit-Log für automatischen Logout (vor session.clear()!)
|
||||||
|
try:
|
||||||
|
log_audit('AUTO_LOGOUT', 'session', additional_info={'reason': 'Session timeout (5 minutes)', 'username': username})
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
session.clear()
|
||||||
|
flash('Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an.', 'warning')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
# Aktivität NICHT automatisch aktualisieren
|
||||||
|
# Nur bei expliziten Benutzeraktionen (wird vom Heartbeat gemacht)
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
return decorated_function
|
return decorated_function
|
||||||
|
|
||||||
@@ -533,8 +566,10 @@ def login():
|
|||||||
|
|
||||||
if login_success:
|
if login_success:
|
||||||
# Erfolgreicher Login
|
# Erfolgreicher Login
|
||||||
|
session.permanent = True # Aktiviert das Timeout
|
||||||
session['logged_in'] = True
|
session['logged_in'] = True
|
||||||
session['username'] = username
|
session['username'] = username
|
||||||
|
session['last_activity'] = datetime.now().isoformat()
|
||||||
reset_login_attempts(ip_address)
|
reset_login_attempts(ip_address)
|
||||||
log_audit('LOGIN_SUCCESS', 'user',
|
log_audit('LOGIN_SUCCESS', 'user',
|
||||||
additional_info=f"Erfolgreiche Anmeldung von IP: {ip_address}")
|
additional_info=f"Erfolgreiche Anmeldung von IP: {ip_address}")
|
||||||
@@ -569,6 +604,21 @@ def logout():
|
|||||||
session.pop('username', None)
|
session.pop('username', None)
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@app.route("/heartbeat", methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def heartbeat():
|
||||||
|
"""Endpoint für Session Keep-Alive - aktualisiert last_activity"""
|
||||||
|
# Aktualisiere last_activity nur wenn explizit angefordert
|
||||||
|
session['last_activity'] = datetime.now().isoformat()
|
||||||
|
# Force session save
|
||||||
|
session.modified = True
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'status': 'ok',
|
||||||
|
'last_activity': session['last_activity'],
|
||||||
|
'username': session.get('username')
|
||||||
|
})
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
@login_required
|
@login_required
|
||||||
def dashboard():
|
def dashboard():
|
||||||
|
|||||||
@@ -1,44 +1,35 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Audit-Log - Admin Panel</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
.audit-details {
|
|
||||||
font-size: 0.85em;
|
|
||||||
max-width: 300px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
.json-display {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
padding: 5px;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 0.8em;
|
|
||||||
max-height: 100px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
.action-CREATE { color: #28a745; }
|
|
||||||
.action-UPDATE { color: #007bff; }
|
|
||||||
.action-DELETE { color: #dc3545; }
|
|
||||||
.action-LOGIN { color: #17a2b8; }
|
|
||||||
.action-LOGOUT { color: #6c757d; }
|
|
||||||
.action-EXPORT { color: #ffc107; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="bg-light">
|
|
||||||
<nav class="navbar navbar-dark bg-dark">
|
|
||||||
<div class="container">
|
|
||||||
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<span class="text-white me-3">Angemeldet als: {{ username }}</span>
|
|
||||||
<a href="/logout" class="btn btn-outline-light btn-sm">Abmelden</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
|
{% block title %}Audit-Log{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<style>
|
||||||
|
.audit-details {
|
||||||
|
font-size: 0.85em;
|
||||||
|
max-width: 300px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.json-display {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.8em;
|
||||||
|
max-height: 100px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.action-CREATE { color: #28a745; }
|
||||||
|
.action-UPDATE { color: #007bff; }
|
||||||
|
.action-DELETE { color: #dc3545; }
|
||||||
|
.action-LOGIN { color: #17a2b8; }
|
||||||
|
.action-LOGOUT { color: #6c757d; }
|
||||||
|
.action-AUTO_LOGOUT { color: #fd7e14; }
|
||||||
|
.action-EXPORT { color: #ffc107; }
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2>📋 Audit-Log</h2>
|
<h2>📋 Audit-Log</h2>
|
||||||
@@ -70,6 +61,7 @@
|
|||||||
<option value="DELETE" {% if filter_action == 'DELETE' %}selected{% endif %}>🗑️ Gelöscht</option>
|
<option value="DELETE" {% if filter_action == 'DELETE' %}selected{% endif %}>🗑️ Gelöscht</option>
|
||||||
<option value="LOGIN" {% if filter_action == 'LOGIN' %}selected{% endif %}>🔑 Anmeldung</option>
|
<option value="LOGIN" {% if filter_action == 'LOGIN' %}selected{% endif %}>🔑 Anmeldung</option>
|
||||||
<option value="LOGOUT" {% if filter_action == 'LOGOUT' %}selected{% endif %}>🚪 Abmeldung</option>
|
<option value="LOGOUT" {% if filter_action == 'LOGOUT' %}selected{% endif %}>🚪 Abmeldung</option>
|
||||||
|
<option value="AUTO_LOGOUT" {% if filter_action == 'AUTO_LOGOUT' %}selected{% endif %}>⏰ Auto-Logout</option>
|
||||||
<option value="EXPORT" {% if filter_action == 'EXPORT' %}selected{% endif %}>📥 Export</option>
|
<option value="EXPORT" {% if filter_action == 'EXPORT' %}selected{% endif %}>📥 Export</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,6 +111,7 @@
|
|||||||
{% elif log[3] == 'DELETE' %}🗑️ Gelöscht
|
{% elif log[3] == 'DELETE' %}🗑️ Gelöscht
|
||||||
{% elif log[3] == 'LOGIN' %}🔑 Anmeldung
|
{% elif log[3] == 'LOGIN' %}🔑 Anmeldung
|
||||||
{% elif log[3] == 'LOGOUT' %}🚪 Abmeldung
|
{% elif log[3] == 'LOGOUT' %}🚪 Abmeldung
|
||||||
|
{% elif log[3] == 'AUTO_LOGOUT' %}⏰ Auto-Logout
|
||||||
{% elif log[3] == 'EXPORT' %}📥 Export
|
{% elif log[3] == 'EXPORT' %}📥 Export
|
||||||
{% else %}{{ log[3] }}
|
{% else %}{{ log[3] }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -230,7 +223,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %}
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,27 +1,17 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Backup-Verwaltung - Admin Panel</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
.status-success { color: #28a745; }
|
|
||||||
.status-failed { color: #dc3545; }
|
|
||||||
.status-in_progress { color: #ffc107; }
|
|
||||||
.backup-actions { white-space: nowrap; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="bg-light">
|
|
||||||
<nav class="navbar navbar-dark bg-dark">
|
|
||||||
<div class="container">
|
|
||||||
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<span class="text-white me-3">Angemeldet als: {{ username }}</span>
|
|
||||||
<a href="/logout" class="btn btn-outline-light btn-sm">Abmelden</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
|
{% block title %}Backup-Verwaltung{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<style>
|
||||||
|
.status-success { color: #28a745; }
|
||||||
|
.status-failed { color: #dc3545; }
|
||||||
|
.status-in_progress { color: #ffc107; }
|
||||||
|
.backup-actions { white-space: nowrap; }
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2>💾 Backup-Verwaltung</h2>
|
<h2>💾 Backup-Verwaltung</h2>
|
||||||
@@ -197,8 +187,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
{% block extra_js %}
|
||||||
<script>
|
<script>
|
||||||
function createBackup() {
|
function createBackup() {
|
||||||
const btn = document.getElementById('createBackupBtn');
|
const btn = document.getElementById('createBackupBtn');
|
||||||
@@ -282,5 +273,4 @@ function confirmRestore() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
{% endblock %}
|
||||||
</html>
|
|
||||||
213
v2_adminpanel/templates/base.html
Normale Datei
213
v2_adminpanel/templates/base.html
Normale Datei
@@ -0,0 +1,213 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{% block title %}Admin Panel{% endblock %} - Lizenzverwaltung</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
{% block extra_css %}{% endblock %}
|
||||||
|
<style>
|
||||||
|
#session-timer {
|
||||||
|
font-family: monospace;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-normal {
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-warning {
|
||||||
|
background-color: #ffc107;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-danger {
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
animation: pulse 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-critical {
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
animation: blink 0.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% { opacity: 1; }
|
||||||
|
50% { opacity: 0.7; }
|
||||||
|
100% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
0%, 49% { opacity: 1; }
|
||||||
|
50%, 100% { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-warning-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 9999;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-light">
|
||||||
|
<nav class="navbar navbar-dark bg-dark">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div id="session-timer" class="timer-normal me-3">
|
||||||
|
⏱️ <span id="timer-display">5:00</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-white me-3">Angemeldet als: {{ username }}</span>
|
||||||
|
<a href="/logout" class="btn btn-outline-light btn-sm">Abmelden</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
|
||||||
|
<!-- Session Warning Modal -->
|
||||||
|
<div id="session-warning" class="session-warning-modal" style="display: none;">
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<strong>⚠️ Session läuft ab!</strong><br>
|
||||||
|
Ihre Session läuft in weniger als 1 Minute ab.<br>
|
||||||
|
<button type="button" class="btn btn-sm btn-success mt-2" onclick="extendSession()">
|
||||||
|
Session verlängern
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Session-Timer Konfiguration
|
||||||
|
const SESSION_TIMEOUT = 5 * 60; // 5 Minuten in Sekunden
|
||||||
|
let timeRemaining = SESSION_TIMEOUT;
|
||||||
|
let timerInterval;
|
||||||
|
let warningShown = false;
|
||||||
|
let lastActivity = Date.now();
|
||||||
|
|
||||||
|
// Timer Display Update
|
||||||
|
function updateTimerDisplay() {
|
||||||
|
const minutes = Math.floor(timeRemaining / 60);
|
||||||
|
const seconds = timeRemaining % 60;
|
||||||
|
const display = `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||||||
|
document.getElementById('timer-display').textContent = display;
|
||||||
|
|
||||||
|
// Timer-Farbe ändern
|
||||||
|
const timerElement = document.getElementById('session-timer');
|
||||||
|
timerElement.className = timerElement.className.replace(/timer-\w+/, '');
|
||||||
|
|
||||||
|
if (timeRemaining <= 30) {
|
||||||
|
timerElement.classList.add('timer-critical');
|
||||||
|
} else if (timeRemaining <= 60) {
|
||||||
|
timerElement.classList.add('timer-danger');
|
||||||
|
if (!warningShown) {
|
||||||
|
showSessionWarning();
|
||||||
|
warningShown = true;
|
||||||
|
}
|
||||||
|
} else if (timeRemaining <= 120) {
|
||||||
|
timerElement.classList.add('timer-warning');
|
||||||
|
} else {
|
||||||
|
timerElement.classList.add('timer-normal');
|
||||||
|
warningShown = false;
|
||||||
|
hideSessionWarning();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session Warning anzeigen
|
||||||
|
function showSessionWarning() {
|
||||||
|
document.getElementById('session-warning').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session Warning verstecken
|
||||||
|
function hideSessionWarning() {
|
||||||
|
document.getElementById('session-warning').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timer zurücksetzen
|
||||||
|
function resetTimer() {
|
||||||
|
timeRemaining = SESSION_TIMEOUT;
|
||||||
|
lastActivity = Date.now();
|
||||||
|
updateTimerDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session verlängern
|
||||||
|
function extendSession() {
|
||||||
|
fetch('/heartbeat', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'ok') {
|
||||||
|
resetTimer();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Heartbeat error:', error));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timer Countdown
|
||||||
|
function countdown() {
|
||||||
|
timeRemaining--;
|
||||||
|
updateTimerDisplay();
|
||||||
|
|
||||||
|
if (timeRemaining <= 0) {
|
||||||
|
clearInterval(timerInterval);
|
||||||
|
window.location.href = '/logout';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aktivitäts-Tracking
|
||||||
|
function trackActivity() {
|
||||||
|
const now = Date.now();
|
||||||
|
// Nur wenn mehr als 5 Sekunden seit letzter Aktivität
|
||||||
|
if (now - lastActivity > 5000) {
|
||||||
|
lastActivity = now;
|
||||||
|
extendSession(); // resetTimer() wird in extendSession nach erfolgreicher Response aufgerufen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event Listeners für Benutzeraktivität
|
||||||
|
document.addEventListener('click', trackActivity);
|
||||||
|
document.addEventListener('keypress', trackActivity);
|
||||||
|
document.addEventListener('mousemove', () => {
|
||||||
|
const now = Date.now();
|
||||||
|
// Mausbewegung nur alle 30 Sekunden tracken
|
||||||
|
if (now - lastActivity > 30000) {
|
||||||
|
trackActivity();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// AJAX Interceptor für automatische Session-Verlängerung
|
||||||
|
const originalFetch = window.fetch;
|
||||||
|
window.fetch = function(...args) {
|
||||||
|
// Nur für non-heartbeat requests den Timer verlängern
|
||||||
|
if (!args[0].includes('/heartbeat')) {
|
||||||
|
trackActivity();
|
||||||
|
}
|
||||||
|
return originalFetch.apply(this, args);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Timer starten
|
||||||
|
timerInterval = setInterval(countdown, 1000);
|
||||||
|
updateTimerDisplay();
|
||||||
|
|
||||||
|
// Initial Heartbeat
|
||||||
|
extendSession();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% block extra_js %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,21 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Gesperrte IPs - Admin Panel</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
<body class="bg-light">
|
|
||||||
<nav class="navbar navbar-dark bg-dark">
|
|
||||||
<div class="container">
|
|
||||||
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<span class="text-white me-3">Angemeldet als: {{ username }}</span>
|
|
||||||
<a href="/logout" class="btn btn-outline-light btn-sm">Abmelden</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
|
{% block title %}Gesperrte IPs{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<div class="container py-4">
|
<div class="container py-4">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h1>🔒 Gesperrte IPs</h1>
|
<h1>🔒 Gesperrte IPs</h1>
|
||||||
@@ -110,6 +97,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %}
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,21 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Kundenverwaltung - Admin Panel</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
<body class="bg-light">
|
|
||||||
<nav class="navbar navbar-dark bg-dark">
|
|
||||||
<div class="container">
|
|
||||||
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<span class="text-white me-3">Angemeldet als: {{ username }}</span>
|
|
||||||
<a href="/logout" class="btn btn-outline-light btn-sm">Abmelden</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
|
{% block title %}Kundenverwaltung{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2>Kundenverwaltung</h2>
|
<h2>Kundenverwaltung</h2>
|
||||||
@@ -156,6 +143,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
{% endblock %}
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,32 +1,22 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Dashboard - Admin Panel</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
.stat-card {
|
|
||||||
transition: transform 0.2s;
|
|
||||||
}
|
|
||||||
.stat-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
}
|
|
||||||
.status-aktiv { color: #28a745; }
|
|
||||||
.status-ablaufend { color: #ffc107; }
|
|
||||||
.status-abgelaufen { color: #dc3545; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="bg-light">
|
|
||||||
<nav class="navbar navbar-dark bg-dark">
|
|
||||||
<div class="container">
|
|
||||||
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<span class="text-white me-3">Angemeldet als: {{ username }}</span>
|
|
||||||
<a href="/logout" class="btn btn-outline-light btn-sm">Abmelden</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
|
{% block title %}Dashboard{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<style>
|
||||||
|
.stat-card {
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
.stat-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
.status-aktiv { color: #28a745; }
|
||||||
|
.status-ablaufend { color: #ffc107; }
|
||||||
|
.status-abgelaufen { color: #dc3545; }
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<div class="container py-4">
|
<div class="container py-4">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h1>Dashboard</h1>
|
<h1>Dashboard</h1>
|
||||||
@@ -303,5 +293,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
{% endblock %}
|
||||||
</html>
|
|
||||||
@@ -1,21 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Kunde bearbeiten - Admin Panel</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
<body class="bg-light">
|
|
||||||
<nav class="navbar navbar-dark bg-dark">
|
|
||||||
<div class="container">
|
|
||||||
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<span class="text-white me-3">Angemeldet als: {{ username }}</span>
|
|
||||||
<a href="/logout" class="btn btn-outline-light btn-sm">Abmelden</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
|
{% block title %}Kunde bearbeiten{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2>Kunde bearbeiten</h2>
|
<h2>Kunde bearbeiten</h2>
|
||||||
@@ -108,5 +95,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
{% endblock %}
|
||||||
</html>
|
|
||||||
@@ -1,21 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Lizenz bearbeiten - Admin Panel</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
<body class="bg-light">
|
|
||||||
<nav class="navbar navbar-dark bg-dark">
|
|
||||||
<div class="container">
|
|
||||||
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<span class="text-white me-3">Angemeldet als: {{ username }}</span>
|
|
||||||
<a href="/logout" class="btn btn-outline-light btn-sm">Abmelden</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
|
{% block title %}Lizenz bearbeiten{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2>Lizenz bearbeiten</h2>
|
<h2>Lizenz bearbeiten</h2>
|
||||||
@@ -80,5 +67,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
{% endblock %}
|
||||||
</html>
|
|
||||||
@@ -1,21 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Admin Panel – Lizenzverwaltung</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
<body class="bg-light">
|
|
||||||
<nav class="navbar navbar-dark bg-dark">
|
|
||||||
<div class="container">
|
|
||||||
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<span class="text-white me-3">Angemeldet als: {{ username }}</span>
|
|
||||||
<a href="/logout" class="btn btn-outline-light btn-sm">Abmelden</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
|
{% block title %}Admin Panel{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2>Neue Lizenz erstellen</h2>
|
<h2>Neue Lizenz erstellen</h2>
|
||||||
@@ -65,5 +52,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
{% endblock %}
|
||||||
</html>
|
|
||||||
|
|||||||
@@ -1,26 +1,16 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Lizenzübersicht - Admin Panel</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
.status-aktiv { color: #28a745; }
|
|
||||||
.status-ablaufend { color: #ffc107; }
|
|
||||||
.status-abgelaufen { color: #dc3545; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="bg-light">
|
|
||||||
<nav class="navbar navbar-dark bg-dark">
|
|
||||||
<div class="container">
|
|
||||||
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<span class="text-white me-3">Angemeldet als: {{ username }}</span>
|
|
||||||
<a href="/logout" class="btn btn-outline-light btn-sm">Abmelden</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
|
{% block title %}Lizenzübersicht{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<style>
|
||||||
|
.status-aktiv { color: #28a745; }
|
||||||
|
.status-ablaufend { color: #ffc107; }
|
||||||
|
.status-abgelaufen { color: #dc3545; }
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2>Lizenzübersicht</h2>
|
<h2>Lizenzübersicht</h2>
|
||||||
@@ -208,6 +198,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
{% endblock %}
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,26 +1,16 @@
|
|||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Session-Tracking - Admin Panel</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
.session-active { background-color: #d4edda; }
|
|
||||||
.session-warning { background-color: #fff3cd; }
|
|
||||||
.session-inactive { background-color: #f8f9fa; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="bg-light">
|
|
||||||
<nav class="navbar navbar-dark bg-dark">
|
|
||||||
<div class="container">
|
|
||||||
<span class="navbar-brand">🎛️ Lizenzverwaltung</span>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<span class="text-white me-3">Angemeldet als: {{ username }}</span>
|
|
||||||
<a href="/logout" class="btn btn-outline-light btn-sm">Abmelden</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
|
{% block title %}Session-Tracking{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<style>
|
||||||
|
.session-active { background-color: #d4edda; }
|
||||||
|
.session-warning { background-color: #fff3cd; }
|
||||||
|
.session-inactive { background-color: #f8f9fa; }
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<div class="container py-5">
|
<div class="container py-5">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h2>Session-Tracking</h2>
|
<h2>Session-Tracking</h2>
|
||||||
@@ -137,5 +127,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
{% endblock %}
|
||||||
</html>
|
|
||||||
In neuem Issue referenzieren
Einen Benutzer sperren