851 lines
30 KiB
Python
851 lines
30 KiB
Python
from pathlib import Path
|
|
import re
|
|
from datetime import timedelta
|
|
from tempfile import NamedTemporaryFile
|
|
import json
|
|
from io import BytesIO
|
|
from celery import current_app
|
|
from django.conf import settings
|
|
from django.db import connection
|
|
from django.db import IntegrityError
|
|
from django.db.models import Q
|
|
from django.db.models import Count
|
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
from django.contrib import messages
|
|
from django.contrib.auth import get_user_model, login as auth_login
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.contrib.auth.tokens import default_token_generator
|
|
from django.http import JsonResponse
|
|
from django.views.decorators.http import require_POST
|
|
from django.views.decorators.csrf import ensure_csrf_cookie
|
|
from django.utils import timezone
|
|
from django.utils.encoding import force_bytes
|
|
from django.utils.http import urlsafe_base64_encode
|
|
from django.utils.translation import gettext as _
|
|
from django.urls import reverse
|
|
|
|
from .app_registry import build_portal_app_sections, get_portal_app_registry_rows, normalize_portal_app_sort_orders
|
|
from .backup_ops import (
|
|
create_backup_bundle,
|
|
delete_backup_bundle,
|
|
latest_backup_health_snapshot,
|
|
list_backup_bundles,
|
|
verify_backup_bundle,
|
|
)
|
|
from .branding import get_branding_email_copy, get_company_email_domain, get_default_notification_templates, get_portal_trial_config, is_trial_expired
|
|
from . import account_views, admin_config_views, integrations_views, request_views
|
|
from .forms import AccountAvatarForm, AccountDetailsForm, AccountNotificationPreferencesForm, AccountTOTPDisableForm, AccountTOTPEnableForm, AccountTOTPRegenerateRecoveryCodesForm, AppLoginForm, AppTOTPChallengeForm, OffboardingRequestForm, OnboardingRequestForm, PortalBrandingForm, PortalCompanyConfigForm, PortalTrialConfigForm, UserManagementCreateForm
|
|
from .form_builder import (
|
|
DEFAULT_FIELD_ORDER,
|
|
DEFAULT_CONDITIONAL_RULES,
|
|
FORM_PRESETS,
|
|
LOCKED_FIELD_RULES,
|
|
LOCKED_SECTION_RULES,
|
|
OFFBOARDING_PAGE_LABELS,
|
|
OFFBOARDING_PAGE_ORDER,
|
|
ONBOARDING_DEFAULT_PAGE,
|
|
build_custom_field_key,
|
|
custom_field_target_key,
|
|
ensure_form_field_configs,
|
|
ensure_form_conditional_rule_configs,
|
|
ensure_form_section_configs,
|
|
get_custom_field_configs,
|
|
get_custom_section_configs,
|
|
get_default_page_map,
|
|
get_section_definitions,
|
|
get_section_labels,
|
|
get_section_order,
|
|
apply_form_preset,
|
|
)
|
|
from .form_builder_views import form_builder_page_impl
|
|
from .intro_builder_views import intro_builder_page_impl
|
|
from .observability_views import (
|
|
audit_log_page_impl,
|
|
backup_recovery_page_impl,
|
|
create_backup_from_admin_impl,
|
|
job_monitor_page_impl,
|
|
verify_backup_from_admin_impl,
|
|
)
|
|
from .view_audit import audit as _audit, audit_action_label as _audit_action_label, display_user_name as _display_user_name
|
|
from .view_context import (
|
|
form_field_labels as _form_field_labels,
|
|
request_custom_field_details as _request_custom_field_details,
|
|
request_status_label as _request_status_label,
|
|
request_target_label as _request_target_label,
|
|
)
|
|
from .view_form_runtime import (
|
|
CONDITIONAL_RULE_OPERATOR_CHOICES,
|
|
ONBOARDING_CHECKBOX_LISTS,
|
|
ONBOARDING_GROUPS,
|
|
ONBOARDING_INLINE_CHECKS,
|
|
active_conditional_target_keys as _active_conditional_target_keys,
|
|
build_offboarding_sections as _build_offboarding_sections,
|
|
build_onboarding_layout as _build_onboarding_layout,
|
|
build_onboarding_sections as _build_onboarding_sections,
|
|
conditional_rule_summary as _conditional_rule_summary,
|
|
field_rule_summary as _field_rule_summary,
|
|
normalized_conditional_rule_payload as _normalized_conditional_rule_payload,
|
|
translate_choice_list as _translate_choice_list,
|
|
)
|
|
from .view_permissions import require_capability as _require_capability
|
|
from .models import AdminAuditLog, AsyncTaskLog, EmployeeProfile, FormConditionalRuleConfig, FormCustomFieldConfig, FormCustomSectionConfig, FormFieldConfig, FormOption, FormSectionConfig, NotificationRule, NotificationTemplate, OffboardingRequest, OnboardingIntroductionSession, OnboardingRequest, PortalAppConfig, PortalBranding, PortalCompanyConfig, PortalTrialConfig, ScheduledWelcomeEmail, SystemEmailConfig, UserNotification, UserProfile, WorkflowConfig
|
|
from .emailing import send_system_email
|
|
from .notifications import notify_user
|
|
from .roles import ROLE_GROUP_NAMES, ROLE_LABELS, ROLE_PLATFORM_OWNER, ROLE_SUPER_ADMIN, assign_user_role, get_user_role_key, get_user_role_label, user_has_capability
|
|
from .services import get_email_test_redirect, is_email_test_mode, is_nextcloud_enabled, upload_to_nextcloud
|
|
from .totp import build_otpauth_uri, generate_recovery_codes, generate_totp_secret
|
|
from .tasks import (
|
|
_generate_onboarding_intro_pdf,
|
|
_generate_onboarding_intro_session_pdf,
|
|
build_intro_sections_for_request,
|
|
process_offboarding_request,
|
|
process_onboarding_request,
|
|
send_scheduled_welcome_email,
|
|
)
|
|
|
|
|
|
def _redirect_back(request, fallback: str):
|
|
target = (request.POST.get('next') or request.GET.get('next') or '').strip()
|
|
if target.startswith('/'):
|
|
return redirect(target)
|
|
referer = (request.META.get('HTTP_REFERER') or '').strip()
|
|
if referer.startswith('http://127.0.0.1') or referer.startswith('http://localhost') or referer.startswith('/'):
|
|
return redirect(referer)
|
|
return redirect(fallback)
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def mark_notification_read(request, notification_id: int):
|
|
notification = get_object_or_404(UserNotification, id=notification_id, user=request.user)
|
|
notification.mark_read()
|
|
return _redirect_back(request, 'home')
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def mark_all_notifications_read(request):
|
|
UserNotification.objects.filter(user=request.user, read_at__isnull=True).update(read_at=timezone.now())
|
|
return _redirect_back(request, 'home')
|
|
|
|
def healthz(request):
|
|
db_ok = True
|
|
try:
|
|
with connection.cursor() as cursor:
|
|
cursor.execute('SELECT 1')
|
|
cursor.fetchone()
|
|
except Exception:
|
|
db_ok = False
|
|
|
|
status_code = 200 if db_ok else 503
|
|
return JsonResponse(
|
|
{
|
|
'status': 'ok' if db_ok else 'degraded',
|
|
'service': 'workdock',
|
|
'db': 'ok' if db_ok else 'error',
|
|
'time': timezone.now().isoformat(),
|
|
},
|
|
status=status_code,
|
|
)
|
|
|
|
|
|
def login_page(request):
|
|
return account_views.login_page_impl(request)
|
|
|
|
|
|
def login_totp_page(request):
|
|
return account_views.login_totp_page_impl(request)
|
|
|
|
|
|
@login_required
|
|
def account_profile_page(request):
|
|
return account_views.account_profile_page_impl(request)
|
|
|
|
|
|
@login_required
|
|
def home(request):
|
|
config, _ = WorkflowConfig.objects.get_or_create(name='Default')
|
|
role_key = get_user_role_key(request.user)
|
|
return render(
|
|
request,
|
|
'workflows/home.html',
|
|
{
|
|
'nextcloud_enabled': is_nextcloud_enabled(),
|
|
'email_test_mode': is_email_test_mode(),
|
|
'workflow_config': config,
|
|
'role_label': get_user_role_label(request.user),
|
|
'role_key': role_key,
|
|
'portal_app_sections': build_portal_app_sections(request.user),
|
|
},
|
|
)
|
|
|
|
|
|
@_require_capability('manage_app_registry')
|
|
def portal_app_registry_page(request):
|
|
return admin_config_views.portal_app_registry_page_impl(request, translate_choice_list=_translate_choice_list)
|
|
|
|
|
|
@_require_capability('view_job_monitor')
|
|
def job_monitor_page(request):
|
|
return job_monitor_page_impl(request)
|
|
|
|
|
|
@_require_capability('manage_app_registry')
|
|
@require_POST
|
|
def save_portal_app_registry(request):
|
|
return admin_config_views.save_portal_app_registry_impl(request, audit_fn=_audit)
|
|
|
|
|
|
def _user_management_rows():
|
|
user_model = get_user_model()
|
|
role_order = {
|
|
ROLE_PLATFORM_OWNER: 0,
|
|
ROLE_SUPER_ADMIN: 0,
|
|
'admin': 1,
|
|
'it_staff': 2,
|
|
'staff': 3,
|
|
}
|
|
rows = []
|
|
for user in user_model.objects.all().order_by('-is_active', 'username'):
|
|
role_key = get_user_role_key(user)
|
|
rows.append(
|
|
{
|
|
'user': user,
|
|
'role_key': role_key,
|
|
'role_label': str(ROLE_LABELS[role_key]),
|
|
'role_sort': role_order.get(role_key, 99),
|
|
'display_name': _display_user_name(user),
|
|
}
|
|
)
|
|
rows.sort(key=lambda item: (not item['user'].is_active, item['role_sort'], item['user'].username.lower()))
|
|
return rows
|
|
|
|
|
|
def _render_user_management(request, create_form=None, status_code: int = 200):
|
|
recent_user_events = list(
|
|
AdminAuditLog.objects.select_related('actor')
|
|
.filter(action__in=['user_created', 'user_updated', 'user_password_reset_sent', 'user_deleted'])
|
|
.order_by('-created_at', '-id')[:12]
|
|
)
|
|
for row in recent_user_events:
|
|
row.action_label = _audit_action_label(row.action)
|
|
role_key = (row.details or {}).get('role')
|
|
row.role_label = str(ROLE_LABELS[role_key]) if role_key in ROLE_LABELS else role_key
|
|
include_product_owner = get_user_role_key(request.user) == ROLE_PLATFORM_OWNER
|
|
return render(
|
|
request,
|
|
'workflows/user_management.html',
|
|
{
|
|
'create_form': create_form or UserManagementCreateForm(include_product_owner=include_product_owner),
|
|
'rows': _user_management_rows(),
|
|
'role_choices': [
|
|
(key, str(ROLE_LABELS[key]))
|
|
for key in ROLE_GROUP_NAMES
|
|
if include_product_owner or key != ROLE_PLATFORM_OWNER
|
|
],
|
|
'include_product_owner': include_product_owner,
|
|
'recent_user_events': recent_user_events,
|
|
},
|
|
status=status_code,
|
|
)
|
|
|
|
|
|
def _platform_owner_user_count() -> int:
|
|
user_model = get_user_model()
|
|
return sum(1 for user in user_model.objects.all() if get_user_role_key(user) == ROLE_PLATFORM_OWNER and user.is_active)
|
|
|
|
|
|
def _super_admin_user_count() -> int:
|
|
user_model = get_user_model()
|
|
return sum(1 for user in user_model.objects.all() if get_user_role_key(user) == ROLE_SUPER_ADMIN and user.is_active)
|
|
|
|
|
|
def _would_remove_last_super_admin(user, new_role_key: str | None = None, new_is_active: bool | None = None, deleting: bool = False) -> bool:
|
|
if get_user_role_key(user) != ROLE_SUPER_ADMIN or not user.is_active:
|
|
return False
|
|
if _super_admin_user_count() > 1:
|
|
return False
|
|
if deleting:
|
|
return True
|
|
if new_role_key is not None and new_role_key != ROLE_SUPER_ADMIN:
|
|
return True
|
|
if new_is_active is not None and not new_is_active:
|
|
return True
|
|
return False
|
|
|
|
|
|
def _would_remove_last_platform_owner(user, new_role_key: str | None = None, new_is_active: bool | None = None, deleting: bool = False) -> bool:
|
|
if get_user_role_key(user) != ROLE_PLATFORM_OWNER or not user.is_active:
|
|
return False
|
|
if _platform_owner_user_count() > 1:
|
|
return False
|
|
if deleting:
|
|
return True
|
|
if new_role_key is not None and new_role_key != ROLE_PLATFORM_OWNER:
|
|
return True
|
|
if new_is_active is not None and not new_is_active:
|
|
return True
|
|
return False
|
|
|
|
|
|
def _send_user_access_email(request, target_user, *, invitation: bool) -> None:
|
|
email = (target_user.email or '').strip()
|
|
if not email:
|
|
raise ValueError(_('Für diesen Benutzer ist keine E-Mail-Adresse hinterlegt.'))
|
|
|
|
uid = urlsafe_base64_encode(force_bytes(target_user.pk))
|
|
token = default_token_generator.make_token(target_user)
|
|
reset_path = reverse('password_reset_confirm', kwargs={'uidb64': uid, 'token': token})
|
|
reset_url = request.build_absolute_uri(reset_path)
|
|
branding_copy = get_branding_email_copy()
|
|
|
|
if invitation:
|
|
subject = _('Zugangseinladung für %(username)s') % {'username': target_user.username}
|
|
body = _(
|
|
'Hallo %(name)s,\n\n'
|
|
'für Sie wurde ein Benutzerkonto im %(portal_title)s angelegt.\n'
|
|
'Bitte öffnen Sie den folgenden Link, um Ihr Passwort zu setzen:\n'
|
|
'%(url)s\n\n'
|
|
'Wenn Sie diese Einladung nicht erwartet haben, melden Sie sich bitte bei Ihrem Administrator.'
|
|
) % {
|
|
'name': _display_user_name(target_user),
|
|
'portal_title': branding_copy['portal_title'],
|
|
'url': reset_url,
|
|
}
|
|
else:
|
|
subject = _('Passwort zurücksetzen für %(username)s') % {'username': target_user.username}
|
|
body = _(
|
|
'Hallo %(name)s,\n\n'
|
|
'für Ihr Konto wurde ein Link zum Zurücksetzen des Passworts erstellt.\n'
|
|
'Bitte öffnen Sie den folgenden Link:\n'
|
|
'%(url)s\n\n'
|
|
'Wenn Sie diese Anfrage nicht erwartet haben, können Sie diese E-Mail ignorieren.'
|
|
) % {
|
|
'name': _display_user_name(target_user),
|
|
'url': reset_url,
|
|
}
|
|
|
|
send_system_email(subject=subject, body=body, to=[email])
|
|
|
|
|
|
@_require_capability('manage_users')
|
|
def user_management_page(request):
|
|
return admin_config_views.user_management_page_impl(request, render_user_management_fn=_render_user_management)
|
|
|
|
|
|
@_require_capability('manage_product_branding')
|
|
def portal_branding_page(request):
|
|
return admin_config_views.portal_branding_page_impl(request, build_branding_sections_fn=_build_branding_sections)
|
|
|
|
|
|
@_require_capability('manage_product_branding')
|
|
@require_POST
|
|
def save_portal_branding(request):
|
|
return admin_config_views.save_portal_branding_impl(request, audit_fn=_audit, build_branding_sections_fn=_build_branding_sections)
|
|
|
|
|
|
def _build_branding_sections(form, branding):
|
|
sections = [
|
|
{
|
|
'key': 'identity',
|
|
'title': _('Identität'),
|
|
'subtitle': _('Titel, Firmenname und zentrale Spracheinstellungen.'),
|
|
'fields': ['portal_title', 'company_name', 'company_domain', 'default_language', 'login_subtitle'],
|
|
'field_full': {'login_subtitle'},
|
|
'hint_map': {
|
|
'company_domain': _('Wird für E-Mail-Vorschläge und Domain-bezogene Standardtexte verwendet, z. B. workdock.de.'),
|
|
},
|
|
},
|
|
{
|
|
'key': 'appearance',
|
|
'title': _('Farben & Erscheinungsbild'),
|
|
'subtitle': _('Zentrale visuelle Markenwerte und Browser-Icon.'),
|
|
'fields': ['primary_color', 'secondary_color', 'logo_image', 'favicon_image'],
|
|
'field_full': set(),
|
|
'hint_map': {
|
|
'logo_image': _('Erlaubte Formate: SVG, PNG, JPG, JPEG, WEBP. Maximal 5 MB.'),
|
|
'favicon_image': _('Erlaubte Formate: ICO, PNG, SVG, WEBP. Maximal 2 MB.'),
|
|
},
|
|
},
|
|
{
|
|
'key': 'communication',
|
|
'title': _('Kommunikation'),
|
|
'subtitle': _('Absender, Support und PDF-Branding für ausgehende Kommunikation.'),
|
|
'fields': ['support_email', 'sender_display_name', 'pdf_letterhead'],
|
|
'field_full': {'pdf_letterhead'},
|
|
'hint_map': {
|
|
'sender_display_name': _('Wird für ausgehende System-E-Mails als Anzeigename verwendet.'),
|
|
'pdf_letterhead': _('Erlaubtes Format: PDF. Maximal 10 MB.'),
|
|
},
|
|
},
|
|
{
|
|
'key': 'legal',
|
|
'title': _('Footer & Rechtliches'),
|
|
'subtitle': _('Gemeinsame Footer-Texte und rechtliche Hinweise für die Shell.'),
|
|
'fields': ['footer_text', 'legal_notice', 'footer_text_en', 'legal_notice_en'],
|
|
'field_full': {'legal_notice', 'legal_notice_en'},
|
|
'hint_map': {},
|
|
},
|
|
]
|
|
for section in sections:
|
|
rows = []
|
|
for field_name in section['fields']:
|
|
field = form[field_name]
|
|
value = getattr(branding, field_name, '') or ''
|
|
is_file = bool(getattr(field.field.widget, 'input_type', '') == 'file')
|
|
rows.append(
|
|
{
|
|
'name': field_name,
|
|
'bound_field': field,
|
|
'label': field.label,
|
|
'value': value,
|
|
'is_file': is_file,
|
|
'is_full': field_name in section.get('field_full', set()),
|
|
'hint': section.get('hint_map', {}).get(field_name, ''),
|
|
}
|
|
)
|
|
section['rows'] = rows
|
|
return sections
|
|
|
|
|
|
@_require_capability('manage_company_config')
|
|
def portal_company_config_page(request):
|
|
return admin_config_views.portal_company_config_page_impl(request, build_company_config_sections_fn=_build_company_config_sections)
|
|
|
|
|
|
@_require_capability('manage_company_config')
|
|
@require_POST
|
|
def save_portal_company_config(request):
|
|
return admin_config_views.save_portal_company_config_impl(request, audit_fn=_audit, build_company_config_sections_fn=_build_company_config_sections)
|
|
|
|
|
|
def _build_company_config_sections(form, company_config):
|
|
sections = [
|
|
{
|
|
'key': 'profile',
|
|
'title': _('Firmenprofil'),
|
|
'subtitle': _('Rechtlicher Name und zentrale Stammdaten der Firma.'),
|
|
'fields': ['legal_company_name', 'phone_number', 'website_url', 'country'],
|
|
},
|
|
{
|
|
'key': 'address',
|
|
'title': _('Adresse & Register'),
|
|
'subtitle': _('Anschrift sowie optionale Register- und Steuerangaben.'),
|
|
'fields': ['street_address', 'postal_code', 'city', 'registration_number', 'vat_id'],
|
|
},
|
|
{
|
|
'key': 'contacts',
|
|
'title': _('Kontaktpunkte'),
|
|
'subtitle': _('Zentrale Ansprechpartner für HR, IT und Operations.'),
|
|
'fields': ['hr_contact_email', 'it_contact_email', 'operations_contact_email'],
|
|
},
|
|
{
|
|
'key': 'public',
|
|
'title': _('Recht & Öffentlichkeit'),
|
|
'subtitle': _('Öffentliche Links für Website, Impressum und Datenschutz.'),
|
|
'fields': ['imprint_url', 'privacy_url'],
|
|
'hint': _('Diese Links können später im Portal-Footer oder in öffentlichen Seiten verwendet werden.'),
|
|
},
|
|
]
|
|
for section in sections:
|
|
rows = []
|
|
for field_name in section['fields']:
|
|
field = form[field_name]
|
|
rows.append(
|
|
{
|
|
'name': field_name,
|
|
'bound_field': field,
|
|
'label': field.label,
|
|
'value': getattr(company_config, field_name, '') or '',
|
|
}
|
|
)
|
|
section['rows'] = rows
|
|
return sections
|
|
|
|
|
|
@_require_capability('manage_trial_lifecycle')
|
|
def portal_trial_config_page(request):
|
|
return admin_config_views.portal_trial_config_page_impl(request)
|
|
|
|
|
|
@_require_capability('manage_trial_lifecycle')
|
|
@require_POST
|
|
def save_portal_trial_config(request):
|
|
return admin_config_views.save_portal_trial_config_impl(request, audit_fn=_audit)
|
|
|
|
|
|
@_require_capability('manage_users')
|
|
@require_POST
|
|
def create_user_from_admin(request):
|
|
return admin_config_views.create_user_from_admin_impl(
|
|
request,
|
|
render_user_management_fn=_render_user_management,
|
|
send_user_access_email_fn=_send_user_access_email,
|
|
audit_fn=_audit,
|
|
display_user_name_fn=_display_user_name,
|
|
)
|
|
|
|
|
|
@_require_capability('manage_users')
|
|
@require_POST
|
|
def update_user_from_admin(request, user_id: int):
|
|
return admin_config_views.update_user_from_admin_impl(
|
|
request,
|
|
user_id,
|
|
would_remove_last_platform_owner_fn=_would_remove_last_platform_owner,
|
|
would_remove_last_super_admin_fn=_would_remove_last_super_admin,
|
|
audit_fn=_audit,
|
|
display_user_name_fn=_display_user_name,
|
|
)
|
|
|
|
|
|
@_require_capability('manage_users')
|
|
@require_POST
|
|
def send_password_reset_from_admin(request, user_id: int):
|
|
return admin_config_views.send_password_reset_from_admin_impl(
|
|
request,
|
|
user_id,
|
|
send_user_access_email_fn=_send_user_access_email,
|
|
audit_fn=_audit,
|
|
display_user_name_fn=_display_user_name,
|
|
)
|
|
|
|
|
|
@_require_capability('manage_users')
|
|
@require_POST
|
|
def delete_user_from_admin(request, user_id: int):
|
|
return admin_config_views.delete_user_from_admin_impl(
|
|
request,
|
|
user_id,
|
|
would_remove_last_platform_owner_fn=_would_remove_last_platform_owner,
|
|
would_remove_last_super_admin_fn=_would_remove_last_super_admin,
|
|
audit_fn=_audit,
|
|
display_user_name_fn=_display_user_name,
|
|
)
|
|
|
|
|
|
@_require_capability('view_docs')
|
|
def handbook_page(request):
|
|
return admin_config_views.handbook_page_impl(request)
|
|
|
|
|
|
@_require_capability('view_docs')
|
|
def project_wiki_page(request):
|
|
return admin_config_views.project_wiki_page_impl(request)
|
|
|
|
|
|
@_require_capability('view_docs')
|
|
def developer_handbook_page(request):
|
|
return admin_config_views.developer_handbook_page_impl(request)
|
|
|
|
|
|
@_require_capability('view_docs')
|
|
def release_checklist_page(request):
|
|
return admin_config_views.release_checklist_page_impl(request)
|
|
|
|
|
|
@_require_capability('view_audit_log')
|
|
def audit_log_page(request):
|
|
return audit_log_page_impl(request)
|
|
|
|
|
|
@_require_capability('manage_backups')
|
|
def backup_recovery_page(request):
|
|
return backup_recovery_page_impl(request)
|
|
|
|
|
|
@_require_capability('manage_backups')
|
|
@require_POST
|
|
def create_backup_from_admin(request):
|
|
return create_backup_from_admin_impl(
|
|
request,
|
|
audit_fn=_audit,
|
|
notify_user_fn=notify_user,
|
|
create_backup_bundle_fn=create_backup_bundle,
|
|
)
|
|
|
|
|
|
@_require_capability('manage_backups')
|
|
@require_POST
|
|
def verify_backup_from_admin(request, backup_name: str):
|
|
return verify_backup_from_admin_impl(
|
|
request,
|
|
backup_name,
|
|
audit_fn=_audit,
|
|
notify_user_fn=notify_user,
|
|
verify_backup_bundle_fn=verify_backup_bundle,
|
|
)
|
|
|
|
|
|
@_require_capability('manage_backups')
|
|
@require_POST
|
|
def delete_backup_from_admin(request, backup_name: str):
|
|
try:
|
|
result = delete_backup_bundle(backup_name)
|
|
_audit(
|
|
request,
|
|
'backup_deleted',
|
|
target_type='backup_bundle',
|
|
target_label=backup_name,
|
|
details={},
|
|
)
|
|
messages.success(request, _('Backup wurde gelöscht: %(name)s') % {'name': result['name']})
|
|
except Exception as exc:
|
|
messages.error(request, _('Backup konnte nicht gelöscht werden: %(error)s') % {'error': exc})
|
|
return redirect('backup_recovery_page')
|
|
|
|
|
|
@_require_capability('view_request_timeline')
|
|
def request_timeline_page(request, kind: str, request_id: int):
|
|
return request_views.request_timeline_page_impl(
|
|
request,
|
|
kind,
|
|
request_id,
|
|
request_target_label_fn=_request_target_label,
|
|
request_custom_field_details_fn=_request_custom_field_details,
|
|
audit_action_label_fn=_audit_action_label,
|
|
)
|
|
|
|
|
|
@login_required
|
|
def requests_dashboard(request):
|
|
return request_views.requests_dashboard_impl(
|
|
request,
|
|
audit_fn=_audit,
|
|
request_target_label_fn=_request_target_label,
|
|
request_status_label_fn=_request_status_label,
|
|
)
|
|
|
|
|
|
@login_required
|
|
@ensure_csrf_cookie
|
|
def onboarding_create(request):
|
|
return request_views.onboarding_create_impl(
|
|
request,
|
|
build_onboarding_layout_fn=_build_onboarding_layout,
|
|
build_onboarding_sections_fn=_build_onboarding_sections,
|
|
normalized_conditional_rule_payload_fn=_normalized_conditional_rule_payload,
|
|
display_user_name_fn=_display_user_name,
|
|
onboarding_inline_checks=ONBOARDING_INLINE_CHECKS,
|
|
onboarding_checkbox_lists=ONBOARDING_CHECKBOX_LISTS,
|
|
)
|
|
|
|
|
|
@login_required
|
|
def onboarding_success(request, request_id: int):
|
|
return request_views.onboarding_success_impl(request, request_id)
|
|
|
|
|
|
@_require_capability('generate_intro_pdfs')
|
|
@require_POST
|
|
def generate_onboarding_intro_pdf(request, request_id: int):
|
|
return request_views.generate_onboarding_intro_pdf_impl(request, request_id, audit_fn=_audit)
|
|
|
|
|
|
@_require_capability('generate_intro_pdfs')
|
|
@require_POST
|
|
def generate_onboarding_intro_session_pdf(request, request_id: int):
|
|
return request_views.generate_onboarding_intro_session_pdf_impl(request, request_id, audit_fn=_audit, display_user_name_fn=_display_user_name)
|
|
|
|
|
|
@_require_capability('run_intro_session')
|
|
def onboarding_intro_session_page(request, request_id: int):
|
|
return request_views.onboarding_intro_session_page_impl(request, request_id, audit_fn=_audit, display_user_name_fn=_display_user_name)
|
|
|
|
|
|
@login_required
|
|
@ensure_csrf_cookie
|
|
def offboarding_create(request):
|
|
return request_views.offboarding_create_impl(
|
|
request,
|
|
build_offboarding_sections_fn=_build_offboarding_sections,
|
|
display_user_name_fn=_display_user_name,
|
|
)
|
|
|
|
|
|
@login_required
|
|
def offboarding_success(request, request_id: int):
|
|
return request_views.offboarding_success_impl(request, request_id)
|
|
|
|
|
|
@_require_capability('manage_builders')
|
|
def form_builder_page(request):
|
|
return form_builder_page_impl(
|
|
request,
|
|
audit_fn=_audit,
|
|
translate_choice_list=_translate_choice_list,
|
|
form_field_labels_fn=_form_field_labels,
|
|
field_rule_summary_fn=_field_rule_summary,
|
|
conditional_rule_summary_fn=_conditional_rule_summary,
|
|
onboarding_groups=ONBOARDING_GROUPS,
|
|
conditional_rule_operator_choices=CONDITIONAL_RULE_OPERATOR_CHOICES,
|
|
)
|
|
|
|
|
|
@_require_capability('manage_builders')
|
|
def intro_builder_page(request):
|
|
return intro_builder_page_impl(
|
|
request,
|
|
audit_fn=_audit,
|
|
translate_choice_list=_translate_choice_list,
|
|
)
|
|
|
|
|
|
@_require_capability('manage_integrations')
|
|
def integrations_setup_page(request):
|
|
return integrations_views.integrations_setup_page_impl(request)
|
|
|
|
|
|
@_require_capability('manage_welcome_emails')
|
|
def welcome_emails_page(request):
|
|
return integrations_views.welcome_emails_page_impl(request)
|
|
|
|
|
|
@_require_capability('manage_welcome_emails')
|
|
@require_POST
|
|
def trigger_welcome_email_now(request, schedule_id: int):
|
|
return integrations_views.trigger_welcome_email_now_impl(request, schedule_id, audit_fn=_audit)
|
|
|
|
|
|
@_require_capability('manage_welcome_emails')
|
|
@require_POST
|
|
def save_welcome_email_settings(request):
|
|
return integrations_views.save_welcome_email_settings_impl(request, audit_fn=_audit)
|
|
|
|
|
|
def _revoke_celery_task(task_id: str) -> None:
|
|
if not task_id:
|
|
return
|
|
try:
|
|
current_app.control.revoke(task_id, terminate=False)
|
|
except Exception:
|
|
return
|
|
|
|
|
|
def _parse_selected_schedule_ids(raw: str) -> list[int]:
|
|
if not raw:
|
|
return []
|
|
parsed: list[int] = []
|
|
seen: set[int] = set()
|
|
for token in raw.split(','):
|
|
token = token.strip()
|
|
if not token:
|
|
continue
|
|
try:
|
|
schedule_id = int(token)
|
|
except ValueError:
|
|
continue
|
|
if schedule_id in seen:
|
|
continue
|
|
seen.add(schedule_id)
|
|
parsed.append(schedule_id)
|
|
return parsed
|
|
|
|
|
|
@_require_capability('manage_welcome_emails')
|
|
@require_POST
|
|
def bulk_welcome_email_action(request):
|
|
return integrations_views.bulk_welcome_email_action_impl(request, audit_fn=_audit)
|
|
|
|
|
|
@_require_capability('manage_welcome_emails')
|
|
@require_POST
|
|
def pause_welcome_email(request, schedule_id: int):
|
|
return integrations_views.pause_welcome_email_impl(request, schedule_id, audit_fn=_audit)
|
|
|
|
|
|
@_require_capability('manage_welcome_emails')
|
|
@require_POST
|
|
def resume_welcome_email(request, schedule_id: int):
|
|
return integrations_views.resume_welcome_email_impl(request, schedule_id, audit_fn=_audit)
|
|
|
|
|
|
@_require_capability('manage_welcome_emails')
|
|
@require_POST
|
|
def cancel_welcome_email(request, schedule_id: int):
|
|
return integrations_views.cancel_welcome_email_impl(request, schedule_id, audit_fn=_audit)
|
|
|
|
|
|
@_require_capability('manage_builders')
|
|
@require_POST
|
|
def form_builder_save_order(request):
|
|
return integrations_views.form_builder_save_order_impl(request, audit_fn=_audit)
|
|
|
|
|
|
@_require_capability('manage_integrations')
|
|
@require_POST
|
|
def send_test_email(request):
|
|
return integrations_views.send_test_email_impl(request, audit_fn=_audit, redirect_back_fn=_redirect_back)
|
|
|
|
|
|
@_require_capability('manage_integrations')
|
|
@require_POST
|
|
def nextcloud_test_upload(request):
|
|
return integrations_views.nextcloud_test_upload_impl(request, audit_fn=_audit, redirect_back_fn=_redirect_back)
|
|
|
|
|
|
@_require_capability('manage_integrations')
|
|
@require_POST
|
|
def toggle_nextcloud_enabled(request):
|
|
return integrations_views.toggle_nextcloud_enabled_impl(request, audit_fn=_audit, redirect_back_fn=_redirect_back)
|
|
|
|
|
|
@_require_capability('manage_integrations')
|
|
@require_POST
|
|
def toggle_email_mode(request):
|
|
return integrations_views.toggle_email_mode_impl(request, audit_fn=_audit, redirect_back_fn=_redirect_back)
|
|
|
|
|
|
@_require_capability('manage_integrations')
|
|
@require_POST
|
|
def save_integrations_settings(request):
|
|
return integrations_views.save_integrations_settings_impl(request, audit_fn=_audit)
|
|
|
|
|
|
@_require_capability('manage_integrations')
|
|
@require_POST
|
|
def save_nextcloud_settings(request):
|
|
return integrations_views.save_nextcloud_settings_impl(request, audit_fn=_audit)
|
|
|
|
|
|
@_require_capability('manage_integrations')
|
|
@require_POST
|
|
def save_workflow_rules(request):
|
|
return integrations_views.save_workflow_rules_impl(request, audit_fn=_audit)
|
|
|
|
|
|
@_require_capability('manage_integrations')
|
|
@require_POST
|
|
def save_backup_settings(request):
|
|
return integrations_views.save_backup_settings_impl(request, audit_fn=_audit)
|
|
|
|
|
|
@_require_capability('manage_integrations')
|
|
@require_POST
|
|
def save_mail_settings(request):
|
|
return integrations_views.save_mail_settings_impl(request, audit_fn=_audit)
|
|
|
|
|
|
@_require_capability('manage_integrations')
|
|
@require_POST
|
|
def save_email_routing_settings(request):
|
|
return integrations_views.save_email_routing_settings_impl(request, audit_fn=_audit)
|
|
|
|
|
|
@_require_capability('manage_integrations')
|
|
@require_POST
|
|
def save_notification_rules(request):
|
|
return integrations_views.save_notification_rules_impl(request, audit_fn=_audit)
|
|
|
|
|
|
@_require_capability('delete_requests')
|
|
@require_POST
|
|
def delete_request_from_dashboard(request, kind: str, request_id: int):
|
|
return request_views.delete_request_from_dashboard_impl(request, kind, request_id, audit_fn=_audit, request_target_label_fn=_request_target_label)
|
|
|
|
|
|
@_require_capability('retry_requests')
|
|
@require_POST
|
|
def retry_request_from_dashboard(request, kind: str, request_id: int):
|
|
return request_views.retry_request_from_dashboard_impl(request, kind, request_id, audit_fn=_audit, request_target_label_fn=_request_target_label)
|