Lizenzserver - Integration Admin Panel

Dieser Commit ist enthalten in:
2025-06-18 22:03:46 +02:00
Ursprung ab3db0ab0d
Commit 86d805c392
21 geänderte Dateien mit 3110 neuen und 26 gelöschten Zeilen

Datei anzeigen

@@ -553,4 +553,422 @@ def clear_attempts():
cur.close()
conn.close()
return redirect(url_for('admin.blocked_ips'))
return redirect(url_for('admin.blocked_ips'))
# ===================== LICENSE SERVER MONITORING ROUTES =====================
@admin_bp.route("/lizenzserver/monitor")
@login_required
def license_monitor():
"""License server live monitoring dashboard"""
try:
conn = get_connection()
cur = conn.cursor()
# Get current statistics
# Active validations in last 5 minutes
cur.execute("""
SELECT COUNT(DISTINCT license_id) as active_licenses,
COUNT(*) as total_validations,
COUNT(DISTINCT hardware_id) as unique_devices,
COUNT(DISTINCT ip_address) as unique_ips
FROM license_heartbeats
WHERE timestamp > NOW() - INTERVAL '5 minutes'
""")
live_stats = cur.fetchone()
# Get validation rate (per minute)
cur.execute("""
SELECT DATE_TRUNC('minute', timestamp) as minute,
COUNT(*) as validations
FROM license_heartbeats
WHERE timestamp > NOW() - INTERVAL '10 minutes'
GROUP BY minute
ORDER BY minute DESC
LIMIT 10
""")
validation_rates = cur.fetchall()
# Get top active licenses
cur.execute("""
SELECT l.id, l.license_key, c.name as customer_name,
COUNT(DISTINCT lh.hardware_id) as device_count,
COUNT(*) as validation_count,
MAX(lh.timestamp) as last_seen
FROM licenses l
JOIN customers c ON l.customer_id = c.id
JOIN license_heartbeats lh ON l.id = lh.license_id
WHERE lh.timestamp > NOW() - INTERVAL '15 minutes'
GROUP BY l.id, l.license_key, c.name
ORDER BY validation_count DESC
LIMIT 10
""")
top_licenses = cur.fetchall()
# Get recent anomalies
cur.execute("""
SELECT ad.*, l.license_key, c.name as customer_name
FROM anomaly_detections ad
LEFT JOIN licenses l ON ad.license_id = l.id
LEFT JOIN customers c ON l.customer_id = c.id
WHERE ad.resolved = false
ORDER BY ad.detected_at DESC
LIMIT 10
""")
recent_anomalies = cur.fetchall()
# Get geographic distribution
cur.execute("""
SELECT ip_address, COUNT(*) as count
FROM license_heartbeats
WHERE timestamp > NOW() - INTERVAL '1 hour'
AND ip_address IS NOT NULL
GROUP BY ip_address
ORDER BY count DESC
LIMIT 20
""")
geo_distribution = cur.fetchall()
return render_template('license_monitor.html',
live_stats=live_stats,
validation_rates=validation_rates,
top_licenses=top_licenses,
recent_anomalies=recent_anomalies,
geo_distribution=geo_distribution
)
except Exception as e:
flash(f'Fehler beim Laden der Monitoring-Daten: {str(e)}', 'error')
return render_template('license_monitor.html')
finally:
if 'cur' in locals():
cur.close()
if 'conn' in locals():
conn.close()
@admin_bp.route("/lizenzserver/analytics")
@login_required
def license_analytics():
"""License usage analytics"""
try:
conn = get_connection()
cur = conn.cursor()
# Time range from query params
days = int(request.args.get('days', 30))
# Usage trends over time
cur.execute("""
SELECT DATE(timestamp) as date,
COUNT(DISTINCT license_id) as unique_licenses,
COUNT(DISTINCT hardware_id) as unique_devices,
COUNT(*) as total_validations
FROM license_heartbeats
WHERE timestamp > NOW() - INTERVAL '%s days'
GROUP BY date
ORDER BY date
""", (days,))
usage_trends = cur.fetchall()
# License performance metrics
cur.execute("""
SELECT l.id, l.license_key, c.name as customer_name,
COUNT(DISTINCT lh.hardware_id) as device_count,
l.max_devices,
COUNT(*) as total_validations,
COUNT(DISTINCT DATE(lh.timestamp)) as active_days,
MIN(lh.timestamp) as first_seen,
MAX(lh.timestamp) as last_seen
FROM licenses l
JOIN customers c ON l.customer_id = c.id
LEFT JOIN license_heartbeats lh ON l.id = lh.license_id
WHERE lh.timestamp > NOW() - INTERVAL '%s days'
GROUP BY l.id, l.license_key, c.name, l.max_devices
ORDER BY total_validations DESC
""", (days,))
license_metrics = cur.fetchall()
# Device distribution
cur.execute("""
SELECT l.max_devices as limit,
COUNT(*) as license_count,
AVG(device_count) as avg_usage
FROM licenses l
LEFT JOIN (
SELECT license_id, COUNT(DISTINCT hardware_id) as device_count
FROM license_heartbeats
WHERE timestamp > NOW() - INTERVAL '30 days'
GROUP BY license_id
) usage ON l.id = usage.license_id
WHERE l.is_active = true
GROUP BY l.max_devices
ORDER BY l.max_devices
""")
device_distribution = cur.fetchall()
# Revenue analysis
cur.execute("""
SELECT l.license_type,
COUNT(DISTINCT l.id) as license_count,
COUNT(DISTINCT CASE WHEN lh.license_id IS NOT NULL THEN l.id END) as active_licenses,
COUNT(DISTINCT lh.hardware_id) as total_devices
FROM licenses l
LEFT JOIN license_heartbeats lh ON l.id = lh.license_id
AND lh.timestamp > NOW() - INTERVAL '%s days'
GROUP BY l.license_type
""", (days,))
revenue_analysis = cur.fetchall()
return render_template('license_analytics.html',
days=days,
usage_trends=usage_trends,
license_metrics=license_metrics,
device_distribution=device_distribution,
revenue_analysis=revenue_analysis
)
except Exception as e:
flash(f'Fehler beim Laden der Analytics-Daten: {str(e)}', 'error')
return render_template('license_analytics.html', days=30)
finally:
if 'cur' in locals():
cur.close()
if 'conn' in locals():
conn.close()
@admin_bp.route("/lizenzserver/anomalies")
@login_required
def license_anomalies():
"""Anomaly detection and management"""
try:
conn = get_connection()
cur = conn.cursor()
# Filter parameters
severity = request.args.get('severity', 'all')
resolved = request.args.get('resolved', 'false')
# Build query
query = """
SELECT ad.*, l.license_key, c.name as customer_name, c.email
FROM anomaly_detections ad
LEFT JOIN licenses l ON ad.license_id = l.id
LEFT JOIN customers c ON l.customer_id = c.id
WHERE 1=1
"""
params = []
if severity != 'all':
query += " AND ad.severity = %s"
params.append(severity)
if resolved == 'false':
query += " AND ad.resolved = false"
elif resolved == 'true':
query += " AND ad.resolved = true"
query += " ORDER BY ad.detected_at DESC LIMIT 100"
cur.execute(query, params)
anomalies = cur.fetchall()
# Get anomaly statistics
cur.execute("""
SELECT anomaly_type, severity, COUNT(*) as count
FROM anomaly_detections
WHERE resolved = false
GROUP BY anomaly_type, severity
ORDER BY count DESC
""")
anomaly_stats = cur.fetchall()
return render_template('license_anomalies.html',
anomalies=anomalies,
anomaly_stats=anomaly_stats,
severity=severity,
resolved=resolved
)
except Exception as e:
flash(f'Fehler beim Laden der Anomalie-Daten: {str(e)}', 'error')
return render_template('license_anomalies.html')
finally:
if 'cur' in locals():
cur.close()
if 'conn' in locals():
conn.close()
@admin_bp.route("/lizenzserver/anomaly/<anomaly_id>/resolve", methods=["POST"])
@login_required
def resolve_anomaly(anomaly_id):
"""Resolve an anomaly"""
try:
conn = get_connection()
cur = conn.cursor()
action_taken = request.form.get('action_taken', '')
cur.execute("""
UPDATE anomaly_detections
SET resolved = true,
resolved_at = NOW(),
resolved_by = %s,
action_taken = %s
WHERE id = %s
""", (session.get('username'), action_taken, str(anomaly_id)))
conn.commit()
flash('Anomalie wurde als behoben markiert', 'success')
log_audit('RESOLVE_ANOMALY', 'license_server', entity_id=str(anomaly_id),
additional_info=f"Action: {action_taken}")
except Exception as e:
if 'conn' in locals():
conn.rollback()
flash(f'Fehler beim Beheben der Anomalie: {str(e)}', 'error')
finally:
if 'cur' in locals():
cur.close()
if 'conn' in locals():
conn.close()
return redirect(url_for('admin.license_anomalies'))
@admin_bp.route("/lizenzserver/config")
@login_required
def license_config():
"""License server configuration"""
try:
conn = get_connection()
cur = conn.cursor()
# Get feature flags
cur.execute("""
SELECT * FROM feature_flags
ORDER BY feature_name
""")
feature_flags = cur.fetchall()
# Get API clients
cur.execute("""
SELECT id, client_name, api_key, is_active, created_at
FROM api_clients
ORDER BY created_at DESC
""")
api_clients = cur.fetchall()
# Get rate limits
cur.execute("""
SELECT * FROM api_rate_limits
ORDER BY api_key
""")
rate_limits = cur.fetchall()
return render_template('license_config.html',
feature_flags=feature_flags,
api_clients=api_clients,
rate_limits=rate_limits
)
except Exception as e:
flash(f'Fehler beim Laden der Konfiguration: {str(e)}', 'error')
return render_template('license_config.html')
finally:
if 'cur' in locals():
cur.close()
if 'conn' in locals():
conn.close()
@admin_bp.route("/lizenzserver/config/feature-flag/<int:flag_id>", methods=["POST"])
@login_required
def update_feature_flag(flag_id):
"""Update feature flag settings"""
try:
conn = get_connection()
cur = conn.cursor()
is_enabled = request.form.get('is_enabled') == 'on'
rollout_percentage = int(request.form.get('rollout_percentage', 0))
cur.execute("""
UPDATE feature_flags
SET is_enabled = %s,
rollout_percentage = %s,
updated_at = NOW()
WHERE id = %s
""", (is_enabled, rollout_percentage, flag_id))
conn.commit()
flash('Feature Flag wurde aktualisiert', 'success')
log_audit('UPDATE_FEATURE_FLAG', 'license_server', entity_id=flag_id)
except Exception as e:
if 'conn' in locals():
conn.rollback()
flash(f'Fehler beim Aktualisieren: {str(e)}', 'error')
finally:
if 'cur' in locals():
cur.close()
if 'conn' in locals():
conn.close()
return redirect(url_for('admin.license_config'))
@admin_bp.route("/api/admin/lizenzserver/live-stats")
@login_required
def license_live_stats():
"""API endpoint for live statistics (for AJAX updates)"""
try:
conn = get_connection()
cur = conn.cursor()
# Get real-time stats
cur.execute("""
SELECT COUNT(DISTINCT license_id) as active_licenses,
COUNT(*) as validations_per_minute,
COUNT(DISTINCT hardware_id) as active_devices
FROM license_heartbeats
WHERE timestamp > NOW() - INTERVAL '1 minute'
""")
stats = cur.fetchone()
# Get latest validations
cur.execute("""
SELECT l.license_key, lh.hardware_id, lh.ip_address, lh.timestamp
FROM license_heartbeats lh
JOIN licenses l ON lh.license_id = l.id
ORDER BY lh.timestamp DESC
LIMIT 5
""")
latest_validations = cur.fetchall()
return jsonify({
'active_licenses': stats[0] or 0,
'validations_per_minute': stats[1] or 0,
'active_devices': stats[2] or 0,
'latest_validations': [
{
'license_key': v[0][:8] + '...',
'hardware_id': v[1][:8] + '...',
'ip_address': v[2] or 'Unknown',
'timestamp': v[3].strftime('%H:%M:%S')
} for v in latest_validations
]
})
except Exception as e:
return jsonify({'error': str(e)}), 500
finally:
if 'cur' in locals():
cur.close()
if 'conn' in locals():
conn.close()

