Skip to content

Gateway branding and other front-end improvements#257

Draft
lucaspar wants to merge 5 commits intomasterfrom
lp/branding
Draft

Gateway branding and other front-end improvements#257
lucaspar wants to merge 5 commits intomasterfrom
lp/branding

Conversation

@lucaspar
Copy link
Member

@lucaspar lucaspar commented Feb 13, 2026

  • Fixes
    • Fixes debouncing when running a capture search.
  • Styling
    • Home page features a branded image if available, to distinguish SDS deployments.
    • Navbar and tab title use short branded name.
    • Captures and dataset listings are more visually coherent.
    • Vertical dividers in navbar.
    • Hiding authenticated urls from navbar if user is not authenticated.
    • When running a capture search, the loading spinner is now in the button and it's visible for slightly longer if the request finishes too fast.
  • Docs
    • Adds deployment instructions for operators, in Gateway changelog.

@lucaspar lucaspar self-assigned this Feb 13, 2026
Copilot AI review requested due to automatic review settings February 13, 2026 18:39
@lucaspar lucaspar added bug Something isn't working gateway Gateway component styling Special focus on styling of front-end components javascript Pull requests that update non-trivial javascript code labels Feb 13, 2026
@semanticdiff-com
Copy link

semanticdiff-com bot commented Feb 13, 2026

@lucaspar lucaspar added the documentation Improvements or additions to documentation label Feb 13, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds customizable branding to the SDS Gateway to distinguish different deployment instances, along with front-end improvements and a critical debouncing fix for capture search. The branding system allows operators to configure institution names, site identifiers, and optional branding images through environment variables, which are then displayed in the navbar, page titles, and homepage.

Changes:

  • Fixed search debouncing using AbortController to cancel stale requests and prevent race conditions
  • Added branding system with configurable site names, institution names, and optional branding images
  • Improved UI consistency across captures and datasets pages with better visual hierarchy
  • Added vertical dividers in navbar and restricted authenticated-only links to authenticated users

Reviewed changes

Copilot reviewed 19 out of 20 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
gateway/sds_gateway/context_processors.py Added branding context processor to expose branding settings to all templates
gateway/sds_gateway/test_context_processors.py Added tests for the new branding context processor
gateway/config/settings/base.py Defined branding settings and helper function to resolve brand image paths
gateway/config/settings/production.py Added SDS_SITE_FQDN to ALLOWED_HOSTS
gateway/config/settings/local.py Added SDS_SITE_FQDN to ALLOWED_HOSTS
gateway/.envs/example/django.env Added example branding environment variables for development
gateway/.envs/example/django.prod-example.env Added example branding environment variables for production
gateway/sds_gateway/templates/base.html Updated navbar with branding, added vertical dividers, restricted links to authenticated users
gateway/sds_gateway/templates/pages/home.html Added branding display with institution names and optional brand image banner
gateway/sds_gateway/templates/pages/about.html Added optional brand image background to description section
gateway/sds_gateway/templates/users/dataset_list.html Changed title from "My Datasets" to "Datasets" for consistency
gateway/sds_gateway/templates/users/partials/my_datasets_tab.html Updated comments for consistency
gateway/sds_gateway/templates/users/file_list.html Restructured layout, added date auto-fill logic, improved search UI
gateway/sds_gateway/static/js/components.js Implemented AbortController pattern for request cancellation in SearchManager
gateway/sds_gateway/static/js/file-list.js Updated to use AbortController signal, added minimum loading time, improved button state management
gateway/sds_gateway/static/css/variables.css Added homepage color variable for institution highlighting
gateway/sds_gateway/static/css/spectrumx_theme.css Added styles for brand images, navbar branding, and institution highlighting
gateway/docs/changelog.md Added comprehensive deployment instructions for operators
.pre-commit-config.yaml Increased max file size check from default to 1MB

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +36 to +37
<div class="brand-banner mb-4"
style="--brand-image: url('{{ SDS_BRAND_IMAGE_URL }}')"></div>
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SDS_BRAND_IMAGE_URL is inserted directly into inline CSS without proper sanitization. While the _get_brand_image_url function in settings/base.py validates the format, if an attacker can control the SDS_BRAND_IMAGE_PATH environment variable, they could inject malicious CSS. Django's template auto-escaping does not protect against CSS injection in style attributes. Consider using a more restrictive validation (e.g., allowlist of domains for external URLs) or using the Content-Security-Policy header to restrict image sources.

Suggested change
<div class="brand-banner mb-4"
style="--brand-image: url('{{ SDS_BRAND_IMAGE_URL }}')"></div>
<div class="brand-banner mb-4">
<img src="{{ SDS_BRAND_IMAGE_URL }}" alt="Brand banner" class="img-fluid" />
</div>

Copilot uses AI. Check for mistakes.
role="navigation"
aria-label="Main navigation">
aria-label="Main navigation"
{% if SDS_BRAND_IMAGE_URL %}style="--navbar-bg-image: url('{{ SDS_BRAND_IMAGE_URL }}')"{% endif %}>
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SDS_BRAND_IMAGE_URL is inserted directly into inline CSS without proper sanitization. While the _get_brand_image_url function in settings/base.py validates the format, if an attacker can control the SDS_BRAND_IMAGE_PATH environment variable, they could inject malicious CSS. Django's template auto-escaping does not protect against CSS injection in style attributes. Consider using a more restrictive validation (e.g., allowlist of domains for external URLs) or using the Content-Security-Policy header to restrict image sources.

