diff --git a/component_catalog/templates/component_catalog/includes/hierarchy_instance_box.html b/component_catalog/templates/component_catalog/includes/hierarchy_instance_box.html
index 984c5198..c5fce9dd 100644
--- a/component_catalog/templates/component_catalog/includes/hierarchy_instance_box.html
+++ b/component_catalog/templates/component_catalog/includes/hierarchy_instance_box.html
@@ -32,7 +32,7 @@
{% include 'component_catalog/includes/vulnerability_icon_link.html' with url=instance.get_absolute_url count=relation.vulnerability_count %}
{% endif %}
{% if relation.package_id and relation.package.declared_dependencies.all %}
-
{{ relation.package.declared_dependencies.all|length }}
diff --git a/dejacode/static/css/dejacode_bootstrap.css b/dejacode/static/css/dejacode_bootstrap.css
index f67badbb..b45e3019 100644
--- a/dejacode/static/css/dejacode_bootstrap.css
+++ b/dejacode/static/css/dejacode_bootstrap.css
@@ -141,6 +141,9 @@ table.text-break thead {
background-color: var(--bs-djc-blue-bg);
height: 54px;
}
+.nav {
+ --bs-nav-link-padding-x: 0.75rem;
+}
.navbar-nav .active>.nav-link,
.navbar-nav .show>.nav-link
.navbar-nav .nav-link.active,
@@ -470,6 +473,11 @@ table.vulnerabilities-table .column-summary {
font-size: 0.75rem;
}
+/* -- Licenses tab -- */
+#tab_licenses .column-usage_policy {
+ min-width: 175px;
+}
+
/* -- Package Details -- */
textarea.licenseexpressionwidget {
height: 62px;
@@ -700,7 +708,17 @@ td.sub-header {
.tab-content .table thead tr th {
font-size: 0.875rem;
}
-th a.sort i {width: auto;}
+th a.sort i {
+ width: auto;
+}
+tr.row-alert-danger > td:first-child {
+ box-shadow: inset 4px 0 0 var(--bs-danger);
+ padding-left: 1rem;
+}
+tr.row-alert-warning > td:first-child {
+ box-shadow: inset 4px 0 0 var(--bs-warning);
+ padding-left: 1rem;
+}
/* -- Better looks for the popover fake links -- */
.tag_popover,
diff --git a/dejacode/static/js/dejacode_main.js b/dejacode/static/js/dejacode_main.js
index 00cc51b2..71503214 100644
--- a/dejacode/static/js/dejacode_main.js
+++ b/dejacode/static/js/dejacode_main.js
@@ -239,6 +239,43 @@ function setupDismissibleAlerts() {
});
}
+function setupScrollToTargets() {
+ // Scroll to an in-page section when an element with data-scroll-to is clicked.
+ // The offset accounts for the sticky header via the body's padding-top.
+ document.addEventListener('click', (event) => {
+ const trigger = event.target.closest('[data-scroll-to]');
+ if (!trigger) return;
+ const target = document.getElementById(trigger.dataset.scrollTo);
+ if (!target) return;
+ const offset = parseFloat(getComputedStyle(document.body).paddingTop) || 0;
+ const top = target.getBoundingClientRect().top + window.scrollY - offset;
+ window.scrollTo({ top, behavior: 'smooth' });
+ });
+}
+
+function setupPaginationKeys() {
+ // Arrow key navigation for the page's own pagination. Pagination living inside
+ // a .tab-content is excluded: arrow keys there belong to the tab's own logic.
+ // Disabled links render as (no href), so querying skips them.
+ const isPageLevel = (link) => link && !link.closest('.tab-content');
+ const previousLink = [...document.querySelectorAll('a.page-link[aria-label="Previous"]')].find(isPageLevel);
+ const nextLink = [...document.querySelectorAll('a.page-link[aria-label="Next"]')].find(isPageLevel);
+ if (!previousLink && !nextLink) return;
+
+ const anyInputHasFocus = () => document.querySelector('input:focus, textarea:focus') !== null;
+
+ document.addEventListener('keydown', (event) => {
+ if (anyInputHasFocus()) return;
+ if (event.key === 'ArrowLeft' && previousLink) {
+ event.preventDefault();
+ window.location.href = previousLink.href;
+ } else if (event.key === 'ArrowRight' && nextLink) {
+ event.preventDefault();
+ window.location.href = nextLink.href;
+ }
+ });
+}
+
document.addEventListener('DOMContentLoaded', () => {
NEXB = {};
NEXB.client_data = JSON.parse(document.getElementById("client_data").textContent);
@@ -277,4 +314,6 @@ document.addEventListener('DOMContentLoaded', () => {
setupThemeSwitcher();
setupPlatformHints();
setupDismissibleAlerts();
+ setupScrollToTargets();
+ setupPaginationKeys();
});
diff --git a/dje/templates/dataspace_home.html b/dje/templates/dataspace_home.html
index 465195d6..94025027 100644
--- a/dje/templates/dataspace_home.html
+++ b/dje/templates/dataspace_home.html
@@ -78,7 +78,7 @@
{% if forloop.first %}
{% endif %}
- - {{ obj }}
+ - {{ obj }}
{% if forloop.last %}
{% endif %}
diff --git a/dje/templates/hierarchy_base.js.html b/dje/templates/hierarchy_base.js.html
index c74e979f..8f80be9b 100644
--- a/dje/templates/hierarchy_base.js.html
+++ b/dje/templates/hierarchy_base.js.html
@@ -43,10 +43,13 @@
// Draw if the hierarchy tab is active
if (isTabActive(tabId)) jsPlumbHierarchy.setSuspendDrawing(false, true);
- document.querySelector('button[data-bs-target="#tab_hierarchy"]').addEventListener('shown.bs.tab', function (e) {
- // Second argument instructs jsPlumb to perform a full repaint.
- jsPlumbHierarchy.setSuspendDrawing(false, true);
- });
+ const hierarchyTab = document.querySelector('button[data-bs-target="#tab_hierarchy"]');
+ if (hierarchyTab) {
+ hierarchyTab.addEventListener('shown.bs.tab', function (e) {
+ // Second argument instructs jsPlumb to perform a full repaint.
+ jsPlumbHierarchy.setSuspendDrawing(false, true);
+ });
+ }
// Repaint on resizing the browser window if the related tab is active
window.addEventListener('resize', function(){
diff --git a/dje/templates/object_details_base.html b/dje/templates/object_details_base.html
index bf1fd9d7..ffaacff2 100644
--- a/dje/templates/object_details_base.html
+++ b/dje/templates/object_details_base.html
@@ -75,7 +75,7 @@