snapshot: preserve app registry and branding domain foundation

This commit is contained in:
Md Bayazid Bostame
2026-03-26 11:59:06 +01:00
parent 51700cfa8b
commit c195efe339
23 changed files with 1122 additions and 561 deletions

View File

@@ -0,0 +1,83 @@
{% extends 'workflows/base_shell.html' %}
{% load static i18n %}
{% block title %}{% trans "App Registry" %}{% 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 "App Registry" %}</h1>
<p class="sub">{% trans "Apps zentral steuern, für Kunden vorbereiten und ohne Template-Eingriffe auf der Landing Page ausspielen." %}</p>
{% include 'workflows/includes/messages.html' %}
<section class="card">
<div class="toolbar">
<div class="hint">{% trans "Sicherheit bleibt codebasiert: Sichtbarkeit und Reihenfolge sind hier steuerbar, Berechtigungen weiterhin über Rollen und Capabilities." %}</div>
<span class="badge scheduled">{% trans "Produktkern" %}</span>
</div>
<form method="post" action="{% url 'save_portal_app_registry' %}" class="stack-form">
{% csrf_token %}
<div class="table-wrap app-registry-wrap">
<table class="app-registry-table">
<thead>
<tr>
<th>{% trans "Key" %}</th>
<th>{% trans "Aktiv" %}</th>
<th>{% trans "Bereich" %}</th>
<th>{% trans "Reihenfolge" %}</th>
<th>{% trans "Titel DE" %}</th>
<th>{% trans "Titel EN" %}</th>
<th>{% trans "Aktion DE" %}</th>
<th>{% trans "Aktion EN" %}</th>
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr>
<td>
<div><strong>{{ row.config.key }}</strong></div>
<div class="mini">{{ row.definition.title }}</div>
</td>
<td class="select-col">
<input type="checkbox" name="is_enabled__{{ row.config.key }}" {% if row.config.is_enabled %}checked{% endif %} />
</td>
<td>
<select name="section__{{ row.config.key }}">
{% for value, label in section_choices %}
<option value="{{ value }}"{% if row.config.section == value %} selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</td>
<td>
<input type="number" name="sort_order__{{ row.config.key }}" value="{{ row.config.sort_order }}" min="0" step="1" />
</td>
<td>
<input type="text" name="title_override__{{ row.config.key }}" value="{{ row.config.title_override }}" placeholder="{{ row.definition.title }}" />
<textarea name="description_override__{{ row.config.key }}" rows="3" placeholder="{{ row.definition.description }}">{{ row.config.description_override }}</textarea>
</td>
<td>
<input type="text" name="title_override_en__{{ row.config.key }}" value="{{ row.config.title_override_en }}" placeholder="{{ row.definition.title }}" />
<textarea name="description_override_en__{{ row.config.key }}" rows="3" placeholder="{{ row.definition.description }}">{{ row.config.description_override_en }}</textarea>
</td>
<td>
<input type="text" name="action_label_override__{{ row.config.key }}" value="{{ row.config.action_label_override }}" placeholder="{{ row.definition.action_label }}" />
</td>
<td>
<input type="text" name="action_label_override_en__{{ row.config.key }}" value="{{ row.config.action_label_override_en }}" placeholder="{{ row.definition.action_label }}" />
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="toolbar" style="margin-top:1rem;">
<div class="hint">{% trans "Empfehlung: Produktweite Apps sparsam halten, kundenbezogene Prozesse unter Apps oder Admin Apps einordnen." %}</div>
<button class="btn btn-primary" type="submit">{% trans "App Registry speichern" %}</button>
</div>
</form>
</section>
{% endblock %}

View File

@@ -26,6 +26,11 @@
<label for="{{ form.company_name.id_for_label }}">{{ form.company_name.label }}</label>
{{ form.company_name }}
</div>
<div class="field">
<label for="{{ form.company_domain.id_for_label }}">{{ form.company_domain.label }}</label>
{{ form.company_domain }}
<div class="hint">{% trans "Wird für E-Mail-Vorschläge und Domain-bezogene Standardtexte verwendet, z. B. tub.co." %}</div>
</div>
<div class="field">
<label for="{{ form.support_email.id_for_label }}">{{ form.support_email.label }}</label>
{{ form.support_email }}

