Add IMAP configuration UI and sidebar navigation for email inbox

- Email settings page at /administration/email/ with IMAP config form
- Connection test button to verify IMAP connectivity
- Sidebar link "E-Mail Eingang" for quick access
- AppConfiguration model extended with email category and password type
- init_config command includes IMAP default settings
- DB-based IMAP config with env var fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
SysAdmin Agent
2026-03-11 21:01:05 +00:00
parent 96204c04dd
commit 2a7c9d8529
8 changed files with 241 additions and 12 deletions

View File

@@ -596,6 +596,10 @@
<i class="fas fa-gift"></i>
<span>Foerderungen</span>
</a>
<a class="sidebar-link" href="{% url 'stiftung:email_eingang_list' %}">
<i class="fas fa-envelope-open-text"></i>
<span>E-Mail Eingang</span>
</a>
<a class="sidebar-link" href="{% url 'stiftung:paechter_list' %}">
<i class="fas fa-user-tie"></i>
<span>Paechter</span>

View File

@@ -217,6 +217,12 @@
<span>App Settings</span>
</a>
</div>
<div class="col-md-3 mb-3">
<a href="{% url 'stiftung:email_settings' %}" class="btn btn-outline-primary w-100">
<i class="fas fa-envelope d-block mb-2 fa-2x"></i>
<span>E-Mail / IMAP</span>
</a>
</div>
<div class="col-md-3 mb-3">
<a href="{% url 'stiftung:audit_log_list' %}" class="btn btn-outline-primary w-100">
<i class="fas fa-history d-block mb-2 fa-2x"></i>

View File

@@ -27,7 +27,7 @@
<div class="card mb-4">
<div class="card-header">
<h4 class="mb-0">
<i class="fas fa-{% if category_name == 'Paperless Integration' %}file-alt{% elif category_name == 'System' %}cog{% elif category_name == 'Database' %}database{% else %}folder{% endif %}"></i>
<i class="fas fa-{% if category_name == 'Paperless Integration' %}file-alt{% elif category_name == 'E-Mail / IMAP' %}envelope{% elif category_name == 'System' %}cog{% elif category_name == 'Database' %}database{% else %}folder{% endif %}"></i>
{{ category_name }}
</h4>
</div>
@@ -49,9 +49,9 @@
{% if setting.setting_type == 'boolean' %}
<div class="form-check">
<input type="checkbox"
class="form-check-input"
id="setting_{{ setting.key }}"
<input type="checkbox"
class="form-check-input"
id="setting_{{ setting.key }}"
name="setting_{{ setting.key }}"
value="True"
{% if setting.get_typed_value %}checked{% endif %}
@@ -63,17 +63,24 @@
{% if not setting.get_typed_value %}
<input type="hidden" name="setting_{{ setting.key }}" value="False">
{% endif %}
{% elif setting.setting_type == 'integer' %}
<input type="number"
class="form-control"
id="setting_{{ setting.key }}"
{% elif setting.setting_type == 'password' %}
<input type="password"
class="form-control"
id="setting_{{ setting.key }}"
name="setting_{{ setting.key }}"
value="{{ setting.value }}"
{% if setting.is_system %}readonly{% endif %}>
{% elif setting.setting_type == 'integer' or setting.setting_type == 'number' %}
<input type="number"
class="form-control"
id="setting_{{ setting.key }}"
name="setting_{{ setting.key }}"
value="{{ setting.get_typed_value }}"
{% if setting.is_system %}readonly{% endif %}>
{% elif setting.setting_type == 'text' or setting.setting_type == 'url' %}
<input type="{% if setting.setting_type == 'url' %}url{% else %}text{% endif %}"
class="form-control"
id="setting_{{ setting.key }}"
<input type="{% if setting.setting_type == 'url' %}url{% else %}text{% endif %}"
class="form-control"
id="setting_{{ setting.key }}"
name="setting_{{ setting.key }}"
value="{{ setting.value }}"
{% if setting.is_system %}readonly{% endif %}>

View File

