322 Zeilen
10 KiB
HTML
322 Zeilen
10 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Alerts{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.alert-card {
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
margin-bottom: 15px;
|
|
border-left: 5px solid #dee2e6;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.alert-critical {
|
|
border-left-color: #dc3545;
|
|
background-color: #f8d7da;
|
|
}
|
|
|
|
.alert-high {
|
|
border-left-color: #fd7e14;
|
|
background-color: #ffe5d1;
|
|
}
|
|
|
|
.alert-warning {
|
|
border-left-color: #ffc107;
|
|
background-color: #fff3cd;
|
|
}
|
|
|
|
.alert-info {
|
|
border-left-color: #17a2b8;
|
|
background-color: #d1ecf1;
|
|
}
|
|
|
|
.severity-badge {
|
|
font-size: 0.75rem;
|
|
padding: 4px 12px;
|
|
border-radius: 15px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.severity-critical {
|
|
background-color: #dc3545;
|
|
color: white;
|
|
}
|
|
|
|
.severity-high {
|
|
background-color: #fd7e14;
|
|
color: white;
|
|
}
|
|
|
|
.severity-medium {
|
|
background-color: #ffc107;
|
|
color: #212529;
|
|
}
|
|
|
|
.severity-low {
|
|
background-color: #28a745;
|
|
color: white;
|
|
}
|
|
|
|
.alert-timestamp {
|
|
font-size: 0.875rem;
|
|
color: #6c757d;
|
|
}
|
|
|
|
.alert-actions {
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.alert-details {
|
|
background: rgba(0,0,0,0.05);
|
|
padding: 10px;
|
|
border-radius: 5px;
|
|
margin-top: 10px;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.alert-stats {
|
|
background: white;
|
|
padding: 20px;
|
|
border-radius: 10px;
|
|
text-align: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.stat-number {
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.filter-pills {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.filter-pill {
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.filter-pill.active {
|
|
transform: scale(1.05);
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row mb-4">
|
|
<div class="col">
|
|
<h2><i class="bi bi-exclamation-triangle"></i> Alerts</h2>
|
|
<p class="text-muted">Aktive Warnungen und Anomalien</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>
|
|
|
|
<!-- Alert Statistics -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-3">
|
|
<div class="alert-stats">
|
|
<div class="stat-number text-danger">{{ alerts|selectattr('severity', 'equalto', 'critical')|list|length }}</div>
|
|
<div class="text-muted">Kritisch</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="alert-stats">
|
|
<div class="stat-number text-warning">{{ alerts|selectattr('severity', 'equalto', 'high')|list|length }}</div>
|
|
<div class="text-muted">Hoch</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="alert-stats">
|
|
<div class="stat-number text-info">{{ alerts|selectattr('severity', 'equalto', 'medium')|list|length }}</div>
|
|
<div class="text-muted">Mittel</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="alert-stats">
|
|
<div class="stat-number text-success">{{ alerts|selectattr('severity', 'equalto', 'low')|list|length }}</div>
|
|
<div class="text-muted">Niedrig</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filter Pills -->
|
|
<div class="filter-pills">
|
|
<span class="badge bg-secondary filter-pill active me-2" onclick="filterAlerts('all')">
|
|
Alle ({{ alerts|length }})
|
|
</span>
|
|
<span class="badge bg-danger filter-pill me-2" onclick="filterAlerts('critical')">
|
|
Kritisch
|
|
</span>
|
|
<span class="badge bg-warning filter-pill me-2" onclick="filterAlerts('high')">
|
|
Hoch
|
|
</span>
|
|
<span class="badge bg-info filter-pill me-2" onclick="filterAlerts('medium')">
|
|
Mittel
|
|
</span>
|
|
<span class="badge bg-success filter-pill me-2" onclick="filterAlerts('low')">
|
|
Niedrig
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Alerts List -->
|
|
<div id="alerts-container">
|
|
{% for alert in alerts %}
|
|
<div class="alert-card alert-{{ alert.severity }}" data-severity="{{ alert.severity }}">
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<div class="d-flex align-items-center mb-2">
|
|
<h5 class="mb-0 me-3">
|
|
{% if alert.anomaly_type == 'multiple_ips' %}
|
|
<i class="bi bi-geo-alt-fill"></i> Mehrere IP-Adressen erkannt
|
|
{% elif alert.anomaly_type == 'rapid_hardware_change' %}
|
|
<i class="bi bi-laptop"></i> Schneller Hardware-Wechsel
|
|
{% elif alert.anomaly_type == 'suspicious_pattern' %}
|
|
<i class="bi bi-shield-exclamation"></i> Verdächtiges Muster
|
|
{% else %}
|
|
<i class="bi bi-exclamation-circle"></i> {{ alert.anomaly_type }}
|
|
{% endif %}
|
|
</h5>
|
|
<span class="severity-badge severity-{{ alert.severity }}">
|
|
{{ alert.severity }}
|
|
</span>
|
|
</div>
|
|
|
|
{% if alert.company_name %}
|
|
<div class="mb-2">
|
|
<strong>Kunde:</strong> {{ alert.company_name }}
|
|
{% if alert.license_key %}
|
|
<span class="text-muted">({{ alert.license_key[:8] }}...)</span>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="alert-timestamp">
|
|
<i class="bi bi-clock"></i> {{ alert.detected_at|default(alert.startsAt) }}
|
|
</div>
|
|
|
|
{% if alert.details %}
|
|
<div class="alert-details">
|
|
<strong>Details:</strong><br>
|
|
{{ alert.details }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<div class="alert-actions">
|
|
{% if not alert.resolved %}
|
|
<button class="btn btn-sm btn-success w-100 mb-2" onclick="resolveAlert('{{ alert.id }}')">
|
|
<i class="bi bi-check-circle"></i> Als gelöst markieren
|
|
</button>
|
|
<button class="btn btn-sm btn-warning w-100 mb-2" onclick="investigateAlert('{{ alert.id }}')">
|
|
<i class="bi bi-search"></i> Untersuchen
|
|
</button>
|
|
{% if alert.severity in ['critical', 'high'] %}
|
|
<button class="btn btn-sm btn-danger w-100" onclick="blockLicense('{{ alert.license_id }}')">
|
|
<i class="bi bi-shield-lock"></i> Lizenz blockieren
|
|
</button>
|
|
{% endif %}
|
|
{% else %}
|
|
<div class="text-success text-center">
|
|
<i class="bi bi-check-circle-fill"></i> Gelöst
|
|
{% if alert.resolved_at %}
|
|
<div class="small">{{ alert.resolved_at }}</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-5">
|
|
<i class="bi bi-shield-check" style="font-size: 4rem; color: #28a745;"></i>
|
|
<h4 class="mt-3">Keine aktiven Alerts</h4>
|
|
<p class="text-muted">Alle Systeme laufen normal</p>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
// Filter alerts by severity
|
|
function filterAlerts(severity) {
|
|
// Update active pill
|
|
document.querySelectorAll('.filter-pill').forEach(pill => {
|
|
pill.classList.remove('active');
|
|
});
|
|
event.target.classList.add('active');
|
|
|
|
// Filter alert cards
|
|
document.querySelectorAll('.alert-card').forEach(card => {
|
|
if (severity === 'all' || card.dataset.severity === severity) {
|
|
card.style.display = 'block';
|
|
} else {
|
|
card.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Resolve alert
|
|
async function resolveAlert(alertId) {
|
|
if (!confirm('Möchten Sie diesen Alert als gelöst markieren?')) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/alerts/${alertId}/resolve`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
alert('Alert wurde als gelöst markiert');
|
|
location.reload();
|
|
}
|
|
} catch (error) {
|
|
alert('Fehler beim Markieren des Alerts');
|
|
}
|
|
}
|
|
|
|
// Investigate alert
|
|
function investigateAlert(alertId) {
|
|
// In production, this would open a detailed investigation view
|
|
alert('Detaillierte Untersuchung wird geöffnet...');
|
|
}
|
|
|
|
// Block license
|
|
async function blockLicense(licenseId) {
|
|
if (!confirm('WARNUNG: Möchten Sie diese Lizenz wirklich blockieren? Der Kunde kann die Software nicht mehr nutzen!')) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/licenses/${licenseId}/block`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
alert('Lizenz wurde blockiert');
|
|
location.reload();
|
|
}
|
|
} catch (error) {
|
|
alert('Fehler beim Blockieren der Lizenz');
|
|
}
|
|
}
|
|
|
|
// Auto-refresh alerts every 60 seconds
|
|
setInterval(() => {
|
|
location.reload();
|
|
}, 60000);
|
|
</script>
|
|
{% endblock %} |