Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
93f49c7
add admin dashboard link to the right dropdown menu
tdruez May 5, 2026
f141bbe
add tools and integrations as scope selector for the search
tdruez May 5, 2026
a66b739
simplify search form action by deriving it from the active scope button
tdruez May 5, 2026
f2acd31
display the current active search value in the top header nav
tdruez May 5, 2026
e725aa0
add compliance watchlist card to the home dashboard
tdruez May 5, 2026
e2c69d9
refactor the compliance related queryset for reusability
tdruez May 6, 2026
539a007
do not include tools in search scope for anonymous mode
tdruez May 6, 2026
2e99115
do not include PurlDB in global search for anonymous mode
tdruez May 6, 2026
06eadcf
fix rendering of the login form breakpoints
tdruez May 6, 2026
8d5edce
refine the documentation links of the dashboard
tdruez May 6, 2026
6a2b7f5
fix the columns rendering in dashboard
tdruez May 6, 2026
55ffa10
refine the cards header
tdruez May 6, 2026
c7c0f92
display the links about the cards
tdruez May 6, 2026
376d82c
align columns height
tdruez May 6, 2026
c574382
redesign the dashboard header
tdruez May 6, 2026
f6a1d1f
fix height alignment
tdruez May 6, 2026
52f2dab
set the row gutter
tdruez May 6, 2026
cd44a3e
add missing url
tdruez May 6, 2026
fbe12a3
fix rendering for custom reporting cards
tdruez May 6, 2026
b85169c
add support for empty compliance queryset
tdruez May 6, 2026
9007767
fix unit tests
tdruez May 6, 2026
37cf7ef
move custom reporting cards after compliance
tdruez May 6, 2026
f77737f
fix margins
tdruez May 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions dejacode/static/css/dejacode_bootstrap.css
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,9 @@ table.purldb-table .column-license_expression {
color: white;
vertical-align: middle;
}
.text-request {
color: var(--bs-djc-request-bg);
}

