mirror of
https://github.com/Dvorinka/Dash.git
synced 2026-10-28 15:51:27 +00:00
feat: webhook alerts for monitor + domain events (merge plan M2)
Generic webhook notification delivery configured via the notify_webhook settings key — no new schema. Fires on monitor up/down transitions (pending stays silent), domain expiry, domain expiring, and TLS cert expiring; dispatch is async and failures are logged, never fatal. POST /api/notify/test verifies configuration; Settings dialog gains a webhook URL field with save + send-test. Payload carries event/name/text/content/message so Slack, Discord, ntfy, and custom receivers all work. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
f9fd1d9cf2
commit
2f3e8a1879
7 files changed
+255
-4
No files matched your search
@@ -1,12 +1,14 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { Download, Upload } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Bell, Download, Upload } from "lucide-react";
|
||||
import { useBoard } from "@/board/store";
|
||||
import { api } from "@/api";
|
||||
import { rendererDescriptions, rendererLabels, rendererList } from "@/renderers";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
|
||||
@@ -14,6 +16,23 @@ export function SettingsDialog({ open, onOpenChange }: { open: boolean; onOpenCh
|
||||
const board = useBoard();
|
||||
const fileRef = useRef<HTMLInputElement>(null);
|
||||
const [msg, setMsg] = useState("");
|
||||
const [webhook, setWebhook] = useState("");
|
||||
const [webhookMsg, setWebhookMsg] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
void api.GET("/api/settings").then(({ data }) => {
|
||||
const v = data?.notify_webhook;
|
||||
setWebhook(typeof v === "string" ? v : "");
|
||||
setWebhookMsg("");
|
||||
});
|
||||
}, [open]);
|
||||
|
||||
async function saveWebhook() {
|
||||
setWebhookMsg("");
|
||||
await api.PUT("/api/settings", { body: { notify_webhook: webhook.trim() } });
|
||||
setWebhookMsg("saved");
|
||||
}
|
||||
|
||||
async function exportJson() {
|
||||
const res = await fetch("/api/export");
|
||||
@@ -93,6 +112,38 @@ export function SettingsDialog({ open, onOpenChange }: { open: boolean; onOpenCh
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label htmlFor="notify-webhook">Notification webhook</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
id="notify-webhook"
|
||||
value={webhook}
|
||||
onChange={(e) => { setWebhook(e.target.value); setWebhookMsg(""); }}
|
||||
placeholder="https://ntfy.sh/dash or Slack/Discord hook"
|
||||
className="font-mono text-[12px]"
|
||||
/>
|
||||
<Button type="button" variant="outline" size="sm" onClick={() => void saveWebhook()}>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
type="button" variant="outline" size="icon" aria-label="Send test notification"
|
||||
disabled={!webhook.trim()}
|
||||
onClick={() => {
|
||||
setWebhookMsg("");
|
||||
void api.POST("/api/notify/test").then(({ error }) =>
|
||||
setWebhookMsg(error ? "failed" : "sent"));
|
||||
}}
|
||||
>
|
||||
<Bell size={12} />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-[11px] text-text-faint">
|
||||
Monitor up/down flips and domain/certificate expiry post JSON here.
|
||||
Works with Slack, Discord, ntfy, or any JSON receiver.
|
||||
</p>
|
||||
{webhookMsg ? <p className="text-[11px] text-text-faint">{webhookMsg}</p> : null}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 pt-1">
|
||||
<Button type="button" variant="outline" size="sm" onClick={() => void exportJson()}>
|
||||
<Download size={12} /> Export JSON
|
||||
|
||||
Reference in new issue
Block a user