feat(notif): show success/failure toast for test notifications (#1442)

* feat(notif): show success/failure toast for test notifications

* fix(lang): remove 'successfully' from test notif success strings
pull/1464/head
TheCatLady 3 years ago committed by GitHub
parent 4faddf3810
commit 079645c2c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -272,7 +272,7 @@ class DiscordAgent
type: Notification[type], type: Notification[type],
subject: payload.subject, subject: payload.subject,
errorMessage: e.message, errorMessage: e.message,
response: e.response.data, response: e.response?.data,
}); });
return false; return false;

@ -170,7 +170,7 @@ class PushbulletAgent
type: Notification[type], type: Notification[type],
subject: payload.subject, subject: payload.subject,
errorMessage: e.message, errorMessage: e.message,
response: e.response.data, response: e.response?.data,
}); });
return false; return false;

@ -196,7 +196,7 @@ class PushoverAgent
type: Notification[type], type: Notification[type],
subject: payload.subject, subject: payload.subject,
errorMessage: e.message, errorMessage: e.message,
response: e.response.data, response: e.response?.data,
}); });
return false; return false;

@ -254,7 +254,7 @@ class SlackAgent
type: Notification[type], type: Notification[type],
subject: payload.subject, subject: payload.subject,
errorMessage: e.message, errorMessage: e.message,
response: e.response.data, response: e.response?.data,
}); });
return false; return false;

@ -244,7 +244,7 @@ class TelegramAgent
type: Notification[type], type: Notification[type],
subject: payload.subject, subject: payload.subject,
errorMessage: e.message, errorMessage: e.message,
response: e.response.data, response: e.response?.data,
}); });
return false; return false;

@ -154,7 +154,7 @@ class WebhookAgent
type: Notification[type], type: Notification[type],
subject: payload.subject, subject: payload.subject,
errorMessage: e.message, errorMessage: e.message,
response: e.response.data, response: e.response?.data,
}); });
return false; return false;

@ -28,23 +28,30 @@ notificationRoutes.post('/discord', (req, res) => {
res.status(200).json(settings.notifications.agents.discord); res.status(200).json(settings.notifications.agents.discord);
}); });
notificationRoutes.post('/discord/test', (req, res, next) => { notificationRoutes.post('/discord/test', async (req, res, next) => {
if (!req.user) { if (!req.user) {
return next({ return next({
status: 500, status: 500,
message: 'User information missing from request', message: 'User information is missing from the request.',
}); });
} }
const discordAgent = new DiscordAgent(req.body); const discordAgent = new DiscordAgent(req.body);
discordAgent.send(Notification.TEST_NOTIFICATION, { if (
notifyUser: req.user, await discordAgent.send(Notification.TEST_NOTIFICATION, {
subject: 'Test Notification', notifyUser: req.user,
message: subject: 'Test Notification',
'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', message:
}); 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?',
})
return res.status(204).send(); ) {
return res.status(204).send();
} else {
return next({
status: 500,
message: 'Failed to send Discord notification.',
});
}
}); });
notificationRoutes.get('/slack', (_req, res) => { notificationRoutes.get('/slack', (_req, res) => {
@ -62,23 +69,30 @@ notificationRoutes.post('/slack', (req, res) => {
res.status(200).json(settings.notifications.agents.slack); res.status(200).json(settings.notifications.agents.slack);
}); });
notificationRoutes.post('/slack/test', (req, res, next) => { notificationRoutes.post('/slack/test', async (req, res, next) => {
if (!req.user) { if (!req.user) {
return next({ return next({
status: 500, status: 500,
message: 'User information missing from request', message: 'User information is missing from the request.',
}); });
} }
const slackAgent = new SlackAgent(req.body); const slackAgent = new SlackAgent(req.body);
slackAgent.send(Notification.TEST_NOTIFICATION, { if (
notifyUser: req.user, await slackAgent.send(Notification.TEST_NOTIFICATION, {
subject: 'Test Notification', notifyUser: req.user,
message: subject: 'Test Notification',
'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', message:
}); 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?',
})
return res.status(204).send(); ) {
return res.status(204).send();
} else {
return next({
status: 500,
message: 'Failed to send Slack notification.',
});
}
}); });
notificationRoutes.get('/telegram', (_req, res) => { notificationRoutes.get('/telegram', (_req, res) => {
@ -96,23 +110,30 @@ notificationRoutes.post('/telegram', (req, res) => {
res.status(200).json(settings.notifications.agents.telegram); res.status(200).json(settings.notifications.agents.telegram);
}); });
notificationRoutes.post('/telegram/test', (req, res, next) => { notificationRoutes.post('/telegram/test', async (req, res, next) => {
if (!req.user) { if (!req.user) {
return next({ return next({
status: 500, status: 500,
message: 'User information missing from request', message: 'User information is missing from the request.',
}); });
} }
const telegramAgent = new TelegramAgent(req.body); const telegramAgent = new TelegramAgent(req.body);
telegramAgent.send(Notification.TEST_NOTIFICATION, { if (
notifyUser: req.user, await telegramAgent.send(Notification.TEST_NOTIFICATION, {
subject: 'Test Notification', notifyUser: req.user,
message: subject: 'Test Notification',
'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', message:
}); 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?',
})
return res.status(204).send(); ) {
return res.status(204).send();
} else {
return next({
status: 500,
message: 'Failed to send Telegram notification.',
});
}
}); });
notificationRoutes.get('/pushbullet', (_req, res) => { notificationRoutes.get('/pushbullet', (_req, res) => {
@ -130,23 +151,30 @@ notificationRoutes.post('/pushbullet', (req, res) => {
res.status(200).json(settings.notifications.agents.pushbullet); res.status(200).json(settings.notifications.agents.pushbullet);
}); });
notificationRoutes.post('/pushbullet/test', (req, res, next) => { notificationRoutes.post('/pushbullet/test', async (req, res, next) => {
if (!req.user) { if (!req.user) {
return next({ return next({
status: 500, status: 500,
message: 'User information missing from request', message: 'User information is missing from the request.',
}); });
} }
const pushbulletAgent = new PushbulletAgent(req.body); const pushbulletAgent = new PushbulletAgent(req.body);
pushbulletAgent.send(Notification.TEST_NOTIFICATION, { if (
notifyUser: req.user, await pushbulletAgent.send(Notification.TEST_NOTIFICATION, {
subject: 'Test Notification', notifyUser: req.user,
message: subject: 'Test Notification',
'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', message:
}); 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?',
})
return res.status(204).send(); ) {
return res.status(204).send();
} else {
return next({
status: 500,
message: 'Failed to send Pushbullet notification.',
});
}
}); });
notificationRoutes.get('/pushover', (_req, res) => { notificationRoutes.get('/pushover', (_req, res) => {
@ -164,23 +192,30 @@ notificationRoutes.post('/pushover', (req, res) => {
res.status(200).json(settings.notifications.agents.pushover); res.status(200).json(settings.notifications.agents.pushover);
}); });
notificationRoutes.post('/pushover/test', (req, res, next) => { notificationRoutes.post('/pushover/test', async (req, res, next) => {
if (!req.user) { if (!req.user) {
return next({ return next({
status: 500, status: 500,
message: 'User information missing from request', message: 'User information is missing from the request.',
}); });
} }
const pushoverAgent = new PushoverAgent(req.body); const pushoverAgent = new PushoverAgent(req.body);
pushoverAgent.send(Notification.TEST_NOTIFICATION, { if (
notifyUser: req.user, await pushoverAgent.send(Notification.TEST_NOTIFICATION, {
subject: 'Test Notification', notifyUser: req.user,
message: subject: 'Test Notification',
'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', message:
}); 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?',
})
return res.status(204).send(); ) {
return res.status(204).send();
} else {
return next({
status: 500,
message: 'Failed to send Pushover notification.',
});
}
}); });
notificationRoutes.get('/email', (_req, res) => { notificationRoutes.get('/email', (_req, res) => {
@ -198,23 +233,30 @@ notificationRoutes.post('/email', (req, res) => {
res.status(200).json(settings.notifications.agents.email); res.status(200).json(settings.notifications.agents.email);
}); });
notificationRoutes.post('/email/test', (req, res, next) => { notificationRoutes.post('/email/test', async (req, res, next) => {
if (!req.user) { if (!req.user) {
return next({ return next({
status: 500, status: 500,
message: 'User information missing from request', message: 'User information is missing from the request.',
}); });
} }
const emailAgent = new EmailAgent(req.body); const emailAgent = new EmailAgent(req.body);
emailAgent.send(Notification.TEST_NOTIFICATION, { if (
notifyUser: req.user, await emailAgent.send(Notification.TEST_NOTIFICATION, {
subject: 'Test Notification', notifyUser: req.user,
message: subject: 'Test Notification',
'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', message:
}); 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?',
})
return res.status(204).send(); ) {
return res.status(204).send();
} else {
return next({
status: 500,
message: 'Failed to send email notification.',
});
}
}); });
notificationRoutes.get('/webpush', (_req, res) => { notificationRoutes.get('/webpush', (_req, res) => {
@ -232,7 +274,7 @@ notificationRoutes.post('/webpush', (req, res) => {
res.status(200).json(settings.notifications.agents.webpush); res.status(200).json(settings.notifications.agents.webpush);
}); });
notificationRoutes.post('/webpush/test', (req, res, next) => { notificationRoutes.post('/webpush/test', async (req, res, next) => {
if (!req.user) { if (!req.user) {
return next({ return next({
status: 500, status: 500,
@ -241,14 +283,21 @@ notificationRoutes.post('/webpush/test', (req, res, next) => {
} }
const webpushAgent = new WebPushAgent(req.body); const webpushAgent = new WebPushAgent(req.body);
webpushAgent.send(Notification.TEST_NOTIFICATION, { if (
notifyUser: req.user, await webpushAgent.send(Notification.TEST_NOTIFICATION, {
subject: 'Test Notification', notifyUser: req.user,
message: subject: 'Test Notification',
'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', message:
}); 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?',
})
return res.status(204).send(); ) {
return res.status(204).send();
} else {
return next({
status: 500,
message: 'Failed to send web push notification.',
});
}
}); });
notificationRoutes.get('/webhook', (_req, res) => { notificationRoutes.get('/webhook', (_req, res) => {
@ -296,11 +345,11 @@ notificationRoutes.post('/webhook', (req, res, next) => {
} }
}); });
notificationRoutes.post('/webhook/test', (req, res, next) => { notificationRoutes.post('/webhook/test', async (req, res, next) => {
if (!req.user) { if (!req.user) {
return next({ return next({
status: 500, status: 500,
message: 'User information missing from request', message: 'User information is missing from the request.',
}); });
} }
@ -320,14 +369,21 @@ notificationRoutes.post('/webhook/test', (req, res, next) => {
}; };
const webhookAgent = new WebhookAgent(testBody); const webhookAgent = new WebhookAgent(testBody);
webhookAgent.send(Notification.TEST_NOTIFICATION, { if (
notifyUser: req.user, await webhookAgent.send(Notification.TEST_NOTIFICATION, {
subject: 'Test Notification', notifyUser: req.user,
message: subject: 'Test Notification',
'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', message:
}); 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?',
})
return res.status(204).send(); ) {
return res.status(204).send();
} else {
return next({
status: 500,
message: 'Failed to send webhook notification.',
});
}
} catch (e) { } catch (e) {
next({ status: 500, message: e.message }); next({ status: 500, message: e.message });
} }
@ -348,7 +404,7 @@ notificationRoutes.post('/lunasea', (req, res) => {
res.status(200).json(settings.notifications.agents.lunasea); res.status(200).json(settings.notifications.agents.lunasea);
}); });
notificationRoutes.post('/lunasea/test', (req, res, next) => { notificationRoutes.post('/lunasea/test', async (req, res, next) => {
if (!req.user) { if (!req.user) {
return next({ return next({
status: 500, status: 500,
@ -357,14 +413,21 @@ notificationRoutes.post('/lunasea/test', (req, res, next) => {
} }
const lunaseaAgent = new LunaSeaAgent(req.body); const lunaseaAgent = new LunaSeaAgent(req.body);
lunaseaAgent.send(Notification.TEST_NOTIFICATION, { if (
notifyUser: req.user, await lunaseaAgent.send(Notification.TEST_NOTIFICATION, {
subject: 'Test Notification', notifyUser: req.user,
message: subject: 'Test Notification',
'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?', message:
}); 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?',
})
return res.status(204).send(); ) {
return res.status(204).send();
} else {
return next({
status: 500,
message: 'Failed to send web push notification.',
});
}
}); });
export default notificationRoutes; export default notificationRoutes;

@ -1,6 +1,6 @@
import axios from 'axios'; import axios from 'axios';
import { Field, Form, Formik } from 'formik'; import { Field, Form, Formik } from 'formik';
import React from 'react'; import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr'; import useSWR from 'swr';
@ -18,13 +18,16 @@ const messages = defineMessages({
webhookUrlPlaceholder: 'Server Settings → Integrations → Webhooks', webhookUrlPlaceholder: 'Server Settings → Integrations → Webhooks',
discordsettingssaved: 'Discord notification settings saved successfully!', discordsettingssaved: 'Discord notification settings saved successfully!',
discordsettingsfailed: 'Discord notification settings failed to save.', discordsettingsfailed: 'Discord notification settings failed to save.',
discordtestsent: 'Discord test notification sent!', toastDiscordTestSending: 'Sending Discord test notification…',
toastDiscordTestSuccess: 'Discord test notification sent!',
toastDiscordTestFailed: 'Discord test notification failed to send.',
validationUrl: 'You must provide a valid URL', validationUrl: 'You must provide a valid URL',
}); });
const NotificationsDiscord: React.FC = () => { const NotificationsDiscord: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const { addToast } = useToasts(); const { addToast, removeToast } = useToasts();
const [isTesting, setIsTesting] = useState(false);
const { data, error, revalidate } = useSWR( const { data, error, revalidate } = useSWR(
'/api/v1/settings/notifications/discord' '/api/v1/settings/notifications/discord'
); );
@ -86,20 +89,47 @@ const NotificationsDiscord: React.FC = () => {
> >
{({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => { {({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => {
const testSettings = async () => { const testSettings = async () => {
await axios.post('/api/v1/settings/notifications/discord/test', { setIsTesting(true);
enabled: true, let toastId: string | undefined;
types: values.types, try {
options: { addToast(
botUsername: values.botUsername, intl.formatMessage(messages.toastDiscordTestSending),
botAvatarUrl: values.botAvatarUrl, {
webhookUrl: values.webhookUrl, autoDismiss: false,
}, appearance: 'info',
}); },
(id) => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/discord/test', {
enabled: true,
types: values.types,
options: {
botUsername: values.botUsername,
botAvatarUrl: values.botAvatarUrl,
webhookUrl: values.webhookUrl,
},
});
addToast(intl.formatMessage(messages.discordtestsent), { if (toastId) {
appearance: 'info', removeToast(toastId);
autoDismiss: true, }
}); addToast(intl.formatMessage(messages.toastDiscordTestSuccess), {
autoDismiss: true,
appearance: 'success',
});
} catch (e) {
if (toastId) {
removeToast(toastId);
}
addToast(intl.formatMessage(messages.toastDiscordTestFailed), {
autoDismiss: true,
appearance: 'error',
});
} finally {
setIsTesting(false);
}
}; };
return ( return (
@ -178,21 +208,22 @@ const NotificationsDiscord: React.FC = () => {
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="warning" buttonType="warning"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid || isTesting}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
testSettings(); testSettings();
}} }}
> >
{intl.formatMessage(globalMessages.test)} {isTesting
? intl.formatMessage(globalMessages.testing)
: intl.formatMessage(globalMessages.test)}
</Button> </Button>
</span> </span>
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="primary" buttonType="primary"
type="submit" type="submit"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid || isTesting}
> >
{isSubmitting {isSubmitting
? intl.formatMessage(globalMessages.saving) ? intl.formatMessage(globalMessages.saving)

@ -1,6 +1,6 @@
import axios from 'axios'; import axios from 'axios';
import { Field, Form, Formik } from 'formik'; import { Field, Form, Formik } from 'formik';
import React from 'react'; import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr'; import useSWR from 'swr';
@ -24,7 +24,9 @@ const messages = defineMessages({
authPass: 'SMTP Password', authPass: 'SMTP Password',
emailsettingssaved: 'Email notification settings saved successfully!', emailsettingssaved: 'Email notification settings saved successfully!',
emailsettingsfailed: 'Email notification settings failed to save.', emailsettingsfailed: 'Email notification settings failed to save.',
emailtestsent: 'Email test notification sent!', toastEmailTestSending: 'Sending email test notification…',
toastEmailTestSuccess: 'Email test notification sent!',
toastEmailTestFailed: 'Email test notification failed to send.',
allowselfsigned: 'Allow Self-Signed Certificates', allowselfsigned: 'Allow Self-Signed Certificates',
ssldisabletip: ssldisabletip:
'SSL should be disabled on standard TLS connections (port 587)', 'SSL should be disabled on standard TLS connections (port 587)',
@ -56,7 +58,8 @@ export function OpenPgpLink(msg: string): JSX.Element {
const NotificationsEmail: React.FC = () => { const NotificationsEmail: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const { addToast } = useToasts(); const { addToast, removeToast } = useToasts();
const [isTesting, setIsTesting] = useState(false);
const { data, error, revalidate } = useSWR( const { data, error, revalidate } = useSWR(
'/api/v1/settings/notifications/email' '/api/v1/settings/notifications/email'
); );
@ -172,26 +175,53 @@ const NotificationsEmail: React.FC = () => {
> >
{({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => { {({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => {
const testSettings = async () => { const testSettings = async () => {
await axios.post('/api/v1/settings/notifications/email/test', { setIsTesting(true);
enabled: true, let toastId: string | undefined;
types: values.types, try {
options: { addToast(
emailFrom: values.emailFrom, intl.formatMessage(messages.toastEmailTestSending),
smtpHost: values.smtpHost, {
smtpPort: Number(values.smtpPort), autoDismiss: false,
secure: values.secure, appearance: 'info',
authUser: values.authUser, },
authPass: values.authPass, (id) => {
senderName: values.senderName, toastId = id;
pgpPrivateKey: values.pgpPrivateKey, }
pgpPassword: values.pgpPassword, );
}, await axios.post('/api/v1/settings/notifications/email/test', {
}); enabled: true,
types: values.types,
options: {
emailFrom: values.emailFrom,
smtpHost: values.smtpHost,
smtpPort: Number(values.smtpPort),
secure: values.secure,
authUser: values.authUser,
authPass: values.authPass,
senderName: values.senderName,
pgpPrivateKey: values.pgpPrivateKey,
pgpPassword: values.pgpPassword,
},
});
addToast(intl.formatMessage(messages.emailtestsent), { if (toastId) {
appearance: 'info', removeToast(toastId);
autoDismiss: true, }
}); addToast(intl.formatMessage(messages.toastEmailTestSuccess), {
autoDismiss: true,
appearance: 'success',
});
} catch (e) {
if (toastId) {
removeToast(toastId);
}
addToast(intl.formatMessage(messages.toastEmailTestFailed), {
autoDismiss: true,
appearance: 'error',
});
} finally {
setIsTesting(false);
}
}; };
return ( return (
@ -425,21 +455,22 @@ const NotificationsEmail: React.FC = () => {
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="warning" buttonType="warning"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid || isTesting}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
testSettings(); testSettings();
}} }}
> >
{intl.formatMessage(globalMessages.test)} {isTesting
? intl.formatMessage(globalMessages.testing)
: intl.formatMessage(globalMessages.test)}
</Button> </Button>
</span> </span>
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="primary" buttonType="primary"
type="submit" type="submit"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid || isTesting}
> >
{isSubmitting {isSubmitting
? intl.formatMessage(globalMessages.saving) ? intl.formatMessage(globalMessages.saving)

@ -1,6 +1,6 @@
import axios from 'axios'; import axios from 'axios';
import { Field, Form, Formik } from 'formik'; import { Field, Form, Formik } from 'formik';
import React from 'react'; import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr'; import useSWR from 'swr';
@ -18,12 +18,15 @@ const messages = defineMessages({
profileNameTip: 'Only required if not using the <code>default</code> profile', profileNameTip: 'Only required if not using the <code>default</code> profile',
settingsSaved: 'LunaSea notification settings saved successfully!', settingsSaved: 'LunaSea notification settings saved successfully!',
settingsFailed: 'LunaSea notification settings failed to save.', settingsFailed: 'LunaSea notification settings failed to save.',
testSent: 'LunaSea test notification sent!', toastLunaSeaTestSending: 'Sending LunaSea test notification…',
toastLunaSeaTestSuccess: 'LunaSea test notification sent!',
toastLunaSeaTestFailed: 'LunaSea test notification failed to send.',
}); });
const NotificationsLunaSea: React.FC = () => { const NotificationsLunaSea: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const { addToast } = useToasts(); const { addToast, removeToast } = useToasts();
const [isTesting, setIsTesting] = useState(false);
const { data, error, revalidate } = useSWR( const { data, error, revalidate } = useSWR(
'/api/v1/settings/notifications/lunasea' '/api/v1/settings/notifications/lunasea'
); );
@ -79,19 +82,46 @@ const NotificationsLunaSea: React.FC = () => {
> >
{({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => { {({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => {
const testSettings = async () => { const testSettings = async () => {
await axios.post('/api/v1/settings/notifications/lunasea/test', { setIsTesting(true);
enabled: true, let toastId: string | undefined;
types: values.types, try {
options: { addToast(
webhookUrl: values.webhookUrl, intl.formatMessage(messages.toastLunaSeaTestSending),
profileName: values.profileName, {
}, autoDismiss: false,
}); appearance: 'info',
},
(id) => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/lunasea/test', {
enabled: true,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
profileName: values.profileName,
},
});
addToast(intl.formatMessage(messages.testSent), { if (toastId) {
appearance: 'info', removeToast(toastId);
autoDismiss: true, }
}); addToast(intl.formatMessage(messages.toastLunaSeaTestSuccess), {
autoDismiss: true,
appearance: 'success',
});
} catch (e) {
if (toastId) {
removeToast(toastId);
}
addToast(intl.formatMessage(messages.toastLunaSeaTestFailed), {
autoDismiss: true,
appearance: 'error',
});
} finally {
setIsTesting(false);
}
}; };
return ( return (
@ -144,21 +174,22 @@ const NotificationsLunaSea: React.FC = () => {
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="warning" buttonType="warning"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid || isTesting}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
testSettings(); testSettings();
}} }}
> >
{intl.formatMessage(globalMessages.test)} {isTesting
? intl.formatMessage(globalMessages.testing)
: intl.formatMessage(globalMessages.test)}
</Button> </Button>
</span> </span>
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="primary" buttonType="primary"
type="submit" type="submit"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid || isTesting}
> >
{isSubmitting {isSubmitting
? intl.formatMessage(globalMessages.saving) ? intl.formatMessage(globalMessages.saving)

@ -1,6 +1,6 @@
import axios from 'axios'; import axios from 'axios';
import { Field, Form, Formik } from 'formik'; import { Field, Form, Formik } from 'formik';
import React from 'react'; import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr'; import useSWR from 'swr';
@ -18,14 +18,17 @@ const messages = defineMessages({
pushbulletSettingsSaved: pushbulletSettingsSaved:
'Pushbullet notification settings saved successfully!', 'Pushbullet notification settings saved successfully!',
pushbulletSettingsFailed: 'Pushbullet notification settings failed to save.', pushbulletSettingsFailed: 'Pushbullet notification settings failed to save.',
testSent: 'Pushbullet test notification sent!', toastPushbulletTestSending: 'Sending Pushbullet test notification…',
toastPushbulletTestSuccess: 'Pushbullet test notification sent!',
toastPushbulletTestFailed: 'Pushbullet test notification failed to send.',
settingUpPushbulletDescription: settingUpPushbulletDescription:
'To configure Pushbullet notifications, you will need to <CreateAccessTokenLink>create an access token</CreateAccessTokenLink>.', 'To configure Pushbullet notifications, you will need to <CreateAccessTokenLink>create an access token</CreateAccessTokenLink>.',
}); });
const NotificationsPushbullet: React.FC = () => { const NotificationsPushbullet: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const { addToast } = useToasts(); const { addToast, removeToast } = useToasts();
const [isTesting, setIsTesting] = useState(false);
const { data, error, revalidate } = useSWR( const { data, error, revalidate } = useSWR(
'/api/v1/settings/notifications/pushbullet' '/api/v1/settings/notifications/pushbullet'
); );
@ -77,18 +80,45 @@ const NotificationsPushbullet: React.FC = () => {
> >
{({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => { {({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => {
const testSettings = async () => { const testSettings = async () => {
await axios.post('/api/v1/settings/notifications/pushbullet/test', { setIsTesting(true);
enabled: true, let toastId: string | undefined;
types: values.types, try {
options: { addToast(
accessToken: values.accessToken, intl.formatMessage(messages.toastPushbulletTestSending),
}, {
}); autoDismiss: false,
appearance: 'info',
},
(id) => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/pushbullet/test', {
enabled: true,
types: values.types,
options: {
accessToken: values.accessToken,
},
});
addToast(intl.formatMessage(messages.testSent), { if (toastId) {
appearance: 'info', removeToast(toastId);
autoDismiss: true, }
}); addToast(intl.formatMessage(messages.toastPushbulletTestSuccess), {
autoDismiss: true,
appearance: 'success',
});
} catch (e) {
if (toastId) {
removeToast(toastId);
}
addToast(intl.formatMessage(messages.toastPushbulletTestFailed), {
autoDismiss: true,
appearance: 'error',
});
} finally {
setIsTesting(false);
}
}; };
return ( return (
@ -150,21 +180,22 @@ const NotificationsPushbullet: React.FC = () => {
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="warning" buttonType="warning"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid || isTesting}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
testSettings(); testSettings();
}} }}
> >
{intl.formatMessage(globalMessages.test)} {isTesting
? intl.formatMessage(globalMessages.testing)
: intl.formatMessage(globalMessages.test)}
</Button> </Button>
</span> </span>
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="primary" buttonType="primary"
type="submit" type="submit"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid || isTesting}
> >
{isSubmitting {isSubmitting
? intl.formatMessage(globalMessages.saving) ? intl.formatMessage(globalMessages.saving)

@ -1,6 +1,6 @@
import axios from 'axios'; import axios from 'axios';
import { Field, Form, Formik } from 'formik'; import { Field, Form, Formik } from 'formik';
import React from 'react'; import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr'; import useSWR from 'swr';
@ -19,14 +19,17 @@ const messages = defineMessages({
validationUserTokenRequired: 'You must provide a valid user key', validationUserTokenRequired: 'You must provide a valid user key',
pushoversettingssaved: 'Pushover notification settings saved successfully!', pushoversettingssaved: 'Pushover notification settings saved successfully!',
pushoversettingsfailed: 'Pushover notification settings failed to save.', pushoversettingsfailed: 'Pushover notification settings failed to save.',
testsent: 'Pushover test notification sent!', toastPushoverTestSending: 'Sending Pushover test notification…',
toastPushoverTestSuccess: 'Pushover test notification sent!',
toastPushoverTestFailed: 'Pushover test notification failed to send.',
settinguppushoverDescription: settinguppushoverDescription:
'To configure Pushover notifications, you will need to <RegisterApplicationLink>register an application</RegisterApplicationLink> and enter the API token below. (You can use one of the <IconLink>official Overseerr icons on GitHub</IconLink>.)', 'To configure Pushover notifications, you will need to <RegisterApplicationLink>register an application</RegisterApplicationLink>. (You can use one of the <IconLink>official Overseerr icons on GitHub</IconLink>.)',
}); });
const NotificationsPushover: React.FC = () => { const NotificationsPushover: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const { addToast } = useToasts(); const { addToast, removeToast } = useToasts();
const [isTesting, setIsTesting] = useState(false);
const { data, error, revalidate } = useSWR( const { data, error, revalidate } = useSWR(
'/api/v1/settings/notifications/pushover' '/api/v1/settings/notifications/pushover'
); );
@ -97,19 +100,46 @@ const NotificationsPushover: React.FC = () => {
> >
{({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => { {({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => {
const testSettings = async () => { const testSettings = async () => {
await axios.post('/api/v1/settings/notifications/pushover/test', { setIsTesting(true);
enabled: true, let toastId: string | undefined;
types: values.types, try {
options: { addToast(
accessToken: values.accessToken, intl.formatMessage(messages.toastPushoverTestSending),
userToken: values.userToken, {
}, autoDismiss: false,
}); appearance: 'info',
},
(id) => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/pushover/test', {
enabled: true,
types: values.types,
options: {
accessToken: values.accessToken,
userToken: values.userToken,
},
});
addToast(intl.formatMessage(messages.testsent), { if (toastId) {
appearance: 'info', removeToast(toastId);
autoDismiss: true, }
}); addToast(intl.formatMessage(messages.toastPushoverTestSuccess), {
autoDismiss: true,
appearance: 'success',
});
} catch (e) {
if (toastId) {
removeToast(toastId);
}
addToast(intl.formatMessage(messages.toastPushoverTestFailed), {
autoDismiss: true,
appearance: 'error',
});
} finally {
setIsTesting(false);
}
}; };
return ( return (
@ -199,21 +229,22 @@ const NotificationsPushover: React.FC = () => {
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="warning" buttonType="warning"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid || isTesting}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
testSettings(); testSettings();
}} }}
> >
{intl.formatMessage(globalMessages.test)} {isTesting
? intl.formatMessage(globalMessages.testing)
: intl.formatMessage(globalMessages.test)}
</Button> </Button>
</span> </span>
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="primary" buttonType="primary"
type="submit" type="submit"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid || isTesting}
> >
{isSubmitting {isSubmitting
? intl.formatMessage(globalMessages.saving) ? intl.formatMessage(globalMessages.saving)

@ -1,6 +1,6 @@
import axios from 'axios'; import axios from 'axios';
import { Field, Form, Formik } from 'formik'; import { Field, Form, Formik } from 'formik';
import React from 'react'; import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr'; import useSWR from 'swr';
@ -16,7 +16,9 @@ const messages = defineMessages({
webhookUrl: 'Webhook URL', webhookUrl: 'Webhook URL',
slacksettingssaved: 'Slack notification settings saved successfully!', slacksettingssaved: 'Slack notification settings saved successfully!',
slacksettingsfailed: 'Slack notification settings failed to save.', slacksettingsfailed: 'Slack notification settings failed to save.',
testsent: 'Slack test notification sent!', toastSlackTestSending: 'Sending Slack test notification…',
toastSlackTestSuccess: 'Slack test notification sent!',
toastSlackTestFailed: 'Slack test notification failed to send.',
settingupslackDescription: settingupslackDescription:
'To configure Slack notifications, you will need to create an <WebhookLink>Incoming Webhook</WebhookLink> integration and enter the webhook URL below.', 'To configure Slack notifications, you will need to create an <WebhookLink>Incoming Webhook</WebhookLink> integration and enter the webhook URL below.',
validationWebhookUrl: 'You must provide a valid URL', validationWebhookUrl: 'You must provide a valid URL',
@ -24,7 +26,8 @@ const messages = defineMessages({
const NotificationsSlack: React.FC = () => { const NotificationsSlack: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const { addToast } = useToasts(); const { addToast, removeToast } = useToasts();
const [isTesting, setIsTesting] = useState(false);
const { data, error, revalidate } = useSWR( const { data, error, revalidate } = useSWR(
'/api/v1/settings/notifications/slack' '/api/v1/settings/notifications/slack'
); );
@ -103,18 +106,45 @@ const NotificationsSlack: React.FC = () => {
setFieldValue, setFieldValue,
}) => { }) => {
const testSettings = async () => { const testSettings = async () => {
await axios.post('/api/v1/settings/notifications/slack/test', { setIsTesting(true);
enabled: true, let toastId: string | undefined;
types: values.types, try {
options: { addToast(
webhookUrl: values.webhookUrl, intl.formatMessage(messages.toastSlackTestSending),
}, {
}); autoDismiss: false,
appearance: 'info',
},
(id) => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/slack/test', {
enabled: true,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
},
});
addToast(intl.formatMessage(messages.testsent), { if (toastId) {
appearance: 'info', removeToast(toastId);
autoDismiss: true, }
}); addToast(intl.formatMessage(messages.toastSlackTestSuccess), {
autoDismiss: true,
appearance: 'success',
});
} catch (e) {
if (toastId) {
removeToast(toastId);
}
addToast(intl.formatMessage(messages.toastSlackTestFailed), {
autoDismiss: true,
appearance: 'error',
});
} finally {
setIsTesting(false);
}
}; };
return ( return (
@ -150,21 +180,22 @@ const NotificationsSlack: React.FC = () => {
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="warning" buttonType="warning"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid || isTesting}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
testSettings(); testSettings();
}} }}
> >
{intl.formatMessage(globalMessages.test)} {isTesting
? intl.formatMessage(globalMessages.testing)
: intl.formatMessage(globalMessages.test)}
</Button> </Button>
</span> </span>
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="primary" buttonType="primary"
type="submit" type="submit"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid || isTesting}
> >
{isSubmitting {isSubmitting
? intl.formatMessage(globalMessages.saving) ? intl.formatMessage(globalMessages.saving)

@ -1,6 +1,6 @@
import axios from 'axios'; import axios from 'axios';
import { Field, Form, Formik } from 'formik'; import { Field, Form, Formik } from 'formik';
import React from 'react'; import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr'; import useSWR from 'swr';
@ -22,7 +22,9 @@ const messages = defineMessages({
validationChatIdRequired: 'You must provide a valid chat ID', validationChatIdRequired: 'You must provide a valid chat ID',
telegramsettingssaved: 'Telegram notification settings saved successfully!', telegramsettingssaved: 'Telegram notification settings saved successfully!',
telegramsettingsfailed: 'Telegram notification settings failed to save.', telegramsettingsfailed: 'Telegram notification settings failed to save.',
telegramtestsent: 'Telegram test notification sent!', toastTelegramTestSending: 'Sending Telegram test notification…',
toastTelegramTestSuccess: 'Telegram test notification sent!',
toastTelegramTestFailed: 'Telegram test notification failed to send.',
settinguptelegramDescription: settinguptelegramDescription:
'To configure Telegram notifications, you will need to <CreateBotLink>create a bot</CreateBotLink> and get the bot API key. Additionally, you will need the chat ID for the chat to which you would like to send notifications. You can find this by adding <GetIdBotLink>@get_id_bot</GetIdBotLink> to the chat and issuing the <code>/my_id</code> command.', 'To configure Telegram notifications, you will need to <CreateBotLink>create a bot</CreateBotLink> and get the bot API key. Additionally, you will need the chat ID for the chat to which you would like to send notifications. You can find this by adding <GetIdBotLink>@get_id_bot</GetIdBotLink> to the chat and issuing the <code>/my_id</code> command.',
sendSilently: 'Send Silently', sendSilently: 'Send Silently',
@ -31,7 +33,8 @@ const messages = defineMessages({
const NotificationsTelegram: React.FC = () => { const NotificationsTelegram: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const { addToast } = useToasts(); const { addToast, removeToast } = useToasts();
const [isTesting, setIsTesting] = useState(false);
const { data, error, revalidate } = useSWR( const { data, error, revalidate } = useSWR(
'/api/v1/settings/notifications/telegram' '/api/v1/settings/notifications/telegram'
); );
@ -102,21 +105,48 @@ const NotificationsTelegram: React.FC = () => {
> >
{({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => { {({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => {
const testSettings = async () => { const testSettings = async () => {
await axios.post('/api/v1/settings/notifications/telegram/test', { setIsTesting(true);
enabled: true, let toastId: string | undefined;
types: values.types, try {
options: { addToast(
botAPI: values.botAPI, intl.formatMessage(messages.toastTelegramTestSending),
chatId: values.chatId, {
sendSilently: values.sendSilently, autoDismiss: false,
botUsername: values.botUsername, appearance: 'info',
}, },
}); (id) => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/telegram/test', {
enabled: true,
types: values.types,
options: {
botAPI: values.botAPI,
chatId: values.chatId,
sendSilently: values.sendSilently,
botUsername: values.botUsername,
},
});
addToast(intl.formatMessage(messages.telegramtestsent), { if (toastId) {
appearance: 'info', removeToast(toastId);
autoDismiss: true, }
}); addToast(intl.formatMessage(messages.toastTelegramTestSuccess), {
autoDismiss: true,
appearance: 'success',
});
} catch (e) {
if (toastId) {
removeToast(toastId);
}
addToast(intl.formatMessage(messages.toastTelegramTestFailed), {
autoDismiss: true,
appearance: 'error',
});
} finally {
setIsTesting(false);
}
}; };
return ( return (
@ -245,21 +275,22 @@ const NotificationsTelegram: React.FC = () => {
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="warning" buttonType="warning"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid || isTesting}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
testSettings(); testSettings();
}} }}
> >
{intl.formatMessage(globalMessages.test)} {isTesting
? intl.formatMessage(globalMessages.testing)
: intl.formatMessage(globalMessages.test)}
</Button> </Button>
</span> </span>
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="primary" buttonType="primary"
type="submit" type="submit"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid || isTesting}
> >
{isSubmitting {isSubmitting
? intl.formatMessage(globalMessages.saving) ? intl.formatMessage(globalMessages.saving)

@ -1,6 +1,6 @@
import axios from 'axios'; import axios from 'axios';
import { Field, Form, Formik } from 'formik'; import { Field, Form, Formik } from 'formik';
import React from 'react'; import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr'; import useSWR from 'swr';
@ -13,12 +13,15 @@ const messages = defineMessages({
agentenabled: 'Enable Agent', agentenabled: 'Enable Agent',
webpushsettingssaved: 'Web push notification settings saved successfully!', webpushsettingssaved: 'Web push notification settings saved successfully!',
webpushsettingsfailed: 'Web push notification settings failed to save.', webpushsettingsfailed: 'Web push notification settings failed to save.',
testsent: 'Web push test notification sent!', toastWebPushTestSending: 'Sending web push test notification…',
toastWebPushTestSuccess: 'Web push test notification sent!',
toastWebPushTestFailed: 'Web push test notification failed to send.',
}); });
const NotificationsWebPush: React.FC = () => { const NotificationsWebPush: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const { addToast } = useToasts(); const { addToast, removeToast } = useToasts();
const [isTesting, setIsTesting] = useState(false);
const { data, error, revalidate } = useSWR( const { data, error, revalidate } = useSWR(
'/api/v1/settings/notifications/webpush' '/api/v1/settings/notifications/webpush'
); );
@ -57,16 +60,43 @@ const NotificationsWebPush: React.FC = () => {
> >
{({ isSubmitting, values, isValid, setFieldValue }) => { {({ isSubmitting, values, isValid, setFieldValue }) => {
const testSettings = async () => { const testSettings = async () => {
await axios.post('/api/v1/settings/notifications/webpush/test', { setIsTesting(true);
enabled: true, let toastId: string | undefined;
types: values.types, try {
options: {}, addToast(
}); intl.formatMessage(messages.toastWebPushTestSending),
{
autoDismiss: false,
appearance: 'info',
},
(id) => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/webpush/test', {
enabled: true,
types: values.types,
options: {},
});
addToast(intl.formatMessage(messages.testsent), { if (toastId) {
appearance: 'info', removeToast(toastId);
autoDismiss: true, }
}); addToast(intl.formatMessage(messages.toastWebPushTestSuccess), {
autoDismiss: true,
appearance: 'success',
});
} catch (e) {
if (toastId) {
removeToast(toastId);
}
addToast(intl.formatMessage(messages.toastWebPushTestFailed), {
autoDismiss: true,
appearance: 'error',
});
} finally {
setIsTesting(false);
}
}; };
return ( return (
@ -88,21 +118,22 @@ const NotificationsWebPush: React.FC = () => {
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="warning" buttonType="warning"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid || isTesting}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
testSettings(); testSettings();
}} }}
> >
{intl.formatMessage(globalMessages.test)} {isTesting
? intl.formatMessage(globalMessages.testing)
: intl.formatMessage(globalMessages.test)}
</Button> </Button>
</span> </span>
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="primary" buttonType="primary"
type="submit" type="submit"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid || isTesting}
> >
{isSubmitting {isSubmitting
? intl.formatMessage(globalMessages.saving) ? intl.formatMessage(globalMessages.saving)

@ -2,7 +2,7 @@ import { QuestionMarkCircleIcon, RefreshIcon } from '@heroicons/react/solid';
import axios from 'axios'; import axios from 'axios';
import { Field, Form, Formik } from 'formik'; import { Field, Form, Formik } from 'formik';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import React from 'react'; import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr'; import useSWR from 'swr';
@ -46,7 +46,9 @@ const messages = defineMessages({
validationJsonPayloadRequired: 'You must provide a valid JSON payload', validationJsonPayloadRequired: 'You must provide a valid JSON payload',
webhooksettingssaved: 'Webhook notification settings saved successfully!', webhooksettingssaved: 'Webhook notification settings saved successfully!',
webhooksettingsfailed: 'Webhook notification settings failed to save.', webhooksettingsfailed: 'Webhook notification settings failed to save.',
testsent: 'Webhook test notification sent!', toastWebhookTestSending: 'Sending webhook test notification…',
toastWebhookTestSuccess: 'Webhook test notification sent!',
toastWebhookTestFailed: 'Webhook test notification failed to send.',
resetPayload: 'Reset to Default', resetPayload: 'Reset to Default',
resetPayloadSuccess: 'JSON payload reset successfully!', resetPayloadSuccess: 'JSON payload reset successfully!',
customJson: 'JSON Payload', customJson: 'JSON Payload',
@ -56,7 +58,8 @@ const messages = defineMessages({
const NotificationsWebhook: React.FC = () => { const NotificationsWebhook: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const { addToast } = useToasts(); const { addToast, removeToast } = useToasts();
const [isTesting, setIsTesting] = useState(false);
const { data, error, revalidate } = useSWR( const { data, error, revalidate } = useSWR(
'/api/v1/settings/notifications/webhook' '/api/v1/settings/notifications/webhook'
); );
@ -157,20 +160,47 @@ const NotificationsWebhook: React.FC = () => {
}; };
const testSettings = async () => { const testSettings = async () => {
await axios.post('/api/v1/settings/notifications/webhook/test', { setIsTesting(true);
enabled: true, let toastId: string | undefined;
types: values.types, try {
options: { addToast(
webhookUrl: values.webhookUrl, intl.formatMessage(messages.toastWebhookTestSending),
jsonPayload: JSON.stringify(values.jsonPayload), {
authHeader: values.authHeader, autoDismiss: false,
}, appearance: 'info',
}); },
(id) => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/webhook/test', {
enabled: true,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
jsonPayload: JSON.stringify(values.jsonPayload),
authHeader: values.authHeader,
},
});
addToast(intl.formatMessage(messages.testsent), { if (toastId) {
appearance: 'info', removeToast(toastId);
autoDismiss: true, }
}); addToast(intl.formatMessage(messages.toastWebhookTestSuccess), {
autoDismiss: true,
appearance: 'success',
});
} catch (e) {
if (toastId) {
removeToast(toastId);
}
addToast(intl.formatMessage(messages.toastWebhookTestFailed), {
autoDismiss: true,
appearance: 'error',
});
} finally {
setIsTesting(false);
}
}; };
return ( return (
@ -257,21 +287,22 @@ const NotificationsWebhook: React.FC = () => {
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="warning" buttonType="warning"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid || isTesting}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
testSettings(); testSettings();
}} }}
> >
{intl.formatMessage(globalMessages.test)} {isTesting
? intl.formatMessage(globalMessages.testing)
: intl.formatMessage(globalMessages.test)}
</Button> </Button>
</span> </span>
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="primary" buttonType="primary"
type="submit" type="submit"
disabled={isSubmitting || !isValid} disabled={isSubmitting || !isValid || isTesting}
> >
{isSubmitting {isSubmitting
? intl.formatMessage(globalMessages.saving) ? intl.formatMessage(globalMessages.saving)

@ -19,8 +19,6 @@ const messages = defineMessages({
notificationsettings: 'Notification Settings', notificationsettings: 'Notification Settings',
email: 'Email', email: 'Email',
webpush: 'Web Push', webpush: 'Web Push',
toastSettingsSuccess: 'Notification settings saved successfully!',
toastSettingsFailure: 'Something went wrong while saving settings.',
}); });
const UserNotificationSettings: React.FC = ({ children }) => { const UserNotificationSettings: React.FC = ({ children }) => {

@ -250,7 +250,9 @@
"components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Only required if not using the <code>default</code> profile", "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Only required if not using the <code>default</code> profile",
"components.Settings.Notifications.NotificationsLunaSea.settingsFailed": "LunaSea notification settings failed to save.", "components.Settings.Notifications.NotificationsLunaSea.settingsFailed": "LunaSea notification settings failed to save.",
"components.Settings.Notifications.NotificationsLunaSea.settingsSaved": "LunaSea notification settings saved successfully!", "components.Settings.Notifications.NotificationsLunaSea.settingsSaved": "LunaSea notification settings saved successfully!",
"components.Settings.Notifications.NotificationsLunaSea.testSent": "LunaSea test notification sent!", "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestFailed": "LunaSea test notification failed to send.",
"components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSending": "Sending LunaSea test notification…",
"components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSuccess": "LunaSea test notification sent!",
"components.Settings.Notifications.NotificationsLunaSea.validationWebhookUrl": "You must provide a valid URL", "components.Settings.Notifications.NotificationsLunaSea.validationWebhookUrl": "You must provide a valid URL",
"components.Settings.Notifications.NotificationsLunaSea.webhookUrl": "Webhook URL", "components.Settings.Notifications.NotificationsLunaSea.webhookUrl": "Webhook URL",
"components.Settings.Notifications.NotificationsPushbullet.accessToken": "Access Token", "components.Settings.Notifications.NotificationsPushbullet.accessToken": "Access Token",
@ -258,14 +260,18 @@
"components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Pushbullet notification settings failed to save.", "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Pushbullet notification settings failed to save.",
"components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Pushbullet notification settings saved successfully!", "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Pushbullet notification settings saved successfully!",
"components.Settings.Notifications.NotificationsPushbullet.settingUpPushbulletDescription": "To configure Pushbullet notifications, you will need to <CreateAccessTokenLink>create an access token</CreateAccessTokenLink>.", "components.Settings.Notifications.NotificationsPushbullet.settingUpPushbulletDescription": "To configure Pushbullet notifications, you will need to <CreateAccessTokenLink>create an access token</CreateAccessTokenLink>.",
"components.Settings.Notifications.NotificationsPushbullet.testSent": "Pushbullet test notification sent!", "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestFailed": "Pushbullet test notification failed to send.",
"components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSending": "Sending Pushbullet test notification…",
"components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSuccess": "Pushbullet test notification sent!",
"components.Settings.Notifications.NotificationsPushbullet.validationAccessTokenRequired": "You must provide an access token", "components.Settings.Notifications.NotificationsPushbullet.validationAccessTokenRequired": "You must provide an access token",
"components.Settings.Notifications.NotificationsPushover.accessToken": "Application/API Token", "components.Settings.Notifications.NotificationsPushover.accessToken": "Application/API Token",
"components.Settings.Notifications.NotificationsPushover.agentenabled": "Enable Agent", "components.Settings.Notifications.NotificationsPushover.agentenabled": "Enable Agent",
"components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Pushover notification settings failed to save.", "components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Pushover notification settings failed to save.",
"components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Pushover notification settings saved successfully!", "components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Pushover notification settings saved successfully!",
"components.Settings.Notifications.NotificationsPushover.settinguppushoverDescription": "To configure Pushover notifications, you will need to <RegisterApplicationLink>register an application</RegisterApplicationLink> and enter the API token below. (You can use one of the <IconLink>official Overseerr icons on GitHub</IconLink>.)", "components.Settings.Notifications.NotificationsPushover.settinguppushoverDescription": "To configure Pushover notifications, you will need to <RegisterApplicationLink>register an application</RegisterApplicationLink>. (You can use one of the <IconLink>official Overseerr icons on GitHub</IconLink>.)",
"components.Settings.Notifications.NotificationsPushover.testsent": "Pushover test notification sent!", "components.Settings.Notifications.NotificationsPushover.toastPushoverTestFailed": "Pushover test notification failed to send.",
"components.Settings.Notifications.NotificationsPushover.toastPushoverTestSending": "Sending Pushover test notification…",
"components.Settings.Notifications.NotificationsPushover.toastPushoverTestSuccess": "Pushover test notification sent!",
"components.Settings.Notifications.NotificationsPushover.userToken": "User or Group Key", "components.Settings.Notifications.NotificationsPushover.userToken": "User or Group Key",
"components.Settings.Notifications.NotificationsPushover.validationAccessTokenRequired": "You must provide a valid application token", "components.Settings.Notifications.NotificationsPushover.validationAccessTokenRequired": "You must provide a valid application token",
"components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "You must provide a valid user key", "components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "You must provide a valid user key",
@ -273,11 +279,15 @@
"components.Settings.Notifications.NotificationsSlack.settingupslackDescription": "To configure Slack notifications, you will need to create an <WebhookLink>Incoming Webhook</WebhookLink> integration and enter the webhook URL below.", "components.Settings.Notifications.NotificationsSlack.settingupslackDescription": "To configure Slack notifications, you will need to create an <WebhookLink>Incoming Webhook</WebhookLink> integration and enter the webhook URL below.",
"components.Settings.Notifications.NotificationsSlack.slacksettingsfailed": "Slack notification settings failed to save.", "components.Settings.Notifications.NotificationsSlack.slacksettingsfailed": "Slack notification settings failed to save.",
"components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Slack notification settings saved successfully!", "components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Slack notification settings saved successfully!",
"components.Settings.Notifications.NotificationsSlack.testsent": "Slack test notification sent!", "components.Settings.Notifications.NotificationsSlack.toastSlackTestFailed": "Slack test notification failed to send.",
"components.Settings.Notifications.NotificationsSlack.toastSlackTestSending": "Sending Slack test notification…",
"components.Settings.Notifications.NotificationsSlack.toastSlackTestSuccess": "Slack test notification sent!",
"components.Settings.Notifications.NotificationsSlack.validationWebhookUrl": "You must provide a valid URL", "components.Settings.Notifications.NotificationsSlack.validationWebhookUrl": "You must provide a valid URL",
"components.Settings.Notifications.NotificationsSlack.webhookUrl": "Webhook URL", "components.Settings.Notifications.NotificationsSlack.webhookUrl": "Webhook URL",
"components.Settings.Notifications.NotificationsWebPush.agentenabled": "Enable Agent", "components.Settings.Notifications.NotificationsWebPush.agentenabled": "Enable Agent",
"components.Settings.Notifications.NotificationsWebPush.testsent": "Web push test notification sent!", "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestFailed": "Web push test notification failed to send.",
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSending": "Sending web push test notification…",
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSuccess": "Web push test notification sent!",
"components.Settings.Notifications.NotificationsWebPush.webpushsettingsfailed": "Web push notification settings failed to save.", "components.Settings.Notifications.NotificationsWebPush.webpushsettingsfailed": "Web push notification settings failed to save.",
"components.Settings.Notifications.NotificationsWebPush.webpushsettingssaved": "Web push notification settings saved successfully!", "components.Settings.Notifications.NotificationsWebPush.webpushsettingssaved": "Web push notification settings saved successfully!",
"components.Settings.Notifications.NotificationsWebhook.agentenabled": "Enable Agent", "components.Settings.Notifications.NotificationsWebhook.agentenabled": "Enable Agent",
@ -286,7 +296,9 @@
"components.Settings.Notifications.NotificationsWebhook.resetPayload": "Reset to Default", "components.Settings.Notifications.NotificationsWebhook.resetPayload": "Reset to Default",
"components.Settings.Notifications.NotificationsWebhook.resetPayloadSuccess": "JSON payload reset successfully!", "components.Settings.Notifications.NotificationsWebhook.resetPayloadSuccess": "JSON payload reset successfully!",
"components.Settings.Notifications.NotificationsWebhook.templatevariablehelp": "Template Variable Help", "components.Settings.Notifications.NotificationsWebhook.templatevariablehelp": "Template Variable Help",
"components.Settings.Notifications.NotificationsWebhook.testsent": "Webhook test notification sent!", "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestFailed": "Webhook test notification failed to send.",
"components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSending": "Sending webhook test notification…",
"components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSuccess": "Webhook test notification sent!",
"components.Settings.Notifications.NotificationsWebhook.validationJsonPayloadRequired": "You must provide a valid JSON payload", "components.Settings.Notifications.NotificationsWebhook.validationJsonPayloadRequired": "You must provide a valid JSON payload",
"components.Settings.Notifications.NotificationsWebhook.validationWebhookUrl": "You must provide a valid URL", "components.Settings.Notifications.NotificationsWebhook.validationWebhookUrl": "You must provide a valid URL",
"components.Settings.Notifications.NotificationsWebhook.webhookUrl": "Webhook URL", "components.Settings.Notifications.NotificationsWebhook.webhookUrl": "Webhook URL",
@ -303,13 +315,11 @@
"components.Settings.Notifications.chatId": "Chat ID", "components.Settings.Notifications.chatId": "Chat ID",
"components.Settings.Notifications.discordsettingsfailed": "Discord notification settings failed to save.", "components.Settings.Notifications.discordsettingsfailed": "Discord notification settings failed to save.",
"components.Settings.Notifications.discordsettingssaved": "Discord notification settings saved successfully!", "components.Settings.Notifications.discordsettingssaved": "Discord notification settings saved successfully!",
"components.Settings.Notifications.discordtestsent": "Discord test notification sent!",
"components.Settings.Notifications.emailNotificationTypesAlertDescription": "<strong>Media Requested</strong>, <strong>Media Automatically Approved</strong>, and <strong>Media Failed</strong> email notifications are sent to all users with the <strong>Manage Requests</strong> permission.", "components.Settings.Notifications.emailNotificationTypesAlertDescription": "<strong>Media Requested</strong>, <strong>Media Automatically Approved</strong>, and <strong>Media Failed</strong> email notifications are sent to all users with the <strong>Manage Requests</strong> permission.",
"components.Settings.Notifications.emailNotificationTypesAlertDescriptionPt2": "<strong>Media Approved</strong>, <strong>Media Declined</strong>, and <strong>Media Available</strong> email notifications are sent to the user who submitted the request.", "components.Settings.Notifications.emailNotificationTypesAlertDescriptionPt2": "<strong>Media Approved</strong>, <strong>Media Declined</strong>, and <strong>Media Available</strong> email notifications are sent to the user who submitted the request.",
"components.Settings.Notifications.emailsender": "Sender Address", "components.Settings.Notifications.emailsender": "Sender Address",
"components.Settings.Notifications.emailsettingsfailed": "Email notification settings failed to save.", "components.Settings.Notifications.emailsettingsfailed": "Email notification settings failed to save.",
"components.Settings.Notifications.emailsettingssaved": "Email notification settings saved successfully!", "components.Settings.Notifications.emailsettingssaved": "Email notification settings saved successfully!",
"components.Settings.Notifications.emailtestsent": "Email test notification sent!",
"components.Settings.Notifications.enableSsl": "Enable SSL", "components.Settings.Notifications.enableSsl": "Enable SSL",
"components.Settings.Notifications.pgpPassword": "PGP Password", "components.Settings.Notifications.pgpPassword": "PGP Password",
"components.Settings.Notifications.pgpPasswordTip": "Sign encrypted email messages using <OpenPgpLink>OpenPGP</OpenPgpLink>", "components.Settings.Notifications.pgpPasswordTip": "Sign encrypted email messages using <OpenPgpLink>OpenPGP</OpenPgpLink>",
@ -324,7 +334,15 @@
"components.Settings.Notifications.ssldisabletip": "SSL should be disabled on standard TLS connections (port 587)", "components.Settings.Notifications.ssldisabletip": "SSL should be disabled on standard TLS connections (port 587)",
"components.Settings.Notifications.telegramsettingsfailed": "Telegram notification settings failed to save.", "components.Settings.Notifications.telegramsettingsfailed": "Telegram notification settings failed to save.",
"components.Settings.Notifications.telegramsettingssaved": "Telegram notification settings saved successfully!", "components.Settings.Notifications.telegramsettingssaved": "Telegram notification settings saved successfully!",
"components.Settings.Notifications.telegramtestsent": "Telegram test notification sent!", "components.Settings.Notifications.toastDiscordTestFailed": "Discord test notification failed to send.",
"components.Settings.Notifications.toastDiscordTestSending": "Sending Discord test notification…",
"components.Settings.Notifications.toastDiscordTestSuccess": "Discord test notification sent!",
"components.Settings.Notifications.toastEmailTestFailed": "Email test notification failed to send.",
"components.Settings.Notifications.toastEmailTestSending": "Sending email test notification…",
"components.Settings.Notifications.toastEmailTestSuccess": "Email test notification sent!",
"components.Settings.Notifications.toastTelegramTestFailed": "Telegram test notification failed to send.",
"components.Settings.Notifications.toastTelegramTestSending": "Sending Telegram test notification…",
"components.Settings.Notifications.toastTelegramTestSuccess": "Telegram test notification sent!",
"components.Settings.Notifications.validationBotAPIRequired": "You must provide a bot authentication token", "components.Settings.Notifications.validationBotAPIRequired": "You must provide a bot authentication token",
"components.Settings.Notifications.validationChatIdRequired": "You must provide a valid chat ID", "components.Settings.Notifications.validationChatIdRequired": "You must provide a valid chat ID",
"components.Settings.Notifications.validationEmail": "You must provide a valid email address", "components.Settings.Notifications.validationEmail": "You must provide a valid email address",
@ -748,8 +766,6 @@
"components.UserProfile.UserSettings.UserNotificationSettings.telegramChatIdTipLong": "<TelegramBotLink>Start a chat</TelegramBotLink>, add <GetIdBotLink>@get_id_bot</GetIdBotLink>, and issue the <code>/my_id</code> command", "components.UserProfile.UserSettings.UserNotificationSettings.telegramChatIdTipLong": "<TelegramBotLink>Start a chat</TelegramBotLink>, add <GetIdBotLink>@get_id_bot</GetIdBotLink>, and issue the <code>/my_id</code> command",
"components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingsfailed": "Telegram notification settings failed to save.", "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingsfailed": "Telegram notification settings failed to save.",
"components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Telegram notification settings saved successfully!", "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Telegram notification settings saved successfully!",
"components.UserProfile.UserSettings.UserNotificationSettings.toastSettingsFailure": "Something went wrong while saving settings.",
"components.UserProfile.UserSettings.UserNotificationSettings.toastSettingsSuccess": "Notification settings saved successfully!",
"components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "You must provide a valid user ID", "components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "You must provide a valid user ID",
"components.UserProfile.UserSettings.UserNotificationSettings.validationPgpPublicKey": "You must provide a valid PGP public key", "components.UserProfile.UserSettings.UserNotificationSettings.validationPgpPublicKey": "You must provide a valid PGP public key",
"components.UserProfile.UserSettings.UserNotificationSettings.validationTelegramChatId": "You must provide a valid chat ID", "components.UserProfile.UserSettings.UserNotificationSettings.validationTelegramChatId": "You must provide a valid chat ID",

Loading…
Cancel
Save