Compare commits
No commits in common. "cd31b6f30546ce06241ea079ec859713c7a99852" and "16b928041a63152a1efb45fbaafe2622c95182b4" have entirely different histories.
cd31b6f305
...
16b928041a
@ -1 +1 @@
|
||||
v20260112-01-synology-abb-partial-warning
|
||||
v20260109-13-ntfs-audit-jobname-prefix-flex
|
||||
|
||||
@ -178,23 +178,8 @@ _ABB_SUBJECT_RE = re.compile(r"\bactive\s+backup\s+for\s+business\b", re.I)
|
||||
# Example (EN):
|
||||
# "The backup task vSphere-Task-1 on KANTOOR-NEW has completed."
|
||||
_ABB_COMPLETED_RE = re.compile(
|
||||
# NL examples:
|
||||
# "De back-uptaak <job> op <host> is voltooid."
|
||||
# "De virtuele machine back-uptaak <job> is voltooid."
|
||||
# EN examples:
|
||||
# "The backup task <job> on <host> has completed."
|
||||
r"\b(?:de\s+)?(?:virtuele\s+machine\s+)?back-?up\s*taak\s+(?P<job>.+?)(?:\s+op\s+(?P<host>.+?))?\s+is\s+voltooid\b"
|
||||
r"|\b(?:the\s+)?back-?up\s+task\s+(?P<job_en>.+?)(?:\s+on\s+(?P<host_en>.+?))?\s+(?:is\s+)?(?:completed|finished|has\s+completed)\b",
|
||||
re.I,
|
||||
)
|
||||
|
||||
_ABB_PARTIAL_RE = re.compile(
|
||||
# NL examples:
|
||||
# " ... is gedeeltelijk voltooid."
|
||||
# EN examples:
|
||||
# " ... is/has partially completed."
|
||||
r"\b(?:de\s+)?(?:virtuele\s+machine\s+)?back-?up\s*taak\s+(?P<job>.+?)(?:\s+op\s+(?P<host>.+?))?\s+is\s+gedeeltelijk\s+voltooid\b"
|
||||
r"|\b(?:the\s+)?back-?up\s+task\s+(?P<job_en>.+?)(?:\s+on\s+(?P<host_en>.+?))?\s+(?:has\s+)?(?:partially\s+completed|completed\s+partially|is\s+partially\s+completed)\b",
|
||||
r"\b(?:de\s+)?back-?up\s*taak\s+(?P<job>.+?)\s+op\s+(?P<host>.+?)\s+is\s+voltooid\b"
|
||||
r"|\b(?:the\s+)?back-?up\s+task\s+(?P<job_en>.+?)\s+on\s+(?P<host_en>.+?)\s+(?:is\s+)?(?:completed|finished|has\s+completed)\b",
|
||||
re.I,
|
||||
)
|
||||
|
||||
@ -205,8 +190,6 @@ _ABB_FAILED_RE = re.compile(
|
||||
)
|
||||
|
||||
_ABB_DEVICE_LIST_RE = re.compile(r"^\s*(?:Apparaatlijst|Device\s+list)\s*:\s*(?P<list>.+?)\s*$", re.I)
|
||||
_ABB_DEVICE_SUCCESS_RE = re.compile(r"^\s*(?:Lijst\s+met\s+apparaten\s*\(back-?up\s+gelukt\)|List\s+of\s+devices\s*\(backup\s+succeeded\)|Device\s+list\s*\(backup\s+succeeded\))\s*:\s*(?P<list>.*)\s*$", re.I)
|
||||
_ABB_DEVICE_FAILED_RE = re.compile(r"^\s*(?:Lijst\s+met\s+apparaten\s*\(back-?up\s+mislukt\)|List\s+of\s+devices\s*\(backup\s+failed\)|Device\s+list\s*\(backup\s+failed\))\s*:\s*(?P<list>.*)\s*$", re.I)
|
||||
|
||||
|
||||
def _is_synology_active_backup_for_business(subject: str, text: str) -> bool:
|
||||
@ -219,78 +202,32 @@ def _is_synology_active_backup_for_business(subject: str, text: str) -> bool:
|
||||
|
||||
def _parse_active_backup_for_business(subject: str, text: str) -> Tuple[bool, Dict, List[Dict]]:
|
||||
haystack = f"{subject}\n{text}"
|
||||
|
||||
# ABB mails always contain a completion marker (completed / partially completed) in subject or body.
|
||||
m = _ABB_COMPLETED_RE.search(haystack) or _ABB_PARTIAL_RE.search(haystack)
|
||||
m = _ABB_COMPLETED_RE.search(haystack)
|
||||
if not m:
|
||||
# Not our ABB format
|
||||
return False, {}, []
|
||||
|
||||
job_name = (m.groupdict().get("job") or m.groupdict().get("job_en") or "").strip()
|
||||
host = (m.groupdict().get("host") or m.groupdict().get("host_en") or "").strip()
|
||||
if not host:
|
||||
m_host = re.search(r"\b(?:Van|From)\s+(?P<host>[^\r\n<]+)", text or "", re.I)
|
||||
if m_host:
|
||||
host = (m_host.group("host") or "").strip()
|
||||
job_name = (m.group("job") or m.group("job_en") or "").strip()
|
||||
host = (m.group("host") or m.group("host_en") or "").strip()
|
||||
|
||||
# Determine overall status:
|
||||
# - Failed -> Error
|
||||
# - Partially completed -> Warning
|
||||
# - Otherwise -> Success
|
||||
overall_status = "Success"
|
||||
overall_message = "Success"
|
||||
|
||||
is_failed = _ABB_FAILED_RE.search(haystack) is not None
|
||||
is_partial = _ABB_PARTIAL_RE.search(haystack) is not None
|
||||
|
||||
if is_failed:
|
||||
if _ABB_FAILED_RE.search(haystack):
|
||||
overall_status = "Error"
|
||||
overall_message = "Failed"
|
||||
elif is_partial:
|
||||
overall_status = "Warning"
|
||||
overall_message = "Partially completed"
|
||||
|
||||
# Parse device lists (newer ABB mail templates).
|
||||
# NL examples:
|
||||
# "Lijst met apparaten (back-up gelukt): SQL01, DC01"
|
||||
# "Lijst met apparaten (back-up mislukt): DC02"
|
||||
# EN examples:
|
||||
# "List of devices (backup succeeded): ..."
|
||||
# "List of devices (backup failed): ..."
|
||||
objects: List[Dict] = []
|
||||
seen: set[str] = set()
|
||||
|
||||
def _add_devices(raw: str, status: str) -> None:
|
||||
if raw is None:
|
||||
return
|
||||
raw = raw.strip()
|
||||
if not raw:
|
||||
return
|
||||
for name in [p.strip() for p in raw.split(",")]:
|
||||
if not name or name in seen:
|
||||
continue
|
||||
seen.add(name)
|
||||
objects.append({"name": name, "status": status})
|
||||
|
||||
# First parse explicit succeeded/failed device lists when present.
|
||||
for line in (text or "").splitlines():
|
||||
mm_ok = _ABB_DEVICE_SUCCESS_RE.match(line.strip())
|
||||
if mm_ok:
|
||||
_add_devices(mm_ok.group("list"), "Success")
|
||||
mm = _ABB_DEVICE_LIST_RE.match(line.strip())
|
||||
if not mm:
|
||||
continue
|
||||
raw_list = (mm.group("list") or "").strip()
|
||||
# "DC01, SQL01"
|
||||
for name in [p.strip() for p in raw_list.split(",")]:
|
||||
if name:
|
||||
objects.append({"name": name, "status": overall_status})
|
||||
|
||||
mm_fail = _ABB_DEVICE_FAILED_RE.match(line.strip())
|
||||
if mm_fail:
|
||||
_add_devices(mm_fail.group("list"), "Error")
|
||||
|
||||
# Fallback: generic "Apparaatlijst:" / "Device list:" (older templates)
|
||||
if not objects:
|
||||
for line in (text or "").splitlines():
|
||||
mm = _ABB_DEVICE_LIST_RE.match(line.strip())
|
||||
if not mm:
|
||||
continue
|
||||
_add_devices(mm.group("list"), overall_status)
|
||||
|
||||
result: Dict = {
|
||||
result = {
|
||||
"backup_software": "Synology",
|
||||
"backup_type": "Active Backup for Business",
|
||||
"job_name": job_name,
|
||||
@ -299,11 +236,73 @@ def _parse_active_backup_for_business(subject: str, text: str) -> Tuple[bool, Di
|
||||
}
|
||||
|
||||
# Provide a slightly nicer overall message when host is available
|
||||
if host and overall_message in ("Success", "Failed", "Partially completed"):
|
||||
if host and overall_message in ("Success", "Failed"):
|
||||
result["overall_message"] = f"{overall_message} ({host})"
|
||||
|
||||
return True, result, objects
|
||||
|
||||
def _is_synology_active_backup(subject: str, text: str) -> bool:
|
||||
# Keep matching conservative to avoid false positives.
|
||||
subj = (subject or "").lower()
|
||||
if "active backup" in subj:
|
||||
return True
|
||||
|
||||
# Fallback for senders that don't include it in the subject
|
||||
t = (text or "").lower()
|
||||
return "active backup" in t and ("adminconsole" in t or "back-up" in t or "backup" in t)
|
||||
|
||||
|
||||
def _is_synology_hyper_backup(subject: str, text: str) -> bool:
|
||||
# Subject often does not mention Hyper Backup; body typically contains it.
|
||||
s = (subject or "").lower()
|
||||
t = (text or "").lower()
|
||||
|
||||
if "hyper backup" in s:
|
||||
return True
|
||||
|
||||
# Dutch/English variants that appear in Hyper Backup task notifications.
|
||||
if ("hyper backup" in t) and (
|
||||
("taaknaam:" in t)
|
||||
or ("task name:" in t)
|
||||
or ("gegevensback-uptaak" in t)
|
||||
or ("data backup task" in t)
|
||||
):
|
||||
return True
|
||||
|
||||
# Newer task notification variant (often used for cloud destinations like HiDrive)
|
||||
# does not always include "Hyper Backup" in the subject/body but contains these fields.
|
||||
if ("backup task:" in t) and ("backup destination:" in t):
|
||||
return True
|
||||
|
||||
# Dutch task notification variant (e.g. "Uw back-uptaak ... is nu voltooid")
|
||||
return ("back-uptaak:" in t) and ("back-updoel:" in t)
|
||||
|
||||
|
||||
_HB_TASKNAME_RE = re.compile(
|
||||
r"^(?:Taaknaam|Task name|Back-uptaak|Backup Task|Backup task)\s*:\s*(?P<name>.+)$",
|
||||
re.I | re.M,
|
||||
)
|
||||
_HB_BACKUP_TASK_RE = re.compile(r"^Backup Task\s*:\s*(?P<name>.+)$", re.I | re.M)
|
||||
_HB_FAILED_RE = re.compile(r"\bis\s+mislukt\b|\bhas\s+failed\b|\bfailed\b", re.I)
|
||||
_HB_SUCCESS_RE = re.compile(
|
||||
r"\bis\s+(?:nu\s+)?voltooid\b|\bhas\s+completed\b|\bsuccessful\b|\bgeslaagd\b",
|
||||
re.I,
|
||||
)
|
||||
_HB_WARNING_RE = re.compile(r"\bgedeeltelijk\s+voltooid\b|\bpartially\s+completed\b|\bwarning\b|\bwaarschuwing\b", re.I)
|
||||
|
||||
|
||||
# Synology Network Backup / R-Sync task notifications
|
||||
# Example (NL):
|
||||
# "Uw back-uptaak R-Sync ASP-NAS02 is nu voltooid."
|
||||
# "Back-uptaak: R-Sync ASP-NAS02"
|
||||
# Example (EN):
|
||||
# "Your backup task R-Sync ASP-NAS02 has completed."
|
||||
# "Backup task: R-Sync ASP-NAS02"
|
||||
_RSYNC_MARKER_RE = re.compile(r"\br-?sync\b", re.I)
|
||||
_RSYNC_TASK_RE = re.compile(r"^(?:Back-uptaak|Backup\s+task)\s*:\s*(?P<name>.+)$", re.I | re.M)
|
||||
_RSYNC_FAILED_RE = re.compile(r"\bis\s+mislukt\b|\bhas\s+failed\b|\bfailed\b", re.I)
|
||||
_RSYNC_WARNING_RE = re.compile(r"\bgedeeltelijk\s+voltooid\b|\bpartially\s+completed\b|\bwarning\b|\bwaarschuwing\b", re.I)
|
||||
_RSYNC_SUCCESS_RE = re.compile(r"\bis\s+(?:nu\s+)?voltooid\b|\bhas\s+completed\b|\bcompleted\b|\bsuccessful\b|\bgeslaagd\b", re.I)
|
||||
|
||||
|
||||
def _is_synology_rsync(subject: str, text: str) -> bool:
|
||||
|
||||
@ -97,25 +97,6 @@
|
||||
|
||||
- Updated NTFS Auditing (Audit) subject parsing to extract the hostname/FQDN directly from the start of the subject before "file audits".
|
||||
- Removed the dependency on a fixed "Bouter <host>" subject prefix, so hosts like dc01.totall-it.local and fs01.totall-it.local are recognized.
|
||||
|
||||
---
|
||||
|
||||
## v20260109-13-ntfs-audit-jobname-prefix-flex
|
||||
|
||||
- Updated NTFS Auditing (Audit) subject parsing to support both formats:
|
||||
- Subjects starting with "Bouter <host> file audits"
|
||||
- Subjects starting directly with "<host> file audits"
|
||||
- Ensures both btr-dc001/btr-dc002 and dc01/fs01 style hostnames are recognized consistently.
|
||||
|
||||
---
|
||||
|
||||
## v20260112-01-synology-abb-partial-warning
|
||||
|
||||
- Updated the Synology Active Backup for Business parser to detect “partially completed” jobs as Warning instead of ignoring them.
|
||||
- Added support for localized variants of the completion message (including cases without an explicit host in the first line).
|
||||
- Improved host detection by extracting the system name from the “From” header when not present in the subject/body.
|
||||
- Extended object parsing to recognize per-device backup results and correctly classify them as Success or Error.
|
||||
- Fixed the ABB-specific regular expression to prevent parser failures on valid warning mails.
|
||||
================================================================================================================================================
|
||||
## v0.1.19
|
||||
This release delivers a broad set of improvements focused on reliability, transparency, and operational control across mail processing, administrative auditing, and Run Checks workflows. The changes aim to make message handling more robust, provide better insight for administrators, and give operators clearer and more flexible control when reviewing backup runs.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user