mirror of
https://github.com/henrygd/beszel.git
synced 2025-12-18 03:06:16 +01:00
216 lines
6.7 KiB
Go
216 lines
6.7 KiB
Go
import { Button } from "@/components/ui/button"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Label } from "@/components/ui/label"
|
|
import { pb } from "@/lib/stores"
|
|
import { Separator } from "@/components/ui/separator"
|
|
import { Card } from "@/components/ui/card"
|
|
import { BellIcon, LoaderCircleIcon, PlusIcon, SaveIcon, Trash2Icon } from "lucide-react"
|
|
import { ChangeEventHandler, useEffect, useState } from "react"
|
|
import { toast } from "@/components/ui/use-toast"
|
|
import { InputTags } from "@/components/ui/input-tags"
|
|
import { UserSettings } from "@/types"
|
|
import { saveSettings } from "./layout"
|
|
import * as v from "valibot"
|
|
import { isAdmin } from "@/lib/utils"
|
|
import { useTranslation } from "react-i18next"
|
|
|
|
interface ShoutrrrUrlCardProps {
|
|
url: string
|
|
onUrlChange: ChangeEventHandler<HTMLInputElement>
|
|
onRemove: () => void
|
|
}
|
|
|
|
const NotificationSchema = v.object({
|
|
emails: v.array(v.pipe(v.string(), v.email())),
|
|
webhooks: v.array(v.pipe(v.string(), v.url())),
|
|
})
|
|
|
|
const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSettings }) => {
|
|
const { t } = useTranslation()
|
|
|
|
const [webhooks, setWebhooks] = useState(userSettings.webhooks ?? [])
|
|
const [emails, setEmails] = useState<string[]>(userSettings.emails ?? [])
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
|
|
// update values when userSettings changes
|
|
useEffect(() => {
|
|
setWebhooks(userSettings.webhooks ?? [])
|
|
setEmails(userSettings.emails ?? [])
|
|
}, [userSettings])
|
|
|
|
function addWebhook() {
|
|
setWebhooks([...webhooks, ""])
|
|
// focus on the new input
|
|
queueMicrotask(() => {
|
|
const inputs = document.querySelectorAll("#webhooks input") as NodeListOf<HTMLInputElement>
|
|
inputs[inputs.length - 1]?.focus()
|
|
})
|
|
}
|
|
const removeWebhook = (index: number) => setWebhooks(webhooks.filter((_, i) => i !== index))
|
|
|
|
function updateWebhook(index: number, value: string) {
|
|
const newWebhooks = [...webhooks]
|
|
newWebhooks[index] = value
|
|
setWebhooks(newWebhooks)
|
|
}
|
|
|
|
async function updateSettings() {
|
|
setIsLoading(true)
|
|
try {
|
|
const parsedData = v.parse(NotificationSchema, { emails, webhooks })
|
|
await saveSettings(parsedData)
|
|
} catch (e: any) {
|
|
toast({
|
|
title: "Failed to save settings",
|
|
description: e.message,
|
|
variant: "destructive",
|
|
})
|
|
}
|
|
setIsLoading(false)
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<div>
|
|
<h3 className="text-xl font-medium mb-2">{t("settings.notifications.title")}</h3>
|
|
<p className="text-sm text-muted-foreground leading-relaxed">{t("settings.notifications.subtitle_1")}</p>
|
|
<p className="text-sm text-muted-foreground mt-1.5 leading-relaxed">
|
|
{t("settings.notifications.subtitle_2")} <BellIcon className="inline h-4 w-4" />{" "}
|
|
{t("settings.notifications.subtitle_3")}
|
|
</p>
|
|
</div>
|
|
<Separator className="my-4" />
|
|
<div className="space-y-5">
|
|
<div className="space-y-2">
|
|
<div className="mb-4">
|
|
<h3 className="mb-1 text-lg font-medium">{t("settings.notifications.email.title")}</h3>
|
|
{isAdmin() && (
|
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
{t("settings.notifications.email.please")}{" "}
|
|
<a href="/_/#/settings/mail" className="link" target="_blank">
|
|
{t("settings.notifications.email.configure_an_SMTP_server")}
|
|
</a>{" "}
|
|
{t("settings.notifications.email.to_ensure_alerts_are_delivered")}{" "}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<Label className="block" htmlFor="email">
|
|
{t("settings.notifications.email.to_email_s")}
|
|
</Label>
|
|
<InputTags
|
|
value={emails}
|
|
onChange={setEmails}
|
|
placeholder={t("settings.notifications.email.enter_email_address")}
|
|
className="w-full"
|
|
type="email"
|
|
id="email"
|
|
/>
|
|
<p className="text-[0.8rem] text-muted-foreground">{t("settings.notifications.email.des")}</p>
|
|
</div>
|
|
<Separator />
|
|
<div className="space-y-3">
|
|
<div>
|
|
<h3 className="mb-1 text-lg font-medium">{t("settings.notifications.webhook_push.title")}</h3>
|
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
{t("settings.notifications.webhook_push.des_1")}{" "}
|
|
<a href="https://containrrr.dev/shoutrrr/services/overview/" target="_blank" className="link">
|
|
Shoutrrr
|
|
</a>{" "}
|
|
{t("settings.notifications.webhook_push.des_2")}
|
|
</p>
|
|
</div>
|
|
{webhooks.length > 0 && (
|
|
<div className="grid gap-2.5" id="webhooks">
|
|
{webhooks.map((webhook, index) => (
|
|
<ShoutrrrUrlCard
|
|
key={index}
|
|
url={webhook}
|
|
onUrlChange={(e: React.ChangeEvent<HTMLInputElement>) => updateWebhook(index, e.target.value)}
|
|
onRemove={() => removeWebhook(index)}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
size="sm"
|
|
className="mt-2 flex items-center gap-1"
|
|
onClick={addWebhook}
|
|
>
|
|
<PlusIcon className="h-4 w-4 -ml-0.5" />
|
|
{t("settings.notifications.webhook_push.add_url")}
|
|
</Button>
|
|
</div>
|
|
<Separator />
|
|
<Button
|
|
type="button"
|
|
className="flex items-center gap-1.5 disabled:opacity-100"
|
|
onClick={updateSettings}
|
|
disabled={isLoading}
|
|
>
|
|
{isLoading ? <LoaderCircleIcon className="h-4 w-4 animate-spin" /> : <SaveIcon className="h-4 w-4" />}
|
|
{t("settings.save_settings")}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const ShoutrrrUrlCard = ({ url, onUrlChange, onRemove }: ShoutrrrUrlCardProps) => {
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
|
|
const sendTestNotification = async () => {
|
|
setIsLoading(true)
|
|
const res = await pb.send("/api/beszel/send-test-notification", { url })
|
|
if ("err" in res && !res.err) {
|
|
toast({
|
|
title: "Test notification sent",
|
|
description: "Check your notification service",
|
|
})
|
|
} else {
|
|
toast({
|
|
title: "Error",
|
|
description: res.err ?? "Failed to send test notification",
|
|
variant: "destructive",
|
|
})
|
|
}
|
|
setIsLoading(false)
|
|
}
|
|
|
|
return (
|
|
<Card className="bg-muted/30 p-2 md:p-3">
|
|
<div className="flex items-center gap-1">
|
|
<Input
|
|
type="url"
|
|
className="light:bg-card"
|
|
required
|
|
placeholder="generic://webhook.site/xxxxxx"
|
|
value={url}
|
|
onChange={onUrlChange}
|
|
/>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
className="w-20 md:w-28"
|
|
disabled={isLoading || url === ""}
|
|
onClick={sendTestNotification}
|
|
>
|
|
{isLoading ? (
|
|
<LoaderCircleIcon className="h-4 w-4 animate-spin" />
|
|
) : (
|
|
<span>
|
|
Test <span className="hidden md:inline">URL</span>
|
|
</span>
|
|
)}
|
|
</Button>
|
|
<Button type="button" variant="outline" size="icon" className="shrink-0" aria-label="Delete" onClick={onRemove}>
|
|
<Trash2Icon className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
export default SettingsNotificationsPage
|