Change Likelihood from Map_Choices to DB
This commit is contained in:
parent
65dc2231bb
commit
a5a31f4dcf
10 changed files with 127 additions and 107 deletions
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
|
@ -7,6 +7,7 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from .models import (
|
from .models import (
|
||||||
Control,
|
Control,
|
||||||
Incident,
|
Incident,
|
||||||
|
LikelihoodChoice,
|
||||||
Notification,
|
Notification,
|
||||||
NotificationPreference,
|
NotificationPreference,
|
||||||
NotificationRule,
|
NotificationRule,
|
||||||
|
@ -230,3 +231,11 @@ class UserAdmin(BaseUserAdmin):
|
||||||
def responsible_controls_count(self, obj):
|
def responsible_controls_count(self, obj):
|
||||||
return obj.controls_responsible.count()
|
return obj.controls_responsible.count()
|
||||||
responsible_controls_count.short_description = _("Controls Responsible")
|
responsible_controls_count.short_description = _("Controls Responsible")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# LikelihoodChoice
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
@admin.register(LikelihoodChoice)
|
||||||
|
class LikelihoodChoiceAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("value", "name", "description")
|
|
@ -1,6 +1,7 @@
|
||||||
from .auditlog import AuditLog
|
from .auditlog import AuditLog
|
||||||
from .control import Control
|
from .control import Control
|
||||||
from .incident import Incident
|
from .incident import Incident
|
||||||
|
from .likelihood_choice import LikelihoodChoice
|
||||||
from .notification import Notification
|
from .notification import Notification
|
||||||
from .notification_kind import NotificationKind
|
from .notification_kind import NotificationKind
|
||||||
from .notification_preference import NotificationPreference
|
from .notification_preference import NotificationPreference
|
||||||
|
@ -10,4 +11,4 @@ from .risk import Risk
|
||||||
from .user import User
|
from .user import User
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["AuditLog", "Control", "Incident", "Notification", "NotificationKind", "NotificationPreference", "NotificationRule", "ResidualRisk", "Risk", "User"]
|
__all__ = ["AuditLog", "Control", "Incident", "LikelihoodChoice", "Notification", "NotificationKind", "NotificationPreference", "NotificationRule", "ResidualRisk", "Risk", "User"]
|
13
risks/models/likelihood_choice.py
Normal file
13
risks/models/likelihood_choice.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
class LikelihoodChoice(models.Model):
|
||||||
|
"""
|
||||||
|
Likelihood choices for Risks and Controls.
|
||||||
|
"""
|
||||||
|
name = models.CharField(_("Likelihood Name"), max_length=50)
|
||||||
|
description = models.TextField(_("Description"), blank=True, null=True)
|
||||||
|
value = models.IntegerField(_("Numeric Value"), unique=True, default=1)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.value} - {self.name} ({self.description})"
|
|
@ -1,7 +1,9 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from .likelihood_choice import LikelihoodChoice
|
||||||
from .risk import Risk
|
from .risk import Risk
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Residual Risk
|
# Residual Risk
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
@ -13,7 +15,12 @@ class ResidualRisk(models.Model):
|
||||||
verbose_name_plural = _("Residual Risks")
|
verbose_name_plural = _("Residual Risks")
|
||||||
|
|
||||||
risk = models.OneToOneField("Risk", on_delete=models.CASCADE, related_name="residual_risk")
|
risk = models.OneToOneField("Risk", on_delete=models.CASCADE, related_name="residual_risk")
|
||||||
likelihood = models.IntegerField(choices=Risk.LIKELIHOOD_CHOICES, default=1)
|
likelihood = models.ForeignKey(
|
||||||
|
LikelihoodChoice,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
verbose_name=_("Likelihood"),
|
||||||
|
related_name="residual_risks",
|
||||||
|
)
|
||||||
impact = models.IntegerField(choices=Risk.IMPACT_CHOICES, default=1)
|
impact = models.IntegerField(choices=Risk.IMPACT_CHOICES, default=1)
|
||||||
score = models.IntegerField(editable=False)
|
score = models.IntegerField(editable=False)
|
||||||
level = models.CharField(max_length=50, editable=False)
|
level = models.CharField(max_length=50, editable=False)
|
||||||
|
@ -29,7 +36,7 @@ class ResidualRisk(models.Model):
|
||||||
self.status = "review_required"
|
self.status = "review_required"
|
||||||
|
|
||||||
# Calculate residual risk score and level
|
# Calculate residual risk score and level
|
||||||
self.score = self.likelihood * self.impact
|
self.score = self.likelihood.value * self.impact
|
||||||
if self.score <= 4:
|
if self.score <= 4:
|
||||||
self.level = "Low"
|
self.level = "Low"
|
||||||
elif self.score <= 8:
|
elif self.score <= 8:
|
||||||
|
|
|
@ -2,6 +2,7 @@ from django.db import models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from multiselectfield import MultiSelectField
|
from multiselectfield import MultiSelectField
|
||||||
|
from .likelihood_choice import LikelihoodChoice
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Risk
|
# Risk
|
||||||
|
@ -18,12 +19,6 @@ class Risk(models.Model):
|
||||||
("closed", _("Closed")),
|
("closed", _("Closed")),
|
||||||
("review_required", _("Review required")),
|
("review_required", _("Review required")),
|
||||||
]
|
]
|
||||||
LIKELIHOOD_CHOICES = [
|
|
||||||
(1, _("Very low – occurs less than once every 5 years")),
|
|
||||||
(2, _("Low – once every 1–5 years")),
|
|
||||||
(3, _("Likely – once per year or more")),
|
|
||||||
(4, _("Very likely – multiple times per year/monthly")),
|
|
||||||
]
|
|
||||||
IMPACT_CHOICES = [
|
IMPACT_CHOICES = [
|
||||||
(1, _("Very Low (< 1,000 € – minor operational impact)")),
|
(1, _("Very Low (< 1,000 € – minor operational impact)")),
|
||||||
(2, _("Low (1,000–5,000 € – local impact)")),
|
(2, _("Low (1,000–5,000 € – local impact)")),
|
||||||
|
@ -58,7 +53,12 @@ class Risk(models.Model):
|
||||||
cia = MultiSelectField(choices=CIA_CHOICES, max_length=100, blank=True, null=True)
|
cia = MultiSelectField(choices=CIA_CHOICES, max_length=100, blank=True, null=True)
|
||||||
|
|
||||||
# Risk evaluation before controls
|
# Risk evaluation before controls
|
||||||
likelihood = models.IntegerField(choices=LIKELIHOOD_CHOICES, default=1)
|
likelihood = models.ForeignKey(
|
||||||
|
LikelihoodChoice,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
verbose_name=_("Likelihood"),
|
||||||
|
related_name="risks",
|
||||||
|
)
|
||||||
impact = models.IntegerField(choices=IMPACT_CHOICES, default=1)
|
impact = models.IntegerField(choices=IMPACT_CHOICES, default=1)
|
||||||
|
|
||||||
# Calculated fields
|
# Calculated fields
|
||||||
|
@ -85,7 +85,7 @@ class Risk(models.Model):
|
||||||
self.status = "review_required"
|
self.status = "review_required"
|
||||||
|
|
||||||
# Calculate risk score and level
|
# Calculate risk score and level
|
||||||
self.score = self.likelihood * self.impact
|
self.score = self.likelihood.value * self.impact
|
||||||
if self.score <= 4:
|
if self.score <= 4:
|
||||||
self.level = "Low"
|
self.level = "Low"
|
||||||
elif self.score <= 8:
|
elif self.score <= 8:
|
||||||
|
|
|
@ -1,16 +1,50 @@
|
||||||
from django import template
|
from django import template
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from ..models import Control, Incident, Risk
|
from ..models import Control, Incident, Risk, LikelihoodChoice
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
_RISK_STATUS_MAP = dict(Risk.STATUS_CHOICES)
|
_RISK_STATUS_MAP = dict(Risk.STATUS_CHOICES)
|
||||||
_CONTROL_STATUS_MAP = dict(Control.STATUS_CHOICES)
|
_CONTROL_STATUS_MAP = dict(Control.STATUS_CHOICES)
|
||||||
_INCIDENT_STATUS_MAP = dict(Incident.STATUS_CHOICES)
|
_INCIDENT_STATUS_MAP = dict(Incident.STATUS_CHOICES)
|
||||||
_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}
|
||||||
|
|
||||||
|
# Likelihood
|
||||||
|
def get_likelihood_label(likelihood_obj):
|
||||||
|
if not likelihood_obj:
|
||||||
|
return ""
|
||||||
|
return f"{likelihood_obj.value} ({likelihood_obj.name})"
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def likelihood_id_label(likelihood):
|
||||||
|
if isinstance(likelihood, LikelihoodChoice):
|
||||||
|
return f"{likelihood.value} ({likelihood.name})"
|
||||||
|
return likelihood
|
||||||
|
|
||||||
|
def get_likelihood_value(likelihood):
|
||||||
|
if isinstance(likelihood, LikelihoodChoice):
|
||||||
|
return likelihood.value
|
||||||
|
return likelihood
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def likelihood_value(likelihood_obj):
|
||||||
|
return get_likelihood_value(likelihood_obj)
|
||||||
|
|
||||||
|
def get_likelihood_class(likelihood):
|
||||||
|
value = get_likelihood_value(likelihood)
|
||||||
|
LIKELIHOOD_MAP = {
|
||||||
|
1: "is-control-verylow",
|
||||||
|
2: "is-control-low",
|
||||||
|
3: "is-control-mid",
|
||||||
|
4: "is-control-high",
|
||||||
|
}
|
||||||
|
return LIKELIHOOD_MAP.get(value, "is-light")
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def likelihood_class(likelihood):
|
||||||
|
return get_likelihood_class(likelihood)
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def sort_url(request, field, current_sort, current_dir):
|
def sort_url(request, field, current_sort, current_dir):
|
||||||
query = request.GET.copy()
|
query = request.GET.copy()
|
||||||
|
@ -56,16 +90,6 @@ def _short(label: str) -> str:
|
||||||
return label.split(sep, 1)[0].strip()
|
return label.split(sep, 1)[0].strip()
|
||||||
return label.strip()
|
return label.strip()
|
||||||
|
|
||||||
@register.filter
|
|
||||||
def likelihood_id_label(val):
|
|
||||||
try:
|
|
||||||
i = int(val)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return ""
|
|
||||||
label = _LIKELIHOOD_LABELS.get(i, "")
|
|
||||||
short = _short(str(label)) if label else ""
|
|
||||||
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):
|
||||||
try:
|
try:
|
||||||
|
@ -115,13 +139,6 @@ LEVEL_MAP = {
|
||||||
"Critical": "is-control-veryhigh",
|
"Critical": "is-control-veryhigh",
|
||||||
}
|
}
|
||||||
|
|
||||||
@register.filter
|
|
||||||
def likelihood_class(val):
|
|
||||||
try:
|
|
||||||
return LIKELIHOOD_MAP.get(int(val), "is-light")
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return "is-light"
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def impact_class(val):
|
def impact_class(val):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -13,7 +13,7 @@ from rest_framework import viewsets
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
|
||||||
from .forms import RiskStatusForm, ControlStatusForm, IncidentStatusForm, ResidualReviewForm
|
from .forms import RiskStatusForm, ControlStatusForm, IncidentStatusForm, ResidualReviewForm
|
||||||
from .models import AuditLog, Risk, Control, ResidualRisk, AuditLog, Incident, Notification
|
from .models import AuditLog, Risk, Control, ResidualRisk, AuditLog, Incident, LikelihoodChoice, Notification
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
ControlSerializer, RiskSerializer, ResidualRiskSerializer,
|
ControlSerializer, RiskSerializer, ResidualRiskSerializer,
|
||||||
UserSerializer, AuditSerializer, IncidentSerializer,
|
UserSerializer, AuditSerializer, IncidentSerializer,
|
||||||
|
@ -467,16 +467,17 @@ def risk_matrix(request):
|
||||||
"""Show gross/net risk matrix."""
|
"""Show gross/net risk matrix."""
|
||||||
risks = Risk.objects.select_related("owner", "residual_risk").all()
|
risks = Risk.objects.select_related("owner", "residual_risk").all()
|
||||||
impacts = sorted(Risk.IMPACT_CHOICES, key=lambda x: x[0])
|
impacts = sorted(Risk.IMPACT_CHOICES, key=lambda x: x[0])
|
||||||
likelihoods = sorted(Risk.LIKELIHOOD_CHOICES, key=lambda x: x[0])
|
likelihoods = LikelihoodChoice.objects.all().order_by('value')
|
||||||
|
|
||||||
gross_matrix = {i: {l: [] for l, _ in likelihoods} for i, _ in impacts}
|
# Erstelle die Matrizen mit den Werten der LikelihoodChoice-Objekte
|
||||||
net_matrix = {i: {l: [] for l, _ in likelihoods} for i, _ in impacts}
|
gross_matrix = {i: {likelihood.value: [] for likelihood in likelihoods} for i, _ in impacts}
|
||||||
|
net_matrix = {i: {likelihood.value: [] for likelihood in likelihoods} for i, _ in impacts}
|
||||||
|
|
||||||
for r in risks:
|
for r in risks:
|
||||||
gross_matrix[r.impact][r.likelihood].append(r)
|
gross_matrix[r.impact][r.likelihood.value].append(r)
|
||||||
rr = getattr(r, "residual_risk", None)
|
rr = getattr(r, "residual_risk", None)
|
||||||
if rr:
|
if rr:
|
||||||
net_matrix[rr.impact][rr.likelihood].append(r)
|
net_matrix[rr.impact][rr.likelihood.value].append(r)
|
||||||
|
|
||||||
return render(request, "risks/risk_matrix.html", {
|
return render(request, "risks/risk_matrix.html", {
|
||||||
"impacts": impacts,
|
"impacts": impacts,
|
||||||
|
|
|
@ -98,8 +98,8 @@
|
||||||
<div class="column is-half">
|
<div class="column is-half">
|
||||||
<button class="risk-chip {{ risk.likelihood|likelihood_class }}" type="button">
|
<button class="risk-chip {{ risk.likelihood|likelihood_class }}" type="button">
|
||||||
<span class="chip-head">{% trans "Likelihood" %}</span>
|
<span class="chip-head">{% trans "Likelihood" %}</span>
|
||||||
<span class="chip-id">{{ risk.likelihood }}</span>
|
<span class="chip-id">{{ risk.likelihood.value }} - {{ risk.likelihood.name }}</span>
|
||||||
<span class="chip-label">{{ risk.get_likelihood_display }}</span>
|
<span class="chip-label">{{ risk.likelihood.description }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -144,8 +144,8 @@
|
||||||
<div class="column is-half">
|
<div class="column is-half">
|
||||||
<button class="risk-chip {{ risk.residual_risk.likelihood|likelihood_class }}" type="button">
|
<button class="risk-chip {{ risk.residual_risk.likelihood|likelihood_class }}" type="button">
|
||||||
<span class="chip-head">{% trans "Likelihood" %}</span>
|
<span class="chip-head">{% trans "Likelihood" %}</span>
|
||||||
<span class="chip-id">{{ risk.residual_risk.likelihood }}</span>
|
<span class="chip-id">{{ risk.residual_risk.likelihood.value }} - {{ risk.residual_risk.likelihood.name }}</span>
|
||||||
<span class="chip-label">{{ risk.residual_risk.get_likelihood_display }}</span>
|
<span class="chip-label">{{ risk.residual_risk.likelihood.description }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,7 @@
|
||||||
<a class="is-active" data-tab="matrix">{% trans "Risk Matrix" %}</a>
|
<a class="is-active" data-tab="matrix">{% trans "Risk Matrix" %}</a>
|
||||||
<a data-tab="details">{% trans "Detail View" %}</a>
|
<a data-tab="details">{% trans "Detail View" %}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
|
|
||||||
<!-- Main Container -->
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<!-- Panel: Matrix View -->
|
<!-- Panel: Matrix View -->
|
||||||
<div class="tab-panel" data-tab="matrix">
|
<div class="tab-panel" data-tab="matrix">
|
||||||
|
@ -19,8 +16,8 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="has-text-left">{% trans "Impact" %} * {% trans "Likelihood" %}</th>
|
<th class="has-text-left">{% trans "Impact" %} * {% trans "Likelihood" %}</th>
|
||||||
{% for l_val, l_label in likelihoods %}
|
{% for likelihood in likelihoods %}
|
||||||
<th class="{{ l_val|likelihood_class|to_bg }}">{{ l_label }}</th>
|
<th class="{{ likelihood.value|likelihood_class|to_bg }}">{{ likelihood.value }} ({{ likelihood.name }}) <br> {{ likelihood.description }}</th>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -28,8 +25,8 @@
|
||||||
{% for i_val, i_label in impacts reversed %}
|
{% for i_val, i_label in impacts reversed %}
|
||||||
<tr>
|
<tr>
|
||||||
<th class="has-text-left {{ i_val|impact_class|to_bg }}">{{ i_label }}</th>
|
<th class="has-text-left {{ i_val|impact_class|to_bg }}">{{ i_label }}</th>
|
||||||
{% for l_val, l_label in likelihoods %}
|
{% for likelihood in likelihoods %}
|
||||||
{% with s=i_val|mul:l_val %}
|
{% with s=i_val|mul:likelihood.value %}
|
||||||
<td class="risk-matrix-cell {{ s|score_bg_class }}">
|
<td class="risk-matrix-cell {{ s|score_bg_class }}">
|
||||||
<div class="is-flex is-justify-content-center is-align-items-center">
|
<div class="is-flex is-justify-content-center is-align-items-center">
|
||||||
{% if s <= 4 %}
|
{% if s <= 4 %}
|
||||||
|
@ -49,12 +46,9 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div><!-- Panel: Matrix View End -->
|
</div>
|
||||||
|
|
||||||
<!-- Panel: Details View -->
|
<!-- Panel: Details View -->
|
||||||
<div class="tab-panel is-hidden" data-tab="details">
|
<div class="tab-panel is-hidden" data-tab="details">
|
||||||
|
|
||||||
<!-- Mode Toggle (Gross / Net) -->
|
|
||||||
<div class="level mb-3">
|
<div class="level mb-3">
|
||||||
<div class="level-left">
|
<div class="level-left">
|
||||||
<div class="level-item"><strong>{% trans "Show" %}:</strong></div>
|
<div class="level-item"><strong>{% trans "Show" %}:</strong></div>
|
||||||
|
@ -69,16 +63,15 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- Mode Toggle End -->
|
</div>
|
||||||
|
|
||||||
<!-- Gross Risk List -->
|
<!-- Gross Risk List -->
|
||||||
<div class="details-table" data-mode="gross">
|
<div class="details-table" data-mode="gross">
|
||||||
<table class="table is-bordered is-fullwidth has-text-centered risk-matrix-table">
|
<table class="table is-bordered is-fullwidth has-text-centered risk-matrix-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="has-text-left">{% trans "Impact" %} / {% trans "Likelihood" %}</th>
|
<th class="has-text-left">{% trans "Impact" %} / {% trans "Likelihood" %}</th>
|
||||||
{% for l_val, l_label in likelihoods %}
|
{% for likelihood in likelihoods %}
|
||||||
<th class="{{ l_val|likelihood_class|to_bg }}">{{ l_label }}</th>
|
<th class="{{ likelihood.value|likelihood_class|to_bg }}">{{ likelihood.value }} ({{ likelihood.name }})</th>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -86,11 +79,11 @@
|
||||||
{% for i_val, i_label in impacts reversed %}
|
{% for i_val, i_label in impacts reversed %}
|
||||||
<tr>
|
<tr>
|
||||||
<th class="has-text-left {{ i_val|impact_class|to_bg }}">{{ i_label }}</th>
|
<th class="has-text-left {{ i_val|impact_class|to_bg }}">{{ i_label }}</th>
|
||||||
{% for l_val, l_label in likelihoods %}
|
{% for likelihood in likelihoods %}
|
||||||
{% with row=gross_matrix|dict_get:i_val %}
|
{% with row=gross_matrix|dict_get:i_val %}
|
||||||
{% with cell=row|dict_get:l_val %}
|
{% with cell=row|dict_get:likelihood.value %}
|
||||||
{% with s=i_val|mul:l_val %}
|
{% with s=i_val|mul:likelihood.value %}
|
||||||
<td class="risk-matrix-cell {{ s|score_bg_class }}">
|
<td class="risk-matrix-cell {{ likelihood|likelihood_class }}">
|
||||||
{% if cell %}
|
{% if cell %}
|
||||||
<ul class="risk-cell-list">
|
<ul class="risk-cell-list">
|
||||||
{% for risk in cell %}
|
{% for risk in cell %}
|
||||||
|
@ -111,16 +104,15 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div><!-- Gross Risk List End -->
|
</div>
|
||||||
|
|
||||||
<!-- Net Risk List -->
|
<!-- Net Risk List -->
|
||||||
<div class="details-table is-hidden" data-mode="net">
|
<div class="details-table is-hidden" data-mode="net">
|
||||||
<table class="table is-bordered is-fullwidth has-text-centered risk-matrix-table">
|
<table class="table is-bordered is-fullwidth has-text-centered risk-matrix-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="has-text-left">{% trans "Impact" %} / {% trans "Likelihood" %}</th>
|
<th class="has-text-left">{% trans "Impact" %} / {% trans "Likelihood" %}</th>
|
||||||
{% for l_val, l_label in likelihoods %}
|
{% for likelihood in likelihoods %}
|
||||||
<th class="{{ l_val|likelihood_class|to_bg }}">{{ l_label }}</th>
|
<th class="{{ likelihood.value|likelihood_class|to_bg }}">{{ likelihood.value }} ({{ likelihood.name }})</th>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -128,11 +120,11 @@
|
||||||
{% for i_val, i_label in impacts reversed %}
|
{% for i_val, i_label in impacts reversed %}
|
||||||
<tr>
|
<tr>
|
||||||
<th class="has-text-left {{ i_val|impact_class|to_bg }}">{{ i_label }}</th>
|
<th class="has-text-left {{ i_val|impact_class|to_bg }}">{{ i_label }}</th>
|
||||||
{% for l_val, l_label in likelihoods %}
|
{% for likelihood in likelihoods %}
|
||||||
{% with row=net_matrix|dict_get:i_val %}
|
{% with row=net_matrix|dict_get:i_val %}
|
||||||
{% with cell=row|dict_get:l_val %}
|
{% with cell=row|dict_get:likelihood.value %}
|
||||||
{% with s=i_val|mul:l_val %}
|
{% with s=i_val|mul:likelihood.value %}
|
||||||
<td class="risk-matrix-cell {{ s|score_bg_class }}">
|
<td class="risk-matrix-cell {{ likelihood|likelihood_class }}">
|
||||||
{% if cell %}
|
{% if cell %}
|
||||||
<ul class="risk-cell-list">
|
<ul class="risk-cell-list">
|
||||||
{% for risk in cell %}
|
{% for risk in cell %}
|
||||||
|
@ -153,49 +145,31 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div><!-- Net Risk List End -->
|
</div>
|
||||||
|
</div>
|
||||||
</div><!-- Panel: Details View End -->
|
</div>
|
||||||
|
</section>
|
||||||
</div><!-- Main Container End -->
|
|
||||||
</section><!-- Section End -->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Handle ERP-style tabs
|
|
||||||
const tabs = document.querySelectorAll('.erp-tabs a[data-tab]');
|
const tabs = document.querySelectorAll('.erp-tabs a[data-tab]');
|
||||||
const panels = document.querySelectorAll('.tab-panel');
|
const panels = document.querySelectorAll('.tab-panel');
|
||||||
|
|
||||||
tabs.forEach(tab => {
|
tabs.forEach(tab => {
|
||||||
tab.addEventListener('click', e => {
|
tab.addEventListener('click', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// Deactivate all
|
|
||||||
tabs.forEach(x => x.classList.remove('is-active'));
|
tabs.forEach(x => x.classList.remove('is-active'));
|
||||||
panels.forEach(p => p.classList.add('is-hidden'));
|
panels.forEach(p => p.classList.add('is-hidden'));
|
||||||
|
|
||||||
// Activate clicked
|
|
||||||
tab.classList.add('is-active');
|
tab.classList.add('is-active');
|
||||||
const target = tab.getAttribute('data-tab');
|
const target = tab.getAttribute('data-tab');
|
||||||
const activePanel = document.querySelector(`.tab-panel[data-tab="${target}"]`);
|
const activePanel = document.querySelector(`.tab-panel[data-tab="${target}"]`);
|
||||||
if (activePanel) activePanel.classList.remove('is-hidden');
|
if (activePanel) activePanel.classList.remove('is-hidden');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle Gross/Net toggle buttons in "Details" tab
|
|
||||||
const toggles = document.querySelectorAll('.details-toggle');
|
const toggles = document.querySelectorAll('.details-toggle');
|
||||||
const tables = document.querySelectorAll('.details-table');
|
const tables = document.querySelectorAll('.details-table');
|
||||||
|
|
||||||
toggles.forEach(btn => {
|
toggles.forEach(btn => {
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
const mode = btn.getAttribute('data-mode'); // "gross" or "net"
|
const mode = btn.getAttribute('data-mode');
|
||||||
|
|
||||||
// Toggle active state on buttons
|
|
||||||
toggles.forEach(b => b.classList.toggle('is-active', b === btn));
|
toggles.forEach(b => b.classList.toggle('is-active', b === btn));
|
||||||
|
|
||||||
// Show the right table
|
|
||||||
tables.forEach(t => {
|
tables.forEach(t => {
|
||||||
t.classList.toggle('is-hidden', t.getAttribute('data-mode') !== mode);
|
t.classList.toggle('is-hidden', t.getAttribute('data-mode') !== mode);
|
||||||
});
|
});
|
||||||
|
@ -203,6 +177,4 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Add table
Reference in a new issue