From 5fe26178570d05a7593228bc21d12e4becd7bcb6 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Thu, 5 Mar 2026 18:05:48 +0100 Subject: [PATCH] refactor: Unified Inbox aus Core entfernt (nach Lehrer migriert) - Mail-Seite, API-Route, Kommunikation-Kategorie entfernt - Screen-Flow: Mail-Node und Kommunikation-Legende entfernt Co-Authored-By: Claude Opus 4.6 --- .../app/(admin)/communication/mail/page.tsx | 945 ------------------ .../(admin)/development/screen-flow/page.tsx | 12 +- admin-core/app/api/admin/mail/route.ts | 81 -- admin-core/lib/navigation.ts | 23 +- 4 files changed, 2 insertions(+), 1059 deletions(-) delete mode 100644 admin-core/app/(admin)/communication/mail/page.tsx delete mode 100644 admin-core/app/api/admin/mail/route.ts diff --git a/admin-core/app/(admin)/communication/mail/page.tsx b/admin-core/app/(admin)/communication/mail/page.tsx deleted file mode 100644 index 2ddb70b..0000000 --- a/admin-core/app/(admin)/communication/mail/page.tsx +++ /dev/null @@ -1,945 +0,0 @@ -'use client' - -/** - * Unified Inbox Mail Admin Page - * Migrated from website/admin/mail to admin-v2/communication/mail - * - * Admin interface for managing email accounts, viewing system status, - * and configuring AI analysis settings. - */ - -import { useState, useEffect, useCallback } from 'react' -import Link from 'next/link' -import { PagePurpose } from '@/components/common/PagePurpose' - -// API Base URL for backend operations (accounts, sync, etc.) -const API_BASE = process.env.NEXT_PUBLIC_KLAUSUR_SERVICE_URL || 'http://macmini:8086' - -// Types -interface EmailAccount { - id: string - email: string - displayName: string - imapHost: string - imapPort: number - smtpHost: string - smtpPort: number - status: 'active' | 'inactive' | 'error' | 'syncing' - lastSync: string | null - emailCount: number - unreadCount: number - createdAt: string -} - -interface MailStats { - totalAccounts: number - activeAccounts: number - totalEmails: number - unreadEmails: number - totalTasks: number - pendingTasks: number - overdueTasks: number - aiAnalyzedCount: number - lastSyncTime: string | null -} - -interface SyncStatus { - running: boolean - accountsInProgress: string[] - lastCompleted: string | null - errors: string[] -} - -// Tab definitions -type TabId = 'overview' | 'accounts' | 'ai-settings' | 'templates' | 'logs' - -const tabs: { id: TabId; name: string }[] = [ - { id: 'overview', name: 'Uebersicht' }, - { id: 'accounts', name: 'Konten' }, - { id: 'ai-settings', name: 'KI-Einstellungen' }, - { id: 'templates', name: 'Vorlagen' }, - { id: 'logs', name: 'Audit-Log' }, -] - -// Main Component -export default function MailAdminPage() { - const [activeTab, setActiveTab] = useState('overview') - const [stats, setStats] = useState(null) - const [accounts, setAccounts] = useState([]) - const [syncStatus, setSyncStatus] = useState(null) - const [loading, setLoading] = useState(true) - const [error, setError] = useState(null) - - const fetchData = useCallback(async () => { - try { - setLoading(true) - - // Fetch stats via our proxy API (avoids CORS/mixed-content issues) - const response = await fetch('/api/admin/mail') - - if (response.ok) { - const data = await response.json() - setStats(data.stats) - setAccounts(data.accounts) - setSyncStatus(data.syncStatus) - setError(null) - } else { - const errorData = await response.json().catch(() => ({})) - throw new Error(errorData.details || `API returned ${response.status}`) - } - } catch (err) { - console.error('Failed to fetch mail data:', err) - setError('Verbindung zum Mail-Service (Mailpit) fehlgeschlagen. Laeuft Mailpit auf Port 8025?') - } finally { - setLoading(false) - } - }, []) - - useEffect(() => { - fetchData() - - // Refresh every 10 seconds if syncing - const interval = setInterval(() => { - if (syncStatus?.running) { - fetchData() - } - }, 10000) - - return () => clearInterval(interval) - }, [fetchData, syncStatus?.running]) - - return ( -
- {/* Page Purpose */} - - - {/* Quick Link to Wizard */} -
- - - - - Mail Wizard starten - -
- - {/* Error Banner */} - {error && ( -
- - - - {error} - -
- )} - - {/* Tab Navigation */} -
- -
- - {/* Tab Content */} - {activeTab === 'overview' && ( - - )} - {activeTab === 'accounts' && ( - - )} - {activeTab === 'ai-settings' && ( - - )} - {activeTab === 'templates' && ( - - )} - {activeTab === 'logs' && ( - - )} -
- ) -} - -// ============================================================================ -// Overview Tab -// ============================================================================ - -function OverviewTab({ - stats, - syncStatus, - loading, - onRefresh -}: { - stats: MailStats | null - syncStatus: SyncStatus | null - loading: boolean - onRefresh: () => void -}) { - const triggerSync = async () => { - try { - await fetch(`${API_BASE}/api/v1/mail/sync/all`, { - method: 'POST', - }) - onRefresh() - } catch (err) { - console.error('Failed to trigger sync:', err) - } - } - - return ( -
- {/* Header */} -
-
-

