Add latest changes

Dieser Commit ist enthalten in:
2025-07-03 20:38:33 +00:00
Ursprung 63f3d92724
Commit 6f6cde65db
129 geänderte Dateien mit 3998 neuen und 1199 gelöschten Zeilen

Datei anzeigen

@@ -4,6 +4,7 @@ from zoneinfo import ZoneInfo
from pathlib import Path
from flask import Blueprint, render_template, request, redirect, session, url_for, flash, send_file, jsonify, current_app
import requests
import traceback
import config
from config import DATABASE_CONFIG
@@ -12,6 +13,7 @@ from utils.audit import log_audit
from utils.backup import create_backup, restore_backup, create_backup_with_github, create_server_backup
from utils.network import get_client_ip
from db import get_connection, get_db_connection, get_db_cursor, execute_query
from db_license import get_license_db_cursor
from utils.export import create_excel_export, prepare_audit_export_data
# Create Blueprint
@@ -116,9 +118,17 @@ def dashboard():
cur.execute("SELECT COUNT(*) FROM licenses WHERE is_fake = true")
test_licenses_count = cur.fetchone()[0] if cur.rowcount > 0 else 0
# Anzahl aktiver Sessions (Admin-Panel)
cur.execute("SELECT COUNT(*) FROM sessions WHERE is_active = true")
active_sessions = cur.fetchone()[0] if cur.rowcount > 0 else 0
# Anzahl aktiver Sessions aus License Server DB
active_sessions = 0
try:
with get_license_db_cursor() as license_cur:
license_cur.execute("SELECT COUNT(*) FROM license_sessions WHERE ended_at IS NULL")
active_sessions = license_cur.fetchone()[0] if license_cur.rowcount > 0 else 0
except Exception as e:
current_app.logger.warning(f"Could not get active sessions from license server: {str(e)}")
# Fallback auf Admin DB
cur.execute("SELECT COUNT(*) FROM sessions WHERE is_active = true")
active_sessions = cur.fetchone()[0] if cur.rowcount > 0 else 0
# Aktive Nutzung (Kunden-Software) - Lizenzen mit Heartbeats in den letzten 15 Minuten
active_usage = 0
@@ -333,6 +343,52 @@ def dashboard():
except:
pass
# Session-Auslastung (Concurrent Sessions)
try:
cur.execute("""
SELECT
SUM(active_sessions) as total_active_sessions,
SUM(max_concurrent_sessions) as total_max_sessions,
COUNT(CASE WHEN active_sessions >= max_concurrent_sessions THEN 1 END) as at_limit_count
FROM (
SELECT
l.id,
l.max_concurrent_sessions,
(SELECT COUNT(*) FROM license_sessions ls WHERE ls.license_id = l.id) as active_sessions
FROM licenses l
WHERE l.is_fake = false AND l.is_active = true
) session_data
""")
session_stats = cur.fetchone()
if session_stats:
total_active = session_stats[0] or 0
total_max = session_stats[1] or 0
at_limit = session_stats[2] or 0
utilization = int((total_active / total_max * 100)) if total_max > 0 else 0
stats['session_stats'] = {
'total_active_sessions': total_active,
'total_max_sessions': total_max,
'utilization_percent': utilization,
'licenses_at_limit': at_limit
}
else:
stats['session_stats'] = {
'total_active_sessions': 0,
'total_max_sessions': 0,
'utilization_percent': 0,
'licenses_at_limit': 0
}
except Exception as e:
current_app.logger.warning(f"Could not get session statistics: {str(e)}")
stats['session_stats'] = {
'total_active_sessions': 0,
'total_max_sessions': 0,
'utilization_percent': 0,
'licenses_at_limit': 0
}
conn.rollback()
license_distribution = []
hourly_sessions = []
@@ -621,7 +677,12 @@ def create_backup_route():
def restore_backup_route(backup_id):
"""Backup wiederherstellen"""
from flask import jsonify
encryption_key = request.form.get('encryption_key')
# Handle both JSON and form data
if request.is_json:
encryption_key = request.json.get('encryption_key')
else:
encryption_key = request.form.get('encryption_key')
success, message = restore_backup(backup_id, encryption_key)
@@ -967,7 +1028,7 @@ def license_analytics():
AVG(device_count) as avg_usage
FROM licenses l
LEFT JOIN (
SELECT license_id, COUNT(DISTINCT hardware_id) as device_count
SELECT license_id, COUNT(DISTINCT hardware_fingerprint) as device_count
FROM license_heartbeats
WHERE timestamp > NOW() - INTERVAL '30 days'
GROUP BY license_id
@@ -1282,7 +1343,7 @@ def terminate_session(session_id):
# Get session info
cur.execute("""
SELECT license_id, hardware_id, ip_address, client_version, started_at
SELECT license_id, hardware_fingerprint, ip_address, client_version, started_at
FROM license_sessions
WHERE id = %s
""", (session_id,))
@@ -1424,6 +1485,9 @@ def regenerate_api_key():
random_part = ''.join(random.choices(string.ascii_uppercase + string.digits, k=32))
new_api_key = f"AF-{year_part}-{random_part}"
# Log what we're attempting
app.logger.info(f"Attempting to regenerate API key. New key: {new_api_key[:10]}...")
# Update the API key
cur.execute("""
UPDATE system_api_key
@@ -1433,15 +1497,27 @@ def regenerate_api_key():
WHERE id = 1
""", (new_api_key, session.get('username')))
# Log rows affected
app.logger.info(f"Rows affected by UPDATE: {cur.rowcount}")
conn.commit()
flash('API Key wurde erfolgreich regeneriert', 'success')
# Verify the update
cur.execute("SELECT api_key FROM system_api_key WHERE id = 1")
result = cur.fetchone()
if result and result[0] == new_api_key:
app.logger.info("API key successfully updated in database")
flash('API Key wurde erfolgreich regeneriert', 'success')
else:
app.logger.error(f"API key update verification failed. Expected: {new_api_key[:10]}..., Found: {result[0][:10] if result else 'None'}...")
flash('API Key wurde regeneriert, aber Verifizierung fehlgeschlagen', 'warning')
# Log action
log_audit('API_KEY_REGENERATED', 'system_api_key', 1,
additional_info="API Key regenerated")
except Exception as e:
app.logger.error(f"Error regenerating API key: {str(e)}", exc_info=True)
conn.rollback()
flash(f'Fehler beim Regenerieren des API Keys: {str(e)}', 'error')
@@ -1452,6 +1528,63 @@ def regenerate_api_key():
return redirect(url_for('admin.license_config'))
@admin_bp.route("/api-key/test-regenerate", methods=["GET"])
@login_required
def test_regenerate_api_key():
"""Test endpoint to check if regeneration works"""
import string
import random
conn = get_connection()
cur = conn.cursor()
try:
# Check current API key
cur.execute("SELECT api_key, regenerated_at FROM system_api_key WHERE id = 1")
current = cur.fetchone()
# Generate new API key
year_part = datetime.now().strftime('%Y')
random_part = ''.join(random.choices(string.ascii_uppercase + string.digits, k=32))
new_api_key = f"AF-{year_part}-{random_part}"
# Update the API key
cur.execute("""
UPDATE system_api_key
SET api_key = %s,
regenerated_at = CURRENT_TIMESTAMP,
regenerated_by = %s
WHERE id = 1
""", (new_api_key, session.get('username')))
rows_affected = cur.rowcount
conn.commit()
# Verify the update
cur.execute("SELECT api_key, regenerated_at FROM system_api_key WHERE id = 1")
updated = cur.fetchone()
result = {
'current_api_key': current[0] if current else None,
'current_regenerated_at': str(current[1]) if current and current[1] else None,
'new_api_key': new_api_key,
'rows_affected': rows_affected,
'updated_api_key': updated[0] if updated else None,
'updated_regenerated_at': str(updated[1]) if updated and updated[1] else None,
'success': updated and updated[0] == new_api_key
}
return jsonify(result)
except Exception as e:
conn.rollback()
return jsonify({'error': str(e), 'traceback': traceback.format_exc()})
finally:
cur.close()
conn.close()
@admin_bp.route("/test-api-key")
@login_required
def test_api_key():

Datei anzeigen

@@ -193,29 +193,27 @@ def get_license_devices(license_id):
cur.execute("""
SELECT
dr.id,
dr.hardware_id,
dr.hardware_fingerprint,
dr.device_name,
dr.device_type,
dr.first_seen as registration_date,
dr.last_seen,
dr.first_activated_at as registration_date,
dr.last_seen_at,
dr.is_active,
dr.operating_system,
dr.ip_address,
(SELECT COUNT(*) FROM sessions s
WHERE s.license_key = l.license_key
AND s.hardware_id = dr.hardware_id
AND s.is_active = true) as active_sessions
(SELECT COUNT(*) FROM license_sessions ls
WHERE ls.device_registration_id = dr.id
AND ls.ended_at IS NULL) as active_sessions
FROM device_registrations dr
JOIN licenses l ON dr.license_id = l.id
WHERE l.license_key = %s
ORDER BY dr.first_seen DESC
""", (license_data['license_key'],))
WHERE dr.license_id = %s
ORDER BY dr.last_seen_at DESC
""", (license_id,))
devices = []
for row in cur.fetchall():
devices.append({
'id': row[0],
'hardware_id': row[1],
'hardware_fingerprint': row[1],
'device_name': row[2],
'device_type': row[3],
'registration_date': row[4].isoformat() if row[4] else None,
@@ -268,22 +266,20 @@ def register_device(license_id):
# Prüfe Gerätelimit
cur.execute("""
SELECT COUNT(*) FROM device_registrations dr
JOIN licenses l ON dr.license_id = l.id
WHERE l.license_key = %s AND dr.is_active = true
""", (license_data['license_key'],))
SELECT COUNT(*) FROM device_registrations
WHERE license_id = %s AND is_active = true
""", (license_id,))
active_device_count = cur.fetchone()[0]
if active_device_count >= license_data['device_limit']:
if active_device_count >= license_data.get('device_limit', 3):
return jsonify({'error': 'Gerätelimit erreicht'}), 400
# Prüfe ob Gerät bereits registriert
cur.execute("""
SELECT dr.id, dr.is_active FROM device_registrations dr
JOIN licenses l ON dr.license_id = l.id
WHERE l.license_key = %s AND dr.hardware_id = %s
""", (license_data['license_key'], hardware_id))
SELECT id, is_active FROM device_registrations
WHERE license_id = %s AND hardware_fingerprint = %s
""", (license_id, hardware_id))
existing = cur.fetchone()
@@ -294,16 +290,18 @@ def register_device(license_id):
# Reaktiviere Gerät
cur.execute("""
UPDATE device_registrations
SET is_active = true, last_seen = CURRENT_TIMESTAMP
SET is_active = true, last_seen_at = CURRENT_TIMESTAMP
WHERE id = %s
""", (existing[0],))
else:
# Registriere neues Gerät
cur.execute("""
INSERT INTO device_registrations
(license_id, hardware_id, device_name, device_type, is_active)
VALUES (%s, %s, %s, %s, true)
""", (license_id, hardware_id, device_name, device_type))
(license_id, hardware_fingerprint, device_name, device_type, is_active,
first_activated_at, last_seen_at, operating_system, app_version)
VALUES (%s, %s, %s, %s, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, %s, %s)
""", (license_id, hardware_id, device_name, device_type,
data.get('operating_system', 'unknown'), data.get('app_version')))
conn.commit()
@@ -332,7 +330,7 @@ def deactivate_device(license_id, device_id):
try:
# Prüfe ob Gerät zur Lizenz gehört
cur.execute("""
SELECT dr.device_name, dr.hardware_id, l.license_key
SELECT dr.device_name, dr.hardware_fingerprint, l.license_key
FROM device_registrations dr
JOIN licenses l ON dr.license_id = l.id
WHERE dr.id = %s AND l.id = %s
@@ -345,15 +343,15 @@ def deactivate_device(license_id, device_id):
# Deaktiviere Gerät
cur.execute("""
UPDATE device_registrations
SET is_active = false
SET is_active = false, deactivated_at = CURRENT_TIMESTAMP, deactivated_by = %s
WHERE id = %s
""", (device_id,))
""", (session.get('username'), device_id))
# Beende aktive Sessions
cur.execute("""
UPDATE sessions
SET is_active = false, ended_at = CURRENT_TIMESTAMP
WHERE license_key = %s AND hardware_id = %s AND is_active = true
WHERE license_key = %s AND hardware_fingerprint = %s AND is_active = true
""", (device[2], device[1]))
conn.commit()
@@ -440,7 +438,7 @@ def bulk_delete_licenses():
try:
cur.execute("""
SELECT COUNT(*)
FROM activations
FROM device_registrations
WHERE license_id = %s
AND is_active = true
""", (license_id,))
@@ -451,7 +449,7 @@ def bulk_delete_licenses():
skipped_licenses.append(license_id)
continue
except:
# If activations table doesn't exist, continue
# If device_registrations table doesn't exist, continue
pass
# Delete associated data
@@ -468,7 +466,7 @@ def bulk_delete_licenses():
pass
try:
cur.execute("DELETE FROM activations WHERE license_id = %s", (license_id,))
cur.execute("DELETE FROM license_sessions WHERE license_id = %s", (license_id,))
except:
pass
@@ -946,9 +944,9 @@ def global_search():
# Suche in Sessions
cur.execute("""
SELECT id, license_key, username, hardware_id, is_active
SELECT id, license_key, username, hardware_fingerprint as hardware_id, is_active
FROM sessions
WHERE username ILIKE %s OR hardware_id ILIKE %s
WHERE username ILIKE %s OR hardware_fingerprint ILIKE %s
ORDER BY started_at DESC
LIMIT 10
""", (f'%{query}%', f'%{query}%'))

