From f680e799b2e335fdbdc665ce68b9cbd13b3f8763 Mon Sep 17 00:00:00 2001 From: Ivo Oskamp Date: Sun, 4 Jan 2026 16:50:41 +0100 Subject: [PATCH] Auto-commit local changes before build (2026-01-04 16:50:41) --- .last-branch | 2 +- .../backend/app/main/routes_reporting_api.py | 33 +++++++++++++++---- .../src/templates/main/reports.html | 18 +++++++++- .../src/templates/main/reports_new.html | 12 ++++--- docs/changelog.md | 8 +++++ 5 files changed, 59 insertions(+), 14 deletions(-) diff --git a/.last-branch b/.last-branch index 2d5d8e0..f43e141 100644 --- a/.last-branch +++ b/.last-branch @@ -1 +1 @@ -v20260104-11-reports-jobs-remove-text-column-option +v20260104-12-reports-object-name-job-name-columns-fix diff --git a/containers/backupchecks/src/backend/app/main/routes_reporting_api.py b/containers/backupchecks/src/backend/app/main/routes_reporting_api.py index 35f5d61..93e4c9b 100644 --- a/containers/backupchecks/src/backend/app/main/routes_reporting_api.py +++ b/containers/backupchecks/src/backend/app/main/routes_reporting_api.py @@ -262,6 +262,10 @@ def build_report_columns_meta(): { "name": "Job Information", "items": [ + # NOTE: object_name is currently populated with the job name in the reporting snapshot + # generator. Keep the key stable (report_config stores keys) but present it as "Job name" + # so it can be toggled like other columns. + {"key": "object_name", "label": "Job name", "views": ["summary", "snapshot", "jobs"]}, {"key": "job_name", "label": "Job name", "views": ["snapshot", "jobs"]}, {"key": "job_id", "label": "Job ID", "views": ["snapshot"]}, {"key": "backup_software", "label": "Software", "views": ["snapshot", "jobs"]}, @@ -1239,23 +1243,34 @@ def _export_html_response(report: ReportDefinition, report_id: int, view: str): allowed_by_view.setdefault(v, set()).add(k) def _selected_cols(view_key: str) -> list[str]: - cols = [] + # Only apply defaults when the report config does not define a list for this view. + # If a list is present but empty, the user intentionally selected no columns. + user_provided = False + cols: list[str] | None = None + rc_cols = rc.get("columns") if isinstance(rc, dict) else None - if isinstance(rc_cols, dict): + if isinstance(rc_cols, dict) and view_key in rc_cols: v = rc_cols.get(view_key) if isinstance(v, list): + user_provided = True cols = [str(x).strip() for x in v if str(x).strip()] - if not cols: + if cols is None: d = (cols_meta.get("defaults") or {}).get(view_key) if isinstance(d, list): cols = [str(x).strip() for x in d if str(x).strip()] + else: + cols = [] allowed = allowed_by_view.get(view_key) or set() - cols = [c for c in cols if c in allowed] - if not cols: - cols = [c for c in (cols_meta.get("defaults") or {}).get(view_key, []) if c in allowed] - return cols + filtered = [c for c in cols if c in allowed] + + # If the user provided keys but all were invalid/out-of-scope, fall back to defaults. + if user_provided and cols and not filtered: + filtered = [c for c in (cols_meta.get("defaults") or {}).get(view_key, []) if c in allowed] + + # If the user explicitly selected none, keep it empty. + return filtered def _td(value: str, cls: str = "") -> str: cl = f" class='{cls}'" if cls else "" @@ -1268,6 +1283,10 @@ def _export_html_response(report: ReportDefinition, report_id: int, view: str): def _render_table(view_key: str, rows: list[dict]) -> tuple[str, str]: cols = _selected_cols(view_key) + if not cols: + # Keep the HTML valid and explicit when the user deselects all columns. + return _th("No columns selected", "text-muted"), "No columns selected." + th = [] for k in cols: cls = "text-end" if k in ("missed", "override_applied") else "" diff --git a/containers/backupchecks/src/templates/main/reports.html b/containers/backupchecks/src/templates/main/reports.html index 3e0de3a..af0f7b5 100644 --- a/containers/backupchecks/src/templates/main/reports.html +++ b/containers/backupchecks/src/templates/main/reports.html @@ -210,7 +210,16 @@ function selectedColsFor(view) { var cfg = rawReportConfig || {}; - var cols = (cfg.columns && cfg.columns[view]) ? cfg.columns[view] : null; + var cols = null; + var hasView = false; + if (cfg.columns && typeof cfg.columns === 'object') { + hasView = Object.prototype.hasOwnProperty.call(cfg.columns, view); + cols = cfg.columns[view]; + } + if (hasView && Array.isArray(cols)) { + // If an empty list is saved, keep it empty. + return cols; + } if (cols && cols.length) return cols; return defaultColsFor(view); } @@ -300,6 +309,13 @@ var cols = selectedColsFor(view); + if (!cols || !cols.length) { + thead.innerHTML = 'No columns selected'; + tbody.innerHTML = 'No columns selected.'; + setRawDownloadLink(); + return; + } + function thRow(keys) { return '' + keys.map(function (k) { return '' + escapeHtml(colLabel(k)) + ''; }).join('') + ''; } diff --git a/containers/backupchecks/src/templates/main/reports_new.html b/containers/backupchecks/src/templates/main/reports_new.html index ed0e37a..23f9648 100644 --- a/containers/backupchecks/src/templates/main/reports_new.html +++ b/containers/backupchecks/src/templates/main/reports_new.html @@ -275,14 +275,16 @@ // --- Report content / column selector --- var repColsView = 'summary'; var repColsMeta = window.__reportColumnsMeta || null; - var repColsSelected = { summary: [], snapshot: [], jobs: [] }; + // Use null to indicate "no value configured" so defaults can be applied. + // If the user explicitly saves an empty list, keep it empty. + var repColsSelected = { summary: null, snapshot: null, jobs: null }; if (isEdit && initialReport && initialReport.report_config && initialReport.report_config.columns) { var cols = initialReport.report_config.columns; repColsSelected = { - summary: Array.isArray(cols.summary) ? cols.summary.slice() : [], - snapshot: Array.isArray(cols.snapshot) ? cols.snapshot.slice() : [], - jobs: Array.isArray(cols.jobs) ? cols.jobs.slice() : [], + summary: (cols && Object.prototype.hasOwnProperty.call(cols, 'summary') && Array.isArray(cols.summary)) ? cols.summary.slice() : null, + snapshot: (cols && Object.prototype.hasOwnProperty.call(cols, 'snapshot') && Array.isArray(cols.snapshot)) ? cols.snapshot.slice() : null, + jobs: (cols && Object.prototype.hasOwnProperty.call(cols, 'jobs') && Array.isArray(cols.jobs)) ? cols.jobs.slice() : null, }; } @@ -435,7 +437,7 @@ function ensureDefaultsFromMeta() { if (!repColsMeta || !repColsMeta.defaults) return; ['summary', 'snapshot', 'jobs'].forEach(function (v) { - if (!repColsSelected[v] || !repColsSelected[v].length) { + if (repColsSelected[v] === null || typeof repColsSelected[v] === 'undefined') { repColsSelected[v] = (repColsMeta.defaults[v] || []).slice(); } }); diff --git a/docs/changelog.md b/docs/changelog.md index a9f12df..d9a0372 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -281,6 +281,14 @@ - Cleaned up column metadata so the “Text” option no longer appears in the Reports UI. - Ensured consistency between selectable columns and rendered report output. +--- + +## v20260104-12-reports-object-name-job-name-columns-fix + +- Added the missing `object_name` column to the available report columns and labeled it as "Job name" for Summary/Snapshot/Jobs. +- Fixed column selection behavior so an explicitly saved empty selection stays empty (defaults are only applied when the view has no configured selection). +- Updated the raw data table and HTML export to show a clear “No columns selected” message when all columns are disabled, instead of still rendering default data. + ================================================================================================================================================ ## v0.1.15