diff --git a/PRODUCTIZATION_ROADMAP.md b/PRODUCTIZATION_ROADMAP.md index c05a29e..cb4480a 100644 --- a/PRODUCTIZATION_ROADMAP.md +++ b/PRODUCTIZATION_ROADMAP.md @@ -16,6 +16,7 @@ Current branch roles: 3. Start as single-tenant configurable, not full multi-tenant. 4. Make branding and document identity admin-managed, not code-managed. 5. Add new business apps only after the core platform layer is standardized. +6. Prefer inline editing for lightweight profile and configuration data, but keep explicit forms for sensitive or high-risk settings. ## Product Layers @@ -213,3 +214,35 @@ This is the first productization slice because it gives: - keep migrations backward-compatible - update both wiki and developer handbook for every architecture change - snapshot at the end of each major phase + +## Shared UI Pattern: Inline Editing + +Use inline editing as a platform pattern where it improves speed without weakening clarity or safety. + +Good candidates: + +- user profile and contact data +- company config sections +- branding text and non-sensitive metadata +- low-risk app-registry metadata + +Do not use it by default for: + +- credentials and secrets +- integrations with side effects +- destructive actions +- multi-step workflow forms +- settings that need heavy validation or confirmation + +Preferred implementation style: + +- section-level inline editing +- explicit `Bearbeiten`, `Speichern`, `Abbrechen` +- no noisy per-field autosave +- clear view mode and edit mode separation + +Reason: + +- keeps Workdock faster and more product-grade +- avoids large admin-style forms for simple edits +- still preserves reliable validation and safer change boundaries diff --git a/backend/locale/en/LC_MESSAGES/django.mo b/backend/locale/en/LC_MESSAGES/django.mo index b04c453..612fd69 100644 Binary files a/backend/locale/en/LC_MESSAGES/django.mo and b/backend/locale/en/LC_MESSAGES/django.mo differ diff --git a/backend/locale/en/LC_MESSAGES/django.po b/backend/locale/en/LC_MESSAGES/django.po index a94e56c..99412a3 100644 --- a/backend/locale/en/LC_MESSAGES/django.po +++ b/backend/locale/en/LC_MESSAGES/django.po @@ -2,14 +2,14 @@ msgid "" msgstr "" "Project-Id-Version: tubco-portal\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-27 01:04+0000\n" +"POT-Creation-Date: 2026-03-27 01:45+0000\n" "PO-Revision-Date: 2026-03-24 00:00+0000\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: workflows/app_registry.py:35 workflows/models.py:373 workflows/models.py:454 +#: workflows/app_registry.py:35 workflows/models.py:389 workflows/models.py:470 #: workflows/templates/workflows/onboarding_form.html:25 #: workflows/templates/workflows/requests_dashboard.html:68 #: workflows/templates/workflows/requests_dashboard.html:131 @@ -36,7 +36,7 @@ msgstr "Multi-step form" msgid "E-Mail Routing" msgstr "Email routing" -#: workflows/app_registry.py:46 workflows/models.py:374 workflows/models.py:455 +#: workflows/app_registry.py:46 workflows/models.py:390 workflows/models.py:471 #: workflows/templates/workflows/requests_dashboard.html:78 #: workflows/templates/workflows/requests_dashboard.html:132 msgid "Offboarding" @@ -85,12 +85,13 @@ msgid "Dashboard öffnen" msgstr "Open dashboard" #: workflows/app_registry.py:62 -#: workflows/templates/workflows/app_registry.html:27 +#: workflows/templates/workflows/app_registry.html:28 msgid "Suche" msgstr "Search" #: workflows/app_registry.py:62 -#: workflows/templates/workflows/app_registry.html:31 +#: workflows/templates/workflows/account_profile.html:218 +#: workflows/templates/workflows/app_registry.html:32 #: workflows/templates/workflows/backup_recovery.html:72 #: workflows/templates/workflows/job_monitor.html:29 #: workflows/templates/workflows/job_monitor.html:50 @@ -151,8 +152,8 @@ msgid "Logo, Portalname, Farben und PDF-Briefkopf verwalten." msgstr "Manage logo, portal name, colors, and PDF letterhead." #: workflows/app_registry.py:95 -#: workflows/templates/workflows/app_registry.html:5 -#: workflows/templates/workflows/app_registry.html:13 +#: workflows/templates/workflows/app_registry.html:6 +#: workflows/templates/workflows/app_registry.html:14 msgid "App Registry" msgstr "" @@ -258,13 +259,13 @@ msgid "Nur Platform" msgstr "" #: workflows/app_registry.py:311 -#: workflows/templates/workflows/app_registry.html:85 +#: workflows/templates/workflows/app_registry.html:107 msgid "Alle Firmenrollen" msgstr "" -#: workflows/app_registry.py:317 workflows/models.py:150 -#: workflows/templates/workflows/app_registry.html:43 -#: workflows/templates/workflows/app_registry.html:78 +#: workflows/app_registry.py:317 workflows/models.py:166 +#: workflows/templates/workflows/app_registry.html:44 +#: workflows/templates/workflows/app_registry.html:100 msgid "Apps" msgstr "Apps" @@ -272,9 +273,9 @@ msgstr "Apps" msgid "Wählen Sie den gewünschten Prozess." msgstr "Choose the desired process." -#: workflows/app_registry.py:323 workflows/models.py:151 -#: workflows/templates/workflows/app_registry.html:44 -#: workflows/templates/workflows/app_registry.html:74 +#: workflows/app_registry.py:323 workflows/models.py:167 +#: workflows/templates/workflows/app_registry.html:45 +#: workflows/templates/workflows/app_registry.html:96 msgid "Platform Apps" msgstr "" @@ -284,9 +285,9 @@ msgstr "" msgid "Produktweite Konfiguration und Produktsteuerung." msgstr "Configuration, tests, and controls." -#: workflows/app_registry.py:329 workflows/models.py:152 -#: workflows/templates/workflows/app_registry.html:45 -#: workflows/templates/workflows/app_registry.html:76 +#: workflows/app_registry.py:329 workflows/models.py:168 +#: workflows/templates/workflows/app_registry.html:46 +#: workflows/templates/workflows/app_registry.html:98 msgid "Admin Apps" msgstr "Admin Apps" @@ -383,76 +384,88 @@ msgstr "" msgid "Remote Backup in Nextcloud konnte nicht gelöscht werden." msgstr "" -#: workflows/forms.py:104 workflows/forms.py:227 +#: workflows/forms.py:106 workflows/forms.py:326 #: workflows/templates/workflows/account_profile.html:66 #: workflows/templates/workflows/user_management.html:72 #: workflows/templates/workflows/user_management.html:170 msgid "Benutzername" msgstr "" -#: workflows/forms.py:105 +#: workflows/forms.py:107 msgid "Passwort" msgstr "Password" -#: workflows/forms.py:109 workflows/forms.py:173 workflows/forms.py:228 +#: workflows/forms.py:109 workflows/forms.py:265 workflows/forms.py:297 +msgid "TOTP-Code" +msgstr "" + +#: workflows/forms.py:117 workflows/forms.py:286 workflows/forms.py:319 +msgid "Der TOTP-Code ist ungültig." +msgstr "" + +#: workflows/forms.py:118 +msgid "Bitte geben Sie Ihren TOTP-Code ein." +msgstr "" + +#: workflows/forms.py:143 workflows/forms.py:207 workflows/forms.py:327 #, fuzzy #| msgid "E-Mail" msgid "E-Mail-Adresse" msgstr "Email" -#: workflows/forms.py:114 workflows/forms.py:133 +#: workflows/forms.py:148 workflows/forms.py:167 #: workflows/templates/workflows/user_management.html:77 #: workflows/templates/workflows/user_management.html:108 msgid "Neues Passwort" msgstr "New password" -#: workflows/forms.py:120 workflows/forms.py:139 +#: workflows/forms.py:154 workflows/forms.py:173 msgid "Neues Passwort bestätigen" msgstr "Confirm new password" -#: workflows/forms.py:128 +#: workflows/forms.py:162 workflows/forms.py:260 workflows/forms.py:292 #, fuzzy #| msgid "Neues Passwort" msgid "Aktuelles Passwort" msgstr "New password" -#: workflows/forms.py:150 workflows/templates/workflows/account_profile.html:36 +#: workflows/forms.py:184 workflows/templates/workflows/account_profile.html:36 #: workflows/templates/workflows/includes/app_header.html:27 msgid "Profilbild" msgstr "" -#: workflows/forms.py:166 +#: workflows/forms.py:200 msgid "Das Profilbild darf maximal 5 MB groß sein." msgstr "" -#: workflows/forms.py:171 workflows/forms.py:225 +#: workflows/forms.py:205 workflows/forms.py:324 #: workflows/templates/workflows/account_profile.html:112 msgid "Vorname" msgstr "" -#: workflows/forms.py:172 workflows/forms.py:226 +#: workflows/forms.py:206 workflows/forms.py:325 #: workflows/templates/workflows/account_profile.html:116 msgid "Nachname" msgstr "" -#: workflows/forms.py:174 +#: workflows/forms.py:208 #: workflows/templates/workflows/account_profile.html:120 msgid "Telefon" msgstr "" -#: workflows/forms.py:175 +#: workflows/forms.py:209 #: workflows/templates/workflows/account_profile.html:124 msgid "Mobil" msgstr "" -#: workflows/forms.py:176 workflows/templates/workflows/account_profile.html:70 +#: workflows/forms.py:210 workflows/templates/workflows/account_profile.html:70 #: workflows/templates/workflows/account_profile.html:128 #, fuzzy #| msgid "Produktion" msgid "Position" msgstr "Production" -#: workflows/forms.py:177 workflows/models.py:334 +#: workflows/forms.py:211 workflows/models.py:350 #: workflows/templates/workflows/account_profile.html:74 #: workflows/templates/workflows/account_profile.html:132 #: workflows/templates/workflows/onboarding_intro_session.html:28 @@ -460,19 +473,27 @@ msgstr "Production" msgid "Abteilung" msgstr "Department" -#: workflows/forms.py:178 +#: workflows/forms.py:212 #: workflows/templates/workflows/account_profile.html:136 msgid "Standort" msgstr "" -#: workflows/forms.py:180 +#: workflows/forms.py:214 #: workflows/templates/workflows/account_profile.html:140 #, fuzzy #| msgid "Einweisung" msgid "Hinweise" msgstr "Introduction" -#: workflows/forms.py:229 workflows/templates/workflows/account_profile.html:62 +#: workflows/forms.py:278 workflows/forms.py:310 +msgid "Das aktuelle Passwort ist nicht korrekt." +msgstr "" + +#: workflows/forms.py:284 workflows/forms.py:316 +msgid "Bitte geben Sie einen gültigen TOTP-Code ein." +msgstr "" + +#: workflows/forms.py:328 workflows/templates/workflows/account_profile.html:62 #: workflows/templates/workflows/user_management.html:74 #: workflows/templates/workflows/user_management.html:93 #: workflows/templates/workflows/user_management.html:171 @@ -481,205 +502,207 @@ msgstr "Introduction" msgid "Rolle" msgstr "Role:" -#: workflows/forms.py:243 +#: workflows/forms.py:342 msgid "Dieser Benutzername ist bereits vergeben." msgstr "This username is already taken." -#: workflows/forms.py:252 workflows/views.py:807 +#: workflows/forms.py:351 workflows/views.py:987 msgid "Ungültige Rolle." msgstr "Invalid role." -#: workflows/forms.py:254 workflows/views.py:810 +#: workflows/forms.py:353 workflows/views.py:990 msgid "Nur Platform Owner dürfen diese Rolle vergeben." msgstr "" -#: workflows/forms.py:293 +#: workflows/forms.py:392 msgid "Portal-Titel" msgstr "Portal title" -#: workflows/forms.py:294 +#: workflows/forms.py:393 msgid "Firmenname" msgstr "Company name" -#: workflows/forms.py:295 +#: workflows/forms.py:394 #, fuzzy #| msgid "Firmenname" msgid "Firmen-Domain" msgstr "Company name" -#: workflows/forms.py:296 +#: workflows/forms.py:395 msgid "Support-E-Mail" msgstr "Support email" -#: workflows/forms.py:297 +#: workflows/forms.py:396 msgid "Absender-Anzeigename" msgstr "" -#: workflows/forms.py:298 +#: workflows/forms.py:397 msgid "Login-Untertitel" msgstr "" -#: workflows/forms.py:299 +#: workflows/forms.py:398 msgid "Footer-Text DE" msgstr "" -#: workflows/forms.py:300 +#: workflows/forms.py:399 msgid "Footer-Text EN" msgstr "" -#: workflows/forms.py:301 +#: workflows/forms.py:400 msgid "Rechtlicher Hinweis DE" msgstr "" -#: workflows/forms.py:302 +#: workflows/forms.py:401 msgid "Rechtlicher Hinweis EN" msgstr "" -#: workflows/forms.py:303 +#: workflows/forms.py:402 msgid "Standardsprache" msgstr "Default language" -#: workflows/forms.py:304 +#: workflows/forms.py:403 msgid "Logo" msgstr "Logo" -#: workflows/forms.py:305 +#: workflows/forms.py:404 msgid "PDF-Briefkopf" msgstr "PDF letterhead" -#: workflows/forms.py:306 +#: workflows/forms.py:405 msgid "Favicon" msgstr "" -#: workflows/forms.py:307 -#: workflows/templates/workflows/branding_settings.html:94 +#: workflows/forms.py:406 +#: workflows/templates/workflows/branding_settings.html:89 +#: workflows/templates/workflows/branding_settings.html:162 msgid "Primärfarbe" msgstr "Primary color" -#: workflows/forms.py:308 -#: workflows/templates/workflows/branding_settings.html:95 +#: workflows/forms.py:407 +#: workflows/templates/workflows/branding_settings.html:90 +#: workflows/templates/workflows/branding_settings.html:163 msgid "Sekundärfarbe" msgstr "Secondary color" -#: workflows/forms.py:325 +#: workflows/forms.py:424 msgid "Das Logo darf maximal 5 MB groß sein." msgstr "" -#: workflows/forms.py:333 +#: workflows/forms.py:432 msgid "Der PDF-Briefkopf darf maximal 10 MB groß sein." msgstr "" -#: workflows/forms.py:341 +#: workflows/forms.py:440 msgid "Das Favicon darf maximal 2 MB groß sein." msgstr "" -#: workflows/forms.py:365 +#: workflows/forms.py:464 #, fuzzy #| msgid "Firmenname" msgid "Rechtlicher Firmenname" msgstr "Company name" -#: workflows/forms.py:366 +#: workflows/forms.py:465 msgid "Straße und Hausnummer" msgstr "" -#: workflows/forms.py:367 +#: workflows/forms.py:466 msgid "Postleitzahl" msgstr "" -#: workflows/forms.py:368 +#: workflows/forms.py:467 msgid "Stadt" msgstr "" -#: workflows/forms.py:369 +#: workflows/forms.py:468 msgid "Land" msgstr "" -#: workflows/forms.py:370 workflows/templates/workflows/base_shell.html:64 +#: workflows/forms.py:469 workflows/templates/workflows/base_shell.html:64 msgid "Website" msgstr "" -#: workflows/forms.py:371 +#: workflows/forms.py:470 msgid "Impressum-URL" msgstr "" -#: workflows/forms.py:372 +#: workflows/forms.py:471 msgid "Datenschutz-URL" msgstr "" -#: workflows/forms.py:373 +#: workflows/forms.py:472 msgid "HR-Kontakt" msgstr "" -#: workflows/forms.py:374 +#: workflows/forms.py:473 msgid "IT-Kontakt" msgstr "" -#: workflows/forms.py:375 +#: workflows/forms.py:474 #, fuzzy #| msgid "Operations" msgid "Operations-Kontakt" msgstr "Operations" -#: workflows/forms.py:376 +#: workflows/forms.py:475 msgid "Zentrale Telefonnummer" msgstr "" -#: workflows/forms.py:377 +#: workflows/forms.py:476 msgid "USt-IdNr." msgstr "" -#: workflows/forms.py:378 +#: workflows/forms.py:477 msgid "Register- oder Handelsnummer" msgstr "" -#: workflows/forms.py:395 +#: workflows/forms.py:494 msgid "Trial-Modus aktiv" msgstr "" -#: workflows/forms.py:396 +#: workflows/forms.py:495 msgid "Trial-Beginn" msgstr "" -#: workflows/forms.py:397 +#: workflows/forms.py:496 msgid "Trial-Ende" msgstr "" -#: workflows/forms.py:398 +#: workflows/forms.py:497 msgid "Produktive Integrationen begrenzen" msgstr "" -#: workflows/forms.py:399 +#: workflows/forms.py:498 msgid "Cleanup nach Ablauf zulassen" msgstr "" -#: workflows/forms.py:400 +#: workflows/forms.py:499 msgid "Banner-Text DE" msgstr "" -#: workflows/forms.py:401 +#: workflows/forms.py:500 msgid "Banner-Text EN" msgstr "" -#: workflows/forms.py:421 +#: workflows/forms.py:520 msgid "Bitte ein Trial-Ende festlegen." msgstr "" -#: workflows/forms.py:423 +#: workflows/forms.py:522 msgid "Das Trial-Ende muss nach dem Trial-Beginn liegen." msgstr "" -#: workflows/forms.py:562 workflows/forms.py:747 +#: workflows/forms.py:661 workflows/forms.py:846 #, python-format msgid "Bitte nutzen Sie das Format name@%(domain)s." msgstr "" -#: workflows/forms.py:584 workflows/forms.py:761 +#: workflows/forms.py:683 workflows/forms.py:860 #, python-format msgid "Bitte verwenden Sie eine @%(domain)s E-Mail-Adresse." msgstr "" -#: workflows/forms.py:669 +#: workflows/forms.py:768 #, python-format msgid "" "Das Übergabedatum muss mindestens %(days)s Tage in der Zukunft liegen " @@ -731,251 +754,251 @@ msgid "" "ausführen." msgstr "" -#: workflows/models.py:199 workflows/views.py:420 +#: workflows/models.py:215 workflows/views.py:460 #, fuzzy #| msgid "Gesamtbestand" msgid "Gestartet" msgstr "Total records" -#: workflows/models.py:200 workflows/views.py:420 +#: workflows/models.py:216 workflows/views.py:460 #, fuzzy #| msgid "Eingereicht" msgid "Erfolgreich" msgstr "Submitted" -#: workflows/models.py:201 workflows/models.py:254 workflows/models.py:508 +#: workflows/models.py:217 workflows/models.py:270 workflows/models.py:524 #: workflows/templates/workflows/backup_recovery.html:102 #: workflows/templates/workflows/requests_dashboard.html:222 -#: workflows/templates/workflows/welcome_emails.html:108 workflows/views.py:246 -#: workflows/views.py:420 +#: workflows/templates/workflows/welcome_emails.html:108 workflows/views.py:286 +#: workflows/views.py:460 msgid "Fehlgeschlagen" msgstr "Failed" -#: workflows/models.py:251 workflows/views.py:243 +#: workflows/models.py:267 workflows/views.py:283 msgid "Eingereicht" msgstr "Submitted" -#: workflows/models.py:252 workflows/views.py:244 +#: workflows/models.py:268 workflows/views.py:284 msgid "In Bearbeitung" msgstr "Processing" -#: workflows/models.py:253 workflows/models.py:568 workflows/views.py:245 +#: workflows/models.py:269 workflows/models.py:584 workflows/views.py:285 msgid "Abgeschlossen" msgstr "Completed" -#: workflows/models.py:261 +#: workflows/models.py:277 msgid "Herr" msgstr "" -#: workflows/models.py:261 +#: workflows/models.py:277 msgid "Frau" msgstr "" -#: workflows/models.py:261 +#: workflows/models.py:277 msgid "Divers" msgstr "" -#: workflows/models.py:271 +#: workflows/models.py:287 msgid "befristet" msgstr "" -#: workflows/models.py:271 +#: workflows/models.py:287 msgid "unbefristet" msgstr "" -#: workflows/models.py:335 +#: workflows/models.py:351 msgid "Geräte" msgstr "" -#: workflows/models.py:336 +#: workflows/models.py:352 msgid "Software" msgstr "" -#: workflows/models.py:337 +#: workflows/models.py:353 #, fuzzy #| msgid "Vorgänge" msgid "Zugänge" msgstr "Requests" -#: workflows/models.py:338 +#: workflows/models.py:354 msgid "Workspace-Gruppen" msgstr "" -#: workflows/models.py:339 +#: workflows/models.py:355 msgid "Ressourcen" msgstr "" -#: workflows/models.py:340 +#: workflows/models.py:356 msgid "Telefonnummern" msgstr "" -#: workflows/models.py:366 +#: workflows/models.py:382 msgid "Automatisch" msgstr "" -#: workflows/models.py:367 workflows/views.py:101 +#: workflows/models.py:383 workflows/views.py:102 msgid "Stammdaten" msgstr "Master data" -#: workflows/models.py:368 workflows/views.py:102 +#: workflows/models.py:384 workflows/views.py:103 msgid "Vertrag" msgstr "Contract" -#: workflows/models.py:369 workflows/views.py:103 +#: workflows/models.py:385 workflows/views.py:104 msgid "IT-Setup" msgstr "IT setup" -#: workflows/models.py:370 workflows/views.py:104 +#: workflows/models.py:386 workflows/views.py:105 msgid "Abschluss" msgstr "Finish" -#: workflows/models.py:412 +#: workflows/models.py:428 #, fuzzy #| msgid "Onboarding" msgid "Onboarding: IT" msgstr "Onboarding" -#: workflows/models.py:413 +#: workflows/models.py:429 #, fuzzy #| msgid "Offboarding-Anfrage speichern" msgid "Onboarding: Allgemeine Info" msgstr "Save offboarding request" -#: workflows/models.py:414 +#: workflows/models.py:430 #, fuzzy #| msgid "Onboarding starten" msgid "Onboarding: Visitenkarte" msgstr "Start onboarding" -#: workflows/models.py:415 +#: workflows/models.py:431 #, fuzzy #| msgid "Onboarding" msgid "Onboarding: HR Works" msgstr "Onboarding" -#: workflows/models.py:416 +#: workflows/models.py:432 #, fuzzy #| msgid "Onboarding starten" msgid "Onboarding: Schlüssel" msgstr "Start onboarding" -#: workflows/models.py:417 +#: workflows/models.py:433 msgid "Onboarding: Referenz Anfordernde Person" msgstr "" -#: workflows/models.py:418 +#: workflows/models.py:434 #, fuzzy #| msgid "Welcome E-Mails" msgid "Onboarding: Welcome E-Mail" msgstr "Welcome Emails" -#: workflows/models.py:419 +#: workflows/models.py:435 #, fuzzy #| msgid "Offboarding" msgid "Offboarding: IT" msgstr "Offboarding" -#: workflows/models.py:420 +#: workflows/models.py:436 #, fuzzy #| msgid "Offboarding-Anfrage speichern" msgid "Offboarding: Allgemeine Info" msgstr "Save offboarding request" -#: workflows/models.py:421 +#: workflows/models.py:437 #, fuzzy #| msgid "Offboarding starten" msgid "Offboarding: HR Works Deaktivierung" msgstr "Start offboarding" -#: workflows/models.py:422 +#: workflows/models.py:438 msgid "Offboarding: Referenz Anfordernde Person" msgstr "" -#: workflows/models.py:458 +#: workflows/models.py:474 msgid "Immer" msgstr "" -#: workflows/models.py:459 workflows/models.py:537 +#: workflows/models.py:475 workflows/models.py:553 msgid "Enthält" msgstr "" -#: workflows/models.py:460 workflows/models.py:538 +#: workflows/models.py:476 workflows/models.py:554 msgid "Ist gleich" msgstr "" -#: workflows/models.py:461 +#: workflows/models.py:477 msgid "Ist aktiv/Ja" msgstr "" -#: workflows/models.py:462 +#: workflows/models.py:478 #, fuzzy #| msgid "inaktiv" msgid "Ist inaktiv/Nein" msgstr "inactive" -#: workflows/models.py:504 +#: workflows/models.py:520 #: workflows/templates/workflows/welcome_emails.html:100 msgid "Geplant" msgstr "Scheduled" -#: workflows/models.py:505 +#: workflows/models.py:521 #: workflows/templates/workflows/welcome_emails.html:102 msgid "Pausiert" msgstr "Paused" -#: workflows/models.py:506 +#: workflows/models.py:522 #: workflows/templates/workflows/welcome_emails.html:104 msgid "Abgebrochen" msgstr "Cancelled" -#: workflows/models.py:507 +#: workflows/models.py:523 #: workflows/templates/workflows/welcome_emails.html:106 msgid "Gesendet" msgstr "Sent" -#: workflows/models.py:530 workflows/tasks.py:600 +#: workflows/models.py:546 workflows/tasks.py:600 msgid "Geräte und Arbeitsplatz" msgstr "Devices and workplace" -#: workflows/models.py:531 workflows/tasks.py:601 +#: workflows/models.py:547 workflows/tasks.py:601 msgid "Konten und Berechtigungen" msgstr "Accounts and permissions" -#: workflows/models.py:532 workflows/tasks.py:602 +#: workflows/models.py:548 workflows/tasks.py:602 msgid "Software und Tools" msgstr "Software and tools" -#: workflows/models.py:533 workflows/tasks.py:603 +#: workflows/models.py:549 workflows/tasks.py:603 msgid "Prozesse und Hinweise" msgstr "Processes and notes" -#: workflows/models.py:536 +#: workflows/models.py:552 msgid "Immer anzeigen" msgstr "Always show" -#: workflows/models.py:539 +#: workflows/models.py:555 msgid "Ist Ja / aktiv" msgstr "Is yes / active" -#: workflows/models.py:540 +#: workflows/models.py:556 msgid "Ist Nein / inaktiv" msgstr "Is no / inactive" -#: workflows/models.py:567 +#: workflows/models.py:583 msgid "Entwurf" msgstr "Draft" -#: workflows/models.py:587 +#: workflows/models.py:603 #, fuzzy #| msgid "Nextcloud:" msgid "Nextcloud" msgstr "Nextcloud:" -#: workflows/models.py:588 +#: workflows/models.py:604 msgid "S3" msgstr "" -#: workflows/models.py:589 +#: workflows/models.py:605 msgid "NFS" msgstr "" @@ -983,18 +1006,18 @@ msgstr "" msgid "Platform Owner" msgstr "" -#: workflows/roles.py:27 workflows/templates/workflows/app_registry.html:88 -#: workflows/templates/workflows/app_registry.html:114 +#: workflows/roles.py:27 workflows/templates/workflows/app_registry.html:110 +#: workflows/templates/workflows/app_registry.html:136 msgid "Super Admin" msgstr "Super Admin" -#: workflows/roles.py:28 workflows/templates/workflows/app_registry.html:89 -#: workflows/templates/workflows/app_registry.html:118 +#: workflows/roles.py:28 workflows/templates/workflows/app_registry.html:111 +#: workflows/templates/workflows/app_registry.html:140 msgid "Admin" msgstr "Admin" -#: workflows/roles.py:29 workflows/templates/workflows/app_registry.html:90 -#: workflows/templates/workflows/app_registry.html:122 +#: workflows/roles.py:29 workflows/templates/workflows/app_registry.html:112 +#: workflows/templates/workflows/app_registry.html:144 msgid "IT Staff" msgstr "IT Staff" @@ -1105,13 +1128,12 @@ msgid "Anmeldung fehlgeschlagen" msgstr "Failed" #: workflows/templates/registration/login.html:31 -#: workflows/templates/workflows/auth/login.html:29 msgid "" "Benutzername oder Passwort sind nicht korrekt. Bitte versuchen Sie es erneut." msgstr "" #: workflows/templates/registration/login.html:37 -#: workflows/templates/workflows/auth/login.html:35 +#: workflows/templates/workflows/auth/login.html:39 msgid "Anmelden" msgstr "Sign in" @@ -1250,6 +1272,8 @@ msgid "Die wichtigsten Stammdaten Ihres aktuellen Kontos." msgstr "The most important master data of your current account." #: workflows/templates/workflows/account_profile.html:97 +#: workflows/templates/workflows/branding_settings.html:32 +#: workflows/templates/workflows/company_config.html:25 #, fuzzy #| msgid "In Bearbeitung" msgid "Bearbeiten" @@ -1271,12 +1295,16 @@ msgid "E-Mail" msgstr "Email" #: workflows/templates/workflows/account_profile.html:165 +#: workflows/templates/workflows/branding_settings.html:177 +#: workflows/templates/workflows/company_config.html:54 #: workflows/templates/workflows/user_management.html:115 msgid "Speichern" msgstr "Save" #: workflows/templates/workflows/account_profile.html:166 #: workflows/templates/workflows/base_shell.html:79 +#: workflows/templates/workflows/branding_settings.html:178 +#: workflows/templates/workflows/company_config.html:55 #: workflows/templates/workflows/welcome_emails.html:134 msgid "Abbrechen" msgstr "Cancel" @@ -1290,7 +1318,7 @@ msgid "Direkte Aktionen für Ihr Workdock-Konto." msgstr "Direct actions for your Workdock account." #: workflows/templates/workflows/account_profile.html:179 -#: workflows/templates/workflows/account_profile.html:190 +#: workflows/templates/workflows/account_profile.html:278 #: workflows/templates/workflows/auth/password_change_form.html:4 #: workflows/templates/workflows/auth/password_change_form.html:17 #: workflows/templates/workflows/includes/app_header.html:48 @@ -1302,14 +1330,93 @@ msgid "Aktualisieren Sie Ihr Passwort direkt im Konto." msgstr "Update your password directly in your account." #: workflows/templates/workflows/account_profile.html:184 +msgid "TOTP" +msgstr "" + +#: workflows/templates/workflows/account_profile.html:187 +msgid "Zweiter Faktor ist aktiv und wird bei der Anmeldung geprüft." +msgstr "" + +#: workflows/templates/workflows/account_profile.html:189 +msgid "Standardmäßig deaktiviert. Kann hier jederzeit aktiviert werden." +msgstr "" + +#: workflows/templates/workflows/account_profile.html:195 msgid "Sitzung" msgstr "Session" -#: workflows/templates/workflows/account_profile.html:185 +#: workflows/templates/workflows/account_profile.html:196 msgid "Sie können sich jederzeit sicher vom aktuellen Gerät abmelden." msgstr "" -#: workflows/templates/workflows/account_profile.html:193 +#: workflows/templates/workflows/account_profile.html:203 +msgid "Zwei-Faktor-Authentifizierung" +msgstr "" + +#: workflows/templates/workflows/account_profile.html:204 +msgid "" +"Aktivieren Sie TOTP mit einer Authenticator-App. Standardmäßig bleibt es " +"ausgeschaltet." +msgstr "" + +#: workflows/templates/workflows/account_profile.html:208 +#: workflows/templates/workflows/app_registry.html:35 +#: workflows/templates/workflows/app_registry.html:84 +#: workflows/templates/workflows/form_builder.html:91 +#: workflows/templates/workflows/integrations_setup.html:263 +#: workflows/templates/workflows/intro_builder.html:65 +#: workflows/templates/workflows/trial_management.html:28 +#: workflows/templates/workflows/user_management.html:75 +msgid "Aktiv" +msgstr "Active" + +#: workflows/templates/workflows/account_profile.html:210 +#, fuzzy +#| msgid "Auf" +msgid "Aus" +msgstr "To" + +#: workflows/templates/workflows/account_profile.html:219 +#, fuzzy +#| msgid "Deaktivieren" +msgid "TOTP ist aktiviert." +msgstr "Disabled" + +#: workflows/templates/workflows/account_profile.html:222 +msgid "Bestätigt am" +msgstr "" + +#: workflows/templates/workflows/account_profile.html:241 +#, fuzzy +#| msgid "Aktivieren" +msgid "TOTP deaktivieren" +msgstr "Enable" + +#: workflows/templates/workflows/account_profile.html:247 +#, fuzzy +#| msgid "Onboarding starten" +msgid "Manueller Schlüssel" +msgstr "Start onboarding" + +#: workflows/templates/workflows/account_profile.html:251 +#, fuzzy +#| msgid "Setup Mail" +msgid "Setup-Link" +msgstr "Setup Mail" + +#: workflows/templates/workflows/account_profile.html:255 +msgid "" +"Wenn Ihre App keinen QR-Code scannen kann, tragen Sie den Schlüssel oder den " +"otpauth-Link manuell ein." +msgstr "" + +#: workflows/templates/workflows/account_profile.html:271 +#, fuzzy +#| msgid "Aktivieren" +msgid "TOTP aktivieren" +msgstr "Enable" + +#: workflows/templates/workflows/account_profile.html:281 #: workflows/templates/workflows/includes/app_header.html:51 msgid "Abmelden" msgstr "Log out" @@ -1320,32 +1427,39 @@ msgstr "Log out" msgid "Ungespeicherte Änderungen" msgstr "Last updated" -#: workflows/templates/workflows/app_registry.html:14 +#: workflows/templates/workflows/app_registry.html:4 +#: workflows/templates/workflows/app_registry.html:103 +#: workflows/templates/workflows/form_builder.html:87 +#: workflows/templates/workflows/intro_builder.html:58 +msgid "Sortierung" +msgstr "Sort order" + +#: workflows/templates/workflows/app_registry.html:15 msgid "" "Apps zentral steuern, für Kunden vorbereiten und ohne Template-Eingriffe auf " "der Landing Page ausspielen." msgstr "" -#: workflows/templates/workflows/app_registry.html:20 +#: workflows/templates/workflows/app_registry.html:21 msgid "" "Sicherheit bleibt codebasiert: Sichtbarkeit und Reihenfolge sind hier " "steuerbar, Berechtigungen weiterhin über Rollen und Capabilities." msgstr "" -#: workflows/templates/workflows/app_registry.html:21 +#: workflows/templates/workflows/app_registry.html:22 #, fuzzy #| msgid "Produktion" msgid "Produktkern" msgstr "Production" -#: workflows/templates/workflows/app_registry.html:28 +#: workflows/templates/workflows/app_registry.html:29 #, fuzzy #| msgid "Nach Name oder E-Mail suchen" msgid "Nach App-Name oder Key filtern" msgstr "Search by name or email" -#: workflows/templates/workflows/app_registry.html:33 -#: workflows/templates/workflows/app_registry.html:42 +#: workflows/templates/workflows/app_registry.html:34 +#: workflows/templates/workflows/app_registry.html:43 #: workflows/templates/workflows/audit_log.html:25 #: workflows/templates/workflows/job_monitor.html:22 #: workflows/templates/workflows/job_monitor.html:31 @@ -1355,18 +1469,8 @@ msgstr "Search by name or email" msgid "Alle" msgstr "" -#: workflows/templates/workflows/app_registry.html:34 -#: workflows/templates/workflows/app_registry.html:62 -#: workflows/templates/workflows/form_builder.html:91 -#: workflows/templates/workflows/integrations_setup.html:263 -#: workflows/templates/workflows/intro_builder.html:65 -#: workflows/templates/workflows/trial_management.html:28 -#: workflows/templates/workflows/user_management.html:75 -msgid "Aktiv" -msgstr "Active" - -#: workflows/templates/workflows/app_registry.html:35 -#: workflows/templates/workflows/app_registry.html:64 +#: workflows/templates/workflows/app_registry.html:36 +#: workflows/templates/workflows/app_registry.html:86 #: workflows/templates/workflows/backup_recovery.html:106 #: workflows/templates/workflows/trial_management.html:30 #: workflows/templates/workflows/trial_management.html:43 @@ -1375,132 +1479,155 @@ msgstr "Active" msgid "Deaktiviert" msgstr "Disabled" -#: workflows/templates/workflows/app_registry.html:36 -#: workflows/templates/workflows/app_registry.html:83 +#: workflows/templates/workflows/app_registry.html:37 +#: workflows/templates/workflows/app_registry.html:105 msgid "Platform only" msgstr "" -#: workflows/templates/workflows/app_registry.html:40 -#: workflows/templates/workflows/app_registry.html:136 +#: workflows/templates/workflows/app_registry.html:41 +#: workflows/templates/workflows/app_registry.html:158 #, fuzzy #| msgid "Eingereicht" msgid "Bereich" msgstr "Submitted" -#: workflows/templates/workflows/app_registry.html:69 +#: workflows/templates/workflows/app_registry.html:50 +msgid "Für eine verlässliche Reihenfolge bitte ohne aktive Filter umsortieren." +msgstr "" + +#: workflows/templates/workflows/app_registry.html:59 +msgid "Produktweite Steuerung und nur für die Platform sichtbare Oberflächen." +msgstr "" + +#: workflows/templates/workflows/app_registry.html:61 +msgid "Administrative Apps für Kundenrollen mit erhöhter Verantwortung." +msgstr "" + +#: workflows/templates/workflows/app_registry.html:63 +msgid "" +"Operative Apps, die im täglichen Einsatz auf der Landing Page erscheinen." +msgstr "" + +#: workflows/templates/workflows/app_registry.html:79 +#, fuzzy +#| msgid "Ziehen zum Sortieren" +msgid "Ziehen zum Umordnen" +msgstr "Drag to reorder" + +#: workflows/templates/workflows/app_registry.html:91 msgid "Empfohlener Standardzugriff:" msgstr "" -#: workflows/templates/workflows/app_registry.html:81 -#: workflows/templates/workflows/form_builder.html:87 -#: workflows/templates/workflows/intro_builder.html:58 -msgid "Sortierung" -msgstr "Sort order" - -#: workflows/templates/workflows/app_registry.html:91 -#: workflows/templates/workflows/app_registry.html:126 +#: workflows/templates/workflows/app_registry.html:113 +#: workflows/templates/workflows/app_registry.html:148 #, fuzzy #| msgid "IT Staff" msgid "Staff" msgstr "IT Staff" -#: workflows/templates/workflows/app_registry.html:99 +#: workflows/templates/workflows/app_registry.html:121 #, fuzzy #| msgid "Noch nicht verfügbar" msgid "Verfügbarkeit" msgstr "Not available yet" -#: workflows/templates/workflows/app_registry.html:103 +#: workflows/templates/workflows/app_registry.html:125 #, fuzzy #| msgid "Deaktivieren" msgid "App aktiviert" msgstr "Disabled" -#: workflows/templates/workflows/app_registry.html:106 +#: workflows/templates/workflows/app_registry.html:128 msgid "" "Deaktivierte Apps erscheinen nicht auf der Landing Page, selbst wenn Rollen " "sie sehen dürften." msgstr "" -#: workflows/templates/workflows/app_registry.html:110 +#: workflows/templates/workflows/app_registry.html:132 msgid "Sichtbarkeit nach Rolle" msgstr "" -#: workflows/templates/workflows/app_registry.html:129 +#: workflows/templates/workflows/app_registry.html:151 msgid "" "Wenn keine Firmenrolle aktiv ist, bleibt die App nur für die Platform " "sichtbar." msgstr "" -#: workflows/templates/workflows/app_registry.html:133 +#: workflows/templates/workflows/app_registry.html:155 #, fuzzy #| msgid "Sortierung" msgid "Platzierung" msgstr "Sort order" -#: workflows/templates/workflows/app_registry.html:144 +#: workflows/templates/workflows/app_registry.html:166 #, fuzzy #| msgid "Reihenfolge speichern" msgid "Reihenfolge" msgstr "Save order" -#: workflows/templates/workflows/app_registry.html:151 +#: workflows/templates/workflows/app_registry.html:176 +msgid "Wird per Drag-and-drop und Bereichswechsel dynamisch neu nummeriert." +msgstr "" + +#: workflows/templates/workflows/app_registry.html:182 msgid "Bezeichnungen & Texte" msgstr "" -#: workflows/templates/workflows/app_registry.html:154 -#: workflows/templates/workflows/branding_settings.html:141 +#: workflows/templates/workflows/app_registry.html:185 +#: workflows/templates/workflows/branding_settings.html:40 +#: workflows/templates/workflows/branding_settings.html:111 #: workflows/templates/workflows/trial_management.html:105 msgid "Deutsch" msgstr "" -#: workflows/templates/workflows/app_registry.html:156 +#: workflows/templates/workflows/app_registry.html:187 msgid "Titel" msgstr "" -#: workflows/templates/workflows/app_registry.html:160 +#: workflows/templates/workflows/app_registry.html:191 msgid "Beschreibung" msgstr "" -#: workflows/templates/workflows/app_registry.html:164 +#: workflows/templates/workflows/app_registry.html:195 #, fuzzy #| msgid "Aktionen" msgid "Aktionslabel" msgstr "Actions" -#: workflows/templates/workflows/app_registry.html:169 -#: workflows/templates/workflows/branding_settings.html:152 +#: workflows/templates/workflows/app_registry.html:200 +#: workflows/templates/workflows/branding_settings.html:49 +#: workflows/templates/workflows/branding_settings.html:121 #: workflows/templates/workflows/trial_management.html:112 #, fuzzy #| msgid "English label" msgid "English" msgstr "English label" -#: workflows/templates/workflows/app_registry.html:171 +#: workflows/templates/workflows/app_registry.html:202 msgid "Title" msgstr "" -#: workflows/templates/workflows/app_registry.html:175 +#: workflows/templates/workflows/app_registry.html:206 msgid "Description" msgstr "" -#: workflows/templates/workflows/app_registry.html:179 +#: workflows/templates/workflows/app_registry.html:210 #, fuzzy #| msgid "Aktionen" msgid "Action label" msgstr "Actions" -#: workflows/templates/workflows/app_registry.html:191 +#: workflows/templates/workflows/app_registry.html:226 msgid "" "Empfehlung: Produktweite Apps sparsam halten, kundenbezogene Prozesse unter " "Apps oder Admin Apps einordnen." msgstr "" -#: workflows/templates/workflows/app_registry.html:192 +#: workflows/templates/workflows/app_registry.html:227 msgid "Keine ungespeicherten Änderungen" msgstr "" -#: workflows/templates/workflows/app_registry.html:194 +#: workflows/templates/workflows/app_registry.html:229 #, fuzzy #| msgid "Regeln speichern" msgid "App Registry speichern" @@ -1594,6 +1721,16 @@ msgstr "" msgid "Noch keine Audit-Einträge vorhanden." msgstr "No requests available yet." +#: workflows/templates/workflows/auth/login.html:29 +msgid "" +"Anmeldedaten oder TOTP-Code sind nicht korrekt. Bitte versuchen Sie es " +"erneut." +msgstr "" + +#: workflows/templates/workflows/auth/login.html:37 +msgid "Nur erforderlich, wenn TOTP für Ihr Konto aktiviert ist." +msgstr "" + #: workflows/templates/workflows/auth/password_change_done.html:4 #: workflows/templates/workflows/auth/password_change_done.html:17 #: workflows/templates/workflows/user_management.html:174 @@ -1831,83 +1968,26 @@ msgid "Portalname, Firmenauftritt, Logo und PDF-Briefkopf zentral verwalten." msgstr "" "Manage portal name, company branding, logo, and PDF letterhead centrally." -#: workflows/templates/workflows/branding_settings.html:23 -msgid "Identität" -msgstr "" - -#: workflows/templates/workflows/branding_settings.html:24 -msgid "Titel, Firmenname und zentrale Spracheinstellungen." -msgstr "" - -#: workflows/templates/workflows/branding_settings.html:38 -msgid "" -"Wird für E-Mail-Vorschläge und Domain-bezogene Standardtexte verwendet, z. " -"B. tub.co." -msgstr "" - -#: workflows/templates/workflows/branding_settings.html:53 -msgid "Farben & Erscheinungsbild" -msgstr "" - -#: workflows/templates/workflows/branding_settings.html:54 -msgid "Zentrale visuelle Markenwerte und Browser-Icon." -msgstr "" - -#: workflows/templates/workflows/branding_settings.html:68 -msgid "Erlaubte Formate: SVG, PNG, JPG, JPEG, WEBP. Maximal 5 MB." -msgstr "" - -#: workflows/templates/workflows/branding_settings.html:71 -msgid "Aktuelles Logo:" -msgstr "Current logo:" - -#: workflows/templates/workflows/branding_settings.html:71 -#: workflows/templates/workflows/branding_settings.html:80 -#: workflows/templates/workflows/branding_settings.html:128 +#: workflows/templates/workflows/branding_settings.html:66 +#: workflows/templates/workflows/branding_settings.html:144 msgid "öffnen" msgstr "open" -#: workflows/templates/workflows/branding_settings.html:77 -msgid "Erlaubte Formate: ICO, PNG, SVG, WEBP. Maximal 2 MB." -msgstr "" +#: workflows/templates/workflows/branding_settings.html:140 +msgid "Aktuelles Logo:" +msgstr "Current logo:" -#: workflows/templates/workflows/branding_settings.html:80 +#: workflows/templates/workflows/branding_settings.html:141 #, fuzzy #| msgid "Aktuelles Logo:" msgid "Aktuelles Favicon:" msgstr "Current logo:" -#: workflows/templates/workflows/branding_settings.html:109 -#, fuzzy -#| msgid "Produktion" -msgid "Kommunikation" -msgstr "Production" - -#: workflows/templates/workflows/branding_settings.html:110 -msgid "Absender, Support und PDF-Branding für ausgehende Kommunikation." -msgstr "" - -#: workflows/templates/workflows/branding_settings.html:120 -msgid "Wird für ausgehende System-E-Mails als Anzeigename verwendet." -msgstr "" - -#: workflows/templates/workflows/branding_settings.html:125 -msgid "Erlaubtes Format: PDF. Maximal 10 MB." -msgstr "" - -#: workflows/templates/workflows/branding_settings.html:128 +#: workflows/templates/workflows/branding_settings.html:142 msgid "Aktueller Briefkopf:" msgstr "Current letterhead:" -#: workflows/templates/workflows/branding_settings.html:136 -msgid "Footer & Rechtliches" -msgstr "" - -#: workflows/templates/workflows/branding_settings.html:137 -msgid "Gemeinsame Footer-Texte und rechtliche Hinweise für die Shell." -msgstr "" - -#: workflows/templates/workflows/branding_settings.html:166 +#: workflows/templates/workflows/branding_settings.html:185 #, fuzzy #| msgid "" #| "TUBCO bleibt als Standard erhalten, bis hier Werte geändert oder Dateien " @@ -1919,68 +1999,18 @@ msgstr "" "TUBCO remains the default until values are changed or files are uploaded " "here." -#: workflows/templates/workflows/branding_settings.html:167 -msgid "Branding speichern" -msgstr "Save branding" - #: workflows/templates/workflows/company_config.html:13 msgid "" "Strukturierte Firmendaten, Kontaktpunkte und öffentliche Unternehmenslinks " "zentral pflegen." msgstr "" -#: workflows/templates/workflows/company_config.html:23 -#, fuzzy -#| msgid "Firmenname" -msgid "Firmenprofil" -msgstr "Company name" - -#: workflows/templates/workflows/company_config.html:24 -msgid "Rechtlicher Name und zentrale Stammdaten der Firma." -msgstr "" - -#: workflows/templates/workflows/company_config.html:48 -msgid "Adresse & Register" -msgstr "" - -#: workflows/templates/workflows/company_config.html:49 -msgid "Anschrift sowie optionale Register- und Steuerangaben." -msgstr "" - -#: workflows/templates/workflows/company_config.html:77 -msgid "Kontaktpunkte" -msgstr "" - -#: workflows/templates/workflows/company_config.html:78 -msgid "Zentrale Ansprechpartner für HR, IT und Operations." -msgstr "" - -#: workflows/templates/workflows/company_config.html:98 -msgid "Recht & Öffentlichkeit" -msgstr "" - -#: workflows/templates/workflows/company_config.html:99 -msgid "Öffentliche Links für Website, Impressum und Datenschutz." -msgstr "" - -#: workflows/templates/workflows/company_config.html:111 -msgid "" -"Diese Links können später im Portal-Footer oder in öffentlichen Seiten " -"verwendet werden." -msgstr "" - -#: workflows/templates/workflows/company_config.html:115 +#: workflows/templates/workflows/company_config.html:62 msgid "" "Diese Ebene ist bewusst von Branding getrennt: Hier geht es um strukturierte " "Firmendaten, nicht um visuelle Gestaltung." msgstr "" -#: workflows/templates/workflows/company_config.html:116 -#, fuzzy -#| msgid "Optionen speichern" -msgid "Firmenkonfiguration speichern" -msgstr "Save options" - #: workflows/templates/workflows/form_builder.html:15 msgid "Felder per Drag-and-Drop sortieren und pro Schritt gruppieren." msgstr "Sort fields by drag and drop and group them by step." @@ -2847,7 +2877,7 @@ msgid "Dienstliche E-Mail" msgstr "Work email" #: workflows/templates/workflows/onboarding_intro_session.html:31 -#: workflows/views.py:1062 +#: workflows/views.py:1242 msgid "Vertragsbeginn" msgstr "Contract start" @@ -3651,280 +3681,302 @@ msgstr "Resume" msgid "Keine geplanten Welcome E-Mails vorhanden." msgstr "No scheduled welcome emails available." -#: workflows/views.py:101 +#: workflows/views.py:102 msgid "Person, Rolle, Abteilung" msgstr "Person, role, department" -#: workflows/views.py:102 +#: workflows/views.py:103 msgid "Beschäftigung und Termine" msgstr "Employment and dates" -#: workflows/views.py:103 +#: workflows/views.py:104 msgid "Geräte, Software und Zugänge" msgstr "Devices, software, and access" -#: workflows/views.py:104 +#: workflows/views.py:105 msgid "Notizen und Freigabe" msgstr "Notes and approval" -#: workflows/views.py:141 +#: workflows/views.py:154 #, fuzzy #| msgid "Lokal gespeichert" msgid "Profilbild gespeichert." msgstr "Stored locally" -#: workflows/views.py:143 +#: workflows/views.py:156 #, fuzzy #| msgid "Passwort konnte nicht gespeichert werden" msgid "Profilbild konnte nicht gespeichert werden." msgstr "Password could not be saved" -#: workflows/views.py:149 +#: workflows/views.py:162 #, fuzzy #| msgid "Lokal gespeichert" msgid "Profildaten gespeichert." msgstr "Stored locally" -#: workflows/views.py:151 +#: workflows/views.py:164 #, fuzzy #| msgid "Passwort konnte nicht gespeichert werden" msgid "Profildaten konnten nicht gespeichert werden." msgstr "Password could not be saved" -#: workflows/views.py:172 workflows/views.py:1148 workflows/views.py:1153 +#: workflows/views.py:171 +#, fuzzy +#| msgid "Deaktivieren" +msgid "TOTP wurde aktiviert." +msgstr "Disabled" + +#: workflows/views.py:173 +#, fuzzy +#| msgid "Passwort konnte nicht gespeichert werden" +msgid "TOTP konnte nicht aktiviert werden." +msgstr "Password could not be saved" + +#: workflows/views.py:180 +msgid "TOTP wurde deaktiviert." +msgstr "" + +#: workflows/views.py:182 +#, fuzzy +#| msgid "Passwort konnte nicht gespeichert werden" +msgid "TOTP konnte nicht deaktiviert werden." +msgstr "Password could not be saved" + +#: workflows/views.py:212 workflows/views.py:1328 workflows/views.py:1333 msgid "Sie haben keine Berechtigung für diese Aktion." msgstr "You do not have permission for this action." -#: workflows/views.py:253 +#: workflows/views.py:293 #, fuzzy #| msgid "Vorgänge" msgid "Vorgänge gelöscht" msgstr "Requests" -#: workflows/views.py:254 +#: workflows/views.py:294 msgid "Vorgang gelöscht" msgstr "" -#: workflows/views.py:255 +#: workflows/views.py:295 msgid "Vorgang erneut angestoßen" msgstr "" -#: workflows/views.py:256 +#: workflows/views.py:296 #, fuzzy #| msgid "Einweisung" msgid "Einweisungs-PDF erzeugt" msgstr "Introduction" -#: workflows/views.py:257 +#: workflows/views.py:297 #, fuzzy #| msgid "Live-Protokoll erzeugen" msgid "Live-Protokoll erzeugt" msgstr "Generate live protocol" -#: workflows/views.py:258 +#: workflows/views.py:298 #, fuzzy #| msgid "Einweisung wurde zurückgesetzt." msgid "Einweisung zurückgesetzt" msgstr "Introduction was reset." -#: workflows/views.py:259 +#: workflows/views.py:299 #, fuzzy #| msgid "Einweisung wurde als Entwurf gespeichert." msgid "Einweisung als Entwurf gespeichert" msgstr "Introduction was saved as draft." -#: workflows/views.py:260 +#: workflows/views.py:300 #, fuzzy #| msgid "Einweisung wurde als abgeschlossen gespeichert." msgid "Einweisung abgeschlossen" msgstr "Introduction was saved as completed." -#: workflows/views.py:261 +#: workflows/views.py:301 msgid "Formularoption gelöscht" msgstr "" -#: workflows/views.py:262 +#: workflows/views.py:302 #, fuzzy #| msgid "Optionen speichern" msgid "Formularoptionen gespeichert" msgstr "Save options" -#: workflows/views.py:263 +#: workflows/views.py:303 #, fuzzy #| msgid "Feldtexte speichern" msgid "Feldtexte gespeichert" msgstr "Save field text" -#: workflows/views.py:264 +#: workflows/views.py:304 #, fuzzy #| msgid "Offboarding-Anfrage speichern" msgid "Formularlayout gespeichert" msgstr "Save offboarding request" -#: workflows/views.py:265 +#: workflows/views.py:305 msgid "Einweisungs-Checkpunkt gelöscht" msgstr "" -#: workflows/views.py:266 +#: workflows/views.py:306 msgid "Einweisungs-Checkpunkt hinzugefügt" msgstr "" -#: workflows/views.py:267 +#: workflows/views.py:307 #, fuzzy #| msgid "Checkliste speichern" msgid "Einweisungs-Checkliste gespeichert" msgstr "Save checklist" -#: workflows/views.py:268 +#: workflows/views.py:308 #, fuzzy #| msgid "Welcome E-Mails" msgid "Welcome E-Mail sofort ausgelöst" msgstr "Welcome Emails" -#: workflows/views.py:269 +#: workflows/views.py:309 #, fuzzy #| msgid "Welcome-Einstellungen speichern" msgid "Welcome E-Mail Einstellungen gespeichert" msgstr "Save welcome settings" -#: workflows/views.py:270 +#: workflows/views.py:310 msgid "Welcome E-Mail Sammelaktion ausgeführt" msgstr "" -#: workflows/views.py:271 +#: workflows/views.py:311 #, fuzzy #| msgid "Welcome E-Mails" msgid "Welcome E-Mail pausiert" msgstr "Welcome Emails" -#: workflows/views.py:272 +#: workflows/views.py:312 #, fuzzy #| msgid "Welcome E-Mails" msgid "Welcome E-Mail fortgesetzt" msgstr "Welcome Emails" -#: workflows/views.py:273 +#: workflows/views.py:313 #, fuzzy #| msgid "Welcome E-Mails" msgid "Welcome E-Mail abgebrochen" msgstr "Welcome Emails" -#: workflows/views.py:274 +#: workflows/views.py:314 #, fuzzy #| msgid "SMTP-Test" msgid "SMTP-Test gesendet" msgstr "SMTP test" -#: workflows/views.py:275 +#: workflows/views.py:315 #, fuzzy #| msgid "Nextcloud-Test" msgid "Nextcloud-Testupload ausgeführt" msgstr "Nextcloud test" -#: workflows/views.py:276 +#: workflows/views.py:316 #, fuzzy #| msgid "Nextcloud schalten" msgid "Nextcloud-Modus umgeschaltet" msgstr "Toggle Nextcloud" -#: workflows/views.py:277 +#: workflows/views.py:317 msgid "E-Mail-Modus umgeschaltet" msgstr "" -#: workflows/views.py:278 +#: workflows/views.py:318 #, fuzzy #| msgid "Integrationen Setup" msgid "Integrationen gespeichert" msgstr "Integrations Setup" -#: workflows/views.py:279 +#: workflows/views.py:319 #, fuzzy #| msgid "Welcome-Einstellungen speichern" msgid "Nextcloud-Einstellungen gespeichert" msgstr "Save welcome settings" -#: workflows/views.py:280 +#: workflows/views.py:320 #, fuzzy #| msgid "Welcome-Einstellungen speichern" msgid "Mail-Einstellungen gespeichert" msgstr "Save welcome settings" -#: workflows/views.py:281 +#: workflows/views.py:321 #, fuzzy #| msgid "E-Mail Routing & Vorlagen speichern" msgid "E-Mail-Routing gespeichert" msgstr "Save email routing & templates" -#: workflows/views.py:282 +#: workflows/views.py:322 #, fuzzy #| msgid "Offboarding-Anfrage speichern" msgid "Benachrichtigungsregeln gespeichert" msgstr "Save offboarding request" -#: workflows/views.py:283 +#: workflows/views.py:323 #, fuzzy #| msgid "Anfrage gespeichert" msgid "Benutzer erstellt" msgstr "Request saved" -#: workflows/views.py:284 +#: workflows/views.py:324 msgid "Benutzer aktualisiert" msgstr "" -#: workflows/views.py:285 +#: workflows/views.py:325 msgid "Passwort-Reset-Link versendet" msgstr "" -#: workflows/views.py:286 +#: workflows/views.py:326 #, fuzzy #| msgid "Benutzerübersicht" msgid "Benutzer gelöscht" msgstr "User overview" -#: workflows/views.py:287 +#: workflows/views.py:327 #, fuzzy #| msgid "Anfrage gespeichert" msgid "Backup erstellt" msgstr "Request saved" -#: workflows/views.py:288 +#: workflows/views.py:328 msgid "Backup verifiziert" msgstr "" -#: workflows/views.py:289 +#: workflows/views.py:329 #, fuzzy #| msgid "Anfrage gespeichert" msgid "Backup gelöscht" msgstr "Request saved" -#: workflows/views.py:290 +#: workflows/views.py:330 #, fuzzy #| msgid "Welcome-Einstellungen speichern" msgid "Backup-Einstellungen gespeichert" msgstr "Save welcome settings" -#: workflows/views.py:291 +#: workflows/views.py:331 #, fuzzy #| msgid "Anfrage gespeichert" msgid "App-Registry gespeichert" msgstr "Request saved" -#: workflows/views.py:459 +#: workflows/views.py:503 #, fuzzy #| msgid "Anfrage gespeichert" msgid "App-Registry gespeichert." msgstr "Request saved" -#: workflows/views.py:558 +#: workflows/views.py:602 msgid "Für diesen Benutzer ist keine E-Mail-Adresse hinterlegt." msgstr "" -#: workflows/views.py:567 +#: workflows/views.py:611 #, python-format msgid "Zugangseinladung für %(username)s" msgstr "" -#: workflows/views.py:569 +#: workflows/views.py:613 #, python-format msgid "" "Hallo %(name)s,\n" @@ -3937,12 +3989,12 @@ msgid "" "Ihrem Administrator." msgstr "" -#: workflows/views.py:580 +#: workflows/views.py:624 #, python-format msgid "Passwort zurücksetzen für %(username)s" msgstr "" -#: workflows/views.py:582 +#: workflows/views.py:626 #, python-format msgid "" "Hallo %(name)s,\n" @@ -3955,7 +4007,7 @@ msgid "" "ignorieren." msgstr "" -#: workflows/views.py:620 +#: workflows/views.py:677 #, fuzzy #| msgid "" #| "Benutzer konnte nicht erstellt werden. Bitte prüfen Sie die Eingaben." @@ -3963,13 +4015,69 @@ msgid "" "Branding konnte nicht gespeichert werden. Bitte prüfen Sie die Eingaben." msgstr "User could not be created. Please check the input." -#: workflows/views.py:646 +#: workflows/views.py:705 #, fuzzy #| msgid "Offboarding-Anfrage speichern" msgid "Portal-Branding wurde gespeichert." msgstr "Save offboarding request" -#: workflows/views.py:677 +#: workflows/views.py:722 +msgid "Identität" +msgstr "" + +#: workflows/views.py:723 +msgid "Titel, Firmenname und zentrale Spracheinstellungen." +msgstr "" + +#: workflows/views.py:727 +msgid "" +"Wird für E-Mail-Vorschläge und Domain-bezogene Standardtexte verwendet, z. " +"B. tub.co." +msgstr "" + +#: workflows/views.py:732 +msgid "Farben & Erscheinungsbild" +msgstr "" + +#: workflows/views.py:733 +msgid "Zentrale visuelle Markenwerte und Browser-Icon." +msgstr "" + +#: workflows/views.py:737 +msgid "Erlaubte Formate: SVG, PNG, JPG, JPEG, WEBP. Maximal 5 MB." +msgstr "" + +#: workflows/views.py:738 +msgid "Erlaubte Formate: ICO, PNG, SVG, WEBP. Maximal 2 MB." +msgstr "" + +#: workflows/views.py:743 +#, fuzzy +#| msgid "Produktion" +msgid "Kommunikation" +msgstr "Production" + +#: workflows/views.py:744 +msgid "Absender, Support und PDF-Branding für ausgehende Kommunikation." +msgstr "" + +#: workflows/views.py:748 +msgid "Wird für ausgehende System-E-Mails als Anzeigename verwendet." +msgstr "" + +#: workflows/views.py:749 +msgid "Erlaubtes Format: PDF. Maximal 10 MB." +msgstr "" + +#: workflows/views.py:754 +msgid "Footer & Rechtliches" +msgstr "" + +#: workflows/views.py:755 +msgid "Gemeinsame Footer-Texte und rechtliche Hinweise für die Shell." +msgstr "" + +#: workflows/views.py:809 #, fuzzy #| msgid "" #| "Benutzer konnte nicht erstellt werden. Bitte prüfen Sie die Eingaben." @@ -3978,13 +4086,53 @@ msgid "" "Eingaben." msgstr "User could not be created. Please check the input." -#: workflows/views.py:704 +#: workflows/views.py:838 #, fuzzy #| msgid "Offboarding-Anfrage speichern" msgid "Firmenkonfiguration wurde gespeichert." msgstr "Save offboarding request" -#: workflows/views.py:736 +#: workflows/views.py:855 +#, fuzzy +#| msgid "Firmenname" +msgid "Firmenprofil" +msgstr "Company name" + +#: workflows/views.py:856 +msgid "Rechtlicher Name und zentrale Stammdaten der Firma." +msgstr "" + +#: workflows/views.py:861 +msgid "Adresse & Register" +msgstr "" + +#: workflows/views.py:862 +msgid "Anschrift sowie optionale Register- und Steuerangaben." +msgstr "" + +#: workflows/views.py:867 +msgid "Kontaktpunkte" +msgstr "" + +#: workflows/views.py:868 +msgid "Zentrale Ansprechpartner für HR, IT und Operations." +msgstr "" + +#: workflows/views.py:873 +msgid "Recht & Öffentlichkeit" +msgstr "" + +#: workflows/views.py:874 +msgid "Öffentliche Links für Website, Impressum und Datenschutz." +msgstr "" + +#: workflows/views.py:876 +msgid "" +"Diese Links können später im Portal-Footer oder in öffentlichen Seiten " +"verwendet werden." +msgstr "" + +#: workflows/views.py:916 #, fuzzy #| msgid "" #| "Benutzer konnte nicht erstellt werden. Bitte prüfen Sie die Eingaben." @@ -3993,21 +4141,21 @@ msgid "" "Eingaben." msgstr "Trial configuration could not be saved. Please check the input." -#: workflows/views.py:763 +#: workflows/views.py:943 msgid "Trial-Konfiguration wurde gespeichert." msgstr "Trial configuration was saved." -#: workflows/views.py:780 +#: workflows/views.py:960 msgid "Benutzer konnte nicht erstellt werden. Bitte prüfen Sie die Eingaben." msgstr "User could not be created. Please check the input." -#: workflows/views.py:793 +#: workflows/views.py:973 #, fuzzy, python-format #| msgid "Benutzer wurde erstellt: %(username)s" msgid "Benutzer wurde erstellt und eingeladen: %(username)s" msgstr "User created: %(username)s" -#: workflows/views.py:815 +#: workflows/views.py:995 #, fuzzy #| msgid "" #| "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren " @@ -4018,14 +4166,14 @@ msgid "" msgstr "" "The currently signed-in super admin cannot lock or downgrade themselves here." -#: workflows/views.py:818 +#: workflows/views.py:998 msgid "" "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren oder " "herabstufen." msgstr "" "The currently signed-in super admin cannot lock or downgrade themselves here." -#: workflows/views.py:821 +#: workflows/views.py:1001 #, fuzzy #| msgid "" #| "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren " @@ -4036,7 +4184,7 @@ msgid "" msgstr "" "The currently signed-in super admin cannot lock or downgrade themselves here." -#: workflows/views.py:824 +#: workflows/views.py:1004 #, fuzzy #| msgid "" #| "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren " @@ -4047,18 +4195,18 @@ msgid "" msgstr "" "The currently signed-in super admin cannot lock or downgrade themselves here." -#: workflows/views.py:841 +#: workflows/views.py:1021 #, python-format msgid "Benutzer wurde aktualisiert: %(username)s" msgstr "User updated: %(username)s" -#: workflows/views.py:863 +#: workflows/views.py:1043 #, fuzzy, python-format #| msgid "Benutzer wurde erstellt: %(username)s" msgid "Passwort-Reset-Link wurde versendet: %(username)s" msgstr "User created: %(username)s" -#: workflows/views.py:875 +#: workflows/views.py:1055 #, fuzzy #| msgid "" #| "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren " @@ -4068,7 +4216,7 @@ msgid "" msgstr "" "The currently signed-in super admin cannot lock or downgrade themselves here." -#: workflows/views.py:878 +#: workflows/views.py:1058 #, fuzzy #| msgid "" #| "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren " @@ -4078,7 +4226,7 @@ msgid "" msgstr "" "The currently signed-in super admin cannot lock or downgrade themselves here." -#: workflows/views.py:881 +#: workflows/views.py:1061 #, fuzzy #| msgid "" #| "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren " @@ -4087,7 +4235,7 @@ msgid "Der letzte aktive Platform Owner kann nicht gelöscht werden." msgstr "" "The currently signed-in super admin cannot lock or downgrade themselves here." -#: workflows/views.py:884 +#: workflows/views.py:1064 #, fuzzy #| msgid "" #| "Der aktuell angemeldete Super Admin kann sich hier nicht selbst sperren " @@ -4096,124 +4244,132 @@ msgid "Der letzte aktive Super Admin kann nicht gelöscht werden." msgstr "" "The currently signed-in super admin cannot lock or downgrade themselves here." -#: workflows/views.py:897 +#: workflows/views.py:1077 #, fuzzy, python-format #| msgid "Benutzer wurde erstellt: %(username)s" msgid "Benutzer wurde gelöscht: %(username)s" msgstr "User created: %(username)s" -#: workflows/views.py:986 +#: workflows/views.py:1166 #, python-format msgid "Backup wurde erstellt: %(name)s" msgstr "" -#: workflows/views.py:988 +#: workflows/views.py:1168 #, python-format msgid "Backup konnte nicht erstellt werden: %(error)s" msgstr "" -#: workflows/views.py:1004 +#: workflows/views.py:1184 #, python-format msgid "Backup wurde verifiziert: %(name)s" msgstr "" -#: workflows/views.py:1006 +#: workflows/views.py:1186 #, python-format msgid "Backup-Verifikation fehlgeschlagen: %(error)s" msgstr "" -#: workflows/views.py:1022 +#: workflows/views.py:1202 #, python-format msgid "Backup wurde gelöscht: %(name)s" msgstr "" -#: workflows/views.py:1024 +#: workflows/views.py:1204 #, python-format msgid "Backup konnte nicht gelöscht werden: %(error)s" msgstr "" -#: workflows/views.py:1050 +#: workflows/views.py:1230 #, fuzzy #| msgid "Anfrage gespeichert" msgid "Anfrage erstellt" msgstr "Request saved" -#: workflows/views.py:1052 +#: workflows/views.py:1232 #, fuzzy, python-format #| msgid "Sitzungsstatus" msgid "Status: %(status)s" msgstr "Session status" -#: workflows/views.py:1064 +#: workflows/views.py:1244 #, fuzzy #| msgid "Geplant für" msgid "Geplanter Start" msgstr "Scheduled for" -#: workflows/views.py:1074 +#: workflows/views.py:1254 msgid "Geräteübergabe / Hardware-Abholung" msgstr "" -#: workflows/views.py:1076 +#: workflows/views.py:1256 msgid "Geplanter Hardware-Termin" msgstr "" -#: workflows/views.py:1085 +#: workflows/views.py:1265 #, fuzzy #| msgid "Noch nicht verfügbar" msgid "PDF verfügbar" msgstr "Not available yet" -#: workflows/views.py:1111 +#: workflows/views.py:1291 #, fuzzy #| msgid "Einweisung" msgid "Einweisungssitzung" msgstr "Introduction" -#: workflows/views.py:1123 +#: workflows/views.py:1303 #, fuzzy #| msgid "Welcome E-Mails" msgid "Welcome E-Mail" msgstr "Welcome Emails" -#: workflows/views.py:1162 +#: workflows/views.py:1342 msgid "Keine Einträge ausgewählt." msgstr "No entries selected." -#: workflows/views.py:1205 +#: workflows/views.py:1385 #, python-format msgid "%(count)s Eintrag/Einträge gelöscht." msgstr "%(count)s entry/entries deleted." -#: workflows/views.py:1207 +#: workflows/views.py:1387 #, python-format msgid "%(count)s Auswahl(en) konnten nicht verarbeitet werden." msgstr "%(count)s selection(s) could not be processed." -#: workflows/views.py:1209 +#: workflows/views.py:1389 msgid "Keine passenden Einträge gefunden." msgstr "No matching entries found." -#: workflows/views.py:1437 +#: workflows/views.py:1617 msgid "Einweisungs- und Übergabeprotokoll wurde erzeugt." msgstr "Introduction and handover protocol was generated." -#: workflows/views.py:1454 +#: workflows/views.py:1634 msgid "Einweisungsprotokoll aus Live-Status wurde erzeugt." msgstr "Introduction protocol from live status was generated." -#: workflows/views.py:1483 +#: workflows/views.py:1663 msgid "Einweisung wurde zurückgesetzt." msgstr "Introduction was reset." -#: workflows/views.py:1497 +#: workflows/views.py:1677 msgid "Einweisung wurde als abgeschlossen gespeichert." msgstr "Introduction was saved as completed." -#: workflows/views.py:1510 +#: workflows/views.py:1690 msgid "Einweisung wurde als Entwurf gespeichert." msgstr "Introduction was saved as draft." +#~ msgid "Branding speichern" +#~ msgstr "Save branding" + +#, fuzzy +#~| msgid "Optionen speichern" +#~ msgid "Firmenkonfiguration speichern" +#~ msgstr "Save options" + #, fuzzy #~| msgid "Aktion" #~ msgid "Aktion DE" @@ -4250,15 +4406,9 @@ msgstr "Introduction was saved as draft." #~ msgid "Aktiv/Inaktiv direkt umschalten." #~ msgstr "Switch active/inactive directly." -#~ msgid "Aktivieren" -#~ msgstr "Enable" - #~ msgid "Zwischen Testmodus und Produktion wechseln." #~ msgstr "Switch between test mode and production." -#~ msgid "Auf" -#~ msgstr "To" - #~ msgid "SMTP Einstellungen" #~ msgstr "SMTP Settings" diff --git a/backend/workflows/app_registry.py b/backend/workflows/app_registry.py index bf46d01..3e9db5b 100644 --- a/backend/workflows/app_registry.py +++ b/backend/workflows/app_registry.py @@ -349,6 +349,16 @@ def ensure_portal_app_configs() -> None: 'visible_to_staff': visibility.get(ROLE_STAFF, False), }, ) + normalize_portal_app_sort_orders() + + +def normalize_portal_app_sort_orders() -> None: + for section_key, _label in PortalAppConfig.SECTION_CHOICES: + configs = list(PortalAppConfig.objects.filter(section=section_key).order_by('sort_order', 'key')) + for position, config in enumerate(configs): + if config.sort_order != position: + config.sort_order = position + config.save(update_fields=['sort_order']) def get_portal_app_registry_rows() -> list[dict[str, object]]: diff --git a/backend/workflows/forms.py b/backend/workflows/forms.py index 477a401..ccb0a3d 100644 --- a/backend/workflows/forms.py +++ b/backend/workflows/forms.py @@ -3,6 +3,7 @@ from pathlib import Path from datetime import timedelta from django.contrib.auth import get_user_model, password_validation from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm, PasswordResetForm, SetPasswordForm +from django.core.exceptions import ValidationError from django.utils import timezone from django.utils.translation import get_language, gettext as _, gettext_lazy @@ -10,6 +11,7 @@ from .branding import get_company_email_domain from .form_builder import apply_form_field_config from .models import EmployeeProfile, FormOption, OffboardingRequest, OnboardingRequest, PortalBranding, PortalCompanyConfig, PortalTrialConfig, UserProfile, WorkflowConfig from .roles import ROLE_ADMIN, ROLE_GROUP_NAMES, ROLE_IT_STAFF, ROLE_LABELS, ROLE_PLATFORM_OWNER, ROLE_STAFF, ROLE_SUPER_ADMIN, assign_user_role +from .totp import normalize_totp_token, verify_totp_token YES_NO_CHOICES = [('', '--'), ('ja', 'Ja'), ('nein', 'Nein')] @@ -103,6 +105,38 @@ SOFTWARE_EXTRA_CHOICES = [('Adobe Acrobat Pro (Abonnement: Zusätzliche Kosten)' class AppAuthenticationForm(AuthenticationForm): username = forms.CharField(label=gettext_lazy('Benutzername')) password = forms.CharField(label=gettext_lazy('Passwort'), strip=False, widget=forms.PasswordInput(attrs={'autocomplete': 'current-password'})) + otp_code = forms.CharField( + label=gettext_lazy('TOTP-Code'), + required=False, + max_length=12, + widget=forms.TextInput(attrs={'autocomplete': 'one-time-code', 'inputmode': 'numeric'}), + ) + + error_messages = { + **AuthenticationForm.error_messages, + 'invalid_otp': gettext_lazy('Der TOTP-Code ist ungültig.'), + 'missing_otp': gettext_lazy('Bitte geben Sie Ihren TOTP-Code ein.'), + } + + def clean(self): + cleaned_data = super().clean() + user = self.get_user() + if not user: + return cleaned_data + profile, _ = UserProfile.objects.get_or_create(user=user) + if profile.totp_enabled: + otp_code = normalize_totp_token(cleaned_data.get('otp_code')) + if not otp_code: + raise ValidationError( + self.error_messages['missing_otp'], + code='missing_otp', + ) + if not profile.totp_secret or not verify_totp_token(profile.totp_secret, otp_code, for_time=int(timezone.now().timestamp())): + raise ValidationError( + self.error_messages['invalid_otp'], + code='invalid_otp', + ) + return cleaned_data class AppPasswordResetForm(PasswordResetForm): @@ -221,6 +255,71 @@ class AccountDetailsForm(forms.Form): return self.user, self.profile +class AccountTOTPEnableForm(forms.Form): + current_password = forms.CharField( + label=gettext_lazy('Aktuelles Passwort'), + strip=False, + widget=forms.PasswordInput(attrs={'autocomplete': 'current-password'}), + ) + verification_code = forms.CharField( + label=gettext_lazy('TOTP-Code'), + max_length=12, + widget=forms.TextInput(attrs={'autocomplete': 'one-time-code', 'inputmode': 'numeric'}), + ) + + def __init__(self, *args, user=None, secret: str = '', **kwargs): + super().__init__(*args, **kwargs) + self.user = user + self.secret = secret + + def clean_current_password(self): + password = self.cleaned_data.get('current_password') or '' + if not self.user or not self.user.check_password(password): + raise ValidationError(_('Das aktuelle Passwort ist nicht korrekt.')) + return password + + def clean_verification_code(self): + code = normalize_totp_token(self.cleaned_data.get('verification_code')) + if not code: + raise ValidationError(_('Bitte geben Sie einen gültigen TOTP-Code ein.')) + if not self.secret or not verify_totp_token(self.secret, code, for_time=int(timezone.now().timestamp())): + raise ValidationError(_('Der TOTP-Code ist ungültig.')) + return code + + +class AccountTOTPDisableForm(forms.Form): + current_password = forms.CharField( + label=gettext_lazy('Aktuelles Passwort'), + strip=False, + widget=forms.PasswordInput(attrs={'autocomplete': 'current-password'}), + ) + verification_code = forms.CharField( + label=gettext_lazy('TOTP-Code'), + max_length=12, + widget=forms.TextInput(attrs={'autocomplete': 'one-time-code', 'inputmode': 'numeric'}), + ) + + def __init__(self, *args, user=None, profile=None, **kwargs): + super().__init__(*args, **kwargs) + self.user = user + self.profile = profile + + def clean_current_password(self): + password = self.cleaned_data.get('current_password') or '' + if not self.user or not self.user.check_password(password): + raise ValidationError(_('Das aktuelle Passwort ist nicht korrekt.')) + return password + + def clean_verification_code(self): + code = normalize_totp_token(self.cleaned_data.get('verification_code')) + if not code: + raise ValidationError(_('Bitte geben Sie einen gültigen TOTP-Code ein.')) + secret = getattr(self.profile, 'totp_secret', '') or '' + if not secret or not verify_totp_token(secret, code, for_time=int(timezone.now().timestamp())): + raise ValidationError(_('Der TOTP-Code ist ungültig.')) + return code + + class UserManagementCreateForm(forms.Form): first_name = forms.CharField(label=_('Vorname'), max_length=150, required=False) last_name = forms.CharField(label=_('Nachname'), max_length=150, required=False) diff --git a/backend/workflows/migrations/0049_userprofile_totp_fields.py b/backend/workflows/migrations/0049_userprofile_totp_fields.py new file mode 100644 index 0000000..c9f13eb --- /dev/null +++ b/backend/workflows/migrations/0049_userprofile_totp_fields.py @@ -0,0 +1,26 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('workflows', '0048_userprofile'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='totp_confirmed_at', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='userprofile', + name='totp_enabled', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='userprofile', + name='totp_secret', + field=models.CharField(blank=True, default='', max_length=64), + ), + ] diff --git a/backend/workflows/models.py b/backend/workflows/models.py index 1d2633c..b637743 100644 --- a/backend/workflows/models.py +++ b/backend/workflows/models.py @@ -2,6 +2,7 @@ from django.conf import settings from django.core.validators import FileExtensionValidator from django.db import models from django.utils.translation import get_language +from django.utils import timezone from django.utils.translation import gettext_lazy as _ @@ -39,6 +40,9 @@ class UserProfile(models.Model): department = models.CharField(max_length=255, blank=True, default='') location = models.CharField(max_length=255, blank=True, default='') contact_notes = models.CharField(max_length=255, blank=True, default='') + totp_secret = models.CharField(max_length=64, blank=True, default='') + totp_enabled = models.BooleanField(default=False) + totp_confirmed_at = models.DateTimeField(null=True, blank=True) updated_at = models.DateTimeField(auto_now=True) class Meta: @@ -48,6 +52,18 @@ class UserProfile(models.Model): def __str__(self) -> str: return getattr(self.user, 'username', '') or str(self.user_id) + def disable_totp(self) -> None: + self.totp_secret = '' + self.totp_enabled = False + self.totp_confirmed_at = None + self.save(update_fields=['totp_secret', 'totp_enabled', 'totp_confirmed_at', 'updated_at']) + + def enable_totp(self, secret: str) -> None: + self.totp_secret = secret + self.totp_enabled = True + self.totp_confirmed_at = timezone.now() + self.save(update_fields=['totp_secret', 'totp_enabled', 'totp_confirmed_at', 'updated_at']) + class PortalBranding(models.Model): name = models.CharField(max_length=80, default='Default', unique=True) diff --git a/backend/workflows/static/workflows/css/account.css b/backend/workflows/static/workflows/css/account.css index 4c5d8ba..839bd3f 100644 --- a/backend/workflows/static/workflows/css/account.css +++ b/backend/workflows/static/workflows/css/account.css @@ -298,6 +298,34 @@ body { margin-bottom: 18px; } +.account-totp-card { + margin-bottom: 18px; + padding: 18px; + border-radius: 18px; + border: 1px solid #dbe5f2; + background: + radial-gradient(circle at top right, rgba(30, 64, 175, 0.08), transparent 28%), + #f9fbff; +} + +.account-totp-card h3 { + margin: 0 0 6px; + color: #132238; + font-size: 18px; +} + +.account-secret { + display: block; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 12px; + line-height: 1.55; + word-break: break-all; +} + +.account-totp-form { + margin-top: 14px; +} + .account-action-card { display: grid; gap: 6px; diff --git a/backend/workflows/static/workflows/css/admin_tools.css b/backend/workflows/static/workflows/css/admin_tools.css index 619303a..cf2d4af 100644 --- a/backend/workflows/static/workflows/css/admin_tools.css +++ b/backend/workflows/static/workflows/css/admin_tools.css @@ -12,6 +12,19 @@ h1 { margin: 12px 0 6px; color: #000078; } .branding-block-head { margin-bottom: 12px; } .branding-block-head h2 { margin: 0; color: #17345e; font-size: 18px; } .branding-block-head p { margin: 4px 0 0; color: #60738d; font-size: 13px; } +.branding-inline-head, .company-inline-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 14px; } +.branding-inline-trigger, .company-inline-trigger { min-width: 112px; } +.branding-inline-view.is-hidden, .branding-inline-form.is-hidden, .company-inline-view.is-hidden, .company-inline-form.is-hidden { display: none; } +.branding-inline-value, .company-inline-value { min-height: 40px; padding: 10px 12px; border: 1px solid #d9e4f1; border-radius: 10px; background: rgba(248,251,255,0.92); color: #18335b; line-height: 1.45; word-break: break-word; } +.branding-inline-actions, .company-inline-actions { display: flex; gap: 10px; margin-top: 14px; } +.branding-inline-error, .company-inline-error { margin-top: 6px; color: #ab1e1e; font-size: 12px; line-height: 1.4; } +.company-inline-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 14px; } +.company-inline-trigger { min-width: 112px; } +.company-inline-view.is-hidden, .company-inline-form.is-hidden { display: none; } +.company-inline-value { min-height: 40px; padding: 10px 12px; border: 1px solid #d9e4f1; border-radius: 10px; background: rgba(248,251,255,0.92); color: #18335b; line-height: 1.45; word-break: break-word; } +.company-inline-actions { display: flex; gap: 10px; margin-top: 14px; } +.company-inline-error { margin-top: 6px; color: #ab1e1e; font-size: 12px; line-height: 1.4; } +.field.has-error input, .field.has-error select, .field.has-error textarea { border-color: #e3a3a3; background: #fffafa; box-shadow: 0 0 0 4px rgba(185, 28, 28, 0.06); } .lang-pairs { align-items: start; } .lang-block { border: 1px solid #d9e4f1; border-radius: 14px; background: rgba(255,255,255,0.82); padding: 12px; } .lang-block h3 { margin: 0 0 10px; color: #223b63; font-size: 15px; } @@ -146,14 +159,18 @@ th { background: #f6f9ff; color: #334155; } .app-registry-card { border: 1px solid #d9e4f1; border-radius: 18px; background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(246,250,255,0.95)); padding: 16px; box-shadow: inset 0 1px 0 rgba(255,255,255,0.94); transition: border-color 180ms cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 220ms cubic-bezier(0.2, 0.8, 0.2, 1), transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1), opacity 180ms cubic-bezier(0.2, 0.8, 0.2, 1); } .app-registry-card:hover { transform: translateY(-1px); box-shadow: 0 12px 24px rgba(16, 32, 57, 0.06); border-color: #c9d8eb; } .app-registry-card.is-disabled { opacity: 0.84; } +.app-registry-card.is-dragging { opacity: 0.55; transform: rotate(0.4deg); box-shadow: 0 18px 28px rgba(16, 32, 57, 0.14); } .app-registry-card[hidden] { display: none !important; } .app-registry-card-head { display: flex; justify-content: space-between; align-items: start; gap: 14px; margin-bottom: 14px; } .app-registry-card-title-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; } .app-registry-card-title-row h2 { margin: 0; color: #17345e; font-size: 19px; } .app-registry-card-copy { margin: 8px 0 0; color: #60738d; max-width: 760px; } -.app-registry-summary { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(260px, 0.9fr); gap: 16px; align-items: center; list-style: none; cursor: pointer; } +.app-registry-summary { display: grid; grid-template-columns: 28px minmax(0, 1.5fr) minmax(260px, 0.9fr); gap: 16px; align-items: center; list-style: none; cursor: pointer; } .app-registry-summary::-webkit-details-marker { display: none; } .app-registry-summary::marker { display: none; } +.app-registry-drag-handle { display: inline-flex; align-items: center; justify-content: center; width: 28px; min-height: 42px; border-radius: 10px; border: 1px dashed #cbd7e6; background: #f8fbff; color: #5f6f85; font-size: 15px; letter-spacing: 0.04em; cursor: grab; user-select: none; } +.app-registry-card.is-dragging .app-registry-drag-handle { cursor: grabbing; } +.app-registry-card.drag-disabled .app-registry-drag-handle { opacity: 0.4; cursor: not-allowed; border-style: solid; } .app-registry-summary-main { min-width: 0; } .app-registry-summary-meta { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; align-items: center; } .app-registry-card-grid { display: grid; grid-template-columns: repeat(2, minmax(260px, 1fr)); gap: 12px; align-items: start; } @@ -187,6 +204,8 @@ th { background: #f6f9ff; color: #334155; } .actions { white-space: nowrap; } @media (max-width: 760px) { .grid { grid-template-columns: 1fr; } + .branding-inline-head, .company-inline-head { flex-direction: column; } + .branding-inline-actions, .company-inline-actions { flex-direction: column; } .trial-summary-grid { grid-template-columns: 1fr 1fr; } .trial-expired-shell { padding: 20px 16px 28px; } .trial-expired-card { padding: 18px; } @@ -200,3 +219,9 @@ th { background: #f6f9ff; color: #334155; } .app-registry-copy-panel { grid-column: auto; } .app-registry-savebar { align-items: stretch; flex-direction: column; } } +.app-registry-groups { display: grid; gap: 18px; } +.app-registry-group { border: 1px solid #d7e3f0; border-radius: 18px; background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(244,248,255,0.95)); padding: 14px; box-shadow: inset 0 1px 0 rgba(255,255,255,0.94); } +.app-registry-group-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; margin-bottom: 14px; } +.app-registry-group-head h2 { margin: 0; color: #17345e; font-size: 18px; } +.app-registry-group-body { display: grid; gap: 14px; } +.app-registry-group[hidden] { display: none !important; } diff --git a/backend/workflows/templates/workflows/account_profile.html b/backend/workflows/templates/workflows/account_profile.html index 10a5202..6124e0c 100644 --- a/backend/workflows/templates/workflows/account_profile.html +++ b/backend/workflows/templates/workflows/account_profile.html @@ -180,12 +180,100 @@ {% trans "Aktualisieren Sie Ihr Passwort direkt im Konto." %} +
{% trans "Aktivieren Sie TOTP mit einer Authenticator-App. Standardmäßig bleibt es ausgeschaltet." %}
+{% trans "Wenn Ihre App keinen QR-Code scannen kann, tragen Sie den Schlüssel oder den otpauth-Link manuell ein." %}
+ + {% endif %} ++ {% if section_key == 'platform' %} + {% trans "Produktweite Steuerung und nur für die Platform sichtbare Oberflächen." %} + {% elif section_key == 'admin' %} + {% trans "Administrative Apps für Kundenrollen mit erhöhter Verantwortung." %} {% else %} - {% trans "Deaktiviert" %} + {% trans "Operative Apps, die im täglichen Einsatz auf der Landing Page erscheinen." %} {% endif %} -
{{ row.definition.description }}
-{% trans "Empfohlener Standardzugriff:" %} {{ row.default_visibility_summary }}
+{% trans "Deaktivierte Apps erscheinen nicht auf der Landing Page, selbst wenn Rollen sie sehen dürften." %}
-{% trans "Wenn keine Firmenrolle aktiv ist, bleibt die App nur für die Platform sichtbar." %}
-{{ row.definition.description }}
+{% trans "Empfohlener Standardzugriff:" %} {{ row.default_visibility_summary }}
+{% trans "Deaktivierte Apps erscheinen nicht auf der Landing Page, selbst wenn Rollen sie sehen dürften." %}
+{% trans "Wenn keine Firmenrolle aktiv ist, bleibt die App nur für die Platform sichtbar." %}
+