spawner/docs/guides/admin-dashboard-improvements.md
XPS\Micro 1392316068 feat: implement multi-container deletion, DSGVO compliance, and toast notifications
- Add CASCADE DELETE to MagicLinkToken and AdminTakeoverSession models
- Update admin_api.py to support multi-container deletion
- Add DSGVO-compliant user deletion (removes tokens and sessions)
- Integrate Sonner toast system in frontend
- Add bulk-operations UI (select, block, unblock, delete users)
- Implement two-step confirmation for critical actions
- Update TypeScript config for Set iteration
- Add comprehensive documentation in docs/guides/
2026-02-02 17:19:48 +01:00

10 KiB

Admin-Dashboard: Verbesserte Container- und User-Löschung

Datum: 02.02.2026 Version: 2.0 Status: Vollständig implementiert


📋 Übersicht

Diese Dokumentation beschreibt die Verbesserungen des Admin-Dashboards:

  1. Multi-Container-Deletion - Alle Container eines Users löschen (nicht nur Primary)
  2. Toast-Benachrichtigungen - Modernes UI statt primitiver Alerts
  3. Bulk-Operations - Mehrere User gleichzeitig verwalten (Sperren, Löschen, etc.)
  4. DSGVO-Compliance - Vollständige Datenlöschung (MagicLinkToken, AdminTakeoverSession)

🔧 Technische Änderungen

1. Backend - Multi-Container & DSGVO

Datei: admin_api.py

DELETE /api/admin/users/<id>/container (aktualisiert)

Löscht alle Docker-Container eines Users (nicht nur Primary Container):

# Vorher (begrenzt auf Primary):
if user.container_id:
    container_mgr.stop_container(user.container_id)
    container_mgr.remove_container(user.container_id)

# Nachher (alle Container):
for container in user.containers:
    if container.container_id:
        container_mgr.stop_container(container.container_id)
        container_mgr.remove_container(container.container_id)
        db.session.delete(container)

Response:

{
  "message": "Alle 3 Container von user@example.com wurden gelöscht",
  "deleted": 3,
  "failed": []
}

DELETE /api/admin/users/<id> (aktualisiert - DSGVO)

Löscht einen User komplett mit allen Daten:

# 1. Docker-Container löschen
# 2. MagicLinkToken löschen (DSGVO: IP-Adressen)
# 3. AdminTakeoverSession löschen (als Target-User)
# 4. User-Account löschen (CASCADE löscht UserContainer)

Response mit DSGVO-Summary:

{
  "message": "User test@example.com wurde vollständig gelöscht",
  "summary": {
    "containers_deleted": 3,
    "containers_failed": [],
    "magic_tokens_deleted": 5,
    "takeover_sessions_deleted": 0
  }
}

2. Datenbank - CASCADE DELETE

Datei: models.py

MagicLinkToken (Zeile 110-118):

user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False)
user = db.relationship('User', backref=db.backref('magic_tokens', lazy=True, cascade='all, delete-orphan'))

AdminTakeoverSession (Zeile 171-180):

admin_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='SET NULL'), nullable=True)
target_user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False)

admin = db.relationship('User', foreign_keys=[admin_id],
                       backref=db.backref('takeover_sessions_as_admin', lazy=True))
target_user = db.relationship('User', foreign_keys=[target_user_id],
                             backref=db.backref('takeover_sessions_as_target', lazy=True, cascade='all, delete-orphan'))

Warum:

  • admin_id: SET NULL - Erhält Audit-Log auch wenn Admin gelöscht wird
  • target_user_id: CASCADE - Session wird gelöscht wenn User gelöscht wird
  • Verhindert Foreign-Key-Constraint-Fehler

3. Frontend - Toast-System & Bulk-Operations

Datei: frontend/package.json

{
  "dependencies": {
    "sonner": "^1.7.2"
  }
}

Datei: frontend/src/app/layout.tsx

import { Toaster } from "sonner";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Toaster position="top-right" richColors />
      </body>
    </html>
  );
}

Datei: frontend/src/app/admin/page.tsx - Neue Features:

Toast-Nachrichten statt primitiver Alerts:

// Vorher
setSuccessMessage(message);
setTimeout(() => setSuccessMessage(""), 3000);

// Nachher
toast.success(message);  // Modern, stackbar, dunkler
toast.error(`Fehler: ${error}`);
toast.loading("Lösche User...", { id: "delete" });

Bulk-Selection UI:

  • User-Checkboxen pro Zeile (nicht Admin/CurrentUser)
  • "Select All" Checkbox für gefilterte User
  • Bulk-Action-Bar mit 4 Aktionen:
    • Sperren / Entsperren
    • Container löschen
    • User löschen (mit Zwei-Schritt-Bestätigung)

Zwei-Schritt-Bestätigung bei kritischen Aktionen:

// Schritt 1: Vorschau mit Warnung
if (!confirm(`⚠️ WARNUNG: 3 User löschen?\n\nDies löscht:\n- User-Accounts\n- Alle Container\n- Alle Tokens`)) {
  return;
}

// Schritt 2: Numerische Bestätigung
const confirmation = prompt(`Geben Sie die Anzahl ein (3):`);
if (confirmation !== "3") {
  toast.error("Abgebrochen");
  return;
}

🚀 Deployment

Vorbereitungen:

  1. Backend:

    # Syntax-Check
    python -m py_compile admin_api.py models.py
    
  2. Frontend:

    cd frontend
    npm install  # Installiert sonner
    npm run build
    npx tsc --noEmit  # TypeScript-Check
    

Deployment-Befehle:

# Lokal/Entwicklung
cd /volume1/docker/spawner
git pull
docker-compose up -d --build

Nach Deployment:

# Logs checken
docker-compose logs -f spawner

