Skip to content

Commit 8d0a720

Browse files
authored
feat: [five-c] Compliance dashboards improved (#532)
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent d12d2d0 commit 8d0a720

19 files changed

Lines changed: 349 additions & 128 deletions

File tree

dejacode/static/css/dejacode_bootstrap.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,9 @@ table.purldb-table .column-license_expression {
546546
color: white;
547547
vertical-align: middle;
548548
}
549+
.text-request {
550+
color: var(--bs-djc-request-bg);
551+
}
549552

550553
/* -- Requests form -- */
551554
#workflow-request-form fieldset.right-side label {

dejacode/static/js/dejacode_main.js

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,24 @@ function setupSearchModal() {
135135

136136
if (!searchModal) return;
137137

138+
// Apply a scope button as the active one and sync the form action
139+
const applyScope = (button) => {
140+
document.querySelectorAll('.search-scope-btn').forEach(b => b.classList.remove('active'));
141+
button.classList.add('active');
142+
searchForm.setAttribute('action', button.dataset.scopeAction);
143+
};
144+
145+
// Sync form action with the currently active scope button on page load
146+
const activeButton = document.querySelector('.search-scope-btn.active');
147+
if (searchForm && activeButton) {
148+
searchForm.setAttribute('action', activeButton.dataset.scopeAction);
149+
}
150+
138151
// Scope selector buttons
139152
if (searchForm) {
140153
document.querySelectorAll('.search-scope-btn').forEach(button => {
141154
button.addEventListener('click', () => {
142-
document.querySelectorAll('.search-scope-btn').forEach(b => b.classList.remove('active'));
143-
button.classList.add('active');
144-
searchForm.setAttribute('action', button.dataset.scopeAction);
155+
applyScope(button);
145156
searchInput.focus();
146157
});
147158
});
@@ -195,6 +206,39 @@ function setupThemeSwitcher() {
195206
});
196207
}
197208

209+
function setupPlatformHints() {
210+
const isMac = navigator.userAgentData
211+
? navigator.userAgentData.platform === 'macOS'
212+
: /Mac/.test(navigator.platform);
213+
214+
if (!isMac) return;
215+
216+
const searchSubmitKey = document.getElementById('search-submit-key');
217+
if (searchSubmitKey) {
218+
searchSubmitKey.textContent = 'Return';
219+
}
220+
}
221+
222+
function setupDismissibleAlerts() {
223+
// Hide announcements dismissed by the user via localStorage.
224+
// The stored value is the announcement text itself, so when the
225+
// admin updates the message, it automatically reappears for everyone.
226+
227+
const alert = document.getElementById('announcement-alert');
228+
if (!alert) return;
229+
230+
const text = alert.textContent.trim();
231+
if (localStorage.getItem('dismissed_announcement') === text) {
232+
alert.remove();
233+
return;
234+
}
235+
236+
alert.classList.remove('d-none');
237+
alert.addEventListener('closed.bs.alert', () => {
238+
localStorage.setItem('dismissed_announcement', text);
239+
});
240+
}
241+
198242
document.addEventListener('DOMContentLoaded', () => {
199243
NEXB = {};
200244
NEXB.client_data = JSON.parse(document.getElementById("client_data").textContent);
@@ -231,4 +275,6 @@ document.addEventListener('DOMContentLoaded', () => {
231275
setupHTMX();
232276
setupSearchModal();
233277
setupThemeSwitcher();
278+
setupPlatformHints();
279+
setupDismissibleAlerts();
234280
});

dje/templates/dataspace_home.html

Lines changed: 66 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,73 @@
66
{% block page_title %}DejaCode for {{ user.dataspace }}{% endblock %}
77

88
{% block content %}
9-
<h1 class="display-6 p-3 text-center">
10-
DejaCode for <strong>{{ user.dataspace }}</strong>
11-
</h1>
129

13-
<div class="alert alert-success" role="alert">
14-
<strong>
15-
{% if user.dataspace.home_page_announcements %}
16-
{{ user.dataspace.home_page_announcements|urlize_target_blank|linebreaksbr }}
17-
{% else %}
18-
Welcome to DejaCode!
10+
{% block announcements %}
11+
<div id="announcement-alert" class="alert alert-success alert-dismissible d-none" role="alert">
12+
<strong>
13+
{% if user.dataspace.home_page_announcements %}
14+
{{ user.dataspace.home_page_announcements|urlize_target_blank|linebreaksbr }}
15+
{% else %}
16+
Welcome to DejaCode!
17+
{% endif %}
18+
</strong>
19+
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
20+
</div>
21+
{% endblock announcements %}
22+
23+
{% block dataspace_header %}
24+
<div class="card mb-3">
25+
<div class="card-body py-2 px-3">
26+
<div class="d-flex flex-wrap align-items-center justify-content-between">
27+
<div class="d-flex align-items-center gap-3">
28+
<span class="h5 mb-0 fw-bold">
29+
{{ user.dataspace }}
30+
</span>
31+
<div class="vr align-self-stretch my-1"></div>
32+
<span class="text-body-secondary small">Dashboard</span>
33+
</div>
34+
<div class="d-flex flex-wrap align-items-baseline gap-1">
35+
{% for header, urls in sections.items %}
36+
{% for value, url in urls.items %}
37+
{% if not forloop.parentloop.first or not forloop.first %}
38+
<span class="text-muted small mx-1">|</span>
39+
{% endif %}
40+
<a href="{{ url }}" target="_blank" rel="noreferrer" class="small text-decoration-none">{{ value }}</a>
41+
{% endfor %}
42+
{% endfor %}
43+
</div>
44+
</div>
45+
</div>
46+
</div>
47+
{% endblock dataspace_header %}
48+
49+
<div class="row row-cols-1 row-cols-lg-3 g-3 sm-gutter mb-3">
50+
{% block compliance_card %}
51+
<div class="col d-flex">
52+
<div hx-get="{% url 'product_portfolio:compliance_watchlist_card' %}" hx-trigger="load" hx-swap="outerHTML">
53+
<div class="h6">
54+
<i class="fas fa-spinner fa-spin"></i>
55+
</div>
56+
</div>
57+
</div>
58+
{% endblock compliance_card %}
59+
60+
{% block request_cards %}
61+
{% if request_assigned_to_me or request_followed_by_me %}
62+
<div class="col d-flex">
63+
{% include 'workflow/includes/request_home_dashboard.html' with request_qs=request_assigned_to_me header_title='Assigned to me' header_icon='fa-user-check' filter_name='assignee' %}
64+
</div>
65+
<div class="col d-flex">
66+
{% include 'workflow/includes/request_home_dashboard.html' with request_qs=request_followed_by_me header_title='Following' header_icon='fa-eye' filter_name='following' %}
67+
</div>
1968
{% endif %}
20-
</strong>
21-
</div>
69+
{% endblock request_cards %}
2270

23-
{% if cards %}
24-
<div class="row sm-gutter mb-3">
71+
{% block custom_reporting_cards %}
2572
{% for card in cards %}
26-
<div class="col">
27-
<div class="card shadow-sm">
28-
<div class="h6 card-header fw-bold px-2">{{ card.title }}</div>
73+
<div class="col d-flex">
74+
<div class="card flex-fill shadow-sm">
75+
<h2 class="card-header fw-bold px-2 h6">{{ card.title }}</h2>
2976
<div class="card-body p-2">
3077
{% for obj in card.object_list %}
3178
{% if forloop.first %}
@@ -42,41 +89,16 @@ <h1 class="display-6 p-3 text-center">
4289
{% if card.display_changelist_link and request.user.is_staff %}
4390
{% with changelist_url=card.query.get_changelist_url_with_filters %}
4491
{% if changelist_url %}
45-
<div class="card-footer text-center p-2">
46-
<a href="{{ changelist_url }}" class="card-link smaller">View all the objects in changelist</a>
92+
<div class="card-footer text-center p-2 smaller">
93+
<a href="{{ changelist_url }}">View all the objects in changelist</a>
4794
</div>
4895
{% endif %}
4996
{% endwith %}
5097
{% endif %}
5198
</div>
5299
</div>
53100
{% endfor %}
54-
</div>
55-
{% endif %}
101+
{% endblock custom_reporting_cards %}
56102

57-
<div class="row row-cols-3 sm-gutter">
58-
<div class="col">
59-
<div class="card">
60-
<div class="card-body pb-1">
61-
{% for header, urls in sections.items %}
62-
<h2 class="h5">{{ header }}:</h2>
63-
<ul class="ps-4">
64-
{% for value, url in urls.items %}
65-
<li><a target="_blank" href="{{ url }}" rel="noreferrer">{{ value }}</a></li>
66-
{% endfor %}
67-
</ul>
68-
{% endfor %}
69-
</div>
70-
</div>
71-
</div>
72-
73-
{% if request_assigned_to_me or request_followed_by_me %}
74-
<div class="col">
75-
{% include 'workflow/includes/request_home_dashboard.html' with request_qs=request_assigned_to_me header_title='Requests assigned to me' filter_name='assignee' %}
76-
</div>
77-
<div class="col">
78-
{% include 'workflow/includes/request_home_dashboard.html' with request_qs=request_followed_by_me header_title='Requests I am following' filter_name='following' %}
79-
</div>
80-
{% endif %}
81103
</div>
82104
{% endblock %}

dje/templates/modals/search_modal.html

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<div id="search-modal" class="modal fade" tabindex="-1" aria-labelledby="search-modal-label" aria-hidden="true">
33
<div class="modal-dialog modal-lg mt-5">
44
<div class="modal-content">
5-
<form id="search-form" role="search" class="d-flex flex-column" action="{% if product_list_url in request.path %}{{ product_list_url }}{% elif component_list_url in request.path %}{{ component_list_url }}{% if is_reference_data %}{{ dataspace.name }}{% endif %}{% elif package_list_url in request.path %}{{ package_list_url }}{% if is_reference_data %}{{ dataspace.name }}{% endif %}{% elif license_list_url in request.path %}{{ license_list_url }}{% if is_reference_data %}{{ dataspace.name }}{% endif %}{% elif owner_list_url in request.path %}{{ owner_list_url }}{% if is_reference_data %}{{ dataspace.name }}{% endif %}{% else %}{{ global_search_url }}{% endif %}">
5+
<form id="search-form" role="search" class="d-flex flex-column" action="{{ global_search_url }}">
66

77
{# Search input #}
88
<div class="input-group input-group-lg border-bottom">
@@ -15,23 +15,53 @@
1515

1616
{# Scope selector #}
1717
<div class="p-3 border-bottom">
18-
<div class="text-body-secondary small text-uppercase fw-semibold mb-2">{% trans "Search in" %}</div>
19-
<div class="d-flex flex-wrap gap-2" id="search-scope-buttons">
20-
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill search-scope-btn {% if not product_list_url in request.path and not component_list_url in request.path and not package_list_url in request.path and not license_list_url in request.path and not owner_list_url in request.path %}active{% endif %}" data-scope-action="{{ global_search_url }}" data-scope-label="<i class='fa fa-globe'></i> {% trans 'Global' %}">
18+
<div class="text-body-secondary small text-uppercase fw-semibold mb-2">
19+
{% trans "Search in" %}
20+
</div>
21+
{# Catalogs #}
22+
<div class="d-flex flex-wrap gap-2">
23+
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill search-scope-btn {% if not product_list_url in request.path and not component_list_url in request.path and not package_list_url in request.path and not license_list_url in request.path and not owner_list_url in request.path and not request_list_url in request.path and not report_list_url in request.path and not vulnerability_list_url in request.path and not purldb_list_url in request.path %}active{% endif %}" data-scope-action="{{ global_search_url }}">
2124
<i class="fa fa-globe me-1"></i>{% trans "Global" %}
2225
</button>
2326
{% if not user.is_anonymous %}
24-
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill search-scope-btn {% if product_list_url in request.path %}active{% endif %}" data-scope-action="{{ product_list_url }}" data-scope-label="<i class='fa fa-briefcase'></i> {% trans 'Product' %}">
27+
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill search-scope-btn {% if product_list_url in request.path %}active{% endif %}" data-scope-action="{{ product_list_url }}">
2528
<i class="fa fa-briefcase me-1"></i>{% trans "Product" %}
2629
</button>
2730
{% endif %}
28-
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill search-scope-btn {% if package_list_url in request.path %}active{% endif %}" data-scope-action="{{ package_list_url }}{% if is_reference_data %}{{ dataspace.name }}{% endif %}" data-scope-label="<i class='fas fa-archive'></i> {% trans 'Package' %}">
31+
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill search-scope-btn {% if package_list_url in request.path %}active{% endif %}" data-scope-action="{{ package_list_url }}{% if is_reference_data %}{{ dataspace.name }}{% endif %}">
2932
<i class="fas fa-archive me-1"></i>{% trans "Package" %}
3033
</button>
31-
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill search-scope-btn {% if license_list_url in request.path %}active{% endif %}" data-scope-action="{{ license_list_url }}{% if is_reference_data %}{{ dataspace.name }}{% endif %}" data-scope-label="<i class='fa fa-book'></i> {% trans 'License' %}">
34+
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill search-scope-btn {% if license_list_url in request.path %}active{% endif %}" data-scope-action="{{ license_list_url }}{% if is_reference_data %}{{ dataspace.name }}{% endif %}">
3235
<i class="fa fa-book me-1"></i>{% trans "License" %}
3336
</button>
3437
</div>
38+
39+
{# Tools and integrations #}
40+
{% if not user.is_anonymous %}
41+
<div class="d-flex flex-wrap gap-2 mt-3">
42+
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill search-scope-btn {% if request_list_url in request.path %}active{% endif %}" data-scope-action="{{ request_list_url }}">
43+
<span class="badge text-bg-request me-1">R</span>{% trans "Request" %}
44+
</button>
45+
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill search-scope-btn {% if report_list_url in request.path %}active{% endif %}" data-scope-action="{{ report_list_url }}">
46+
<i class="fa fa-chart-bar me-1"></i>{% trans "Report" %}
47+
</button>
48+
{% if user.dataspace.enable_vulnerablecodedb_access %}
49+
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill search-scope-btn {% if vulnerability_list_url in request.path %}active{% endif %}" data-scope-action="{{ vulnerability_list_url }}{% if is_reference_data %}{{ dataspace.name }}{% endif %}">
50+
<i class="fas fa-bug me-1"></i>{% trans "Vulnerability" %}
51+
</button>
52+
{% endif %}
53+
{% if user.dataspace.enable_package_scanning %}
54+
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill search-scope-btn {% if scan_list_url in request.path %}active{% endif %}" data-scope-action="{{ scan_list_url }}{% if is_reference_data %}{{ dataspace.name }}{% endif %}">
55+
<i class="fas fa-barcode me-1"></i>{% trans "Scan" %}
56+
</button>
57+
{% endif %}
58+
{% if user.dataspace.enable_purldb_access %}
59+
<button type="button" class="btn btn-sm btn-outline-secondary rounded-pill search-scope-btn {% if purldb_list_url in request.path %}active{% endif %}" data-scope-action="{{ purldb_list_url }}{% if is_reference_data %}{{ dataspace.name }}{% endif %}">
60+
<i class="fas fa-database me-1"></i>{% trans "PurlDB" %}
61+
</button>
62+
{% endif %}
63+
</div>
64+
{% endif %}
3565
</div>
3666

3767
{# Carry over current list filters into the search #}
@@ -91,7 +121,7 @@
91121
</span>
92122
<span class="px-2">/</span>
93123
<span class="small text-body-secondary">
94-
<kbd class="small">Enter</kbd> {% trans "to search" %}
124+
<kbd class="small" id="search-submit-key">Enter</kbd> {% trans "to search" %}
95125
</span>
96126
</div>
97127
</div>

dje/templates/navbar/navbar_header.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
{% url 'django_registration_register' as register_url %}
77
{% url 'global_search' as global_search_url %}
88
{% url 'workflow:request_list' as request_list_url %}
9+
{% url 'reporting:report_list' as report_list_url %}
10+
{% url 'component_catalog:scan_list' as scan_list_url %}
11+
{% url 'purldb:purldb_list' as purldb_list_url %}
12+
{% url 'vulnerabilities:vulnerability_list' as vulnerability_list_url %}
13+
{% url 'account_profile' as account_profile_url %}
914

1015
{% block navbar %}
1116
<nav class="navbar navbar-expand-md fixed-top">

dje/templates/navbar/navbar_header_right_menu.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@
5959
Change Password
6060
</a>
6161
{% if user.is_staff %}
62+
<div class="dropdown-divider"></div>
63+
<div class="dropdown-header">Administration</div>
64+
<a class="dropdown-item" href="{% url 'admin:index' %}">
65+
<i class="fas fa-tachometer-alt" aria-hidden="true"></i>
66+
Admin Dashboard
67+
</a>
6268
<div class="dropdown-divider"></div>
6369
<div class="dropdown-header">Status</div>
6470
<a class="dropdown-item" href="{% url 'integrations_status' %}">

dje/templates/navbar/navbar_header_search_form.html

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22
<li class="nav-item d-flex align-items-center me-3">
33
<button type="button" class="btn btn-sm bg-body bg-opacity-10 border border-white border-opacity-25 text-white d-flex align-items-center rounded-pill px-2 nav-chip" data-bs-toggle="modal" data-bs-target="#search-modal" aria-label="{% trans 'Open search' %}">
44
<i class="fas fa-magnifying-glass me-2 opacity-75"></i>
5-
<span class="opacity-75 text-start flex-grow-1" style="min-width: 120px;">
6-
{% trans "Search" %}
5+
<span class="opacity-75 text-start flex-grow-1 text-truncate" style="width: 120px;">
6+
{% if request.GET.q %}
7+
{{ request.GET.q }}
8+
{% else %}
9+
{% trans "Search" %}
10+
{% endif %}
711
</span>
812
<kbd class="ms-3 bg-white bg-opacity-10 text-white border border-white border-opacity-25 px-2 rounded fw-normal">/</kbd>
913
</button>

dje/templates/navbar/side_menu.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,17 +98,17 @@ <h5 class="offcanvas-title fw-semibold" id="side-menu-label">
9898
{% trans "Integrations" %}
9999
</span>
100100
</li>
101-
{% if user.dataspace.enable_package_scanning %}
101+
{% if user.dataspace.enable_vulnerablecodedb_access %}
102102
<li class="nav-item">
103-
<a href="{{ scan_list_url }}" class="nav-link text-body d-flex align-items-center {% if scan_list_url in request.path %}active{% endif %}">
104-
<i class="fas fa-barcode fa-fw me-2"></i>{% trans "Scans" %}
103+
<a href="{{ vulnerability_list_url }}" class="nav-link text-body d-flex align-items-center {% if vulnerability_list_url in request.path %}active{% endif %}">
104+
<i class="fas fa-bug fa-fw me-2"></i>{% trans "Vulnerabilities" %}
105105
</a>
106106
</li>
107107
{% endif %}
108-
{% if user.dataspace.enable_vulnerablecodedb_access %}
108+
{% if user.dataspace.enable_package_scanning %}
109109
<li class="nav-item">
110-
<a href="{{ vulnerability_list_url }}" class="nav-link text-body d-flex align-items-center {% if vulnerability_list_url in request.path %}active{% endif %}">
111-
<i class="fas fa-bug fa-fw me-2"></i>{% trans "Vulnerabilities" %}
110+
<a href="{{ scan_list_url }}" class="nav-link text-body d-flex align-items-center {% if scan_list_url in request.path %}active{% endif %}">
111+
<i class="fas fa-barcode fa-fw me-2"></i>{% trans "Scans" %}
112112
</a>
113113
</li>
114114
{% endif %}

dje/templates/registration/login.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
{% block content %}
1010
<h1>Sign in to DejaCode</h1>
1111

12-
<div class="row justify-content-md-center">
13-
<div class="col-sm-4">
14-
<div class="card">
12+
<div class="row justify-content-center">
13+
<div class="mx-auto" style="width: 400px;">
14+
<div class="card shadow-sm">
1515
<div class="card-body">
1616
{% if AXES_ENABLED and form.errors %}
1717
<div class="alert alert-warning">

0 commit comments

Comments
 (0)