View File

@@ -102,12 +102,13 @@ docker compose exec -T web python manage.py check</code></pre>
<h3>Role and Permission Model</h3>
<ul>
<li>Stable Django group names: <code>Super Admin</code>, <code>Admin</code>, <code>IT Staff</code>, <code>Staff</code>.</li>
<li>Stable Django group names: <code>Platform Owner</code>, <code>Super Admin</code>, <code>Admin</code>, <code>IT Staff</code>, <code>Staff</code>.</li>
<li>Groups are created automatically through a <code>post_migrate</code> hook in <code>workflows.signals</code>.</li>
<li>Capability checks are centralized in <code>workflows.roles.CAPABILITIES</code>.</li>
<li>Use <code>_require_capability(...)</code> in views instead of flat <code>is_staff</code> checks.</li>
<li>Templates receive permission flags from <code>workflows.context_processors.role_context</code>.</li>
<li>Super-admin-only user management lives at <code>/admin-tools/users/</code> and is the preferred path for normal role assignment, account activation, invitation mail dispatch, password-reset mail dispatch, and controlled user deletion.</li>
<li><code>Platform Owner</code> is the product-level role. Company roles remain <code>Super Admin</code>, <code>Admin</code>, <code>IT Staff</code>, and <code>Staff</code>.</li>
<li>User management lives at <code>/admin-tools/users/</code> and is the preferred path for normal role assignment, account activation, invitation mail dispatch, password-reset mail dispatch, and controlled user deletion.</li>
<li>Backward-compatibility rule: authenticated legacy users with <code>is_staff=True</code> but no explicit role group currently fall back to the <code>Admin</code> capability set.</li>
<li><code>superuser</code> accounts resolve to <code>Super Admin</code>.</li>
<li>When adding a new operational page or action, define the capability in <code>roles.py</code>, gate the view, and hide the UI affordance when the capability is absent.</li>
@@ -180,6 +181,15 @@ docker compose exec -T web django-admin compilemessages</code></pre>
<li>User invitation emails and welcome-template fallbacks also use the configured branding defaults.</li>
</ul>
<h2 id="app-registry">10b) App Registry</h2>
<ul>
<li>Registry definitions live in <code>workflows/app_registry.py</code>.</li>
<li>DB overrides live in <code>PortalAppConfig</code>.</li>
<li>The landing page now renders from registry data instead of hardcoded cards.</li>
<li>Security remains code-based: app visibility/order is configurable, but access still depends on role capabilities in <code>roles.py</code>.</li>
<li>Management UI: <code>/admin-tools/apps/</code> for <code>Platform Owner</code>.</li>
</ul>
<h2 id="builders">11) Builder Architecture</h2>
<h3>Form Builder</h3>
<ul>

View File

