Refactoring - Fix2
Dieser Commit ist enthalten in:
@@ -140,87 +140,87 @@
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
{{ sortable_header('Zeitstempel', 'timestamp', sort, order) }}
|
||||
{{ sortable_header('Benutzer', 'username', sort, order) }}
|
||||
{{ sortable_header('Aktion', 'action', sort, order) }}
|
||||
{{ sortable_header('Entität', 'entity', sort, order) }}
|
||||
<th>Zeitstempel</th>
|
||||
<th>Benutzer</th>
|
||||
<th>Aktion</th>
|
||||
<th>Entität</th>
|
||||
<th>Details</th>
|
||||
{{ sortable_header('IP-Adresse', 'ip', sort, order) }}
|
||||
<th>IP-Adresse</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in logs %}
|
||||
<tr>
|
||||
<td>{{ log[1].strftime('%d.%m.%Y %H:%M:%S') }}</td>
|
||||
<td><strong>{{ log[2] }}</strong></td>
|
||||
<td>{{ log.timestamp.strftime('%d.%m.%Y %H:%M:%S') }}</td>
|
||||
<td><strong>{{ log.username }}</strong></td>
|
||||
<td>
|
||||
<span class="action-{{ log[3] }}">
|
||||
{% if log[3] == 'CREATE' %}➕ Erstellt
|
||||
{% elif log[3] == 'UPDATE' %}✏️ Bearbeitet
|
||||
{% elif log[3] == 'DELETE' %}🗑️ Gelöscht
|
||||
{% elif log[3] == 'LOGIN' %}🔑 Anmeldung
|
||||
{% elif log[3] == 'LOGOUT' %}🚪 Abmeldung
|
||||
{% elif log[3] == 'AUTO_LOGOUT' %}⏰ Auto-Logout
|
||||
{% elif log[3] == 'EXPORT' %}📥 Export
|
||||
{% elif log[3] == 'GENERATE_KEY' %}🔑 Key generiert
|
||||
{% elif log[3] == 'CREATE_BATCH' %}🔑 Batch erstellt
|
||||
{% elif log[3] == 'BACKUP' %}💾 Backup erstellt
|
||||
{% elif log[3] == 'LOGIN_2FA_SUCCESS' %}🔐 2FA-Anmeldung
|
||||
{% elif log[3] == 'LOGIN_2FA_BACKUP' %}🔒 2FA-Backup-Code
|
||||
{% elif log[3] == 'LOGIN_2FA_FAILED' %}⛔ 2FA-Fehlgeschlagen
|
||||
{% elif log[3] == 'LOGIN_BLOCKED' %}🚫 Login-Blockiert
|
||||
{% elif log[3] == 'RESTORE' %}🔄 Wiederhergestellt
|
||||
{% elif log[3] == 'PASSWORD_CHANGE' %}🔐 Passwort geändert
|
||||
{% elif log[3] == '2FA_ENABLED' %}✅ 2FA aktiviert
|
||||
{% elif log[3] == '2FA_DISABLED' %}❌ 2FA deaktiviert
|
||||
{% else %}{{ log[3] }}
|
||||
<span class="action-{{ log.action }}">
|
||||
{% if log.action == 'CREATE' %}➕ Erstellt
|
||||
{% elif log.action == 'UPDATE' %}✏️ Bearbeitet
|
||||
{% elif log.action == 'DELETE' %}🗑️ Gelöscht
|
||||
{% elif log.action == 'LOGIN' %}🔑 Anmeldung
|
||||
{% elif log.action == 'LOGOUT' %}🚪 Abmeldung
|
||||
{% elif log.action == 'AUTO_LOGOUT' %}⏰ Auto-Logout
|
||||
{% elif log.action == 'EXPORT' %}📥 Export
|
||||
{% elif log.action == 'GENERATE_KEY' %}🔑 Key generiert
|
||||
{% elif log.action == 'CREATE_BATCH' %}🔑 Batch erstellt
|
||||
{% elif log.action == 'BACKUP' %}💾 Backup erstellt
|
||||
{% elif log.action == 'LOGIN_2FA_SUCCESS' %}🔐 2FA-Anmeldung
|
||||
{% elif log.action == 'LOGIN_2FA_BACKUP' %}🔒 2FA-Backup-Code
|
||||
{% elif log.action == 'LOGIN_2FA_FAILED' %}⛔ 2FA-Fehlgeschlagen
|
||||
{% elif log.action == 'LOGIN_BLOCKED' %}🚫 Login-Blockiert
|
||||
{% elif log.action == 'RESTORE' %}🔄 Wiederhergestellt
|
||||
{% elif log.action == 'PASSWORD_CHANGE' %}🔐 Passwort geändert
|
||||
{% elif log.action == '2FA_ENABLED' %}✅ 2FA aktiviert
|
||||
{% elif log.action == '2FA_DISABLED' %}❌ 2FA deaktiviert
|
||||
{% else %}{{ log.action }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{{ log[4] }}
|
||||
{% if log[5] %}
|
||||
<small class="text-muted">#{{ log[5] }}</small>
|
||||
{{ log.entity_type }}
|
||||
{% if log.entity_id %}
|
||||
<small class="text-muted">#{{ log.entity_id }}</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="audit-details">
|
||||
{% if log[10] %}
|
||||
<div class="mb-1"><small class="text-muted">{{ log[10] }}</small></div>
|
||||
{% if log.additional_info %}
|
||||
<div class="mb-1"><small class="text-muted">{{ log.additional_info }}</small></div>
|
||||
{% endif %}
|
||||
|
||||
{% if log[6] and log[3] == 'DELETE' %}
|
||||
{% if log.old_values and log.action == 'DELETE' %}
|
||||
<details>
|
||||
<summary>Gelöschte Werte</summary>
|
||||
<div class="json-display">
|
||||
{% for key, value in log[6].items() %}
|
||||
{% for key, value in log.old_values.items() %}
|
||||
<strong>{{ key }}:</strong> {{ value }}<br>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</details>
|
||||
{% elif log[6] and log[7] and log[3] == 'UPDATE' %}
|
||||
{% elif log.old_values and log.new_values and log.action == 'UPDATE' %}
|
||||
<details>
|
||||
<summary>Änderungen anzeigen</summary>
|
||||
<div class="json-display">
|
||||
<strong>Vorher:</strong><br>
|
||||
{% for key, value in log[6].items() %}
|
||||
{% if log[7][key] != value %}
|
||||
{% for key, value in log.old_values.items() %}
|
||||
{% if log.new_values[key] != value %}
|
||||
{{ key }}: {{ value }}<br>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<hr class="my-1">
|
||||
<strong>Nachher:</strong><br>
|
||||
{% for key, value in log[7].items() %}
|
||||
{% if log[6][key] != value %}
|
||||
{% for key, value in log.new_values.items() %}
|
||||
{% if log.old_values[key] != value %}
|
||||
{{ key }}: {{ value }}<br>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</details>
|
||||
{% elif log[7] and log[3] == 'CREATE' %}
|
||||
{% elif log.new_values and log.action == 'CREATE' %}
|
||||
<details>
|
||||
<summary>Erstellte Werte</summary>
|
||||
<div class="json-display">
|
||||
{% for key, value in log[7].items() %}
|
||||
{% for key, value in log.new_values.items() %}
|
||||
<strong>{{ key }}:</strong> {{ value }}<br>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -228,7 +228,7 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<small class="text-muted">{{ log[8] or '-' }}</small>
|
||||
<small class="text-muted">{{ log.ip_address or '-' }}</small>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">📅 Letztes erfolgreiches Backup</h5>
|
||||
{% if last_backup %}
|
||||
<p class="mb-1"><strong>Zeitpunkt:</strong> {{ last_backup[0].strftime('%d.%m.%Y %H:%M:%S') }}</p>
|
||||
<p class="mb-1"><strong>Größe:</strong> {{ (last_backup[1] / 1024 / 1024)|round(2) }} MB</p>
|
||||
<p class="mb-0"><strong>Dauer:</strong> {{ last_backup[2]|round(1) }} Sekunden</p>
|
||||
<p class="mb-1"><strong>Zeitpunkt:</strong> {{ last_backup.id.strftime('%d.%m.%Y %H:%M:%S') }}</p>
|
||||
<p class="mb-1"><strong>Größe:</strong> {{ (last_backup.filename / 1024 / 1024)|round(2) }} MB</p>
|
||||
<p class="mb-0"><strong>Dauer:</strong> {{ last_backup.filesize|round(1) }} Sekunden</p>
|
||||
{% else %}
|
||||
<p class="text-muted mb-0">Noch kein Backup vorhanden</p>
|
||||
{% endif %}
|
||||
@@ -73,44 +73,44 @@
|
||||
<tbody>
|
||||
{% for backup in backups %}
|
||||
<tr>
|
||||
<td>{{ backup[6].strftime('%d.%m.%Y %H:%M:%S') }}</td>
|
||||
<td>{{ backup.created_at.strftime('%d.%m.%Y %H:%M:%S') }}</td>
|
||||
<td>
|
||||
<small>{{ backup[1] }}</small>
|
||||
{% if backup[11] %}
|
||||
<small>{{ backup.filename }}</small>
|
||||
{% if backup.is_encrypted %}
|
||||
<span class="badge bg-info ms-1">🔒 Verschlüsselt</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if backup[2] %}
|
||||
{{ (backup[2] / 1024 / 1024)|round(2) }} MB
|
||||
{% if backup.filesize %}
|
||||
{{ (backup.filesize / 1024 / 1024)|round(2) }} MB
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if backup[3] == 'manual' %}
|
||||
{% if backup.backup_type == 'manual' %}
|
||||
<span class="badge bg-primary">Manuell</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">Automatisch</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if backup[4] == 'success' %}
|
||||
{% if backup.status == 'success' %}
|
||||
<span class="status-success">✅ Erfolgreich</span>
|
||||
{% elif backup[4] == 'failed' %}
|
||||
<span class="status-failed" title="{{ backup[5] }}">❌ Fehlgeschlagen</span>
|
||||
{% elif backup.status == 'failed' %}
|
||||
<span class="status-failed" title="{{ backup.error_message }}">❌ Fehlgeschlagen</span>
|
||||
{% else %}
|
||||
<span class="status-in_progress">⏳ In Bearbeitung</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ backup[7] }}</td>
|
||||
<td>{{ backup.created_by }}</td>
|
||||
<td>
|
||||
{% if backup[8] and backup[9] %}
|
||||
{% if backup.tables_count and backup.records_count %}
|
||||
<small>
|
||||
{{ backup[8] }} Tabellen<br>
|
||||
{{ backup[9] }} Datensätze<br>
|
||||
{% if backup[10] %}
|
||||
{{ backup[10]|round(1) }}s
|
||||
{{ backup.tables_count }} Tabellen<br>
|
||||
{{ backup.records_count }} Datensätze<br>
|
||||
{% if backup.duration_seconds %}
|
||||
{{ backup.duration_seconds|round(1) }}s
|
||||
{% endif %}
|
||||
</small>
|
||||
{% else %}
|
||||
@@ -118,20 +118,20 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="backup-actions">
|
||||
{% if backup[4] == 'success' %}
|
||||
{% if backup.status == 'success' %}
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<a href="/backup/download/{{ backup[0] }}"
|
||||
<a href="/backup/download/{{ backup.id }}"
|
||||
class="btn btn-outline-primary"
|
||||
title="Backup herunterladen">
|
||||
📥 Download
|
||||
</a>
|
||||
<button class="btn btn-outline-success"
|
||||
onclick="restoreBackup({{ backup[0] }}, '{{ backup[1] }}')"
|
||||
onclick="restoreBackup({{ backup.id }}, '{{ backup.filename }}')"
|
||||
title="Backup wiederherstellen">
|
||||
🔄 Wiederherstellen
|
||||
</button>
|
||||
<button class="btn btn-outline-danger"
|
||||
onclick="deleteBackup({{ backup[0] }}, '{{ backup[1] }}')"
|
||||
onclick="deleteBackup({{ backup.id }}, '{{ backup.filename }}')"
|
||||
title="Backup löschen">
|
||||
🗑️ Löschen
|
||||
</button>
|
||||
|
||||
@@ -33,12 +33,21 @@
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<form method="get" action="/customers" id="customerSearchForm" class="row g-3 align-items-end">
|
||||
<div class="col-md-10">
|
||||
<div class="col-md-8">
|
||||
<label for="search" class="form-label">🔍 Suchen</label>
|
||||
<input type="text" class="form-control" id="search" name="search"
|
||||
placeholder="Kundenname oder E-Mail..."
|
||||
value="{{ search }}" autofocus>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-check mt-4">
|
||||
<input class="form-check-input" type="checkbox" id="show_test" name="show_test" value="true"
|
||||
{% if show_test %}checked{% endif %} onchange="this.form.submit()">
|
||||
<label class="form-check-label" for="show_test">
|
||||
🧪 Testdaten anzeigen
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<a href="/customers" class="btn btn-outline-secondary w-100">Zurücksetzen</a>
|
||||
</div>
|
||||
@@ -69,23 +78,23 @@
|
||||
<tbody>
|
||||
{% for customer in customers %}
|
||||
<tr>
|
||||
<td>{{ customer[0] }}</td>
|
||||
<td>{{ customer.id }}</td>
|
||||
<td>
|
||||
{{ customer[1] }}
|
||||
{% if customer[4] %}
|
||||
{{ customer.name }}
|
||||
{% if customer.is_test %}
|
||||
<span class="badge bg-secondary ms-1" title="Testdaten">🧪</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ customer[2] or '-' }}</td>
|
||||
<td>{{ customer[3].strftime('%d.%m.%Y %H:%M') }}</td>
|
||||
<td>{{ customer.email or '-' }}</td>
|
||||
<td>{{ customer.created_at.strftime('%d.%m.%Y %H:%M') }}</td>
|
||||
<td>
|
||||
<span class="badge bg-info">{{ customer[6] }}/{{ customer[5] }}</span>
|
||||
<span class="badge bg-info">{{ customer.active_licenses }}/{{ customer.license_count }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<a href="/customer/edit/{{ customer[0] }}" class="btn btn-outline-primary">✏️ Bearbeiten</a>
|
||||
{% if customer[5] == 0 %}
|
||||
<form method="post" action="/customer/delete/{{ customer[0] }}" style="display: inline;" onsubmit="return confirm('Kunde wirklich löschen?');">
|
||||
<a href="/customer/edit/{{ customer.id }}" class="btn btn-outline-primary">✏️ Bearbeiten</a>
|
||||
{% if customer.license_count == 0 %}
|
||||
<form method="post" action="/customer/delete/{{ customer.id }}" style="display: inline;" onsubmit="return confirm('Kunde wirklich löschen?');">
|
||||
<button type="submit" class="btn btn-outline-danger">🗑️ Löschen</button>
|
||||
</form>
|
||||
{% else %}
|
||||
|
||||
@@ -13,27 +13,27 @@
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<form method="post" action="/customer/edit/{{ customer[0] }}" accept-charset="UTF-8">
|
||||
<form method="post" action="/customer/edit/{{ customer.id }}" accept-charset="UTF-8">
|
||||
{% if request.args.get('show_test') == 'true' %}
|
||||
<input type="hidden" name="show_test" value="true">
|
||||
{% endif %}
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="name" class="form-label">Kundenname</label>
|
||||
<input type="text" class="form-control" id="name" name="name" value="{{ customer[1] }}" accept-charset="UTF-8" required>
|
||||
<input type="text" class="form-control" id="name" name="name" value="{{ customer.name }}" accept-charset="UTF-8" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="email" class="form-label">E-Mail</label>
|
||||
<input type="email" class="form-control" id="email" name="email" value="{{ customer[2] or '' }}" accept-charset="UTF-8">
|
||||
<input type="email" class="form-control" id="email" name="email" value="{{ customer.email or '' }}" accept-charset="UTF-8">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label text-muted">Erstellt am</label>
|
||||
<p class="form-control-plaintext">{{ customer[3].strftime('%d.%m.%Y %H:%M') }}</p>
|
||||
<p class="form-control-plaintext">{{ customer.created_at.strftime('%d.%m.%Y %H:%M') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check mt-3">
|
||||
<input class="form-check-input" type="checkbox" id="isTest" name="is_test" {% if customer[4] %}checked{% endif %}>
|
||||
<input class="form-check-input" type="checkbox" id="isTest" name="is_test" {% if customer.is_test %}checked{% endif %}>
|
||||
<label class="form-check-label" for="isTest">
|
||||
<i class="fas fa-flask"></i> Als Testdaten markieren
|
||||
<small class="text-muted">(Kunde und seine Lizenzen werden von der Software ignoriert)</small>
|
||||
|
||||
@@ -13,42 +13,42 @@
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="post" action="/license/edit/{{ license[0] }}" accept-charset="UTF-8">
|
||||
<form method="post" action="/license/edit/{{ license.id }}" accept-charset="UTF-8">
|
||||
{% if request.args.get('show_test') == 'true' %}
|
||||
<input type="hidden" name="show_test" value="true">
|
||||
{% endif %}
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Kunde</label>
|
||||
<input type="text" class="form-control" value="{{ license[2] }}" disabled>
|
||||
<input type="text" class="form-control" value="{{ license.customer_name }}" disabled>
|
||||
<small class="text-muted">Kunde kann nicht geändert werden</small>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">E-Mail</label>
|
||||
<input type="email" class="form-control" value="{{ license[3] or '-' }}" disabled>
|
||||
<input type="email" class="form-control" value="-" disabled>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="licenseKey" class="form-label">Lizenzschlüssel</label>
|
||||
<input type="text" class="form-control" id="licenseKey" name="license_key" value="{{ license[1] }}" required>
|
||||
<input type="text" class="form-control" id="licenseKey" name="license_key" value="{{ license.license_key }}" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="licenseType" class="form-label">Lizenztyp</label>
|
||||
<select class="form-select" id="licenseType" name="license_type" required>
|
||||
<option value="full" {% if license[4] == 'full' %}selected{% endif %}>Vollversion</option>
|
||||
<option value="test" {% if license[4] == 'test' %}selected{% endif %}>Testversion</option>
|
||||
<option value="full" {% if license.license_type == 'full' %}selected{% endif %}>Vollversion</option>
|
||||
<option value="test" {% if license.license_type == 'test' %}selected{% endif %}>Testversion</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="validFrom" class="form-label">Gültig von</label>
|
||||
<input type="date" class="form-control" id="validFrom" name="valid_from" value="{{ license[5].strftime('%Y-%m-%d') }}" required>
|
||||
<input type="date" class="form-control" id="validFrom" name="valid_from" value="{{ license.valid_from.strftime('%Y-%m-%d') }}" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="validUntil" class="form-label">Gültig bis</label>
|
||||
<input type="date" class="form-control" id="validUntil" name="valid_until" value="{{ license[6].strftime('%Y-%m-%d') }}" required>
|
||||
<input type="date" class="form-control" id="validUntil" name="valid_until" value="{{ license.valid_until.strftime('%Y-%m-%d') }}" required>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex align-items-end">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="isActive" name="is_active" {% if license[7] %}checked{% endif %}>
|
||||
<input class="form-check-input" type="checkbox" id="isActive" name="is_active" {% if license.is_active %}checked{% endif %}>
|
||||
<label class="form-check-label" for="isActive">
|
||||
Lizenz ist aktiv
|
||||
</label>
|
||||
@@ -58,7 +58,7 @@
|
||||
<label for="deviceLimit" class="form-label">Gerätelimit</label>
|
||||
<select class="form-select" id="deviceLimit" name="device_limit" required>
|
||||
{% for i in range(1, 11) %}
|
||||
<option value="{{ i }}" {% if license[10] == i %}selected{% endif %}>{{ i }} {% if i == 1 %}Gerät{% else %}Geräte{% endif %}</option>
|
||||
<option value="{{ i }}" {% if license.get('device_limit', 3) == i %}selected{% endif %}>{{ i }} {% if i == 1 %}Gerät{% else %}Geräte{% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<small class="form-text text-muted">Maximale Anzahl gleichzeitig aktiver Geräte</small>
|
||||
@@ -66,7 +66,7 @@
|
||||
</div>
|
||||
|
||||
<div class="form-check mt-3">
|
||||
<input class="form-check-input" type="checkbox" id="isTest" name="is_test" {% if license[9] %}checked{% endif %}>
|
||||
<input class="form-check-input" type="checkbox" id="isTest" name="is_test" {% if license.is_test %}checked{% endif %}>
|
||||
<label class="form-check-label" for="isTest">
|
||||
<i class="fas fa-flask"></i> Als Testdaten markieren
|
||||
<small class="text-muted">(wird von der Software ignoriert)</small>
|
||||
|
||||
@@ -106,40 +106,40 @@
|
||||
{% for license in licenses %}
|
||||
<tr>
|
||||
<td class="checkbox-cell">
|
||||
<input type="checkbox" class="form-check-input form-check-input-custom license-checkbox" value="{{ license[0] }}">
|
||||
<input type="checkbox" class="form-check-input form-check-input-custom license-checkbox" value="{{ license.id }}">
|
||||
</td>
|
||||
<td>{{ license[0] }}</td>
|
||||
<td>{{ license.id }}</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<code class="me-2">{{ license[1] }}</code>
|
||||
<button class="btn btn-sm btn-outline-secondary btn-copy" onclick="copyToClipboard('{{ license[1] }}', this)" title="Kopieren">
|
||||
<code class="me-2">{{ license.license_key }}</code>
|
||||
<button class="btn btn-sm btn-outline-secondary btn-copy" onclick="copyToClipboard('{{ license.license_key }}', this)" title="Kopieren">
|
||||
📋
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{{ license[2] }}
|
||||
{% if license[8] %}
|
||||
{{ license.customer_name }}
|
||||
{% if license.is_test %}
|
||||
<span class="badge bg-secondary ms-1" title="Testdaten">🧪</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ license[3] or '-' }}</td>
|
||||
<td>-</td>
|
||||
<td>
|
||||
{% if license[4] == 'full' %}
|
||||
{% if license.license_type == 'full' %}
|
||||
<span class="badge bg-success">Vollversion</span>
|
||||
{% else %}
|
||||
<span class="badge bg-warning">Testversion</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ license[5].strftime('%d.%m.%Y') }}</td>
|
||||
<td>{{ license[6].strftime('%d.%m.%Y') }}</td>
|
||||
<td>{{ license.valid_from.strftime('%d.%m.%Y') }}</td>
|
||||
<td>{{ license.valid_until.strftime('%d.%m.%Y') }}</td>
|
||||
<td>
|
||||
{% if license[9] == 'abgelaufen' %}
|
||||
<span class="status-abgelaufen">⚠️ Abgelaufen</span>
|
||||
{% elif license[9] == 'läuft bald ab' %}
|
||||
<span class="status-ablaufend">⏰ Läuft bald ab</span>
|
||||
{% elif license[9] == 'deaktiviert' %}
|
||||
{% if not license.is_active %}
|
||||
<span class="status-deaktiviert">❌ Deaktiviert</span>
|
||||
{% elif license.valid_until < now().date() %}
|
||||
<span class="status-abgelaufen">⚠️ Abgelaufen</span>
|
||||
{% elif license.valid_until < (now() + timedelta(days=30)).date() %}
|
||||
<span class="status-ablaufend">⏰ Läuft bald ab</span>
|
||||
{% else %}
|
||||
<span class="status-aktiv">✅ Aktiv</span>
|
||||
{% endif %}
|
||||
@@ -147,15 +147,15 @@
|
||||
<td>
|
||||
<div class="form-check form-switch form-switch-custom">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
id="active_{{ license[0] }}"
|
||||
{{ 'checked' if license[7] else '' }}
|
||||
onchange="toggleLicenseStatus({{ license[0] }}, this.checked)">
|
||||
id="active_{{ license.id }}"
|
||||
{{ 'checked' if license.is_active else '' }}
|
||||
onchange="toggleLicenseStatus({{ license.id }}, this.checked)">
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<a href="/license/edit/{{ license[0] }}" class="btn btn-outline-primary">✏️ Bearbeiten</a>
|
||||
<form method="post" action="/license/delete/{{ license[0] }}" style="display: inline;" onsubmit="return confirm('Wirklich löschen?');">
|
||||
<a href="/license/edit/{{ license.id }}" class="btn btn-outline-primary">✏️ Bearbeiten</a>
|
||||
<form method="post" action="/license/delete/{{ license.id }}" style="display: inline;" onsubmit="return confirm('Wirklich löschen?');">
|
||||
<button type="submit" class="btn btn-outline-danger">🗑️ Löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -320,7 +320,7 @@
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label"> </label>
|
||||
<a href="{{ url_for('resources', show_test=show_test) }}" class="btn btn-secondary w-100">
|
||||
<a href="{{ url_for('resources.resources', show_test=show_test) }}" class="btn btn-secondary w-100">
|
||||
🔄 Zurücksetzen
|
||||
</a>
|
||||
</div>
|
||||
@@ -361,7 +361,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="80">
|
||||
<a href="{{ url_for('resources', sort='id', order='desc' if sort_by == 'id' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_test=show_test) }}"
|
||||
<a href="{{ url_for('resources.resources', sort='id', order='desc' if sort_by == 'id' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_test=show_test) }}"
|
||||
class="text-decoration-none text-dark sort-link">
|
||||
ID
|
||||
{% if sort_by == 'id' %}
|
||||
@@ -372,7 +372,7 @@
|
||||
</a>
|
||||
</th>
|
||||
<th width="120">
|
||||
<a href="{{ url_for('resources', sort='type', order='desc' if sort_by == 'type' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_test=show_test) }}"
|
||||
<a href="{{ url_for('resources.resources', sort='type', order='desc' if sort_by == 'type' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_test=show_test) }}"
|
||||
class="text-decoration-none text-dark sort-link">
|
||||
Typ
|
||||
{% if sort_by == 'type' %}
|
||||
@@ -383,7 +383,7 @@
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="{{ url_for('resources', sort='resource', order='desc' if sort_by == 'resource' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_test=show_test) }}"
|
||||
<a href="{{ url_for('resources.resources', sort='resource', order='desc' if sort_by == 'resource' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_test=show_test) }}"
|
||||
class="text-decoration-none text-dark sort-link">
|
||||
Ressource
|
||||
{% if sort_by == 'resource' %}
|
||||
@@ -394,7 +394,7 @@
|
||||
</a>
|
||||
</th>
|
||||
<th width="140">
|
||||
<a href="{{ url_for('resources', sort='status', order='desc' if sort_by == 'status' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_test=show_test) }}"
|
||||
<a href="{{ url_for('resources.resources', sort='status', order='desc' if sort_by == 'status' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_test=show_test) }}"
|
||||
class="text-decoration-none text-dark sort-link">
|
||||
Status
|
||||
{% if sort_by == 'status' %}
|
||||
@@ -405,7 +405,7 @@
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="{{ url_for('resources', sort='assigned', order='desc' if sort_by == 'assigned' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_test=show_test) }}"
|
||||
<a href="{{ url_for('resources.resources', sort='assigned', order='desc' if sort_by == 'assigned' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_test=show_test) }}"
|
||||
class="text-decoration-none text-dark sort-link">
|
||||
Zugewiesen an
|
||||
{% if sort_by == 'assigned' %}
|
||||
@@ -416,7 +416,7 @@
|
||||
</a>
|
||||
</th>
|
||||
<th width="180">
|
||||
<a href="{{ url_for('resources', sort='changed', order='desc' if sort_by == 'changed' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_test=show_test) }}"
|
||||
<a href="{{ url_for('resources.resources', sort='changed', order='desc' if sort_by == 'changed' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_test=show_test) }}"
|
||||
class="text-decoration-none text-dark sort-link">
|
||||
Letzte Änderung
|
||||
{% if sort_by == 'changed' %}
|
||||
@@ -433,13 +433,13 @@
|
||||
{% for resource in resources %}
|
||||
<tr>
|
||||
<td>
|
||||
<span class="text-muted">#{{ resource[0] }}</span>
|
||||
<span class="text-muted">#{{ resource.id }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="resource-icon {{ resource[1] }}">
|
||||
{% if resource[1] == 'domain' %}
|
||||
<div class="resource-icon {{ resource.resource_type }}">
|
||||
{% if resource.resource_type == 'domain' %}
|
||||
🌐
|
||||
{% elif resource[1] == 'ipv4' %}
|
||||
{% elif resource.resource_type == 'ipv4' %}
|
||||
🖥️
|
||||
{% else %}
|
||||
📱
|
||||
@@ -448,19 +448,19 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<code class="me-2">{{ resource[2] }}</code>
|
||||
<button class="copy-btn" onclick="copyToClipboard('{{ resource[2] }}', this)"
|
||||
<code class="me-2">{{ resource.resource_value }}</code>
|
||||
<button class="copy-btn" onclick="copyToClipboard('{{ resource.resource_value }}', this)"
|
||||
title="Kopieren">
|
||||
<i class="bi bi-clipboard"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{% if resource[3] == 'available' %}
|
||||
{% if resource.status == 'available' %}
|
||||
<span class="status-badge status-available">
|
||||
✅ Verfügbar
|
||||
</span>
|
||||
{% elif resource[3] == 'allocated' %}
|
||||
{% elif resource.status == 'allocated' %}
|
||||
<span class="status-badge status-allocated">
|
||||
🔗 Zugeteilt
|
||||
</span>
|
||||
@@ -468,23 +468,23 @@
|
||||
<span class="status-badge status-quarantine">
|
||||
⚠️ Quarantäne
|
||||
</span>
|
||||
{% if resource[8] %}
|
||||
<div class="small text-muted mt-1">{{ resource[8] }}</div>
|
||||
{% if resource.status_changed_by %}
|
||||
<div class="small text-muted mt-1">{{ resource.status_changed_by }}</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if resource[5] %}
|
||||
{% if resource.customer_name %}
|
||||
<div>
|
||||
<a href="{{ url_for('customers_licenses', customer_id=resource[10] if resource[10] else '', show_test=show_test) }}"
|
||||
<a href="{{ url_for('customers.customers_licenses', show_test=show_test) }}"
|
||||
class="text-decoration-none">
|
||||
<strong>{{ resource[5] }}</strong>
|
||||
<strong>{{ resource.customer_name }}</strong>
|
||||
</a>
|
||||
</div>
|
||||
<div class="small text-muted">
|
||||
<a href="{{ url_for('edit_license', license_id=resource[4]) }}?ref=resources{{ '&show_test=true' if show_test else '' }}"
|
||||
<a href="{{ url_for('licenses.edit_license', license_id=resource.allocated_to_license) }}?ref=resources{{ '&show_test=true' if show_test else '' }}"
|
||||
class="text-decoration-none text-muted">
|
||||
{{ resource[6] }}
|
||||
{{ resource.allocated_to_license }}
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
@@ -492,21 +492,21 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if resource[7] %}
|
||||
{% if resource.status_changed_at %}
|
||||
<div class="small">
|
||||
<div>{{ resource[7].strftime('%d.%m.%Y') }}</div>
|
||||
<div class="text-muted">{{ resource[7].strftime('%H:%M Uhr') }}</div>
|
||||
<div>{{ resource.status_changed_at.strftime('%d.%m.%Y') }}</div>
|
||||
<div class="text-muted">{{ resource.status_changed_at.strftime('%H:%M Uhr') }}</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if resource[3] == 'quarantine' %}
|
||||
{% if resource.status == 'quarantine' %}
|
||||
<!-- Quick Action für Quarantäne -->
|
||||
<form method="post" action="/resources/release?show_test={{ show_test }}&type={{ resource_type }}&status={{ status_filter }}&search={{ search }}"
|
||||
style="display: inline-block; margin-right: 5px;">
|
||||
<input type="hidden" name="resource_ids" value="{{ resource[0] }}">
|
||||
<input type="hidden" name="resource_ids" value="{{ resource.id }}">
|
||||
<input type="hidden" name="show_test" value="{{ show_test }}">
|
||||
<button type="submit"
|
||||
class="btn btn-sm btn-success">
|
||||
@@ -519,54 +519,54 @@
|
||||
<div class="dropdown" style="display: inline-block;">
|
||||
<button class="btn btn-sm btn-outline-secondary dropdown-toggle"
|
||||
type="button"
|
||||
id="dropdownMenuButton{{ resource[0] }}"
|
||||
id="dropdownMenuButton{{ resource.id }}"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false">
|
||||
<i class="bi bi-three-dots-vertical"></i> Aktionen
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton{{ resource[0] }}">
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton{{ resource.id }}">
|
||||
<!-- Historie immer verfügbar -->
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('resource_history', resource_id=resource[0]) }}">
|
||||
href="{{ url_for('resource_history', resource_id=resource.id) }}">
|
||||
<i class="bi bi-clock-history text-info"></i> Historie anzeigen
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
|
||||
{% if resource[3] == 'available' %}
|
||||
{% if resource.status == 'available' %}
|
||||
<!-- Aktionen für verfügbare Ressourcen -->
|
||||
<li>
|
||||
<button class="dropdown-item"
|
||||
onclick="showQuarantineModal({{ resource[0] }})">
|
||||
onclick="showQuarantineModal({{ resource.id }})">
|
||||
<i class="bi bi-exclamation-triangle text-warning"></i> In Quarantäne setzen
|
||||
</button>
|
||||
</li>
|
||||
{% elif resource[3] == 'allocated' %}
|
||||
{% elif resource.status == 'allocated' %}
|
||||
<!-- Aktionen für zugeteilte Ressourcen -->
|
||||
{% if resource[4] %}
|
||||
{% if resource.allocated_to_license %}
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('edit_license', license_id=resource[4]) }}?ref=resources{{ '&show_test=true' if show_test else '' }}">
|
||||
href="{{ url_for('edit_license', license_id=resource.allocated_to_license) }}?ref=resources{{ '&show_test=true' if show_test else '' }}">
|
||||
<i class="bi bi-file-text text-primary"></i> Lizenz bearbeiten
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if resource[10] %}
|
||||
{% if resource.id %}
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('customers_licenses', customer_id=resource[10], show_test=show_test) }}">
|
||||
href="{{ url_for('customers_licenses', customer_id=resource.id, show_test=show_test) }}">
|
||||
<i class="bi bi-person text-primary"></i> Kunde anzeigen
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% elif resource[3] == 'quarantine' %}
|
||||
{% elif resource.status == 'quarantine' %}
|
||||
<!-- Aktionen für Quarantäne-Ressourcen -->
|
||||
<li>
|
||||
<form method="post" action="/resources/release?show_test={{ show_test }}&type={{ resource_type }}&status={{ status_filter }}&search={{ search }}"
|
||||
style="display: contents;">
|
||||
<input type="hidden" name="resource_ids" value="{{ resource[0] }}">
|
||||
<input type="hidden" name="resource_ids" value="{{ resource.id }}">
|
||||
<input type="hidden" name="show_test" value="{{ show_test }}">
|
||||
<button type="submit" class="dropdown-item">
|
||||
<i class="bi bi-check-circle text-success"></i> Ressource freigeben
|
||||
@@ -576,7 +576,7 @@
|
||||
{% if resource[9] %}
|
||||
<li>
|
||||
<button class="dropdown-item"
|
||||
onclick="extendQuarantine({{ resource[0] }})">
|
||||
onclick="extendQuarantine({{ resource.id }})">
|
||||
<i class="bi bi-calendar-plus text-warning"></i> Quarantäne verlängern
|
||||
</button>
|
||||
</li>
|
||||
@@ -588,7 +588,7 @@
|
||||
<!-- Kopieren immer verfügbar -->
|
||||
<li>
|
||||
<button class="dropdown-item"
|
||||
onclick="copyToClipboard('{{ resource[2] }}', this)">
|
||||
onclick="copyToClipboard('{{ resource.resource_value }}', this)">
|
||||
<i class="bi bi-clipboard text-secondary"></i> Ressource kopieren
|
||||
</button>
|
||||
</li>
|
||||
@@ -616,13 +616,13 @@
|
||||
<ul class="pagination justify-content-center">
|
||||
<li class="page-item {% if page == 1 %}disabled{% endif %}">
|
||||
<a class="page-link"
|
||||
href="{{ url_for('resources', page=1, type=resource_type, status=status_filter, search=search, show_test=show_test, sort=sort_by, order=sort_order) }}">
|
||||
href="{{ url_for('resources.resources', page=1, type=resource_type, status=status_filter, search=search, show_test=show_test, sort=sort_by, order=sort_order) }}">
|
||||
<i class="bi bi-chevron-double-left"></i> Erste
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item {% if page == 1 %}disabled{% endif %}">
|
||||
<a class="page-link"
|
||||
href="{{ url_for('resources', page=page-1, type=resource_type, status=status_filter, search=search, show_test=show_test, sort=sort_by, order=sort_order) }}">
|
||||
href="{{ url_for('resources.resources', page=page-1, type=resource_type, status=status_filter, search=search, show_test=show_test, sort=sort_by, order=sort_order) }}">
|
||||
<i class="bi bi-chevron-left"></i> Zurück
|
||||
</a>
|
||||
</li>
|
||||
@@ -631,7 +631,7 @@
|
||||
{% if p == page or (p >= page - 2 and p <= page + 2) %}
|
||||
<li class="page-item {% if p == page %}active{% endif %}">
|
||||
<a class="page-link"
|
||||
href="{{ url_for('resources', page=p, type=resource_type, status=status_filter, search=search, show_test=show_test, sort=sort_by, order=sort_order) }}">
|
||||
href="{{ url_for('resources.resources', page=p, type=resource_type, status=status_filter, search=search, show_test=show_test, sort=sort_by, order=sort_order) }}">
|
||||
{{ p }}
|
||||
</a>
|
||||
</li>
|
||||
@@ -640,13 +640,13 @@
|
||||
|
||||
<li class="page-item {% if page == total_pages %}disabled{% endif %}">
|
||||
<a class="page-link"
|
||||
href="{{ url_for('resources', page=page+1, type=resource_type, status=status_filter, search=search, show_test=show_test, sort=sort_by, order=sort_order) }}">
|
||||
href="{{ url_for('resources.resources', page=page+1, type=resource_type, status=status_filter, search=search, show_test=show_test, sort=sort_by, order=sort_order) }}">
|
||||
Weiter <i class="bi bi-chevron-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item {% if page == total_pages %}disabled{% endif %}">
|
||||
<a class="page-link"
|
||||
href="{{ url_for('resources', page=total_pages, type=resource_type, status=status_filter, search=search, show_test=show_test, sort=sort_by, order=sort_order) }}">
|
||||
href="{{ url_for('resources.resources', page=total_pages, type=resource_type, status=status_filter, search=search, show_test=show_test, sort=sort_by, order=sort_order) }}">
|
||||
Letzte <i class="bi bi-chevron-double-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -87,12 +87,12 @@
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
{{ active_sortable_header('Kunde', 'customer', active_sort, active_order) }}
|
||||
{{ active_sortable_header('Lizenz', 'license', active_sort, active_order) }}
|
||||
{{ active_sortable_header('IP-Adresse', 'ip', active_sort, active_order) }}
|
||||
{{ active_sortable_header('Gestartet', 'started', active_sort, active_order) }}
|
||||
{{ active_sortable_header('Letzter Heartbeat', 'last_heartbeat', active_sort, active_order) }}
|
||||
{{ active_sortable_header('Inaktiv seit', 'inactive', active_sort, active_order) }}
|
||||
<th>Kunde</th>
|
||||
<th>Lizenz</th>
|
||||
<th>IP-Adresse</th>
|
||||
<th>Gestartet</th>
|
||||
<th>Letzter Heartbeat</th>
|
||||
<th>Inaktiv seit</th>
|
||||
<th>Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -146,12 +146,12 @@
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
{{ ended_sortable_header('Kunde', 'customer', ended_sort, ended_order) }}
|
||||
{{ ended_sortable_header('Lizenz', 'license', ended_sort, ended_order) }}
|
||||
{{ ended_sortable_header('IP-Adresse', 'ip', ended_sort, ended_order) }}
|
||||
{{ ended_sortable_header('Gestartet', 'started', ended_sort, ended_order) }}
|
||||
{{ ended_sortable_header('Beendet', 'ended_at', ended_sort, ended_order) }}
|
||||
{{ ended_sortable_header('Dauer', 'duration', ended_sort, ended_order) }}
|
||||
<th>Kunde</th>
|
||||
<th>Lizenz</th>
|
||||
<th>IP-Adresse</th>
|
||||
<th>Gestartet</th>
|
||||
<th>Beendet</th>
|
||||
<th>Dauer</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren