Service Status im Dashboard

Dieser Commit ist enthalten in:
2025-06-21 19:16:47 +02:00
Ursprung e2b5247e84
Commit 3d02c7a111
5 geänderte Dateien mit 127 neuen und 298 gelöschten Zeilen

Datei anzeigen

@@ -3,6 +3,7 @@ from datetime import datetime, timedelta
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
from pathlib import Path from pathlib import Path
from flask import Blueprint, render_template, request, redirect, session, url_for, flash, send_file, jsonify, current_app from flask import Blueprint, render_template, request, redirect, session, url_for, flash, send_file, jsonify, current_app
import requests
import config import config
from auth.decorators import login_required from auth.decorators import login_required
@@ -16,6 +17,81 @@ from utils.export import create_excel_export, prepare_audit_export_data
admin_bp = Blueprint('admin', __name__) admin_bp = Blueprint('admin', __name__)
def check_service_health():
"""Check health status of critical services"""
services = []
# License Server Health Check
license_server = {
'name': 'License Server',
'status': 'unknown',
'response_time': None,
'icon': '🔐',
'details': None
}
try:
start_time = datetime.now()
response = requests.get('http://license-server:8443/health', timeout=2)
response_time = (datetime.now() - start_time).total_seconds() * 1000
if response.status_code == 200:
license_server['status'] = 'healthy'
license_server['response_time'] = round(response_time, 1)
license_server['details'] = 'Betriebsbereit'
else:
license_server['status'] = 'unhealthy'
license_server['details'] = f'HTTP {response.status_code}'
except requests.exceptions.Timeout:
license_server['status'] = 'down'
license_server['details'] = 'Timeout - Server antwortet nicht'
except requests.exceptions.ConnectionError:
license_server['status'] = 'down'
license_server['details'] = 'Verbindung fehlgeschlagen'
except Exception as e:
license_server['status'] = 'down'
license_server['details'] = f'Fehler: {str(e)}'
services.append(license_server)
# PostgreSQL Health Check
postgresql = {
'name': 'PostgreSQL',
'status': 'unknown',
'response_time': None,
'icon': '🗄️',
'details': None
}
try:
start_time = datetime.now()
with get_db_connection() as conn:
cur = conn.cursor()
cur.execute('SELECT 1')
cur.close()
response_time = (datetime.now() - start_time).total_seconds() * 1000
postgresql['status'] = 'healthy'
postgresql['response_time'] = round(response_time, 1)
postgresql['details'] = 'Datenbankverbindung aktiv'
except Exception as e:
postgresql['status'] = 'down'
postgresql['details'] = f'Verbindungsfehler: {str(e)}'
services.append(postgresql)
# Calculate overall health
healthy_count = sum(1 for s in services if s['status'] == 'healthy')
total_count = len(services)
return {
'services': services,
'healthy_count': healthy_count,
'total_count': total_count,
'overall_status': 'healthy' if healthy_count == total_count else ('partial' if healthy_count > 0 else 'down')
}
@admin_bp.route("/") @admin_bp.route("/")
@login_required @login_required
def dashboard(): def dashboard():
@@ -258,6 +334,9 @@ def dashboard():
license_distribution = [] license_distribution = []
hourly_sessions = [] hourly_sessions = []
# Get service health status
service_health = check_service_health()
return render_template('dashboard.html', return render_template('dashboard.html',
stats=stats, stats=stats,
top_licenses=top_licenses, top_licenses=top_licenses,
@@ -265,6 +344,7 @@ def dashboard():
license_distribution=license_distribution, license_distribution=license_distribution,
hourly_sessions=hourly_sessions, hourly_sessions=hourly_sessions,
resource_stats=resource_stats, resource_stats=resource_stats,
service_health=service_health,
username=session.get('username')) username=session.get('username'))
finally: finally:
cur.close() cur.close()

Datei anzeigen

@@ -182,57 +182,6 @@ def live_dashboard():
error_message='Fehler beim Laden des Dashboards', error_message='Fehler beim Laden des Dashboards',
details=str(e)) details=str(e))
@monitoring_bp.route('/system-status')
@login_required
def system_status():
"""System status showing service health"""
services = []
# Check each service
service_checks = [
{'name': 'License Server', 'url': 'http://license-server:8443/health', 'port': 8443},
{'name': 'PostgreSQL', 'check': 'database'},
]
for service in service_checks:
status = {'name': service['name'], 'status': 'unknown', 'response_time': None}
try:
if service.get('check') == 'database':
# Check database
start = datetime.now()
conn = get_db_connection()
conn.close()
status['status'] = 'healthy'
status['response_time'] = (datetime.now() - start).total_seconds() * 1000
elif service.get('url'):
# Check HTTP service
start = datetime.now()
response = requests.get(service['url'], timeout=2)
if response.status_code == 200:
status['status'] = 'healthy'
else:
status['status'] = 'unhealthy'
status['response_time'] = (datetime.now() - start).total_seconds() * 1000
except Exception as e:
status['status'] = 'down'
logging.error(f"Health check failed for {service['name']}: {str(e)}")
services.append(status)
# Get Prometheus metrics if available
prometheus_data = None
try:
response = requests.get('http://prometheus:9090/api/v1/query',
params={'query': 'up'}, timeout=2)
if response.status_code == 200:
prometheus_data = response.json()
except:
pass
return render_template('monitoring/system_status.html',
services=services,
prometheus_data=prometheus_data)
@monitoring_bp.route('/alerts') @monitoring_bp.route('/alerts')
@login_required @login_required

