---
backend/static/backoffice/js/alpine-components.js | 4 +++-
backend/templates/backoffice/patterns.html | 6 +++---
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/backend/static/backoffice/js/alpine-components.js b/backend/static/backoffice/js/alpine-components.js
index 0d05f3ca..69364b0a 100644
--- a/backend/static/backoffice/js/alpine-components.js
+++ b/backend/static/backoffice/js/alpine-components.js
@@ -238,7 +238,9 @@ document.addEventListener("alpine:init", function () {
// Build URL - skip query param if value is empty
var url = baseUrl;
if (this.selected) {
- url += "?" + paramName + "=" + encodeURIComponent(this.selected);
+ // Use & if URL already has query params, otherwise use ?
+ var separator = url.indexOf("?") !== -1 ? "&" : "?";
+ url += separator + paramName + "=" + encodeURIComponent(this.selected);
}
// Add focus hash if element has focus (keyboard navigation)
diff --git a/backend/templates/backoffice/patterns.html b/backend/templates/backoffice/patterns.html
index 28a12034..1389a664 100644
--- a/backend/templates/backoffice/patterns.html
+++ b/backend/templates/backoffice/patterns.html
@@ -82,9 +82,9 @@
class="w-full px-3 py-2 rounded-lg text-body-md"
style="background-color: var(--color-page-background); border: 1px solid var(--color-borders-dividers); color: var(--color-body-text);">
-
-
-
+
+
+
{% if request.args.get('demo') %}
From 2f820b6571d1fb1dd61e4b5851412625c897f646 Mon Sep 17 00:00:00 2001
From: gazdagergo
Date: Fri, 20 Mar 2026 18:37:38 +0100
Subject: [PATCH 05/31] Add developer tools dashboard
- Create /backoffice/dev route with cards linking to dev tools
- Links to Service Docs, Frontend Patterns, and Component Showcase
- Admin-only, disabled in production
- Update breadcrumbs in service_docs and patterns to link to dev dashboard
Co-Authored-By: Claude Opus 4.5
---
.../entrypoints/blueprints/backoffice.py | 23 ++++
.../templates/backoffice/dev_dashboard.html | 108 ++++++++++++++++++
backend/templates/backoffice/patterns.html | 2 +-
.../templates/backoffice/service_docs.html | 2 +-
4 files changed, 133 insertions(+), 2 deletions(-)
create mode 100644 backend/templates/backoffice/dev_dashboard.html
diff --git a/backend/src/opendlp/entrypoints/blueprints/backoffice.py b/backend/src/opendlp/entrypoints/blueprints/backoffice.py
index 79197895..08eee98e 100644
--- a/backend/src/opendlp/entrypoints/blueprints/backoffice.py
+++ b/backend/src/opendlp/entrypoints/blueprints/backoffice.py
@@ -512,6 +512,29 @@ def search_demo() -> ResponseReturnValue:
return jsonify(results), 200
+# =============================================================================
+# Developer Tools Dashboard (Admin-only)
+# =============================================================================
+
+
+@backoffice_bp.route("/dev")
+@login_required
+def dev_dashboard() -> ResponseReturnValue:
+ """Developer tools dashboard.
+
+ Admin-only page that links to all developer tools.
+ Disabled in production for security.
+ """
+ if config.is_production():
+ abort(404)
+
+ if not has_global_admin(current_user):
+ flash(_("You don't have permission to access developer tools"), "error")
+ return redirect(url_for("backoffice.dashboard"))
+
+ return render_template("backoffice/dev_dashboard.html"), 200
+
+
# =============================================================================
# Service Layer Documentation (Admin-only developer tools)
# =============================================================================
diff --git a/backend/templates/backoffice/dev_dashboard.html b/backend/templates/backoffice/dev_dashboard.html
new file mode 100644
index 00000000..511e291b
--- /dev/null
+++ b/backend/templates/backoffice/dev_dashboard.html
@@ -0,0 +1,108 @@
+{#
+ABOUTME: Developer tools dashboard for admin users
+ABOUTME: Links to service docs, frontend patterns, and component showcase
+#}
+{% extends "backoffice/base_page.html" %}
+{% from "backoffice/components/card.html" import link_card %}
+{% from "backoffice/components/breadcrumbs.html" import breadcrumbs %}
+
+{% block title %}{{ _("Developer Tools") }}{% endblock %}
+
+{% block breadcrumb_section %}
+
+ {{ breadcrumbs([
+ {"label": _("Dashboard"), "href": url_for('backoffice.dashboard')},
+ {"label": _("Developer Tools")}
+ ]) }}
+
+{% endblock %}
+
+{% block page_content %}
+
+ ๐ ๏ธ{{ _("Developer Tools") }}
+
+
+ {{ _("Interactive documentation and testing tools for development.") }}
+
+
+ {# Warning banner #}
+
+
+ โ ๏ธ {{ _("Development Only") }} โ
+ {{ _("These tools are disabled in production and only available to admin users.") }}
+
+
+
+ {# Developer Tools Cards #}
+
+ {{ _("Documentation & Testing") }}
+
+
+ {# Service Layer Docs #}
+ {% call link_card(
+ href=url_for('backoffice.service_docs'),
+ title=_("Service Layer Docs"),
+ subtitle=_("CSV Upload Services"),
+ id="service-docs-card"
+ ) %}
+
+ {{ _("Interactive documentation for CSV upload service layer functions. Test import_respondents, import_targets, and CSV config services.") }}
+
+
+ ๐ {{ _("4 services documented") }}
+
+{% endcall %}
+
+ {# Frontend Patterns #}
+{% call link_card(
+href=url_for('backoffice.patterns'),
+title=_("Frontend Patterns"),
+subtitle=_("Alpine.js & CSP Guidelines"),
+id="patterns-card"
+) %}
+
+ {{ _("Living documentation for CSP-compatible Alpine.js patterns. Includes dropdown bindings, form state, and AJAX with working examples.") }}
+
+
+ ๐จ {{ _("Patterns with implementation index") }}
+
+{% endcall %}
+
+ {# Component Showcase #}
+{% call link_card(
+href=url_for('backoffice.showcase'),
+title=_("Component Showcase"),
+subtitle=_("Design System Reference"),
+id="showcase-card"
+) %}
+
+ {{ _("Visual reference for all backoffice UI components. Buttons, cards, forms, modals, typography, and color tokens.") }}
+
+
+ ๐งฉ {{ _("All components in one place") }}
+
+{% endcall %}
+
+
+
+ {# Quick Links #}
+
+ {{ _("Quick Reference") }}
+
+
+ -
+ CLAUDE.md โ
+ {{ _("Project guidance for AI assistants (includes Alpine.js CSP constraints)") }}
+
+ -
+ docs/agent/ โ
+ {{ _("Agent-specific documentation for frontend, testing, and code quality") }}
+
+ -
+ static/backoffice/js/alpine-components.js โ
+ {{ _("Reusable Alpine.js components (urlSelect, autocomplete, modal)") }}
+
+
+
+
+{% endblock %}
diff --git a/backend/templates/backoffice/patterns.html b/backend/templates/backoffice/patterns.html
index 1389a664..ea228334 100644
--- a/backend/templates/backoffice/patterns.html
+++ b/backend/templates/backoffice/patterns.html
@@ -14,7 +14,7 @@
{{ breadcrumbs([
{"label": _("Dashboard"), "href": url_for('backoffice.dashboard')},
- {"label": _("Developer Tools")},
+ {"label": _("Developer Tools"), "href": url_for('backoffice.dev_dashboard')},
{"label": _("Patterns")}
]) }}
diff --git a/backend/templates/backoffice/service_docs.html b/backend/templates/backoffice/service_docs.html
index 84bbfae5..8efae99a 100644
--- a/backend/templates/backoffice/service_docs.html
+++ b/backend/templates/backoffice/service_docs.html
@@ -16,7 +16,7 @@
{{ breadcrumbs([
{"label": _("Dashboard"), "href": url_for('backoffice.dashboard')},
- {"label": _("Developer Tools")},
+ {"label": _("Developer Tools"), "href": url_for('backoffice.dev_dashboard')},
{"label": _("Service Docs")}
]) }}
From e3171cc3f56f6ee9bcd4d48697a4599b7858880b Mon Sep 17 00:00:00 2001
From: gazdagergo
Date: Fri, 20 Mar 2026 20:58:34 +0100
Subject: [PATCH 06/31] Add file upload UI component and pattern documentation
UI Component:
- Add file_input macro to components/input.html
- Supports label, hint, error, accept, required, disabled
- Tailwind styling with file: pseudo-class for button
Pattern Documentation:
- Add File Upload tab to frontend patterns page
- Document architecture (UI -> Flask route -> service layer)
- Live demo with client-side file validation and preview
- Template and Flask route code examples
- Implementation index linking to existing code
Co-Authored-By: Claude Opus 4.5
---
.../entrypoints/blueprints/backoffice.py | 2 +-
.../backoffice/components/input.html | 92 +++++-
backend/templates/backoffice/patterns.html | 310 +++++++++++++++++-
3 files changed, 400 insertions(+), 4 deletions(-)
diff --git a/backend/src/opendlp/entrypoints/blueprints/backoffice.py b/backend/src/opendlp/entrypoints/blueprints/backoffice.py
index 08eee98e..a7c37b4c 100644
--- a/backend/src/opendlp/entrypoints/blueprints/backoffice.py
+++ b/backend/src/opendlp/entrypoints/blueprints/backoffice.py
@@ -803,7 +803,7 @@ def patterns() -> ResponseReturnValue:
# Get active tab from query parameter, default to 'dropdown'
active_tab = request.args.get("tab", "dropdown")
- valid_tabs = ["dropdown", "form", "ajax"]
+ valid_tabs = ["dropdown", "form", "ajax", "file-upload"]
if active_tab not in valid_tabs:
active_tab = "dropdown"
diff --git a/backend/templates/backoffice/components/input.html b/backend/templates/backoffice/components/input.html
index 48bf8787..ff236d15 100644
--- a/backend/templates/backoffice/components/input.html
+++ b/backend/templates/backoffice/components/input.html
@@ -1,6 +1,6 @@
{#
-ABOUTME: Text input component macro for backoffice design system
-ABOUTME: Supports text, email, password, number inputs with label and optional hint/error
+ABOUTME: Form input component macros for backoffice design system
+ABOUTME: Supports text, email, password, number, textarea, select, checkbox, radio, and file inputs
#}
{% macro first_error(errors) %}
@@ -421,3 +421,91 @@
{% endif %}
{% endmacro %}
+
+
+{% macro file_input(name, label="", hint="", error="", accept="", required=false, disabled=false, id="", classes="", attrs="") %}
+ {#
+ File input component using semantic design tokens.
+
+ This is a stateless UI component. For file upload handling logic
+ (form submission, reading content, validation), see the File Upload
+ pattern in /backoffice/dev/patterns.
+
+ Args:
+ name: Input name attribute (required)
+ label: Label text displayed above the input
+ hint: Help text displayed below the label
+ error: Error message (displays input in error state)
+ accept: Comma-separated list of accepted file types (e.g., ".csv", ".csv,.xlsx", "text/csv")
+ required: Boolean to mark field as required
+ disabled: Boolean to disable the input
+ id: Optional id attribute (defaults to name)
+ classes: Additional CSS classes for the input
+ attrs: Additional HTML attributes (e.g., 'x-model="file"' for Alpine.js binding)
+
+ Usage:
+ {# Basic file input #}
+ {{ file_input("csv_file", label="CSV File", accept=".csv", required=true) }}
+
+ {# With hint and error #}
+ {{ file_input(
+ "import_file",
+ label="Import File",
+ hint="Select a CSV file containing respondent data",
+ accept=".csv",
+ error=first_error(form.csv_file.errors)
+ ) }}
+
+ {# With Alpine.js binding #}
+ {{ file_input(
+ "csv_file",
+ label="CSV File",
+ accept=".csv",
+ attrs='@change="onFileSelect($event)"'
+ ) }}
+ #}
+ {% set input_id = id if id else name %}
+ {% set has_error = error | length > 0 %}
+
+
+ {# Label #}
+ {% if label %}
+
+ {% endif %}
+
+ {# Hint text #}
+ {% if hint %}
+
{{ hint }}
+ {% endif %}
+
+ {# File input with custom styling #}
+
+
+
+
+ {# Error message #}
+ {% if has_error %}
+
{{ error }}
+ {% endif %}
+
+{% endmacro %}
diff --git a/backend/templates/backoffice/patterns.html b/backend/templates/backoffice/patterns.html
index ea228334..0e88d20d 100644
--- a/backend/templates/backoffice/patterns.html
+++ b/backend/templates/backoffice/patterns.html
@@ -41,7 +41,8 @@
items=[
{"label": _("Dropdown"), "href": url_for('backoffice.patterns', tab='dropdown'), "active": active_tab == 'dropdown'},
{"label": _("Form State"), "href": url_for('backoffice.patterns', tab='form'), "active": active_tab == 'form'},
- {"label": _("AJAX"), "href": url_for('backoffice.patterns', tab='ajax'), "active": active_tab == 'ajax'}
+ {"label": _("AJAX"), "href": url_for('backoffice.patterns', tab='ajax'), "active": active_tab == 'ajax'},
+ {"label": _("File Upload"), "href": url_for('backoffice.patterns', tab='file-upload'), "active": active_tab == 'file-upload'}
],
aria_label=_("Pattern categories")
) }}
@@ -312,6 +313,211 @@