|
|
|
|
@ -178,8 +178,23 @@ _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(
|
|
|
|
|
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",
|
|
|
|
|
# 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",
|
|
|
|
|
re.I,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@ -190,6 +205,8 @@ _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:
|
|
|
|
|
@ -202,32 +219,78 @@ 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}"
|
|
|
|
|
m = _ABB_COMPLETED_RE.search(haystack)
|
|
|
|
|
|
|
|
|
|
# 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)
|
|
|
|
|
if not m:
|
|
|
|
|
# Not our ABB format
|
|
|
|
|
return False, {}, []
|
|
|
|
|
|
|
|
|
|
job_name = (m.group("job") or m.group("job_en") or "").strip()
|
|
|
|
|
host = (m.group("host") or m.group("host_en") or "").strip()
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
# Determine overall status:
|
|
|
|
|
# - Failed -> Error
|
|
|
|
|
# - Partially completed -> Warning
|
|
|
|
|
# - Otherwise -> Success
|
|
|
|
|
overall_status = "Success"
|
|
|
|
|
overall_message = "Success"
|
|
|
|
|
if _ABB_FAILED_RE.search(haystack):
|
|
|
|
|
|
|
|
|
|
is_failed = _ABB_FAILED_RE.search(haystack) is not None
|
|
|
|
|
is_partial = _ABB_PARTIAL_RE.search(haystack) is not None
|
|
|
|
|
|
|
|
|
|
if is_failed:
|
|
|
|
|
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] = []
|
|
|
|
|
for line in (text or "").splitlines():
|
|
|
|
|
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})
|
|
|
|
|
seen: set[str] = set()
|
|
|
|
|
|
|
|
|
|
result = {
|
|
|
|
|
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_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 = {
|
|
|
|
|
"backup_software": "Synology",
|
|
|
|
|
"backup_type": "Active Backup for Business",
|
|
|
|
|
"job_name": job_name,
|
|
|
|
|
@ -236,73 +299,11 @@ 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"):
|
|
|
|
|
if host and overall_message in ("Success", "Failed", "Partially completed"):
|
|
|
|
|
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:
|
|
|
|
|
|