Skip to content
Merged
8 changes: 5 additions & 3 deletions dejacode/static/css/dejacode_bootstrap.css
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,14 @@ table.text-break thead {
}
.bg-warning-orange {
background-color: var(--bs-orange);
color: #000;
color: #fff;
}
.text-warning-orange {
color: var(--bs-orange) !important;
}
.bg-warning-orange-subtle {
background-color: rgba(253, 126, 20, 0.15);
}
.spinner-border-md {
--bs-spinner-width: 1.5rem;
--bs-spinner-height: 1.5rem;
Expand Down Expand Up @@ -798,8 +801,7 @@ pre.log {
.nav-pills .show>.nav-link {
background-color: var(--bs-djc-blue-bg);
}
.card,
.table {
.card {
box-shadow: rgba(0, 0, 0, 0.05) 0 0.0625rem 0.125rem;
}
.table-md th,
Expand Down
1 change: 1 addition & 0 deletions dje/templates/includes/navbar_header.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
{% url 'license_library:license_list' as license_list_url %}
{% url 'organization:owner_list' as owner_list_url %}
{% url 'global_search' as global_search_url %}
{% url 'product_portfolio:compliance_dashboard' as compliance_dashboard_url %}
{% url 'reporting:report_list' as report_list_url %}
{% url 'workflow:request_list' as request_list_url %}
{% url 'component_catalog:scan_list' as scan_list_url %}
Expand Down
6 changes: 6 additions & 0 deletions dje/templates/includes/navbar_header_tools_menu.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
Tools
</a>
<div class="dropdown-menu">
<div class="dropdown-header">Compliance</div>
<a class="dropdown-item{% if compliance_dashboard_url in request.path %} active{% endif %}" href="{{ compliance_dashboard_url }}">
<i class="fa-solid fa-shield-halved" aria-hidden="true"></i>
Control Center
</a>
<div class="dropdown-divider"></div>
<div class="dropdown-header">Reporting</div>
<a class="dropdown-item{% if report_list_url in request.path %} active{% endif %}" href="{{ report_list_url }}">
<i class="far fa-chart-bar" aria-hidden="true"></i>
Expand Down
67 changes: 65 additions & 2 deletions product_portfolio/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@
from django.core.validators import MinValueValidator
from django.db import models
from django.db.models import Case
from django.db.models import Count
from django.db.models import DecimalField
from django.db.models import F
from django.db.models import FloatField
from django.db.models import Max
from django.db.models import OuterRef
from django.db.models import Q
from django.db.models import Value
from django.db.models import When
from django.db.models.expressions import OuterRef
from django.db.models.functions import Coalesce
from django.utils.functional import cached_property
from django.utils.html import format_html
Expand All @@ -41,6 +45,7 @@
from dje.fields import LastModifiedByField
from dje.models import DataspacedManager
from dje.models import DataspacedModel
from dje.models import DataspacedQuerySet
from dje.models import History
from dje.models import HistoryFieldsMixin
from dje.models import ProductSecuredQuerySet
Expand Down Expand Up @@ -131,6 +136,64 @@ class Meta(BaseStatusMixin.Meta):
verbose_name_plural = _("product status")


class ProductQuerySet(DataspacedQuerySet):
def with_risk_threshold(self):
return self.annotate(
risk_threshold=Coalesce(
"vulnerabilities_risk_threshold",
"dataspace__configuration__vulnerabilities_risk_threshold",
output_field=DecimalField(),
),
)

def with_vulnerability_counts(self):
threshold_filter = Q(
productpackages__package__affected_by_vulnerabilities__risk_score__gte=F(
"risk_threshold"
)
)
no_threshold = Q(risk_threshold__isnull=True)

def count_vulns(extra_filter=None):
combined = no_threshold | threshold_filter
if extra_filter:
combined = combined & extra_filter
return Count(
"productpackages__package__affected_by_vulnerabilities",
filter=combined,
distinct=True,
)

def risk_level_filter(level):
return Q(productpackages__package__affected_by_vulnerabilities__risk_level=level)

return self.annotate(
vulnerability_count=count_vulns(),
max_risk_score=Max(
"productpackages__package__affected_by_vulnerabilities__risk_score",
filter=no_threshold | threshold_filter,
),
critical_count=count_vulns(risk_level_filter("critical")),
high_count=count_vulns(risk_level_filter("high")),
medium_count=count_vulns(risk_level_filter("medium")),
low_count=count_vulns(risk_level_filter("low")),
)

def with_license_compliance_counts(self):
return self.annotate(
license_warning_count=Count(
"productpackages__licenses",
filter=Q(productpackages__licenses__usage_policy__compliance_alert="warning"),
distinct=True,
),
license_error_count=Count(
"productpackages__licenses",
filter=Q(productpackages__licenses__usage_policy__compliance_alert="error"),
distinct=True,
),
)


class ProductSecuredManager(DataspacedManager):
"""
WARNING: The security is always enabled on this manager.
Expand Down Expand Up @@ -293,7 +356,7 @@ class Product(
help_text=_("Vulnerabilities directly affecting this product."),
)

objects = ProductSecuredManager()
objects = ProductSecuredManager.from_queryset(ProductQuerySet)()

# WARNING: Bypass the security system implemented in ProductSecuredManager.
# This is to be used only in a few cases where the User scoping is not appropriated.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
{% extends "bootstrap_base.html" %}
{% load i18n humanize %}

{% block page_title %}{% trans "Compliance Control Center" %}{% endblock %}

{% block content %}
<div class="d-flex align-items-baseline gap-3 mb-3">
<h1 class="h3 mb-0">{% trans "Compliance Control Center" %}</h1>
<span class="text-body-secondary">{{ total_products }} {% trans "active product" %}{{ total_products|pluralize }}</span>
</div>

<div class="row g-3 mb-3">
<div class="col-6 col-md-3">
<div class="bg-body-secondary rounded-3 p-3">
<div class="small text-body-secondary mb-1">{% trans "Products with issues" %}</div>
<div class="fs-4 fw-medium lh-sm {% if products_with_issues %}text-danger{% else %}text-success{% endif %}">
{{ products_with_issues }}
</div>
<div class="text-body-tertiary fs-xs mt-1">
{% if products_with_issues %}
{% trans "of" %} {{ total_products }} {% trans "active products" %}
{% else %}
{% trans "All" %} {{ total_products }} {% trans "products are compliant" %}
{% endif %}
</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="bg-body-secondary rounded-3 p-3">
<div class="small text-body-secondary mb-1">{% trans "License issues" %}</div>
<div class="fs-4 fw-medium lh-sm {% if products_with_license_issues %}text-danger{% else %}text-success{% endif %}">
{{ products_with_license_issues }}
</div>
<div class="text-body-tertiary fs-xs mt-1">
{% if products_with_license_issues %}
{% trans "products with policy violations" %}
{% else %}
{% trans "All products within policy" %}
{% endif %}
</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="bg-body-secondary rounded-3 p-3">
<div class="small text-body-secondary mb-1">{% trans "Security issues" %}</div>
<div class="fs-4 fw-medium lh-sm {% if products_with_critical_or_high %}text-danger{% elif products_with_vulnerabilities %}text-warning-orange{% else %}text-success{% endif %}">
{{ products_with_critical_or_high }}
</div>
<div class="text-body-tertiary fs-xs mt-1">
{% if products_with_critical_or_high %}
{% trans "products with critical/high vulnerabilities" %}
{% else %}
{% trans "No critical or high vulnerabilities" %}
{% endif %}
</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="bg-body-secondary rounded-3 p-3">
<div class="small text-body-secondary mb-1">{% trans "Total vulnerabilities" %}</div>
<div class="fs-4 fw-medium lh-sm {% if total_critical %}text-danger{% elif total_vulnerabilities %}text-warning-orange{% else %}text-success{% endif %}">
{{ total_vulnerabilities|intcomma }}
</div>
<div class="text-body-tertiary fs-xs mt-1">
{% if total_critical %}
{{ total_critical }} {% trans "critical" %}{% if total_high %}, {{ total_high }} {% trans "high" %}{% endif %}{% if total_medium %}, {{ total_medium }} {% trans "medium" %}{% endif %}{% if total_low %}, {{ total_low }} {% trans "low" %}{% endif %}
{% elif total_vulnerabilities %}
{% trans "across all products" %}
{% else %}
{% trans "No known vulnerabilities" %}
{% endif %}
</div>
</div>
</div>
</div>

<div class="border rounded-3 p-3 pt-2 mb-3">
<table class="table table-sm mb-0">
<thead>
<tr>
<th class="fw-medium">{% trans "Product" %}</th>
<th class="fw-medium text-end">{% trans "Packages" %}</th>
<th class="fw-medium text-end">{% trans "License compliance" %}</th>
<th class="fw-medium text-end">{% trans "Security compliance" %}</th>
<th class="fw-medium text-end">{% trans "Vulnerabilities" %}</th>
</tr>
</thead>
<tbody>
{% for product in object_list %}
{% with product_url=product.get_absolute_url %}
<tr>
<th class="align-middle">
<a href="{{ product_url }}#compliance">
{{ product }}
</a>
</th>
<td class="text-end">
<a href="{{ product_url }}#inventory">
{{ product.package_count|intcomma }}
</a>
</td>
<td class="text-end">
{% if product.license_error_count %}
<span class="badge bg-danger-subtle text-danger-emphasis">
{{ product.license_error_count }} {% trans "error" %}{{ product.license_error_count|pluralize }}
</span>
{% endif %}
{% if product.license_warning_count %}
<span class="badge bg-warning-subtle text-warning-emphasis ms-1">
{{ product.license_warning_count }} {% trans "warning" %}{{ product.license_warning_count|pluralize }}
</span>
{% endif %}
{% if not product.license_error_count and not product.license_warning_count %}
<span class="badge bg-success-subtle text-success-emphasis">{% trans "OK" %}</span>
{% endif %}
</td>
<td class="text-end">
{% if product.max_risk_level == "critical" %}
<span class="badge bg-danger-subtle text-danger-emphasis">{% trans "Critical" %}</span>
{% elif product.max_risk_level == "high" %}
<span class="badge bg-warning-orange-subtle text-warning-orange">{% trans "High" %}</span>
{% elif product.max_risk_level == "medium" %}
<span class="badge bg-warning-subtle text-warning-emphasis">{% trans "Medium" %}</span>
{% elif product.max_risk_level == "low" %}
<span class="badge bg-info-subtle text-info-emphasis">{% trans "Low" %}</span>
{% else %}
<span class="badge bg-success-subtle text-success-emphasis">{% trans "OK" %}</span>
{% endif %}
</td>
<td class="text-end">
{% if product.risk_threshold and product.vulnerability_count %}
<span class="text-body-tertiary fs-xs" data-bs-toggle="tooltip" title="{% trans 'Risk threshold' %}">
&ge; {{ product.risk_threshold }}
</span>
{% endif %}
{% if product.critical_count %}
<span class="badge bg-danger-subtle text-danger-emphasis">{{ product.critical_count }} {% trans "critical" %}</span>
{% endif %}
{% if product.high_count %}
<span class="badge bg-warning-orange-subtle text-warning-orange ms-1">{{ product.high_count }} {% trans "high" %}</span>
{% endif %}
{% if product.medium_count %}
<span class="badge bg-warning-subtle text-warning-emphasis ms-1">{{ product.medium_count }} {% trans "medium" %}</span>
{% endif %}
{% if product.low_count %}
<span class="badge bg-info-subtle text-info-emphasis ms-1">{{ product.low_count }} {% trans "low" %}</span>
{% endif %}
{% if not product.vulnerability_count %}
<span class="text-body-tertiary small">{% trans "None" %}</span>
{% endif %}
</td>
</tr>
{% endwith %}
{% empty %}
<tr>
<td colspan="5" class="text-center text-body-tertiary py-4">
{% trans "No active products" %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>

{% if is_paginated %}
<div class="d-flex justify-content-center">
{% include 'pagination/object_list_pagination.html' %}
</div>
{% endif %}
{% endblock %}
Loading
Loading