diff --git a/db.sqlite3 b/db.sqlite3 index a13ef47..e831ec3 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/risks/admin.py b/risks/admin.py index 7163e70..0616cf8 100644 --- a/risks/admin.py +++ b/risks/admin.py @@ -43,13 +43,19 @@ class ControlRisksInline(admin.TabularInline): class RiskAdmin(admin.ModelAdmin): list_display = ( "title", - "owner", + "owner_name", "score", "level", "likelihood", "impact", "follow_up", ) + + def owner_name(self, obj): + if not obj.owner: + return "-" + return obj.owner.get_full_name() or obj.owner.username + list_filter = ("level", "likelihood", "impact", "owner") search_fields = ("title", "asset", "process", "category") inlines = [ResidualRiskInline, ControlRisksInline] diff --git a/risks/templatetags/risk_extras.py b/risks/templatetags/risk_extras.py index 524b0f0..f06fd65 100644 --- a/risks/templatetags/risk_extras.py +++ b/risks/templatetags/risk_extras.py @@ -1,8 +1,64 @@ from django import template -from ..models import Risk +from django.utils.html import format_html +from ..models import Control, Incident, Risk register = template.Library() +_CONTROL_STATUS_MAP = dict(Control.STATUS_CHOICES) +_INCIDENT_STATUS_MAP = dict(Incident.STATUS_CHOICES) +_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 level_id(level): + return LEVEL_ID_MAP.get(str(level), "") + +@register.filter +def user_display(user): + if not user: + return "" + full = user.get_full_name() + return full if full else user.username + +def _short(label: str) -> str: + """First Segment of - from Impact and Likelihood""" + if not label: + return "" + for sep in (" – ", " - ", "("): + if sep in label: + return label.split(sep, 1)[0].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 +def impact_id_label(val): + try: + i = int(val) + except (TypeError, ValueError): + return "" + label = _IMPACT_LABELS.get(i, "") + short = _short(str(label)) if label else "" + return format_html("{}
({})", i, short) if label else format_html("{}", i) + + +@register.filter +def control_status_label(code): + return _CONTROL_STATUS_MAP.get(code, code) + +@register.filter +def incident_status_label(code): + return _INCIDENT_STATUS_MAP.get(code, code) + @register.filter def cia_label(value): mapping = dict(Risk.CIA_CHOICES) @@ -63,4 +119,16 @@ def score_class(score): return "is-control-mid" if s <= 16: return "is-control-high" - return "is-control-veryhigh" \ No newline at end of file + return "is-control-veryhigh" + +@register.filter +def to_bg(css_class: str): + """ + Wandelt is-control-foo -> has-background-control-foo um. + Für Tabellenzellen-Hintergrundfarben. + """ + try: + css = str(css_class) + except Exception: + return "" + return css.replace("is-control-", "has-background-control-") if css.startswith("is-control-") else css \ No newline at end of file diff --git a/static/css/design.css b/static/css/design.css index b29d3a4..62fe93d 100644 --- a/static/css/design.css +++ b/static/css/design.css @@ -160,4 +160,87 @@ body.dark-mode .box { /* Optional: Buttons, Links etc. anpassen */ body.dark-mode a { color: #bb86fc; +} + +/* Ticket-Button (ID links, Text rechts) */ +.risk-chip{ + --chip-w: 260px; + --chip-id-w: 40px; + width: var(--chip-w); + display: inline-flex; + align-items: stretch; + border: 0; + border-radius: 8px; + overflow: hidden; + font-weight: 600; + box-shadow: 0 4px 14px rgba(0,0,0,.08); + background: var(--chip-bg, #eee); + color: var(--chip-fg, #111); +} + +.risk-chip{ + display:inline-flex; + flex-direction:column; /* <— neu */ +} + +.risk-chip .chip-head{ + padding:.35rem .6rem; + font-size:.75rem; + font-weight:700; + text-transform:uppercase; + letter-spacing:.02em; + color:var(--chip-fg); + background:rgba(0,0,0,.10); + border-bottom:1px solid rgba(0,0,0,.12); +} + +.risk-chip .chip-main{ + display:flex; + align-items:stretch; +} + +/* linke ID-Spalte mit leichter Textur */ +.risk-chip .chip-id{ + flex: 0 0 var(--chip-id-w); + display: grid; + place-items: center; + font-size: 1.25rem; + position: relative; + background: rgba(0,0,0,.06); +} +.risk-chip .chip-id::after{ + content:""; + position: absolute; inset:0; + background: + linear-gradient( to right, rgba(255,255,255,.15), rgba(0,0,0,.08) 60% ), + repeating-linear-gradient(135deg, rgba(255,255,255,.12) 0 6px, transparent 6px 12px); + mix-blend-mode: soft-light; + opacity:.6; +} + +/* rechte Text-Spalte */ +.risk-chip .chip-label{ + flex: 1 1 auto; + padding: .5rem .75rem; + line-height: 1.25; + display: flex; + align-items: center; + border-left: 1px solid rgba(0,0,0,.08); + min-height: 2.25rem; +} + +/* Farbzuteilung aus deinen Custom-Klassen */ +.risk-chip.is-control-verylow { --chip-bg: var(--c-verylow); --chip-fg: var(--c-verylow-inv); } +.risk-chip.is-control-low { --chip-bg: var(--c-low); --chip-fg: var(--c-low-inv); } +.risk-chip.is-control-mid { --chip-bg: var(--c-mid); --chip-fg: var(--c-mid-inv); } +.risk-chip.is-control-high { --chip-bg: var(--c-high); --chip-fg: var(--c-high-inv); } +.risk-chip.is-control-veryhigh{ --chip-bg: var(--c-veryhigh); --chip-fg: var(--c-veryhigh-inv); } + +/* Responsiv: auf schmalen Screens volle Breite */ +@media (max-width: 480px){ + .risk-chip{ width: 100%; } +} + +@media (max-width: 1215px) { + .risk-chip { --chip-w: 100%; width: var(--chip-w); } } \ No newline at end of file diff --git a/templates/risks/dashboard.html b/templates/risks/dashboard.html index 1ef7563..ab297ea 100644 --- a/templates/risks/dashboard.html +++ b/templates/risks/dashboard.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% load i18n %} +{% load i18n risk_extras %} {% block content %}
@@ -26,7 +26,7 @@ - +

{{ residual_review_required }}

@@ -47,8 +47,8 @@

{% trans "Controls by Status" %}

    - {% for status in controls_by_status %} -
  • {{ status.status }}: {{ status.count }}
  • + {% for row in controls_by_status %} +
  • {{ row.status|control_status_label }}: {{ row.count }}
  • {% endfor %}
@@ -57,8 +57,8 @@

{% trans "Incidents by Status" %}

    - {% for incident in incidents_status %} -
  • {{ incident.status }}: {{ incident.count }}
  • + {% for row in incidents_status %} +
  • {{ row.status|incident_status_label }}: {{ row.count }}
  • {% endfor %}
@@ -72,12 +72,12 @@ {% if cia == '1' %}

{% trans "Confidentiality" %}

-

{{ count }} Risks

+

{{ count }} {% trans "Risks" %}

{% elif cia == '2' %}

{% trans "Integrity" %}

-

{{ count }} Risks

+

{{ count }} {% trans "Risks" %}

{% elif cia == '3' %}
diff --git a/templates/risks/item_risk.html b/templates/risks/item_risk.html index f227dce..2a3267b 100644 --- a/templates/risks/item_risk.html +++ b/templates/risks/item_risk.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% load risk_extras %} +{% load i18n risk_extras %} {% block crumbs %}
  • Risikoanalyse
  • {{ risk.title }}
  • @@ -32,6 +32,7 @@

    Asset: {{ risk.asset|default:"-" }}

    Prozess: {{ risk.process|default:"-" }}

    +

    Kategorie: {{ risk.category|default:"-" }}

    Schutzziele: {% if risk.cia %} @@ -42,103 +43,111 @@

    -

    Kategorie: {{ risk.category|default:"-" }}

    -

    Risikoeigner: {{ risk.owner|default:"-" }}

    +

    Risikoeigner: {{ risk.owner|user_display|default:"-" }}

    Erstellt am: {{ risk.created_at|date:'d.m.Y H:i' }}

    Aktualisiert am: {{ risk.updated_at|date:'d.m.Y H:i' }}

    +

    Wiedervorlage am: {{ risk.follow_up|date:'d.m.Y' }}

    -

    Risikobewertung

    +
    +
    +

    Risikobewertung

    +
    +
    -
    -
    +
    +

    Brutto (vor Maßnahmen)

    -
    -

    Eintrittswahrscheinlichkeit

    - -
    +
    -
    -

    Schadensausmaß

    - -
    +
    -
    -

    Stufe

    - -
    +
    -
    -

    Score

    - -
    +
    -
    -
    +
    +

    Netto (nach Maßnahmen)

    - +
    {% if risk.residual_risk %} -
    - -
    -

    Eintrittswahrscheinlichkeit

    - -
    +
    -
    -

    Schadensausmaß

    - -
    +
    -
    -

    Stufe

    - -
    +
    -
    -

    Score

    - -
    -
    {% else %}

    Noch kein Nettorisiko erfasst.

    {% endif %} - +
    diff --git a/templates/risks/list_risks.html b/templates/risks/list_risks.html index 7927672..35ce759 100644 --- a/templates/risks/list_risks.html +++ b/templates/risks/list_risks.html @@ -1,11 +1,12 @@ {% extends "base.html" %} +{% load i18n risk_extras %} {% block crumbs %} -
  • Risikoanalyse
  • +
  • {% trans "Risk analysis" %}
  • {% endblock %} {% block content %}
    -

    Auswahl

    +

    {% trans "Filter" %}

    @@ -14,11 +15,11 @@
    - +
    - + {% for c in controls %}