# Admin-Dashboard testen
# 1. Einen User mit Containern erstellen
# 2. Container löschen → Toast sollte erscheinen
# 3. User löschen → Toast mit Summary

🧪 Test-Szenarien

Test 1: Multi-Container-Deletion

# Voraussetzung: User mit 3 Containern (template-01, template-02, template-next)

# 1. Admin-Dashboard öffnen
# 2. Container-Icon klicken
# 3. Toast: "3 Container gelöscht"
# 4. Verify: docker ps | grep user- → Keine Container

Test 2: DSGVO-Compliance

# 1. User mit Magic Links erstellen
# 2. Admin: User löschen → Zwei-Schritt-Bestätigung
# 3. Toast mit Summary:
#    - 3 Container deleted
#    - 5 Magic Tokens deleted
#    - 0 Takeover Sessions deleted

# 4. Verify in DB:
docker exec spawner python3 -c "
from app import app, db
from models import MagicLinkToken
with app.app_context():
    tokens = MagicLinkToken.query.filter_by(user_id=123).count()
    print(f'Tokens für User 123: {tokens}')
"
# Expected: 0

Test 3: Toast-Benachrichtigungen

1. Admin-Dashboard öffnen
2. Mehrere Aktionen schnell:
   - Container löschen
   - User sperren
   - User entsperren
3. Erwartung: Toasts stacken oben-rechts, jeder mit X zum Schließen

Test 4: Bulk-Operations

1. 3 User mit Checkboxen auswählen
2. Bulk-Action-Bar erscheint
3. "Sperren" Button → Confirm → Toast "3 User gesperrt"
4. "Select All" Checkbox → Alle (außer Admin) ausgewählt
5. "User löschen" → Zwei-Schritt-Bestätigung → Toast mit Summary

📊 API-Response-Format

Single Container-Deletion:

curl -X DELETE \
  http://localhost:5000/api/admin/users/123/container \
  -H "Authorization: Bearer $TOKEN"

Response (Success):

{
  "message": "Alle 3 Container von user@example.com wurden gelöscht",
  "deleted": 3,
  "failed": []
}

Response (Partial Failure - Status 207):

{
  "message": "2 Container gelöscht, 1 fehlgeschlagen",
  "deleted": 2,
  "failed": ["a1b2c3d4"]
}

Single User-Deletion:

curl -X DELETE \
  http://localhost:5000/api/admin/users/123 \
  -H "Authorization: Bearer $TOKEN"

Response:

{
  "message": "User user@example.com wurde vollständig gelöscht",
  "summary": {
    "containers_deleted": 3,
    "containers_failed": [],
    "magic_tokens_deleted": 5,
    "takeover_sessions_deleted": 1
  }
}

⚠️ Wichtige Hinweise

Breaking Change: CASCADE DELETE

  • Foreign Key Constraints wurden aktualisiert
  • DB-Migration erforderlich (siehe unten)
  • Alte Constraints verursachen Fehler

Datenbank-Migration:

Option 1: Mit Alembic (falls installiert)

cd backend
flask db migrate -m "Add CASCADE DELETE for DSGVO"
flask db upgrade

Option 2: Manuell für SQLite

-- Backup zuerst machen!
.backup /app/spawner.db.backup

-- MagicLinkToken
ALTER TABLE magic_link_token
  DROP CONSTRAINT IF EXISTS magic_link_token_user_id_fkey;
ALTER TABLE magic_link_token
  ADD CONSTRAINT magic_link_token_user_id_fkey
  FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE;

-- AdminTakeoverSession
ALTER TABLE admin_takeover_session
  DROP CONSTRAINT IF EXISTS admin_takeover_session_admin_id_fkey;
ALTER TABLE admin_takeover_session
  DROP CONSTRAINT IF EXISTS admin_takeover_session_target_user_id_fkey;

ALTER TABLE admin_takeover_session
  ADD CONSTRAINT admin_takeover_session_admin_id_fkey
  FOREIGN KEY (admin_id) REFERENCES user(id) ON DELETE SET NULL;
ALTER TABLE admin_takeover_session
  ADD CONSTRAINT admin_takeover_session_target_user_id_fkey
  FOREIGN KEY (target_user_id) REFERENCES user(id) ON DELETE CASCADE;

Backwards Compatibility:

  • Alte API-Clients funktionieren (neue Felder sind optional)
  • Bestehende User-Daten bleiben erhalten
  • ⚠️ Nur neue Deletes sind DSGVO-konform

🔍 Troubleshooting

Problem: Toasts erscheinen nicht

# 1. Prüfe ob sonner installiert ist
cd frontend
npm list sonner

# 2. Browser-Console (F12) auf Fehler prüfen
# 3. Cache leeren: Ctrl+Shift+Del

Problem: Container-Löschung funktioniert nicht

# Logs prüfen
docker-compose logs spawner 2>&1 | tail -100

# Docker-Socket-Permissions
docker exec spawner ls -la /var/run/docker.sock

# Container manuell löschen
docker ps -a | grep user-
docker rm -f user-xyz-123

Problem: Multi-Container nicht sichtbar

# DB-Abfrage
docker exec spawner python3 -c "
from app import app, db
from models import User
with app.app_context():
    user = User.query.filter_by(email='test@example.com').first()
    print(f'User {user.email} hat {len(user.containers)} Container')
    for c in user.containers:
        print(f'  - Type: {c.container_type}, ID: {c.container_id}')
"

📚 Weitere Ressourcen


📝 Änderungshistorie

Version Datum Änderungen
2.0 02.02.2026 Multi-Container, Toast-System, Bulk-Operations, DSGVO
1.0 ≤01.02.2026 Ursprüngliches Admin-Dashboard

Fragen? Siehe Troubleshooting oder Logs prüfen: docker-compose logs spawner