System-Uebersicht

-

Status aller E-Mail-Konten und Aufgaben

-
-
- - -
-
- - {/* Loading State */} - {loading && ( -
-
-
- )} - - {/* Stats Grid */} - {!loading && stats && ( - <> -
- - - - 0 ? 'red' : 'green'} - /> -
- - {/* Sync Status */} -
-

Synchronisierung

-
- {syncStatus?.running ? ( - <> -
- - Synchronisiere {syncStatus.accountsInProgress.length} Konto(en)... - - - ) : ( - <> -
- Bereit - - )} - {stats.lastSyncTime && ( - - Letzte Sync: {new Date(stats.lastSyncTime).toLocaleString('de-DE')} - - )} -
- - {syncStatus?.errors && syncStatus.errors.length > 0 && ( -
-

Fehler

-
    - {syncStatus.errors.slice(0, 3).map((error, i) => ( -
  • {error}
  • - ))} -
-
- )} -
- - {/* AI Stats */} -
-

KI-Analyse

-
-
-

Analysiert

-

{stats.aiAnalyzedCount}

-
-
-

Analyse-Rate

-

- {stats.totalEmails > 0 - ? `${Math.round((stats.aiAnalyzedCount / stats.totalEmails) * 100)}%` - : '0%'} -

-
-
-
- - )} -
- ) -} - -function StatCard({ - title, - value, - subtitle, - color = 'blue' -}: { - title: string - value: number - subtitle?: string - color?: 'blue' | 'green' | 'yellow' | 'red' -}) { - const colorClasses = { - blue: 'text-blue-600', - green: 'text-green-600', - yellow: 'text-yellow-600', - red: 'text-red-600', - } - - return ( -
-

{title}

-

{value.toLocaleString()}

- {subtitle &&

{subtitle}

} -
- ) -} - -// ============================================================================ -// Accounts Tab -// ============================================================================ - -function AccountsTab({ - accounts, - loading, - onRefresh -}: { - accounts: EmailAccount[] - loading: boolean - onRefresh: () => void -}) { - const [showAddModal, setShowAddModal] = useState(false) - - const testConnection = async (accountId: string) => { - try { - const res = await fetch(`${API_BASE}/api/v1/mail/accounts/${accountId}/test`, { - method: 'POST', - }) - if (res.ok) { - alert('Verbindung erfolgreich!') - } else { - alert('Verbindungsfehler') - } - } catch (err) { - alert('Verbindungsfehler') - } - } - - const statusColors = { - active: 'bg-green-100 text-green-800', - inactive: 'bg-gray-100 text-gray-800', - error: 'bg-red-100 text-red-800', - syncing: 'bg-yellow-100 text-yellow-800', - } - - const statusLabels = { - active: 'Aktiv', - inactive: 'Inaktiv', - error: 'Fehler', - syncing: 'Synchronisiert...', - } - - return ( -
- {/* Header */} -
-
-

E-Mail-Konten

-

Verwalten Sie die verbundenen E-Mail-Konten

