80 lines
2.5 KiB
Python
80 lines
2.5 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
import httpx
|
|
|
|
from .db import db, utc_now
|
|
from .settings_store import get_setting
|
|
|
|
|
|
COLORS = {
|
|
"backup_started": 0x3498DB,
|
|
"pve_completed": 0x2ECC71,
|
|
"upload_started": 0x9B59B6,
|
|
"backup_completed": 0x2ECC71,
|
|
"backup_failed": 0xE74C3C,
|
|
"remote_deleted": 0xF39C12,
|
|
"recovery_action": 0x95A5A6,
|
|
}
|
|
|
|
|
|
def _field(name: str, value: Any) -> dict:
|
|
return {"name": name, "value": str(value if value is not None else "-"), "inline": True}
|
|
|
|
|
|
def _duration_seconds(backup: dict) -> int | None:
|
|
started = backup.get("started_at")
|
|
ended = backup.get("completed_at") or backup.get("deleted_at") or backup.get("pve_completed_at")
|
|
if not started or not ended:
|
|
return None
|
|
try:
|
|
return int((datetime.fromisoformat(ended) - datetime.fromisoformat(started)).total_seconds())
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
async def notify(event_type: str, *, backup: dict | None = None, message: str = "") -> None:
|
|
webhook_url = get_setting("discord_webhook_url", "")
|
|
fields = []
|
|
backup_id = None
|
|
if backup:
|
|
backup_id = backup.get("id")
|
|
duration = _duration_seconds(backup)
|
|
enriched = {**backup, "duration_seconds": duration}
|
|
for key in ("guest_name", "guest_vmid", "id", "state", "size_bytes", "duration_seconds", "error_message"):
|
|
if enriched.get(key) not in (None, ""):
|
|
fields.append(_field(key, enriched.get(key)))
|
|
if message:
|
|
fields.append(_field("message", message))
|
|
payload = {
|
|
"embeds": [
|
|
{
|
|
"title": event_type.replace("_", " ").title(),
|
|
"color": COLORS.get(event_type, 0x3498DB),
|
|
"timestamp": utc_now(),
|
|
"fields": fields,
|
|
}
|
|
]
|
|
}
|
|
success = False
|
|
error_message = None
|
|
if webhook_url:
|
|
try:
|
|
async with httpx.AsyncClient(timeout=15) as client:
|
|
response = await client.post(webhook_url, json=payload)
|
|
response.raise_for_status()
|
|
success = True
|
|
except Exception as exc: # Notifications must never fail backups.
|
|
error_message = str(exc)
|
|
with db() as conn:
|
|
conn.execute(
|
|
"""
|
|
INSERT INTO notification_logs(event_type, backup_id, payload, success, error_message, created_at)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
""",
|
|
(event_type, backup_id, json.dumps(payload), int(success), error_message, utc_now()),
|
|
)
|