/* -- Requests form -- */
#workflow-request-form fieldset.right-side label {
Expand Down
17 changes: 14 additions & 3 deletions dejacode/static/js/dejacode_main.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,24 @@ function setupSearchModal() {

if (!searchModal) return;

// Apply a scope button as the active one and sync the form action
const applyScope = (button) => {
document.querySelectorAll('.search-scope-btn').forEach(b => b.classList.remove('active'));
button.classList.add('active');
searchForm.setAttribute('action', button.dataset.scopeAction);
};

// Sync form action with the currently active scope button on page load
const activeButton = document.querySelector('.search-scope-btn.active');
if (searchForm && activeButton) {
searchForm.setAttribute('action', activeButton.dataset.scopeAction);
}

// Scope selector buttons
if (searchForm) {
document.querySelectorAll('.search-scope-btn').forEach(button => {
button.addEventListener('click', () => {
document.querySelectorAll('.search-scope-btn').forEach(b => b.classList.remove('active'));
button.classList.add('active');
searchForm.setAttribute('action', button.dataset.scopeAction);
applyScope(button);
searchInput.focus();
});
});
Expand Down
88 changes: 53 additions & 35 deletions dje/templates/dataspace_home.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
{% block page_title %}DejaCode for {{ user.dataspace }}{% endblock %}

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

{# Announcements #}
<div class="alert alert-success" role="alert">
<strong>
{% if user.dataspace.home_page_announcements %}
Expand All @@ -20,12 +18,58 @@ <h1 class="display-6 p-3 text-center">
</strong>
</div>

{# Dataspace name and links #}
<div class="card mt-1 mb-3">
<div class="card-body py-2 px-3">
<div class="d-flex flex-wrap align-items-center justify-content-between">
<div class="d-flex align-items-center gap-3">
<span class="h5 mb-0 fw-bold">
{{ user.dataspace }}
</span>
<div class="vr align-self-stretch my-1"></div>
<span class="text-body-secondary small">Dashboard</span>
</div>
<div class="d-flex flex-wrap align-items-baseline gap-1">
{% for header, urls in sections.items %}
{% for value, url in urls.items %}
{% if not forloop.parentloop.first or not forloop.first %}
<span class="text-muted small mx-1">|</span>
{% endif %}
<a href="{{ url }}" target="_blank" rel="noreferrer" class="small text-decoration-none">{{ value }}</a>
{% endfor %}
{% endfor %}
</div>
</div>
</div>
</div>

<div class="row row-cols-1 row-cols-lg-3 g-3 sm-gutter mb-3">
{# Compliance #}
<div class="col d-flex">
<div hx-get="{% url 'product_portfolio:compliance_watchlist_card' %}" hx-trigger="load" hx-swap="outerHTML">
<div class="h6 ms-4">
<i class="fas fa-spinner fa-spin"></i>
</div>
</div>
</div>
{# Requests #}
{% if request_assigned_to_me or request_followed_by_me %}
<div class="col d-flex">
{% 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' %}
</div>
<div class="col d-flex">
{% include 'workflow/includes/request_home_dashboard.html' with request_qs=request_followed_by_me header_title='Following' header_icon='fa-eye' filter_name='following' %}
</div>
{% endif %}
</div>

{# Custom reporting cards #}
{% if cards %}
<div class="row sm-gutter mb-3">
<div class="row row-cols-1 row-cols-lg-3 g-3">
{% for card in cards %}
<div class="col">
<div class="card shadow-sm">
<div class="h6 card-header fw-bold px-2">{{ card.title }}</div>
<div class="col d-flex">
<div class="card flex-fill shadow-sm">
<h2 class="card-header fw-bold px-2 h6">{{ card.title }}</h2>
<div class="card-body p-2">
{% for obj in card.object_list %}
{% if forloop.first %}
Expand All @@ -42,8 +86,8 @@ <h1 class="display-6 p-3 text-center">
{% if card.display_changelist_link and request.user.is_staff %}
{% with changelist_url=card.query.get_changelist_url_with_filters %}
{% if changelist_url %}
<div class="card-footer text-center p-2">
<a href="{{ changelist_url }}" class="card-link smaller">View all the objects in changelist</a>
<div class="card-footer text-center p-2 smaller">
<a href="{{ changelist_url }}">View all the objects in changelist</a>
</div>
{% endif %}
{% endwith %}
Expand All @@ -53,30 +97,4 @@ <h1 class="display-6 p-3 text-center">
{% endfor %}
</div>
{% endif %}

<div class="row row-cols-3 sm-gutter">
<div class="col">
<div class="card">
<div class="card-body pb-1">
{% for header, urls in sections.items %}
<h2 class="h5">{{ header }}:</h2>
<ul class="ps-4">
{% for value, url in urls.items %}
<li><a target="_blank" href="{{ url }}" rel="noreferrer">{{ value }}</a></li>
{% endfor %}
</ul>
{% endfor %}
</div>
</div>
</div>

{% if request_assigned_to_me or request_followed_by_me %}
<div class="col">
{% include 'workflow/includes/request_home_dashboard.html' with request_qs=request_assigned_to_me header_title='Requests assigned to me' filter_name='assignee' %}
</div>
<div class="col">
{% include 'workflow/includes/request_home_dashboard.html' with request_qs=request_followed_by_me header_title='Requests I am following' filter_name='following' %}
</div>
{% endif %}
</div>
{% endblock %}
44 changes: 37 additions & 7 deletions dje/templates/modals/search_modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div id="search-modal" class="modal fade" tabindex="-1" aria-labelledby="search-modal-label" aria-hidden="true">
<div class="modal-dialog modal-lg mt-5">
<div class="modal-content">
<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 %}">
<form id="search-form" role="search" class="d-flex flex-column" action="{{ global_search_url }}">

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

{# Scope selector #}
<div class="p-3 border-bottom">
<div class="text-body-secondary small text-uppercase fw-semibold mb-2">{% trans "Search in" %}</div>
<div class="d-flex flex-wrap gap-2" id="search-scope-buttons">
<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' %}">
<div class="text-body-secondary small text-uppercase fw-semibold mb-2">
{% trans "Search in" %}
</div>
{# Catalogs #}
<div class="d-flex flex-wrap gap-2">
<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 }}">
<i class="fa fa-globe me-1"></i>{% trans "Global" %}
</button>
{% if not user.is_anonymous %}
<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' %}">
<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 }}">
<i class="fa fa-briefcase me-1"></i>{% trans "Product" %}
</button>
{% endif %}
<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' %}">
<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 %}">
<i class="fas fa-archive me-1"></i>{% trans "Package" %}
</button>
<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' %}">
<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 %}">
<i class="fa fa-book me-1"></i>{% trans "License" %}
</button>
</div>

{# Tools and integrations #}
{% if not user.is_anonymous %}
<div class="d-flex flex-wrap gap-2 mt-3">
<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 }}">
<span class="badge text-bg-request me-1">R</span>{% trans "Request" %}
</button>
<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 }}">
<i class="fa fa-chart-bar me-1"></i>{% trans "Report" %}
</button>
{% if user.dataspace.enable_vulnerablecodedb_access %}
<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 %}">
<i class="fas fa-bug me-1"></i>{% trans "Vulnerability" %}
</button>
{% endif %}
{% if user.dataspace.enable_package_scanning %}
<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 %}">
<i class="fas fa-barcode me-1"></i>{% trans "Scan" %}
</button>
{% endif %}
{% if user.dataspace.enable_purldb_access %}
<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 %}">
<i class="fas fa-database me-1"></i>{% trans "PurlDB" %}
</button>
{% endif %}
</div>
{% endif %}
</div>

