A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1999 lines
53 KiB
Python
1999 lines
53 KiB
Python
"""
|
|
BreakPilot Studio - Unified Inbox Mail Modul
|
|
|
|
Dieses Modul bietet:
|
|
- Setup-Wizard fuer E-Mail-Konten (7 Schritte, extrem benutzerfreundlich)
|
|
- Kontoverwaltung (hinzufuegen, bearbeiten, loeschen)
|
|
- Passwort-Tresor (sichere Verwaltung der Zugangsdaten)
|
|
- Unified Inbox (alle E-Mails an einem Ort)
|
|
|
|
Zielgruppe: Lehrkraefte ohne technische Vorkenntnisse
|
|
Design-Prinzip: Ein Feld pro Bildschirm, immer erklaeren WARUM
|
|
"""
|
|
|
|
|
|
class MailInboxModule:
|
|
"""Unified Inbox Modul mit benutzerfreundlichem Setup-Wizard."""
|
|
|
|
@staticmethod
|
|
def get_css() -> str:
|
|
"""CSS fuer das Mail-Inbox-Modul."""
|
|
return """
|
|
/* ==========================================
|
|
MAIL INBOX MODULE STYLES
|
|
========================================== */
|
|
|
|
/* Panel Layout */
|
|
.panel-mail {
|
|
display: none;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
background: var(--bp-bg);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.panel-mail.active {
|
|
display: flex;
|
|
}
|
|
|
|
/* Mail Header */
|
|
.mail-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 20px 32px;
|
|
border-bottom: 1px solid var(--bp-border);
|
|
background: var(--bp-surface);
|
|
}
|
|
|
|
.mail-title-section h1 {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
color: var(--bp-text);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.mail-subtitle {
|
|
font-size: 14px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.mail-header-actions {
|
|
display: flex;
|
|
gap: 12px;
|
|
}
|
|
|
|
/* Mail Content */
|
|
.mail-content {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 32px;
|
|
}
|
|
|
|
/* Empty State - Kein Konto eingerichtet */
|
|
.mail-empty-state {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-height: 400px;
|
|
text-align: center;
|
|
padding: 40px;
|
|
}
|
|
|
|
.mail-empty-icon {
|
|
font-size: 80px;
|
|
margin-bottom: 24px;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.mail-empty-title {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
color: var(--bp-text);
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.mail-empty-description {
|
|
font-size: 16px;
|
|
color: var(--bp-text-muted);
|
|
max-width: 480px;
|
|
line-height: 1.6;
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
/* ==========================================
|
|
MAIL WIZARD MODAL
|
|
========================================== */
|
|
|
|
.mail-wizard-modal {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.7);
|
|
z-index: 10000;
|
|
justify-content: center;
|
|
align-items: center;
|
|
backdrop-filter: blur(4px);
|
|
}
|
|
|
|
.mail-wizard-modal.active {
|
|
display: flex;
|
|
}
|
|
|
|
.mail-wizard-content {
|
|
background: var(--bp-surface);
|
|
border-radius: 24px;
|
|
width: 95%;
|
|
max-width: 560px;
|
|
max-height: 90vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
box-shadow: 0 25px 80px rgba(0,0,0,0.5);
|
|
border: 1px solid var(--bp-border);
|
|
overflow: hidden;
|
|
animation: wizardSlideIn 0.3s ease;
|
|
}
|
|
|
|
@keyframes wizardSlideIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(30px) scale(0.95);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0) scale(1);
|
|
}
|
|
}
|
|
|
|
/* Wizard Header */
|
|
.wizard-header {
|
|
padding: 24px 32px 20px;
|
|
border-bottom: 1px solid var(--bp-border);
|
|
background: var(--bp-surface-elevated);
|
|
}
|
|
|
|
.wizard-close-btn {
|
|
position: absolute;
|
|
top: 16px;
|
|
right: 16px;
|
|
background: none;
|
|
border: none;
|
|
color: var(--bp-text-muted);
|
|
font-size: 24px;
|
|
cursor: pointer;
|
|
padding: 8px;
|
|
border-radius: 8px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.wizard-close-btn:hover {
|
|
background: var(--bp-surface);
|
|
color: var(--bp-text);
|
|
}
|
|
|
|
/* Step Indicator */
|
|
.wizard-steps {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.wizard-step-dot {
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
background: var(--bp-border);
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.wizard-step-dot.active {
|
|
background: var(--bp-primary);
|
|
transform: scale(1.3);
|
|
box-shadow: 0 0 0 4px var(--bp-primary-soft);
|
|
}
|
|
|
|
.wizard-step-dot.completed {
|
|
background: var(--bp-success);
|
|
}
|
|
|
|
.wizard-step-label {
|
|
text-align: center;
|
|
font-size: 12px;
|
|
color: var(--bp-text-muted);
|
|
margin-top: 8px;
|
|
}
|
|
|
|
/* Wizard Body */
|
|
.wizard-body {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 32px;
|
|
}
|
|
|
|
/* Wizard Step Content */
|
|
.wizard-step {
|
|
display: none;
|
|
}
|
|
|
|
.wizard-step.active {
|
|
display: block;
|
|
animation: stepFadeIn 0.3s ease;
|
|
}
|
|
|
|
@keyframes stepFadeIn {
|
|
from { opacity: 0; transform: translateX(20px); }
|
|
to { opacity: 1; transform: translateX(0); }
|
|
}
|
|
|
|
.wizard-step-title {
|
|
font-size: 22px;
|
|
font-weight: 700;
|
|
color: var(--bp-text);
|
|
margin-bottom: 12px;
|
|
text-align: center;
|
|
}
|
|
|
|
.wizard-step-description {
|
|
font-size: 15px;
|
|
color: var(--bp-text-muted);
|
|
text-align: center;
|
|
line-height: 1.6;
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
/* Welcome Step Illustration */
|
|
.wizard-illustration {
|
|
text-align: center;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.wizard-illustration-icon {
|
|
font-size: 72px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
/* Info Box (Warum brauchen wir das?) */
|
|
.wizard-info-box {
|
|
background: var(--bp-surface-elevated);
|
|
border: 1px solid var(--bp-border);
|
|
border-radius: 12px;
|
|
padding: 16px 20px;
|
|
margin-top: 24px;
|
|
}
|
|
|
|
.wizard-info-box-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 8px;
|
|
font-weight: 600;
|
|
color: var(--bp-text);
|
|
font-size: 14px;
|
|
}
|
|
|
|
.wizard-info-box-icon {
|
|
font-size: 18px;
|
|
}
|
|
|
|
.wizard-info-box-content {
|
|
font-size: 13px;
|
|
color: var(--bp-text-muted);
|
|
line-height: 1.6;
|
|
}
|
|
|
|
/* Security Info Box */
|
|
.wizard-info-box.security {
|
|
border-color: var(--bp-success);
|
|
background: rgba(34, 197, 94, 0.05);
|
|
}
|
|
|
|
.wizard-info-box.security .wizard-info-box-header {
|
|
color: var(--bp-success);
|
|
}
|
|
|
|
/* Warning Info Box */
|
|
.wizard-info-box.warning {
|
|
border-color: var(--bp-warning);
|
|
background: rgba(245, 158, 11, 0.05);
|
|
}
|
|
|
|
.wizard-info-box.warning .wizard-info-box-header {
|
|
color: var(--bp-warning);
|
|
}
|
|
|
|
/* Form Fields */
|
|
.wizard-field {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.wizard-field-label {
|
|
display: block;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--bp-text);
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.wizard-input-wrapper {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.wizard-input-icon {
|
|
position: absolute;
|
|
left: 16px;
|
|
font-size: 20px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.wizard-input {
|
|
width: 100%;
|
|
padding: 16px 16px 16px 52px;
|
|
background: var(--bp-bg);
|
|
border: 2px solid var(--bp-border);
|
|
border-radius: 12px;
|
|
font-size: 16px;
|
|
color: var(--bp-text);
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.wizard-input:focus {
|
|
outline: none;
|
|
border-color: var(--bp-primary);
|
|
box-shadow: 0 0 0 4px var(--bp-primary-soft);
|
|
}
|
|
|
|
.wizard-input::placeholder {
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.wizard-input-hint {
|
|
font-size: 12px;
|
|
color: var(--bp-text-muted);
|
|
margin-top: 8px;
|
|
}
|
|
|
|
/* Password Toggle */
|
|
.wizard-password-toggle {
|
|
position: absolute;
|
|
right: 16px;
|
|
background: none;
|
|
border: none;
|
|
color: var(--bp-text-muted);
|
|
cursor: pointer;
|
|
padding: 8px;
|
|
font-size: 18px;
|
|
}
|
|
|
|
.wizard-password-toggle:hover {
|
|
color: var(--bp-text);
|
|
}
|
|
|
|
/* Provider Grid */
|
|
.provider-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 12px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.provider-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: 20px 12px;
|
|
background: var(--bp-bg);
|
|
border: 2px solid var(--bp-border);
|
|
border-radius: 12px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.provider-card:hover {
|
|
border-color: var(--bp-primary);
|
|
background: var(--bp-primary-soft);
|
|
}
|
|
|
|
.provider-card.selected {
|
|
border-color: var(--bp-primary);
|
|
background: var(--bp-primary-soft);
|
|
box-shadow: 0 0 0 4px var(--bp-primary-soft);
|
|
}
|
|
|
|
.provider-card-icon {
|
|
font-size: 32px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.provider-card-name {
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
color: var(--bp-text);
|
|
text-align: center;
|
|
}
|
|
|
|
/* Provider Detected Banner */
|
|
.provider-detected {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
padding: 20px;
|
|
background: rgba(34, 197, 94, 0.1);
|
|
border: 2px solid var(--bp-success);
|
|
border-radius: 12px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.provider-detected-icon {
|
|
font-size: 48px;
|
|
}
|
|
|
|
.provider-detected-info h3 {
|
|
font-size: 18px;
|
|
font-weight: 700;
|
|
color: var(--bp-text);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.provider-detected-info p {
|
|
font-size: 13px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.provider-detected-checkmarks {
|
|
margin-top: 8px;
|
|
}
|
|
|
|
.provider-detected-checkmarks span {
|
|
display: block;
|
|
font-size: 13px;
|
|
color: var(--bp-success);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
/* Quick Select Buttons */
|
|
.quick-select-grid {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.quick-select-btn {
|
|
padding: 10px 16px;
|
|
background: var(--bp-bg);
|
|
border: 2px solid var(--bp-border);
|
|
border-radius: 20px;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: var(--bp-text);
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.quick-select-btn:hover {
|
|
border-color: var(--bp-primary);
|
|
background: var(--bp-primary-soft);
|
|
}
|
|
|
|
.quick-select-btn.selected {
|
|
border-color: var(--bp-primary);
|
|
background: var(--bp-primary);
|
|
color: white;
|
|
}
|
|
|
|
/* Connection Test */
|
|
.connection-test {
|
|
padding: 24px;
|
|
background: var(--bp-bg);
|
|
border-radius: 12px;
|
|
text-align: center;
|
|
}
|
|
|
|
.connection-test-animation {
|
|
font-size: 64px;
|
|
margin-bottom: 16px;
|
|
animation: pulse 1.5s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { transform: scale(1); opacity: 1; }
|
|
50% { transform: scale(1.1); opacity: 0.7; }
|
|
}
|
|
|
|
.connection-test-status {
|
|
margin-top: 24px;
|
|
}
|
|
|
|
.connection-test-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 12px;
|
|
padding: 12px;
|
|
font-size: 14px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.connection-test-item.success {
|
|
color: var(--bp-success);
|
|
}
|
|
|
|
.connection-test-item.error {
|
|
color: var(--bp-danger);
|
|
}
|
|
|
|
.connection-test-item.pending {
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
/* Success Screen */
|
|
.wizard-success {
|
|
text-align: center;
|
|
padding: 20px;
|
|
}
|
|
|
|
.wizard-success-icon {
|
|
font-size: 80px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.wizard-success-title {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
color: var(--bp-text);
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.wizard-success-card {
|
|
background: var(--bp-bg);
|
|
border: 2px solid var(--bp-success);
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
margin: 24px 0;
|
|
text-align: left;
|
|
}
|
|
|
|
.wizard-success-card-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.wizard-success-card-icon {
|
|
font-size: 32px;
|
|
}
|
|
|
|
.wizard-success-card-name {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: var(--bp-text);
|
|
}
|
|
|
|
.wizard-success-card-email {
|
|
font-size: 13px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.wizard-success-card-stats {
|
|
margin-top: 12px;
|
|
padding-top: 12px;
|
|
border-top: 1px solid var(--bp-border);
|
|
font-size: 12px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
/* Wizard Footer */
|
|
.wizard-footer {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 20px 32px;
|
|
border-top: 1px solid var(--bp-border);
|
|
background: var(--bp-surface);
|
|
}
|
|
|
|
.wizard-btn {
|
|
padding: 14px 28px;
|
|
border-radius: 12px;
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.wizard-btn-back {
|
|
background: transparent;
|
|
border: 2px solid var(--bp-border);
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.wizard-btn-back:hover {
|
|
border-color: var(--bp-text-muted);
|
|
color: var(--bp-text);
|
|
}
|
|
|
|
.wizard-btn-next {
|
|
background: var(--bp-primary);
|
|
border: none;
|
|
color: white;
|
|
}
|
|
|
|
.wizard-btn-next:hover {
|
|
background: var(--bp-primary-hover);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(108, 27, 27, 0.3);
|
|
}
|
|
|
|
.wizard-btn-next:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
|
|
/* Error Banner */
|
|
.wizard-error-banner {
|
|
display: none;
|
|
background: rgba(239, 68, 68, 0.1);
|
|
border: 1px solid var(--bp-danger);
|
|
border-radius: 12px;
|
|
padding: 16px 20px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.wizard-error-banner.active {
|
|
display: block;
|
|
}
|
|
|
|
.wizard-error-banner-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-weight: 600;
|
|
color: var(--bp-danger);
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.wizard-error-banner-content {
|
|
font-size: 13px;
|
|
color: var(--bp-text-muted);
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.wizard-error-banner-solutions {
|
|
margin-top: 12px;
|
|
padding-left: 20px;
|
|
}
|
|
|
|
.wizard-error-banner-solutions li {
|
|
font-size: 13px;
|
|
color: var(--bp-text-muted);
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
/* ==========================================
|
|
ACCOUNT MANAGEMENT
|
|
========================================== */
|
|
|
|
.mail-accounts-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
gap: 20px;
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.mail-account-card {
|
|
background: var(--bp-surface);
|
|
border: 1px solid var(--bp-border);
|
|
border-radius: 16px;
|
|
padding: 24px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.mail-account-card:hover {
|
|
border-color: var(--bp-primary);
|
|
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.mail-account-card-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.mail-account-icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 12px;
|
|
background: var(--bp-primary-soft);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 24px;
|
|
}
|
|
|
|
.mail-account-info h3 {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: var(--bp-text);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.mail-account-info p {
|
|
font-size: 13px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.mail-account-status {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 4px 10px;
|
|
border-radius: 20px;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.mail-account-status.connected {
|
|
background: rgba(34, 197, 94, 0.1);
|
|
color: var(--bp-success);
|
|
}
|
|
|
|
.mail-account-status.error {
|
|
background: rgba(239, 68, 68, 0.1);
|
|
color: var(--bp-danger);
|
|
}
|
|
|
|
.mail-account-stats {
|
|
display: flex;
|
|
gap: 16px;
|
|
padding-top: 16px;
|
|
border-top: 1px solid var(--bp-border);
|
|
}
|
|
|
|
.mail-account-stat {
|
|
flex: 1;
|
|
}
|
|
|
|
.mail-account-stat-value {
|
|
font-size: 20px;
|
|
font-weight: 700;
|
|
color: var(--bp-text);
|
|
}
|
|
|
|
.mail-account-stat-label {
|
|
font-size: 11px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.mail-account-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
margin-top: 16px;
|
|
}
|
|
|
|
.mail-account-btn {
|
|
flex: 1;
|
|
padding: 10px;
|
|
border-radius: 8px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
text-align: center;
|
|
}
|
|
|
|
.mail-account-btn-sync {
|
|
background: var(--bp-primary-soft);
|
|
border: 1px solid var(--bp-primary);
|
|
color: var(--bp-primary);
|
|
}
|
|
|
|
.mail-account-btn-sync:hover {
|
|
background: var(--bp-primary);
|
|
color: white;
|
|
}
|
|
|
|
.mail-account-btn-settings {
|
|
background: var(--bp-surface-elevated);
|
|
border: 1px solid var(--bp-border);
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.mail-account-btn-settings:hover {
|
|
border-color: var(--bp-text-muted);
|
|
color: var(--bp-text);
|
|
}
|
|
|
|
/* Add Account Card */
|
|
.mail-add-account-card {
|
|
background: var(--bp-bg);
|
|
border: 2px dashed var(--bp-border);
|
|
border-radius: 16px;
|
|
padding: 40px 24px;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.mail-add-account-card:hover {
|
|
border-color: var(--bp-primary);
|
|
background: var(--bp-primary-soft);
|
|
}
|
|
|
|
.mail-add-account-icon {
|
|
font-size: 40px;
|
|
color: var(--bp-text-muted);
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.mail-add-account-text {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
/* ==========================================
|
|
PASSWORD VAULT
|
|
========================================== */
|
|
|
|
.password-vault-section {
|
|
background: var(--bp-surface);
|
|
border: 1px solid var(--bp-border);
|
|
border-radius: 16px;
|
|
padding: 24px;
|
|
margin-top: 32px;
|
|
}
|
|
|
|
.password-vault-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.password-vault-icon {
|
|
font-size: 28px;
|
|
}
|
|
|
|
.password-vault-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: var(--bp-text);
|
|
}
|
|
|
|
.password-vault-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.password-vault-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 16px;
|
|
background: var(--bp-bg);
|
|
border-radius: 12px;
|
|
}
|
|
|
|
.password-vault-item-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.password-vault-item-icon {
|
|
font-size: 24px;
|
|
}
|
|
|
|
.password-vault-item-name {
|
|
font-weight: 600;
|
|
color: var(--bp-text);
|
|
font-size: 14px;
|
|
}
|
|
|
|
.password-vault-item-email {
|
|
font-size: 12px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.password-vault-item-password {
|
|
font-family: monospace;
|
|
font-size: 14px;
|
|
color: var(--bp-text-muted);
|
|
letter-spacing: 2px;
|
|
}
|
|
|
|
.password-vault-item-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.password-vault-btn {
|
|
padding: 6px 12px;
|
|
border-radius: 6px;
|
|
font-size: 11px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
background: var(--bp-surface-elevated);
|
|
border: 1px solid var(--bp-border);
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.password-vault-btn:hover {
|
|
border-color: var(--bp-primary);
|
|
color: var(--bp-primary);
|
|
}
|
|
"""
|
|
|
|
@staticmethod
|
|
def get_html() -> str:
|
|
"""HTML fuer das Mail-Inbox-Modul."""
|
|
return """
|
|
<!-- MAIL INBOX PANEL -->
|
|
<div class="panel-mail">
|
|
<!-- Header -->
|
|
<div class="mail-header">
|
|
<div class="mail-title-section">
|
|
<h1>Unified Inbox</h1>
|
|
<p class="mail-subtitle">Alle E-Mails an einem Ort</p>
|
|
</div>
|
|
<div class="mail-header-actions">
|
|
<button class="btn btn-secondary" onclick="openPasswordVault()">
|
|
<span>🔒</span> Passwort-Tresor
|
|
</button>
|
|
<button class="btn btn-primary" onclick="openMailWizard()">
|
|
<span>➕</span> Konto hinzufuegen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Content -->
|
|
<div class="mail-content" id="mail-content">
|
|
<!-- Accounts Grid (dynamisch befuellt) -->
|
|
<div class="mail-accounts-grid" id="mail-accounts-grid">
|
|
<!-- Wird per JS befuellt -->
|
|
</div>
|
|
|
|
<!-- Empty State (wenn keine Konten) -->
|
|
<div class="mail-empty-state" id="mail-empty-state" style="display: none;">
|
|
<div class="mail-empty-icon">📫</div>
|
|
<h2 class="mail-empty-title">Willkommen bei Ihrer Unified Inbox!</h2>
|
|
<p class="mail-empty-description">
|
|
Verwalten Sie alle Ihre E-Mail-Konten an einem Ort.
|
|
Schulleitung, Verwaltung, Regierungspraesidium - alles uebersichtlich zusammengefasst.
|
|
</p>
|
|
<button class="btn btn-primary btn-lg" onclick="openMailWizard()">
|
|
<span>⚡</span> Erstes E-Mail-Konto einrichten
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Password Vault Section -->
|
|
<div class="password-vault-section" id="password-vault-section" style="display: none;">
|
|
<div class="password-vault-header">
|
|
<span class="password-vault-icon">🔒</span>
|
|
<span class="password-vault-title">Passwort-Tresor</span>
|
|
</div>
|
|
<div class="wizard-info-box" style="margin-bottom: 20px;">
|
|
<div class="wizard-info-box-header">
|
|
<span class="wizard-info-box-icon">💡</span>
|
|
Warum ein Passwort-Tresor?
|
|
</div>
|
|
<div class="wizard-info-box-content">
|
|
Ihre Passwoerter werden mit militaerischer Verschluesselung (AES-256) gespeichert.
|
|
Selbst wir koennen sie nicht lesen. So muessen Sie sich die Passwoerter nicht merken.
|
|
</div>
|
|
</div>
|
|
<div class="password-vault-list" id="password-vault-list">
|
|
<!-- Wird per JS befuellt -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- MAIL WIZARD MODAL -->
|
|
<div class="mail-wizard-modal" id="mail-wizard-modal">
|
|
<div class="mail-wizard-content">
|
|
<button class="wizard-close-btn" onclick="closeMailWizard()">×</button>
|
|
|
|
<!-- Wizard Header -->
|
|
<div class="wizard-header">
|
|
<div class="wizard-steps" id="wizard-steps">
|
|
<div class="wizard-step-dot active" data-step="1"></div>
|
|
<div class="wizard-step-dot" data-step="2"></div>
|
|
<div class="wizard-step-dot" data-step="3"></div>
|
|
<div class="wizard-step-dot" data-step="4"></div>
|
|
<div class="wizard-step-dot" data-step="5"></div>
|
|
<div class="wizard-step-dot" data-step="6"></div>
|
|
<div class="wizard-step-dot" data-step="7"></div>
|
|
</div>
|
|
<div class="wizard-step-label" id="wizard-step-label">Schritt 1 von 7</div>
|
|
</div>
|
|
|
|
<!-- Wizard Body -->
|
|
<div class="wizard-body">
|
|
<!-- Error Banner -->
|
|
<div class="wizard-error-banner" id="wizard-error-banner">
|
|
<div class="wizard-error-banner-header">
|
|
<span>⚠</span> Verbindung fehlgeschlagen
|
|
</div>
|
|
<div class="wizard-error-banner-content" id="wizard-error-content">
|
|
Das Passwort scheint nicht zu stimmen.
|
|
</div>
|
|
<ul class="wizard-error-banner-solutions" id="wizard-error-solutions">
|
|
<li>Pruefen Sie Gross-/Kleinschreibung beim Passwort</li>
|
|
<li>Ist das wirklich das E-Mail-Passwort? (Nicht das Windows-Passwort!)</li>
|
|
<li>Bei Gmail/Outlook mit 2FA: Sie brauchen ein App-Passwort</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Step 1: Welcome -->
|
|
<div class="wizard-step active" data-step="1">
|
|
<div class="wizard-illustration">
|
|
<div class="wizard-illustration-icon">📫➡📥</div>
|
|
</div>
|
|
<h2 class="wizard-step-title">Alle Ihre E-Mails an einem Ort</h2>
|
|
<p class="wizard-step-description">
|
|
Stellen Sie sich vor: Schulleitung, Verwaltung, Regierungspraesidium -
|
|
alles in EINER Ansicht. Keine 4 verschiedenen Programme mehr oeffnen.
|
|
</p>
|
|
<div class="wizard-info-box">
|
|
<div class="wizard-info-box-header">
|
|
<span class="wizard-info-box-icon">💡</span>
|
|
Was passiert mit meinen Daten?
|
|
</div>
|
|
<div class="wizard-info-box-content">
|
|
Ihre E-Mails bleiben bei Ihrem Anbieter (z.B. GMX).
|
|
Wir speichern nur eine Kopie, damit Sie schneller arbeiten koennen.
|
|
Alles DSGVO-konform in Deutschland.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 2: Email Address -->
|
|
<div class="wizard-step" data-step="2">
|
|
<h2 class="wizard-step-title">Welche E-Mail-Adresse?</h2>
|
|
<p class="wizard-step-description">
|
|
Geben Sie die E-Mail-Adresse ein, die Sie hinzufuegen moechten.
|
|
</p>
|
|
<div class="wizard-field">
|
|
<div class="wizard-input-wrapper">
|
|
<span class="wizard-input-icon">📧</span>
|
|
<input type="email" class="wizard-input" id="wizard-email"
|
|
placeholder="z.B. schulleitung@grundschule-muster.de"
|
|
oninput="onEmailInput(this.value)">
|
|
</div>
|
|
<p class="wizard-input-hint">Geben Sie Ihre vollstaendige E-Mail-Adresse ein</p>
|
|
</div>
|
|
<div class="wizard-info-box">
|
|
<div class="wizard-info-box-header">
|
|
<span class="wizard-info-box-icon">💡</span>
|
|
Warum brauchen wir das?
|
|
</div>
|
|
<div class="wizard-info-box-content">
|
|
Anhand Ihrer E-Mail-Adresse erkennen wir automatisch Ihren Anbieter
|
|
und fuellen die technischen Einstellungen fuer Sie aus.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 3: Provider Detected -->
|
|
<div class="wizard-step" data-step="3">
|
|
<h2 class="wizard-step-title">Anbieter erkannt!</h2>
|
|
<div class="provider-detected" id="provider-detected">
|
|
<span class="provider-detected-icon" id="provider-icon">📧</span>
|
|
<div class="provider-detected-info">
|
|
<h3 id="provider-name">GMX</h3>
|
|
<p>Wir haben Ihren E-Mail-Anbieter erkannt</p>
|
|
<div class="provider-detected-checkmarks">
|
|
<span>✓ Servereinstellungen automatisch ausgefuellt</span>
|
|
<span>✓ Verschluesselung aktiviert</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="wizard-info-box">
|
|
<div class="wizard-info-box-header">
|
|
<span class="wizard-info-box-icon">💡</span>
|
|
Stimmt das nicht?
|
|
</div>
|
|
<div class="wizard-info-box-content">
|
|
Falls Sie einen anderen Anbieter nutzen, koennen Sie ihn hier aendern:
|
|
</div>
|
|
</div>
|
|
<div class="provider-grid" style="margin-top: 16px;">
|
|
<div class="provider-card" onclick="selectProvider('gmail')">
|
|
<span class="provider-card-icon">📧</span>
|
|
<span class="provider-card-name">Gmail</span>
|
|
</div>
|
|
<div class="provider-card" onclick="selectProvider('outlook')">
|
|
<span class="provider-card-icon">📩</span>
|
|
<span class="provider-card-name">Outlook</span>
|
|
</div>
|
|
<div class="provider-card" onclick="selectProvider('gmx')">
|
|
<span class="provider-card-icon">📪</span>
|
|
<span class="provider-card-name">GMX</span>
|
|
</div>
|
|
<div class="provider-card" onclick="selectProvider('webde')">
|
|
<span class="provider-card-icon">📫</span>
|
|
<span class="provider-card-name">Web.de</span>
|
|
</div>
|
|
<div class="provider-card" onclick="selectProvider('tonline')">
|
|
<span class="provider-card-icon">📬</span>
|
|
<span class="provider-card-name">T-Online</span>
|
|
</div>
|
|
<div class="provider-card" onclick="selectProvider('generic')">
|
|
<span class="provider-card-icon">⚙</span>
|
|
<span class="provider-card-name">Andere...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 4: Account Name -->
|
|
<div class="wizard-step" data-step="4">
|
|
<h2 class="wizard-step-title">Wie soll das Konto heissen?</h2>
|
|
<p class="wizard-step-description">
|
|
Geben Sie diesem Konto einen Namen, damit Sie es leicht wiedererkennen.
|
|
</p>
|
|
<div class="quick-select-grid" id="account-name-quick-select">
|
|
<button class="quick-select-btn" onclick="selectAccountName('Schulleitung')">Schulleitung</button>
|
|
<button class="quick-select-btn" onclick="selectAccountName('Verwaltung')">Verwaltung</button>
|
|
<button class="quick-select-btn" onclick="selectAccountName('Privat')">Privat</button>
|
|
<button class="quick-select-btn" onclick="selectAccountName('Regierungspraesidium')">Regierungspraesidium</button>
|
|
<button class="quick-select-btn" onclick="selectAccountName('Schulamt')">Schulamt</button>
|
|
<button class="quick-select-btn" onclick="selectAccountName('Gewerkschaft')">Gewerkschaft</button>
|
|
<button class="quick-select-btn" onclick="selectAccountName('Fortbildung')">Fortbildung</button>
|
|
<button class="quick-select-btn" onclick="selectAccountName('Dienstlich')">Dienstlich</button>
|
|
</div>
|
|
<div class="wizard-field">
|
|
<label class="wizard-field-label">Oder eigener Name:</label>
|
|
<div class="wizard-input-wrapper">
|
|
<span class="wizard-input-icon">🏷</span>
|
|
<input type="text" class="wizard-input" id="wizard-account-name"
|
|
placeholder="z.B. Meine Schul-E-Mail"
|
|
oninput="onAccountNameInput(this.value)">
|
|
</div>
|
|
</div>
|
|
<div class="wizard-info-box">
|
|
<div class="wizard-info-box-header">
|
|
<span class="wizard-info-box-icon">💡</span>
|
|
Wozu ist der Name?
|
|
</div>
|
|
<div class="wizard-info-box-content">
|
|
In Ihrer Inbox sehen Sie spaeter auf einen Blick, von welchem Konto
|
|
eine E-Mail stammt. "Schulleitung" ist leichter zu erkennen als
|
|
"vorname.nachname@schule.niedersachsen.de"
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 5: Password -->
|
|
<div class="wizard-step" data-step="5">
|
|
<h2 class="wizard-step-title">Passwort eingeben</h2>
|
|
<p class="wizard-step-description">
|
|
Geben Sie das Passwort fuer Ihr E-Mail-Konto ein.
|
|
</p>
|
|
<div class="wizard-field">
|
|
<div class="wizard-input-wrapper">
|
|
<span class="wizard-input-icon">🔒</span>
|
|
<input type="password" class="wizard-input" id="wizard-password"
|
|
placeholder="Ihr E-Mail-Passwort">
|
|
<button class="wizard-password-toggle" onclick="togglePasswordVisibility()" id="password-toggle">
|
|
👁
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<label style="display: flex; align-items: center; gap: 10px; margin-top: 16px; cursor: pointer;">
|
|
<input type="checkbox" id="wizard-save-password" checked style="width: 18px; height: 18px;">
|
|
<span style="font-size: 14px; color: var(--bp-text);">Passwort im BreakPilot Passwort-Tresor speichern</span>
|
|
</label>
|
|
<p style="font-size: 12px; color: var(--bp-text-muted); margin-left: 28px;">
|
|
(Damit Sie es nicht jedes Mal eingeben muessen)
|
|
</p>
|
|
<div class="wizard-info-box security" style="margin-top: 24px;">
|
|
<div class="wizard-info-box-header">
|
|
<span class="wizard-info-box-icon">🔒</span>
|
|
Wie sicher ist mein Passwort?
|
|
</div>
|
|
<div class="wizard-info-box-content">
|
|
Ihr Passwort wird mit militaerischer Verschluesselung (AES-256) gespeichert.
|
|
Selbst wir koennen es nicht lesen. Es wird nur verwendet, um Ihre E-Mails
|
|
abzurufen - niemals fuer etwas anderes.
|
|
</div>
|
|
</div>
|
|
<div class="wizard-info-box warning" style="margin-top: 16px;" id="app-password-hint" style="display: none;">
|
|
<div class="wizard-info-box-header">
|
|
<span class="wizard-info-box-icon">⚠</span>
|
|
Gmail/Outlook mit 2-Faktor-Authentifizierung?
|
|
</div>
|
|
<div class="wizard-info-box-content">
|
|
Sie brauchen ein spezielles "App-Passwort" statt Ihres normalen Passworts.
|
|
<br><br>
|
|
<a href="#" onclick="showAppPasswordHelp('gmail')" style="color: var(--bp-primary);">Anleitung fuer Gmail</a> |
|
|
<a href="#" onclick="showAppPasswordHelp('outlook')" style="color: var(--bp-primary);">Anleitung fuer Outlook</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 6: Connection Test -->
|
|
<div class="wizard-step" data-step="6">
|
|
<h2 class="wizard-step-title">Verbindung wird geprueft...</h2>
|
|
<div class="connection-test">
|
|
<div class="connection-test-animation" id="connection-animation">📧</div>
|
|
<p style="color: var(--bp-text-muted);">Wir pruefen jetzt, ob alles funktioniert...</p>
|
|
<div class="connection-test-status" id="connection-status">
|
|
<div class="connection-test-item pending" id="test-server">
|
|
<span>○</span> Mit Server verbinden...
|
|
</div>
|
|
<div class="connection-test-item pending" id="test-auth">
|
|
<span>○</span> Anmeldung pruefen...
|
|
</div>
|
|
<div class="connection-test-item pending" id="test-inbox">
|
|
<span>○</span> Posteingang laden...
|
|
</div>
|
|
<div class="connection-test-item pending" id="test-smtp">
|
|
<span>○</span> E-Mail-Versand pruefen...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="wizard-info-box" style="margin-top: 24px;">
|
|
<div class="wizard-info-box-header">
|
|
<span class="wizard-info-box-icon">💡</span>
|
|
Was passiert gerade?
|
|
</div>
|
|
<div class="wizard-info-box-content">
|
|
Wir verbinden uns mit Ihrem E-Mail-Anbieter und pruefen, ob wir
|
|
E-Mails abrufen und senden koennen. Das dauert nur wenige Sekunden.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 7: Success -->
|
|
<div class="wizard-step" data-step="7">
|
|
<div class="wizard-success">
|
|
<div class="wizard-success-icon">🎉</div>
|
|
<h2 class="wizard-success-title">Geschafft!</h2>
|
|
<p style="color: var(--bp-text-muted);">
|
|
Ihr Konto wurde erfolgreich eingerichtet!
|
|
</p>
|
|
<div class="wizard-success-card">
|
|
<div class="wizard-success-card-header">
|
|
<span class="wizard-success-card-icon" id="success-provider-icon">📧</span>
|
|
<div>
|
|
<div class="wizard-success-card-name" id="success-account-name">Schulleitung</div>
|
|
<div class="wizard-success-card-email" id="success-email">schulleitung@grundschule.de</div>
|
|
</div>
|
|
</div>
|
|
<div class="wizard-success-card-stats" id="success-stats">
|
|
147 E-Mails • Zuletzt synchronisiert: Jetzt
|
|
</div>
|
|
</div>
|
|
<div class="wizard-info-box">
|
|
<div class="wizard-info-box-header">
|
|
<span class="wizard-info-box-icon">💡</span>
|
|
Tipp fuer den Alltag
|
|
</div>
|
|
<div class="wizard-info-box-content">
|
|
Haben Sie mehrere E-Mail-Konten? Die meisten Schulleitungen verwalten 3-4 Adressen.
|
|
Fuegen Sie jetzt alle hinzu - dann haben Sie wirklich ALLES an einem Ort!
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Wizard Footer -->
|
|
<div class="wizard-footer">
|
|
<button class="wizard-btn wizard-btn-back" id="wizard-btn-back" onclick="wizardPrevStep()" style="visibility: hidden;">
|
|
<span>←</span> Zurueck
|
|
</button>
|
|
<button class="wizard-btn wizard-btn-next" id="wizard-btn-next" onclick="wizardNextStep()">
|
|
Los geht's! <span>→</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
"""
|
|
|
|
@staticmethod
|
|
def get_js() -> str:
|
|
"""JavaScript fuer das Mail-Inbox-Modul."""
|
|
return """
|
|
/* ==========================================
|
|
MAIL INBOX MODULE - JavaScript
|
|
========================================== */
|
|
|
|
// Mail API Base URL
|
|
const MAIL_API_BASE = 'http://localhost:8086/api/v1/mail';
|
|
|
|
// Current user info (from auth)
|
|
let mailUserId = 'demo-user';
|
|
let mailTenantId = 'demo-tenant';
|
|
|
|
// Wizard State
|
|
let wizardCurrentStep = 1;
|
|
const wizardTotalSteps = 7;
|
|
let wizardData = {
|
|
email: '',
|
|
provider: null,
|
|
accountName: '',
|
|
password: '',
|
|
savePassword: true,
|
|
// Provider settings (auto-filled)
|
|
imapHost: '',
|
|
imapPort: 993,
|
|
smtpHost: '',
|
|
smtpPort: 465,
|
|
};
|
|
|
|
// Provider Presets
|
|
const EMAIL_PROVIDERS = {
|
|
gmail: {
|
|
name: 'Gmail',
|
|
icon: '📧',
|
|
imapHost: 'imap.gmail.com',
|
|
imapPort: 993,
|
|
smtpHost: 'smtp.gmail.com',
|
|
smtpPort: 465,
|
|
needsAppPassword: true,
|
|
helpUrl: 'https://support.google.com/accounts/answer/185833'
|
|
},
|
|
outlook: {
|
|
name: 'Outlook / Microsoft 365',
|
|
icon: '📩',
|
|
imapHost: 'outlook.office365.com',
|
|
imapPort: 993,
|
|
smtpHost: 'smtp.office365.com',
|
|
smtpPort: 587,
|
|
needsAppPassword: true,
|
|
helpUrl: 'https://support.microsoft.com/de-de/account-billing/app-kennw%C3%B6rter'
|
|
},
|
|
gmx: {
|
|
name: 'GMX',
|
|
icon: '📪',
|
|
imapHost: 'imap.gmx.net',
|
|
imapPort: 993,
|
|
smtpHost: 'mail.gmx.net',
|
|
smtpPort: 465,
|
|
needsAppPassword: false,
|
|
helpUrl: 'https://hilfe.gmx.net/pop-imap/imap/index.html'
|
|
},
|
|
webde: {
|
|
name: 'Web.de',
|
|
icon: '📫',
|
|
imapHost: 'imap.web.de',
|
|
imapPort: 993,
|
|
smtpHost: 'smtp.web.de',
|
|
smtpPort: 587,
|
|
needsAppPassword: false,
|
|
helpUrl: 'https://hilfe.web.de/pop-imap/imap/index.html'
|
|
},
|
|
tonline: {
|
|
name: 'T-Online',
|
|
icon: '📬',
|
|
imapHost: 'secureimap.t-online.de',
|
|
imapPort: 993,
|
|
smtpHost: 'securesmtp.t-online.de',
|
|
smtpPort: 465,
|
|
needsAppPassword: false,
|
|
helpUrl: 'https://www.telekom.de/hilfe/festnetz-internet-tv/e-mail'
|
|
},
|
|
niedersachsen: {
|
|
name: 'Niedersachsen Schulportal',
|
|
icon: '🏫',
|
|
imapHost: 'imap.schule.niedersachsen.de',
|
|
imapPort: 993,
|
|
smtpHost: 'smtp.schule.niedersachsen.de',
|
|
smtpPort: 465,
|
|
needsAppPassword: false,
|
|
helpUrl: 'https://www.nibis.de/'
|
|
},
|
|
generic: {
|
|
name: 'Anderer Anbieter',
|
|
icon: '⚙',
|
|
imapHost: '',
|
|
imapPort: 993,
|
|
smtpHost: '',
|
|
smtpPort: 465,
|
|
needsAppPassword: false,
|
|
helpUrl: null
|
|
}
|
|
};
|
|
|
|
// Email domain to provider mapping
|
|
const EMAIL_DOMAIN_MAP = {
|
|
'gmail.com': 'gmail',
|
|
'googlemail.com': 'gmail',
|
|
'outlook.com': 'outlook',
|
|
'outlook.de': 'outlook',
|
|
'hotmail.com': 'outlook',
|
|
'hotmail.de': 'outlook',
|
|
'live.com': 'outlook',
|
|
'live.de': 'outlook',
|
|
'gmx.de': 'gmx',
|
|
'gmx.net': 'gmx',
|
|
'gmx.at': 'gmx',
|
|
'gmx.ch': 'gmx',
|
|
'web.de': 'webde',
|
|
't-online.de': 'tonline',
|
|
'schule.niedersachsen.de': 'niedersachsen',
|
|
'rlsb.de': 'niedersachsen',
|
|
'mk.niedersachsen.de': 'niedersachsen',
|
|
'nibis.de': 'niedersachsen',
|
|
};
|
|
|
|
// Mail accounts storage
|
|
let mailAccounts = [];
|
|
|
|
/* ==========================================
|
|
INITIALIZATION
|
|
========================================== */
|
|
|
|
function initMailModule() {
|
|
console.log('Mail Module initialized');
|
|
loadMailAccounts();
|
|
}
|
|
|
|
/* ==========================================
|
|
WIZARD FUNCTIONS
|
|
========================================== */
|
|
|
|
function openMailWizard() {
|
|
// Reset wizard state
|
|
wizardCurrentStep = 1;
|
|
wizardData = {
|
|
email: '',
|
|
provider: null,
|
|
accountName: '',
|
|
password: '',
|
|
savePassword: true,
|
|
imapHost: '',
|
|
imapPort: 993,
|
|
smtpHost: '',
|
|
smtpPort: 465,
|
|
};
|
|
|
|
// Reset form fields
|
|
document.getElementById('wizard-email').value = '';
|
|
document.getElementById('wizard-account-name').value = '';
|
|
document.getElementById('wizard-password').value = '';
|
|
document.getElementById('wizard-save-password').checked = true;
|
|
|
|
// Hide error banner
|
|
document.getElementById('wizard-error-banner').classList.remove('active');
|
|
|
|
// Update UI
|
|
updateWizardStep();
|
|
|
|
// Show modal
|
|
document.getElementById('mail-wizard-modal').classList.add('active');
|
|
}
|
|
|
|
function closeMailWizard() {
|
|
document.getElementById('mail-wizard-modal').classList.remove('active');
|
|
}
|
|
|
|
function updateWizardStep() {
|
|
// Update step dots
|
|
document.querySelectorAll('.wizard-step-dot').forEach((dot, index) => {
|
|
dot.classList.remove('active', 'completed');
|
|
if (index + 1 < wizardCurrentStep) {
|
|
dot.classList.add('completed');
|
|
} else if (index + 1 === wizardCurrentStep) {
|
|
dot.classList.add('active');
|
|
}
|
|
});
|
|
|
|
// Update step label
|
|
document.getElementById('wizard-step-label').textContent =
|
|
`Schritt ${wizardCurrentStep} von ${wizardTotalSteps}`;
|
|
|
|
// Show/hide steps
|
|
document.querySelectorAll('.wizard-step').forEach((step, index) => {
|
|
step.classList.remove('active');
|
|
if (index + 1 === wizardCurrentStep) {
|
|
step.classList.add('active');
|
|
}
|
|
});
|
|
|
|
// Update buttons
|
|
const backBtn = document.getElementById('wizard-btn-back');
|
|
const nextBtn = document.getElementById('wizard-btn-next');
|
|
|
|
backBtn.style.visibility = wizardCurrentStep > 1 ? 'visible' : 'hidden';
|
|
|
|
// Update next button text based on step
|
|
if (wizardCurrentStep === 1) {
|
|
nextBtn.innerHTML = "Los geht's! <span>→</span>";
|
|
} else if (wizardCurrentStep === 6) {
|
|
nextBtn.innerHTML = "Verbindung testen <span>→</span>";
|
|
} else if (wizardCurrentStep === 7) {
|
|
nextBtn.innerHTML = "Fertig <span>✓</span>";
|
|
} else {
|
|
nextBtn.innerHTML = "Weiter <span>→</span>";
|
|
}
|
|
|
|
// Disable next button if required fields are empty
|
|
updateNextButtonState();
|
|
}
|
|
|
|
function updateNextButtonState() {
|
|
const nextBtn = document.getElementById('wizard-btn-next');
|
|
let canProceed = true;
|
|
|
|
if (wizardCurrentStep === 2) {
|
|
canProceed = isValidEmail(wizardData.email);
|
|
} else if (wizardCurrentStep === 4) {
|
|
canProceed = wizardData.accountName.length >= 2;
|
|
} else if (wizardCurrentStep === 5) {
|
|
canProceed = wizardData.password.length >= 1;
|
|
}
|
|
|
|
nextBtn.disabled = !canProceed;
|
|
}
|
|
|
|
function wizardNextStep() {
|
|
if (wizardCurrentStep === 6) {
|
|
// Start connection test
|
|
runConnectionTest();
|
|
return;
|
|
}
|
|
|
|
if (wizardCurrentStep === 7) {
|
|
// Close wizard and show success
|
|
closeMailWizard();
|
|
loadMailAccounts();
|
|
return;
|
|
}
|
|
|
|
if (wizardCurrentStep < wizardTotalSteps) {
|
|
// Special handling for step 2 -> 3: detect provider
|
|
if (wizardCurrentStep === 2) {
|
|
detectProvider(wizardData.email);
|
|
}
|
|
|
|
wizardCurrentStep++;
|
|
updateWizardStep();
|
|
}
|
|
}
|
|
|
|
function wizardPrevStep() {
|
|
if (wizardCurrentStep > 1) {
|
|
// Hide error banner when going back
|
|
document.getElementById('wizard-error-banner').classList.remove('active');
|
|
|
|
wizardCurrentStep--;
|
|
updateWizardStep();
|
|
}
|
|
}
|
|
|
|
/* ==========================================
|
|
EMAIL INPUT & PROVIDER DETECTION
|
|
========================================== */
|
|
|
|
function onEmailInput(value) {
|
|
wizardData.email = value.trim().toLowerCase();
|
|
updateNextButtonState();
|
|
}
|
|
|
|
function isValidEmail(email) {
|
|
const re = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;
|
|
return re.test(email);
|
|
}
|
|
|
|
function detectProvider(email) {
|
|
const domain = email.split('@')[1];
|
|
const providerKey = EMAIL_DOMAIN_MAP[domain] || 'generic';
|
|
const provider = EMAIL_PROVIDERS[providerKey];
|
|
|
|
wizardData.provider = providerKey;
|
|
wizardData.imapHost = provider.imapHost;
|
|
wizardData.imapPort = provider.imapPort;
|
|
wizardData.smtpHost = provider.smtpHost;
|
|
wizardData.smtpPort = provider.smtpPort;
|
|
|
|
// Update UI
|
|
document.getElementById('provider-name').textContent = provider.name;
|
|
document.getElementById('provider-icon').innerHTML = provider.icon;
|
|
|
|
// Highlight selected provider card
|
|
document.querySelectorAll('.provider-card').forEach(card => {
|
|
card.classList.remove('selected');
|
|
});
|
|
|
|
// Show/hide app password hint
|
|
const appPasswordHint = document.getElementById('app-password-hint');
|
|
if (provider.needsAppPassword) {
|
|
appPasswordHint.style.display = 'block';
|
|
} else {
|
|
appPasswordHint.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function selectProvider(providerKey) {
|
|
const provider = EMAIL_PROVIDERS[providerKey];
|
|
|
|
wizardData.provider = providerKey;
|
|
wizardData.imapHost = provider.imapHost;
|
|
wizardData.imapPort = provider.imapPort;
|
|
wizardData.smtpHost = provider.smtpHost;
|
|
wizardData.smtpPort = provider.smtpPort;
|
|
|
|
// Update UI
|
|
document.getElementById('provider-name').textContent = provider.name;
|
|
document.getElementById('provider-icon').innerHTML = provider.icon;
|
|
|
|
// Highlight selected card
|
|
document.querySelectorAll('.provider-card').forEach(card => {
|
|
card.classList.remove('selected');
|
|
});
|
|
event.target.closest('.provider-card')?.classList.add('selected');
|
|
}
|
|
|
|
/* ==========================================
|
|
ACCOUNT NAME INPUT
|
|
========================================== */
|
|
|
|
function selectAccountName(name) {
|
|
wizardData.accountName = name;
|
|
document.getElementById('wizard-account-name').value = name;
|
|
|
|
// Highlight selected button
|
|
document.querySelectorAll('.quick-select-btn').forEach(btn => {
|
|
btn.classList.remove('selected');
|
|
if (btn.textContent === name) {
|
|
btn.classList.add('selected');
|
|
}
|
|
});
|
|
|
|
updateNextButtonState();
|
|
}
|
|
|
|
function onAccountNameInput(value) {
|
|
wizardData.accountName = value;
|
|
|
|
// Remove selection from quick select buttons
|
|
document.querySelectorAll('.quick-select-btn').forEach(btn => {
|
|
btn.classList.remove('selected');
|
|
});
|
|
|
|
updateNextButtonState();
|
|
}
|
|
|
|
/* ==========================================
|
|
PASSWORD INPUT
|
|
========================================== */
|
|
|
|
function togglePasswordVisibility() {
|
|
const input = document.getElementById('wizard-password');
|
|
const toggle = document.getElementById('password-toggle');
|
|
|
|
if (input.type === 'password') {
|
|
input.type = 'text';
|
|
toggle.innerHTML = '👀';
|
|
} else {
|
|
input.type = 'password';
|
|
toggle.innerHTML = '👁';
|
|
}
|
|
}
|
|
|
|
// Listen for password input
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const passwordInput = document.getElementById('wizard-password');
|
|
if (passwordInput) {
|
|
passwordInput.addEventListener('input', function(e) {
|
|
wizardData.password = e.target.value;
|
|
updateNextButtonState();
|
|
});
|
|
}
|
|
});
|
|
|
|
function showAppPasswordHelp(provider) {
|
|
const urls = {
|
|
gmail: 'https://support.google.com/accounts/answer/185833',
|
|
outlook: 'https://support.microsoft.com/de-de/account-billing/app-kennw%C3%B6rter'
|
|
};
|
|
window.open(urls[provider], '_blank');
|
|
}
|
|
|
|
/* ==========================================
|
|
CONNECTION TEST
|
|
========================================== */
|
|
|
|
async function runConnectionTest() {
|
|
// Show testing step
|
|
wizardCurrentStep = 6;
|
|
updateWizardStep();
|
|
|
|
// Reset status indicators
|
|
const testItems = ['test-server', 'test-auth', 'test-inbox', 'test-smtp'];
|
|
testItems.forEach(id => {
|
|
const item = document.getElementById(id);
|
|
item.classList.remove('success', 'error');
|
|
item.classList.add('pending');
|
|
item.querySelector('span').innerHTML = '○';
|
|
});
|
|
|
|
// Hide error banner
|
|
document.getElementById('wizard-error-banner').classList.remove('active');
|
|
|
|
// Simulate connection test with delays
|
|
try {
|
|
// Step 1: Connect to server
|
|
await delay(800);
|
|
updateTestStatus('test-server', 'success', 'Mit Server verbunden');
|
|
|
|
// Step 2: Authentication
|
|
await delay(1000);
|
|
|
|
// Try to create/test the account
|
|
const testResult = await testMailConnection();
|
|
|
|
if (!testResult.success) {
|
|
throw new Error(testResult.error || 'Authentifizierung fehlgeschlagen');
|
|
}
|
|
|
|
updateTestStatus('test-auth', 'success', 'Anmeldung erfolgreich');
|
|
|
|
// Step 3: Load inbox
|
|
await delay(600);
|
|
const emailCount = testResult.emailCount || 0;
|
|
updateTestStatus('test-inbox', 'success', `${emailCount} E-Mails gefunden`);
|
|
|
|
// Step 4: Test SMTP
|
|
await delay(500);
|
|
updateTestStatus('test-smtp', 'success', 'E-Mail-Versand bereit');
|
|
|
|
// Save account
|
|
await saveMailAccount();
|
|
|
|
// Update success screen
|
|
document.getElementById('success-account-name').textContent = wizardData.accountName;
|
|
document.getElementById('success-email').textContent = wizardData.email;
|
|
document.getElementById('success-provider-icon').innerHTML = EMAIL_PROVIDERS[wizardData.provider]?.icon || '📧';
|
|
document.getElementById('success-stats').textContent = `${emailCount} E-Mails - Zuletzt synchronisiert: Jetzt`;
|
|
|
|
// Move to success step after short delay
|
|
await delay(500);
|
|
wizardCurrentStep = 7;
|
|
updateWizardStep();
|
|
|
|
} catch (error) {
|
|
console.error('Connection test failed:', error);
|
|
showConnectionError(error.message);
|
|
}
|
|
}
|
|
|
|
function updateTestStatus(itemId, status, text) {
|
|
const item = document.getElementById(itemId);
|
|
item.classList.remove('pending', 'success', 'error');
|
|
item.classList.add(status);
|
|
|
|
if (status === 'success') {
|
|
item.querySelector('span').innerHTML = '✓';
|
|
} else if (status === 'error') {
|
|
item.querySelector('span').innerHTML = '✗';
|
|
}
|
|
|
|
// Update text
|
|
const textNode = item.childNodes[item.childNodes.length - 1];
|
|
textNode.textContent = ' ' + text;
|
|
}
|
|
|
|
function showConnectionError(message) {
|
|
// Show error on current step
|
|
const errorBanner = document.getElementById('wizard-error-banner');
|
|
document.getElementById('wizard-error-content').textContent = message;
|
|
errorBanner.classList.add('active');
|
|
|
|
// Go back to password step
|
|
wizardCurrentStep = 5;
|
|
updateWizardStep();
|
|
}
|
|
|
|
async function testMailConnection() {
|
|
// For demo, simulate success
|
|
// In production, this would call the API
|
|
return {
|
|
success: true,
|
|
emailCount: Math.floor(Math.random() * 200) + 50
|
|
};
|
|
|
|
/* Production code:
|
|
try {
|
|
const response = await fetch(`${MAIL_API_BASE}/accounts/test-connection`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
email: wizardData.email,
|
|
imap_host: wizardData.imapHost,
|
|
imap_port: wizardData.imapPort,
|
|
smtp_host: wizardData.smtpHost,
|
|
smtp_port: wizardData.smtpPort,
|
|
password: wizardData.password,
|
|
})
|
|
});
|
|
return await response.json();
|
|
} catch (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
*/
|
|
}
|
|
|
|
/* ==========================================
|
|
ACCOUNT MANAGEMENT
|
|
========================================== */
|
|
|
|
async function saveMailAccount() {
|
|
const account = {
|
|
id: 'acc_' + Date.now(),
|
|
email: wizardData.email,
|
|
displayName: wizardData.accountName,
|
|
provider: wizardData.provider,
|
|
imapHost: wizardData.imapHost,
|
|
imapPort: wizardData.imapPort,
|
|
smtpHost: wizardData.smtpHost,
|
|
smtpPort: wizardData.smtpPort,
|
|
status: 'active',
|
|
emailCount: Math.floor(Math.random() * 200) + 50,
|
|
unreadCount: Math.floor(Math.random() * 20),
|
|
lastSync: new Date().toISOString(),
|
|
};
|
|
|
|
// In production, save to API
|
|
// For demo, save to local storage
|
|
mailAccounts.push(account);
|
|
localStorage.setItem('mailAccounts', JSON.stringify(mailAccounts));
|
|
|
|
return account;
|
|
}
|
|
|
|
function loadMailAccounts() {
|
|
// Load from localStorage for demo
|
|
const stored = localStorage.getItem('mailAccounts');
|
|
mailAccounts = stored ? JSON.parse(stored) : [];
|
|
|
|
renderMailAccounts();
|
|
}
|
|
|
|
function renderMailAccounts() {
|
|
const grid = document.getElementById('mail-accounts-grid');
|
|
const emptyState = document.getElementById('mail-empty-state');
|
|
|
|
if (mailAccounts.length === 0) {
|
|
grid.style.display = 'none';
|
|
emptyState.style.display = 'flex';
|
|
return;
|
|
}
|
|
|
|
grid.style.display = 'grid';
|
|
emptyState.style.display = 'none';
|
|
|
|
grid.innerHTML = mailAccounts.map(account => `
|
|
<div class="mail-account-card" data-account-id="${account.id}">
|
|
<div class="mail-account-card-header">
|
|
<div class="mail-account-icon">${EMAIL_PROVIDERS[account.provider]?.icon || '📧'}</div>
|
|
<div class="mail-account-info">
|
|
<h3>${account.displayName}</h3>
|
|
<p>${account.email}</p>
|
|
</div>
|
|
</div>
|
|
<div class="mail-account-status ${account.status === 'active' ? 'connected' : 'error'}">
|
|
<span>${account.status === 'active' ? '✓' : '✗'}</span>
|
|
${account.status === 'active' ? 'Verbunden' : 'Fehler'}
|
|
</div>
|
|
<div class="mail-account-stats">
|
|
<div class="mail-account-stat">
|
|
<div class="mail-account-stat-value">${account.emailCount || 0}</div>
|
|
<div class="mail-account-stat-label">E-Mails</div>
|
|
</div>
|
|
<div class="mail-account-stat">
|
|
<div class="mail-account-stat-value">${account.unreadCount || 0}</div>
|
|
<div class="mail-account-stat-label">Ungelesen</div>
|
|
</div>
|
|
</div>
|
|
<div class="mail-account-actions">
|
|
<button class="mail-account-btn mail-account-btn-sync" onclick="syncAccount('${account.id}')">
|
|
↻ Synchronisieren
|
|
</button>
|
|
<button class="mail-account-btn mail-account-btn-settings" onclick="openAccountSettings('${account.id}')">
|
|
⚙ Einstellungen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`).join('') + `
|
|
<div class="mail-add-account-card" onclick="openMailWizard()">
|
|
<div class="mail-add-account-icon">➕</div>
|
|
<div class="mail-add-account-text">Weiteres Konto hinzufuegen</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
async function syncAccount(accountId) {
|
|
console.log('Syncing account:', accountId);
|
|
// Implementation for syncing
|
|
}
|
|
|
|
function openAccountSettings(accountId) {
|
|
console.log('Opening settings for:', accountId);
|
|
// Implementation for account settings
|
|
}
|
|
|
|
function deleteAccount(accountId) {
|
|
if (confirm('Moechten Sie dieses Konto wirklich entfernen?')) {
|
|
mailAccounts = mailAccounts.filter(a => a.id !== accountId);
|
|
localStorage.setItem('mailAccounts', JSON.stringify(mailAccounts));
|
|
renderMailAccounts();
|
|
}
|
|
}
|
|
|
|
/* ==========================================
|
|
PASSWORD VAULT
|
|
========================================== */
|
|
|
|
function openPasswordVault() {
|
|
const vaultSection = document.getElementById('password-vault-section');
|
|
vaultSection.style.display = vaultSection.style.display === 'none' ? 'block' : 'none';
|
|
renderPasswordVault();
|
|
}
|
|
|
|
function renderPasswordVault() {
|
|
const list = document.getElementById('password-vault-list');
|
|
|
|
if (mailAccounts.length === 0) {
|
|
list.innerHTML = '<p style="color: var(--bp-text-muted); text-align: center; padding: 20px;">Noch keine Konten gespeichert</p>';
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = mailAccounts.map(account => `
|
|
<div class="password-vault-item">
|
|
<div class="password-vault-item-info">
|
|
<span class="password-vault-item-icon">${EMAIL_PROVIDERS[account.provider]?.icon || '📧'}</span>
|
|
<div>
|
|
<div class="password-vault-item-name">${account.displayName}</div>
|
|
<div class="password-vault-item-email">${account.email}</div>
|
|
</div>
|
|
</div>
|
|
<div class="password-vault-item-password">••••••••</div>
|
|
<div class="password-vault-item-actions">
|
|
<button class="password-vault-btn" onclick="showPassword('${account.id}')">Anzeigen</button>
|
|
<button class="password-vault-btn" onclick="changePassword('${account.id}')">Aendern</button>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
function showPassword(accountId) {
|
|
alert('Passwort-Anzeige erfordert erneute Authentifizierung (nicht implementiert in Demo)');
|
|
}
|
|
|
|
function changePassword(accountId) {
|
|
alert('Passwort-Aenderung (nicht implementiert in Demo)');
|
|
}
|
|
|
|
/* ==========================================
|
|
UTILITY FUNCTIONS
|
|
========================================== */
|
|
|
|
function delay(ms) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
// Initialize on module load
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initMailModule);
|
|
} else {
|
|
initMailModule();
|
|
}
|
|
"""
|