From 13c4c5950dddd2d86b84db91e84278958323eb64 Mon Sep 17 00:00:00 2001 From: Ivo Oskamp Date: Tue, 6 Jan 2026 10:11:59 +0100 Subject: [PATCH] Auto-commit local changes before build (2026-01-06 10:11:59) --- .last-branch | 2 +- .../src/backend/app/parsers/veeam.py | 20 ++++++++++++++++++- docs/changelog.md | 8 ++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.last-branch b/.last-branch index 532f1a2..dbd7510 100644 --- a/.last-branch +++ b/.last-branch @@ -1 +1 @@ -v20260106-02-inbox-bulk-delete +v20260106-03-veeam-full-job-name-merge diff --git a/containers/backupchecks/src/backend/app/parsers/veeam.py b/containers/backupchecks/src/backend/app/parsers/veeam.py index 71f8fd5..ee628ef 100644 --- a/containers/backupchecks/src/backend/app/parsers/veeam.py +++ b/containers/backupchecks/src/backend/app/parsers/veeam.py @@ -711,6 +711,20 @@ def _strip_m365_combined_suffix(job_name: Optional[str]) -> Optional[str]: return cleaned or None +def _strip_full_suffix(job_name: Optional[str]) -> Optional[str]: + """Remove a trailing "(Full)" suffix from a Veeam job name. + + Some Veeam installations create separate emails where the job name is + suffixed with "(Full)" (e.g. "Backup VM DC01 (Full)"). Those should be + treated as the same logical job as the non-suffixed name. + """ + if not job_name: + return job_name + + cleaned = re.sub(r"\s*\(\s*Full\s*\)\s*$", "", job_name, flags=re.IGNORECASE).strip() + return cleaned or None + + def try_parse_veeam(msg: MailMessage) -> Tuple[bool, Dict, List[Dict]]: """Try to parse a Veeam backup report mail. @@ -866,6 +880,10 @@ def try_parse_veeam(msg: MailMessage) -> Tuple[bool, Dict, List[Dict]]: # Do not let retry counters create distinct job names. job_name = _strip_retry_suffix(job_name) + # Veeam can append a "(Full)" suffix to the job name in some reports. + # Strip it so full/non-full mails map to the same logical job. + job_name = _strip_full_suffix(job_name) + # Veeam Backup for Microsoft 365 reports can add a "(Combined)" suffix. # Strip it so combined/non-combined mails map to the same job. if (backup_type or "") == "Veeam Backup for Microsoft 365": @@ -885,7 +903,7 @@ def try_parse_veeam(msg: MailMessage) -> Tuple[bool, Dict, List[Dict]]: result: Dict = { "backup_software": "Veeam", "backup_type": backup_type, - "job_name": _strip_retry_suffix(job_name), + "job_name": job_name, "overall_status": status_word, } diff --git a/docs/changelog.md b/docs/changelog.md index e7a5b00..67bcac2 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -12,6 +12,14 @@ - Added a “Delete selected” bulk action that soft-deletes selected inbox messages (moves them to Deleted), including select-all support and a selected-count indicator. - Implemented a new POST API endpoint to bulk delete inbox messages with proper validation, skip/missing counts, and admin event logging. +--- + +## v20260106-03-veeam-full-job-name-merge + +- Updated Veeam parser to normalize job names. +- Jobs containing "(Full)" are now treated as the same job as those without "(Full)". +- This prevents duplicate job entries and ensures correct aggregation and reporting. + ================================================================================================================================================ ## v0.1.16