i18 drin
Formeleon Deployment Pipeline / Deploy to Dev Environment (push) Successful in 1m1s
Formeleon Deployment Pipeline / Deploy to Production Environment (push) Skipped

mail drin
fehlende routen drin
prod-reife erreicht.
TODOS:

Deine aktuellen offenen TODOs
1. Rate-Limiting (Spam-Schutz VOR dem reCAPTCHA)
Das reCAPTCHA hält zwar Bots ab, aber wenn jemand böswillig ein Script schreibt, das 1.000 POST-Requests pro Sekunde an /api/v1/submissions sendet, muss dein Backend jedes Mal Google kontaktieren und die Datenbank befragen. Das frisst Server-Ressourcen.

Todo: Einbinden von Flask-Limiter und Absichern der API-Route (z.B. max. 10 Submissions pro Minute pro IP-Adresse oder Tenant).
2. Billing & Feature-Enforcement (Limits pro Tenant durchsetzen)
Die Mandanten-Architektur und das config-Feld im JSON existieren bereits. Aber wir erzwingen diese Limits aktuell nicht im Backend.

Todo: Im Dashboard (templates/admin/tenants/view.html) UI-Controls einbauen, um Feature-Flags für einen Tenant festzulegen:
max_forms (z.B. 1 für Free-User, 10 für Pro-User).
allow_instant_mail (Dürfen E-Mails an den Tenant gesendet werden?).
allow_autoresponder (Darf der Autoresponder für Endkunden genutzt werden?).
Todo: Diese Limits hart im Backend (beim Erstellen von Forms und beim Mailversand) kontrollieren. (Der Mailer prüft das zwar schon halbwegs, das UI zum Pflegen fehlt aber noch komplett).
3. E-Mail Templates aufwerten (HTML statt Plain-Text)
Bisher versendet der Service E-Mails als reinen Text (plain). Für eine professionelle SaaS-Lösung sollten diese Mails ansprechend aussehen.

Todo: Im services/mailer.py den MIMEMultipart so erweitern, dass er saubere HTML-Mails aus einem Jinja2-Template (templates/emails/...) generiert, inklusive Branding/Logo deines Services.
4. Frontend-Snippet "Graceful Degradation"
Das embed.js arbeitet aktuell sehr gut. Aber was ist, wenn das Formeleon-Backend mal für 5 Minuten down ist?

Todo: Einen sauberen Timeout im fetch einbauen und dem Endkunden auf der Webseite eine schöne, fallback HTML-Meldung anzeigen ("Entschuldigung, das Formular ist momentan nicht erreichbar"), anstatt dass sich das Script lautlos in der Console mit einem CORS/Network-Error verabschiedet.
5. CI/CD Pipeline (Deployment)
Wenn der Code fertig ist, willst du ihn sicher nicht immer händisch per git pull auf deinen Produktionsserver schieben und dort docker compose up tippen.

Todo: Eine simple Pipeline (Github Actions / Gitea Actions) anlegen, die den Code testet und automatisch auf dem Zielserver ausrollt.
This commit is contained in:
br committed 2026-09-29 05:16:01 +02:00
1 parent 7f81932fa5
commit 5fff17531d
31 files changed
+2683 -550

No files matched your search

