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 ""
"Project-Id-Version: wira-risk-management\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"
"Last-Translator: Kevin Heyer <kevin@example.com>\n"
"Language-Team: German\n"
@ -22,12 +22,12 @@ msgstr "Admin"
#: 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/list_risks.html:76
#: templates/risks/list_risks.html:97
msgid "Risks"
msgstr "Risiken"
#: risks/admin.py:16 risks/models.py:190 templates/base.html:36
#: templates/risks/list_risks.html:37
#: risks/admin.py:16 risks/models.py:190 templates/base.html:38
#: templates/risks/list_risks.html:31
msgid "Controls"
msgstr "Maßnahmen"
@ -39,8 +39,8 @@ msgstr "Restrisiken"
msgid "Reviews"
msgstr "Prüfung"
#: risks/admin.py:19 risks/models.py:258 templates/base.html:37
#: templates/risks/item_risk.html:248
#: risks/admin.py:19 risks/models.py:258 templates/base.html:39
#: templates/risks/item_risk.html:252
msgid "Incidents"
msgstr "Vorfälle"
@ -48,13 +48,13 @@ msgstr "Vorfälle"
msgid "Users"
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"
msgstr "Benutzer"
#: risks/admin.py:139
msgid "Message"
msgstr ""
msgstr "Nachricht"
#: risks/admin.py:147
msgid "Mark selected as read"
@ -76,9 +76,10 @@ msgstr "%(n)d Benachrichtigungen wurden als ungelesen Markiert"
#: risks/admin.py:157
msgid "Mark selected as sent"
msgstr ""
msgstr "Auswahl als gelesen markieren"
#: risks/admin.py:160
#, python-format
msgid "%(n)d notifications marked as sent."
msgstr "%(n)d Benachrichtigungen wurden als gelesen Markiert"
@ -87,18 +88,23 @@ msgid "Mark selected as unsent"
msgstr "Auswahl als ungesendet markieren"
#: risks/admin.py:165
#, python-format
msgid "%(n)d notifications marked as unsent."
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"
msgstr "SSO-Informationen"
#: risks/admin.py:186
#: risks/admin.py:199
msgid "Risks Owned"
msgstr "Eigene Risiken"
#: risks/admin.py:190
#: risks/admin.py:203
msgid "Controls Responsible"
msgstr "Verantwortlich für Maßnahmen"
@ -107,17 +113,17 @@ msgid "Risk Management"
msgstr "Risikomanagement"
#: 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:256
#: templates/risks/item_risk.html:64 templates/risks/item_risk.html:206
#: templates/risks/item_risk.html:260
msgid "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"
msgstr "Prüfung nötig"
#: 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"
msgstr "Risiko"
@ -182,7 +188,7 @@ msgid "Availability"
msgstr "Verfügbarkeit"
#: 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"
msgstr "Titel"
@ -191,15 +197,17 @@ msgid "Description"
msgstr "Beschreibung"
#: risks/models.py:66 templates/risks/item_risk.html:53
#: templates/risks/list_risks.html:57
msgid "Asset"
msgstr "Asset"
#: risks/models.py:67 templates/risks/item_risk.html:54
#: templates/risks/list_risks.html:70
msgid "Process"
msgstr "Prozess"
#: 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"
msgstr "Kategorie"
@ -211,6 +219,10 @@ msgstr "Erstellt am"
msgid "Updated at"
msgstr "Aktualisiert am"
#: risks/models.py:71
msgid "Effects"
msgstr "Auswirkungen"
#: risks/models.py:133
msgid "Residual Risk"
msgstr "Restrisiko"
@ -251,7 +263,7 @@ msgstr "Audit-Log"
msgid "Auditlogs"
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"
msgstr "Vorfall"
@ -267,15 +279,120 @@ msgstr "Meldedatum"
msgid "Reported by"
msgstr "Gemeldet von"
#: risks/models.py:279
#: risks/models.py:279 risks/utils.py:119
msgid "Notification"
msgstr "Benachrichtigung"
#: risks/models.py:280 templates/base.html:78
#: risks/models.py:280 templates/base.html:80
#: templates/risks/notifications.html:4
msgid "Notifications"
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
#, python-brace-format
msgid "User '{u}' created"
@ -291,13 +408,13 @@ msgstr "Benutzer '{u}' löschte"
msgid "Risk '{title}' {state}"
msgstr "Risiko '{title}' {state}"
#: risks/signals.py:72 risks/signals.py:147 risks/signals.py:240
#: risks/signals.py:296
#: risks/signals.py:72 risks/signals.py:166 risks/signals.py:212
#: risks/signals.py:275 risks/signals.py:369 risks/signals.py:411
msgid "created"
msgstr "erstellt"
#: risks/signals.py:72 risks/signals.py:147 risks/signals.py:240
#: risks/signals.py:296
#: risks/signals.py:72 risks/signals.py:166 risks/signals.py:212
#: risks/signals.py:275 risks/signals.py:369 risks/signals.py:411
msgid "updated"
msgstr "Aktualisiert"
@ -306,77 +423,137 @@ msgstr "Aktualisiert"
msgid "Risk '{title}' deleted"
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
msgid "Control '{title}' {state}"
msgstr "Maßnahme '{title}' {state}"
#: risks/signals.py:154
#: risks/signals.py:173
#, python-brace-format
msgid "Control '{title}' deleted"
msgstr "Maßnahme '{title}' gelöscht"
#: risks/signals.py:211
#, 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"
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
msgid "Review required for risk '{t}'"
msgstr "Prüfung benötigt für Risiko '{t}'"
#: risks/signals.py:235
#: risks/signals.py:270
#, python-brace-format
msgid "Review completed for risk '{t}'"
msgstr "Prüfung Abgeschlossen für Risiko '{t}'"
#: risks/signals.py:239
#: risks/signals.py:274
#, python-brace-format
msgid "Residual risk {state} for '{t}'"
msgstr "Restrisiko {state} für '{t}'"
#: risks/signals.py:244
#: risks/signals.py:279
#, python-brace-format
msgid "Residual risk deleted for '{t}'"
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
msgid "Incident '{t}' {s}"
msgstr "Vorfälle '{t}' {s}"
msgstr "Vorfall '{t}' {s}"
#: risks/signals.py:301
#: risks/signals.py:374
#, python-brace-format
msgid "Incident '{t}' deleted"
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
msgid "Follow-up reached: review required for risk '{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."
msgstr "Nachricht als gelesen markiert"
#: risks/views.py:323
#: risks/views.py:353
msgid "All notifications marked as read."
msgstr "Alle Benachrichtigungen wurden als gelesen Markiert"
#: risks/views.py:340
#: risks/views.py:370
msgid "Risk status updated."
msgstr "Risikostatus Aktualisiert"
#: risks/views.py:354
#: risks/views.py:384
msgid "Control status updated."
msgstr "Maßnahmenstatus Aktualisiert"
#: risks/views.py:368
#: risks/views.py:398
msgid "Incident status updated."
msgstr "Vorfallstatus Aktualisiert"
#: risks/views.py:384
#: risks/views.py:414
msgid "Residual review flag updated."
msgstr "Restrisiko geprüft"
@ -384,32 +561,37 @@ msgstr "Restrisiko geprüft"
msgid "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
msgid "Risk analysis"
msgstr "Risikoanalyse"
#: templates/base.html:73
#: templates/base.html:75
msgid "AdminCP"
msgstr "Adminbereich"
#: templates/base.html:86
#: templates/base.html:88
msgid "Derk Mode"
msgstr "Dark Mode"
#: templates/base.html:92
#: templates/base.html:94
msgid "Logout"
msgstr "Logout"
#: templates/base.html:107
#: templates/base.html:109
msgid "Login"
msgstr "Login"
#: templates/base.html:142 templates/base.html:149
#: templates/base.html:144 templates/base.html:151
msgid "Light Mode"
msgstr "Light Mode"
#: templates/base.html:152
#: templates/base.html:154
msgid "Dark Mode"
msgstr "Dark Mode"
@ -471,99 +653,100 @@ msgstr "Aktualisiert am"
msgid "Resubmission"
msgstr "Wiedervorlagedatum"
#: templates/risks/item_risk.html:76
#: templates/risks/item_risk.html:80
msgid "Risk assessment"
msgstr "Risikomanagement"
#: templates/risks/item_risk.html:84
#: templates/risks/item_risk.html:88
msgid "Gross (before measures)"
msgstr "Brutto (vor Maßnahmen)"
#: templates/risks/item_risk.html:89 templates/risks/item_risk.html:135
#: templates/risks/list_risks.html:86
#: templates/risks/item_risk.html:93 templates/risks/item_risk.html:139
#: templates/risks/list_risks.html:118 templates/risks/list_risks.html:122
#: templates/risks/risk_matrix.html:25
msgid "Likelihood"
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"
msgstr "Eintrittswahrscheinlichkeit"
#: templates/risks/item_risk.html:98 templates/risks/item_risk.html:144
#: templates/risks/list_risks.html:87
#: templates/risks/item_risk.html:102 templates/risks/item_risk.html:148
#: templates/risks/list_risks.html:119 templates/risks/list_risks.html:123
#: templates/risks/risk_matrix.html:25
msgid "Impact"
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"
msgstr "Schadensausmaß"
#: templates/risks/item_risk.html:107 templates/risks/item_risk.html:108
#: templates/risks/item_risk.html:153 templates/risks/item_risk.html:154
#: templates/risks/list_risks.html:89
#: templates/risks/item_risk.html:111 templates/risks/item_risk.html:112
#: templates/risks/item_risk.html:157 templates/risks/item_risk.html:158
#: templates/risks/list_risks.html:121 templates/risks/list_risks.html:125
msgid "Level"
msgstr "Stufe"
#: templates/risks/item_risk.html:116 templates/risks/item_risk.html:117
#: templates/risks/item_risk.html:161 templates/risks/item_risk.html:163
#: templates/risks/list_risks.html:88
#: templates/risks/item_risk.html:120 templates/risks/item_risk.html:121
#: templates/risks/item_risk.html:165 templates/risks/item_risk.html:167
#: templates/risks/list_risks.html:120 templates/risks/list_risks.html:124
#: templates/risks/risk_matrix.html:39
msgid "Score"
msgstr "Score"
#: templates/risks/item_risk.html:130
#: templates/risks/item_risk.html:134
msgid "Net (after measures)"
msgstr "Netto (nach Maßnahmen)"
#: templates/risks/item_risk.html:169
#: templates/risks/item_risk.html:173
msgid "No net risk recorded yet."
msgstr "Kein Restrisiko vergeben"
#: templates/risks/item_risk.html:180
#: templates/risks/item_risk.html:184
msgid "Save"
msgstr "Speichern"
#: templates/risks/item_risk.html:194
#: templates/risks/item_risk.html:198
msgid "Measures"
msgstr "Maßnahmen"
#: templates/risks/item_risk.html:203
#: templates/risks/item_risk.html:207
msgid "Deadline"
msgstr "Frist"
#: templates/risks/item_risk.html:204
#: templates/risks/item_risk.html:208
msgid "Responsible"
msgstr "Verantwortliche/r"
#: templates/risks/item_risk.html:205
#: templates/risks/item_risk.html:209
msgid "Link"
msgstr "Link"
#: templates/risks/item_risk.html:239
#: templates/risks/item_risk.html:243
msgid "No measures recorded."
msgstr "Keine Maßnahmen gefunden."
#: templates/risks/item_risk.html:257
#, fuzzy
#| msgid "Reported by"
#: templates/risks/item_risk.html:261
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."
msgstr "Keine Vorfälle gefunden."
#: templates/risks/item_risk.html:279
#: templates/risks/item_risk.html:283
msgid "History"
msgstr ""
#: templates/risks/item_risk.html:286
#: templates/risks/item_risk.html:290
msgid "Time"
msgstr "Zeitpunkt"
#: templates/risks/item_risk.html:288
#: templates/risks/item_risk.html:292
msgid "Action"
msgstr "Aktion"
#: templates/risks/item_risk.html:302
#: templates/risks/item_risk.html:306
msgid "No History found."
msgstr "Keine Historie vorhanden"
@ -571,20 +754,33 @@ msgstr "Keine Historie vorhanden"
msgid "Filter"
msgstr "Filter"
#: templates/risks/list_risks.html:22 templates/risks/list_risks.html:41
#: templates/risks/list_risks.html:60 templates/risks/notifications.html:13
msgid "All"
msgstr "Alle"
#: templates/risks/list_risks.html:56 templates/risks/list_risks.html:90
msgid "Risk Owner"
#: templates/risks/list_risks.html:83
#, fuzzy
#| msgid "Risk Owner"
msgid "Owner"
msgstr "Risikoeigner"
#: templates/risks/list_risks.html:84
#: templates/risks/list_risks.html:111
msgid "Asset / Process"
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"
msgstr "Aktuell keine Risiken"
@ -592,6 +788,10 @@ msgstr "Aktuell keine Risiken"
msgid "Unread"
msgstr "Ungelesen"
#: templates/risks/notifications.html:13
msgid "All"
msgstr "Alle"
#: templates/risks/notifications.html:20
msgid "Mark all as read"
msgstr "Alle als gelesen Markieren"
@ -607,3 +807,23 @@ msgstr "Als gelesen markieren"
#: templates/risks/notifications.html:53
msgid "No notifications."
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 ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\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
#: templates/risks/dashboard.html:80 templates/risks/dashboard.html:85
#: templates/risks/list_risks.html:76
#: templates/risks/list_risks.html:97
msgid "Risks"
msgstr ""
#: risks/admin.py:16 risks/models.py:190 templates/base.html:36
#: templates/risks/list_risks.html:37
#: risks/admin.py:16 risks/models.py:190 templates/base.html:38
#: templates/risks/list_risks.html:31
msgid "Controls"
msgstr ""
@ -45,8 +45,8 @@ msgstr ""
msgid "Reviews"
msgstr ""
#: risks/admin.py:19 risks/models.py:258 templates/base.html:37
#: templates/risks/item_risk.html:248
#: risks/admin.py:19 risks/models.py:258 templates/base.html:39
#: templates/risks/item_risk.html:252
msgid "Incidents"
msgstr ""
@ -54,7 +54,7 @@ msgstr ""
msgid "Users"
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"
msgstr ""
@ -98,15 +98,19 @@ msgstr ""
msgid "%(n)d notifications marked as unsent."
msgstr ""
#: risks/admin.py:177
msgid "SSO Information"
msgstr ""
#: risks/admin.py:186
msgid "Risks Owned"
#: risks/admin.py:182
msgid "Extra recipients"
msgstr ""
#: risks/admin.py:190
msgid "SSO Information"
msgstr ""
#: risks/admin.py:199
msgid "Risks Owned"
msgstr ""
#: risks/admin.py:203
msgid "Controls Responsible"
msgstr ""
@ -115,17 +119,17 @@ msgid "Risk Management"
msgstr ""
#: 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:256
#: templates/risks/item_risk.html:64 templates/risks/item_risk.html:206
#: templates/risks/item_risk.html:260
msgid "Status"
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"
msgstr ""
#: 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"
msgstr ""
@ -190,7 +194,7 @@ msgid "Availability"
msgstr ""
#: 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"
msgstr ""
@ -199,15 +203,17 @@ msgid "Description"
msgstr ""
#: risks/models.py:66 templates/risks/item_risk.html:53
#: templates/risks/list_risks.html:57
msgid "Asset"
msgstr ""
#: risks/models.py:67 templates/risks/item_risk.html:54
#: templates/risks/list_risks.html:70
msgid "Process"
msgstr ""
#: 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"
msgstr ""
@ -219,6 +225,10 @@ msgstr ""
msgid "Updated at"
msgstr ""
#: risks/models.py:71
msgid "Effects"
msgstr ""
#: risks/models.py:133
msgid "Residual Risk"
msgstr ""
@ -259,7 +269,7 @@ msgstr ""
msgid "Auditlogs"
msgstr ""
#: risks/models.py:257 templates/risks/item_risk.html:255
#: risks/models.py:257 templates/risks/item_risk.html:259
msgid "Incident"
msgstr ""
@ -275,15 +285,119 @@ msgstr ""
msgid "Reported by"
msgstr ""
#: risks/models.py:279
#: risks/models.py:279 risks/utils.py:119
msgid "Notification"
msgstr ""
#: risks/models.py:280 templates/base.html:78
#: risks/models.py:280 templates/base.html:80
#: templates/risks/notifications.html:4
msgid "Notifications"
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
#, python-brace-format
msgid "User '{u}' created"
@ -299,13 +413,13 @@ msgstr ""
msgid "Risk '{title}' {state}"
msgstr ""
#: risks/signals.py:72 risks/signals.py:147 risks/signals.py:240
#: risks/signals.py:296
#: risks/signals.py:72 risks/signals.py:166 risks/signals.py:212
#: risks/signals.py:275 risks/signals.py:369 risks/signals.py:411
msgid "created"
msgstr ""
#: risks/signals.py:72 risks/signals.py:147 risks/signals.py:240
#: risks/signals.py:296
#: risks/signals.py:72 risks/signals.py:166 risks/signals.py:212
#: risks/signals.py:275 risks/signals.py:369 risks/signals.py:411
msgid "updated"
msgstr ""
@ -314,77 +428,137 @@ msgstr ""
msgid "Risk '{title}' deleted"
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
msgid "Control '{title}' {state}"
msgstr ""
#: risks/signals.py:154
#: risks/signals.py:173
#, python-brace-format
msgid "Control '{title}' deleted"
msgstr ""
#: risks/signals.py:211
#, 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"
msgstr ""
#: risks/signals.py:230
#: risks/signals.py:265
#, python-brace-format
msgid "Review required for risk '{t}'"
msgstr ""
#: risks/signals.py:235
#: risks/signals.py:270
#, python-brace-format
msgid "Review completed for risk '{t}'"
msgstr ""
#: risks/signals.py:239
#: risks/signals.py:274
#, python-brace-format
msgid "Residual risk {state} for '{t}'"
msgstr ""
#: risks/signals.py:244
#: risks/signals.py:279
#, python-brace-format
msgid "Residual risk deleted for '{t}'"
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
msgid "Incident '{t}' {s}"
msgstr ""
#: risks/signals.py:301
#: risks/signals.py:374
#, python-brace-format
msgid "Incident '{t}' deleted"
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
msgid "Follow-up reached: review required for risk '{t}'"
msgstr ""
#: risks/views.py:315
#: risks/views.py:345
msgid "Notification marked as read."
msgstr ""
#: risks/views.py:323
#: risks/views.py:353
msgid "All notifications marked as read."
msgstr ""
#: risks/views.py:340
#: risks/views.py:370
msgid "Risk status updated."
msgstr ""
#: risks/views.py:354
#: risks/views.py:384
msgid "Control status updated."
msgstr ""
#: risks/views.py:368
#: risks/views.py:398
msgid "Incident status updated."
msgstr ""
#: risks/views.py:384
#: risks/views.py:414
msgid "Residual review flag updated."
msgstr ""
@ -392,32 +566,37 @@ msgstr ""
msgid "Dashboard"
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
msgid "Risk analysis"
msgstr ""
#: templates/base.html:73
#: templates/base.html:75
msgid "AdminCP"
msgstr ""
#: templates/base.html:86
#: templates/base.html:88
msgid "Derk Mode"
msgstr ""
#: templates/base.html:92
#: templates/base.html:94
msgid "Logout"
msgstr ""
#: templates/base.html:107
#: templates/base.html:109
msgid "Login"
msgstr ""
#: templates/base.html:142 templates/base.html:149
#: templates/base.html:144 templates/base.html:151
msgid "Light Mode"
msgstr ""
#: templates/base.html:152
#: templates/base.html:154
msgid "Dark Mode"
msgstr ""
@ -477,97 +656,100 @@ msgstr ""
msgid "Resubmission"
msgstr ""
#: templates/risks/item_risk.html:76
#: templates/risks/item_risk.html:80
msgid "Risk assessment"
msgstr ""
#: templates/risks/item_risk.html:84
#: templates/risks/item_risk.html:88
msgid "Gross (before measures)"
msgstr ""
#: templates/risks/item_risk.html:89 templates/risks/item_risk.html:135
#: templates/risks/list_risks.html:86
#: templates/risks/item_risk.html:93 templates/risks/item_risk.html:139
#: templates/risks/list_risks.html:118 templates/risks/list_risks.html:122
#: templates/risks/risk_matrix.html:25
msgid "Likelihood"
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"
msgstr ""
#: templates/risks/item_risk.html:98 templates/risks/item_risk.html:144
#: templates/risks/list_risks.html:87
#: templates/risks/item_risk.html:102 templates/risks/item_risk.html:148
#: templates/risks/list_risks.html:119 templates/risks/list_risks.html:123
#: templates/risks/risk_matrix.html:25
msgid "Impact"
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"
msgstr ""
#: templates/risks/item_risk.html:107 templates/risks/item_risk.html:108
#: templates/risks/item_risk.html:153 templates/risks/item_risk.html:154
#: templates/risks/list_risks.html:89
#: templates/risks/item_risk.html:111 templates/risks/item_risk.html:112
#: templates/risks/item_risk.html:157 templates/risks/item_risk.html:158
#: templates/risks/list_risks.html:121 templates/risks/list_risks.html:125
msgid "Level"
msgstr ""
#: templates/risks/item_risk.html:116 templates/risks/item_risk.html:117
#: templates/risks/item_risk.html:161 templates/risks/item_risk.html:163
#: templates/risks/list_risks.html:88
#: templates/risks/item_risk.html:120 templates/risks/item_risk.html:121
#: templates/risks/item_risk.html:165 templates/risks/item_risk.html:167
#: templates/risks/list_risks.html:120 templates/risks/list_risks.html:124
#: templates/risks/risk_matrix.html:39
msgid "Score"
msgstr ""
#: templates/risks/item_risk.html:130
#: templates/risks/item_risk.html:134
msgid "Net (after measures)"
msgstr ""
#: templates/risks/item_risk.html:169
#: templates/risks/item_risk.html:173
msgid "No net risk recorded yet."
msgstr ""
#: templates/risks/item_risk.html:180
#: templates/risks/item_risk.html:184
msgid "Save"
msgstr ""
#: templates/risks/item_risk.html:194
#: templates/risks/item_risk.html:198
msgid "Measures"
msgstr ""
#: templates/risks/item_risk.html:203
#: templates/risks/item_risk.html:207
msgid "Deadline"
msgstr ""
#: templates/risks/item_risk.html:204
#: templates/risks/item_risk.html:208
msgid "Responsible"
msgstr ""
#: templates/risks/item_risk.html:205
#: templates/risks/item_risk.html:209
msgid "Link"
msgstr ""
#: templates/risks/item_risk.html:239
#: templates/risks/item_risk.html:243
msgid "No measures recorded."
msgstr ""
#: templates/risks/item_risk.html:257
#: templates/risks/item_risk.html:261
msgid "Reported on"
msgstr ""
#: templates/risks/item_risk.html:271
#: templates/risks/item_risk.html:275
msgid "No incidents recorded."
msgstr ""
#: templates/risks/item_risk.html:279
#: templates/risks/item_risk.html:283
msgid "History"
msgstr ""
#: templates/risks/item_risk.html:286
#: templates/risks/item_risk.html:290
msgid "Time"
msgstr ""
#: templates/risks/item_risk.html:288
#: templates/risks/item_risk.html:292
msgid "Action"
msgstr ""
#: templates/risks/item_risk.html:302
#: templates/risks/item_risk.html:306
msgid "No History found."
msgstr ""
@ -575,20 +757,31 @@ msgstr ""
msgid "Filter"
msgstr ""
#: templates/risks/list_risks.html:22 templates/risks/list_risks.html:41
#: templates/risks/list_risks.html:60 templates/risks/notifications.html:13
msgid "All"
#: templates/risks/list_risks.html:83
msgid "Owner"
msgstr ""
#: templates/risks/list_risks.html:56 templates/risks/list_risks.html:90
msgid "Risk Owner"
msgstr ""
#: templates/risks/list_risks.html:84
#: templates/risks/list_risks.html:111
msgid "Asset / Process"
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"
msgstr ""
@ -596,6 +789,10 @@ msgstr ""
msgid "Unread"
msgstr ""
#: templates/risks/notifications.html:13
msgid "All"
msgstr ""
#: templates/risks/notifications.html:20
msgid "Mark all as read"
msgstr ""
@ -611,3 +808,23 @@ msgstr ""
#: templates/risks/notifications.html:53
msgid "No notifications."
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)
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
def level_id(level):
return LEVEL_ID_MAP.get(str(level), "")
@ -38,7 +52,7 @@ def likelihood_id_label(val):
return ""
label = _LIKELIHOOD_LABELS.get(i, "")
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
def impact_id_label(val):
@ -48,7 +62,7 @@ def impact_id_label(val):
return ""
label = _IMPACT_LABELS.get(i, "")
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
@ -132,3 +146,23 @@ def to_bg(css_class: str):
except Exception:
return ""
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/list_incidents", views.list_incidents, name="list_incidents"),
path("risks/incidents/<int:id>", views.show_incident, name="show_incident"),
path("risks/risk_matrix", views.risk_matrix, name="risk_matrix"),
# 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 rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from collections import Counter
from collections import Counter, defaultdict
from .forms import RiskStatusForm, ControlStatusForm, IncidentStatusForm, ResidualReviewForm
from .models import Risk, Control, ResidualRisk, AuditLog, Incident, Notification
from .serializers import ControlSerializer, RiskSerializer, ResidualRiskSerializer, UserSerializer, AuditSerializer, IncidentSerializer
@ -119,12 +119,15 @@ def list_risks(request):
"""
View for listing all Risks
"""
qs = Risk.objects.all().select_related("owner")
qs = Risk.objects.all().select_related("owner", "residual_risk")
# GET-Parameter lesen
risk_id = request.GET.get("risk")
control_id = request.GET.get("control")
owner_id = request.GET.get("owner")
category = request.GET.get("category")
asset = request.GET.get("asset")
process = request.GET.get("process")
if risk_id:
qs = qs.filter(id=risk_id)
@ -132,16 +135,43 @@ def list_risks(request):
qs = qs.filter(controls__id=control_id)
if 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()
controls = Control.objects.all().order_by("title")
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", {
"risks": risks,
"controls": controls,
"owners": owners,
"categories": categories,
"assets": assets,
"processes": processes,
})
@login_required
@ -383,3 +413,30 @@ def update_residual_review(request, risk_id):
obj.save(update_fields=["review_required", "updated_at"])
messages.success(request, _("Residual review flag updated."))
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 */
: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-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-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) */
@ -80,6 +80,9 @@
.progress.is-control-high::-moz-progress-bar{background:var(--c-high)}
.progress.is-control-veryhigh::-moz-progress-bar{background:var(--c-veryhigh)}
abbr {
text-decoration: none;
}
/* Topbar-Farbe erzwingen (Bulma überschreibt sonst mit weiß) */
.navbar.topbar-nav {

View file

@ -32,6 +32,8 @@
<a class="navbar-link">{% trans "Risk Management" %}</a>
<div class="navbar-dropdown">
<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_controls">{% trans "Controls" %}</a>
<a class="navbar-item" href="/risks/list_incidents">{% trans "Incidents" %}</a>

View file

@ -9,17 +9,13 @@
<h2 class="title is-5">{% trans "Filter" %}</h2>
<!-- Filter -->
<form method="get">
<form method="get" class="mb-4">
<div class="columns is-multiline">
<!-- Risiko Filter -->
<div class="column is-3">
<div class="field">
<label class="label">{% trans "Risk" %}</label>
<div class="control">
<div class="select is-fullwidth">
<div class="column is-2">
<div class="select is-small is-fullwidth">
<select name="risk" onchange="this.form.submit()">
<option value="">{% trans "All" %}</option>
<option value="">{% trans "Risk" %}</option>
{% for r in risks %}
<option value="{{ r.id }}" {% if request.GET.risk == r.id|stringformat:"s" %}selected{% endif %}>
{{ r.title }}
@ -28,17 +24,11 @@
</select>
</div>
</div>
</div>
</div>
<!-- Maßnahmen Filter -->
<div class="column is-3">
<div class="field">
<label class="label">{% trans "Controls" %}</label>
<div class="control">
<div class="select is-fullwidth">
<div class="column is-2">
<div class="select is-small is-fullwidth">
<select name="control" onchange="this.form.submit()">
<option value="">{% trans "All" %}</option>
<option value="">{% trans "Controls" %}</option>
{% for c in controls %}
<option value="{{ c.id }}" {% if request.GET.control == c.id|stringformat:"s" %}selected{% endif %}>
{{ c.title }}
@ -47,17 +37,50 @@
</select>
</div>
</div>
<div class="column is-2">
<div class="select is-small is-fullwidth">
<select name="category" onchange="this.form.submit()">
<option value="">{% trans "Category" %}</option>
{% for cat in categories %}
<option value="{{ cat }}" {% if request.GET.category == cat|stringformat:"s" %}selected{% endif %}>
{{ cat }}
</option>
{% endfor %}
</select>
</div>
</div>
<!-- Risikoeigner Filter -->
<div class="column is-3">
<div class="field">
<label class="label">{% trans "Risk Owner" %}</label>
<div class="control">
<div class="select is-fullwidth">
<div class="column is-2">
<div class="select is-small is-fullwidth">
<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 "All" %}</option>
<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 }}
@ -66,11 +89,9 @@
</select>
</div>
</div>
</div>
</div>
</div>
</form>
</form> <!-- Filter Ende -->
<h2 class="title is-5">{% trans "Risks" %}</h2>
@ -79,36 +100,32 @@
<table class="table is-bordered is-striped is-hoverable is-fullwidth">
<thead>
<tr>
{% if request.user.is_staff %}<th></th>{% endif %}
<th>{% trans "Risk" %}</th>
<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">
<th rowspan="2" class="has-text-centered">
<a class="icon has-text-success" href="{% url 'admin:risks_risk_add' %}" title="Risiko hinzufügen">
<i class="fas fa-add"></i>
</a>
</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</th>
{% endif %}
<th rowspan="2" class="has-text-centered">{% trans "Risk" %}</th>
<th rowspan="2" class="has-text-centered">{% trans "Asset / Process" %}</th>
<th rowspan="2" class="has-text-centered">{% trans "Category" %}</th>
<th rowspan="2" class="has-text-centered">{% trans "Risk Owner" %}</th>
<th colspan="4" class="has-text-centered has-background-light">{% trans "Gross Risk" %}</th>
<th colspan="4" class="has-text-centered has-background-info-light">{% trans "Net Risk" %}</th>
</tr>
<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 %}
<tr>
{% if request.user.is_staff %}
@ -126,19 +143,6 @@
{% endif %}
</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;">
{% if r.owner %}
{{ r.owner|user_display }}
@ -146,6 +150,51 @@
{% endif %}
</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>
{% empty %}
<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 %}