404 lines
13 KiB
Python
404 lines
13 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from django.urls import reverse
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from .models import PortalAppConfig
|
|
from .roles import ROLE_ADMIN, ROLE_IT_STAFF, ROLE_LABELS, ROLE_PLATFORM_OWNER, ROLE_STAFF, ROLE_SUPER_ADMIN, get_user_role_key, user_has_capability
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class AppDefinition:
|
|
key: str
|
|
section: str
|
|
route_name: str
|
|
title: object
|
|
description: object
|
|
action_label: object
|
|
capability: str | None = None
|
|
accent: str = ''
|
|
accent_label: str = 'APP'
|
|
style_variant: str = ''
|
|
tags: tuple[object, ...] = ()
|
|
|
|
|
|
APP_DEFINITIONS: tuple[AppDefinition, ...] = (
|
|
AppDefinition(
|
|
key='onboarding',
|
|
section=PortalAppConfig.SECTION_APP,
|
|
route_name='onboarding_create',
|
|
title=_('Onboarding'),
|
|
description=_('Neue Mitarbeitende erfassen, PDF mit Briefkopf erstellen, Benachrichtigungen senden und in Nextcloud ablegen.'),
|
|
action_label=_('Onboarding starten'),
|
|
accent='ON',
|
|
tags=(_('Mehrschritt-Formular'), 'PDF', _('E-Mail Routing')),
|
|
style_variant='primary',
|
|
),
|
|
AppDefinition(
|
|
key='offboarding',
|
|
section=PortalAppConfig.SECTION_APP,
|
|
route_name='offboarding_create',
|
|
title=_('Offboarding'),
|
|
description=_('Mitarbeitende suchen, Daten vorbefüllen, Offboarding-Dokumente erzeugen und Rückgabe-Prozess starten.'),
|
|
action_label=_('Offboarding starten'),
|
|
accent='OFF',
|
|
tags=(_('Profile-Suche'), _('Hardware-Liste'), _('IT-Rückgabe')),
|
|
style_variant='red',
|
|
),
|
|
AppDefinition(
|
|
key='requests_dashboard',
|
|
section=PortalAppConfig.SECTION_APP,
|
|
route_name='requests_dashboard',
|
|
title=_('Anfragen Dashboard'),
|
|
description=_('Status, Suchfunktion, PDF-Links und Verlauf aller Onboarding-/Offboarding-Anfragen.'),
|
|
action_label=_('Dashboard öffnen'),
|
|
capability='access_requests_dashboard',
|
|
accent='APP',
|
|
tags=(_('Suche'), _('Status'), _('PDF Zugriff')),
|
|
),
|
|
AppDefinition(
|
|
key='company_config',
|
|
section=PortalAppConfig.SECTION_PLATFORM,
|
|
route_name='portal_company_config_page',
|
|
title=_('Company Config'),
|
|
description=_('Rechtliche Firmendaten, Kontaktpunkte und öffentliche Unternehmenslinks pflegen.'),
|
|
action_label=_('Öffnen'),
|
|
capability='manage_company_config',
|
|
),
|
|
AppDefinition(
|
|
key='trial_management',
|
|
section=PortalAppConfig.SECTION_PLATFORM,
|
|
route_name='portal_trial_config_page',
|
|
title=_('Trial Management'),
|
|
description=_('Testlaufzeit, Banner und sichere Einschränkungen für Demo-Umgebungen steuern.'),
|
|
action_label=_('Öffnen'),
|
|
capability='manage_trial_lifecycle',
|
|
),
|
|
AppDefinition(
|
|
key='branding',
|
|
section=PortalAppConfig.SECTION_PLATFORM,
|
|
route_name='portal_branding_page',
|
|
title=_('Branding'),
|
|
description=_('Logo, Portalname, Farben und PDF-Briefkopf verwalten.'),
|
|
action_label=_('Öffnen'),
|
|
capability='manage_product_branding',
|
|
),
|
|
AppDefinition(
|
|
key='app_registry',
|
|
section=PortalAppConfig.SECTION_PLATFORM,
|
|
route_name='portal_app_registry_page',
|
|
title=_('App Registry'),
|
|
description=_('Apps zentral aktivieren, sortieren und für Kundenauftritte vorbereiten.'),
|
|
action_label=_('Öffnen'),
|
|
capability='manage_app_registry',
|
|
),
|
|
AppDefinition(
|
|
key='integrations',
|
|
section=PortalAppConfig.SECTION_ADMIN,
|
|
route_name='integrations_setup_page',
|
|
title=_('Integrationen'),
|
|
description=_('Nextcloud- und E-Mail-Setup.'),
|
|
action_label=_('Öffnen'),
|
|
capability='manage_integrations',
|
|
),
|
|
AppDefinition(
|
|
key='users',
|
|
section=PortalAppConfig.SECTION_ADMIN,
|
|
route_name='user_management_page',
|
|
title=_('Benutzer & Rollen'),
|
|
description=_('Benutzer anlegen, Rollen zuweisen und Zugriffe steuern.'),
|
|
action_label=_('Öffnen'),
|
|
capability='manage_users',
|
|
),
|
|
AppDefinition(
|
|
key='audit_log',
|
|
section=PortalAppConfig.SECTION_ADMIN,
|
|
route_name='audit_log_page',
|
|
title=_('Audit Log'),
|
|
description=_('Wichtige Admin-Aktionen nachvollziehen und prüfen.'),
|
|
action_label=_('Öffnen'),
|
|
capability='view_audit_log',
|
|
),
|
|
AppDefinition(
|
|
key='backups',
|
|
section=PortalAppConfig.SECTION_ADMIN,
|
|
route_name='backup_recovery_page',
|
|
title=_('Backup & Recovery'),
|
|
description=_('Backups erstellen und sicher verifizieren.'),
|
|
action_label=_('Öffnen'),
|
|
capability='manage_backups',
|
|
),
|
|
AppDefinition(
|
|
key='welcome_emails',
|
|
section=PortalAppConfig.SECTION_ADMIN,
|
|
route_name='welcome_emails_page',
|
|
title=_('Welcome E-Mails'),
|
|
description=_('Geplante Welcome Mails verwalten.'),
|
|
action_label=_('Öffnen'),
|
|
capability='manage_welcome_emails',
|
|
),
|
|
AppDefinition(
|
|
key='form_builder',
|
|
section=PortalAppConfig.SECTION_ADMIN,
|
|
route_name='form_builder_page',
|
|
title=_('Form Builder'),
|
|
description=_('Felder, Schritte und Optionen verwalten.'),
|
|
action_label=_('Öffnen'),
|
|
capability='manage_builders',
|
|
),
|
|
AppDefinition(
|
|
key='intro_builder',
|
|
section=PortalAppConfig.SECTION_ADMIN,
|
|
route_name='intro_builder_page',
|
|
title=_('Einweisungs-Builder'),
|
|
description=_('Checklistenpunkte für das Einweisungsprotokoll konfigurieren.'),
|
|
action_label=_('Öffnen'),
|
|
capability='manage_builders',
|
|
),
|
|
AppDefinition(
|
|
key='handbook',
|
|
section=PortalAppConfig.SECTION_ADMIN,
|
|
route_name='handbook_page',
|
|
title=_('Handbook'),
|
|
description=_('Project wiki and developer documentation in one place.'),
|
|
action_label=_('Öffnen'),
|
|
capability='view_docs',
|
|
),
|
|
AppDefinition(
|
|
key='django_admin',
|
|
section=PortalAppConfig.SECTION_ADMIN,
|
|
route_name='admin:index',
|
|
title=_('Django Admin'),
|
|
description=_('Vollständige Datenverwaltung.'),
|
|
action_label=_('Öffnen'),
|
|
capability='access_django_admin_link',
|
|
),
|
|
)
|
|
|
|
|
|
DEFAULT_ROLE_VISIBILITY = {
|
|
'onboarding': {
|
|
ROLE_SUPER_ADMIN: True,
|
|
ROLE_ADMIN: True,
|
|
ROLE_IT_STAFF: True,
|
|
ROLE_STAFF: True,
|
|
},
|
|
'offboarding': {
|
|
ROLE_SUPER_ADMIN: True,
|
|
ROLE_ADMIN: True,
|
|
ROLE_IT_STAFF: True,
|
|
ROLE_STAFF: True,
|
|
},
|
|
'requests_dashboard': {
|
|
ROLE_SUPER_ADMIN: True,
|
|
ROLE_ADMIN: True,
|
|
ROLE_IT_STAFF: True,
|
|
ROLE_STAFF: False,
|
|
},
|
|
'branding': {
|
|
ROLE_SUPER_ADMIN: False,
|
|
ROLE_ADMIN: False,
|
|
ROLE_IT_STAFF: False,
|
|
ROLE_STAFF: False,
|
|
},
|
|
'company_config': {
|
|
ROLE_SUPER_ADMIN: False,
|
|
ROLE_ADMIN: False,
|
|
ROLE_IT_STAFF: False,
|
|
ROLE_STAFF: False,
|
|
},
|
|
'trial_management': {
|
|
ROLE_SUPER_ADMIN: False,
|
|
ROLE_ADMIN: False,
|
|
ROLE_IT_STAFF: False,
|
|
ROLE_STAFF: False,
|
|
},
|
|
'app_registry': {
|
|
ROLE_SUPER_ADMIN: False,
|
|
ROLE_ADMIN: False,
|
|
ROLE_IT_STAFF: False,
|
|
ROLE_STAFF: False,
|
|
},
|
|
'integrations': {
|
|
ROLE_SUPER_ADMIN: True,
|
|
ROLE_ADMIN: True,
|
|
ROLE_IT_STAFF: False,
|
|
ROLE_STAFF: False,
|
|
},
|
|
'users': {
|
|
ROLE_SUPER_ADMIN: True,
|
|
ROLE_ADMIN: False,
|
|
ROLE_IT_STAFF: False,
|
|
ROLE_STAFF: False,
|
|
},
|
|
'audit_log': {
|
|
ROLE_SUPER_ADMIN: True,
|
|
ROLE_ADMIN: True,
|
|
ROLE_IT_STAFF: False,
|
|
ROLE_STAFF: False,
|
|
},
|
|
'backups': {
|
|
ROLE_SUPER_ADMIN: True,
|
|
ROLE_ADMIN: True,
|
|
ROLE_IT_STAFF: False,
|
|
ROLE_STAFF: False,
|
|
},
|
|
'welcome_emails': {
|
|
ROLE_SUPER_ADMIN: True,
|
|
ROLE_ADMIN: True,
|
|
ROLE_IT_STAFF: False,
|
|
ROLE_STAFF: False,
|
|
},
|
|
'form_builder': {
|
|
ROLE_SUPER_ADMIN: True,
|
|
ROLE_ADMIN: True,
|
|
ROLE_IT_STAFF: False,
|
|
ROLE_STAFF: False,
|
|
},
|
|
'intro_builder': {
|
|
ROLE_SUPER_ADMIN: True,
|
|
ROLE_ADMIN: True,
|
|
ROLE_IT_STAFF: False,
|
|
ROLE_STAFF: False,
|
|
},
|
|
'handbook': {
|
|
ROLE_SUPER_ADMIN: True,
|
|
ROLE_ADMIN: True,
|
|
ROLE_IT_STAFF: False,
|
|
ROLE_STAFF: False,
|
|
},
|
|
'django_admin': {
|
|
ROLE_SUPER_ADMIN: False,
|
|
ROLE_ADMIN: False,
|
|
ROLE_IT_STAFF: False,
|
|
ROLE_STAFF: False,
|
|
},
|
|
}
|
|
|
|
|
|
def _default_visibility_summary(definition_key: str) -> str:
|
|
visibility = DEFAULT_ROLE_VISIBILITY.get(definition_key, {})
|
|
enabled_roles = [
|
|
role
|
|
for role in (ROLE_SUPER_ADMIN, ROLE_ADMIN, ROLE_IT_STAFF, ROLE_STAFF)
|
|
if visibility.get(role)
|
|
]
|
|
if not enabled_roles:
|
|
return str(_('Nur Platform'))
|
|
if enabled_roles == [ROLE_SUPER_ADMIN, ROLE_ADMIN, ROLE_IT_STAFF, ROLE_STAFF]:
|
|
return str(_('Alle Firmenrollen'))
|
|
return ' + '.join(str(ROLE_LABELS[role]) for role in enabled_roles if role in ROLE_LABELS)
|
|
|
|
|
|
SECTION_META = {
|
|
PortalAppConfig.SECTION_APP: {
|
|
'title': _('Apps'),
|
|
'subtitle': _('Wählen Sie den gewünschten Prozess.'),
|
|
'css_class': 'section-head-primary',
|
|
'grid_class': 'apps-grid',
|
|
},
|
|
PortalAppConfig.SECTION_PLATFORM: {
|
|
'title': _('Platform Apps'),
|
|
'subtitle': _('Produktweite Konfiguration und Produktsteuerung.'),
|
|
'css_class': 'section-head-platform',
|
|
'grid_class': 'admin-grid',
|
|
},
|
|
PortalAppConfig.SECTION_ADMIN: {
|
|
'title': _('Admin Apps'),
|
|
'subtitle': _('Konfiguration, Tests und Steuerung.'),
|
|
'css_class': 'section-head-admin',
|
|
'grid_class': 'admin-grid',
|
|
},
|
|
}
|
|
|
|
|
|
def ensure_portal_app_configs() -> None:
|
|
for index, definition in enumerate(APP_DEFINITIONS):
|
|
visibility = DEFAULT_ROLE_VISIBILITY.get(definition.key, {})
|
|
PortalAppConfig.objects.get_or_create(
|
|
key=definition.key,
|
|
defaults={
|
|
'section': definition.section,
|
|
'sort_order': index,
|
|
'is_enabled': True,
|
|
'visible_to_super_admin': visibility.get(ROLE_SUPER_ADMIN, False),
|
|
'visible_to_admin': visibility.get(ROLE_ADMIN, False),
|
|
'visible_to_it_staff': visibility.get(ROLE_IT_STAFF, False),
|
|
'visible_to_staff': visibility.get(ROLE_STAFF, False),
|
|
},
|
|
)
|
|
|
|
|
|
def get_portal_app_registry_rows() -> list[dict[str, object]]:
|
|
ensure_portal_app_configs()
|
|
config_map = {config.key: config for config in PortalAppConfig.objects.all()}
|
|
rows: list[dict[str, object]] = []
|
|
for index, definition in enumerate(APP_DEFINITIONS):
|
|
config = config_map[definition.key]
|
|
rows.append(
|
|
{
|
|
'definition': definition,
|
|
'config': config,
|
|
'default_section': definition.section,
|
|
'default_sort_order': index,
|
|
'default_visibility_summary': _default_visibility_summary(definition.key),
|
|
}
|
|
)
|
|
return rows
|
|
|
|
|
|
def build_portal_app_sections(user) -> list[dict[str, object]]:
|
|
ensure_portal_app_configs()
|
|
config_map = {config.key: config for config in PortalAppConfig.objects.all()}
|
|
grouped: dict[str, list[dict[str, object]]] = {key: [] for key in SECTION_META}
|
|
role_key = get_user_role_key(user)
|
|
|
|
for definition in APP_DEFINITIONS:
|
|
config = config_map.get(definition.key)
|
|
if not config or not config.is_enabled:
|
|
continue
|
|
if definition.capability and not user_has_capability(user, definition.capability):
|
|
continue
|
|
if role_key != ROLE_PLATFORM_OWNER:
|
|
if role_key == ROLE_SUPER_ADMIN and not config.visible_to_super_admin:
|
|
continue
|
|
if role_key == ROLE_ADMIN and not config.visible_to_admin:
|
|
continue
|
|
if role_key == ROLE_IT_STAFF and not config.visible_to_it_staff:
|
|
continue
|
|
if role_key == ROLE_STAFF and not config.visible_to_staff:
|
|
continue
|
|
grouped[config.section].append(
|
|
{
|
|
'key': definition.key,
|
|
'href': reverse(definition.route_name),
|
|
'title': config.translated_title_override() or str(definition.title),
|
|
'description': config.translated_description_override() or str(definition.description),
|
|
'action_label': config.translated_action_label_override() or str(definition.action_label),
|
|
'accent': definition.accent,
|
|
'accent_label': definition.accent_label,
|
|
'style_variant': definition.style_variant,
|
|
'tags': [str(tag) for tag in definition.tags],
|
|
'sort_order': config.sort_order,
|
|
}
|
|
)
|
|
|
|
sections: list[dict[str, object]] = []
|
|
for section_key, meta in SECTION_META.items():
|
|
apps = sorted(grouped.get(section_key, []), key=lambda item: (item['sort_order'], item['title']))
|
|
if not apps:
|
|
continue
|
|
sections.append(
|
|
{
|
|
'key': section_key,
|
|
'title': str(meta['title']),
|
|
'subtitle': str(meta['subtitle']),
|
|
'css_class': meta['css_class'],
|
|
'grid_class': meta['grid_class'],
|
|
'apps': apps,
|
|
}
|
|
)
|
|
return sections
|