snapshot: preserve trial lifecycle and product-grade expiry UX

This commit is contained in:
Md Bayazid Bostame
2026-03-26 14:43:10 +01:00
parent 8821a7943b
commit 811bcd8745
24 changed files with 1196 additions and 148 deletions

View File

@@ -14,6 +14,43 @@
</head>
<body{% block body_attrs %}{% endblock %}>
{% block pre_shell %}{% endblock %}
{% if portal_trial_enabled %}
<div class="app-trial-banner{% if portal_trial_expired %} is-expired{% endif %}">
<div class="app-trial-banner-inner">
<div class="app-trial-banner-chip">
{% if portal_trial_expired %}{% trans "Trial abgelaufen" %}{% else %}{% trans "Trial-Modus" %}{% endif %}
</div>
<div class="app-trial-banner-copy">
<strong class="app-trial-banner-title">
{% if portal_trial_expired %}
{% trans "Zugriff für Testnutzer gesperrt" %}
{% else %}
{% trans "Kontrollierte Testumgebung aktiv" %}
{% endif %}
</strong>
<span class="app-trial-banner-text">
{% if portal_trial_banner_text %}
{{ portal_trial_banner_text }}
{% elif portal_trial_expires_at %}
{% if portal_trial_expired %}
{% blocktrans with expires=portal_trial_expires_at|date:"d.m.Y H:i" %}Diese Testumgebung ist seit {{ expires }} abgelaufen.{% endblocktrans %}
{% else %}
{% blocktrans with expires=portal_trial_expires_at|date:"d.m.Y H:i" %}Diese Testumgebung ist bis {{ expires }} aktiv.{% endblocktrans %}
{% endif %}
{% else %}
{% trans "Diese Umgebung läuft im Trial-Modus." %}
{% endif %}
</span>
</div>
{% if portal_trial_expires_at %}
<div class="app-trial-banner-meta">
<span class="app-trial-banner-meta-label">{% trans "Ende" %}</span>
<strong>{{ portal_trial_expires_at|date:"d.m.Y H:i" }}</strong>
</div>
{% endif %}
</div>
</div>
{% endif %}
<div class="{% block shell_class %}shell{% endblock %}">
{% block shell_header %}{% endblock %}
{% block shell_body %}{% endblock %}

View File

@@ -192,6 +192,18 @@ docker compose exec -T web django-admin compilemessages</code></pre>
<li>Management UI: <code>/admin-tools/apps/</code> for <code>Platform Owner</code>.</li>
</ul>
<h2 id="trial">10c) Trial Lifecycle</h2>
<ul>
<li>Deployment-level trial settings are stored in the singleton model <code>PortalTrialConfig</code>.</li>
<li>Management UI: <code>/admin-tools/trial/</code> for <code>Platform Owner</code>.</li>
<li>Current scope: trial enable/disable, start/end timestamps, bilingual shell banner text, production-integration restriction, and cleanup readiness.</li>
<li>Enforcement lives in <code>workflows.middleware.TrialModeMiddleware</code>, so expiry is handled centrally instead of per-view.</li>
<li>While trial restriction is active, service-level integration checks force Nextcloud off and E-Mail into test mode.</li>
<li>Expired-trial cleanup is intentionally CLI-only:
<pre><code>docker compose exec -T web python manage.py cleanup_expired_trial_workspace --yes-delete</code></pre>
</li>
</ul>
<h2 id="builders">11) Builder Architecture</h2>
<h3>Form Builder</h3>
<ul>

View File

@@ -179,7 +179,8 @@
<li><strong>Einweisungs-Builder:</strong> manage custom checklist items for the intro PDF and live introduction checklist, including section, visibility, and conditional display logic.</li>
<li><strong>Integrations:</strong> Nextcloud, SMTP, default routing addresses, notification rules, workflow rules, and remote backup target settings.</li>
<li><strong>Branding:</strong> portal title, company name, company domain, support email, sender display name, logo, favicon, default language, PDF letterhead, footer/legal text, and basic brand colors.</li>
<li><strong>App Registry:</strong> platform-level registry for enabling, ordering, and relabeling landing-page apps without editing the home template.</li>
<li><strong>App Registry:</strong> platform-level registry for enabling, ordering, relabeling, and role-targeting landing-page apps without editing the home template.</li>
<li><strong>Trial Management:</strong> platform-only control surface for trial runtime, expiry, shell banner, and safe demo restrictions.</li>
<li><strong>Benutzer &amp; Rollen:</strong> super-admin-only page for creating users, assigning roles, activating/deactivating access, sending access or password-reset links by email, and deleting accounts when appropriate.</li>
<li><strong>Welcome Emails:</strong> scheduled jobs, pause/resume/cancel/trigger now.</li>
<li><strong>Audit Log:</strong> staff-only trace of important admin changes such as builder edits, settings updates, PDF generation, welcome-email operations, and request deletions. Supports filtering by action, user, and date range.</li>
@@ -210,6 +211,10 @@
<li>Nextcloud remote backups must use a separate backup directory, not the normal onboarding/offboarding document directory.</li>
<li>Longer-running admin actions such as backup create/verify and integration tests use the same shared progress overlay after confirmation.</li>
<li>Brand assets such as logo and PDF letterhead are managed separately under Admin Apps → <code>Branding</code>.</li>
<li>Trial deployments can force safe integration behavior: Nextcloud is treated as disabled and email remains in test mode while the trial restriction is active.</li>
<li>Expired trials should be cleaned with the dedicated command, not from the browser:
<pre><code>docker compose exec -T web python manage.py cleanup_expired_trial_workspace --yes-delete</code></pre>
</li>
</ul>
<h3>Deployment Notes</h3>

