Session-Timeout nach 5min.
Dieser Commit ist enthalten in:
@@ -41,7 +41,10 @@
|
||||
"Bash(docker exec:*)",
|
||||
"Bash(rm:*)",
|
||||
"Bash(mv:*)",
|
||||
"Bash(docker-compose restart:*)"
|
||||
"Bash(docker-compose restart:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(docker network:*)",
|
||||
"Bash(curl:*)"
|
||||
],
|
||||
"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:**
|
||||
- CAPTCHA-Keys müssen in .env konfiguriert werden
|
||||
- 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 psycopg2
|
||||
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 functools import wraps
|
||||
from dotenv import load_dotenv
|
||||
@@ -25,6 +25,13 @@ app.config['SECRET_KEY'] = os.urandom(24)
|
||||
app.config['SESSION_TYPE'] = 'filesystem'
|
||||
app.config['JSON_AS_ASCII'] = False # JSON-Ausgabe mit 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)
|
||||
|
||||
# Backup-Konfiguration
|
||||
@@ -57,6 +64,32 @@ def login_required(f):
|
||||
def decorated_function(*args, **kwargs):
|
||||
if 'logged_in' not in session:
|
||||
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 decorated_function
|
||||
|
||||
@@ -533,8 +566,10 @@ def login():
|
||||
|
||||
if login_success:
|
||||
# Erfolgreicher Login
|
||||
session.permanent = True # Aktiviert das Timeout
|
||||
session['logged_in'] = True
|
||||
session['username'] = username
|
||||
session['last_activity'] = datetime.now().isoformat()
|
||||
reset_login_attempts(ip_address)
|
||||
log_audit('LOGIN_SUCCESS', 'user',
|
||||
additional_info=f"Erfolgreiche Anmeldung von IP: {ip_address}")
|
||||
@@ -569,6 +604,21 @@ def logout():
|
||||
session.pop('username', None)
|
||||
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("/")
|
||||
@login_required
|
||||
def dashboard():
|
||||
|
||||
@@ -1,44 +1,35 @@
|
||||
<!DOCTYPE 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>
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% 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="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>📋 Audit-Log</h2>
|
||||
@@ -70,6 +61,7 @@
|
||||
<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="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>
|
||||
</select>
|
||||
</div>
|
||||
@@ -119,6 +111,7 @@
|
||||
{% elif log[3] == 'DELETE' %}🗑️ Gelöscht
|
||||
{% elif log[3] == 'LOGIN' %}🔑 Anmeldung
|
||||
{% elif log[3] == 'LOGOUT' %}🚪 Abmeldung
|
||||
{% elif log[3] == 'AUTO_LOGOUT' %}⏰ Auto-Logout
|
||||
{% elif log[3] == 'EXPORT' %}📥 Export
|
||||
{% else %}{{ log[3] }}
|
||||
{% endif %}
|
||||
@@ -230,7 +223,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
@@ -1,27 +1,17 @@
|
||||
<!DOCTYPE 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>
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% 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="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>💾 Backup-Verwaltung</h2>
|
||||
@@ -197,8 +187,9 @@
|
||||
</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>
|
||||
function createBackup() {
|
||||
const btn = document.getElementById('createBackupBtn');
|
||||
@@ -282,5 +273,4 @@ function confirmRestore() {
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
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>
|
||||
<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>
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Gesperrte IPs{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>🔒 Gesperrte IPs</h1>
|
||||
@@ -110,6 +97,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
@@ -1,21 +1,8 @@
|
||||
<!DOCTYPE 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>
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Kundenverwaltung{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Kundenverwaltung</h2>
|
||||
@@ -156,6 +143,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
@@ -1,32 +1,22 @@
|
||||
<!DOCTYPE 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>
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% 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="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>Dashboard</h1>
|
||||
@@ -303,5 +293,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
@@ -1,21 +1,8 @@
|
||||
<!DOCTYPE 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>
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Kunde bearbeiten{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Kunde bearbeiten</h2>
|
||||
@@ -108,5 +95,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
@@ -1,21 +1,8 @@
|
||||
<!DOCTYPE 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>
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Lizenz bearbeiten{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Lizenz bearbeiten</h2>
|
||||
@@ -80,5 +67,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
@@ -1,21 +1,8 @@
|
||||
<!DOCTYPE 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>
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Admin Panel{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Neue Lizenz erstellen</h2>
|
||||
@@ -65,5 +52,4 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,26 +1,16 @@
|
||||
<!DOCTYPE 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>
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% 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="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Lizenzübersicht</h2>
|
||||
@@ -208,6 +198,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
@@ -1,26 +1,16 @@
|
||||
<!DOCTYPE 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>
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% 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="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Session-Tracking</h2>
|
||||
@@ -137,5 +127,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren