v20260108-37-synology-updates-info-parser #72

Merged
ivooskamp merged 3 commits from v20260108-37-synology-updates-info-parser into main 2026-01-13 11:29:00 +01:00
5 changed files with 75 additions and 3 deletions

View File

@ -1 +1 @@
v20260108-36-inbox-jobdetails-details-above-mail
v20260108-37-synology-updates-info-parser

View File

@ -352,9 +352,14 @@ def build_report_job_filters_meta():
for bs, bt in rows:
bs_val = (bs or "").strip()
bt_val = (bt or "").strip()
# Exclude known informational types.
if bt_val.lower() in info_backup_types:
continue
# Synology DSM Updates are informational and should never appear in reports.
if (bs_val or "").strip().lower() == "synology" and bt_val.lower() == "updates":
continue
if bs_val:
backup_softwares_set.add(bs_val)
if bt_val:
@ -376,7 +381,7 @@ def build_report_job_filters_meta():
"backup_softwares": backup_softwares,
"backup_types": backup_types,
"by_backup_software": by_backup_software_out,
"excluded_backup_types": ["License Key"],
"excluded_backup_types": ["License Key", "Updates (Synology)"],
}
@ -499,7 +504,10 @@ def api_reports_generate(report_id: int):
where_customer = ""
params = {"rid": report_id, "start_ts": report.period_start, "end_ts": report.period_end}
# Job filters from report_config
where_filters = " AND COALESCE(j.backup_type,'') NOT ILIKE 'license key' "
where_filters = (
" AND COALESCE(j.backup_type,'') NOT ILIKE 'license key' "
" AND NOT (LOWER(COALESCE(j.backup_software,'')) = 'synology' AND LOWER(COALESCE(j.backup_type,'')) = 'updates') "
)
rc = _safe_json_dict(getattr(report, "report_config", None))
filters = rc.get("filters") if isinstance(rc, dict) else None
if isinstance(filters, dict):

View File

@ -635,6 +635,8 @@ def _infer_schedule_map_from_runs(job_id: int):
return schedule
if bs == 'synology' and bt == 'account protection':
return schedule
if bs == 'synology' and bt == 'updates':
return schedule
if bs == 'syncovery' and bt == 'syncovery':
return schedule
except Exception:

View File

@ -1,5 +1,7 @@
# --- Synology DSM Updates (informational, excluded from reporting) ---
from __future__ import annotations
import re
from typing import Dict, Tuple, List, Optional
@ -12,6 +14,52 @@ from ..models import MailMessage
# - Hyper Backup (Synology): task notifications from Hyper Backup
# - Account Protection (Synology): DSM Account Protection lockout notifications
DSM_UPDATE_CANCELLED_PATTERNS = [
"Automatische update van DSM is geannuleerd",
"Automatic DSM update was cancelled",
"Automatic update of DSM was cancelled",
]
_DSM_UPDATE_CANCELLED_HOST_RE = re.compile(
r"\b(?:geannuleerd\s+op|cancelled\s+on)\s+(?P<host>[A-Za-z0-9._-]+)\b",
re.I,
)
_DSM_UPDATE_FROM_HOST_RE = re.compile(r"\bVan\s+(?P<host>[A-Za-z0-9._-]+)\b", re.I)
def _is_synology_dsm_update_cancelled(subject: str, text: str) -> bool:
haystack = f"{subject}\n{text}".lower()
return any(p.lower() in haystack for p in DSM_UPDATE_CANCELLED_PATTERNS)
def _parse_synology_dsm_update_cancelled(subject: str, text: str) -> Tuple[bool, Dict, List[Dict]]:
haystack = f"{subject}\n{text}"
host = ""
m = _DSM_UPDATE_CANCELLED_HOST_RE.search(haystack)
if m:
host = (m.group("host") or "").strip()
if not host:
m = _DSM_UPDATE_FROM_HOST_RE.search(haystack)
if m:
host = (m.group("host") or "").strip()
# Informational job: show in Run Checks, but do not participate in schedules / reporting.
result: Dict = {
"backup_software": "Synology",
"backup_type": "Updates",
"job_name": "Synology Automatic Update",
"overall_status": "Warning",
"overall_message": "Automatic DSM update cancelled" + (f" ({host})" if host else ""),
}
objects: List[Dict] = []
if host:
objects.append({"name": host, "status": "Warning"})
return True, result, objects
_BR_RE = re.compile(r"<\s*br\s*/?\s*>", re.I)
_TAG_RE = re.compile(r"<[^>]+>")
_WS_RE = re.compile(r"[\t\r\f\v ]+")
@ -385,6 +433,12 @@ def try_parse_synology(msg: MailMessage) -> Tuple[bool, Dict, List[Dict]]:
# If html_body is empty, treat text_body as already-normalized text.
text = _html_to_text(html_body) if html_body else (text_body or "")
# DSM Updates (informational; no schedule; excluded from reporting)
if _is_synology_dsm_update_cancelled(subject, text):
ok, result, objects = _parse_synology_dsm_update_cancelled(subject, text)
if ok:
return True, result, objects
# DSM Account Protection (informational; no schedule)
if _is_synology_account_protection(subject, text):
ok, result, objects = _parse_account_protection(subject, text)

View File

@ -88,6 +88,14 @@
- Moved the **Details** section above the email body in the **Job Details** popup.
- Improved readability by avoiding long detail texts being constrained to the narrow left column.
---
## v20260108-37-synology-updates-info-parser
- Added detection for Synology DSM automatic update cancellation messages (NL/EN).
- Classified Synology “Updates” messages as informational only.
- Set overall status to Warning without creating schedules or expected-run logic.
- Excluded Synology Updates informational messages from reporting output.
================================================================================================================================================
## v0.1.18