mirror of
https://github.com/henrygd/beszel.git
synced 2025-12-17 10:46:16 +01:00
241 lines
6.8 KiB
Go
241 lines
6.8 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
|