Compare commits
4 Commits
4e1c300f0c
...
f9fd0ce016
| Author | SHA1 | Date | |
|---|---|---|---|
| f9fd0ce016 | |||
| 9ac125d60c | |||
| bbfcfebfc2 | |||
| 63d4b0126b |
@ -1 +1 @@
|
||||
v20260108-36-inbox-jobdetails-details-above-mail
|
||||
v20260108-37-synology-updates-info-parser
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user