Filtern geht schneller

Dieser Commit ist enthalten in:
2025-06-09 01:52:47 +02:00
Ursprung 245a2dc7fa
Commit f124b5a5fd
5 geänderte Dateien mit 170 neuen und 12 gelöschten Zeilen

Datei anzeigen

@@ -137,10 +137,13 @@ Lizenzmanagement-System für Social Media Account-Erstellungssoftware mit Docker
--- ---
## Nächste Schritte ## Nächste Schritte
1. UTF-8 Support implementieren 1. License Server API entwickeln mit Endpunkten:
2. Reverse Proxy mit SSL einrichten - `/api/version` - Versionscheck
3. Admin Panel Funktionalität erweitern - `/api/validate` - Lizenzvalidierung
4. License Server API entwickeln - `/api/heartbeat` - Session-Management
2. Mobile Optimizations für Admin Panel
3. Weitere Sicherheitsfeatures
4. Performance-Optimierungen
--- ---
@@ -1485,4 +1488,81 @@ ALTER TABLE login_attempts ALTER COLUMN blocked_until TYPE TIMESTAMP WITH TIME Z
- ✅ Erster Klick auf Spalte: Aufsteigend sortieren - ✅ Erster Klick auf Spalte: Aufsteigend sortieren
- ✅ Zweiter Klick: Absteigend sortieren - ✅ Zweiter Klick: Absteigend sortieren
- ✅ Weitere Klicks: Toggle zwischen ASC/DESC - ✅ Weitere Klicks: Toggle zwischen ASC/DESC
- ✅ Sortierung funktioniert korrekt mit Pagination und Filtern - ✅ Sortierung funktioniert korrekt mit Pagination und Filtern
### 2025-01-08: Port 8443 geschlossen - API nur noch über Nginx
**Problem:**
- Doppelte Verfügbarkeit des License Servers (Port 8443 + Nginx) machte keinen Sinn
- Inkonsistente Sicherheitskonfiguration (Nginx hatte Security Headers, Port 8443 nicht)
- Doppelte SSL-Konfiguration nötig
- Verwirrung welcher Zugangsweg genutzt werden soll
**Lösung:**
- Port-Mapping für License Server in docker-compose.yaml entfernt
- API nur noch über Nginx erreichbar: https://api-software-undso.z5m7q9dk3ah2v1plx6ju.com
- Interne Kommunikation zwischen Nginx und License Server bleibt bestehen
**Vorteile:**
- ✅ Einheitliche Sicherheitskonfiguration (Security Headers, HSTS)
- ✅ Zentrale SSL-Verwaltung nur in Nginx
- ✅ Möglichkeit für Rate Limiting und zentrales Logging
- ✅ Keine zusätzlichen offenen Ports (nur 80/443)
- ✅ Professionellere API-URL ohne Port-Angabe
**Geänderte Dateien:**
- `v2/docker-compose.yaml`: Port-Mapping "8443:8443" entfernt
**Hinweis für Client-Software:**
- API-Endpunkte sind weiterhin unter https://api-software-undso.z5m7q9dk3ah2v1plx6ju.com erreichbar
- Keine Änderung der API-URLs nötig, nur Port 8443 ist nicht mehr direkt zugänglich
**Status:**
- ✅ Port 8443 geschlossen
- ✅ API nur noch über Nginx Reverse Proxy erreichbar
- ✅ Sicherheit erhöht durch zentrale Verwaltung
### 2025-01-08: Live-Filtering implementiert
**Problem:**
- Benutzer mussten immer auf "Filter anwenden" klicken
- Umständliche Bedienung, besonders bei mehreren Filterkriterien
- Nicht zeitgemäße User Experience
**Lösung:**
- JavaScript Event-Listener für automatisches Filtern
- Text-Eingaben: 300ms Debouncing (verzögerte Suche nach Tipp-Pause)
- Dropdowns: Sofortiges Filtern bei Änderung
- "Filter anwenden" Button entfernt, nur "Zurücksetzen" bleibt
**Implementierte Live-Filter:**
1. **Lizenzübersicht** (licenses.html):
- Suchfeld mit Debouncing
- Typ-Dropdown (Vollversion/Testversion)
- Status-Dropdown (Aktiv/Ablaufend/Abgelaufen/Deaktiviert)
2. **Kundenübersicht** (customers.html):
- Suchfeld mit Debouncing
- "Suchen" Button entfernt
3. **Audit-Log** (audit_log.html):
- Benutzer-Textfeld mit Debouncing
- Aktion-Dropdown
- Entität-Dropdown
**Technische Details:**
- `addEventListener('input')` für Textfelder
- `addEventListener('change')` für Select-Elemente
- `setTimeout()` mit 300ms für Debouncing
- Automatisches `form.submit()` bei Änderungen
**Vorteile:**
- ✅ Schnellere und intuitivere Bedienung
- ✅ Weniger Klicks erforderlich
- ✅ Moderne User Experience
- ✅ Besonders hilfreich bei komplexen Filterkriterien
**Status:**
- ✅ Live-Filtering auf allen Hauptseiten implementiert
- ✅ Debouncing verhindert zu viele Server-Requests
- ✅ Zurücksetzen-Button bleibt für schnelles Löschen aller Filter

