i18 drin
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:
1 parent
7f81932fa5
commit
5fff17531d
31 files changed
+2683
-550
No files matched your search
@@ -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 %}
|
||||
Reference in new issue
Block a user