feat: Add risk matrix view and related functionality

- Implemented a new view for the risk matrix, allowing users to visualize risks based on their impact and likelihood.
- Added filters for category, asset, and process in the risk listing view.
- Enhanced risk listing template to include new filters and improved layout.
- Introduced new CSS variables for better color management in the design.
- Updated existing template tags to support new functionalities, including score background class mapping.
- Modified existing risk listing to display residual risk details alongside gross risk.
- Added new risk matrix HTML template with tabbed interface for gross and net risk views.
This commit is contained in:
Kevin Heyer 2025-09-11 10:22:20 +02:00
parent ed300ca403
commit 9d02badf14
11 changed files with 1038 additions and 270 deletions

Binary file not shown.

Binary file not shown.

View file

@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: wira-risk-management\n" "Project-Id-Version: wira-risk-management\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-10 13:44+0200\n" "POT-Creation-Date: 2025-09-11 10:17+0200\n"
"PO-Revision-Date: 2025-09-09 13:45+0200\n" "PO-Revision-Date: 2025-09-09 13:45+0200\n"
"Last-Translator: Kevin Heyer <kevin@example.com>\n" "Last-Translator: Kevin Heyer <kevin@example.com>\n"
"Language-Team: German\n" "Language-Team: German\n"
@ -22,12 +22,12 @@ msgstr "Admin"
#: risks/admin.py:15 risks/models.py:36 templates/risks/dashboard.html:75 #: risks/admin.py:15 risks/models.py:36 templates/risks/dashboard.html:75
#: templates/risks/dashboard.html:80 templates/risks/dashboard.html:85 #: templates/risks/dashboard.html:80 templates/risks/dashboard.html:85
#: templates/risks/list_risks.html:76 #: templates/risks/list_risks.html:97
msgid "Risks" msgid "Risks"
msgstr "Risiken" msgstr "Risiken"
#: risks/admin.py:16 risks/models.py:190 templates/base.html:36 #: risks/admin.py:16 risks/models.py:190 templates/base.html:38
#: templates/risks/list_risks.html:37 #: templates/risks/list_risks.html:31
msgid "Controls" msgid "Controls"
msgstr "Maßnahmen" msgstr "Maßnahmen"
@ -39,8 +39,8 @@ msgstr "Restrisiken"
msgid "Reviews" msgid "Reviews"
msgstr "Prüfung" msgstr "Prüfung"
#: risks/admin.py:19 risks/models.py:258 templates/base.html:37 #: risks/admin.py:19 risks/models.py:258 templates/base.html:39
#: templates/risks/item_risk.html:248 #: templates/risks/item_risk.html:252
msgid "Incidents" msgid "Incidents"
msgstr "Vorfälle" msgstr "Vorfälle"
@ -48,13 +48,13 @@ msgstr "Vorfälle"
msgid "Users" msgid "Users"
msgstr "Benutzer" msgstr "Benutzer"
#: risks/admin.py:133 risks/models.py:302 templates/risks/item_risk.html:287 #: risks/admin.py:133 risks/models.py:326 templates/risks/item_risk.html:291
msgid "User" msgid "User"
msgstr "Benutzer" msgstr "Benutzer"
#: risks/admin.py:139 #: risks/admin.py:139
msgid "Message" msgid "Message"
msgstr "" msgstr "Nachricht"
#: risks/admin.py:147 #: risks/admin.py:147
msgid "Mark selected as read" msgid "Mark selected as read"
@ -76,9 +76,10 @@ msgstr "%(n)d Benachrichtigungen wurden als ungelesen Markiert"
#: risks/admin.py:157 #: risks/admin.py:157
msgid "Mark selected as sent" msgid "Mark selected as sent"
msgstr "" msgstr "Auswahl als gelesen markieren"
#: risks/admin.py:160 #: risks/admin.py:160
#, python-format
msgid "%(n)d notifications marked as sent." msgid "%(n)d notifications marked as sent."
msgstr "%(n)d Benachrichtigungen wurden als gelesen Markiert" msgstr "%(n)d Benachrichtigungen wurden als gelesen Markiert"
@ -87,18 +88,23 @@ msgid "Mark selected as unsent"
msgstr "Auswahl als ungesendet markieren" msgstr "Auswahl als ungesendet markieren"
#: risks/admin.py:165 #: risks/admin.py:165
#, python-format
msgid "%(n)d notifications marked as unsent." msgid "%(n)d notifications marked as unsent."
msgstr "%(n)d Benachrichtigungen wurden als gelesen Markiert" msgstr "%(n)d Benachrichtigungen wurden als gelesen Markiert"
#: risks/admin.py:177 #: risks/admin.py:182
msgid "Extra recipients"
msgstr "Zusätzliche Empfänger"
#: risks/admin.py:190
msgid "SSO Information" msgid "SSO Information"
msgstr "SSO-Informationen" msgstr "SSO-Informationen"
#: risks/admin.py:186 #: risks/admin.py:199
msgid "Risks Owned" msgid "Risks Owned"
msgstr "Eigene Risiken" msgstr "Eigene Risiken"
#: risks/admin.py:190 #: risks/admin.py:203
msgid "Controls Responsible" msgid "Controls Responsible"
msgstr "Verantwortlich für Maßnahmen" msgstr "Verantwortlich für Maßnahmen"
@ -107,17 +113,17 @@ msgid "Risk Management"
msgstr "Risikomanagement" msgstr "Risikomanagement"
#: risks/forms.py:9 risks/forms.py:16 risks/forms.py:23 risks/models.py:73 #: risks/forms.py:9 risks/forms.py:16 risks/forms.py:23 risks/models.py:73
#: templates/risks/item_risk.html:64 templates/risks/item_risk.html:202 #: templates/risks/item_risk.html:64 templates/risks/item_risk.html:206
#: templates/risks/item_risk.html:256 #: templates/risks/item_risk.html:260
msgid "Status" msgid "Status"
msgstr "Status" msgstr "Status"
#: risks/forms.py:30 risks/models.py:42 templates/risks/item_risk.html:177 #: risks/forms.py:30 risks/models.py:42 templates/risks/item_risk.html:181
msgid "Review required" msgid "Review required"
msgstr "Prüfung nötig" msgstr "Prüfung nötig"
#: risks/models.py:35 templates/risks/item_risk.html:11 #: risks/models.py:35 templates/risks/item_risk.html:11
#: templates/risks/list_risks.html:18 templates/risks/list_risks.html:83 #: templates/risks/list_risks.html:18 templates/risks/list_risks.html:110
msgid "Risk" msgid "Risk"
msgstr "Risiko" msgstr "Risiko"
@ -182,7 +188,7 @@ msgid "Availability"
msgstr "Verfügbarkeit" msgstr "Verfügbarkeit"
#: risks/models.py:64 risks/models.py:200 risks/models.py:265 #: risks/models.py:64 risks/models.py:200 risks/models.py:265
#: templates/risks/item_risk.html:201 #: templates/risks/item_risk.html:205
msgid "Title" msgid "Title"
msgstr "Titel" msgstr "Titel"
@ -191,15 +197,17 @@ msgid "Description"
msgstr "Beschreibung" msgstr "Beschreibung"
#: risks/models.py:66 templates/risks/item_risk.html:53 #: risks/models.py:66 templates/risks/item_risk.html:53
#: templates/risks/list_risks.html:57
msgid "Asset" msgid "Asset"
msgstr "Asset" msgstr "Asset"
#: risks/models.py:67 templates/risks/item_risk.html:54 #: risks/models.py:67 templates/risks/item_risk.html:54
#: templates/risks/list_risks.html:70
msgid "Process" msgid "Process"
msgstr "Prozess" msgstr "Prozess"
#: risks/models.py:68 templates/risks/item_risk.html:55 #: risks/models.py:68 templates/risks/item_risk.html:55
#: templates/risks/list_risks.html:85 #: templates/risks/list_risks.html:44 templates/risks/list_risks.html:112
msgid "Category" msgid "Category"
msgstr "Kategorie" msgstr "Kategorie"
@ -211,6 +219,10 @@ msgstr "Erstellt am"
msgid "Updated at" msgid "Updated at"
msgstr "Aktualisiert am" msgstr "Aktualisiert am"
#: risks/models.py:71
msgid "Effects"
msgstr "Auswirkungen"
#: risks/models.py:133 #: risks/models.py:133
msgid "Residual Risk" msgid "Residual Risk"
msgstr "Restrisiko" msgstr "Restrisiko"
@ -251,7 +263,7 @@ msgstr "Audit-Log"
msgid "Auditlogs" msgid "Auditlogs"
msgstr "Audit-Logs" msgstr "Audit-Logs"
#: risks/models.py:257 templates/risks/item_risk.html:255 #: risks/models.py:257 templates/risks/item_risk.html:259
msgid "Incident" msgid "Incident"
msgstr "Vorfall" msgstr "Vorfall"
@ -267,15 +279,120 @@ msgstr "Meldedatum"
msgid "Reported by" msgid "Reported by"
msgstr "Gemeldet von" msgstr "Gemeldet von"
#: risks/models.py:279 #: risks/models.py:279 risks/utils.py:119
msgid "Notification" msgid "Notification"
msgstr "Benachrichtigung" msgstr "Benachrichtigung"
#: risks/models.py:280 templates/base.html:78 #: risks/models.py:280 templates/base.html:80
#: templates/risks/notifications.html:4 #: templates/risks/notifications.html:4
msgid "Notifications" msgid "Notifications"
msgstr "Nachrichten" msgstr "Nachrichten"
#: risks/models.py:295
msgid "Risk created"
msgstr "Risiko erstellt"
#: risks/models.py:296
msgid "Risk updated"
msgstr "Risiko Aktualisiert"
#: risks/models.py:297
msgid "Risk deleted"
msgstr "Risiko gelöscht"
#: risks/models.py:298
msgid "Risk review required"
msgstr "Risikoprüfung nötig"
#: risks/models.py:299
msgid "Risk review completed"
msgstr "Risikoprüfung Abgeschlossen"
#: risks/models.py:301
msgid "Control created"
msgstr "Maßnahme erstellt"
#: risks/models.py:302
msgid "Control updated"
msgstr "Maßnahme Aktualisiert"
#: risks/models.py:303
msgid "Control deleted"
msgstr "Maßnahme '{title}' gelöscht"
#: risks/models.py:305
msgid "Residual created"
msgstr "Restrisiko erstellt"
#: risks/models.py:306
msgid "Residual updated"
msgstr "Restrisiko Aktualisiert"
#: risks/models.py:307
msgid "Residual deleted"
msgstr "Restrisiko gelöscht"
#: risks/models.py:308
msgid "Residual review required"
msgstr "Restrisikoprüfung nötig"
#: risks/models.py:309
msgid "Residual review completed"
msgstr "Restrisiko geprüft"
#: risks/models.py:311
msgid "Incident created"
msgstr "Vorfall erstellt"
#: risks/models.py:312
msgid "Incident updated"
msgstr "Vorfall Aktualisiert"
#: risks/models.py:313
msgid "Incident deleted"
msgstr "Vorfall gelöscht"
#: risks/models.py:315
msgid "User created"
msgstr "Benutzer erstellt"
#: risks/models.py:316
msgid "User deleted"
msgstr "Benutzer gelöscht"
#: risks/models.py:371
msgid "Notification rule"
msgstr "Benachrichtigungsregel"
#: risks/models.py:372
msgid "Notification rules"
msgstr "Benachrichtigungsregeln"
#: risks/models.py:375
msgid "Event"
msgstr "Aktion"
#: risks/models.py:380
msgid "Show in app"
msgstr "Zeige in der WebApp"
#: risks/models.py:381
msgid "Send via email"
msgstr "Sende via E-Mail"
#: risks/models.py:385
msgid "Send to owner/responsible/reporter (if available)"
msgstr "Sende an Risikoeigner/Verantwortliche/Melder (Wenn vorhanden)"
#: risks/models.py:389
msgid "Send to all staff"
msgstr "Sende an alle App-Mitarbeiter"
#: risks/models.py:393
msgid "Extra recipients (emails, comma or newline separated)"
msgstr ""
"Zusätzliche Empfänger (E-Mails, durch Komma oder Zeilenumbruch getrennt)"
#: risks/signals.py:57 #: risks/signals.py:57
#, python-brace-format #, python-brace-format
msgid "User '{u}' created" msgid "User '{u}' created"
@ -291,13 +408,13 @@ msgstr "Benutzer '{u}' löschte"
msgid "Risk '{title}' {state}" msgid "Risk '{title}' {state}"
msgstr "Risiko '{title}' {state}" msgstr "Risiko '{title}' {state}"
#: risks/signals.py:72 risks/signals.py:147 risks/signals.py:240 #: risks/signals.py:72 risks/signals.py:166 risks/signals.py:212
#: risks/signals.py:296 #: risks/signals.py:275 risks/signals.py:369 risks/signals.py:411
msgid "created" msgid "created"
msgstr "erstellt" msgstr "erstellt"
#: risks/signals.py:72 risks/signals.py:147 risks/signals.py:240 #: risks/signals.py:72 risks/signals.py:166 risks/signals.py:212
#: risks/signals.py:296 #: risks/signals.py:275 risks/signals.py:369 risks/signals.py:411
msgid "updated" msgid "updated"
msgstr "Aktualisiert" msgstr "Aktualisiert"
@ -306,77 +423,137 @@ msgstr "Aktualisiert"
msgid "Risk '{title}' deleted" msgid "Risk '{title}' deleted"
msgstr "Risiko '{title}' gelöscht" msgstr "Risiko '{title}' gelöscht"
#: risks/signals.py:145 #: risks/signals.py:117
#, python-brace-format
msgid "Risk created: {t}"
msgstr "Risiko erstellt: {t}"
#: risks/signals.py:123
#, python-brace-format
msgid "Risk updated: {t}"
msgstr "Risiko Aktualisiert: {t}"
#: risks/signals.py:143
#, python-brace-format
msgid "Risk deleted: {t}"
msgstr "Risiko gelöscht: {t}"
#: risks/signals.py:164
#, python-brace-format #, python-brace-format
msgid "Control '{title}' {state}" msgid "Control '{title}' {state}"
msgstr "Maßnahme '{title}' {state}" msgstr "Maßnahme '{title}' {state}"
#: risks/signals.py:154 #: risks/signals.py:173
#, python-brace-format #, python-brace-format
msgid "Control '{title}' deleted" msgid "Control '{title}' deleted"
msgstr "Maßnahme '{title}' gelöscht" msgstr "Maßnahme '{title}' gelöscht"
#: risks/signals.py:211 #: risks/signals.py:211
#, python-brace-format #, python-brace-format
msgid "Control {event}: {t}"
msgstr "Maßnahme {event}: {t}"
#: risks/signals.py:231
#, python-brace-format
msgid "Control deleted: {t}"
msgstr "Maßnahme gelöscht: {t}"
#: risks/signals.py:246
#, python-brace-format
msgid "Review required for risk '{t}' due to control change" msgid "Review required for risk '{t}' due to control change"
msgstr "Prüfung nötig für: '{t}', da Maßnahmen geändert wurden" msgstr "Prüfung nötig für: '{t}', da Maßnahmen geändert wurden"
#: risks/signals.py:230 #: risks/signals.py:265
#, python-brace-format #, python-brace-format
msgid "Review required for risk '{t}'" msgid "Review required for risk '{t}'"
msgstr "Prüfung benötigt für Risiko '{t}'" msgstr "Prüfung benötigt für Risiko '{t}'"
#: risks/signals.py:235 #: risks/signals.py:270
#, python-brace-format #, python-brace-format
msgid "Review completed for risk '{t}'" msgid "Review completed for risk '{t}'"
msgstr "Prüfung Abgeschlossen für Risiko '{t}'" msgstr "Prüfung Abgeschlossen für Risiko '{t}'"
#: risks/signals.py:239 #: risks/signals.py:274
#, python-brace-format #, python-brace-format
msgid "Residual risk {state} for '{t}'" msgid "Residual risk {state} for '{t}'"
msgstr "Restrisiko {state} für '{t}'" msgstr "Restrisiko {state} für '{t}'"
#: risks/signals.py:244 #: risks/signals.py:279
#, python-brace-format #, python-brace-format
msgid "Residual risk deleted for '{t}'" msgid "Residual risk deleted for '{t}'"
msgstr "Restrisiko für '{t}' gelöscht" msgstr "Restrisiko für '{t}' gelöscht"
#: risks/signals.py:296 #: risks/signals.py:316
#, python-brace-format
msgid "Residual created for risk: {t}"
msgstr "Restrisiko erstellt für das Risiko: {t}"
#: risks/signals.py:328
#, python-brace-format
msgid "Residual review required for risk: {t}"
msgstr "Restrisikoprüfung benötigt für Risiko {t}"
#: risks/signals.py:334
#, python-brace-format
msgid "Residual review completed for risk: {t}"
msgstr "Restrisikoprüfung Abgeschlossen für Risiko {t}"
#: risks/signals.py:340
#, python-brace-format
msgid "Residual updated for risk: {t}"
msgstr "Restrisiko Aktualisiert für das Risiko: {t}"
#: risks/signals.py:358
#, python-brace-format
msgid "Residual deleted for risk: {t}"
msgstr "Restrisiko gelöscht für das Risiko: {t}"
#: risks/signals.py:369
#, python-brace-format #, python-brace-format
msgid "Incident '{t}' {s}" msgid "Incident '{t}' {s}"
msgstr "Vorfälle '{t}' {s}" msgstr "Vorfall '{t}' {s}"
#: risks/signals.py:301 #: risks/signals.py:374
#, python-brace-format #, python-brace-format
msgid "Incident '{t}' deleted" msgid "Incident '{t}' deleted"
msgstr "Vorfall '{t}' gelöscht" msgstr "Vorfall '{t}' gelöscht"
#: risks/utils.py:48 #: risks/signals.py:410
#, python-brace-format
msgid "Incident {event}: {t}"
msgstr "Vorfall {event}: {t}"
#: risks/signals.py:442
#, python-brace-format
msgid "Incident deleted: {t}"
msgstr "Vorfall gelöscht: {t}"
#: risks/utils.py:53 risks/utils.py:67
#, python-brace-format #, python-brace-format
msgid "Follow-up reached: review required for risk '{t}'" msgid "Follow-up reached: review required for risk '{t}'"
msgstr "Wiedervorlagedatum erreicht: Prüfung nötig für Risiko '{t}'" msgstr "Wiedervorlagedatum erreicht: Prüfung nötig für Risiko '{t}'"
#: risks/views.py:315 #: risks/views.py:345
msgid "Notification marked as read." msgid "Notification marked as read."
msgstr "Nachricht als gelesen markiert" msgstr "Nachricht als gelesen markiert"
#: risks/views.py:323 #: risks/views.py:353
msgid "All notifications marked as read." msgid "All notifications marked as read."
msgstr "Alle Benachrichtigungen wurden als gelesen Markiert" msgstr "Alle Benachrichtigungen wurden als gelesen Markiert"
#: risks/views.py:340 #: risks/views.py:370
msgid "Risk status updated." msgid "Risk status updated."
msgstr "Risikostatus Aktualisiert" msgstr "Risikostatus Aktualisiert"
#: risks/views.py:354 #: risks/views.py:384
msgid "Control status updated." msgid "Control status updated."
msgstr "Maßnahmenstatus Aktualisiert" msgstr "Maßnahmenstatus Aktualisiert"
#: risks/views.py:368 #: risks/views.py:398
msgid "Incident status updated." msgid "Incident status updated."
msgstr "Vorfallstatus Aktualisiert" msgstr "Vorfallstatus Aktualisiert"
#: risks/views.py:384 #: risks/views.py:414
msgid "Residual review flag updated." msgid "Residual review flag updated."
msgstr "Restrisiko geprüft" msgstr "Restrisiko geprüft"
@ -384,32 +561,37 @@ msgstr "Restrisiko geprüft"
msgid "Dashboard" msgid "Dashboard"
msgstr "Dashboard" msgstr "Dashboard"
#: templates/base.html:35 templates/risks/item_risk.html:4 #: templates/base.html:35 templates/risks/risk_matrix.html:4
#: templates/risks/risk_matrix.html:12 templates/risks/risk_matrix.html:18
msgid "Risk Matrix"
msgstr "Risikomatrix"
#: templates/base.html:37 templates/risks/item_risk.html:4
#: templates/risks/list_risks.html:4 #: templates/risks/list_risks.html:4
msgid "Risk analysis" msgid "Risk analysis"
msgstr "Risikoanalyse" msgstr "Risikoanalyse"
#: templates/base.html:73 #: templates/base.html:75
msgid "AdminCP" msgid "AdminCP"
msgstr "Adminbereich" msgstr "Adminbereich"
#: templates/base.html:86 #: templates/base.html:88
msgid "Derk Mode" msgid "Derk Mode"
msgstr "Dark Mode" msgstr "Dark Mode"
#: templates/base.html:92 #: templates/base.html:94
msgid "Logout" msgid "Logout"
msgstr "Logout" msgstr "Logout"
#: templates/base.html:107 #: templates/base.html:109
msgid "Login" msgid "Login"
msgstr "Login" msgstr "Login"
#: templates/base.html:142 templates/base.html:149 #: templates/base.html:144 templates/base.html:151
msgid "Light Mode" msgid "Light Mode"
msgstr "Light Mode" msgstr "Light Mode"
#: templates/base.html:152 #: templates/base.html:154
msgid "Dark Mode" msgid "Dark Mode"
msgstr "Dark Mode" msgstr "Dark Mode"
@ -471,99 +653,100 @@ msgstr "Aktualisiert am"
msgid "Resubmission" msgid "Resubmission"
msgstr "Wiedervorlagedatum" msgstr "Wiedervorlagedatum"
#: templates/risks/item_risk.html:76 #: templates/risks/item_risk.html:80
msgid "Risk assessment" msgid "Risk assessment"
msgstr "Risikomanagement" msgstr "Risikomanagement"
#: templates/risks/item_risk.html:84 #: templates/risks/item_risk.html:88
msgid "Gross (before measures)" msgid "Gross (before measures)"
msgstr "Brutto (vor Maßnahmen)" msgstr "Brutto (vor Maßnahmen)"
#: templates/risks/item_risk.html:89 templates/risks/item_risk.html:135 #: templates/risks/item_risk.html:93 templates/risks/item_risk.html:139
#: templates/risks/list_risks.html:86 #: templates/risks/list_risks.html:118 templates/risks/list_risks.html:122
#: templates/risks/risk_matrix.html:25
msgid "Likelihood" msgid "Likelihood"
msgstr "Eintritt" msgstr "Eintritt"
#: templates/risks/item_risk.html:90 templates/risks/item_risk.html:136 #: templates/risks/item_risk.html:94 templates/risks/item_risk.html:140
msgid "Probability of occurrence" msgid "Probability of occurrence"
msgstr "Eintrittswahrscheinlichkeit" msgstr "Eintrittswahrscheinlichkeit"
#: templates/risks/item_risk.html:98 templates/risks/item_risk.html:144 #: templates/risks/item_risk.html:102 templates/risks/item_risk.html:148
#: templates/risks/list_risks.html:87 #: templates/risks/list_risks.html:119 templates/risks/list_risks.html:123
#: templates/risks/risk_matrix.html:25
msgid "Impact" msgid "Impact"
msgstr "Schaden" msgstr "Schaden"
#: templates/risks/item_risk.html:99 templates/risks/item_risk.html:145 #: templates/risks/item_risk.html:103 templates/risks/item_risk.html:149
msgid "Extent of damage" msgid "Extent of damage"
msgstr "Schadensausmaß" msgstr "Schadensausmaß"
#: templates/risks/item_risk.html:107 templates/risks/item_risk.html:108 #: templates/risks/item_risk.html:111 templates/risks/item_risk.html:112
#: templates/risks/item_risk.html:153 templates/risks/item_risk.html:154 #: templates/risks/item_risk.html:157 templates/risks/item_risk.html:158
#: templates/risks/list_risks.html:89 #: templates/risks/list_risks.html:121 templates/risks/list_risks.html:125
msgid "Level" msgid "Level"
msgstr "Stufe" msgstr "Stufe"
#: templates/risks/item_risk.html:116 templates/risks/item_risk.html:117 #: templates/risks/item_risk.html:120 templates/risks/item_risk.html:121
#: templates/risks/item_risk.html:161 templates/risks/item_risk.html:163 #: templates/risks/item_risk.html:165 templates/risks/item_risk.html:167
#: templates/risks/list_risks.html:88 #: templates/risks/list_risks.html:120 templates/risks/list_risks.html:124
#: templates/risks/risk_matrix.html:39
msgid "Score" msgid "Score"
msgstr "Score" msgstr "Score"
#: templates/risks/item_risk.html:130 #: templates/risks/item_risk.html:134
msgid "Net (after measures)" msgid "Net (after measures)"
msgstr "Netto (nach Maßnahmen)" msgstr "Netto (nach Maßnahmen)"
#: templates/risks/item_risk.html:169 #: templates/risks/item_risk.html:173
msgid "No net risk recorded yet." msgid "No net risk recorded yet."
msgstr "Kein Restrisiko vergeben" msgstr "Kein Restrisiko vergeben"
#: templates/risks/item_risk.html:180 #: templates/risks/item_risk.html:184
msgid "Save" msgid "Save"
msgstr "Speichern" msgstr "Speichern"
#: templates/risks/item_risk.html:194 #: templates/risks/item_risk.html:198
msgid "Measures" msgid "Measures"
msgstr "Maßnahmen" msgstr "Maßnahmen"
#: templates/risks/item_risk.html:203 #: templates/risks/item_risk.html:207
msgid "Deadline" msgid "Deadline"
msgstr "Frist" msgstr "Frist"
#: templates/risks/item_risk.html:204 #: templates/risks/item_risk.html:208
msgid "Responsible" msgid "Responsible"
msgstr "Verantwortliche/r" msgstr "Verantwortliche/r"
#: templates/risks/item_risk.html:205 #: templates/risks/item_risk.html:209
msgid "Link" msgid "Link"
msgstr "Link" msgstr "Link"
#: templates/risks/item_risk.html:239 #: templates/risks/item_risk.html:243
msgid "No measures recorded." msgid "No measures recorded."
msgstr "Keine Maßnahmen gefunden." msgstr "Keine Maßnahmen gefunden."
#: templates/risks/item_risk.html:257 #: templates/risks/item_risk.html:261
#, fuzzy
#| msgid "Reported by"
msgid "Reported on" msgid "Reported on"
msgstr "Gemeldet von" msgstr "Gemeldet am"
#: templates/risks/item_risk.html:271 #: templates/risks/item_risk.html:275
msgid "No incidents recorded." msgid "No incidents recorded."
msgstr "Keine Vorfälle gefunden." msgstr "Keine Vorfälle gefunden."
#: templates/risks/item_risk.html:279 #: templates/risks/item_risk.html:283
msgid "History" msgid "History"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:286 #: templates/risks/item_risk.html:290
msgid "Time" msgid "Time"
msgstr "Zeitpunkt" msgstr "Zeitpunkt"
#: templates/risks/item_risk.html:288 #: templates/risks/item_risk.html:292
msgid "Action" msgid "Action"
msgstr "Aktion" msgstr "Aktion"
#: templates/risks/item_risk.html:302 #: templates/risks/item_risk.html:306
msgid "No History found." msgid "No History found."
msgstr "Keine Historie vorhanden" msgstr "Keine Historie vorhanden"
@ -571,20 +754,33 @@ msgstr "Keine Historie vorhanden"
msgid "Filter" msgid "Filter"
msgstr "Filter" msgstr "Filter"
#: templates/risks/list_risks.html:22 templates/risks/list_risks.html:41 #: templates/risks/list_risks.html:83
#: templates/risks/list_risks.html:60 templates/risks/notifications.html:13 #, fuzzy
msgid "All" #| msgid "Risk Owner"
msgstr "Alle" msgid "Owner"
#: templates/risks/list_risks.html:56 templates/risks/list_risks.html:90
msgid "Risk Owner"
msgstr "Risikoeigner" msgstr "Risikoeigner"
#: templates/risks/list_risks.html:84 #: templates/risks/list_risks.html:111
msgid "Asset / Process" msgid "Asset / Process"
msgstr "Asset / Prozess" msgstr "Asset / Prozess"
#: templates/risks/list_risks.html:152 #: templates/risks/list_risks.html:113
msgid "Risk Owner"
msgstr "Risikoeigner"
#: templates/risks/list_risks.html:114
msgid "Gross Risk"
msgstr "Bruttorisiko"
#: templates/risks/list_risks.html:115
msgid "Net Risk"
msgstr "Nettorisiko"
#: templates/risks/list_risks.html:195
msgid "No residual risk defined"
msgstr "Noch kein Restrisiko definiert"
#: templates/risks/list_risks.html:201
msgid "No risks present" msgid "No risks present"
msgstr "Aktuell keine Risiken" msgstr "Aktuell keine Risiken"
@ -592,6 +788,10 @@ msgstr "Aktuell keine Risiken"
msgid "Unread" msgid "Unread"
msgstr "Ungelesen" msgstr "Ungelesen"
#: templates/risks/notifications.html:13
msgid "All"
msgstr "Alle"
#: templates/risks/notifications.html:20 #: templates/risks/notifications.html:20
msgid "Mark all as read" msgid "Mark all as read"
msgstr "Alle als gelesen Markieren" msgstr "Alle als gelesen Markieren"
@ -607,3 +807,23 @@ msgstr "Als gelesen markieren"
#: templates/risks/notifications.html:53 #: templates/risks/notifications.html:53
msgid "No notifications." msgid "No notifications."
msgstr "Keine Nachrichten" msgstr "Keine Nachrichten"
#: templates/risks/risk_matrix.html:13
msgid "Detail View"
msgstr "Detailansicht"
#: templates/risks/risk_matrix.html:54
msgid "Show"
msgstr "Zeige"
#: templates/risks/risk_matrix.html:58
msgid "Gross"
msgstr "Brutto"
#: templates/risks/risk_matrix.html:61
msgid "Net"
msgstr "Netto"
#: templates/risks/risk_matrix.html:73 templates/risks/risk_matrix.html:115
msgid "Impact \\\\ Likelihood"
msgstr "Schadensausmaß \\\\ Eintrittswahrscheinlichkeit"

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-10 13:44+0200\n" "POT-Creation-Date: 2025-09-11 10:17+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -28,12 +28,12 @@ msgstr ""
#: risks/admin.py:15 risks/models.py:36 templates/risks/dashboard.html:75 #: risks/admin.py:15 risks/models.py:36 templates/risks/dashboard.html:75
#: templates/risks/dashboard.html:80 templates/risks/dashboard.html:85 #: templates/risks/dashboard.html:80 templates/risks/dashboard.html:85
#: templates/risks/list_risks.html:76 #: templates/risks/list_risks.html:97
msgid "Risks" msgid "Risks"
msgstr "" msgstr ""
#: risks/admin.py:16 risks/models.py:190 templates/base.html:36 #: risks/admin.py:16 risks/models.py:190 templates/base.html:38
#: templates/risks/list_risks.html:37 #: templates/risks/list_risks.html:31
msgid "Controls" msgid "Controls"
msgstr "" msgstr ""
@ -45,8 +45,8 @@ msgstr ""
msgid "Reviews" msgid "Reviews"
msgstr "" msgstr ""
#: risks/admin.py:19 risks/models.py:258 templates/base.html:37 #: risks/admin.py:19 risks/models.py:258 templates/base.html:39
#: templates/risks/item_risk.html:248 #: templates/risks/item_risk.html:252
msgid "Incidents" msgid "Incidents"
msgstr "" msgstr ""
@ -54,7 +54,7 @@ msgstr ""
msgid "Users" msgid "Users"
msgstr "" msgstr ""
#: risks/admin.py:133 risks/models.py:302 templates/risks/item_risk.html:287 #: risks/admin.py:133 risks/models.py:326 templates/risks/item_risk.html:291
msgid "User" msgid "User"
msgstr "" msgstr ""
@ -98,15 +98,19 @@ msgstr ""
msgid "%(n)d notifications marked as unsent." msgid "%(n)d notifications marked as unsent."
msgstr "" msgstr ""
#: risks/admin.py:177 #: risks/admin.py:182
msgid "SSO Information" msgid "Extra recipients"
msgstr ""
#: risks/admin.py:186
msgid "Risks Owned"
msgstr "" msgstr ""
#: risks/admin.py:190 #: risks/admin.py:190
msgid "SSO Information"
msgstr ""
#: risks/admin.py:199
msgid "Risks Owned"
msgstr ""
#: risks/admin.py:203
msgid "Controls Responsible" msgid "Controls Responsible"
msgstr "" msgstr ""
@ -115,17 +119,17 @@ msgid "Risk Management"
msgstr "" msgstr ""
#: risks/forms.py:9 risks/forms.py:16 risks/forms.py:23 risks/models.py:73 #: risks/forms.py:9 risks/forms.py:16 risks/forms.py:23 risks/models.py:73
#: templates/risks/item_risk.html:64 templates/risks/item_risk.html:202 #: templates/risks/item_risk.html:64 templates/risks/item_risk.html:206
#: templates/risks/item_risk.html:256 #: templates/risks/item_risk.html:260
msgid "Status" msgid "Status"
msgstr "" msgstr ""
#: risks/forms.py:30 risks/models.py:42 templates/risks/item_risk.html:177 #: risks/forms.py:30 risks/models.py:42 templates/risks/item_risk.html:181
msgid "Review required" msgid "Review required"
msgstr "" msgstr ""
#: risks/models.py:35 templates/risks/item_risk.html:11 #: risks/models.py:35 templates/risks/item_risk.html:11
#: templates/risks/list_risks.html:18 templates/risks/list_risks.html:83 #: templates/risks/list_risks.html:18 templates/risks/list_risks.html:110
msgid "Risk" msgid "Risk"
msgstr "" msgstr ""
@ -190,7 +194,7 @@ msgid "Availability"
msgstr "" msgstr ""
#: risks/models.py:64 risks/models.py:200 risks/models.py:265 #: risks/models.py:64 risks/models.py:200 risks/models.py:265
#: templates/risks/item_risk.html:201 #: templates/risks/item_risk.html:205
msgid "Title" msgid "Title"
msgstr "" msgstr ""
@ -199,15 +203,17 @@ msgid "Description"
msgstr "" msgstr ""
#: risks/models.py:66 templates/risks/item_risk.html:53 #: risks/models.py:66 templates/risks/item_risk.html:53
#: templates/risks/list_risks.html:57
msgid "Asset" msgid "Asset"
msgstr "" msgstr ""
#: risks/models.py:67 templates/risks/item_risk.html:54 #: risks/models.py:67 templates/risks/item_risk.html:54
#: templates/risks/list_risks.html:70
msgid "Process" msgid "Process"
msgstr "" msgstr ""
#: risks/models.py:68 templates/risks/item_risk.html:55 #: risks/models.py:68 templates/risks/item_risk.html:55
#: templates/risks/list_risks.html:85 #: templates/risks/list_risks.html:44 templates/risks/list_risks.html:112
msgid "Category" msgid "Category"
msgstr "" msgstr ""
@ -219,6 +225,10 @@ msgstr ""
msgid "Updated at" msgid "Updated at"
msgstr "" msgstr ""
#: risks/models.py:71
msgid "Effects"
msgstr ""
#: risks/models.py:133 #: risks/models.py:133
msgid "Residual Risk" msgid "Residual Risk"
msgstr "" msgstr ""
@ -259,7 +269,7 @@ msgstr ""
msgid "Auditlogs" msgid "Auditlogs"
msgstr "" msgstr ""
#: risks/models.py:257 templates/risks/item_risk.html:255 #: risks/models.py:257 templates/risks/item_risk.html:259
msgid "Incident" msgid "Incident"
msgstr "" msgstr ""
@ -275,15 +285,119 @@ msgstr ""
msgid "Reported by" msgid "Reported by"
msgstr "" msgstr ""
#: risks/models.py:279 #: risks/models.py:279 risks/utils.py:119
msgid "Notification" msgid "Notification"
msgstr "" msgstr ""
#: risks/models.py:280 templates/base.html:78 #: risks/models.py:280 templates/base.html:80
#: templates/risks/notifications.html:4 #: templates/risks/notifications.html:4
msgid "Notifications" msgid "Notifications"
msgstr "" msgstr ""
#: risks/models.py:295
msgid "Risk created"
msgstr ""
#: risks/models.py:296
msgid "Risk updated"
msgstr ""
#: risks/models.py:297
msgid "Risk deleted"
msgstr ""
#: risks/models.py:298
msgid "Risk review required"
msgstr ""
#: risks/models.py:299
msgid "Risk review completed"
msgstr ""
#: risks/models.py:301
msgid "Control created"
msgstr ""
#: risks/models.py:302
msgid "Control updated"
msgstr ""
#: risks/models.py:303
msgid "Control deleted"
msgstr ""
#: risks/models.py:305
msgid "Residual created"
msgstr ""
#: risks/models.py:306
msgid "Residual updated"
msgstr ""
#: risks/models.py:307
msgid "Residual deleted"
msgstr ""
#: risks/models.py:308
msgid "Residual review required"
msgstr ""
#: risks/models.py:309
msgid "Residual review completed"
msgstr ""
#: risks/models.py:311
msgid "Incident created"
msgstr ""
#: risks/models.py:312
msgid "Incident updated"
msgstr ""
#: risks/models.py:313
msgid "Incident deleted"
msgstr ""
#: risks/models.py:315
msgid "User created"
msgstr ""
#: risks/models.py:316
msgid "User deleted"
msgstr ""
#: risks/models.py:371
msgid "Notification rule"
msgstr ""
#: risks/models.py:372
msgid "Notification rules"
msgstr ""
#: risks/models.py:375
msgid "Event"
msgstr ""
#: risks/models.py:380
msgid "Show in app"
msgstr ""
#: risks/models.py:381
msgid "Send via email"
msgstr ""
#: risks/models.py:385
msgid "Send to owner/responsible/reporter (if available)"
msgstr ""
#: risks/models.py:389
msgid "Send to all staff"
msgstr ""
#: risks/models.py:393
msgid "Extra recipients (emails, comma or newline separated)"
msgstr ""
#: risks/signals.py:57 #: risks/signals.py:57
#, python-brace-format #, python-brace-format
msgid "User '{u}' created" msgid "User '{u}' created"
@ -299,13 +413,13 @@ msgstr ""
msgid "Risk '{title}' {state}" msgid "Risk '{title}' {state}"
msgstr "" msgstr ""
#: risks/signals.py:72 risks/signals.py:147 risks/signals.py:240 #: risks/signals.py:72 risks/signals.py:166 risks/signals.py:212
#: risks/signals.py:296 #: risks/signals.py:275 risks/signals.py:369 risks/signals.py:411
msgid "created" msgid "created"
msgstr "" msgstr ""
#: risks/signals.py:72 risks/signals.py:147 risks/signals.py:240 #: risks/signals.py:72 risks/signals.py:166 risks/signals.py:212
#: risks/signals.py:296 #: risks/signals.py:275 risks/signals.py:369 risks/signals.py:411
msgid "updated" msgid "updated"
msgstr "" msgstr ""
@ -314,77 +428,137 @@ msgstr ""
msgid "Risk '{title}' deleted" msgid "Risk '{title}' deleted"
msgstr "" msgstr ""
#: risks/signals.py:145 #: risks/signals.py:117
#, python-brace-format
msgid "Risk created: {t}"
msgstr ""
#: risks/signals.py:123
#, python-brace-format
msgid "Risk updated: {t}"
msgstr ""
#: risks/signals.py:143
#, python-brace-format
msgid "Risk deleted: {t}"
msgstr ""
#: risks/signals.py:164
#, python-brace-format #, python-brace-format
msgid "Control '{title}' {state}" msgid "Control '{title}' {state}"
msgstr "" msgstr ""
#: risks/signals.py:154 #: risks/signals.py:173
#, python-brace-format #, python-brace-format
msgid "Control '{title}' deleted" msgid "Control '{title}' deleted"
msgstr "" msgstr ""
#: risks/signals.py:211 #: risks/signals.py:211
#, python-brace-format #, python-brace-format
msgid "Control {event}: {t}"
msgstr ""
#: risks/signals.py:231
#, python-brace-format
msgid "Control deleted: {t}"
msgstr ""
#: risks/signals.py:246
#, python-brace-format
msgid "Review required for risk '{t}' due to control change" msgid "Review required for risk '{t}' due to control change"
msgstr "" msgstr ""
#: risks/signals.py:230 #: risks/signals.py:265
#, python-brace-format #, python-brace-format
msgid "Review required for risk '{t}'" msgid "Review required for risk '{t}'"
msgstr "" msgstr ""
#: risks/signals.py:235 #: risks/signals.py:270
#, python-brace-format #, python-brace-format
msgid "Review completed for risk '{t}'" msgid "Review completed for risk '{t}'"
msgstr "" msgstr ""
#: risks/signals.py:239 #: risks/signals.py:274
#, python-brace-format #, python-brace-format
msgid "Residual risk {state} for '{t}'" msgid "Residual risk {state} for '{t}'"
msgstr "" msgstr ""
#: risks/signals.py:244 #: risks/signals.py:279
#, python-brace-format #, python-brace-format
msgid "Residual risk deleted for '{t}'" msgid "Residual risk deleted for '{t}'"
msgstr "" msgstr ""
#: risks/signals.py:296 #: risks/signals.py:316
#, python-brace-format
msgid "Residual created for risk: {t}"
msgstr ""
#: risks/signals.py:328
#, python-brace-format
msgid "Residual review required for risk: {t}"
msgstr ""
#: risks/signals.py:334
#, python-brace-format
msgid "Residual review completed for risk: {t}"
msgstr ""
#: risks/signals.py:340
#, python-brace-format
msgid "Residual updated for risk: {t}"
msgstr ""
#: risks/signals.py:358
#, python-brace-format
msgid "Residual deleted for risk: {t}"
msgstr ""
#: risks/signals.py:369
#, python-brace-format #, python-brace-format
msgid "Incident '{t}' {s}" msgid "Incident '{t}' {s}"
msgstr "" msgstr ""
#: risks/signals.py:301 #: risks/signals.py:374
#, python-brace-format #, python-brace-format
msgid "Incident '{t}' deleted" msgid "Incident '{t}' deleted"
msgstr "" msgstr ""
#: risks/utils.py:48 #: risks/signals.py:410
#, python-brace-format
msgid "Incident {event}: {t}"
msgstr ""
#: risks/signals.py:442
#, python-brace-format
msgid "Incident deleted: {t}"
msgstr ""
#: risks/utils.py:53 risks/utils.py:67
#, python-brace-format #, python-brace-format
msgid "Follow-up reached: review required for risk '{t}'" msgid "Follow-up reached: review required for risk '{t}'"
msgstr "" msgstr ""
#: risks/views.py:315 #: risks/views.py:345
msgid "Notification marked as read." msgid "Notification marked as read."
msgstr "" msgstr ""
#: risks/views.py:323 #: risks/views.py:353
msgid "All notifications marked as read." msgid "All notifications marked as read."
msgstr "" msgstr ""
#: risks/views.py:340 #: risks/views.py:370
msgid "Risk status updated." msgid "Risk status updated."
msgstr "" msgstr ""
#: risks/views.py:354 #: risks/views.py:384
msgid "Control status updated." msgid "Control status updated."
msgstr "" msgstr ""
#: risks/views.py:368 #: risks/views.py:398
msgid "Incident status updated." msgid "Incident status updated."
msgstr "" msgstr ""
#: risks/views.py:384 #: risks/views.py:414
msgid "Residual review flag updated." msgid "Residual review flag updated."
msgstr "" msgstr ""
@ -392,32 +566,37 @@ msgstr ""
msgid "Dashboard" msgid "Dashboard"
msgstr "" msgstr ""
#: templates/base.html:35 templates/risks/item_risk.html:4 #: templates/base.html:35 templates/risks/risk_matrix.html:4
#: templates/risks/risk_matrix.html:12 templates/risks/risk_matrix.html:18
msgid "Risk Matrix"
msgstr ""
#: templates/base.html:37 templates/risks/item_risk.html:4
#: templates/risks/list_risks.html:4 #: templates/risks/list_risks.html:4
msgid "Risk analysis" msgid "Risk analysis"
msgstr "" msgstr ""
#: templates/base.html:73 #: templates/base.html:75
msgid "AdminCP" msgid "AdminCP"
msgstr "" msgstr ""
#: templates/base.html:86 #: templates/base.html:88
msgid "Derk Mode" msgid "Derk Mode"
msgstr "" msgstr ""
#: templates/base.html:92 #: templates/base.html:94
msgid "Logout" msgid "Logout"
msgstr "" msgstr ""
#: templates/base.html:107 #: templates/base.html:109
msgid "Login" msgid "Login"
msgstr "" msgstr ""
#: templates/base.html:142 templates/base.html:149 #: templates/base.html:144 templates/base.html:151
msgid "Light Mode" msgid "Light Mode"
msgstr "" msgstr ""
#: templates/base.html:152 #: templates/base.html:154
msgid "Dark Mode" msgid "Dark Mode"
msgstr "" msgstr ""
@ -477,97 +656,100 @@ msgstr ""
msgid "Resubmission" msgid "Resubmission"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:76 #: templates/risks/item_risk.html:80
msgid "Risk assessment" msgid "Risk assessment"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:84 #: templates/risks/item_risk.html:88
msgid "Gross (before measures)" msgid "Gross (before measures)"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:89 templates/risks/item_risk.html:135 #: templates/risks/item_risk.html:93 templates/risks/item_risk.html:139
#: templates/risks/list_risks.html:86 #: templates/risks/list_risks.html:118 templates/risks/list_risks.html:122
#: templates/risks/risk_matrix.html:25
msgid "Likelihood" msgid "Likelihood"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:90 templates/risks/item_risk.html:136 #: templates/risks/item_risk.html:94 templates/risks/item_risk.html:140
msgid "Probability of occurrence" msgid "Probability of occurrence"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:98 templates/risks/item_risk.html:144 #: templates/risks/item_risk.html:102 templates/risks/item_risk.html:148
#: templates/risks/list_risks.html:87 #: templates/risks/list_risks.html:119 templates/risks/list_risks.html:123
#: templates/risks/risk_matrix.html:25
msgid "Impact" msgid "Impact"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:99 templates/risks/item_risk.html:145 #: templates/risks/item_risk.html:103 templates/risks/item_risk.html:149
msgid "Extent of damage" msgid "Extent of damage"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:107 templates/risks/item_risk.html:108 #: templates/risks/item_risk.html:111 templates/risks/item_risk.html:112
#: templates/risks/item_risk.html:153 templates/risks/item_risk.html:154 #: templates/risks/item_risk.html:157 templates/risks/item_risk.html:158
#: templates/risks/list_risks.html:89 #: templates/risks/list_risks.html:121 templates/risks/list_risks.html:125
msgid "Level" msgid "Level"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:116 templates/risks/item_risk.html:117 #: templates/risks/item_risk.html:120 templates/risks/item_risk.html:121
#: templates/risks/item_risk.html:161 templates/risks/item_risk.html:163 #: templates/risks/item_risk.html:165 templates/risks/item_risk.html:167
#: templates/risks/list_risks.html:88 #: templates/risks/list_risks.html:120 templates/risks/list_risks.html:124
#: templates/risks/risk_matrix.html:39
msgid "Score" msgid "Score"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:130 #: templates/risks/item_risk.html:134
msgid "Net (after measures)" msgid "Net (after measures)"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:169 #: templates/risks/item_risk.html:173
msgid "No net risk recorded yet." msgid "No net risk recorded yet."
msgstr "" msgstr ""
#: templates/risks/item_risk.html:180 #: templates/risks/item_risk.html:184
msgid "Save" msgid "Save"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:194 #: templates/risks/item_risk.html:198
msgid "Measures" msgid "Measures"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:203 #: templates/risks/item_risk.html:207
msgid "Deadline" msgid "Deadline"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:204 #: templates/risks/item_risk.html:208
msgid "Responsible" msgid "Responsible"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:205 #: templates/risks/item_risk.html:209
msgid "Link" msgid "Link"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:239 #: templates/risks/item_risk.html:243
msgid "No measures recorded." msgid "No measures recorded."
msgstr "" msgstr ""
#: templates/risks/item_risk.html:257 #: templates/risks/item_risk.html:261
msgid "Reported on" msgid "Reported on"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:271 #: templates/risks/item_risk.html:275
msgid "No incidents recorded." msgid "No incidents recorded."
msgstr "" msgstr ""
#: templates/risks/item_risk.html:279 #: templates/risks/item_risk.html:283
msgid "History" msgid "History"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:286 #: templates/risks/item_risk.html:290
msgid "Time" msgid "Time"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:288 #: templates/risks/item_risk.html:292
msgid "Action" msgid "Action"
msgstr "" msgstr ""
#: templates/risks/item_risk.html:302 #: templates/risks/item_risk.html:306
msgid "No History found." msgid "No History found."
msgstr "" msgstr ""
@ -575,20 +757,31 @@ msgstr ""
msgid "Filter" msgid "Filter"
msgstr "" msgstr ""
#: templates/risks/list_risks.html:22 templates/risks/list_risks.html:41 #: templates/risks/list_risks.html:83
#: templates/risks/list_risks.html:60 templates/risks/notifications.html:13 msgid "Owner"
msgid "All"
msgstr "" msgstr ""
#: templates/risks/list_risks.html:56 templates/risks/list_risks.html:90 #: templates/risks/list_risks.html:111
msgid "Risk Owner"
msgstr ""
#: templates/risks/list_risks.html:84
msgid "Asset / Process" msgid "Asset / Process"
msgstr "" msgstr ""
#: templates/risks/list_risks.html:152 #: templates/risks/list_risks.html:113
msgid "Risk Owner"
msgstr ""
#: templates/risks/list_risks.html:114
msgid "Gross Risk"
msgstr ""
#: templates/risks/list_risks.html:115
msgid "Net Risk"
msgstr ""
#: templates/risks/list_risks.html:195
msgid "No residual risk defined"
msgstr ""
#: templates/risks/list_risks.html:201
msgid "No risks present" msgid "No risks present"
msgstr "" msgstr ""
@ -596,6 +789,10 @@ msgstr ""
msgid "Unread" msgid "Unread"
msgstr "" msgstr ""
#: templates/risks/notifications.html:13
msgid "All"
msgstr ""
#: templates/risks/notifications.html:20 #: templates/risks/notifications.html:20
msgid "Mark all as read" msgid "Mark all as read"
msgstr "" msgstr ""
@ -611,3 +808,23 @@ msgstr ""
#: templates/risks/notifications.html:53 #: templates/risks/notifications.html:53
msgid "No notifications." msgid "No notifications."
msgstr "" msgstr ""
#: templates/risks/risk_matrix.html:13
msgid "Detail View"
msgstr ""
#: templates/risks/risk_matrix.html:54
msgid "Show"
msgstr ""
#: templates/risks/risk_matrix.html:58
msgid "Gross"
msgstr ""
#: templates/risks/risk_matrix.html:61
msgid "Net"
msgstr ""
#: templates/risks/risk_matrix.html:73 templates/risks/risk_matrix.html:115
msgid "Impact \\\\ Likelihood"
msgstr ""

