Merge branch 'v20260209-07-synology-drive-health-parser' into main
This commit is contained in:
commit
49f24595c3
@ -1 +1 @@
|
|||||||
v20260209-06-synology-firmware-update-parser
|
v20260209-07-synology-drive-health-parser
|
||||||
|
|||||||
@ -113,6 +113,32 @@ PARSER_DEFINITIONS = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "synology_drive_health",
|
||||||
|
"backup_software": "Synology",
|
||||||
|
"backup_types": ["Health Report"],
|
||||||
|
"order": 237,
|
||||||
|
"enabled": True,
|
||||||
|
"match": {
|
||||||
|
"subject_contains_any": ["schijfintegriteitsrapport", "Drive Health Report"],
|
||||||
|
"body_contains_any": ["health of the drives", "integriteitsrapport van de schijven"],
|
||||||
|
},
|
||||||
|
"description": "Parses Synology monthly drive health reports (informational; excluded from reporting and missing logic).",
|
||||||
|
"example": {
|
||||||
|
"subject": "[NAS-HOSTNAME] Monthly Drive Health Report on NAS-HOSTNAME - Healthy",
|
||||||
|
"from_address": "nas@example.local",
|
||||||
|
"body_snippet": "The following is your monthly report regarding the health of the drives on NAS-HOSTNAME. No problem detected with the drives in DSM.",
|
||||||
|
"parsed_result": {
|
||||||
|
"backup_software": "Synology",
|
||||||
|
"backup_type": "Health Report",
|
||||||
|
"job_name": "Monthly Drive Health",
|
||||||
|
"overall_status": "Success",
|
||||||
|
"objects": [
|
||||||
|
{"name": "NAS-HOSTNAME", "status": "Success"}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "veeam_replication_job",
|
"name": "veeam_replication_job",
|
||||||
"backup_software": "Veeam",
|
"backup_software": "Veeam",
|
||||||
|
|||||||
@ -73,6 +73,75 @@ def _parse_synology_dsm_update_cancelled(subject: str, text: str) -> Tuple[bool,
|
|||||||
|
|
||||||
return True, result, objects
|
return True, result, objects
|
||||||
|
|
||||||
|
|
||||||
|
# --- Synology Drive Health Report (informational, excluded from reporting) ---
|
||||||
|
|
||||||
|
DRIVE_HEALTH_PATTERNS = [
|
||||||
|
"schijfintegriteitsrapport",
|
||||||
|
"Drive Health Report",
|
||||||
|
"Monthly Drive Health",
|
||||||
|
"health of the drives",
|
||||||
|
"integriteitsrapport van de schijven",
|
||||||
|
]
|
||||||
|
|
||||||
|
_DRIVE_HEALTH_SUBJECT_RE = re.compile(
|
||||||
|
r"\b(?:schijfintegriteitsrapport\s+over|Drive\s+Health\s+Report\s+on)\s+(?P<host>[A-Za-z0-9._-]+)",
|
||||||
|
re.I,
|
||||||
|
)
|
||||||
|
|
||||||
|
_DRIVE_HEALTH_FROM_RE = re.compile(r"\b(?:Van|From)\s+(?P<host>[A-Za-z0-9._-]+)\b", re.I)
|
||||||
|
|
||||||
|
_DRIVE_HEALTH_STATUS_HEALTHY_RE = re.compile(
|
||||||
|
r"\b(?:Gezond|Healthy|geen\s+problemen\s+gedetecteerd|No\s+problem\s+detected)\b",
|
||||||
|
re.I,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_synology_drive_health(subject: str, text: str) -> bool:
|
||||||
|
haystack = f"{subject}\n{text}".lower()
|
||||||
|
return any(p.lower() in haystack for p in DRIVE_HEALTH_PATTERNS)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_synology_drive_health(subject: str, text: str) -> Tuple[bool, Dict, List[Dict]]:
|
||||||
|
haystack = f"{subject}\n{text}"
|
||||||
|
host = ""
|
||||||
|
|
||||||
|
# Try to extract hostname from subject first
|
||||||
|
m = _DRIVE_HEALTH_SUBJECT_RE.search(subject or "")
|
||||||
|
if m:
|
||||||
|
host = (m.group("host") or "").strip()
|
||||||
|
|
||||||
|
# Fallback: extract from body "Van/From NAS-NAME"
|
||||||
|
if not host:
|
||||||
|
m = _DRIVE_HEALTH_FROM_RE.search(text or "")
|
||||||
|
if m:
|
||||||
|
host = (m.group("host") or "").strip()
|
||||||
|
|
||||||
|
# Determine status based on health indicators
|
||||||
|
overall_status = "Success"
|
||||||
|
overall_message = "Healthy"
|
||||||
|
|
||||||
|
if not _DRIVE_HEALTH_STATUS_HEALTHY_RE.search(haystack):
|
||||||
|
# If we don't find healthy indicators, mark as Warning
|
||||||
|
overall_status = "Warning"
|
||||||
|
overall_message = "Drive health issue detected"
|
||||||
|
|
||||||
|
# Informational job: show in Run Checks, but do not participate in schedules / reporting.
|
||||||
|
result: Dict = {
|
||||||
|
"backup_software": "Synology",
|
||||||
|
"backup_type": "Health Report",
|
||||||
|
"job_name": "Monthly Drive Health",
|
||||||
|
"overall_status": overall_status,
|
||||||
|
"overall_message": overall_message + (f" ({host})" if host else ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
objects: List[Dict] = []
|
||||||
|
if host:
|
||||||
|
objects.append({"name": host, "status": overall_status})
|
||||||
|
|
||||||
|
return True, result, objects
|
||||||
|
|
||||||
|
|
||||||
_BR_RE = re.compile(r"<\s*br\s*/?\s*>", re.I)
|
_BR_RE = re.compile(r"<\s*br\s*/?\s*>", re.I)
|
||||||
_TAG_RE = re.compile(r"<[^>]+>")
|
_TAG_RE = re.compile(r"<[^>]+>")
|
||||||
_WS_RE = re.compile(r"[\t\r\f\v ]+")
|
_WS_RE = re.compile(r"[\t\r\f\v ]+")
|
||||||
@ -509,6 +578,12 @@ def try_parse_synology(msg: MailMessage) -> Tuple[bool, Dict, List[Dict]]:
|
|||||||
if ok:
|
if ok:
|
||||||
return True, result, objects
|
return True, result, objects
|
||||||
|
|
||||||
|
# Drive Health Report (informational; no schedule; excluded from reporting)
|
||||||
|
if _is_synology_drive_health(subject, text):
|
||||||
|
ok, result, objects = _parse_synology_drive_health(subject, text)
|
||||||
|
if ok:
|
||||||
|
return True, result, objects
|
||||||
|
|
||||||
# DSM Account Protection (informational; no schedule)
|
# DSM Account Protection (informational; no schedule)
|
||||||
if _is_synology_account_protection(subject, text):
|
if _is_synology_account_protection(subject, text):
|
||||||
ok, result, objects = _parse_account_protection(subject, text)
|
ok, result, objects = _parse_account_protection(subject, text)
|
||||||
|
|||||||
@ -5,6 +5,7 @@ This file documents all changes made to this project via Claude Code.
|
|||||||
## [2026-02-09]
|
## [2026-02-09]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- Added parser for Synology monthly drive health reports (backup software: Synology, backup type: Health Report, job name: Monthly Drive Health, informational only, no schedule learning) with support for both Dutch and English notifications ("schijfintegriteitsrapport"/"Drive Health Report") and automatic status detection (Healthy/Gezond → Success, problems → Warning)
|
||||||
- Added "Cleanup orphaned jobs" maintenance option in Settings → Maintenance to delete jobs without valid customer links and their associated emails/runs permanently from database (useful when customers are removed)
|
- Added "Cleanup orphaned jobs" maintenance option in Settings → Maintenance to delete jobs without valid customer links and their associated emails/runs permanently from database (useful when customers are removed)
|
||||||
- Added "Preview orphaned jobs" button to show detailed list of jobs to be deleted with run/email counts before confirming deletion (verification step for safety)
|
- Added "Preview orphaned jobs" button to show detailed list of jobs to be deleted with run/email counts before confirming deletion (verification step for safety)
|
||||||
- Added "Generate test emails" feature in Settings → Maintenance with three separate buttons to create fixed test email sets (success/warning/error) in inbox for testing parsers and maintenance operations (each set contains exactly 3 Veeam Backup Job emails with the same job name "Test-Backup-Job" and different dates/objects/statuses for reproducible testing and proper status flow testing)
|
- Added "Generate test emails" feature in Settings → Maintenance with three separate buttons to create fixed test email sets (success/warning/error) in inbox for testing parsers and maintenance operations (each set contains exactly 3 Veeam Backup Job emails with the same job name "Test-Backup-Job" and different dates/objects/statuses for reproducible testing and proper status flow testing)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user