-
- -
- - {/* Loading State */} - {loading && ( -
-
-
- )} - - {/* Accounts Grid */} - {!loading && ( -
- {accounts.length === 0 ? ( -
- - - -

Keine E-Mail-Konten

-

Fuegen Sie Ihr erstes E-Mail-Konto hinzu.

-
- ) : ( - accounts.map((account) => ( -
-
-
-
- - - -
-
-

- {account.displayName || account.email} -

-

{account.email}

-
-
-
- - {statusLabels[account.status]} - - -
-
- -
-
-

E-Mails

-

{account.emailCount}

-
-
-

Ungelesen

-

{account.unreadCount}

-
-
-

IMAP

-

{account.imapHost}:{account.imapPort}

-
-
-

Letzte Sync

-

- {account.lastSync - ? new Date(account.lastSync).toLocaleString('de-DE') - : 'Nie'} -

-
-
-
- )) - )} -
- )} - - {/* Add Account Modal */} - {showAddModal && ( - setShowAddModal(false)} onSuccess={() => { setShowAddModal(false); onRefresh(); }} /> - )} -
- ) -} - -function AddAccountModal({ - onClose, - onSuccess -}: { - onClose: () => void - onSuccess: () => void -}) { - const [formData, setFormData] = useState({ - email: '', - displayName: '', - imapHost: '', - imapPort: 993, - smtpHost: '', - smtpPort: 587, - username: '', - password: '', - }) - const [submitting, setSubmitting] = useState(false) - const [error, setError] = useState(null) - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() - setSubmitting(true) - setError(null) - - try { - const res = await fetch(`${API_BASE}/api/v1/mail/accounts`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - email: formData.email, - display_name: formData.displayName, - imap_host: formData.imapHost, - imap_port: formData.imapPort, - smtp_host: formData.smtpHost, - smtp_port: formData.smtpPort, - username: formData.username, - password: formData.password, - }), - }) - - if (res.ok) { - onSuccess() - } else { - const data = await res.json() - setError(data.detail || 'Fehler beim Hinzufuegen des Kontos') - } - } catch (err) { - setError('Netzwerkfehler') - } finally { - setSubmitting(false) - } - } - - return ( -
-
-
-

E-Mail-Konto hinzufuegen

-
- -
- {error && ( -
{error}
- )} - -
-
- - setFormData({ ...formData, email: e.target.value })} - className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500" - placeholder="schulleitung@grundschule-xy.de" - /> -
-
- - setFormData({ ...formData, displayName: e.target.value })} - className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500" - placeholder="Schulleitung" - /> -
- -
- - setFormData({ ...formData, imapHost: e.target.value })} - className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500" - placeholder="imap.example.com" - /> -
-
- - setFormData({ ...formData, imapPort: parseInt(e.target.value) })} - className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500" - /> -
- -
- - setFormData({ ...formData, smtpHost: e.target.value })} - className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500" - placeholder="smtp.example.com" - /> -
-
- - setFormData({ ...formData, smtpPort: parseInt(e.target.value) })} - className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500" - /> -
- -
- - setFormData({ ...formData, username: e.target.value })} - className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500" - /> -
- -
- - setFormData({ ...formData, password: e.target.value })} - className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500" - /> -

- Das Passwort wird verschluesselt in Vault gespeichert. -

-
-
- -
- - -
-
-
-
- ) -} - -// ============================================================================ -// AI Settings Tab -// ============================================================================ - -function AISettingsTab() { - const [settings, setSettings] = useState({ - autoAnalyze: true, - autoCreateTasks: true, - analysisModel: 'breakpilot-teacher-8b', - confidenceThreshold: 0.7, - }) - - return ( -
-
-

KI-Einstellungen

-

Konfigurieren Sie die automatische E-Mail-Analyse

-
- -
- {/* Auto-Analyze */} -
-
-

Automatische Analyse

-

E-Mails automatisch beim Empfang analysieren

-
- -
- - {/* Auto-Create Tasks */} -
-
-

Aufgaben automatisch erstellen

-

Erkannte Fristen als Aufgaben anlegen

-
- -
- - {/* Model Selection */} -
- - -
- - {/* Confidence Threshold */} -
- - setSettings({ ...settings, confidenceThreshold: parseFloat(e.target.value) })} - className="w-full md:w-64" - /> -

- Mindest-Konfidenz fuer automatische Aufgabenerstellung -

-
-
- - {/* Sender Classification */} -
-

Bekannte Absender (Niedersachsen)

-
- {[ - { domain: '@mk.niedersachsen.de', type: 'Kultusministerium', priority: 'Hoch' }, - { domain: '@rlsb.de', type: 'RLSB', priority: 'Hoch' }, - { domain: '@landesschulbehoerde-nds.de', type: 'Landesschulbehoerde', priority: 'Hoch' }, - { domain: '@nibis.de', type: 'NiBiS', priority: 'Mittel' }, - { domain: '@schultraeger.de', type: 'Schultraeger', priority: 'Mittel' }, - ].map((sender) => ( -
-

{sender.domain}

-

{sender.type}

- - {sender.priority} - -
- ))} -
-
-
- ) -} - -// ============================================================================ -// Templates Tab -// ============================================================================ - -function TemplatesTab() { - const [templates] = useState([ - { id: '1', name: 'Eingangsbestaetigung', category: 'Standard', usageCount: 45 }, - { id: '2', name: 'Terminbestaetigung', category: 'Termine', usageCount: 23 }, - { id: '3', name: 'Elternbrief-Vorlage', category: 'Eltern', usageCount: 67 }, - ]) - - return ( -
-
-
-