View file

@ -10,6 +10,20 @@ _LIKELIHOOD_LABELS = dict(Risk.LIKELIHOOD_CHOICES)
_IMPACT_LABELS = dict(Risk.IMPACT_CHOICES) _IMPACT_LABELS = dict(Risk.IMPACT_CHOICES)
LEVEL_ID_MAP = {"Low": 1, "Medium": 2, "High": 3, "Critical": 4} LEVEL_ID_MAP = {"Low": 1, "Medium": 2, "High": 3, "Critical": 4}
@register.filter
def dict_get(d, key):
try:
return d.get(key)
except Exception:
return None
@register.filter
def mul(a, b):
try:
return int(a) * int(b)
except (TypeError, ValueError):
return ""
@register.filter @register.filter
def level_id(level): def level_id(level):
return LEVEL_ID_MAP.get(str(level), "") return LEVEL_ID_MAP.get(str(level), "")
@ -38,7 +52,7 @@ def likelihood_id_label(val):
return "" return ""
label = _LIKELIHOOD_LABELS.get(i, "") label = _LIKELIHOOD_LABELS.get(i, "")
short = _short(str(label)) if label else "" short = _short(str(label)) if label else ""
return format_html("{}<br>({})", i, short) if label else format_html("{}", i) return format_html("{} ({})", i, short) if label else format_html("{}", i)
@register.filter @register.filter
def impact_id_label(val): def impact_id_label(val):
@ -48,7 +62,7 @@ def impact_id_label(val):
return "" return ""
label = _IMPACT_LABELS.get(i, "") label = _IMPACT_LABELS.get(i, "")
short = _short(str(label)) if label else "" short = _short(str(label)) if label else ""
return format_html("{}<br>({})", i, short) if label else format_html("{}", i) return format_html("{} ({})", i, short) if label else format_html("{}", i)
@register.filter @register.filter
@ -131,4 +145,24 @@ def to_bg(css_class: str):
css = str(css_class) css = str(css_class)
except Exception: except Exception:
return "" return ""
return css.replace("is-control-", "has-background-control-") if css.startswith("is-control-") else css return css.replace("is-control-", "has-background-control-") if css.startswith("is-control-") else css
@register.filter
def score_bg_class(score):
"""
Mappt Score (1..20) auf deine Farbhintergründe.
"""
try:
s = int(score)
except (TypeError, ValueError):
return ""
if s <= 4:
return "has-background-control-verylow"
elif s <= 8:
return "has-background-control-low"
elif s <= 12:
return "has-background-control-mid"
elif s <= 16:
return "has-background-control-high"
else:
return "has-background-control-veryhigh"