Datei anzeigen

@@ -415,18 +415,12 @@
</li> </li>
</ul> </ul>
</li> </li>
<li class="nav-item {% if request.endpoint in ['monitoring.live_dashboard', 'monitoring.system_status', 'monitoring.alerts', 'admin.audit_log', 'admin.license_monitor', 'admin.license_analytics', 'admin.license_anomalies'] %}has-active-child{% endif %}"> <li class="nav-item {% if request.endpoint in ['monitoring.live_dashboard', 'monitoring.alerts', 'admin.audit_log', 'admin.license_monitor', 'admin.license_analytics', 'admin.license_anomalies'] %}has-active-child{% endif %}">
<a class="nav-link has-submenu" href="{{ url_for('monitoring.live_dashboard') }}"> <a class="nav-link has-submenu" href="{{ url_for('monitoring.live_dashboard') }}">
<i class="bi bi-activity"></i> <i class="bi bi-activity"></i>
<span>Monitoring</span> <span>Monitoring</span>
</a> </a>
<ul class="sidebar-submenu"> <ul class="sidebar-submenu">
<li class="nav-item">
<a class="nav-link {% if request.endpoint == 'monitoring.system_status' %}active{% endif %}" href="{{ url_for('monitoring.system_status') }}">
<i class="bi bi-pc-display"></i>
<span>System Status</span>
</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if request.endpoint == 'admin.license_anomalies' %}active{% endif %}" href="{{ url_for('admin.license_anomalies') }}"> <a class="nav-link {% if request.endpoint == 'admin.license_anomalies' %}active{% endif %}" href="{{ url_for('admin.license_anomalies') }}">
<i class="bi bi-bug"></i> <i class="bi bi-bug"></i>

Datei anzeigen

@@ -156,6 +156,52 @@
</div> </div>
</div> </div>
<!-- Service Health Status -->
<div class="row g-3 mb-4">
<div class="col-12">
<div class="card">
<div class="card-header bg-dark text-white">
<h5 class="mb-0">
<i class="bi bi-heartbeat"></i> Service Status
{% if service_health.overall_status == 'healthy' %}
<span class="badge bg-success float-end">Alle Systeme betriebsbereit</span>
{% elif service_health.overall_status == 'partial' %}
<span class="badge bg-warning float-end">Teilweise Störungen</span>
{% else %}
<span class="badge bg-danger float-end">Kritische Störungen</span>
{% endif %}
</h5>
</div>
<div class="card-body">
<div class="row">
{% for service in service_health.services %}
<div class="col-md-6 mb-3">
<div class="d-flex align-items-center p-3 border rounded"
style="border-left: 4px solid {% if service.status == 'healthy' %}#28a745{% elif service.status == 'unhealthy' %}#ffc107{% else %}#dc3545{% endif %} !important;">
<div class="me-3 fs-2">{{ service.icon }}</div>
<div class="flex-grow-1">
<h6 class="mb-1">{{ service.name }}</h6>
<div class="d-flex justify-content-between align-items-center">
<span class="badge bg-{% if service.status == 'healthy' %}success{% elif service.status == 'unhealthy' %}warning{% else %}danger{% endif %}">
{% if service.status == 'healthy' %}Betriebsbereit{% elif service.status == 'unhealthy' %}Eingeschränkt{% else %}Ausgefallen{% endif %}
</span>
{% if service.response_time %}
<small class="text-muted">{{ service.response_time }}ms</small>
{% endif %}
</div>
{% if service.details %}
<small class="text-muted">{{ service.details }}</small>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<!-- Backup-Status und Sicherheit nebeneinander --> <!-- Backup-Status und Sicherheit nebeneinander -->
<div class="row g-3 mb-4"> <div class="row g-3 mb-4">
<div class="col-md-6"> <div class="col-md-6">

Datei anzeigen

