477 Zeilen
23 KiB
HTML
477 Zeilen
23 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Dashboard{% endblock %}
|
|
|
|
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.stat-card {
|
|
transition: all 0.3s ease;
|
|
cursor: pointer;
|
|
border: none;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
.stat-card:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
|
|
}
|
|
.stat-card .card-icon {
|
|
font-size: 3rem;
|
|
margin-bottom: 0.5rem;
|
|
opacity: 0.8;
|
|
}
|
|
.stat-card .card-value {
|
|
font-size: 2.5rem;
|
|
font-weight: bold;
|
|
margin: 0.5rem 0;
|
|
}
|
|
.stat-card .card-label {
|
|
font-size: 0.9rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
opacity: 0.7;
|
|
}
|
|
a:hover .stat-card {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
|
|
}
|
|
.text-decoration-none:hover {
|
|
text-decoration: none !important;
|
|
}
|
|
|
|
|
|
/* Session pulse effect */
|
|
@keyframes pulse {
|
|
0% { transform: scale(1); opacity: 1; }
|
|
50% { transform: scale(1.05); opacity: 0.8; }
|
|
100% { transform: scale(1); opacity: 1; }
|
|
}
|
|
.pulse-effect {
|
|
animation: pulse 2s infinite;
|
|
}
|
|
|
|
/* Progress bar styles */
|
|
.progress-custom {
|
|
height: 8px;
|
|
background-color: #e9ecef;
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
}
|
|
.progress-bar-custom {
|
|
background-color: #28a745;
|
|
transition: width 0.3s ease;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<h1 class="mb-4">Dashboard</h1>
|
|
|
|
<!-- Statistik-Karten -->
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-md-4">
|
|
<a href="{{ url_for('customers.customers_licenses') }}" class="text-decoration-none">
|
|
<div class="card stat-card h-100">
|
|
<div class="card-body text-center">
|
|
<div class="card-icon text-primary">👥</div>
|
|
<div class="card-value text-primary">{{ stats.total_customers }}</div>
|
|
<div class="card-label text-muted">Kunden Gesamt</div>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<a href="{{ url_for('customers.customers_licenses') }}" class="text-decoration-none">
|
|
<div class="card stat-card h-100">
|
|
<div class="card-body text-center">
|
|
<div class="card-icon text-info">📋</div>
|
|
<div class="card-value text-info">{{ stats.total_licenses }}</div>
|
|
<div class="card-label text-muted">Lizenzen Gesamt</div>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card stat-card h-100">
|
|
<div class="card-body text-center">
|
|
<div class="card-icon text-success{% if stats.active_usage > 0 %} pulse-effect{% endif %}">🟢</div>
|
|
<div class="card-value text-success">{{ stats.active_usage }}</div>
|
|
<div class="card-label text-muted">Aktive Nutzung</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Lizenztypen -->
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-md-6">
|
|
<div class="card h-100">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Lizenztypen</h5>
|
|
<div class="row">
|
|
<div class="col-6 text-center">
|
|
<h3 class="text-success">{{ stats.full_licenses }}</h3>
|
|
<p class="text-muted">Vollversionen</p>
|
|
</div>
|
|
<div class="col-6 text-center">
|
|
<h3 class="text-warning">{{ stats.fake_licenses }}</h3>
|
|
<p class="text-muted">Testversionen</p>
|
|
</div>
|
|
</div>
|
|
{% if stats.fake_data_count > 0 or stats.fake_customers_count > 0 or stats.fake_resources_count > 0 %}
|
|
<div class="alert alert-info mt-3 mb-0">
|
|
<small>
|
|
<i class="fas fa-flask"></i> Fake-Daten:
|
|
{{ stats.fake_data_count }} Lizenzen,
|
|
{{ stats.fake_customers_count }} Kunden,
|
|
{{ stats.fake_resources_count }} Ressourcen
|
|
</small>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="card h-100">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Lizenzstatus</h5>
|
|
<div class="row">
|
|
<div class="col-4 text-center">
|
|
<h3 class="text-success">{{ stats.active_licenses }}</h3>
|
|
<p class="text-muted">Aktiv</p>
|
|
</div>
|
|
<div class="col-4 text-center">
|
|
<h3 class="text-danger">{{ stats.expired_licenses }}</h3>
|
|
<p class="text-muted">Abgelaufen</p>
|
|
</div>
|
|
<div class="col-4 text-center">
|
|
<h3 class="text-secondary">{{ stats.inactive_licenses }}</h3>
|
|
<p class="text-muted">Deaktiviert</p>
|
|
</div>
|
|
</div>
|
|
</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 -->
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-md-6">
|
|
<a href="{{ url_for('admin.backups') }}" class="text-decoration-none">
|
|
<div class="card stat-card h-100">
|
|
<div class="card-body">
|
|
<h5 class="card-title">💾 Backup-Status</h5>
|
|
{% if stats.last_backup %}
|
|
{% if stats.last_backup[4] == 'success' %}
|
|
<div class="d-flex align-items-center mb-2">
|
|
<span class="text-success me-2">✅</span>
|
|
<small>{{ stats.last_backup[0].strftime('%d.%m.%Y %H:%M') }}</small>
|
|
</div>
|
|
<div class="progress-custom">
|
|
<div class="progress-bar-custom" style="width: 100%;"></div>
|
|
</div>
|
|
<small class="text-muted mt-1 d-block">
|
|
{{ (stats.last_backup[1] / 1024 / 1024)|round(1) }} MB • {{ stats.last_backup[2]|round(0)|int }}s
|
|
</small>
|
|
{% else %}
|
|
<div class="d-flex align-items-center">
|
|
<span class="text-danger me-2">❌</span>
|
|
<small>Backup fehlgeschlagen</small>
|
|
</div>
|
|
{% endif %}
|
|
{% else %}
|
|
<p class="text-muted mb-0">Noch kein Backup vorhanden</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Sicherheitsstatus -->
|
|
<div class="col-md-6">
|
|
<div class="card h-100">
|
|
<div class="card-body">
|
|
<h5 class="card-title">🔒 Sicherheitsstatus</h5>
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<span>Sicherheitslevel:</span>
|
|
<span class="badge bg-{{ stats.security_level }} fs-6">{{ stats.security_level_text }}</span>
|
|
</div>
|
|
<div class="row text-center">
|
|
<div class="col-6">
|
|
<h4 class="text-danger mb-0">{{ stats.blocked_ips_count }}</h4>
|
|
<small class="text-muted">Gesperrte IPs</small>
|
|
</div>
|
|
<div class="col-6">
|
|
<h4 class="text-warning mb-0">{{ stats.failed_attempts_today }}</h4>
|
|
<small class="text-muted">Fehlversuche heute</small>
|
|
</div>
|
|
</div>
|
|
<a href="{{ url_for('admin.blocked_ips') }}" class="btn btn-sm btn-outline-danger mt-3">IP-Verwaltung →</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sicherheitsereignisse -->
|
|
{% if stats.recent_security_events %}
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header bg-dark text-white">
|
|
<h6 class="mb-0">🚨 Letzte Sicherheitsereignisse</h6>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table table-sm mb-0 sortable-table">
|
|
<thead>
|
|
<tr>
|
|
<th class="sortable" data-type="date">Zeit</th>
|
|
<th class="sortable">IP-Adresse</th>
|
|
<th class="sortable" data-type="numeric">Versuche</th>
|
|
<th class="sortable">Fehlermeldung</th>
|
|
<th class="sortable">Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for event in stats.recent_security_events %}
|
|
<tr>
|
|
<td>{{ event.last_attempt }}</td>
|
|
<td><code>{{ event.ip_address }}</code></td>
|
|
<td><span class="badge bg-secondary">{{ event.attempt_count }}</span></td>
|
|
<td><strong class="text-danger">{{ event.error_message }}</strong></td>
|
|
<td>
|
|
{% if event.blocked_until %}
|
|
<span class="badge bg-danger">Gesperrt bis {{ event.blocked_until }}</span>
|
|
{% else %}
|
|
<span class="badge bg-warning">Aktiv</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Resource Pool Status -->
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header bg-primary text-white">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-server"></i> Resource Pool Status
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
{% if resource_stats %}
|
|
{% for type, data in resource_stats.items() %}
|
|
<div class="col-md-4 mb-3">
|
|
<div class="d-flex align-items-center">
|
|
<div class="me-3">
|
|
<a href="{{ url_for('resources.resources', type=type, show_test=request.args.get('show_test')) }}"
|
|
class="text-decoration-none">
|
|
<i class="fas fa-{{ 'globe' if type == 'domain' else ('network-wired' if type == 'ipv4' else 'phone') }} fa-2x text-{{ 'success' if data.available_percent > 50 else ('warning' if data.available_percent > 20 else 'danger') }}"></i>
|
|
</a>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<h6 class="mb-1">
|
|
<a href="{{ url_for('resources.resources', type=type, show_test=request.args.get('show_test')) }}"
|
|
class="text-decoration-none text-dark">{{ type|upper }}</a>
|
|
</h6>
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<span>
|
|
<strong class="text-{{ 'success' if data.available_percent > 50 else ('warning' if data.available_percent > 20 else 'danger') }}">{{ data.available }}</strong> / {{ data.total }} verfügbar
|
|
</span>
|
|
<span class="badge bg-{{ 'success' if data.available_percent > 50 else ('warning' if data.available_percent > 20 else 'danger') }}">
|
|
{{ data.available_percent }}%
|
|
</span>
|
|
</div>
|
|
<div class="progress mt-1" style="height: 8px;">
|
|
<div class="progress-bar bg-{{ 'success' if data.available_percent > 50 else ('warning' if data.available_percent > 20 else 'danger') }}"
|
|
style="width: {{ data.available_percent }}%"
|
|
data-bs-toggle="tooltip"
|
|
title="{{ data.available }} von {{ data.total }} verfügbar"></div>
|
|
</div>
|
|
<div class="d-flex justify-content-between mt-1">
|
|
<small class="text-muted">
|
|
<a href="{{ url_for('resources.resources', type=type, status='allocated', show_test=request.args.get('show_test')) }}"
|
|
class="text-decoration-none text-muted">
|
|
{{ data.allocated }} zugeteilt
|
|
</a>
|
|
</small>
|
|
{% if data.quarantine > 0 %}
|
|
<small>
|
|
<a href="{{ url_for('resources.resources', type=type, status='quarantine', show_test=request.args.get('show_test')) }}"
|
|
class="text-decoration-none text-warning">
|
|
<i class="bi bi-exclamation-triangle"></i> {{ data.quarantine }} in Quarantäne
|
|
</a>
|
|
</small>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="col-12 text-center text-muted">
|
|
<i class="fas fa-inbox fa-3x mb-3"></i>
|
|
<p>Keine Ressourcen im Pool vorhanden.</p>
|
|
<a href="{{ url_for('resources.add_resources', show_test=request.args.get('show_test')) }}" class="btn btn-primary">
|
|
<i class="fas fa-plus"></i> Ressourcen hinzufügen
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% if resource_warning %}
|
|
<div class="alert alert-danger mt-3 mb-0 d-flex justify-content-between align-items-center" role="alert">
|
|
<div>
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<strong>Kritisch:</strong> {{ resource_warning }}
|
|
</div>
|
|
<a href="{{ url_for('resources.add_resources', show_test=request.args.get('show_test')) }}"
|
|
class="btn btn-sm btn-danger">
|
|
<i class="bi bi-plus"></i> Ressourcen auffüllen
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3">
|
|
<!-- Bald ablaufende Lizenzen -->
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header bg-warning text-dark">
|
|
<h5 class="mb-0">⏰ Bald ablaufende Lizenzen</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if stats.expiring_licenses %}
|
|
<div class="table-responsive">
|
|
<table class="table table-sm sortable-table">
|
|
<thead>
|
|
<tr>
|
|
<th class="sortable">Kunde</th>
|
|
<th class="sortable">Lizenz</th>
|
|
<th class="sortable" data-type="numeric">Tage</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for license in stats.expiring_licenses %}
|
|
<tr>
|
|
<td>{{ license[2] }}</td>
|
|
<td><small><code>{{ license[1][:8] }}...</code></small></td>
|
|
<td><span class="badge bg-warning">{{ license[4] }} Tage</span></td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<p class="text-muted mb-0">Keine Lizenzen laufen in den nächsten 30 Tagen ab.</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Letzte Lizenzen -->
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header bg-info text-white">
|
|
<h5 class="mb-0">🆕 Zuletzt erstellte Lizenzen</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if stats.recent_licenses %}
|
|
<div class="table-responsive">
|
|
<table class="table table-sm sortable-table">
|
|
<thead>
|
|
<tr>
|
|
<th class="sortable">Kunde</th>
|
|
<th class="sortable">Lizenz</th>
|
|
<th class="sortable">Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for license in stats.recent_licenses %}
|
|
<tr>
|
|
<td>{{ license[2] }}</td>
|
|
<td><small><code>{{ license[1][:8] }}...</code></small></td>
|
|
<td>
|
|
{% if license[4] == 'deaktiviert' %}
|
|
<span class="status-deaktiviert">🚫 Deaktiviert</span>
|
|
{% elif license[4] == 'abgelaufen' %}
|
|
<span class="status-abgelaufen">⚠️ Abgelaufen</span>
|
|
{% elif license[4] == 'läuft bald ab' %}
|
|
<span class="status-ablaufend">⏰ Läuft bald ab</span>
|
|
{% else %}
|
|
<span class="status-aktiv">✅ Aktiv</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<p class="text-muted mb-0">Noch keine Lizenzen erstellt.</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %} |