View file

@ -13,6 +13,7 @@ urlpatterns = [
path("risks/controls/<int:id>", views.show_control, name="show_control"), path("risks/controls/<int:id>", views.show_control, name="show_control"),
path("risks/list_incidents", views.list_incidents, name="list_incidents"), path("risks/list_incidents", views.list_incidents, name="list_incidents"),
path("risks/incidents/<int:id>", views.show_incident, name="show_incident"), path("risks/incidents/<int:id>", views.show_incident, name="show_incident"),
path("risks/risk_matrix", views.risk_matrix, name="risk_matrix"),
# Notifications # Notifications
path("notifications/", views.notifications, name="notifications"), path("notifications/", views.notifications, name="notifications"),

View file

@ -9,7 +9,7 @@ from django.shortcuts import redirect, render, get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from collections import Counter from collections import Counter, defaultdict
from .forms import RiskStatusForm, ControlStatusForm, IncidentStatusForm, ResidualReviewForm from .forms import RiskStatusForm, ControlStatusForm, IncidentStatusForm, ResidualReviewForm
from .models import Risk, Control, ResidualRisk, AuditLog, Incident, Notification from .models import Risk, Control, ResidualRisk, AuditLog, Incident, Notification
from .serializers import ControlSerializer, RiskSerializer, ResidualRiskSerializer, UserSerializer, AuditSerializer, IncidentSerializer from .serializers import ControlSerializer, RiskSerializer, ResidualRiskSerializer, UserSerializer, AuditSerializer, IncidentSerializer
@ -119,12 +119,15 @@ def list_risks(request):
""" """
View for listing all Risks View for listing all Risks
""" """
qs = Risk.objects.all().select_related("owner") qs = Risk.objects.all().select_related("owner", "residual_risk")
# GET-Parameter lesen # GET-Parameter lesen
risk_id = request.GET.get("risk") risk_id = request.GET.get("risk")
control_id = request.GET.get("control") control_id = request.GET.get("control")
owner_id = request.GET.get("owner") owner_id = request.GET.get("owner")
category = request.GET.get("category")
asset = request.GET.get("asset")
process = request.GET.get("process")
if risk_id: if risk_id:
qs = qs.filter(id=risk_id) qs = qs.filter(id=risk_id)
@ -132,16 +135,43 @@ def list_risks(request):
qs = qs.filter(controls__id=control_id) qs = qs.filter(controls__id=control_id)
if owner_id: if owner_id:
qs = qs.filter(owner_id=owner_id) qs = qs.filter(owner_id=owner_id)
if category:
qs = qs.filter(category=category)
if asset:
qs = qs.filter(asset=asset)
if process:
qs = qs.filter(process=process)
risks = qs.order_by("title").distinct() risks = qs.order_by("title").distinct()
controls = Control.objects.all().order_by("title") controls = Control.objects.all().order_by("title")
owners = User.objects.filter(owned_risks__isnull=False).distinct().order_by("username") owners = User.objects.filter(owned_risks__isnull=False).distinct().order_by("username")
categories = (Risk.objects
.exclude(category__isnull=True)
.exclude(category__exact="")
.values_list("category", flat=True)
.distinct()
.order_by("category"))
assets = (Risk.objects
.exclude(asset__isnull=True)
.exclude(asset__exact="")
.values_list("asset", flat=True)
.distinct()
.order_by("asset"))
processes = (Risk.objects
.exclude(process__isnull=True)
.exclude(process__exact="")
.values_list("process", flat=True)
.distinct()
.order_by("process"))
return render(request, "risks/list_risks.html", { return render(request, "risks/list_risks.html", {
"risks": risks, "risks": risks,
"controls": controls, "controls": controls,
"owners": owners, "owners": owners,
"categories": categories,
"assets": assets,
"processes": processes,
}) })
@login_required @login_required
@ -382,4 +412,31 @@ def update_residual_review(request, risk_id):
obj._changed_by = request.user obj._changed_by = request.user
obj.save(update_fields=["review_required", "updated_at"]) obj.save(update_fields=["review_required", "updated_at"])
messages.success(request, _("Residual review flag updated.")) messages.success(request, _("Residual review flag updated."))
return redirect("risks:show_risk", id=risk.pk) return redirect("risks:show_risk", id=risk.pk)
def risk_matrix(request):
risks = (Risk.objects
.select_related("owner", "residual_risk") # wichtig fürs Netto
.all())
impacts = sorted(Risk.IMPACT_CHOICES, key=lambda x: x[0])
likelihoods = sorted(Risk.LIKELIHOOD_CHOICES, key=lambda x: x[0])
gross_matrix = {i: {l: [] for l, _ in likelihoods} for i, _ in impacts}
net_matrix = {i: {l: [] for l, _ in likelihoods} for i, _ in impacts}
for r in risks:
# Brutto platzieren
gross_matrix[r.impact][r.likelihood].append(r)
# Netto (falls vorhanden) platzieren
rr = getattr(r, "residual_risk", None)
if rr:
net_matrix[rr.impact][rr.likelihood].append(r)
return render(request, "risks/risk_matrix.html", {
"impacts": impacts,
"likelihoods": likelihoods,
"gross_matrix": gross_matrix,
"net_matrix": net_matrix,
})

View file

@ -1,10 +1,10 @@
/* Base palette */ /* Base palette */
:root{ :root{
--c-verylow:#22c55e; --c-verylow-100:#dcfce7; --c-verylow-300:#86efac; --c-verylow-inv:#fff; --c-verylow:#22c55e; --c-verylow-100:#dcfce7; --c-verylow-300:#86efac; --c-verylow-inv:#000;
--c-low:#84cc16; --c-low-100:#ecfccb; --c-low-300:#bef264; --c-low-inv:#111; --c-low:#84cc16; --c-low-100:#ecfccb; --c-low-300:#bef264; --c-low-inv:#111;
--c-mid:#eab308; --c-mid-100:#fef9c3; --c-mid-300:#fde047; --c-mid-inv:#111; --c-mid:#eab308; --c-mid-100:#fef9c3; --c-mid-300:#fde047; --c-mid-inv:#111;
--c-high:#f97316; --c-high-100:#ffedd5; --c-high-300:#fbbf24; --c-high-inv:#111; --c-high:#f97316; --c-high-100:#ffedd5; --c-high-300:#fbbf24; --c-high-inv:#111;
--c-veryhigh:#dc2626; --c-veryhigh-100:#fee2e2;--c-veryhigh-300:#fca5a5; --c-veryhigh-inv:#fff; --c-veryhigh:#dc2626; --c-veryhigh-100:#fee2e2;--c-veryhigh-300:#fca5a5; --c-veryhigh-inv:#000;
} }
/* Helpers (wie Bulma) */ /* Helpers (wie Bulma) */
@ -80,6 +80,9 @@
.progress.is-control-high::-moz-progress-bar{background:var(--c-high)} .progress.is-control-high::-moz-progress-bar{background:var(--c-high)}
.progress.is-control-veryhigh::-moz-progress-bar{background:var(--c-veryhigh)} .progress.is-control-veryhigh::-moz-progress-bar{background:var(--c-veryhigh)}
abbr {
text-decoration: none;
}
/* Topbar-Farbe erzwingen (Bulma überschreibt sonst mit weiß) */ /* Topbar-Farbe erzwingen (Bulma überschreibt sonst mit weiß) */
.navbar.topbar-nav { .navbar.topbar-nav {

View file

@ -32,6 +32,8 @@
<a class="navbar-link">{% trans "Risk Management" %}</a> <a class="navbar-link">{% trans "Risk Management" %}</a>
<div class="navbar-dropdown"> <div class="navbar-dropdown">
<a class="navbar-item" href="/risks/index">{% trans "Dashboard" %}</a> <a class="navbar-item" href="/risks/index">{% trans "Dashboard" %}</a>
<a class="navbar-item" href="/risks/risk_matrix">{% trans "Risk Matrix" %}</a>
<hr class="navbar-divider">
<a class="navbar-item" href="/risks/list_risks">{% trans "Risk analysis" %}</a> <a class="navbar-item" href="/risks/list_risks">{% trans "Risk analysis" %}</a>
<a class="navbar-item" href="/risks/list_controls">{% trans "Controls" %}</a> <a class="navbar-item" href="/risks/list_controls">{% trans "Controls" %}</a>
<a class="navbar-item" href="/risks/list_incidents">{% trans "Incidents" %}</a> <a class="navbar-item" href="/risks/list_incidents">{% trans "Incidents" %}</a>

View file

@ -9,68 +9,89 @@
<h2 class="title is-5">{% trans "Filter" %}</h2> <h2 class="title is-5">{% trans "Filter" %}</h2>
<!-- Filter --> <!-- Filter -->
<form method="get"> <form method="get" class="mb-4">
<div class="columns is-multiline"> <div class="columns is-multiline">
<!-- Risiko Filter --> <div class="column is-2">
<div class="column is-3"> <div class="select is-small is-fullwidth">
<div class="field"> <select name="risk" onchange="this.form.submit()">
<label class="label">{% trans "Risk" %}</label> <option value="">{% trans "Risk" %}</option>
<div class="control"> {% for r in risks %}
<div class="select is-fullwidth"> <option value="{{ r.id }}" {% if request.GET.risk == r.id|stringformat:"s" %}selected{% endif %}>
<select name="risk" onchange="this.form.submit()"> {{ r.title }}
<option value="">{% trans "All" %}</option> </option>
{% for r in risks %} {% endfor %}
<option value="{{ r.id }}" {% if request.GET.risk == r.id|stringformat:"s" %}selected{% endif %}> </select>
{{ r.title }}
</option>
{% endfor %}
</select>
</div>
</div>
</div> </div>
</div> </div>
<!-- Maßnahmen Filter --> <div class="column is-2">
<div class="column is-3"> <div class="select is-small is-fullwidth">
<div class="field"> <select name="control" onchange="this.form.submit()">
<label class="label">{% trans "Controls" %}</label> <option value="">{% trans "Controls" %}</option>
<div class="control"> {% for c in controls %}
<div class="select is-fullwidth"> <option value="{{ c.id }}" {% if request.GET.control == c.id|stringformat:"s" %}selected{% endif %}>
<select name="control" onchange="this.form.submit()"> {{ c.title }}
<option value="">{% trans "All" %}</option> </option>
{% for c in controls %} {% endfor %}
<option value="{{ c.id }}" {% if request.GET.control == c.id|stringformat:"s" %}selected{% endif %}> </select>
{{ c.title }}
</option>
{% endfor %}
</select>
</div>
</div>
</div> </div>
</div> </div>
<!-- Risikoeigner Filter --> <div class="column is-2">
<div class="column is-3"> <div class="select is-small is-fullwidth">
<div class="field"> <select name="category" onchange="this.form.submit()">
<label class="label">{% trans "Risk Owner" %}</label> <option value="">{% trans "Category" %}</option>
<div class="control"> {% for cat in categories %}
<div class="select is-fullwidth"> <option value="{{ cat }}" {% if request.GET.category == cat|stringformat:"s" %}selected{% endif %}>
<select name="owner" onchange="this.form.submit()"> {{ cat }}
<option value="">{% trans "All" %}</option> </option>
{% for u in owners %} {% endfor %}
<option value="{{ u.id }}" {% if request.GET.owner == u.id|stringformat:"s" %}selected{% endif %}> </select>
{{ u.get_full_name|default:u.username }} </div>
</option> </div>
{% endfor %}
</select> <div class="column is-2">
</div> <div class="select is-small is-fullwidth">
</div> <select name="asset" onchange="this.form.submit()">
<option value="">{% trans "Asset" %}</option>
{% for a in assets %}
<option value="{{ a }}" {% if request.GET.asset == a|stringformat:"s" %}selected{% endif %}>
{{ a }}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="column is-2">
<div class="select is-small is-fullwidth">
<select name="process" onchange="this.form.submit()">
<option value="">{% trans "Process" %}</option>
{% for p in processes %}
<option value="{{ p }}" {% if request.GET.process == p|stringformat:"s" %}selected{% endif %}>
{{ p }}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="column is-2">
<div class="select is-small is-fullwidth">
<select name="owner" onchange="this.form.submit()">
<option value="">{% trans "Owner" %}</option>
{% for u in owners %}
<option value="{{ u.id }}" {% if request.GET.owner == u.id|stringformat:"s" %}selected{% endif %}>
{{ u.get_full_name|default:u.username }}
</option>
{% endfor %}
</select>
</div> </div>
</div> </div>
</div> </div>
</form> </form> <!-- Filter Ende -->
<h2 class="title is-5">{% trans "Risks" %}</h2> <h2 class="title is-5">{% trans "Risks" %}</h2>
@ -79,36 +100,32 @@
<table class="table is-bordered is-striped is-hoverable is-fullwidth"> <table class="table is-bordered is-striped is-hoverable is-fullwidth">
<thead> <thead>
<tr> <tr>
{% if request.user.is_staff %}<th></th>{% endif %} {% if request.user.is_staff %}
<th>{% trans "Risk" %}</th> <th rowspan="2" class="has-text-centered">
<th>{% trans "Asset / Process" %}</th>
<th>{% trans "Category" %}</th>
<th>{% trans "Likelihood" %}</th>
<th>{% trans "Impact" %}</th>
<th>{% trans "Score" %}</th>
<th>{% trans "Level" %}</th>
<th>{% trans "Risk Owner" %}</th>
</tr>
</thead>
<tbody>
{% if request.user.is_staff %}
<tr>
<td class="has-text-centered">
<a class="icon has-text-success" href="{% url 'admin:risks_risk_add' %}" title="Risiko hinzufügen"> <a class="icon has-text-success" href="{% url 'admin:risks_risk_add' %}" title="Risiko hinzufügen">
<i class="fas fa-add"></i> <i class="fas fa-add"></i>
</a> </a>
</td> </th>
<td></td> {% endif %}
<td></td> <th rowspan="2" class="has-text-centered">{% trans "Risk" %}</th>
<td></td> <th rowspan="2" class="has-text-centered">{% trans "Asset / Process" %}</th>
<td></td> <th rowspan="2" class="has-text-centered">{% trans "Category" %}</th>
<td></td> <th rowspan="2" class="has-text-centered">{% trans "Risk Owner" %}</th>
<td></td> <th colspan="4" class="has-text-centered has-background-light">{% trans "Gross Risk" %}</th>
<td></td> <th colspan="4" class="has-text-centered has-background-info-light">{% trans "Net Risk" %}</th>
<td></td>
</tr> </tr>
{% endif %} <tr>
<th class="has-text-centered has-background-light">{% trans "Likelihood" %}</th>
<th class="has-text-centered has-background-light">{% trans "Impact" %}</th>
<th class="has-text-centered has-background-light">{% trans "Score" %}</th>
<th class="has-text-centered has-background-light">{% trans "Level" %}</th>
<th class="has-text-centered has-background-info-light">{% trans "Likelihood" %}</th>
<th class="has-text-centered has-background-info-light">{% trans "Impact" %}</th>
<th class="has-text-centered has-background-info-light">{% trans "Score" %}</th>
<th class="has-text-centered has-background-info-light">{% trans "Level" %}</th>
</tr>
</thead>
<tbody>
{% for r in risks %} {% for r in risks %}
<tr> <tr>
{% if request.user.is_staff %} {% if request.user.is_staff %}
@ -126,19 +143,6 @@
{% endif %} {% endif %}
</td> </td>
<td onclick="window.location.href='{% url 'risks:show_risk' r.id %}'" style="cursor:pointer;">{{ r.category }}</td> <td onclick="window.location.href='{% url 'risks:show_risk' r.id %}'" style="cursor:pointer;">{{ r.category }}</td>
<td onclick="window.location.href='{% url 'risks:show_risk' r.id %}'" class="has-text-centered {{ r.likelihood|likelihood_class|to_bg }}" style="cursor:pointer;">
{{ r.likelihood|likelihood_id_label }}
</td>
<td onclick="window.location.href='{% url 'risks:show_risk' r.id %}'" class="has-text-centered {{ r.impact|impact_class|to_bg }}" style="cursor:pointer;">
{{ r.impact|impact_id_label }}
</td>
<td onclick="window.location.href='{% url 'risks:show_risk' r.id %}'" class="has-text-centered {{ r.score|score_class|to_bg }}" style="cursor:pointer;">
{{ r.score }}
</td>
<td onclick="window.location.href='{% url 'risks:show_risk' r.id %}'" class="has-text-centered {{ r.level|level_class|to_bg }}" style="cursor:pointer;">
{{ r.level }}
</td>
<td onclick="window.location.href='{% url 'risks:show_risk' r.id %}'" style="cursor:pointer;"> <td onclick="window.location.href='{% url 'risks:show_risk' r.id %}'" style="cursor:pointer;">
{% if r.owner %} {% if r.owner %}
{{ r.owner|user_display }} {{ r.owner|user_display }}
@ -146,6 +150,51 @@
{% endif %} {% endif %}
</td> </td>
<!-- Brutto Risiko -->
<td onclick="window.location.href='{% url 'risks:show_risk' r.id %}'" class="has-text-centered {{ r.likelihood|likelihood_class|to_bg }}" style="cursor:pointer;">
<abbr title="{{ r.likelihood|likelihood_id_label }}">{{ r.likelihood }}</abbr>
</td>
<td onclick="window.location.href='{% url 'risks:show_risk' r.id %}'" class="has-text-centered {{ r.impact|impact_class|to_bg }}" style="cursor:pointer;">
<abbr title="{{ r.impact|impact_id_label }}">{{ r.impact }}</abbr>
</td>
<td onclick="window.location.href='{% url 'risks:show_risk' r.id %}'" class="has-text-centered {{ r.score|score_class|to_bg }}" style="cursor:pointer;">
{{ r.score }} / 20
</td>
<td onclick="window.location.href='{% url 'risks:show_risk' r.id %}'" class="has-text-centered {{ r.level|level_class|to_bg }}" style="cursor:pointer;">
{{ r.level }}
</td>
<!-- Netto Risiko -->
{% if r.residual_risk %}
<td onclick="window.location.href='{% url 'risks:show_risk' r.id %}'"
class="has-text-centered {{ r.residual_risk.likelihood|likelihood_class|to_bg }}"
style="cursor:pointer;">
<abbr title="{{ r.residual_risk.likelihood|likelihood_id_label }}">
{{ r.residual_risk.likelihood }}
</abbr>
</td>
<td onclick="window.location.href='{% url 'risks:show_risk' r.id %}'"
class="has-text-centered {{ r.residual_risk.impact|impact_class|to_bg }}"
style="cursor:pointer;">
<abbr title="{{ r.residual_risk.impact|impact_id_label }}">
{{ r.residual_risk.impact }}
</abbr>
</td>
<td onclick="window.location.href='{% url 'risks:show_risk' r.id %}'"
class="has-text-centered {{ r.residual_risk.score|score_class|to_bg }}"
style="cursor:pointer;">
{{ r.residual_risk.score }} / 20
</td>
<td onclick="window.location.href='{% url 'risks:show_risk' r.id %}'"
class="has-text-centered {{ r.residual_risk.level|level_class|to_bg }}"
style="cursor:pointer;">
{{ r.residual_risk.level }}
</td>
{% else %}
<td colspan="4" class="has-text-centered has-text-grey">
{% trans "No residual risk defined" %}
</td>
{% endif %}
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>

View file

@ -0,0 +1,185 @@
{% extends "base.html" %}
{% load i18n risk_extras %}
{% block crumbs %}
<li><a href="{% url 'risks:risk_matrix' %}">{% trans "Risk Matrix" %}</a></li>
{% endblock %}
{% block content %}
<section class="section">
<!-- Tabs -->
<div class="tabs is-boxed" role="tablist">
<ul>
<li class="is-active" data-tab="riskmatrix" role="tab" aria-selected="true"><a>{% trans "Risk Matrix" %}</a></li>
<li data-tab="details" role="tab" aria-selected="false"><a>{% trans "Detail View" %}</a></li>
</ul>
</div>
<div class="box">
<h2 class="title is-4">{% trans "Risk Matrix" %}</h2>
{# Panel: Brutto (Score-Matrix) #}
<div class="tab-panel" data-tab="riskmatrix">
<table class="table is-bordered has-text-centered risk-matrix-table">
<thead>
<tr>
<th class="has-text-left">{% trans "Impact" %} / {% trans "Likelihood" %}</th>
{% for l_val, l_label in likelihoods %}
<th class="py-6 {{ l_val|likelihood_class|to_bg }}">{{ l_label }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for i_val, i_label in impacts reversed %}
<tr>
<th class="py-6 has-text-left {{ i_val|impact_class|to_bg }}">{{ i_label }}</th>
{% for l_val, l_label in likelihoods %}
{% with s=i_val|mul:l_val %}
<td class="risk-matrix-cell {{ s|score_bg_class }}">
<div class="is-flex is-justify-content-center is-align-items-center py-6">
<span class="tag is-light is-rounded">{% trans "Score" %} {{ s }}</span>
</div>
</td>
{% endwith %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{# Panel: Details (Listen je Zelle, mit Brutto/Netto-Umschalter) #}
<div class="tab-panel is-hidden" data-tab="details">
<div class="level mb-3">
<div class="level-left">
<div class="level-item"><strong>{% trans "Show" %}:</strong></div>
<div class="level-item">
<div class="buttons has-addons">
<button type="button" class="button is-small is-info is-light details-toggle is-active" data-mode="gross">
{% trans "Gross" %}
</button>
<button type="button" class="button is-small is-info is-light details-toggle" data-mode="net">
{% trans "Net" %}
</button>
</div>
</div>
</div>
</div>
{# Brutto-Listen #}
<div class="details-table" data-mode="gross">
<table class="table is-bordered has-text-centered risk-matrix-table">
<thead>
<tr>
<th class="has-text-left">{% trans "Impact" %} / {% trans "Likelihood" %}</th>
{% for l_val, l_label in likelihoods %}
<th class="py-6 {{ l_val|likelihood_class|to_bg }}">{{ l_label }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for i_val, i_label in impacts reversed %}
<tr>
<th class="py-6 has-text-left {{ i_val|impact_class|to_bg }}">{{ i_label }}</th>
{% for l_val, l_label in likelihoods %}
{% with row=gross_matrix|dict_get:i_val %}
{% with cell=row|dict_get:l_val %}
{% with s=i_val|mul:l_val %}
<td class="risk-matrix-cell {{ s|score_bg_class }}">
{% if cell and cell|length %}
<ul class="risk-cell-list">
{% for risk in cell %}
<li style="list-style: none;">
<a href="{% url 'risks:show_risk' risk.id %}" class="tag">{{ risk.title }}</a>
</li>
{% endfor %}
</ul>
{% else %}
<span class="has-text-grey"></span>
{% endif %}
</td>
{% endwith %}
{% endwith %}
{% endwith %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{# Netto-Listen #}
<div class="details-table is-hidden" data-mode="net">
<table class="table is-bordered has-text-centered risk-matrix-table">
<thead>
<tr>
<th class="has-text-left">{% trans "Impact" %} / {% trans "Likelihood" %}</th>
{% for l_val, l_label in likelihoods %}
<th class="py-6 {{ l_val|likelihood_class|to_bg }}">{{ l_label }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for i_val, i_label in impacts reversed %}
<tr>
<th class="py-6 has-text-left {{ i_val|impact_class|to_bg }}">{{ i_label }}</th>
{% for l_val, l_label in likelihoods %}
{% with row=net_matrix|dict_get:i_val %}
{% with cell=row|dict_get:l_val %}
{% with s=i_val|mul:l_val %}
<td class="risk-matrix-cell {{ s|score_bg_class }}">
{% if cell and cell|length %}
<ul class="risk-cell-list">
{% for risk in cell %}
<li style="list-style: none;">
<a href="{% url 'risks:show_risk' risk.id %}" class="tag">{{ risk.title }}</a>
</li>
{% endfor %}
</ul>
{% else %}
<span class="has-text-grey"></span>
{% endif %}
</td>
{% endwith %}
{% endwith %}
{% endwith %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</section>
<style>
.risk-matrix-table th, .risk-matrix-table td { padding: .5rem; }
.risk-matrix-cell { min-height: 120px; vertical-align: middle; }
.tab-panel.is-hidden { display: none; }
.risk-cell-list { text-align: left; margin: 0; padding-left: 1rem; }
</style>
<script>
document.addEventListener('DOMContentLoaded', function () {
// Tabs
const tabs = document.querySelectorAll('.tabs li[data-tab]');
const panels = document.querySelectorAll('.tab-panel');
tabs.forEach(t => t.addEventListener('click', () => {
tabs.forEach(x => { x.classList.remove('is-active'); x.setAttribute('aria-selected','false'); });
t.classList.add('is-active'); t.setAttribute('aria-selected','true');
const target = t.getAttribute('data-tab');
panels.forEach(p => p.classList.toggle('is-hidden', p.getAttribute('data-tab') !== target));
}));
// Umschalter im „Details“-Tab
const toggles = document.querySelectorAll('.details-toggle');
const tables = document.querySelectorAll('.details-table');
toggles.forEach(btn => btn.addEventListener('click', () => {
const mode = btn.getAttribute('data-mode'); // 'gross' | 'net'
toggles.forEach(b => b.classList.toggle('is-active', b === btn));
tables.forEach(t => t.classList.toggle('is-hidden', t.getAttribute('data-mode') !== mode));
}));
});
</script>
{% endblock %}