Suggested change
{% if SDS_BRAND_IMAGE_URL %}style="--navbar-bg-image: url('{{ SDS_BRAND_IMAGE_URL }}')"{% endif %}>
{% if SDS_BRAND_IMAGE_URL %}style="--navbar-bg-image: url('{{ SDS_BRAND_IMAGE_URL|urlencode }}')"{% endif %}>

Copilot uses AI. Check for mistakes.
<div class="p-5 mb-4 bg-light rounded-3">
<div id="descriptionSection"
class="p-5 mb-4 bg-light rounded-3 description-hero"
{% if SDS_BRAND_IMAGE_URL %}style="--description-bg-image: url('{{ SDS_BRAND_IMAGE_URL }}')"{% endif %}>
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SDS_BRAND_IMAGE_URL is inserted directly into inline CSS without proper sanitization. While the _get_brand_image_url function in settings/base.py validates the format, if an attacker can control the SDS_BRAND_IMAGE_PATH environment variable, they could inject malicious CSS. Django's template auto-escaping does not protect against CSS injection in style attributes. Consider using a more restrictive validation (e.g., allowlist of domains for external URLs) or using the Content-Security-Policy header to restrict image sources.

Suggested change
{% if SDS_BRAND_IMAGE_URL %}style="--description-bg-image: url('{{ SDS_BRAND_IMAGE_URL }}')"{% endif %}>
{% if SDS_BRAND_IMAGE_URL %}style="--description-bg-image: url('{{ SDS_BRAND_IMAGE_URL|urlencode }}')"{% endif %}>

Copilot uses AI. Check for mistakes.
Comment on lines +473 to +480
// Only set end date if it's not already set or if it's not greater than start date
if (!currentEndDate) {
startDate.setDate(startDate.getDate() + 1);
const nextDay = startDate.toISOString().split('T')[0];
endDateInput.value = nextDay;
} else {
const endDate = new Date(currentEndDate);
if (endDate <= startDate) {
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The date comparison logic has a potential timezone issue. When creating Date objects from date strings (YYYY-MM-DD), JavaScript interprets them as UTC midnight. However, the comparison "endDate <= startDate" at line 480 should be "endDate < startDate" because if both dates are equal (same day), there's no need to auto-correct the end date to the next day. With the current logic, if a user selects the same start and end date intentionally, the system will incorrectly force the end date to be the next day.

Suggested change
// Only set end date if it's not already set or if it's not greater than start date
if (!currentEndDate) {
startDate.setDate(startDate.getDate() + 1);
const nextDay = startDate.toISOString().split('T')[0];
endDateInput.value = nextDay;
} else {
const endDate = new Date(currentEndDate);
if (endDate <= startDate) {
// Only set end date if it's not already set or if it's earlier than start date
if (!currentEndDate) {
startDate.setDate(startDate.getDate() + 1);
const nextDay = startDate.toISOString().split('T')[0];
endDateInput.value = nextDay;
} else {
const endDate = new Date(currentEndDate);
if (endDate < startDate) {

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +28
assert "SDS_BRANDED_SITE_NAME" in context
assert "SDS_FULL_INSTITUTION_NAME" in context
assert "SDS_SHORT_INSTITUTION_NAME" in context
assert "SDS_PROGRAMMATIC_SITE_NAME" in context
assert "SDS_SITE_FQDN" in context
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test for the branding context processor is missing a test for SDS_BRAND_IMAGE_URL. The branding function returns SDS_BRAND_IMAGE_URL at line 46 in context_processors.py, but the test only checks for the other five branding settings. This creates incomplete test coverage for the branding context processor.

Copilot uses AI. Check for mistakes.
Comment on lines +580 to +604
def _get_brand_image_url() -> str | None:
"""Resolve brand image path to a usable URL.

Supports:
- Complete URLs (http://, https://)
- Local static paths (converted to /static/...)
- Empty strings (returns None)
"""
image_path: str = env.str("SDS_BRAND_IMAGE_PATH", default="")

if not image_path:
return None

# If it's already a complete URL, use as-is
if image_path.startswith(("http://", "https://")):
return image_path

# If it's a relative path, prepend /static/
if not image_path.startswith("/"):
image_path = f"/{image_path}"

if not image_path.startswith("/static/"):
image_path = f"/static{image_path}"

return image_path
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _get_brand_image_url function in base.py lacks test coverage. This function has multiple code paths (empty string, http/https URLs, relative paths, paths with/without /static/) that should be tested to ensure the URL resolution logic works correctly in all cases. Without tests, future changes could break this functionality without being detected.

Copilot uses AI. Check for mistakes.
@lucaspar lucaspar marked this pull request as draft February 16, 2026 14:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation gateway Gateway component javascript Pull requests that update non-trivial javascript code styling Special focus on styling of front-end components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant