(feature) Add run completion notifications
Add email and webhook notification targets with delivery tracking, and send notifications when backup runs reach a terminal status. Expose notification target management in the Django UI and keep delivery failures recorded without failing the backup worker.
This commit is contained in:
@@ -9,7 +9,7 @@ from tempfile import TemporaryDirectory
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
|
||||
from .models import GlobalConfig, HostConfig, ScheduleConfig, SshCredential
|
||||
from .models import BackupRun, GlobalConfig, HostConfig, NotificationTarget, ScheduleConfig, SshCredential
|
||||
from .scheduler import parse_cron_expr
|
||||
|
||||
|
||||
@@ -153,6 +153,62 @@ class ManualBackupForm(forms.Form):
|
||||
)
|
||||
|
||||
|
||||
class NotificationTargetForm(forms.ModelForm):
|
||||
TERMINAL_STATUS_CHOICES = (
|
||||
(BackupRun.Status.SUCCESS, BackupRun.Status.SUCCESS.label),
|
||||
(BackupRun.Status.WARNING, BackupRun.Status.WARNING.label),
|
||||
(BackupRun.Status.FAILED, BackupRun.Status.FAILED.label),
|
||||
(BackupRun.Status.CANCELLED, BackupRun.Status.CANCELLED.label),
|
||||
)
|
||||
|
||||
statuses = forms.MultipleChoiceField(
|
||||
choices=TERMINAL_STATUS_CHOICES,
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
initial=[choice[0] for choice in TERMINAL_STATUS_CHOICES],
|
||||
help_text="Send notifications for these terminal run statuses.",
|
||||
)
|
||||
email_to = forms.CharField(
|
||||
widget=forms.Textarea,
|
||||
required=False,
|
||||
help_text="One recipient per line, or comma-separated.",
|
||||
)
|
||||
webhook_headers = forms.JSONField(
|
||||
required=False,
|
||||
widget=forms.Textarea(attrs={"rows": 4}),
|
||||
help_text='Optional JSON object with extra headers, for example {"Authorization": "Bearer ..."}.',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = NotificationTarget
|
||||
fields = (
|
||||
"name",
|
||||
"enabled",
|
||||
"channel",
|
||||
"statuses",
|
||||
"email_to",
|
||||
"webhook_url",
|
||||
"webhook_headers",
|
||||
"notes",
|
||||
)
|
||||
widgets = {
|
||||
"notes": forms.Textarea,
|
||||
}
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
channel = cleaned_data.get("channel")
|
||||
if channel == NotificationTarget.Channel.EMAIL and not cleaned_data.get("email_to", "").strip():
|
||||
self.add_error("email_to", "Email targets need at least one recipient.")
|
||||
if channel == NotificationTarget.Channel.WEBHOOK and not cleaned_data.get("webhook_url"):
|
||||
self.add_error("webhook_url", "Webhook targets need a URL.")
|
||||
return cleaned_data
|
||||
|
||||
def clean_email_to(self) -> str:
|
||||
value = self.cleaned_data.get("email_to", "")
|
||||
recipients = [line.strip() for line in value.replace(",", "\n").splitlines() if line.strip()]
|
||||
return "\n".join(recipients)
|
||||
|
||||
|
||||
class SshCredentialForm(forms.ModelForm):
|
||||
private_key_file = forms.FileField(
|
||||
required=False,
|
||||
|
||||
Reference in New Issue
Block a user