+110
View File
@@ -0,0 +1,110 @@
{% extends "base.html" %}
{% block title %}My Profile - Formeleon{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-person-badge me-2"></i> My Profile</h2>
</div>
<div class="row">
<!-- User Information -->
<div class="col-lg-5 mb-4">
<div class="card shadow-sm h-100 border-top border-primary border-4">
<div class="card-header bg-light bg-gradient">
<h5 class="card-title mb-0"><i class="bi bi-person-circle me-2"></i> Account Details</h5>
</div>
<div class="card-body">
<div class="text-center mb-4 mt-2">
<div class="rounded-circle bg-primary text-white d-inline-flex align-items-center justify-content-center mx-auto shadow-sm" style="width: 80px; height: 80px; font-size: 2rem;">
{{ user.name[0]|upper if user.name else '?' }}
</div>
<h4 class="mt-3 mb-1">{{ user.name }}</h4>
<p class="text-muted mb-0">{{ user.email }}</p>
</div>
<ul class="list-group list-group-flush mb-4">
<li class="list-group-item d-flex justify-content-between align-items-center px-0">
<span class="text-muted">Account Role</span>
{% if user.role.value == 'admin' %}
<span class="badge bg-danger">Global Admin</span>
{% elif user.role.value == 'tenant_admin' %}
<span class="badge bg-primary">Tenant Admin</span>
{% elif user.role.value == 'user' %}
<span class="badge bg-info text-dark">User</span>
{% else %}
<span class="badge bg-secondary">{{ user.role.value|title }}</span>
{% endif %}
</li>
<li class="list-group-item d-flex justify-content-between align-items-center px-0">
<span class="text-muted">Member Since</span>
<span>{{ user.created_at.strftime('%Y-%m-%d') if user.created_at else 'Unknown' }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center px-0">
<span class="text-muted">Last Login</span>
<span>{{ user.last_login.strftime('%Y-%m-%d %H:%M') if user.last_login else 'Unknown' }}</span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center px-0">
<span class="text-muted">Authentication</span>
{% if dev_mode %}
<span class="badge bg-warning text-dark"><i class="bi bi-bug"></i> Dev Mode Bypass</span>
{% else %}
<span class="badge bg-success"><i class="bi bi-shield-check"></i> OIDC (Zitadel)</span>
{% endif %}
</li>
</ul>
{% if not dev_mode %}
<div class="alert alert-info py-2 small mb-0">
<i class="bi bi-info-circle me-1"></i>
To change your name, email, or password, please visit the <a href="{{ config.get('ZITADEL_ISSUER') }}/ui/console/users/me" target="_blank" class="alert-link">Zitadel Identity Console <i class="bi bi-box-arrow-up-right"></i></a>.
</div>
{% endif %}
</div>
</div>
</div>
<!-- Assigned Tenants -->
<div class="col-lg-7 mb-4">
<div class="card shadow-sm h-100">
<div class="card-header bg-light bg-gradient d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0"><i class="bi bi-buildings me-2"></i> My Workspaces (Tenants)</h5>
<span class="badge bg-secondary rounded-pill">{{ user.tenants|length if not user.is_admin() else 'All' }}</span>
</div>
<div class="card-body p-0">
{% if user.is_admin() %}
<div class="p-4 text-center">
<div class="text-danger mb-3">
<i class="bi bi-shield-lock-fill" style="font-size: 3rem;"></i>
</div>
<h5>Global Admin Access</h5>
<p class="text-muted">As a global administrator, you automatically have access to view and manage <strong>all</strong> tenants in the system.</p>
<a href="{{ url_for('admin.list_tenants') }}" class="btn btn-outline-primary">Manage All Tenants</a>
</div>
{% else %}
{% if user.tenants %}
<div class="list-group list-group-flush">
{% for t in user.tenants %}
<a href="{{ url_for('admin.view_tenant', tenant_id=t.id) }}" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center p-3">
<div>
<h6 class="mb-1">{{ t.name }}</h6>
<small class="text-muted font-monospace">{{ t.id }}</small>
</div>
<span class="badge bg-{{ 'success' if t.is_active() else 'secondary' }}">
{{ 'Active' if t.is_active() else 'Inactive' }}
</span>
</a>
{% endfor %}
</div>
{% else %}
<div class="p-5 text-center text-muted">
<i class="bi bi-building-x fs-1 d-block mb-3"></i>
<p>You are not currently assigned to any workspaces.</p>
<p class="small">Please contact your administrator to request access.</p>
</div>
{% endif %}
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}