Service Status im Dashboard
Dieser Commit ist enthalten in:
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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 %}
|
|
||||||
In neuem Issue referenzieren
Einen Benutzer sperren