@@ -51,147 +51,44 @@
<main class="main">
{% include 'workflows/includes/messages.html' %}
<div class="section-head section-head-primary">
<h2>{% trans "Apps" %}</h2>
<p>{% trans "Wählen Sie den gewünschten Prozess." %}</p>
</div>
<div class="apps-grid">
<section class="app-card primary">
<div>
<div class="top-line"><div class="accent">ON</div></div>
<h3 class="app-title">{% trans "Onboarding" %}</h3>
<p class="app-text">{% trans "Neue Mitarbeitende erfassen, PDF mit Briefkopf erstellen, Benachrichtigungen senden und in Nextcloud ablegen." %}</p>
<div class="tag-row">
<span class="tag">{% trans "Mehrschritt-Formular" %}</span>
<span class="tag">PDF</span>
<span class="tag">{% trans "E-Mail Routing" %}</span>
</div>
</div>
<div class="card-actions">
<a class="btn btn-primary" href="/onboarding/new/">{% trans "Onboarding starten" %}</a>
</div>
</section>
<section class="app-card red">
<div>
<div class="top-line"><div class="accent red">OFF</div></div>
<h3 class="app-title">{% trans "Offboarding" %}</h3>
<p class="app-text">{% trans "Mitarbeitende suchen, Daten vorbefüllen, Offboarding-Dokumente erzeugen und Rückgabe-Prozess starten." %}</p>
<div class="tag-row">
<span class="tag">{% trans "Profile-Suche" %}</span>
<span class="tag">{% trans "Hardware-Liste" %}</span>
<span class="tag">{% trans "IT-Rückgabe" %}</span>
</div>
</div>
<div class="card-actions">
<a class="btn btn-primary" href="/offboarding/new/">{% trans "Offboarding starten" %}</a>
</div>
</section>
{% if can_access_requests_dashboard %}
<section class="app-card">
<div>
<div class="top-line"><div class="accent">APP</div></div>
<h3 class="app-title">{% trans "Anfragen Dashboard" %}</h3>
<p class="app-text">{% trans "Status, Suchfunktion, PDF-Links und Verlauf aller Onboarding-/Offboarding-Anfragen." %}</p>
<div class="tag-row">
<span class="tag">{% trans "Suche" %}</span>
<span class="tag">{% trans "Status" %}</span>
<span class="tag">{% trans "PDF Zugriff" %}</span>
</div>
</div>
<div class="card-actions">
<a class="btn btn-secondary" href="/requests/">{% trans "Dashboard öffnen" %}</a>
</div>
</section>
{% endif %}
</div>
{% if can_manage_product_branding %}
{% for section in portal_app_sections %}
{% if not forloop.first %}
<div class="section-divider" aria-hidden="true"></div>
<div class="section-head section-head-platform">
<h2>{% trans "Platform Apps" %}</h2>
<p>{% trans "Produktweite Konfiguration und Produktsteuerung." %}</p>
</div>
<div class="admin-grid">
<section class="admin-card">
<h3>{% trans "Branding" %}</h3>
<p>{% trans "Logo, Portalname, Farben und PDF-Briefkopf verwalten." %}</p>
<a class="btn btn-secondary" href="/admin-tools/branding/">{% trans "Öffnen" %}</a>
</section>
</div>
{% endif %}
{% if can_manage_users or can_manage_integrations or can_view_audit_log or can_manage_backups or can_manage_welcome_emails or can_manage_builders or can_view_docs or can_access_django_admin_link %}
<div class="section-divider" aria-hidden="true"></div>
<div class="section-head section-head-admin">
<h2>{% trans "Admin Apps" %}</h2>
<p>{% trans "Konfiguration, Tests und Steuerung." %}</p>
<div class="section-head {{ section.css_class }}">
<h2>{{ section.title }}</h2>
<p>{{ section.subtitle }}</p>
</div>
<div class="admin-grid">
{% if can_manage_integrations %}
<div class="{{ section.grid_class }}">
{% for app in section.apps %}
{% if section.key == 'app' %}
<section class="app-card{% if app.style_variant %} {{ app.style_variant }}{% endif %}">
<div>
<div class="top-line"><div class="accent{% if app.style_variant == 'red' %} red{% endif %}">{{ app.accent }}</div></div>
<h3 class="app-title">{{ app.title }}</h3>
<p class="app-text">{{ app.description }}</p>
{% if app.tags %}
<div class="tag-row">
{% for tag in app.tags %}
<span class="tag">{{ tag }}</span>
{% endfor %}
</div>
{% endif %}
</div>
<div class="card-actions">
<a class="btn {% if app.style_variant == 'primary' or app.style_variant == 'red' %}btn-primary{% else %}btn-secondary{% endif %}" href="{{ app.href }}">{{ app.action_label }}</a>
</div>
</section>
{% else %}
<section class="admin-card">
<h3>{% trans "Integrationen" %}</h3>
<p>{% trans "Nextcloud- und E-Mail-Setup." %}</p>
<a class="btn btn-secondary" href="/admin-tools/integrations/?kind=nextcloud">{% trans "Öffnen" %}</a>
</section>
{% endif %}
{% if can_manage_users %}
<section class="admin-card">
<h3>{% trans "Benutzer & Rollen" %}</h3>
<p>{% trans "Benutzer anlegen, Rollen zuweisen und Zugriffe steuern." %}</p>
<a class="btn btn-secondary" href="/admin-tools/users/">{% trans "Öffnen" %}</a>
</section>
{% endif %}
{% if can_view_audit_log %}
<section class="admin-card">
<h3>{% trans "Audit Log" %}</h3>
<p>{% trans "Wichtige Admin-Aktionen nachvollziehen und prüfen." %}</p>
<a class="btn btn-secondary" href="/admin-tools/audit-log/">{% trans "Öffnen" %}</a>
</section>
{% endif %}
{% if can_manage_backups %}
<section class="admin-card">
<h3>{% trans "Backup & Recovery" %}</h3>
<p>{% trans "Backups erstellen und sicher verifizieren." %}</p>
<a class="btn btn-secondary" href="/admin-tools/backups/">{% trans "Öffnen" %}</a>
</section>
{% endif %}
{% if can_manage_welcome_emails %}
<section class="admin-card">
<h3>{% trans "Welcome E-Mails" %}</h3>
<p>{% trans "Geplante Welcome Mails verwalten." %}</p>
<a class="btn btn-secondary" href="/admin-tools/welcome-emails/">{% trans "Öffnen" %}</a>
</section>
{% endif %}
{% if can_manage_builders %}
<section class="admin-card">
<h3>{% trans "Form Builder" %}</h3>
<p>{% trans "Felder, Schritte und Optionen verwalten." %}</p>
<a class="btn btn-secondary" href="/admin-tools/form-builder/">{% trans "Öffnen" %}</a>
</section>
<section class="admin-card">
<h3>{% trans "Einweisungs-Builder" %}</h3>
<p>{% trans "Checklistenpunkte für das Einweisungsprotokoll konfigurieren." %}</p>
<a class="btn btn-secondary" href="/admin-tools/intro-builder/">{% trans "Öffnen" %}</a>
</section>
{% endif %}
{% if can_view_docs %}
<section class="admin-card">
<h3>{% trans "Handbook" %}</h3>
<p>{% trans "Project wiki and developer documentation in one place." %}</p>
<a class="btn btn-secondary" href="/admin-tools/handbook/">{% trans "Öffnen" %}</a>
</section>
{% endif %}
{% if can_access_django_admin_link %}
<section class="admin-card">
<h3>{% trans "Django Admin" %}</h3>
<p>{% trans "Vollständige Datenverwaltung." %}</p>
<a class="btn btn-secondary" href="/admin/">{% trans "Öffnen" %}</a>
<h3>{{ app.title }}</h3>
<p>{{ app.description }}</p>
<a class="btn btn-secondary" href="{{ app.href }}">{{ app.action_label }}</a>
</section>
{% endif %}
{% endfor %}
</div>
{% endif %}
{% endfor %}
<div class="footer-note">
{% trans "Tipp: Die letzten Vorgänge sehen Sie jederzeit im Anfragen Dashboard." %}

