feat(PROJ-17): Admin Dashboard Systemauslastung immer anzeigen

- Systemauslastungs-Sektion wird immer gerendert (nicht nur bei Erfolg)
- Fehlermeldung wenn /api/admin/system/stats nicht erreichbar ist
- Feature-Status auf In Review gesetzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-03-14 11:43:19 +01:00
parent a893084a88
commit d360c9a5ba
68 changed files with 11938 additions and 435 deletions
+184
View File
@@ -0,0 +1,184 @@
# PROJ-7: E-Mail-Ansicht (Lesen & Anhänge)
## Status: In Progress
**Created:** 2026-03-12
**Last Updated:** 2026-03-12
## Dependencies
- Requires: PROJ-1 (Authentifizierung) nur eingeloggte Nutzer mit Zugriffsrecht
- Requires: PROJ-5 (Speicherung & Indexierung) E-Mail-Daten aus der Datenbank
## User Stories
- Als Nutzer möchte ich eine E-Mail aus den Suchergebnissen öffnen und lesen, damit ich den vollständigen Inhalt sehe.
- Als Nutzer möchte ich Anhänge herunterladen, damit ich auf angefügte Dokumente zugreifen kann.
- Als Nutzer möchte ich die originalen E-Mail-Header einsehen (technische Details), damit ich Routing und Authentizität prüfen kann.
- Als Nutzer möchte ich E-Mails im HTML-Format sehen (mit sanitizierten externen Inhalten), damit die Formatierung erhalten bleibt.
- Als Nutzer möchte ich die Originalmail als EML herunterladen können.
## Acceptance Criteria
- [ ] E-Mail-Detailansicht zeigt: Von, An, CC, Datum, Betreff, Body
- [ ] HTML-Body wird **original** dargestellt kein Entfernen oder Verändern von Inhalten
- [ ] Darstellung in einem vollständig isolierten `<iframe sandbox>` kein JavaScript aus der Mail kann ausgeführt werden
- [ ] Externe Bilder/Ressourcen standardmäßig blockiert, Nutzer kann per Button "externe Inhalte laden" freischalten
- [ ] Fallback auf Plain-Text wenn kein HTML vorhanden
- [ ] Anhang-Liste mit Dateiname, Typ und Größe
- [ ] Anhänge einzeln herunterladbar
- [ ] Header-Ansicht (klappbar) zeigt alle Original-MIME-Header
- [ ] Download der Original-E-Mail als .eml Datei
- [ ] Zugriffsschutz: Nutzer kann nur E-Mails aus eigenen Postfächern öffnen
- [ ] Jeder Zugriff auf eine E-Mail wird im Audit-Log erfasst
## Edge Cases
- E-Mail mit nur Plain-Text → normales Rendering ohne HTML
- HTML mit JavaScript → Script wird durch iframe-Sandbox blockiert, HTML-Inhalt bleibt unverändert sichtbar
- Externe Tracker (Pixel, Links) → standardmäßig blockiert durch CSP, auf Wunsch des Nutzers freischaltbar
- E-Mail mit sehr großen Anhängen (> 100 MB) → Download-Streaming, kein Speicher-Overflow
- E-Mail mit verschachteltem MIME (E-Mail in E-Mail als Anhang) → als EML-Anhang anzeigen
- Nicht unterstützte Zeichenkodierung → graceful Fallback mit Hinweis
## Technical Requirements
- **Kein HTML-Sanitizing** originale Darstellung ohne Veränderung des Inhalts
- Isolation über `<iframe sandbox="allow-same-origin">` JavaScript blockiert, Inhalt originalgetreu
- Externe Ressourcen über CSP (`Content-Security-Policy`) serverseitig blockiert, opt-in per Nutzer-Aktion
- Anhang-Downloads als Stream (kein vollständiges In-Memory-Laden)
- Audit-Log-Eintrag: Nutzer-ID, E-Mail-ID, Zeitstempel bei jedem Lesezugriff
---
## Tech Design (Solution Architect)
### Komponentenstruktur
**Next.js Frontend:**
```
/mail/[message_id]
├── MailHeader
│ ├── Betreff
│ ├── Von / An / CC / Datum / Größe
│ └── HeaderToggle (klappbar)
│ └── RawHeaderView ← alle Original-MIME-Header als Text
├── ActionBar
│ ├── EML-Download Button ← lädt Original-Mail herunter
│ ├── Externe Inhalte laden ← Button, standardmäßig deaktiviert
│ └── Zurück zur Suche
├── MailBody
│ ├── HtmlView ← originales HTML in <iframe sandbox>
│ │ └── ExternalContentBanner ← Hinweis "Externe Inhalte blockiert [Laden]"
│ └── PlainTextView ← Fallback wenn kein HTML vorhanden
└── AttachmentList
└── AttachmentItem (pro Anhang)
├── Icon (nach MIME-Type)
├── Dateiname + Typ + Größe
└── Download-Button ← direkter Stream vom Go-Backend
```
**Go Backend:**
```
GET /api/mails/{message_id}
├── Session Middleware ← Auth prüfen
├── Zugriffsrecht prüfen ← user: nur eigenes Postfach / auditor: alle
├── .m-Datei von Disk lesen ← Pfad aus PostgreSQL
├── AES-256-GCM entschlüsseln ← Schlüssel aus Prozessspeicher
├── MIME-Parser ← Body + Header + Anhang-Metadaten extrahieren
└── JSON-Antwort ← Metadaten + originaler HTML-Body + Anhang-Liste
GET /api/mails/{message_id}/attachments/{index}
├── Session Middleware
├── Zugriffsrecht prüfen
├── Hash aus PostgreSQL ← welche astore/-Datei?
├── astore/-Datei öffnen
├── AES-256-GCM entschlüsseln (stream)
└── HTTP-Streaming-Response ← Content-Disposition: attachment
GET /api/mails/{message_id}/raw
├── Session Middleware
├── Zugriffsrecht prüfen
├── .m-Datei entschlüsseln
└── HTTP-Streaming-Response ← Content-Type: message/rfc822
```
### Datenabruf-Fluss
```
Browser klickt MailCard aus Suchergebnissen
│ GET /api/mails/<message_id>
Zugriffsrecht prüfen
PostgreSQL → store_path
.m-Datei lesen + AES-256-GCM entschlüsseln
MIME-Parser → body_html (original, unverändert), body_plain, headers[], attachments[]
JSON-Antwort an Next.js
Next.js:
├── HTML → <iframe sandbox="allow-same-origin">
│ └── CSP-Header blockiert externe Ressourcen
│ Nutzer klickt "Externe Inhalte laden"
│ → iframe neu laden ohne CSP-Restriction
└── Anhang-Liste → Download-Links
```
### Technische Entscheidungen
| Entscheidung | Begründung |
|---|---|
| **Kein HTML-Sanitizing** | Originale Darstellung kein Inhalt wird verändert oder entfernt |
| **`<iframe sandbox>`** | JavaScript aus der Mail wird blockiert ohne den HTML-Inhalt zu verändern Inhalt bleibt originalgetreu |
| **CSP für externe Ressourcen** | Tracking-Pixel und externe Bilder standardmäßig blockiert Nutzer kann bewusst freischalten |
| **Entschlüsselung nur im Backend** | Verschlüsselte Rohdaten verlassen den Server nie |
| **Anhang-Download als Stream** | Große Anhänge (>100 MB) nie komplett in RAM direkt von Disk zum Browser |
| **Kein Audit-Log bei Lesezugriff** | Bewusste Entscheidung (PROJ-11): Lesezugriffe werden nicht geloggt |
### Abhängigkeiten
**Go Backend:**
| Paket | Zweck |
|---|---|
| `mime`, `mime/multipart` | MIME-Parsing (Stdlib) |
**Next.js Frontend:** Nur shadcn/ui (bereits installiert), kein zusätzliches Paket nötig.
## QA Test Results
_To be added by /qa_
## Deployment
### Lokal bauen
```bash
# Im Projektverzeichnis
npm run build
```
Build-Artefakt liegt danach in `.next/`.
### Auf Server übertragen (192.168.1.131)
```bash
# Next.js-Build + Abhängigkeiten übertragen
rsync -avz --delete \
.next/ \
package.json \
package-lock.json \
next.config.ts \
root@192.168.1.131:/opt/archivmail/frontend/
# Auf dem Server: Abhängigkeiten installieren & Dienst neu starten
ssh root@192.168.1.131 "cd /opt/archivmail/frontend && npm ci --omit=dev && systemctl restart archivmail-frontend"
```
### Voraussetzungen auf dem Server
- Node.js ≥ 20 installiert (`node -v`)
- Verzeichnis `/opt/archivmail/frontend/` existiert
- Systemd-Unit `archivmail-frontend` läuft `npm run start` (Port 3000)
- Go-Backend läuft auf Port 8080, Next.js proxied `/api/*` dorthin (siehe `next.config.ts`)