{# Carry over current list filters into the search #}
Expand Down
5 changes: 5 additions & 0 deletions dje/templates/navbar/navbar_header.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
{% url 'django_registration_register' as register_url %}
{% url 'global_search' as global_search_url %}
{% url 'workflow:request_list' as request_list_url %}
{% url 'reporting:report_list' as report_list_url %}
{% url 'component_catalog:scan_list' as scan_list_url %}
{% url 'purldb:purldb_list' as purldb_list_url %}
{% url 'vulnerabilities:vulnerability_list' as vulnerability_list_url %}
{% url 'account_profile' as account_profile_url %}

{% block navbar %}
<nav class="navbar navbar-expand-md fixed-top">
Expand Down
6 changes: 6 additions & 0 deletions dje/templates/navbar/navbar_header_right_menu.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@
Change Password
</a>
{% if user.is_staff %}
<div class="dropdown-divider"></div>
<div class="dropdown-header">Administration</div>
<a class="dropdown-item" href="{% url 'admin:index' %}">
<i class="fas fa-tachometer-alt" aria-hidden="true"></i>
Admin Dashboard
</a>
<div class="dropdown-divider"></div>
<div class="dropdown-header">Status</div>
<a class="dropdown-item" href="{% url 'integrations_status' %}">
Expand Down
8 changes: 6 additions & 2 deletions dje/templates/navbar/navbar_header_search_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
<li class="nav-item d-flex align-items-center me-3">
<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' %}">
<i class="fas fa-magnifying-glass me-2 opacity-75"></i>
<span class="opacity-75 text-start flex-grow-1" style="min-width: 120px;">
{% trans "Search" %}
<span class="opacity-75 text-start flex-grow-1 text-truncate" style="width: 120px;">
{% if request.GET.q %}
{{ request.GET.q }}
{% else %}
{% trans "Search" %}
{% endif %}
</span>
<kbd class="ms-3 bg-white bg-opacity-10 text-white border border-white border-opacity-25 px-2 rounded fw-normal">/</kbd>
</button>
Expand Down
12 changes: 6 additions & 6 deletions dje/templates/navbar/side_menu.html
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,17 @@ <h5 class="offcanvas-title fw-semibold" id="side-menu-label">
{% trans "Integrations" %}
</span>
</li>
{% if user.dataspace.enable_package_scanning %}
{% if user.dataspace.enable_vulnerablecodedb_access %}
<li class="nav-item">
<a href="{{ scan_list_url }}" class="nav-link text-body d-flex align-items-center {% if scan_list_url in request.path %}active{% endif %}">
<i class="fas fa-barcode fa-fw me-2"></i>{% trans "Scans" %}
<a href="{{ vulnerability_list_url }}" class="nav-link text-body d-flex align-items-center {% if vulnerability_list_url in request.path %}active{% endif %}">
<i class="fas fa-bug fa-fw me-2"></i>{% trans "Vulnerabilities" %}
</a>
</li>
{% endif %}
{% if user.dataspace.enable_vulnerablecodedb_access %}
{% if user.dataspace.enable_package_scanning %}
<li class="nav-item">
<a href="{{ vulnerability_list_url }}" class="nav-link text-body d-flex align-items-center {% if vulnerability_list_url in request.path %}active{% endif %}">
<i class="fas fa-bug fa-fw me-2"></i>{% trans "Vulnerabilities" %}
<a href="{{ scan_list_url }}" class="nav-link text-body d-flex align-items-center {% if scan_list_url in request.path %}active{% endif %}">
<i class="fas fa-barcode fa-fw me-2"></i>{% trans "Scans" %}
</a>
</li>
{% endif %}
Expand Down
6 changes: 3 additions & 3 deletions dje/templates/registration/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
{% block content %}
<h1>Sign in to DejaCode</h1>

<div class="row justify-content-md-center">
<div class="col-sm-4">
<div class="card">
<div class="row justify-content-center">
<div class="mx-auto" style="width: 400px;">
<div class="card shadow-sm">
<div class="card-body">
{% if AXES_ENABLED and form.errors %}
<div class="alert alert-warning">
Expand Down
11 changes: 5 additions & 6 deletions dje/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def test_home_view(self):
self.assertFalse(self.super_user.request_as_assignee.exists())
self.assertFalse(self.super_user.request_as_requester.exists())
self.assertContains(response, "Welcome to DejaCode!")
self.assertContains(response, "Documentation:")
self.assertContains(response, "Documentation")

self.dataspace1.home_page_announcements = "Custom announcements"
self.dataspace1.save()
Expand All @@ -89,8 +89,8 @@ def test_home_view(self):
self.assertTrue(self.super_user.request_as_requester.exists())

response = self.client.get(home_url)
self.assertContains(response, "Requests assigned to me")
self.assertContains(response, "Requests I am following")
self.assertContains(response, "Assigned to me")
self.assertContains(response, "Following")
request_list_url = reverse("workflow:request_list")
expected = f'<a href="{request_list_url}?following=yes">View all 1 requests</a>'
self.assertContains(response, expected, html=True)
Expand All @@ -114,16 +114,15 @@ def test_home_view_card_layout(self):

response = self.client.get(home_url)
self.assertContains(
response, '<div class="h6 card-header fw-bold px-2">Card1</div>', html=True
response, ' <h2 class="card-header fw-bold px-2 h6">Card1</h2>', html=True
)
self.assertContains(
response,
'<li><a href="/owners/Dataspace/Organization/">Organization</a></li>',
html=True,
)
changelist_link = (
f'<a href="/admin/organization/owner/?reporting_query={query.id}"'
f' class="card-link smaller">'
f'<a href="/admin/organization/owner/?reporting_query={query.id}">'
f" View all the objects in changelist"
f"</a>"
)
Expand Down
4 changes: 2 additions & 2 deletions dje/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -701,10 +701,9 @@ def home_view(request):
rtd_url = "https://dejacode.readthedocs.io/en/latest"

documentation_urls = {
"Documentation": "https://dejacode.readthedocs.io/en/latest/",
"Tutorials": f"{rtd_url}/tutorial-1.html",
"How-to": f"{rtd_url}/howto-1.html",
"API documentation": reverse("api-docs:docs-index"),
"How-To videos": "https://www.youtube.com/playlist?list=PLCq_LXeUqhkQj0u7M26fSHt1ebFhNCpCv",
}

support_urls = {
Expand Down Expand Up @@ -1886,6 +1885,7 @@ def get_context_data(self, **kwargs):
)

include_purldb_conditions = [
user.is_authenticated,
user_dataspace.enable_purldb_access,
PurlDB(user_dataspace).is_available(),
]
Expand Down
Loading
Loading