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