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:
@@ -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`)
|
||||
Reference in New Issue
Block a user