241 Zeilen
12 KiB
HTML
241 Zeilen
12 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}License Anomalien{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<h1 class="h3">Anomalie-Erkennung</h1>
|
|
|
|
<!-- Filter -->
|
|
<div class="card mb-3">
|
|
<div class="card-body">
|
|
<form method="get" class="row g-3">
|
|
<div class="col-md-3">
|
|
<label class="form-label">Schweregrad</label>
|
|
<select name="severity" class="form-select">
|
|
<option value="">Alle</option>
|
|
<option value="low" {% if request.args.get('severity') == 'low' %}selected{% endif %}>Niedrig</option>
|
|
<option value="medium" {% if request.args.get('severity') == 'medium' %}selected{% endif %}>Mittel</option>
|
|
<option value="high" {% if request.args.get('severity') == 'high' %}selected{% endif %}>Hoch</option>
|
|
<option value="critical" {% if request.args.get('severity') == 'critical' %}selected{% endif %}>Kritisch</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Status</label>
|
|
<select name="resolved" class="form-select">
|
|
<option value="false" {% if request.args.get('resolved', 'false') == 'false' %}selected{% endif %}>Ungelöst</option>
|
|
<option value="true" {% if request.args.get('resolved') == 'true' %}selected{% endif %}>Gelöst</option>
|
|
<option value="">Alle</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Anomalie-Typ</label>
|
|
<select name="anomaly_type" class="form-select">
|
|
<option value="">Alle</option>
|
|
<option value="multiple_ips" {% if request.args.get('anomaly_type') == 'multiple_ips' %}selected{% endif %}>Multiple IPs</option>
|
|
<option value="rapid_hardware_change" {% if request.args.get('anomaly_type') == 'rapid_hardware_change' %}selected{% endif %}>Schneller Hardware-Wechsel</option>
|
|
<option value="suspicious_pattern" {% if request.args.get('anomaly_type') == 'suspicious_pattern' %}selected{% endif %}>Verdächtiges Muster</option>
|
|
<option value="concurrent_use" {% if request.args.get('anomaly_type') == 'concurrent_use' %}selected{% endif %}>Gleichzeitige Nutzung</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-3 d-flex align-items-end">
|
|
<button type="submit" class="btn btn-primary">Filter anwenden</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Summary Cards -->
|
|
<div class="row mb-3">
|
|
<div class="col-md-3">
|
|
<div class="card border-danger">
|
|
<div class="card-body text-center">
|
|
<h5 class="card-title text-danger">Kritisch</h5>
|
|
<h2 class="mb-0">
|
|
{% set critical_count = namespace(value=0) %}
|
|
{% for stat in anomaly_stats if stat[1] == 'critical' %}
|
|
{% set critical_count.value = critical_count.value + stat[2] %}
|
|
{% endfor %}
|
|
{{ critical_count.value }}
|
|
</h2>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card border-warning">
|
|
<div class="card-body text-center">
|
|
<h5 class="card-title text-warning">Hoch</h5>
|
|
<h2 class="mb-0">
|
|
{% set high_count = namespace(value=0) %}
|
|
{% for stat in anomaly_stats if stat[1] == 'high' %}
|
|
{% set high_count.value = high_count.value + stat[2] %}
|
|
{% endfor %}
|
|
{{ high_count.value }}
|
|
</h2>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card border-info">
|
|
<div class="card-body text-center">
|
|
<h5 class="card-title text-info">Mittel</h5>
|
|
<h2 class="mb-0">
|
|
{% set medium_count = namespace(value=0) %}
|
|
{% for stat in anomaly_stats if stat[1] == 'medium' %}
|
|
{% set medium_count.value = medium_count.value + stat[2] %}
|
|
{% endfor %}
|
|
{{ medium_count.value }}
|
|
</h2>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card">
|
|
<div class="card-body text-center">
|
|
<h5 class="card-title text-muted">Niedrig</h5>
|
|
<h2 class="mb-0">
|
|
{% set low_count = namespace(value=0) %}
|
|
{% for stat in anomaly_stats if stat[1] == 'low' %}
|
|
{% set low_count.value = low_count.value + stat[2] %}
|
|
{% endfor %}
|
|
{{ low_count.value }}
|
|
</h2>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Anomaly List -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">Anomalien</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Zeitpunkt</th>
|
|
<th>Lizenz</th>
|
|
<th>Typ</th>
|
|
<th>Schweregrad</th>
|
|
<th>Details</th>
|
|
<th>Status</th>
|
|
<th>Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for anomaly in anomalies %}
|
|
<tr class="{% if anomaly[6] == 'critical' %}table-danger{% elif anomaly[6] == 'high' %}table-warning{% endif %}">
|
|
<td>{{ anomaly[3].strftime('%d.%m.%Y %H:%M') if anomaly[3] else '-' }}</td>
|
|
<td>
|
|
{% if anomaly[8] %}
|
|
<small>{{ anomaly[8][:8] }}...</small><br>
|
|
<span class="text-muted">{{ anomaly[9] }}</span>
|
|
{% else %}
|
|
<span class="text-muted">Unbekannt</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-secondary">{{ anomaly[5] }}</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{% if anomaly[6] == 'critical' %}danger{% elif anomaly[6] == 'high' %}warning{% elif anomaly[6] == 'medium' %}info{% else %}secondary{% endif %}">
|
|
{{ anomaly[6] }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-link" onclick="showDetails('{{ anomaly[7] }}')">
|
|
Details anzeigen
|
|
</button>
|
|
</td>
|
|
<td>
|
|
{% if anomaly[2] %}
|
|
<span class="badge bg-success">Gelöst</span>
|
|
{% else %}
|
|
<span class="badge bg-danger">Ungelöst</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if not anomaly[2] %}
|
|
<button class="btn btn-sm btn-success" onclick="resolveAnomaly('{{ anomaly[0] }}')">
|
|
Lösen
|
|
</button>
|
|
{% else %}
|
|
<small class="text-muted">{{ anomaly[4].strftime('%d.%m %H:%M') if anomaly[4] else '' }}</small>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="7" class="text-center text-muted">Keine Anomalien gefunden</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Details Modal -->
|
|
<div class="modal fade" id="detailsModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Anomalie Details</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<pre id="anomalyDetails"></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Resolve Modal -->
|
|
<div class="modal fade" id="resolveModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<form id="resolveForm" method="post">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Anomalie lösen</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label class="form-label">Ergriffene Maßnahme</label>
|
|
<textarea name="action_taken" class="form-control" rows="3" required></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
|
<button type="submit" class="btn btn-success">Als gelöst markieren</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
function showDetails(detailsJson) {
|
|
try {
|
|
const details = JSON.parse(detailsJson);
|
|
document.getElementById('anomalyDetails').textContent = JSON.stringify(details, null, 2);
|
|
new bootstrap.Modal(document.getElementById('detailsModal')).show();
|
|
} catch (e) {
|
|
alert('Fehler beim Anzeigen der Details');
|
|
}
|
|
}
|
|
|
|
function resolveAnomaly(anomalyId) {
|
|
const form = document.getElementById('resolveForm');
|
|
form.action = `/lizenzserver/anomaly/${anomalyId}/resolve`;
|
|
new bootstrap.Modal(document.getElementById('resolveModal')).show();
|
|
}
|
|
</script>
|
|
{% endblock %} |