From 5a62c262c13a7e6e48bbbb0fdd64abb6d8327e94 Mon Sep 17 00:00:00 2001
From: ClaudioWayne <35531629+ClaudioWayne@users.noreply.github.com>
Date: Wed, 23 Sep 2026 10:19:37 +0000
Subject: [PATCH 1/5] Restore Top Detections on the dashboard (#3254)
---
web/dashboard/test_dashboard_scope.py | 79 ++++++++++++++++++++
web/dashboard/views.py | 11 +++
web/templates/dashboard/_top_detections.html | 18 +++++
web/templates/dashboard/index.html | 23 +-----
4 files changed, 112 insertions(+), 19 deletions(-)
mode change 100644 => 100755 web/dashboard/test_dashboard_scope.py
mode change 100644 => 100755 web/dashboard/views.py
create mode 100755 web/templates/dashboard/_top_detections.html
mode change 100644 => 100755 web/templates/dashboard/index.html
diff --git a/web/dashboard/test_dashboard_scope.py b/web/dashboard/test_dashboard_scope.py
old mode 100644
new mode 100755
index da43c868033..0017a2ec61d
--- a/web/dashboard/test_dashboard_scope.py
+++ b/web/dashboard/test_dashboard_scope.py
@@ -6,6 +6,17 @@
+@pytest.fixture(autouse=True)
+def dashboard_detections(monkeypatch):
+ """Keep dashboard view tests independent of the live report database."""
+ from unittest.mock import Mock
+ import dashboard.views as dv
+
+ detections = Mock(return_value=[])
+ monkeypatch.setattr(dv, "top_detections", detections)
+ return detections
+
+
@pytest.mark.django_db
def test_dashboard_entitled_scopes(cape_db, mt_enabled, monkeypatch):
from dashboard.views import entitled_scopes
@@ -269,3 +280,71 @@ def fake_render(request, template, context=None):
assert '
' in html
# 'mine' has no done tasks -> its estimate alert is suppressed.
assert "Mine —" not in html
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize("detections", [[], False, [{"family": "ExampleFamily", "total": 7}]])
+def test_index_global_top_detections(monkeypatch, mt_disabled, dashboard_detections, detections):
+ dashboard_detections.return_value = detections
+ user = User.objects.create_user("top-global")
+ context, html = _call_index(monkeypatch, user, _FakeDB())
+
+ dashboard_detections.assert_called_once_with()
+ assert context["panels"][0]["top_detections"] == detections
+ assert ("Top Detections" in html) == bool(detections)
+ if detections:
+ assert '/analysis/search/detections:ExampleFamily' in html
+ assert '>7' in html
+
+
+@pytest.mark.django_db
+def test_index_top_detections_disabled(monkeypatch, mt_disabled):
+ import dashboard.views as dv
+ import lib.cuckoo.common.web_utils as wu
+ from unittest.mock import Mock
+
+ aggregate = Mock(side_effect=AssertionError("Disabled detections must not query MongoDB"))
+ monkeypatch.setattr(wu.web_cfg.general, "top_detections", False)
+ monkeypatch.setattr(wu, "mongo_aggregate", aggregate, raising=False)
+ monkeypatch.setattr(dv, "top_detections", wu.top_detections)
+ user = User.objects.create_user("top-disabled")
+ context, html = _call_index(monkeypatch, user, _FakeDB())
+
+ assert context["panels"][0]["top_detections"] is False
+ assert "Top Detections" not in html
+ aggregate.assert_not_called()
+
+
+@pytest.mark.django_db
+def test_index_scoped_top_detections(monkeypatch, mt_enabled, cape_db, dashboard_detections):
+ from users.models import Tenant, UserProfile
+
+ tenant = Tenant.objects.create(slug="top-tenant", name="Top Tenant")
+ user = User.objects.create_user("top-scoped")
+ profile = UserProfile.objects.get(user=user)
+ profile.tenant = tenant
+ profile.save()
+ user = User.objects.get(pk=user.pk)
+ results = [
+ [{"family": "PublicFamily", "total": 3}],
+ [{"family": "TenantFamily", "total": 2}],
+ [{"family": "PersonalFamily", "total": 1}],
+ ]
+ dashboard_detections.side_effect = results
+ context, html = _call_index(monkeypatch, user, _FakeDB())
+
+ assert [p["top_detections"] for p in context["panels"]] == results
+ calls = dashboard_detections.call_args_list
+ assert [call.kwargs["scope_match"] for call in calls] == [
+ {"info.visibility": "public"},
+ {"info.tenant_id": tenant.id, "info.visibility": "tenant"},
+ {"info.user_id": user.id},
+ ]
+ assert all(call.kwargs["viewer"].user_id == user.id for call in calls)
+ assert all(call.kwargs["viewer"].tenant_id == tenant.id for call in calls)
+ assert html.count("Top Detections") == 3
+ for panel_html, detections in zip(html.split('
')[1:], results):
+ assert f'/analysis/search/detections:{detections[0]["family"]}' in panel_html
+ for other in results:
+ if other != detections:
+ assert other[0]["family"] not in panel_html
diff --git a/web/dashboard/views.py b/web/dashboard/views.py
old mode 100644
new mode 100755
index 9f54ccf5190..d971e307e29
--- a/web/dashboard/views.py
+++ b/web/dashboard/views.py
@@ -13,6 +13,8 @@
sys.path.append(settings.CUCKOO_PATH)
+from lib.cuckoo.common.tenancy_optional import scope_match
+from lib.cuckoo.common.web_utils import top_detections
from lib.cuckoo.core.database import Database
from lib.cuckoo.core.data.task import TASK_COMPLETED, TASK_REPORTED
try:
@@ -168,5 +170,14 @@ def index(request):
"estimate_day": estimate_day,
})
+ for panel in panels:
+ if panel["scope"] == "global":
+ panel["top_detections"] = top_detections()
+ else:
+ panel["top_detections"] = top_detections(
+ scope_match=scope_match(panel["scope"], v),
+ viewer=v,
+ )
+
data = {"title": "Dashboard", "panels": panels}
return render(request, "dashboard/index.html", data)
diff --git a/web/templates/dashboard/_top_detections.html b/web/templates/dashboard/_top_detections.html
new file mode 100755
index 00000000000..463c48a968c
--- /dev/null
+++ b/web/templates/dashboard/_top_detections.html
@@ -0,0 +1,18 @@
+{% if p.top_detections %}
+
+
+
+
+ {% for block in p.top_detections %}
+
+ {{block.total}}
+ {{block.family}}
+
+ {% endfor %}
+
+
+
+{% endif %}
diff --git a/web/templates/dashboard/index.html b/web/templates/dashboard/index.html
old mode 100644
new mode 100755
index 228f5a605b7..62ad083084d
--- a/web/templates/dashboard/index.html
+++ b/web/templates/dashboard/index.html
@@ -25,6 +25,8 @@
+{% include "dashboard/_top_detections.html" with p=p only %}
+