Datei anzeigen

@@ -338,6 +338,11 @@ def api_customer_licenses(customer_id):
WHEN l.is_active = false THEN 'inaktiv'
ELSE 'aktiv'
END as status,
-- License Server Status
(SELECT COUNT(*) FROM license_heartbeats lh WHERE lh.license_id = l.id AND lh.timestamp > NOW() - INTERVAL '5 minutes') as recent_heartbeats,
(SELECT MAX(timestamp) FROM license_heartbeats lh WHERE lh.license_id = l.id) as last_heartbeat,
(SELECT COUNT(DISTINCT hardware_id) FROM license_heartbeats lh WHERE lh.license_id = l.id AND lh.timestamp > NOW() - INTERVAL '15 minutes') as active_server_devices,
(SELECT COUNT(*) FROM anomaly_detections ad WHERE ad.license_id = l.id AND ad.resolved = false) as unresolved_anomalies,
l.domain_count,
l.ipv4_count,
l.phone_count,
@@ -408,14 +413,19 @@ def api_customer_licenses(customer_id):
'active_sessions': row[9],
'registered_devices': row[10],
'status': row[11],
'domain_count': row[12],
'ipv4_count': row[13],
'phone_count': row[14],
'active_devices': row[15],
'actual_domain_count': row[16],
'actual_ipv4_count': row[17],
'actual_phone_count': row[18],
'resources': resources
'domain_count': row[16],
'ipv4_count': row[17],
'phone_count': row[18],
'active_devices': row[19],
'actual_domain_count': row[20],
'actual_ipv4_count': row[21],
'actual_phone_count': row[22],
'resources': resources,
# License Server Data
'recent_heartbeats': row[12],
'last_heartbeat': row[13].strftime('%Y-%m-%d %H:%M:%S') if row[13] else None,
'active_server_devices': row[14],
'unresolved_anomalies': row[15]
})
return jsonify({