Filtern geht schneller
Dieser Commit ist enthalten in:
90
JOURNAL.md
90
JOURNAL.md
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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 %}
|
||||||
@@ -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 %}
|
||||||
@@ -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() {
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren