Initial pve cloud backup app
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
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()),
|
||||
)
|
||||
Reference in New Issue
Block a user