Service Status im Dashboard
Dieser Commit ist enthalten in:
@@ -3,6 +3,7 @@ from datetime import datetime, timedelta
|
||||
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 config
|
||||
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__)
|
||||
|
||||
|
||||
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("/")
|
||||
@login_required
|
||||
def dashboard():
|
||||
@@ -258,6 +334,9 @@ def dashboard():
|
||||
license_distribution = []
|
||||
hourly_sessions = []
|
||||
|
||||
# Get service health status
|
||||
service_health = check_service_health()
|
||||
|
||||
return render_template('dashboard.html',
|
||||
stats=stats,
|
||||
top_licenses=top_licenses,
|
||||
@@ -265,6 +344,7 @@ def dashboard():
|
||||
license_distribution=license_distribution,
|
||||
hourly_sessions=hourly_sessions,
|
||||
resource_stats=resource_stats,
|
||||
service_health=service_health,
|
||||
username=session.get('username'))
|
||||
finally:
|
||||
cur.close()
|
||||
|
||||
@@ -182,57 +182,6 @@ def live_dashboard():
|
||||
error_message='Fehler beim Laden des Dashboards',
|
||||
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')
|
||||
@login_required
|
||||
|
||||
@@ -415,18 +415,12 @@
|
||||
</li>
|
||||
</ul>
|
||||
</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') }}">
|
||||
<i class="bi bi-activity"></i>
|
||||
<span>Monitoring</span>
|
||||
</a>
|
||||
<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">
|
||||
<a class="nav-link {% if request.endpoint == 'admin.license_anomalies' %}active{% endif %}" href="{{ url_for('admin.license_anomalies') }}">
|
||||
<i class="bi bi-bug"></i>
|
||||
|
||||
@@ -156,6 +156,52 @@
|
||||
</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 -->
|
||||
<div class="row g-3 mb-4">
|
||||
<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