Datei anzeigen

@@ -32,8 +32,7 @@ services:
context: ../v2_lizenzserver context: ../v2_lizenzserver
container_name: license-server container_name: license-server
restart: always restart: always
ports: # Port-Mapping entfernt - nur noch über Nginx erreichbar
- "8443:8443"
env_file: .env env_file: .env
environment: environment:
TZ: Europe/Berlin TZ: Europe/Berlin

Datei anzeigen

@@ -68,7 +68,7 @@
<!-- Filter --> <!-- Filter -->
<div class="card mb-3"> <div class="card mb-3">
<div class="card-body"> <div class="card-body">
<form method="get" action="/audit"> <form method="get" action="/audit" id="auditFilterForm">
<div class="row g-3 align-items-end"> <div class="row g-3 align-items-end">
<div class="col-md-3"> <div class="col-md-3">
<label for="user" class="form-label">Benutzer</label> <label for="user" class="form-label">Benutzer</label>
@@ -101,7 +101,6 @@
</select> </select>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<button type="submit" class="btn btn-primary">Filter anwenden</button>
<a href="/audit" class="btn btn-outline-secondary">Zurücksetzen</a> <a href="/audit" class="btn btn-outline-secondary">Zurücksetzen</a>
</div> </div>
</div> </div>
@@ -250,4 +249,36 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock %}
{% block extra_js %}
<script>
// Live Filtering für Audit Log
document.addEventListener('DOMContentLoaded', function() {
const filterForm = document.getElementById('auditFilterForm');
const userInput = document.getElementById('user');
const actionSelect = document.getElementById('action');
const entitySelect = document.getElementById('entity');
// Debounce timer für Textfelder
let searchTimeout;
// Live-Filter für Benutzer-Textfeld (mit 300ms Verzögerung)
userInput.addEventListener('input', function() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
filterForm.submit();
}, 300);
});
// Live-Filter für Dropdowns (sofort)
actionSelect.addEventListener('change', function() {
filterForm.submit();
});
entitySelect.addEventListener('change', function() {
filterForm.submit();
});
});
</script>
{% endblock %} {% endblock %}

Datei anzeigen

@@ -46,7 +46,7 @@
<!-- Suchformular --> <!-- Suchformular -->
<div class="card mb-3"> <div class="card mb-3">
<div class="card-body"> <div class="card-body">
<form method="get" action="/customers" class="row g-3 align-items-end"> <form method="get" action="/customers" id="customerSearchForm" class="row g-3 align-items-end">
<div class="col-md-10"> <div class="col-md-10">
<label for="search" class="form-label">🔍 Suchen</label> <label for="search" class="form-label">🔍 Suchen</label>
<input type="text" class="form-control" id="search" name="search" <input type="text" class="form-control" id="search" name="search"
@@ -54,7 +54,7 @@
value="{{ search }}" autofocus> value="{{ search }}" autofocus>
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">Suchen</button> <a href="/customers" class="btn btn-outline-secondary w-100">Zurücksetzen</a>
</div> </div>
</form> </form>
{% if search %} {% if search %}
@@ -161,4 +161,25 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock %}
{% block extra_js %}
<script>
// Live Search für Kunden
document.addEventListener('DOMContentLoaded', function() {
const searchForm = document.getElementById('customerSearchForm');
const searchInput = document.getElementById('search');
// Debounce timer für Suchfeld
let searchTimeout;
// Live-Suche mit 300ms Verzögerung
searchInput.addEventListener('input', function() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
searchForm.submit();
}, 300);
});
});
</script>
{% endblock %} {% endblock %}

Datei anzeigen

@@ -76,7 +76,6 @@
</select> </select>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<button type="submit" class="btn btn-primary">Filter anwenden</button>
<a href="/licenses" class="btn btn-outline-secondary">Zurücksetzen</a> <a href="/licenses" class="btn btn-outline-secondary">Zurücksetzen</a>
</div> </div>
</div> </div>
@@ -241,6 +240,34 @@
{% block extra_js %} {% block extra_js %}
<script> <script>
// Live Filtering
document.addEventListener('DOMContentLoaded', function() {
const filterForm = document.getElementById('filterForm');
const searchInput = document.getElementById('search');
const typeSelect = document.getElementById('type');
const statusSelect = document.getElementById('status');
// Debounce timer für Suchfeld
let searchTimeout;
// Live-Filter für Suchfeld (mit 300ms Verzögerung)
searchInput.addEventListener('input', function() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
filterForm.submit();
}, 300);
});
// Live-Filter für Dropdowns (sofort)
typeSelect.addEventListener('change', function() {
filterForm.submit();
});
statusSelect.addEventListener('change', function() {
filterForm.submit();
});
});
// Copy to Clipboard // Copy to Clipboard
function copyToClipboard(text, button) { function copyToClipboard(text, button) {
navigator.clipboard.writeText(text).then(function() { navigator.clipboard.writeText(text).then(function() {