E-Mail-Vorlagen

-

Verwalten Sie Antwort-Templates

-
- -
- -
- - - - - - - - - - - {templates.map((template) => ( - - - - - - - ))} - -
NameKategorieVerwendetAktionen
{template.name} - {template.category} - {template.usageCount}x - -
-
-
- ) -} - -// ============================================================================ -// Audit Log Tab -// ============================================================================ - -function AuditLogTab() { - const [logs] = useState([ - { id: '1', action: 'account_created', user: 'admin@breakpilot.de', timestamp: new Date().toISOString(), details: 'Konto schulleitung@example.de hinzugefuegt' }, - { id: '2', action: 'email_analyzed', user: 'system', timestamp: new Date(Date.now() - 3600000).toISOString(), details: '5 E-Mails analysiert' }, - { id: '3', action: 'task_created', user: 'system', timestamp: new Date(Date.now() - 7200000).toISOString(), details: 'Aufgabe aus Fristenerkennung erstellt' }, - ]) - - const actionLabels: Record = { - account_created: 'Konto erstellt', - email_analyzed: 'E-Mail analysiert', - task_created: 'Aufgabe erstellt', - sync_completed: 'Sync abgeschlossen', - } - - return ( -
-
-

Audit-Log

-

Alle Aktionen im Mail-System

