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"; export function SettingsDialog({ open, onOpenChange }: { open: boolean; onOpenChange: (o: boolean) => void }) { const board = useBoard(); const fileRef = useRef(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"); if (!res.ok) return; const blob = await res.blob(); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "dash-export.json"; a.click(); URL.revokeObjectURL(a.href); } async function importJson(file: File) { setMsg(""); // Dash exports are JSON with {version, sections}; anything else // (Homepage/Homarr/Dashy) goes to the external importer for sniffing. let target = "/api/import/external"; try { const j: unknown = JSON.parse(await file.text()); if (j !== null && typeof j === "object" && "version" in j && "sections" in j) { target = "/api/import"; } } catch { /* not JSON — external importer handles YAML */ } const res = await fetch(target, { method: "POST", body: file }); if (res.ok) { location.reload(); // import replaces state; a reload re-syncs everything } else { // SAFETY: error responses always carry {error: string} per openapi.yaml Error schema. const body = (await res.json().catch(() => null)) as { error?: string } | null; setMsg(body?.error ?? "Import failed"); } } return ( Settings Board layout, theme, and backup.
{rendererList.map((r) => ( ))}
{ setWebhook(e.target.value); setWebhookMsg(""); }} placeholder="https://ntfy.sh/dash or Slack/Discord hook" className="font-mono text-[12px]" />

Monitor up/down flips and domain/certificate expiry post JSON here. Works with Slack, Discord, ntfy, or any JSON receiver.

{webhookMsg ?

{webhookMsg}

: null}
{ const f = e.target.files?.[0]; if (f) void importJson(f); e.target.value = ""; }} />

Import accepts Dash exports plus Homepage, Homarr, and Dashy configs.

{msg ?

{msg}

: null}
); }