Datei anzeigen

@@ -81,12 +81,14 @@ def batch_create():
INSERT INTO licenses (
license_key, customer_id,
license_type, valid_from, valid_until, device_limit,
max_devices, max_concurrent_sessions,
is_fake, created_at
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
RETURNING id
""", (
license_key, customer_id,
license_type, valid_from, valid_until, device_limit,
device_limit, 1, # max_devices = device_limit, max_concurrent_sessions = 1 (default)
is_fake, datetime.now()
))

Datei anzeigen

@@ -338,7 +338,9 @@ def api_customer_licenses(customer_id):
END as status,
COALESCE(l.domain_count, 0) as domain_count,
COALESCE(l.ipv4_count, 0) as ipv4_count,
COALESCE(l.phone_count, 0) as phone_count
COALESCE(l.phone_count, 0) as phone_count,
l.max_concurrent_sessions,
(SELECT COUNT(*) FROM license_sessions ls WHERE ls.license_id = l.id) as active_sessions
FROM licenses l
WHERE l.customer_id = %s
ORDER BY l.created_at DESC, l.id DESC
@@ -379,6 +381,13 @@ def api_customer_licenses(customer_id):
elif res_row[1] == 'phone':
resources['phones'].append(resource_data)
# Count active devices from activations table
cur2.execute("""
SELECT COUNT(*) FROM activations
WHERE license_id = %s AND is_active = true
""", (license_id,))
active_device_count = cur2.fetchone()[0]
cur2.close()
conn2.close()
@@ -396,9 +405,10 @@ def api_customer_licenses(customer_id):
'domain_count': row[10],
'ipv4_count': row[11],
'phone_count': row[12],
'active_sessions': 0, # Platzhalter
'registered_devices': 0, # Platzhalter
'active_devices': 0, # Platzhalter
'max_concurrent_sessions': row[13],
'active_sessions': row[14],
'registered_devices': active_device_count,
'active_devices': active_device_count,
'actual_domain_count': len(resources['domains']),
'actual_ipv4_count': len(resources['ipv4s']),
'actual_phone_count': len(resources['phones']),

Datei anzeigen

@@ -32,6 +32,7 @@ def export_licenses():
l.valid_until,
l.is_active,
l.device_limit,
l.max_concurrent_sessions,
l.created_at,
l.is_fake,
CASE
@@ -39,8 +40,8 @@ def export_licenses():
WHEN l.is_active = false THEN 'Deaktiviert'
ELSE 'Aktiv'
END as status,
(SELECT COUNT(*) FROM sessions s WHERE s.license_key = l.license_key AND s.is_active = true) as active_sessions,
(SELECT COUNT(DISTINCT hardware_id) FROM sessions s WHERE s.license_key = l.license_key) as registered_devices
(SELECT COUNT(*) FROM license_sessions ls WHERE ls.license_id = l.id) as active_sessions,
(SELECT COUNT(DISTINCT hardware_fingerprint) FROM device_registrations dr WHERE dr.license_id = l.id AND dr.is_active = true) as registered_devices
FROM licenses l
LEFT JOIN customers c ON l.customer_id = c.id
WHERE l.is_fake = false
@@ -52,7 +53,7 @@ def export_licenses():
# Daten für Export vorbereiten
data = []
columns = ['ID', 'Lizenzschlüssel', 'Kunde', 'E-Mail', 'Typ', 'Gültig von',
'Gültig bis', 'Aktiv', 'Gerätelimit', 'Erstellt am', 'Fake-Lizenz',
'Gültig bis', 'Aktiv', 'Gerätelimit', 'Max. Sessions', 'Erstellt am', 'Fake-Lizenz',
'Status', 'Aktive Sessions', 'Registrierte Geräte']
for row in cur.fetchall():
@@ -62,8 +63,8 @@ def export_licenses():
row_data[5] = format_datetime_for_export(row_data[5])
if row_data[6]: # valid_until
row_data[6] = format_datetime_for_export(row_data[6])
if row_data[9]: # created_at
row_data[9] = format_datetime_for_export(row_data[9])
if row_data[10]: # created_at (index shifted due to max_concurrent_sessions)
row_data[10] = format_datetime_for_export(row_data[10])
data.append(row_data)
# Format prüfen
@@ -239,7 +240,7 @@ def export_sessions():
s.license_key,
l.customer_name,
s.username,
s.hardware_id,
s.hardware_fingerprint as hardware_id,
s.started_at,
s.ended_at,
s.last_heartbeat,
@@ -259,7 +260,7 @@ def export_sessions():
s.license_key,
l.customer_name,
s.username,
s.hardware_id,
s.hardware_fingerprint as hardware_id,
s.started_at,
s.ended_at,
s.last_heartbeat,
@@ -416,7 +417,7 @@ def export_monitoring():
lh.license_id,
l.license_key,
c.name as customer_name,
lh.hardware_id,
lh.hardware_fingerprint as hardware_id,
lh.ip_address,
'Heartbeat' as event_type,
'Normal' as severity,
@@ -447,7 +448,7 @@ def export_monitoring():
ad.license_id,
l.license_key,
c.name as customer_name,
ad.details->>'hardware_id' as hardware_id,
ad.details->>'hardware_fingerprint' as hardware_id,
ad.details->>'ip_address' as ip_address,
ad.anomaly_type as event_type,
ad.severity,

Datei anzeigen

@@ -118,13 +118,14 @@ def edit_license(license_id):
'valid_from': request.form['valid_from'],
'valid_until': request.form['valid_until'],
'is_active': 'is_active' in request.form,
'device_limit': int(request.form.get('device_limit', 3))
'max_devices': int(request.form.get('device_limit', 3)), # Form still uses device_limit
'max_concurrent_sessions': int(request.form.get('max_concurrent_sessions', 1))
}
cur.execute("""
UPDATE licenses
SET license_key = %s, license_type = %s, valid_from = %s,
valid_until = %s, is_active = %s, device_limit = %s
valid_until = %s, is_active = %s, max_devices = %s, max_concurrent_sessions = %s
WHERE id = %s
""", (
new_values['license_key'],
@@ -132,7 +133,8 @@ def edit_license(license_id):
new_values['valid_from'],
new_values['valid_until'],
new_values['is_active'],
new_values['device_limit'],
new_values['max_devices'],
new_values['max_concurrent_sessions'],
license_id
))
@@ -146,7 +148,8 @@ def edit_license(license_id):
'valid_from': str(current_license.get('valid_from', '')),
'valid_until': str(current_license.get('valid_until', '')),
'is_active': current_license.get('is_active'),
'device_limit': current_license.get('device_limit', 3)
'max_devices': current_license.get('max_devices', 3),
'max_concurrent_sessions': current_license.get('max_concurrent_sessions', 1)
},
new_values=new_values)
@@ -313,6 +316,7 @@ def create_license():
ipv4_count = int(request.form.get("ipv4_count", 1))
phone_count = int(request.form.get("phone_count", 1))
device_limit = int(request.form.get("device_limit", 3))
max_concurrent_sessions = int(request.form.get("max_concurrent_sessions", 1))
conn = get_connection()
cur = conn.cursor()
@@ -365,11 +369,11 @@ def create_license():
# Lizenz hinzufügen
cur.execute("""
INSERT INTO licenses (license_key, customer_id, license_type, valid_from, valid_until, is_active,
domain_count, ipv4_count, phone_count, device_limit, is_fake)
VALUES (%s, %s, %s, %s, %s, TRUE, %s, %s, %s, %s, %s)
domain_count, ipv4_count, phone_count, device_limit, max_devices, max_concurrent_sessions, is_fake)
VALUES (%s, %s, %s, %s, %s, TRUE, %s, %s, %s, %s, %s, %s, %s)
RETURNING id
""", (license_key, customer_id, license_type, valid_from, valid_until,
domain_count, ipv4_count, phone_count, device_limit, is_fake))
domain_count, ipv4_count, phone_count, device_limit, device_limit, max_concurrent_sessions, is_fake))
license_id = cur.fetchone()[0]
# Ressourcen zuweisen

Datei anzeigen

@@ -91,7 +91,7 @@ def unified_monitoring():
SELECT
COUNT(DISTINCT license_id) as active_licenses,
COUNT(*) as total_validations,
COUNT(DISTINCT hardware_id) as unique_devices,
COUNT(DISTINCT hardware_fingerprint) as unique_devices,
COUNT(DISTINCT ip_address) as unique_ips,
0 as avg_response_time
FROM license_heartbeats
@@ -126,7 +126,7 @@ def unified_monitoring():
l.license_key,
c.name as customer_name,
lh.ip_address,
lh.hardware_id,
lh.hardware_fingerprint,
NULL as anomaly_type,
NULL as description
FROM license_heartbeats lh
@@ -143,8 +143,8 @@ def unified_monitoring():
ad.severity,
l.license_key,
c.name as customer_name,
ad.details->>'ip_address' as ip_address,
ad.details->>'hardware_id' as hardware_id,
(ad.details->>'ip_address')::inet as ip_address,
ad.details->>'hardware_fingerprint' as hardware_fingerprint,
ad.anomaly_type,
ad.details->>'description' as description
FROM anomaly_detections ad
@@ -166,7 +166,7 @@ def unified_monitoring():
l.license_key,
c.name as customer_name,
lh.ip_address,
lh.hardware_id,
lh.hardware_fingerprint,
NULL as anomaly_type,
NULL as description
FROM license_heartbeats lh
@@ -199,7 +199,7 @@ def unified_monitoring():
l.id,
l.license_key,
c.name as customer_name,
COUNT(DISTINCT lh.hardware_id) as device_count,
COUNT(DISTINCT lh.hardware_fingerprint) as device_count,
COUNT(lh.*) as validation_count,
MAX(lh.timestamp) as last_seen,
COUNT(DISTINCT ad.id) as anomaly_count
@@ -220,7 +220,7 @@ def unified_monitoring():
l.id,
l.license_key,
c.name as customer_name,
COUNT(DISTINCT lh.hardware_id) as device_count,
COUNT(DISTINCT lh.hardware_fingerprint) as device_count,
COUNT(lh.*) as validation_count,
MAX(lh.timestamp) as last_seen,
0 as anomaly_count
@@ -345,7 +345,7 @@ def analytics():
SELECT
COUNT(DISTINCT license_id) as active_licenses,
COUNT(*) as total_validations,
COUNT(DISTINCT hardware_id) as unique_devices,
COUNT(DISTINCT hardware_fingerprint) as unique_devices,
COUNT(DISTINCT ip_address) as unique_ips
FROM license_heartbeats
WHERE timestamp > NOW() - INTERVAL '5 minutes'
@@ -403,7 +403,7 @@ def analytics_stream():
SELECT
COUNT(DISTINCT license_id) as active_licenses,
COUNT(*) as total_validations,
COUNT(DISTINCT hardware_id) as unique_devices,
COUNT(DISTINCT hardware_fingerprint) as unique_devices,
COUNT(DISTINCT ip_address) as unique_ips
FROM license_heartbeats
WHERE timestamp > NOW() - INTERVAL '5 minutes'
@@ -425,4 +425,15 @@ def analytics_stream():
time.sleep(5) # Update every 5 seconds
from flask import Response
return Response(generate(), mimetype="text/event-stream")
return Response(generate(), mimetype="text/event-stream")
@monitoring_bp.route('/device_limits')
@login_required
def device_limits():
"""Device limit monitoring dashboard"""
from utils.device_monitoring import check_device_limits, get_device_usage_stats
warnings = check_device_limits()
stats = get_device_usage_stats()
return render_template('monitoring/device_limits.html', warnings=warnings, stats=stats)

Datei anzeigen

@@ -8,6 +8,7 @@ from auth.decorators import login_required
from utils.audit import log_audit
from utils.network import get_client_ip
from db import get_connection, get_db_connection, get_db_cursor
from db_license import get_license_db_cursor
from models import get_active_sessions
# Create Blueprint
@@ -17,37 +18,72 @@ session_bp = Blueprint('sessions', __name__)
@session_bp.route("/sessions")
@login_required
def sessions():
# Use regular DB for customer/license info
conn = get_connection()
cur = conn.cursor()
try:
# Get is_active sessions with calculated inactive time
cur.execute("""
SELECT s.id, s.session_id, l.license_key, c.name, s.ip_address,
s.user_agent, s.started_at, s.last_heartbeat,
EXTRACT(EPOCH FROM (NOW() - s.last_heartbeat))/60 as minutes_inactive
FROM sessions s
JOIN licenses l ON s.license_id = l.id
JOIN customers c ON l.customer_id = c.id
WHERE s.is_active = TRUE
ORDER BY s.last_heartbeat DESC
""")
active_sessions = cur.fetchall()
# First get license mapping from admin DB
cur.execute("SELECT id, license_key FROM licenses")
license_map = {row[0]: row[1] for row in cur.fetchall()}
# Get recent ended sessions
cur.execute("""
SELECT s.id, s.session_id, l.license_key, c.name, s.ip_address,
s.started_at, s.ended_at,
EXTRACT(EPOCH FROM (s.ended_at - s.started_at))/60 as duration_minutes
FROM sessions s
JOIN licenses l ON s.license_id = l.id
JOIN customers c ON l.customer_id = c.id
WHERE s.is_active = FALSE
AND s.ended_at > NOW() - INTERVAL '24 hours'
ORDER BY s.ended_at DESC
LIMIT 50
""")
recent_sessions = cur.fetchall()
# Get customer mapping
cur.execute("SELECT l.id, c.name FROM licenses l JOIN customers c ON l.customer_id = c.id")
customer_map = {row[0]: row[1] for row in cur.fetchall()}
cur.close()
conn.close()
# Now get sessions from license server DB
with get_license_db_cursor() as license_cur:
# Get active sessions
license_cur.execute("""
SELECT id, license_id, session_token, ip_address, client_version,
started_at, last_heartbeat, hardware_id,
EXTRACT(EPOCH FROM (NOW() - last_heartbeat))/60 as minutes_inactive
FROM license_sessions
WHERE ended_at IS NULL
ORDER BY last_heartbeat DESC
""")
active_sessions = []
for row in license_cur.fetchall():
active_sessions.append((
row[0], # id
row[2], # session_token
license_map.get(row[1], 'Unknown'), # license_key
customer_map.get(row[1], 'Unknown'), # customer name
row[3], # ip_address
row[4], # client_version
row[5], # started_at
row[6], # last_heartbeat
row[8] # minutes_inactive
))
# Get recent ended sessions
license_cur.execute("""
SELECT id, license_id, session_token, ip_address,
started_at, ended_at,
EXTRACT(EPOCH FROM (ended_at - started_at))/60 as duration_minutes
FROM license_sessions
WHERE ended_at IS NOT NULL
AND ended_at > NOW() - INTERVAL '24 hours'
ORDER BY ended_at DESC
LIMIT 50
""")
recent_sessions = []
for row in license_cur.fetchall():
recent_sessions.append((
row[0], # id
row[2], # session_token
license_map.get(row[1], 'Unknown'), # license_key
customer_map.get(row[1], 'Unknown'), # customer name
row[3], # ip_address
row[4], # started_at
row[5], # ended_at
row[6] # duration_minutes
))
return render_template("sessions.html",
active_sessions=active_sessions,
@@ -57,9 +93,6 @@ def sessions():
logging.error(f"Error loading sessions: {str(e)}")
flash('Fehler beim Laden der Sessions!', 'error')
return redirect(url_for('admin.dashboard'))
finally:
cur.close()
conn.close()
@session_bp.route("/sessions/history")
@@ -78,19 +111,20 @@ def session_history():
# Base query
query = """
SELECT
s.id,
s.license_key,
s.username,
s.hardware_id,
s.started_at,
s.ended_at,
s.last_heartbeat,
s.is_active,
l.customer_name,
ls.id,
l.license_key,
ls.machine_name as username,
ls.hardware_id,
ls.started_at,
ls.ended_at,
ls.last_heartbeat,
CASE WHEN ls.ended_at IS NULL THEN true ELSE false END as is_active,
c.name as customer_name,
l.license_type,
l.is_test
FROM sessions s
LEFT JOIN licenses l ON s.license_key = l.license_key
FROM license_sessions ls
JOIN licenses l ON ls.license_id = l.id
LEFT JOIN customers c ON l.customer_id = c.id
WHERE 1=1
"""
@@ -98,18 +132,18 @@ def session_history():
# Apply filters
if license_key:
query += " AND s.license_key = %s"
query += " AND l.license_key = %s"
params.append(license_key)
if username:
query += " AND s.username ILIKE %s"
query += " AND ls.machine_name ILIKE %s"
params.append(f'%{username}%')
# Time filter
query += " AND s.started_at >= CURRENT_TIMESTAMP - INTERVAL '%s days'"
query += " AND ls.started_at >= CURRENT_TIMESTAMP - INTERVAL '%s days'"
params.append(days)
query += " ORDER BY s.started_at DESC LIMIT 1000"
query += " ORDER BY ls.started_at DESC LIMIT 1000"
cur.execute(query, params)
@@ -144,11 +178,12 @@ def session_history():
# Get unique license keys for filter dropdown
cur.execute("""
SELECT DISTINCT s.license_key, l.customer_name
FROM sessions s
LEFT JOIN licenses l ON s.license_key = l.license_key
WHERE s.started_at >= CURRENT_TIMESTAMP - INTERVAL '30 days'
ORDER BY l.customer_name, s.license_key
SELECT DISTINCT l.license_key, c.name as customer_name
FROM license_sessions ls
JOIN licenses l ON ls.license_id = l.id
LEFT JOIN customers c ON l.customer_id = c.id
WHERE ls.started_at >= CURRENT_TIMESTAMP - INTERVAL '30 days'
ORDER BY c.name, l.license_key
""")
available_licenses = []
@@ -180,44 +215,48 @@ def session_history():
@login_required
def terminate_session(session_id):
"""Beendet eine aktive Session"""
conn = get_connection()
cur = conn.cursor()
try:
# Get session info
cur.execute("""
SELECT license_key, username, hardware_id
FROM sessions
WHERE id = %s AND is_active = true
""", (session_id,))
session_info = None
session_info = cur.fetchone()
if not session_info:
flash('Session nicht gefunden oder bereits beendet!', 'error')
return redirect(url_for('sessions.sessions'))
# Get session info from license server DB
with get_license_db_cursor() as license_cur:
license_cur.execute("""
SELECT license_id, hardware_id, machine_name
FROM license_sessions
WHERE id = %s AND ended_at IS NULL
""", (session_id,))
result = license_cur.fetchone()
if not result:
flash('Session nicht gefunden oder bereits beendet!', 'error')
return redirect(url_for('sessions.sessions'))
license_id = result[0]
# Terminate session in license server DB
license_cur.execute("""
UPDATE license_sessions
SET ended_at = CURRENT_TIMESTAMP, end_reason = 'admin_terminated'
WHERE id = %s
""", (session_id,))
# Terminate session
cur.execute("""
UPDATE sessions
SET is_active = false, ended_at = CURRENT_TIMESTAMP
WHERE id = %s
""", (session_id,))
conn.commit()
# Get license key from admin DB for audit log
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT license_key FROM licenses WHERE id = %s", (license_id,))
license_key = cur.fetchone()[0] if cur.fetchone() else 'Unknown'
cur.close()
conn.close()
# Audit log
log_audit('SESSION_TERMINATE', 'session', session_id,
additional_info=f"Session beendet für {session_info[1]} auf Lizenz {session_info[0]}")
additional_info=f"Session beendet für Lizenz {license_key}")
flash('Session erfolgreich beendet!', 'success')
except Exception as e:
conn.rollback()
logging.error(f"Fehler beim Beenden der Session: {str(e)}")
flash('Fehler beim Beenden der Session!', 'error')
finally:
cur.close()
conn.close()
return redirect(url_for('sessions.sessions'))
@@ -230,10 +269,11 @@ def terminate_all_sessions(license_key):
cur = conn.cursor()
try:
# Count is_active sessions
# Count active sessions
cur.execute("""
SELECT COUNT(*) FROM sessions
WHERE license_key = %s AND is_active = true
SELECT COUNT(*) FROM license_sessions ls
JOIN licenses l ON ls.license_id = l.id
WHERE l.license_key = %s AND ls.ended_at IS NULL
""", (license_key,))
active_count = cur.fetchone()[0]
@@ -244,9 +284,11 @@ def terminate_all_sessions(license_key):
# Terminate all sessions
cur.execute("""
UPDATE sessions
SET is_active = false, ended_at = CURRENT_TIMESTAMP
WHERE license_key = %s AND is_active = true
UPDATE license_sessions
SET ended_at = CURRENT_TIMESTAMP, end_reason = 'admin_terminated_all'
WHERE license_id IN (
SELECT id FROM licenses WHERE license_key = %s
) AND ended_at IS NULL
""", (license_key,))
conn.commit()
@@ -280,8 +322,8 @@ def cleanup_sessions():
# Delete old inactive sessions
cur.execute("""
DELETE FROM sessions
WHERE is_active = false
DELETE FROM license_sessions
WHERE ended_at IS NOT NULL
AND ended_at < CURRENT_TIMESTAMP - INTERVAL '%s days'
RETURNING id
""", (days,))
@@ -320,12 +362,13 @@ def session_statistics():
# Aktuelle Statistiken
cur.execute("""
SELECT
COUNT(DISTINCT s.license_key) as active_licenses,
COUNT(DISTINCT s.username) as unique_users,
COUNT(DISTINCT s.hardware_id) as unique_devices,
COUNT(DISTINCT l.license_key) as active_licenses,
COUNT(DISTINCT ls.machine_name) as unique_users,
COUNT(DISTINCT ls.hardware_id) as unique_devices,
COUNT(*) as total_active_sessions
FROM sessions s
WHERE s.is_active = true
FROM license_sessions ls
JOIN licenses l ON ls.license_id = l.id
WHERE ls.ended_at IS NULL
""")
current_stats = cur.fetchone()
@@ -335,9 +378,9 @@ def session_statistics():
SELECT
l.license_type,
COUNT(*) as session_count
FROM sessions s
JOIN licenses l ON s.license_key = l.license_key
WHERE s.is_active = true
FROM license_sessions ls
JOIN licenses l ON ls.license_id = l.id
WHERE ls.ended_at IS NULL
GROUP BY l.license_type
ORDER BY session_count DESC
""")
@@ -352,14 +395,15 @@ def session_statistics():
# Top 10 Lizenzen nach aktiven Sessions
cur.execute("""
SELECT
s.license_key,
l.customer_name,
l.license_key,
c.name as customer_name,
COUNT(*) as session_count,
l.device_limit
FROM sessions s
JOIN licenses l ON s.license_key = l.license_key
WHERE s.is_active = true
GROUP BY s.license_key, l.customer_name, l.device_limit
FROM license_sessions ls
JOIN licenses l ON ls.license_id = l.id
JOIN customers c ON l.customer_id = c.id
WHERE ls.ended_at IS NULL
GROUP BY l.license_key, c.name, l.device_limit
ORDER BY session_count DESC
LIMIT 10
""")
@@ -376,13 +420,14 @@ def session_statistics():
# Session-Verlauf (letzte 7 Tage)
cur.execute("""
SELECT
DATE(started_at) as date,
DATE(ls.started_at) as date,
COUNT(*) as login_count,
COUNT(DISTINCT license_key) as unique_licenses,
COUNT(DISTINCT username) as unique_users
FROM sessions
WHERE started_at >= CURRENT_DATE - INTERVAL '7 days'
GROUP BY DATE(started_at)
COUNT(DISTINCT l.license_key) as unique_licenses,
COUNT(DISTINCT ls.machine_name) as unique_users
FROM license_sessions ls
JOIN licenses l ON ls.license_id = l.id
WHERE ls.started_at >= CURRENT_DATE - INTERVAL '7 days'
GROUP BY DATE(ls.started_at)
ORDER BY date
""")
@@ -399,9 +444,8 @@ def session_statistics():
cur.execute("""
SELECT
AVG(EXTRACT(EPOCH FROM (ended_at - started_at))/3600) as avg_duration_hours
FROM sessions
WHERE is_active = false
AND ended_at IS NOT NULL
FROM license_sessions
WHERE ended_at IS NOT NULL
AND ended_at - started_at < INTERVAL '24 hours'
AND started_at >= CURRENT_DATE - INTERVAL '30 days'
""")