-
- -
- - - - - - - - - - - {logs.map((log) => ( - - - - - - - ))} - -
ZeitAktionBenutzerDetails
- {new Date(log.timestamp).toLocaleString('de-DE')} - - - {actionLabels[log.action] || log.action} - - {log.user}{log.details}
-
-
- ) -} diff --git a/admin-core/app/(admin)/development/screen-flow/page.tsx b/admin-core/app/(admin)/development/screen-flow/page.tsx index e661dcb..c9199e2 100644 --- a/admin-core/app/(admin)/development/screen-flow/page.tsx +++ b/admin-core/app/(admin)/development/screen-flow/page.tsx @@ -13,10 +13,9 @@ import ReactFlow, { } from 'reactflow' import 'reactflow/dist/style.css' -type CategoryFilter = 'all' | 'communication' | 'infrastructure' | 'development' | 'meta' +type CategoryFilter = 'all' | 'infrastructure' | 'development' | 'meta' const categoryColors: Record = { - communication: '#22c55e', infrastructure: '#f97316', development: '#64748b', meta: '#0ea5e9', @@ -27,9 +26,6 @@ const initialNodes: Node[] = [ { id: 'role-select', position: { x: 400, y: 0 }, data: { label: 'Rollenauswahl', category: 'meta' }, style: { background: '#e0f2fe', border: '2px solid #0ea5e9', borderRadius: '12px', padding: '10px 16px' } }, { id: 'dashboard', position: { x: 400, y: 100 }, data: { label: 'Dashboard', category: 'meta' }, style: { background: '#e0f2fe', border: '2px solid #0ea5e9', borderRadius: '12px', padding: '10px 16px' } }, - // Communication (Green) - { id: 'mail', position: { x: 50, y: 250 }, data: { label: 'Unified Inbox', category: 'communication' }, style: { background: '#dcfce7', border: '2px solid #22c55e', borderRadius: '12px', padding: '10px 16px' } }, - // Infrastructure (Orange) { id: 'gpu', position: { x: 300, y: 250 }, data: { label: 'GPU Infrastruktur', category: 'infrastructure' }, style: { background: '#ffedd5', border: '2px solid #f97316', borderRadius: '12px', padding: '10px 16px' } }, { id: 'middleware', position: { x: 300, y: 350 }, data: { label: 'Middleware', category: 'infrastructure' }, style: { background: '#ffedd5', border: '2px solid #f97316', borderRadius: '12px', padding: '10px 16px' } }, @@ -49,7 +45,6 @@ const initialEdges: Edge[] = [ { id: 'e-role-dash', source: 'role-select', target: 'dashboard', markerEnd: { type: MarkerType.ArrowClosed }, style: { stroke: '#0ea5e9' } }, // Dashboard to categories - { id: 'e-dash-mail', source: 'dashboard', target: 'mail', markerEnd: { type: MarkerType.ArrowClosed }, style: { stroke: '#22c55e' } }, { id: 'e-dash-gpu', source: 'dashboard', target: 'gpu', markerEnd: { type: MarkerType.ArrowClosed }, style: { stroke: '#f97316' } }, { id: 'e-dash-cicd', source: 'dashboard', target: 'ci-cd', markerEnd: { type: MarkerType.ArrowClosed }, style: { stroke: '#f97316' } }, { id: 'e-dash-docs', source: 'dashboard', target: 'docs', markerEnd: { type: MarkerType.ArrowClosed }, style: { stroke: '#64748b' } }, @@ -83,7 +78,6 @@ export default function ScreenFlowPage() { const filters: { id: CategoryFilter; label: string; color: string }[] = [ { id: 'all', label: 'Alle', color: '#0ea5e9' }, - { id: 'communication', label: 'Kommunikation', color: '#22c55e' }, { id: 'infrastructure', label: 'Infrastruktur', color: '#f97316' }, { id: 'development', label: 'Entwicklung', color: '#64748b' }, ] @@ -149,10 +143,6 @@ export default function ScreenFlowPage() { {/* Legend */}
-
-
- Kommunikation (4) -
Infrastruktur (6) diff --git a/admin-core/app/api/admin/mail/route.ts b/admin-core/app/api/admin/mail/route.ts deleted file mode 100644 index 41d1a97..0000000 --- a/admin-core/app/api/admin/mail/route.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { NextResponse } from 'next/server' - -/** - * Server-side proxy for Mailpit API - * Avoids CORS and mixed-content issues by fetching from server - */ - -// Use internal Docker hostname when running in container -const getMailpitHost = (): string => { - return process.env.BACKEND_URL ? 'mailpit' : 'localhost' -} - -export async function GET() { - const host = getMailpitHost() - const mailpitUrl = `http://${host}:8025/api/v1/info` - - try { - const response = await fetch(mailpitUrl, { - method: 'GET', - signal: AbortSignal.timeout(5000), - }) - - if (!response.ok) { - return NextResponse.json( - { error: 'Mailpit API error', status: response.status }, - { status: response.status } - ) - } - - const data = await response.json() - - // Transform Mailpit response to our expected format - return NextResponse.json({ - stats: { - totalAccounts: 1, - activeAccounts: 1, - totalEmails: data.Messages || 0, - unreadEmails: data.Unread || 0, - totalTasks: 0, - pendingTasks: 0, - overdueTasks: 0, - aiAnalyzedCount: 0, - lastSyncTime: new Date().toISOString(), - }, - accounts: [{ - id: 'mailpit-dev', - email: 'dev@mailpit.local', - displayName: 'Mailpit (Development)', - imapHost: 'mailpit', - imapPort: 1143, - smtpHost: 'mailpit', - smtpPort: 1025, - status: 'active' as const, - lastSync: new Date().toISOString(), - emailCount: data.Messages || 0, - unreadCount: data.Unread || 0, - createdAt: new Date().toISOString(), - }], - syncStatus: { - running: false, - accountsInProgress: [], - lastCompleted: new Date().toISOString(), - errors: [], - }, - mailpitInfo: { - version: data.Version, - databaseSize: data.DatabaseSize, - uptime: data.RuntimeStats?.Uptime, - } - }) - } catch (error) { - console.error('Failed to fetch from Mailpit:', error) - return NextResponse.json( - { - error: 'Failed to connect to Mailpit', - details: error instanceof Error ? error.message : 'Unknown error' - }, - { status: 503 } - ) - } -} diff --git a/admin-core/lib/navigation.ts b/admin-core/lib/navigation.ts index 8e7fe8c..db623f1 100644 --- a/admin-core/lib/navigation.ts +++ b/admin-core/lib/navigation.ts @@ -4,7 +4,7 @@ * 3 Categories: Communication, Infrastructure, Development */ -export type CategoryId = 'communication' | 'infrastructure' | 'development' +export type CategoryId = 'infrastructure' | 'development' export interface NavModule { id: string @@ -27,27 +27,6 @@ export interface NavCategory { } export const navigation: NavCategory[] = [ - // ========================================================================= - // Kommunikation & Alerts (Green) - // ========================================================================= - { - id: 'communication', - name: 'Kommunikation', - icon: 'message-circle', - color: '#22c55e', - colorClass: 'communication', - description: 'E-Mail Management', - modules: [ - { - id: 'mail', - name: 'Unified Inbox', - href: '/communication/mail', - description: 'E-Mail-Konten & KI-Analyse', - purpose: 'E-Mail-Konten verwalten und KI-Kategorisierung nutzen.', - audience: ['Support', 'Admins'], - }, - ], - }, // ========================================================================= // Infrastruktur & DevOps (Orange) // =========================================================================