View File

@@ -28,7 +28,7 @@
<form method="get" action="/offboarding/new/">
<div class="field">
<label for="q">{% trans "Mitarbeitende suchen (Name oder E-Mail)" %}</label>
<input id="q" name="q" value="{{ search_query }}" placeholder="{% trans "z. B. max.mustermann@tub.co" %}" />
<input id="q" name="q" value="{{ search_query }}" placeholder="{% blocktrans trimmed with domain=portal_email_domain %}z. B. max.mustermann@{{ domain }}{% endblocktrans %}" />
</div>
<button class="btn btn-primary" type="submit">{% trans "Suchen" %}</button>
</form>
@@ -47,7 +47,7 @@
</div>
<div class="card">
<form method="post">
<form method="post" data-email-domain="{{ portal_email_domain }}">
{% csrf_token %}
<div class="grid">
{% for field in form.visible_fields %}
@@ -71,4 +71,3 @@
{% block extra_scripts %}
<script src="{% static 'workflows/js/offboarding_form.js' %}"></script>
{% endblock %}

View File

@@ -42,7 +42,7 @@
<div class="error-banner">{% trans "Bitte prüfen Sie die markierten Felder. Ungültige Eingaben wurden erkannt." %}</div>
{% endif %}
<form method="post" id="onboarding-form" enctype="multipart/form-data">
<form method="post" id="onboarding-form" enctype="multipart/form-data" data-email-domain="{{ portal_email_domain }}">
{% csrf_token %}
{% for section in onboarding_sections %}
@@ -165,4 +165,3 @@
{% block extra_scripts %}
<script src="{% static 'workflows/js/onboarding_form.js' %}"></script>
{% endblock %}

View File

@@ -179,6 +179,7 @@
<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, logo, support email, default language, PDF letterhead, 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>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>