diff --git a/.last-branch b/.last-branch index 48fd3f9..1d633b1 100644 --- a/.last-branch +++ b/.last-branch @@ -1 +1 @@ -changes-v20260103-02-reports-delete +v20260103-03-reports-loading-fix diff --git a/containers/backupchecks/src/backend/app/main/routes_reports.py b/containers/backupchecks/src/backend/app/main/routes_reports.py index 74343c7..24b22a7 100644 --- a/containers/backupchecks/src/backend/app/main/routes_reports.py +++ b/containers/backupchecks/src/backend/app/main/routes_reports.py @@ -1,16 +1,50 @@ from .routes_shared import * # noqa: F401,F403 +def _safe_json_list(value): + if not value: + return [] + try: + if isinstance(value, (list, tuple)): + return [int(v) for v in value] + return json.loads(value) + except Exception: + return [] + + +def _build_report_item(r): + return { + "id": int(r.id), + "name": r.name or "", + "description": r.description or "", + "report_type": r.report_type, + "output_format": r.output_format, + "customer_scope": getattr(r, "customer_scope", "all") or "all", + "customer_ids": _safe_json_list(getattr(r, "customer_ids", None)), + "period_start": r.period_start.isoformat() if getattr(r, "period_start", None) else "", + "period_end": r.period_end.isoformat() if getattr(r, "period_end", None) else "", + "schedule": r.schedule or "", + "created_at": r.created_at.isoformat() if getattr(r, "created_at", None) else "", + } + + @main_bp.route("/reports") @login_required -@roles_required("admin", "operator", "reporter", "viewer") def reports(): - # Defaults are used by the Reports UI for quick testing. All values are UTC. - period_end = datetime.utcnow().replace(microsecond=0) - period_start = (period_end - timedelta(days=7)).replace(microsecond=0) + # Pre-render items so the page is usable even if JS fails to load/execute. + rows = ( + db.session.query(ReportDefinition) + .order_by(ReportDefinition.created_at.desc()) + .limit(200) + .all() + ) + items = [_build_report_item(r) for r in rows] + + period_start, period_end = get_default_report_period() return render_template( "main/reports.html", + initial_reports=items, default_period_start=period_start.isoformat(), default_period_end=period_end.isoformat(), ) @@ -18,6 +52,14 @@ def reports(): @main_bp.route("/reports/new") @login_required -@roles_required("admin", "operator", "reporter", "viewer") def reports_new(): - return render_template("main/reports_new.html") + # Preload customers so the form remains usable if JS fails to load/execute. + customers = ( + db.session.query(Customer) + .filter(Customer.active.is_(True)) + .order_by(Customer.name.asc()) + .all() + ) + customer_items = [{"id": int(c.id), "name": c.name or ""} for c in customers] + + return render_template("main/reports_new.html", initial_customers=customer_items) diff --git a/containers/backupchecks/src/templates/main/reports.html b/containers/backupchecks/src/templates/main/reports.html index c40cb20..ab7e847 100644 --- a/containers/backupchecks/src/templates/main/reports.html +++ b/containers/backupchecks/src/templates/main/reports.html @@ -36,10 +36,35 @@ - - Loading… - - + {% if initial_reports %} + {% for item in initial_reports %} + + {{ item.name }}
{{ item.description }}
+ {{ item.created_at.replace('T',' ') if item.created_at else '' }} + + {% if item.period_start or item.period_end %} + {{ item.period_start.replace('T',' ') if item.period_start else '' }} → {{ item.period_end.replace('T',' ') if item.period_end else '' }} + {% else %} + + {% endif %} + + {{ item.output_format }} + + + + Download + {% if active_role in ('admin','operator','reporter') %} + + {% endif %} + + + {% endfor %} + {% else %} + + No reports found. + + {% endif %} + > diff --git a/containers/backupchecks/src/templates/main/reports_new.html b/containers/backupchecks/src/templates/main/reports_new.html index ac5b88f..3ce76ac 100644 --- a/containers/backupchecks/src/templates/main/reports_new.html +++ b/containers/backupchecks/src/templates/main/reports_new.html @@ -94,13 +94,26 @@
- +
Search will be added later. For MVP this is a simple dropdown.
- +
Hold Ctrl/Cmd to select multiple customers.
@@ -225,6 +238,10 @@ } function loadCustomers() { + if (initialCustomers && Array.isArray(initialCustomers) && initialCustomers.length) { + // Already rendered server-side. Keep the selects usable even if API calls fail. + return; + } qs('rep_customer_single').innerHTML = ''; qs('rep_customer_multiple').innerHTML = ''; diff --git a/docs/changelog.md b/docs/changelog.md index 826d051..1e615b7 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -12,6 +12,15 @@ - Introduced a Delete action per report, available to authorized roles (admin/operator/reporter). - Implemented backend deletion handling and automatic refresh of the reports list after removal. +--- + +## v20260103-03-reports-loading-fix + +- Fixed an issue where the Reports page remained stuck on “Loading…”. +- Ensured reports are rendered correctly when the page is loaded, even if the client-side API call fails. +- Fixed customer selection components on the Reports pages that could remain in a loading state. +- Improved robustness of report data handling to prevent rendering issues caused by invalid or missing customer references. + ================================================================================================================================================ ## v0.1.15