feat: allow users to select notification types (#1512)

* feat: allow users to select notification types

* fix(ui): display personal notification types before management types

* fix: update allRequestsAutoApproved check to account for new REQUEST_MOVIE & REQUEST_TV perms

* fix(ui): do not display Discord notif type selector if user not eligible for any types

* refactor(ui): remove unnecessary 'enabled' checkboxes from user notif settings

* fix(ui): correct checkbox behavior

* fix: add missing return type on hasNotificationType

* refactor: remove unused isValid prop in NotificationsWebPush

* fix(ui): use SensitiveInput for users' public PGP keys

* fix(ui): add missing tip/hint for email encryption setting

* refactor(svg): use the new Discord logo

* revert(api): undo breaking change removing discordEnabled from UserSettingsNotificationsResponse

* fix(lang): update notification type descriptions for clarity

* fix(telegram): do not send users notifications of their own auto-approved requests
This commit is contained in:
TheCatLady
2021-06-04 06:31:05 -04:00
committed by GitHub
parent 6a3649f620
commit e60598905b
35 changed files with 1072 additions and 653 deletions

View File

@@ -23,6 +23,7 @@ const messages = defineMessages({
toastDiscordTestSuccess: 'Discord test notification sent!',
toastDiscordTestFailed: 'Discord test notification failed to send.',
validationUrl: 'You must provide a valid URL',
validationTypes: 'You must select at least one notification type',
});
const NotificationsDiscord: React.FC = () => {
@@ -46,6 +47,13 @@ const NotificationsDiscord: React.FC = () => {
otherwise: Yup.string().nullable(),
})
.url(intl.formatMessage(messages.validationUrl)),
types: Yup.number().when('enabled', {
is: true,
then: Yup.number()
.nullable()
.moreThan(0, intl.formatMessage(messages.validationTypes)),
otherwise: Yup.number().nullable(),
}),
});
if (!data && !error) {
@@ -88,7 +96,15 @@ const NotificationsDiscord: React.FC = () => {
}
}}
>
{({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => {
{({
errors,
touched,
isSubmitting,
values,
isValid,
setFieldValue,
setFieldTouched,
}) => {
const testSettings = async () => {
setIsTesting(true);
let toastId: string | undefined;
@@ -211,8 +227,20 @@ const NotificationsDiscord: React.FC = () => {
</div>
</div>
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) => setFieldValue('types', newTypes)}
currentTypes={values.enabled ? values.types : 0}
onUpdate={(newTypes) => {
setFieldValue('types', newTypes);
setFieldTouched('types');
if (newTypes) {
setFieldValue('enabled', true);
}
}}
error={
errors.types && touched.types
? (errors.types as string)
: undefined
}
/>
<div className="actions">
<div className="flex justify-end">

View File

@@ -6,12 +6,10 @@ import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr';
import * as Yup from 'yup';
import globalMessages from '../../../i18n/globalMessages';
import Alert from '../../Common/Alert';
import Badge from '../../Common/Badge';
import Button from '../../Common/Button';
import LoadingSpinner from '../../Common/LoadingSpinner';
import SensitiveInput from '../../Common/SensitiveInput';
import NotificationTypeSelector from '../../NotificationTypeSelector';
const messages = defineMessages({
validationSmtpHostRequired: 'You must provide a valid hostname or IP address',
@@ -37,20 +35,14 @@ const messages = defineMessages({
allowselfsigned: 'Allow Self-Signed Certificates',
senderName: 'Sender Name',
validationEmail: 'You must provide a valid email address',
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.',
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.',
pgpPrivateKey: 'PGP Private Key',
pgpPrivateKeyTip:
'Sign encrypted email messages using <OpenPgpLink>OpenPGP</OpenPgpLink>',
validationPgpPrivateKey:
'You must provide a valid PGP private key if a PGP password is entered',
validationPgpPrivateKey: 'You must provide a valid PGP private key',
pgpPassword: 'PGP Password',
pgpPasswordTip:
'Sign encrypted email messages using <OpenPgpLink>OpenPGP</OpenPgpLink>',
validationPgpPassword:
'You must provide a PGP password if a PGP private key is entered',
validationPgpPassword: 'You must provide a PGP password',
});
export function OpenPgpLink(msg: string): JSX.Element {
@@ -130,7 +122,6 @@ const NotificationsEmail: React.FC = () => {
<Formik
initialValues={{
enabled: data.enabled,
types: data.types,
emailFrom: data.options.emailFrom,
smtpHost: data.options.smtpHost,
smtpPort: data.options.smtpPort ?? 587,
@@ -153,7 +144,6 @@ const NotificationsEmail: React.FC = () => {
try {
await axios.post('/api/v1/settings/notifications/email', {
enabled: values.enabled,
types: values.types,
options: {
emailFrom: values.emailFrom,
smtpHost: values.smtpHost,
@@ -184,7 +174,7 @@ const NotificationsEmail: React.FC = () => {
}
}}
>
{({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => {
{({ errors, touched, isSubmitting, values, isValid }) => {
const testSettings = async () => {
setIsTesting(true);
let toastId: string | undefined;
@@ -201,7 +191,6 @@ const NotificationsEmail: React.FC = () => {
);
await axios.post('/api/v1/settings/notifications/email/test', {
enabled: true,
types: values.types,
options: {
emailFrom: values.emailFrom,
smtpHost: values.smtpHost,
@@ -238,274 +227,234 @@ const NotificationsEmail: React.FC = () => {
};
return (
<>
<Alert
title={
<>
<p className="mb-2">
{intl.formatMessage(
messages.emailNotificationTypesAlertDescription,
{
strong: function strong(msg) {
return (
<strong className="font-semibold text-indigo-100">
{msg}
</strong>
);
},
}
)}
</p>
<p>
{intl.formatMessage(
messages.emailNotificationTypesAlertDescriptionPt2,
{
strong: function strong(msg) {
return (
<strong className="font-semibold text-indigo-100">
{msg}
</strong>
);
},
}
)}
</p>
</>
}
type="info"
/>
<Form className="section">
<div className="form-row">
<label htmlFor="enabled" className="checkbox-label">
{intl.formatMessage(messages.agentenabled)}
<span className="label-required">*</span>
</label>
<div className="form-input">
<Field type="checkbox" id="enabled" name="enabled" />
<Form className="section">
<div className="form-row">
<label htmlFor="enabled" className="checkbox-label">
{intl.formatMessage(messages.agentenabled)}
<span className="label-required">*</span>
</label>
<div className="form-input">
<Field type="checkbox" id="enabled" name="enabled" />
</div>
</div>
<div className="form-row">
<label htmlFor="senderName" className="text-label">
{intl.formatMessage(messages.senderName)}
</label>
<div className="form-input">
<div className="form-input-field">
<Field id="senderName" name="senderName" type="text" />
</div>
</div>
<div className="form-row">
<label htmlFor="senderName" className="text-label">
{intl.formatMessage(messages.senderName)}
</label>
<div className="form-input">
<div className="form-input-field">
<Field id="senderName" name="senderName" type="text" />
</div>
</div>
</div>
<div className="form-row">
<label htmlFor="emailFrom" className="text-label">
{intl.formatMessage(messages.emailsender)}
<span className="label-required">*</span>
</label>
<div className="form-input">
<div className="form-input-field">
<Field
id="emailFrom"
name="emailFrom"
type="text"
inputMode="email"
/>
</div>
{errors.emailFrom && touched.emailFrom && (
<div className="error">{errors.emailFrom}</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="smtpHost" className="text-label">
{intl.formatMessage(messages.smtpHost)}
<span className="label-required">*</span>
</label>
<div className="form-input">
<div className="form-input-field">
<Field
id="smtpHost"
name="smtpHost"
type="text"
inputMode="url"
/>
</div>
{errors.smtpHost && touched.smtpHost && (
<div className="error">{errors.smtpHost}</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="smtpPort" className="text-label">
{intl.formatMessage(messages.smtpPort)}
<span className="label-required">*</span>
</label>
<div className="form-input">
</div>
<div className="form-row">
<label htmlFor="emailFrom" className="text-label">
{intl.formatMessage(messages.emailsender)}
<span className="label-required">*</span>
</label>
<div className="form-input">
<div className="form-input-field">
<Field
id="smtpPort"
name="smtpPort"
id="emailFrom"
name="emailFrom"
type="text"
inputMode="numeric"
className="short"
inputMode="email"
/>
{errors.smtpPort && touched.smtpPort && (
<div className="error">{errors.smtpPort}</div>
)}
</div>
{errors.emailFrom && touched.emailFrom && (
<div className="error">{errors.emailFrom}</div>
)}
</div>
<div className="form-row">
<label htmlFor="encryption" className="text-label">
{intl.formatMessage(messages.encryption)}
<span className="label-required">*</span>
</label>
<div className="form-input">
<div className="form-input-field">
<Field as="select" id="encryption" name="encryption">
<option value="none">
{intl.formatMessage(messages.encryptionNone)}
</option>
<option value="default">
{intl.formatMessage(messages.encryptionDefault)}
</option>
<option value="opportunistic">
{intl.formatMessage(
messages.encryptionOpportunisticTls
)}
</option>
<option value="implicit">
{intl.formatMessage(messages.encryptionImplicitTls)}
</option>
</Field>
</div>
</div>
</div>
<div className="form-row">
<label htmlFor="allowSelfSigned" className="checkbox-label">
{intl.formatMessage(messages.allowselfsigned)}
</label>
<div className="form-input">
</div>
<div className="form-row">
<label htmlFor="smtpHost" className="text-label">
{intl.formatMessage(messages.smtpHost)}
<span className="label-required">*</span>
</label>
<div className="form-input">
<div className="form-input-field">
<Field
type="checkbox"
id="allowSelfSigned"
name="allowSelfSigned"
id="smtpHost"
name="smtpHost"
type="text"
inputMode="url"
/>
</div>
{errors.smtpHost && touched.smtpHost && (
<div className="error">{errors.smtpHost}</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="smtpPort" className="text-label">
{intl.formatMessage(messages.smtpPort)}
<span className="label-required">*</span>
</label>
<div className="form-input">
<Field
id="smtpPort"
name="smtpPort"
type="text"
inputMode="numeric"
className="short"
/>
{errors.smtpPort && touched.smtpPort && (
<div className="error">{errors.smtpPort}</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="encryption" className="text-label">
{intl.formatMessage(messages.encryption)}
<span className="label-required">*</span>
<span className="label-tip">
{intl.formatMessage(messages.encryptionTip)}
</span>
</label>
<div className="form-input">
<div className="form-input-field">
<Field as="select" id="encryption" name="encryption">
<option value="none">
{intl.formatMessage(messages.encryptionNone)}
</option>
<option value="default">
{intl.formatMessage(messages.encryptionDefault)}
</option>
<option value="opportunistic">
{intl.formatMessage(messages.encryptionOpportunisticTls)}
</option>
<option value="implicit">
{intl.formatMessage(messages.encryptionImplicitTls)}
</option>
</Field>
</div>
</div>
</div>
<div className="form-row">
<label htmlFor="allowSelfSigned" className="checkbox-label">
{intl.formatMessage(messages.allowselfsigned)}
</label>
<div className="form-input">
<Field
type="checkbox"
id="allowSelfSigned"
name="allowSelfSigned"
/>
</div>
</div>
<div className="form-row">
<label htmlFor="authUser" className="text-label">
{intl.formatMessage(messages.authUser)}
</label>
<div className="form-input">
<div className="form-input-field">
<Field id="authUser" name="authUser" type="text" />
</div>
</div>
</div>
<div className="form-row">
<label htmlFor="authPass" className="text-label">
{intl.formatMessage(messages.authPass)}
</label>
<div className="form-input">
<div className="form-input-field">
<SensitiveInput
as="field"
id="authPass"
name="authPass"
autoComplete="one-time-code"
/>
</div>
</div>
<div className="form-row">
<label htmlFor="authUser" className="text-label">
{intl.formatMessage(messages.authUser)}
</label>
<div className="form-input">
<div className="form-input-field">
<Field id="authUser" name="authUser" type="text" />
</div>
</div>
<div className="form-row">
<label htmlFor="pgpPrivateKey" className="text-label">
<span className="mr-2">
{intl.formatMessage(messages.pgpPrivateKey)}
</span>
<Badge badgeType="danger">
{intl.formatMessage(globalMessages.advanced)}
</Badge>
<span className="label-tip">
{intl.formatMessage(messages.pgpPrivateKeyTip, {
OpenPgpLink: OpenPgpLink,
})}
</span>
</label>
<div className="form-input">
<div className="form-input-field">
<SensitiveInput
as="field"
id="pgpPrivateKey"
name="pgpPrivateKey"
type="textarea"
rows="10"
className="font-mono text-xs"
/>
</div>
{errors.pgpPrivateKey && touched.pgpPrivateKey && (
<div className="error">{errors.pgpPrivateKey}</div>
)}
</div>
<div className="form-row">
<label htmlFor="authPass" className="text-label">
{intl.formatMessage(messages.authPass)}
</label>
<div className="form-input">
<div className="form-input-field">
<SensitiveInput
as="field"
id="authPass"
name="authPass"
autoComplete="one-time-code"
/>
</div>
</div>
<div className="form-row">
<label htmlFor="pgpPassword" className="text-label">
<span className="mr-2">
{intl.formatMessage(messages.pgpPassword)}
</span>
<Badge badgeType="danger">
{intl.formatMessage(globalMessages.advanced)}
</Badge>
<span className="label-tip">
{intl.formatMessage(messages.pgpPasswordTip, {
OpenPgpLink: OpenPgpLink,
})}
</span>
</label>
<div className="form-input">
<div className="form-input-field">
<SensitiveInput
as="field"
id="pgpPassword"
name="pgpPassword"
autoComplete="one-time-code"
/>
</div>
{errors.pgpPassword && touched.pgpPassword && (
<div className="error">{errors.pgpPassword}</div>
)}
</div>
<div className="form-row">
<label htmlFor="pgpPrivateKey" className="text-label">
<span className="mr-2">
{intl.formatMessage(messages.pgpPrivateKey)}
</span>
<Badge badgeType="danger">
{intl.formatMessage(globalMessages.advanced)}
</Badge>
<span className="label-tip">
{intl.formatMessage(messages.pgpPrivateKeyTip, {
OpenPgpLink: OpenPgpLink,
})}
</span>
</label>
<div className="form-input">
<div className="form-input-field">
<SensitiveInput
as="field"
id="pgpPrivateKey"
name="pgpPrivateKey"
type="textarea"
rows="10"
className="font-mono text-xs"
/>
</div>
{errors.pgpPrivateKey && touched.pgpPrivateKey && (
<div className="error">{errors.pgpPrivateKey}</div>
)}
</div>
</div>
<div className="actions">
<div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
buttonType="warning"
disabled={isSubmitting || !isValid || isTesting}
onClick={(e) => {
e.preventDefault();
testSettings();
}}
>
{isTesting
? intl.formatMessage(globalMessages.testing)
: intl.formatMessage(globalMessages.test)}
</Button>
</span>
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting || !isValid || isTesting}
>
{isSubmitting
? intl.formatMessage(globalMessages.saving)
: intl.formatMessage(globalMessages.save)}
</Button>
</span>
</div>
<div className="form-row">
<label htmlFor="pgpPassword" className="text-label">
<span className="mr-2">
{intl.formatMessage(messages.pgpPassword)}
</span>
<Badge badgeType="danger">
{intl.formatMessage(globalMessages.advanced)}
</Badge>
<span className="label-tip">
{intl.formatMessage(messages.pgpPasswordTip, {
OpenPgpLink: OpenPgpLink,
})}
</span>
</label>
<div className="form-input">
<div className="form-input-field">
<SensitiveInput
as="field"
id="pgpPassword"
name="pgpPassword"
autoComplete="one-time-code"
/>
</div>
{errors.pgpPassword && touched.pgpPassword && (
<div className="error">{errors.pgpPassword}</div>
)}
</div>
</div>
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) => setFieldValue('types', newTypes)}
/>
<div className="actions">
<div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
buttonType="warning"
disabled={isSubmitting || !isValid || isTesting}
onClick={(e) => {
e.preventDefault();
testSettings();
}}
>
{isTesting
? intl.formatMessage(globalMessages.testing)
: intl.formatMessage(globalMessages.test)}
</Button>
</span>
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting || !isValid || isTesting}
>
{isSubmitting
? intl.formatMessage(globalMessages.saving)
: intl.formatMessage(globalMessages.save)}
</Button>
</span>
</div>
</div>
</Form>
</>
</div>
</Form>
);
}}
</Formik>

View File

@@ -23,6 +23,7 @@ const messages = defineMessages({
toastLunaSeaTestSending: 'Sending LunaSea test notification…',
toastLunaSeaTestSuccess: 'LunaSea test notification sent!',
toastLunaSeaTestFailed: 'LunaSea test notification failed to send.',
validationTypes: 'You must select at least one notification type',
});
const NotificationsLunaSea: React.FC = () => {
@@ -43,6 +44,13 @@ const NotificationsLunaSea: React.FC = () => {
otherwise: Yup.string().nullable(),
})
.url(intl.formatMessage(messages.validationWebhookUrl)),
types: Yup.number().when('enabled', {
is: true,
then: Yup.number()
.nullable()
.moreThan(0, intl.formatMessage(messages.validationTypes)),
otherwise: Yup.number().nullable(),
}),
});
if (!data && !error) {
@@ -82,7 +90,15 @@ const NotificationsLunaSea: React.FC = () => {
}
}}
>
{({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => {
{({
errors,
touched,
isSubmitting,
values,
isValid,
setFieldValue,
setFieldTouched,
}) => {
const testSettings = async () => {
setIsTesting(true);
let toastId: string | undefined;
@@ -190,8 +206,20 @@ const NotificationsLunaSea: React.FC = () => {
</div>
</div>
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) => setFieldValue('types', newTypes)}
currentTypes={values.enabled ? values.types : 0}
onUpdate={(newTypes) => {
setFieldValue('types', newTypes);
setFieldTouched('types');
if (newTypes) {
setFieldValue('enabled', true);
}
}}
error={
errors.types && touched.types
? (errors.types as string)
: undefined
}
/>
<div className="actions">
<div className="flex justify-end">

View File

@@ -23,6 +23,7 @@ const messages = defineMessages({
toastPushbulletTestSending: 'Sending Pushbullet test notification…',
toastPushbulletTestSuccess: 'Pushbullet test notification sent!',
toastPushbulletTestFailed: 'Pushbullet test notification failed to send.',
validationTypes: 'You must select at least one notification type',
});
const NotificationsPushbullet: React.FC = () => {
@@ -41,6 +42,13 @@ const NotificationsPushbullet: React.FC = () => {
.required(intl.formatMessage(messages.validationAccessTokenRequired)),
otherwise: Yup.string().nullable(),
}),
types: Yup.number().when('enabled', {
is: true,
then: Yup.number()
.nullable()
.moreThan(0, intl.formatMessage(messages.validationTypes)),
otherwise: Yup.number().nullable(),
}),
});
if (!data && !error) {
@@ -78,7 +86,15 @@ const NotificationsPushbullet: React.FC = () => {
}
}}
>
{({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => {
{({
errors,
touched,
isSubmitting,
values,
isValid,
setFieldValue,
setFieldTouched,
}) => {
const testSettings = async () => {
setIsTesting(true);
let toastId: string | undefined;
@@ -170,8 +186,20 @@ const NotificationsPushbullet: React.FC = () => {
</div>
</div>
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) => setFieldValue('types', newTypes)}
currentTypes={values.enabled ? values.types : 0}
onUpdate={(newTypes) => {
setFieldValue('types', newTypes);
setFieldTouched('types');
if (newTypes) {
setFieldValue('enabled', true);
}
}}
error={
errors.types && touched.types
? (errors.types as string)
: undefined
}
/>
<div className="actions">
<div className="flex justify-end">

View File

@@ -19,12 +19,13 @@ const messages = defineMessages({
userTokenTip:
'Your 30-character <UsersGroupsLink>user or group identifier</UsersGroupsLink>',
validationAccessTokenRequired: 'You must provide a valid application token',
validationUserTokenRequired: 'You must provide a valid user key',
validationUserTokenRequired: 'You must provide a valid user or group key',
pushoversettingssaved: 'Pushover notification settings saved successfully!',
pushoversettingsfailed: 'Pushover notification settings failed to save.',
toastPushoverTestSending: 'Sending Pushover test notification…',
toastPushoverTestSuccess: 'Pushover test notification sent!',
toastPushoverTestFailed: 'Pushover test notification failed to send.',
validationTypes: 'You must select at least one notification type',
});
const NotificationsPushover: React.FC = () => {
@@ -60,6 +61,13 @@ const NotificationsPushover: React.FC = () => {
/^[a-z\d]{30}$/i,
intl.formatMessage(messages.validationUserTokenRequired)
),
types: Yup.number().when('enabled', {
is: true,
then: Yup.number()
.nullable()
.moreThan(0, intl.formatMessage(messages.validationTypes)),
otherwise: Yup.number().nullable(),
}),
});
if (!data && !error) {
@@ -99,7 +107,15 @@ const NotificationsPushover: React.FC = () => {
}
}}
>
{({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => {
{({
errors,
touched,
isSubmitting,
values,
isValid,
setFieldValue,
setFieldTouched,
}) => {
const testSettings = async () => {
setIsTesting(true);
let toastId: string | undefined;
@@ -216,8 +232,20 @@ const NotificationsPushover: React.FC = () => {
</div>
</div>
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) => setFieldValue('types', newTypes)}
currentTypes={values.enabled ? values.types : 0}
onUpdate={(newTypes) => {
setFieldValue('types', newTypes);
setFieldTouched('types');
if (newTypes) {
setFieldValue('enabled', true);
}
}}
error={
errors.types && touched.types
? (errors.types as string)
: undefined
}
/>
<div className="actions">
<div className="flex justify-end">

View File

@@ -21,6 +21,7 @@ const messages = defineMessages({
toastSlackTestSuccess: 'Slack test notification sent!',
toastSlackTestFailed: 'Slack test notification failed to send.',
validationWebhookUrl: 'You must provide a valid URL',
validationTypes: 'You must select at least one notification type',
});
const NotificationsSlack: React.FC = () => {
@@ -41,6 +42,13 @@ const NotificationsSlack: React.FC = () => {
otherwise: Yup.string().nullable(),
})
.url(intl.formatMessage(messages.validationWebhookUrl)),
types: Yup.number().when('enabled', {
is: true,
then: Yup.number()
.nullable()
.moreThan(0, intl.formatMessage(messages.validationTypes)),
otherwise: Yup.number().nullable(),
}),
});
if (!data && !error) {
@@ -78,7 +86,15 @@ const NotificationsSlack: React.FC = () => {
}
}}
>
{({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => {
{({
errors,
touched,
isSubmitting,
values,
isValid,
setFieldValue,
setFieldTouched,
}) => {
const testSettings = async () => {
setIsTesting(true);
let toastId: string | undefined;
@@ -168,8 +184,20 @@ const NotificationsSlack: React.FC = () => {
</div>
</div>
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) => setFieldValue('types', newTypes)}
currentTypes={values.enabled ? values.types : 0}
onUpdate={(newTypes) => {
setFieldValue('types', newTypes);
setFieldTouched('types');
if (newTypes) {
setFieldValue('enabled', true);
}
}}
error={
errors.types && touched.types
? (errors.types as string)
: undefined
}
/>
<div className="actions">
<div className="flex justify-end">

View File

@@ -105,7 +105,15 @@ const NotificationsTelegram: React.FC = () => {
}
}}
>
{({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => {
{({
errors,
touched,
isSubmitting,
values,
isValid,
setFieldValue,
setFieldTouched,
}) => {
const testSettings = async () => {
setIsTesting(true);
let toastId: string | undefined;
@@ -232,6 +240,24 @@ const NotificationsTelegram: React.FC = () => {
<label htmlFor="chatId" className="text-label">
{intl.formatMessage(messages.chatId)}
<span className="label-required">*</span>
<span className="label-tip">
{intl.formatMessage(messages.chatIdTip, {
GetIdBotLink: function GetIdBotLink(msg) {
return (
<a
href="https://telegram.me/get_id_bot"
target="_blank"
rel="noreferrer"
>
{msg}
</a>
);
},
code: function code(msg) {
return <code>{msg}</code>;
},
})}
</span>
</label>
<div className="form-input">
<div className="form-input-field">
@@ -254,8 +280,20 @@ const NotificationsTelegram: React.FC = () => {
</div>
</div>
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) => setFieldValue('types', newTypes)}
currentTypes={values.enabled ? values.types : 0}
onUpdate={(newTypes) => {
setFieldValue('types', newTypes);
setFieldTouched('types');
if (newTypes) {
setFieldValue('enabled', true);
}
}}
error={
errors.types && touched.types
? (errors.types as string)
: undefined
}
/>
<div className="actions">
<div className="flex justify-end">

View File

@@ -8,7 +8,6 @@ import globalMessages from '../../../../i18n/globalMessages';
import Alert from '../../../Common/Alert';
import Button from '../../../Common/Button';
import LoadingSpinner from '../../../Common/LoadingSpinner';
import NotificationTypeSelector from '../../../NotificationTypeSelector';
const messages = defineMessages({
agentenabled: 'Enable Agent',
@@ -49,13 +48,11 @@ const NotificationsWebPush: React.FC = () => {
<Formik
initialValues={{
enabled: data.enabled,
types: data.types,
}}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/settings/notifications/webpush', {
enabled: values.enabled,
types: values.types,
options: {},
});
mutate('/api/v1/settings/public');
@@ -73,7 +70,7 @@ const NotificationsWebPush: React.FC = () => {
}
}}
>
{({ isSubmitting, values, isValid, setFieldValue }) => {
{({ isSubmitting }) => {
const testSettings = async () => {
setIsTesting(true);
let toastId: string | undefined;
@@ -90,7 +87,6 @@ const NotificationsWebPush: React.FC = () => {
);
await axios.post('/api/v1/settings/notifications/webpush/test', {
enabled: true,
types: values.types,
options: {},
});
@@ -125,16 +121,12 @@ const NotificationsWebPush: React.FC = () => {
<Field type="checkbox" id="enabled" name="enabled" />
</div>
</div>
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) => setFieldValue('types', newTypes)}
/>
<div className="actions">
<div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
buttonType="warning"
disabled={isSubmitting || !isValid || isTesting}
disabled={isSubmitting || isTesting}
onClick={(e) => {
e.preventDefault();
testSettings();
@@ -149,7 +141,7 @@ const NotificationsWebPush: React.FC = () => {
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting || !isValid || isTesting}
disabled={isSubmitting || isTesting}
>
{isSubmitting
? intl.formatMessage(globalMessages.saving)

View File

@@ -55,6 +55,7 @@ const messages = defineMessages({
customJson: 'JSON Payload',
templatevariablehelp: 'Template Variable Help',
validationWebhookUrl: 'You must provide a valid URL',
validationTypes: 'You must select at least one notification type',
});
const NotificationsWebhook: React.FC = () => {
@@ -99,6 +100,13 @@ const NotificationsWebhook: React.FC = () => {
}
}
),
types: Yup.number().when('enabled', {
is: true,
then: Yup.number()
.nullable()
.moreThan(0, intl.formatMessage(messages.validationTypes)),
otherwise: Yup.number().nullable(),
}),
});
if (!data && !error) {
@@ -293,8 +301,20 @@ const NotificationsWebhook: React.FC = () => {
</div>
</div>
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) => setFieldValue('types', newTypes)}
currentTypes={values.enabled ? values.types : 0}
onUpdate={(newTypes) => {
setFieldValue('types', newTypes);
setFieldTouched('types');
if (newTypes) {
setFieldValue('enabled', true);
}
}}
error={
errors.types && touched.types
? (errors.types as string)
: undefined
}
/>
<div className="actions">
<div className="flex justify-end">