@@ -1,240 +0,0 @@
{% extends "base.html" %}
{% block title %}System Status{% endblock %}
{% block extra_css %}
<style>
.service-card {
background: white;
border-radius: 10px;
padding: 20px;
margin-bottom: 15px;
border-left: 5px solid #dee2e6;
transition: all 0.3s;
}
.service-card.healthy {
border-left-color: #28a745;
}
.service-card.unhealthy {
border-left-color: #ffc107;
}
.service-card.down {
border-left-color: #dc3545;
}
.status-badge {
font-size: 0.875rem;
padding: 5px 15px;
border-radius: 20px;
font-weight: 500;
}
.status-healthy {
background-color: #d4edda;
color: #155724;
}
.status-unhealthy {
background-color: #fff3cd;
color: #856404;
}
.status-down {
background-color: #f8d7da;
color: #721c24;
}
.response-time {
font-size: 0.875rem;
color: #6c757d;
}
.service-icon {
font-size: 2rem;
margin-bottom: 10px;
}
.grafana-embed {
width: 100%;
height: 400px;
border: 1px solid #dee2e6;
border-radius: 8px;
}
.metric-card {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
text-align: center;
}
.metric-value {
font-size: 1.5rem;
font-weight: bold;
color: #495057;
}
.metric-label {
font-size: 0.875rem;
color: #6c757d;
text-transform: uppercase;
}
</style>
{% endblock %}
{% block content %}
<div class="row mb-4">
<div class="col">
<h2><i class="bi bi-pc-display"></i> System Status</h2>
<p class="text-muted">Übersicht über die Gesundheit aller Services</p>
</div>
<div class="col-auto">
<button class="btn btn-outline-primary" onclick="location.reload()">
<i class="bi bi-arrow-clockwise"></i> Aktualisieren
</button>
</div>
</div>
<!-- Service Status Grid -->
<div class="row">
{% for service in services %}
<div class="col-md-6 col-lg-4">
<div class="service-card {{ service.status }}">
<div class="text-center">
{% if service.name == 'License Server' %}
<i class="bi bi-shield-check service-icon text-primary"></i>
{% elif service.name == 'PostgreSQL' %}
<i class="bi bi-database service-icon text-info"></i>
{% elif service.name == 'Redis' %}
<i class="bi bi-memory service-icon text-danger"></i>
{% elif service.name == 'Auth Service' %}
<i class="bi bi-key service-icon text-warning"></i>
{% elif service.name == 'Analytics Service' %}
<i class="bi bi-graph-up service-icon text-success"></i>
{% else %}
<i class="bi bi-server service-icon text-secondary"></i>
{% endif %}
<h5>{{ service.name }}</h5>
<div class="mb-3">
{% if service.status == 'healthy' %}
<span class="status-badge status-healthy">
<i class="bi bi-check-circle"></i> Healthy
</span>
{% elif service.status == 'unhealthy' %}
<span class="status-badge status-unhealthy">
<i class="bi bi-exclamation-circle"></i> Unhealthy
</span>
{% else %}
<span class="status-badge status-down">
<i class="bi bi-x-circle"></i> Down
</span>
{% endif %}
</div>
{% if service.response_time %}
<div class="response-time">
<i class="bi bi-speedometer2"></i> {{ "%.1f"|format(service.response_time) }}ms
</div>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
<!-- System Metrics -->
<div class="card mt-4">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-cpu"></i> System Metriken</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-3">
<div class="metric-card">
<div class="metric-value" id="cpu-usage">-</div>
<div class="metric-label">CPU Auslastung</div>
</div>
</div>
<div class="col-md-3">
<div class="metric-card">
<div class="metric-value" id="memory-usage">-</div>
<div class="metric-label">RAM Auslastung</div>
</div>
</div>
<div class="col-md-3">
<div class="metric-card">
<div class="metric-value" id="disk-usage">-</div>
<div class="metric-label">Festplatte</div>
</div>
</div>
<div class="col-md-3">
<div class="metric-card">
<div class="metric-value" id="uptime">-</div>
<div class="metric-label">Uptime</div>
</div>
</div>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="card mt-4">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-lightning"></i> Quick Actions</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<button class="btn btn-outline-primary w-100 mb-2" onclick="restartService('license-server')">
<i class="bi bi-arrow-clockwise"></i> License Server neustarten
</button>
</div>
<div class="col-md-6">
<button class="btn btn-outline-warning w-100 mb-2" onclick="clearCache()">
<i class="bi bi-trash"></i> Cache leeren
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
// Mock functions for demonstration
function restartService(service) {
if (confirm(`Möchten Sie ${service} wirklich neustarten?`)) {
alert('Service-Neustart wurde initiiert. Dies kann einige Minuten dauern.');
}
}
function clearCache() {
if (confirm('Möchten Sie den Cache wirklich leeren?')) {
alert('Cache wurde geleert.');
}
}
// Fetch system metrics (mock data for now)
function updateSystemMetrics() {
// In production, these would come from actual monitoring endpoints
document.getElementById('cpu-usage').textContent = Math.floor(Math.random() * 40 + 20) + '%';
document.getElementById('memory-usage').textContent = Math.floor(Math.random() * 30 + 40) + '%';
document.getElementById('disk-usage').textContent = Math.floor(Math.random() * 20 + 60) + '%';
document.getElementById('uptime').textContent = '14 Tage';
}
// Update metrics on page load
document.addEventListener('DOMContentLoaded', function() {
updateSystemMetrics();
// Auto-refresh every 30 seconds
setInterval(updateSystemMetrics, 30000);
});
</script>
{% endblock %}