@@ -0,0 +1,142 @@
{% extends 'base.html' %}
{% block title %}{{ title }} - Stiftung{% endblock %}
{% block breadcrumb %}
<a href="{% url 'stiftung:home' %}">Stiftungsverwaltung</a>
<span class="mx-1">/</span>
<a href="{% url 'stiftung:administration' %}">Administration</a>
<span class="mx-1">/</span>
{{ title }}
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-lg-8">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h3 class="card-title mb-0">
<i class="fas fa-envelope"></i>
{{ title }}
</h3>
<a href="{% url 'stiftung:administration' %}" class="btn btn-sm btn-secondary">
<i class="fas fa-arrow-left"></i> Zurück
</a>
</div>
<div class="card-body">
{% if test_result %}
<div class="alert alert-{% if test_result.success %}success{% else %}danger{% endif %} alert-dismissible fade show">
<i class="fas fa-{% if test_result.success %}check-circle{% else %}exclamation-triangle{% endif %} me-1"></i>
{{ test_result.message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endif %}
<form method="post">
{% csrf_token %}
{% for setting in imap_settings %}
<div class="mb-3">
<label for="setting_{{ setting.key }}" class="form-label">
<strong>{{ setting.display_name }}</strong>
</label>
{% if setting.description %}
<div class="form-text mb-1">{{ setting.description }}</div>
{% endif %}
{% if setting.setting_type == 'boolean' %}
<div class="form-check form-switch">
<input type="checkbox"
class="form-check-input"
id="setting_{{ setting.key }}"
name="setting_{{ setting.key }}"
value="True"
{% if setting.get_typed_value %}checked{% endif %}>
<label class="form-check-label" for="setting_{{ setting.key }}">Aktiviert</label>
</div>
{% elif setting.setting_type == 'password' %}
<div class="input-group">
<input type="password"
class="form-control"
id="setting_{{ setting.key }}"
name="setting_{{ setting.key }}"
value="{{ setting.value }}"
placeholder="{% if setting.value %}••••••••{% else %}Passwort eingeben{% endif %}">
<button type="button" class="btn btn-outline-secondary" onclick="togglePassword(this)" title="Passwort anzeigen">
<i class="fas fa-eye"></i>
</button>
</div>
{% elif setting.setting_type == 'number' %}
<input type="number"
class="form-control"
id="setting_{{ setting.key }}"
name="setting_{{ setting.key }}"
value="{{ setting.value }}">
{% else %}
<input type="text"
class="form-control"
id="setting_{{ setting.key }}"
name="setting_{{ setting.key }}"
value="{{ setting.value }}">
{% endif %}
</div>
{% endfor %}
<hr>
<div class="d-flex gap-2">
<button type="submit" name="action" value="save" class="btn btn-primary">
<i class="fas fa-save me-1"></i> Speichern
</button>
<button type="submit" name="action" value="test" class="btn btn-outline-primary">
<i class="fas fa-plug me-1"></i> Verbindung testen
</button>
<a href="{% url 'stiftung:email_eingang_list' %}" class="btn btn-outline-secondary ms-auto">
<i class="fas fa-inbox me-1"></i> Zum Posteingang
</a>
</div>
</form>
</div>
</div>
</div>
<!-- Info sidebar -->
<div class="col-lg-4">
<div class="card">
<div class="card-header">
<i class="fas fa-info-circle"></i> Hinweise
</div>
<div class="card-body" style="font-size: 0.85rem;">
<p>Konfigurieren Sie hier die IMAP-Verbindung zum E-Mail-Server. Eingehende E-Mails werden automatisch alle <strong>15 Minuten</strong> abgerufen und den Destinatären zugeordnet.</p>
<hr>
<p class="mb-1"><strong>Typische Einstellungen:</strong></p>
<ul class="mb-0" style="font-size: 0.8rem;">
<li>SSL/TLS: Port <code>993</code></li>
<li>Unverschlüsselt: Port <code>143</code></li>
</ul>
<hr>
<p class="mb-0"><i class="fas fa-shield-alt text-success me-1"></i> Das Passwort wird in der Datenbank gespeichert. Umgebungsvariablen (<code>IMAP_HOST</code>, etc.) werden als Fallback verwendet, wenn hier keine Werte gesetzt sind.</p>
</div>
</div>
</div>
</div>
</div>
<script>
function togglePassword(btn) {
const input = btn.parentElement.querySelector('input');
const icon = btn.querySelector('i');
if (input.type === 'password') {
input.type = 'text';
icon.classList.replace('fa-eye', 'fa-eye-slash');
} else {
input.type = 'password';
icon.classList.replace('fa-eye-slash', 'fa-eye');
}
}
</script>
{% endblock %}