371 Zeilen
13 KiB
Python
371 Zeilen
13 KiB
Python
from flask import Blueprint, render_template, jsonify, request, session
|
|
from functools import wraps
|
|
import psycopg2
|
|
from psycopg2.extras import RealDictCursor
|
|
import os
|
|
import requests
|
|
from datetime import datetime, timedelta
|
|
import logging
|
|
from utils.partition_helper import ensure_partition_exists, check_table_exists
|
|
|
|
monitoring_bp = Blueprint('monitoring', __name__)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Database connection
|
|
def get_db_connection():
|
|
return psycopg2.connect(
|
|
host=os.environ.get('POSTGRES_HOST', 'postgres'),
|
|
database=os.environ.get('POSTGRES_DB', 'v2_adminpanel'),
|
|
user=os.environ.get('POSTGRES_USER', 'postgres'),
|
|
password=os.environ.get('POSTGRES_PASSWORD', 'postgres')
|
|
)
|
|
|
|
# Login required decorator
|
|
def login_required(f):
|
|
@wraps(f)
|
|
def decorated_function(*args, **kwargs):
|
|
if 'logged_in' not in session:
|
|
return render_template('error.html',
|
|
error_message='Nicht autorisiert',
|
|
details='Sie müssen angemeldet sein, um diese Seite zu sehen.')
|
|
return f(*args, **kwargs)
|
|
return decorated_function
|
|
|
|
@monitoring_bp.route('/live-dashboard')
|
|
@login_required
|
|
def live_dashboard():
|
|
"""Live Dashboard showing active customer sessions and analytics"""
|
|
try:
|
|
conn = get_db_connection()
|
|
cur = conn.cursor(cursor_factory=RealDictCursor)
|
|
|
|
# Check if license_heartbeats table exists
|
|
if not check_table_exists(conn, 'license_heartbeats'):
|
|
logger.warning("license_heartbeats table does not exist")
|
|
# Return empty data
|
|
return render_template('monitoring/live_dashboard.html',
|
|
active_sessions=[],
|
|
stats={'active_licenses': 0, 'active_devices': 0, 'total_heartbeats': 0},
|
|
validation_timeline=[],
|
|
live_stats=[0, 0, 0, 0],
|
|
validation_rates=[],
|
|
recent_anomalies=[],
|
|
top_licenses=[])
|
|
|
|
# Ensure current month partition exists
|
|
ensure_partition_exists(conn, 'license_heartbeats', datetime.now())
|
|
|
|
# Get active customer sessions (last 5 minutes)
|
|
cur.execute("""
|
|
SELECT
|
|
l.id,
|
|
l.license_key,
|
|
c.name as company_name,
|
|
lh.hardware_id,
|
|
lh.ip_address,
|
|
lh.timestamp as last_activity,
|
|
lh.session_data,
|
|
COUNT(DISTINCT lh.hardware_id) OVER (PARTITION BY l.id) as active_devices
|
|
FROM license_heartbeats lh
|
|
JOIN licenses l ON l.id = lh.license_id
|
|
JOIN customers c ON c.id = l.customer_id
|
|
WHERE lh.timestamp > NOW() - INTERVAL '5 minutes'
|
|
AND l.is_active = true
|
|
ORDER BY lh.timestamp DESC
|
|
LIMIT 100
|
|
""")
|
|
active_sessions = cur.fetchall()
|
|
|
|
# Get session statistics
|
|
cur.execute("""
|
|
SELECT
|
|
COUNT(DISTINCT license_id) as active_licenses,
|
|
COUNT(DISTINCT hardware_id) as active_devices,
|
|
COUNT(*) as total_heartbeats
|
|
FROM license_heartbeats
|
|
WHERE timestamp > NOW() - INTERVAL '5 minutes'
|
|
""")
|
|
stats = cur.fetchone()
|
|
|
|
# Get validations per minute (for both charts)
|
|
cur.execute("""
|
|
SELECT
|
|
DATE_TRUNC('minute', timestamp) as minute,
|
|
COUNT(*) as validations
|
|
FROM license_heartbeats
|
|
WHERE timestamp > NOW() - INTERVAL '60 minutes'
|
|
GROUP BY minute
|
|
ORDER BY minute DESC
|
|
LIMIT 60
|
|
""")
|
|
validation_timeline = cur.fetchall()
|
|
|
|
# Get live statistics for analytics cards
|
|
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_data = cur.fetchone()
|
|
live_stats = [
|
|
live_stats_data['active_licenses'] or 0,
|
|
live_stats_data['total_validations'] or 0,
|
|
live_stats_data['unique_devices'] or 0,
|
|
live_stats_data['unique_ips'] or 0
|
|
]
|
|
|
|
# Get validation rates for analytics chart (last 30 minutes)
|
|
cur.execute("""
|
|
SELECT
|
|
DATE_TRUNC('minute', timestamp) as minute,
|
|
COUNT(*) as count
|
|
FROM license_heartbeats
|
|
WHERE timestamp > NOW() - INTERVAL '30 minutes'
|
|
GROUP BY minute
|
|
ORDER BY minute DESC
|
|
LIMIT 30
|
|
""")
|
|
validation_rates = [(row['minute'].isoformat(), row['count']) for row in 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 l.id = ad.license_id
|
|
LEFT JOIN customers c ON c.id = l.customer_id
|
|
WHERE ad.detected_at > NOW() - INTERVAL '24 hours'
|
|
ORDER BY ad.detected_at DESC
|
|
LIMIT 10
|
|
""")
|
|
recent_anomalies = cur.fetchall()
|
|
|
|
# Get top active licenses
|
|
cur.execute("""
|
|
SELECT
|
|
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 license_heartbeats lh
|
|
JOIN licenses l ON l.id = lh.license_id
|
|
JOIN customers c ON c.id = l.customer_id
|
|
WHERE lh.timestamp > NOW() - INTERVAL '15 minutes'
|
|
GROUP BY l.license_key, c.name
|
|
ORDER BY validation_count DESC
|
|
LIMIT 10
|
|
""")
|
|
top_licenses = cur.fetchall()
|
|
|
|
cur.close()
|
|
conn.close()
|
|
|
|
return render_template('monitoring/live_dashboard.html',
|
|
active_sessions=active_sessions,
|
|
stats=stats,
|
|
validation_timeline=validation_timeline,
|
|
live_stats=live_stats,
|
|
validation_rates=validation_rates,
|
|
recent_anomalies=recent_anomalies,
|
|
top_licenses=top_licenses)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in live dashboard: {str(e)}")
|
|
return render_template('error.html',
|
|
error_message='Fehler beim Laden des Dashboards',
|
|
details=str(e))
|
|
|
|
|
|
@monitoring_bp.route('/alerts')
|
|
@login_required
|
|
def alerts():
|
|
"""Show active alerts from Alertmanager"""
|
|
alerts = []
|
|
|
|
try:
|
|
# Get alerts from Alertmanager
|
|
response = requests.get('http://alertmanager:9093/api/v1/alerts', timeout=2)
|
|
if response.status_code == 200:
|
|
alerts = response.json()
|
|
except:
|
|
# Fallback to database anomalies
|
|
conn = get_db_connection()
|
|
cur = conn.cursor(cursor_factory=RealDictCursor)
|
|
|
|
cur.execute("""
|
|
SELECT
|
|
ad.*,
|
|
l.license_key,
|
|
c.name as company_name
|
|
FROM anomaly_detections ad
|
|
LEFT JOIN licenses l ON l.id = ad.license_id
|
|
LEFT JOIN customers c ON c.id = l.customer_id
|
|
WHERE ad.resolved = false
|
|
ORDER BY ad.detected_at DESC
|
|
LIMIT 50
|
|
""")
|
|
alerts = cur.fetchall()
|
|
|
|
cur.close()
|
|
conn.close()
|
|
|
|
return render_template('monitoring/alerts.html', alerts=alerts)
|
|
|
|
@monitoring_bp.route('/analytics')
|
|
@login_required
|
|
def analytics():
|
|
"""Combined analytics and license server status page"""
|
|
try:
|
|
conn = get_db_connection()
|
|
cur = conn.cursor(cursor_factory=RealDictCursor)
|
|
|
|
# Get live statistics for the top cards
|
|
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()
|
|
live_stats = [
|
|
live_stats['active_licenses'] or 0,
|
|
live_stats['total_validations'] or 0,
|
|
live_stats['unique_devices'] or 0,
|
|
live_stats['unique_ips'] or 0
|
|
]
|
|
|
|
# Get validation rates for chart
|
|
cur.execute("""
|
|
SELECT
|
|
DATE_TRUNC('minute', timestamp) as minute,
|
|
COUNT(*) as count
|
|
FROM license_heartbeats
|
|
WHERE timestamp > NOW() - INTERVAL '30 minutes'
|
|
GROUP BY minute
|
|
ORDER BY minute DESC
|
|
LIMIT 30
|
|
""")
|
|
validation_rates = [(row['minute'].isoformat(), row['count']) for row in 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 l.id = ad.license_id
|
|
LEFT JOIN customers c ON c.id = l.customer_id
|
|
WHERE ad.detected_at > NOW() - INTERVAL '24 hours'
|
|
ORDER BY ad.detected_at DESC
|
|
LIMIT 10
|
|
""")
|
|
recent_anomalies = cur.fetchall()
|
|
|
|
# Get top active licenses
|
|
cur.execute("""
|
|
SELECT
|
|
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 license_heartbeats lh
|
|
JOIN licenses l ON l.id = lh.license_id
|
|
JOIN customers c ON c.id = l.customer_id
|
|
WHERE lh.timestamp > NOW() - INTERVAL '15 minutes'
|
|
GROUP BY l.license_key, c.name
|
|
ORDER BY validation_count DESC
|
|
LIMIT 10
|
|
""")
|
|
top_licenses = cur.fetchall()
|
|
|
|
cur.close()
|
|
conn.close()
|
|
|
|
return render_template('monitoring/analytics.html',
|
|
live_stats=live_stats,
|
|
validation_rates=validation_rates,
|
|
recent_anomalies=recent_anomalies,
|
|
top_licenses=top_licenses)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in analytics: {str(e)}")
|
|
return render_template('monitoring/analytics.html',
|
|
live_stats=[0, 0, 0, 0],
|
|
validation_rates=[],
|
|
recent_anomalies=[],
|
|
top_licenses=[])
|
|
|
|
# API endpoints for live data
|
|
@monitoring_bp.route('/api/live-stats')
|
|
@login_required
|
|
def api_live_stats():
|
|
"""API endpoint for live statistics"""
|
|
try:
|
|
conn = get_db_connection()
|
|
cur = conn.cursor(cursor_factory=RealDictCursor)
|
|
|
|
# Get current stats
|
|
cur.execute("""
|
|
SELECT
|
|
COUNT(DISTINCT license_id) as active_licenses,
|
|
COUNT(DISTINCT hardware_id) as active_devices,
|
|
COUNT(*) as validations_last_minute
|
|
FROM license_heartbeats
|
|
WHERE timestamp > NOW() - INTERVAL '1 minute'
|
|
""")
|
|
stats = cur.fetchone()
|
|
|
|
cur.close()
|
|
conn.close()
|
|
|
|
return jsonify(stats)
|
|
|
|
except Exception as e:
|
|
return jsonify({'error': str(e)}), 500
|
|
|
|
@monitoring_bp.route('/api/active-sessions')
|
|
@login_required
|
|
def api_active_sessions():
|
|
"""API endpoint for active customer sessions"""
|
|
try:
|
|
conn = get_db_connection()
|
|
cur = conn.cursor(cursor_factory=RealDictCursor)
|
|
|
|
# Get active sessions with geo data
|
|
cur.execute("""
|
|
SELECT
|
|
l.license_key,
|
|
c.name as company_name,
|
|
lh.hardware_id,
|
|
lh.ip_address,
|
|
lh.timestamp as last_activity,
|
|
EXTRACT(EPOCH FROM (NOW() - lh.timestamp)) as seconds_ago,
|
|
lh.session_data
|
|
FROM license_heartbeats lh
|
|
JOIN licenses l ON l.id = lh.license_id
|
|
JOIN customers c ON c.id = l.customer_id
|
|
WHERE lh.timestamp > NOW() - INTERVAL '5 minutes'
|
|
ORDER BY lh.timestamp DESC
|
|
LIMIT 50
|
|
""")
|
|
sessions = cur.fetchall()
|
|
|
|
cur.close()
|
|
conn.close()
|
|
|
|
return jsonify(sessions)
|
|
|
|
except Exception as e:
|
|
return jsonify({'error': str(e)}), 500 |