import Button from '@app/components/Common/Button'; import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import SensitiveInput from '@app/components/Common/SensitiveInput'; import NotificationTypeSelector from '@app/components/NotificationTypeSelector'; import globalMessages from '@app/i18n/globalMessages'; import defineMessages from '@app/utils/defineMessages'; import { isValidURL } from '@app/utils/urlValidationHelper'; import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline'; import type { NotificationAgentNtfy } from '@server/lib/settings'; import axios from 'axios'; import { Field, Form, Formik } from 'formik'; import { useState } from 'react'; import { useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; import useSWR from 'swr'; import * as Yup from 'yup'; const messages = defineMessages( 'components.Settings.Notifications.NotificationsNtfy', { agentenabled: 'Enable Agent', embedPoster: 'Embed Poster', url: 'Server root URL', topic: 'Topic', usernamePasswordAuth: 'Username + Password authentication', username: 'Username', password: 'Password', tokenAuth: 'Token authentication', token: 'Token', priority: 'Priority', ntfysettingssaved: 'Ntfy notification settings saved successfully!', ntfysettingsfailed: 'Ntfy notification settings failed to save.', toastNtfyTestSending: 'Sending ntfy test notification…', toastNtfyTestSuccess: 'Ntfy test notification sent!', toastNtfyTestFailed: 'Ntfy test notification failed to send.', validationNtfyUrl: 'You must provide a valid URL', validationNtfyTopic: 'You must provide a topic', validationPriorityRequired: 'You must provide a priority between 1 and 5', validationTypes: 'You must select at least one notification type', } ); const NotificationsNtfy = () => { const intl = useIntl(); const { addToast, removeToast } = useToasts(); const [isTesting, setIsTesting] = useState(false); const { data, error, mutate: revalidate, } = useSWR('/api/v1/settings/notifications/ntfy'); const NotificationsNtfySchema = Yup.object().shape({ url: Yup.string() .when('enabled', { is: true, then: Yup.string() .nullable() .required(intl.formatMessage(messages.validationNtfyUrl)), otherwise: Yup.string().nullable(), }) .test( 'valid-url', intl.formatMessage(messages.validationNtfyUrl), isValidURL ), topic: Yup.string() .when('enabled', { is: true, then: Yup.string() .nullable() .required(intl.formatMessage(messages.validationNtfyUrl)), otherwise: Yup.string().nullable(), }) .defined(intl.formatMessage(messages.validationNtfyTopic)), priority: Yup.number().when('enabled', { is: true, then: Yup.number() .min(1) .max(5) .required(intl.formatMessage(messages.validationPriorityRequired)), otherwise: Yup.number().nullable(), }), }); if (!data && !error) { return ; } return ( { try { await axios.post('/api/v1/settings/notifications/ntfy', { enabled: values.enabled, embedPoster: values.embedPoster, types: values.types, options: { url: values.url, topic: values.topic, authMethodUsernamePassword: values.authMethodUsernamePassword, username: values.username, password: values.password, authMethodToken: values.authMethodToken, token: values.token, priority: values.priority, }, }); addToast(intl.formatMessage(messages.ntfysettingssaved), { appearance: 'success', autoDismiss: true, }); } catch { addToast(intl.formatMessage(messages.ntfysettingsfailed), { appearance: 'error', autoDismiss: true, }); } finally { revalidate(); } }} > {({ errors, touched, isSubmitting, values, isValid, setFieldValue, setFieldTouched, }) => { const testSettings = async () => { setIsTesting(true); let toastId: string | undefined; try { addToast( intl.formatMessage(messages.toastNtfyTestSending), { autoDismiss: false, appearance: 'info', }, (id) => { toastId = id; } ); await axios.post('/api/v1/settings/notifications/ntfy/test', { enabled: true, types: values.types, options: { url: values.url, topic: values.topic, authMethodUsernamePassword: values.authMethodUsernamePassword, username: values.username, password: values.password, authMethodToken: values.authMethodToken, token: values.token, priority: values.priority, }, }); if (toastId) { removeToast(toastId); } addToast(intl.formatMessage(messages.toastNtfyTestSuccess), { autoDismiss: true, appearance: 'success', }); } catch { if (toastId) { removeToast(toastId); } addToast(intl.formatMessage(messages.toastNtfyTestFailed), { autoDismiss: true, appearance: 'error', }); } finally { setIsTesting(false); } }; return (
{errors.url && touched.url && typeof errors.url === 'string' && (
{errors.url}
)}
{errors.topic && touched.topic && typeof errors.topic === 'string' && (
{errors.topic}
)}
{ setFieldValue( 'authMethodUsernamePassword', !values.authMethodUsernamePassword ); }} />
{values.authMethodUsernamePassword && (
)}
{ setFieldValue('authMethodToken', !values.authMethodToken); }} />
{values.authMethodToken && (
)}
{ setFieldValue('types', newTypes); setFieldTouched('types'); if (newTypes) { setFieldValue('enabled', true); } }} error={ values.enabled && !values.types && touched.types ? intl.formatMessage(messages.validationTypes) : undefined } />
); }}
); }; export default NotificationsNtfy;