View File

@@ -0,0 +1,46 @@
{% extends 'workflows/base_shell.html' %}
{% load static i18n %}
{% block title %}{% trans "Trial abgelaufen" %}{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'workflows/css/admin_tools.css' %}" />
{% endblock %}
{% block shell_body %}
{% include 'workflows/includes/app_header.html' with header_show_home=0 header_show_lang=1 header_inside_shell=1 %}
<section class="trial-expired-shell">
<div class="trial-expired-card">
<div class="trial-expired-badge">{% trans "Trial expired" %}</div>
<h1>{% trans "Trial abgelaufen" %}</h1>
<p class="sub">{% trans "Diese Testumgebung ist nicht mehr aktiv. Bitte wenden Sie sich für eine Verlängerung oder ein Produktiv-Setup an den Plattformbetreiber." %}</p>
<div class="trial-expired-grid">
<div class="trial-expired-panel">
<span class="trial-expired-label">{% trans "Status" %}</span>
<strong>{% trans "Zugriff gesperrt" %}</strong>
<p>{% trans "Nicht-Platform-Nutzer können diese Umgebung nach Ablauf nicht mehr verwenden." %}</p>
</div>
<div class="trial-expired-panel">
<span class="trial-expired-label">{% trans "Nächster Schritt" %}</span>
<strong>{% trans "Verlängern oder Produktiv-Setup" %}</strong>
<p>{% trans "Ein Platform Owner kann den Trial verlängern oder das Setup in einen regulären Betrieb überführen." %}</p>
</div>
{% if portal_trial_expires_at %}
<div class="trial-expired-panel">
<span class="trial-expired-label">{% trans "Ablaufzeit" %}</span>
<strong>{{ portal_trial_expires_at|date:"d.m.Y H:i" }}</strong>
<p>{% trans "Das ist der im System hinterlegte Endzeitpunkt der Testumgebung." %}</p>
</div>
{% endif %}
</div>
<div class="actions">
<a class="btn btn-secondary" href="{% url 'login' %}">{% trans "Zur Anmeldung" %}</a>
</div>
{% if portal_support_email %}
<div class="trial-expired-contact">{% blocktrans with email=portal_support_email %}Kontakt: {{ email }}{% endblocktrans %}</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,127 @@
{% extends 'workflows/base_shell.html' %}
{% load static i18n %}
{% block title %}{% trans "Trial Management" %}{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'workflows/css/admin_tools.css' %}" />
{% endblock %}
{% block shell_body %}
{% include 'workflows/includes/app_header.html' with header_show_home=1 header_show_lang=1 header_inside_shell=1 %}
<h1>{% trans "Trial Management" %}</h1>
<p class="sub">{% trans "Testlaufzeit, Banner und sichere Einschränkungen für Demo- und Pilotumgebungen steuern." %}</p>
{% include 'workflows/includes/messages.html' %}
<section class="branding-sections">
<section class="branding-block trial-overview">
<div class="branding-block-head">
<h2>{% trans "Übersicht" %}</h2>
<p>{% trans "Aktueller Trial-Status und die daraus resultierende Systemwirkung." %}</p>
</div>
<div class="trial-summary-grid">
<div class="trial-summary-card">
<span class="trial-summary-label">{% trans "Status" %}</span>
<strong class="trial-summary-value {% if portal_trial_enabled %}{% if portal_trial_expired %}is-expired{% else %}is-active{% endif %}{% else %}is-inactive{% endif %}">
{% if portal_trial_enabled %}
{% if portal_trial_expired %}{% trans "Abgelaufen" %}{% else %}{% trans "Aktiv" %}{% endif %}
{% else %}
{% trans "Deaktiviert" %}
{% endif %}
</strong>
</div>
<div class="trial-summary-card">
<span class="trial-summary-label">{% trans "Ende" %}</span>
<strong class="trial-summary-value">
{% if portal_trial_expires_at %}{{ portal_trial_expires_at|date:"d.m.Y H:i" }}{% else %}{% trans "Nicht gesetzt" %}{% endif %}
</strong>
</div>
<div class="trial-summary-card">
<span class="trial-summary-label">{% trans "Nextcloud effektiv" %}</span>
<strong class="trial-summary-value {% if portal_trial_restrict_integrations and portal_trial_enabled %}is-inactive{% else %}is-active{% endif %}">
{% if portal_trial_restrict_integrations and portal_trial_enabled %}{% trans "Deaktiviert" %}{% else %}{% trans "Unverändert" %}{% endif %}
</strong>
</div>
<div class="trial-summary-card">
<span class="trial-summary-label">{% trans "E-Mail effektiv" %}</span>
<strong class="trial-summary-value {% if portal_trial_restrict_integrations and portal_trial_enabled %}is-warn{% else %}is-active{% endif %}">
{% if portal_trial_restrict_integrations and portal_trial_enabled %}{% trans "Testmodus" %}{% else %}{% trans "Unverändert" %}{% endif %}
</strong>
</div>
</div>
<div class="hint">
{% trans "Zum Deaktivieren des Trial-Modus entfernen Sie den Haken bei „Trial-Modus aktiv“ und speichern Sie die Seite." %}
</div>
</section>
<form method="post" action="{% url 'save_portal_trial_config' %}" class="stack-form">
{% csrf_token %}
<section class="branding-block">
<div class="branding-block-head">
<h2>{% trans "Trial-Status" %}</h2>
<p>{% trans "Aktivieren Sie den Trial-Modus und definieren Sie die gültige Laufzeit." %}</p>
</div>
<div class="grid">
<div class="field field-full">
<label for="{{ form.is_trial_mode.id_for_label }}">{{ form.is_trial_mode.label }}</label>
<div class="check-row">
<label>{{ form.is_trial_mode }} {% trans "Diese Deployment-Umgebung als Trial führen" %}</label>
</div>
<div class="hint">{% trans "Sobald dieser Schalter deaktiviert ist, verschwindet das Trial-Banner und die normalen Integrationsregeln greifen wieder." %}</div>
</div>
<div class="field">
<label for="{{ form.trial_started_at.id_for_label }}">{{ form.trial_started_at.label }}</label>
{{ form.trial_started_at }}
</div>
<div class="field">
<label for="{{ form.trial_expires_at.id_for_label }}">{{ form.trial_expires_at.label }}</label>
{{ form.trial_expires_at }}
{% if trial_is_expired %}<div class="hint">{% trans "Der konfigurierte Trial ist derzeit abgelaufen." %}</div>{% endif %}
</div>
</div>
</section>
<section class="branding-block">
<div class="branding-block-head">
<h2>{% trans "Sicherheitsregeln" %}</h2>
<p>{% trans "Testumgebungen sollen keine produktiven Integrationen verwenden." %}</p>
</div>
<div class="check-row">
<label>{{ form.restrict_production_integrations }} {% trans "Nextcloud produktiv deaktivieren und E-Mail-Testmodus erzwingen" %}</label>
<label>{{ form.auto_cleanup_enabled }} {% trans "Cleanup nach Ablauf vorbereiten" %}</label>
</div>
<div class="hint">{% trans "Wenn diese Regel aktiv ist, bleiben produktive Integrationen technisch gesperrt, auch wenn lokale Overrides anders gesetzt sind." %}</div>
</section>
<section class="branding-block">
<div class="branding-block-head">
<h2>{% trans "Banner" %}</h2>
<p>{% trans "Optionaler Hinweistext für die Shell. Ohne Text wird ein Standardhinweis mit Enddatum verwendet." %}</p>
</div>
<div class="grid lang-pairs">
<div class="lang-block">
<h3>{% trans "Deutsch" %}</h3>
<div class="field">
<label for="{{ form.trial_banner_text.id_for_label }}">{{ form.trial_banner_text.label }}</label>
{{ form.trial_banner_text }}
</div>
</div>
<div class="lang-block">
<h3>{% trans "English" %}</h3>
<div class="field">
<label for="{{ form.trial_banner_text_en.id_for_label }}">{{ form.trial_banner_text_en.label }}</label>
{{ form.trial_banner_text_en }}
</div>
</div>
</div>
</section>
<div class="toolbar" style="margin-top:1rem;">
<div class="hint">{% trans "Die eigentliche Datenbereinigung läuft bewusst nicht über die Web-UI. Nutzen Sie dafür den Cleanup-Command im Betrieb." %}</div>
<button class="btn btn-primary" type="submit">{% trans "Trial-Konfiguration speichern" %}</button>
</div>
</form>
</section>
{% endblock %}