feat(notifications): control notifcation types per agent

closes #513
pull/519/head
sct 4 years ago
parent d00e470b55
commit 8af6a1f566

@ -1,5 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import { Notification } from '..'; import { hasNotificationType, Notification } from '..';
import logger from '../../../logger'; import logger from '../../../logger';
import { getSettings, NotificationAgentDiscord } from '../../settings'; import { getSettings, NotificationAgentDiscord } from '../../settings';
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent'; import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
@ -196,10 +196,12 @@ class DiscordAgent
}; };
} }
// TODO: Add checking for type here once we add notification type filters for agents public shouldSend(type: Notification): boolean {
// eslint-disable-next-line @typescript-eslint/no-unused-vars if (
public shouldSend(_type: Notification): boolean { this.getSettings().enabled &&
if (this.getSettings().enabled && this.getSettings().options.webhookUrl) { this.getSettings().options.webhookUrl &&
hasNotificationType(type, this.getSettings().types)
) {
return true; return true;
} }

@ -1,5 +1,5 @@
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent'; import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
import { Notification } from '..'; import { hasNotificationType, Notification } from '..';
import path from 'path'; import path from 'path';
import { getSettings, NotificationAgentEmail } from '../../settings'; import { getSettings, NotificationAgentEmail } from '../../settings';
import nodemailer from 'nodemailer'; import nodemailer from 'nodemailer';
@ -22,12 +22,13 @@ class EmailAgent
return settings.notifications.agents.email; return settings.notifications.agents.email;
} }
// TODO: Add checking for type here once we add notification type filters for agents public shouldSend(type: Notification): boolean {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public shouldSend(_type: Notification): boolean {
const settings = this.getSettings(); const settings = this.getSettings();
if (settings.enabled) { if (
settings.enabled &&
hasNotificationType(type, this.getSettings().types)
) {
return true; return true;
} }

@ -1,5 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import { Notification } from '..'; import { hasNotificationType, Notification } from '..';
import logger from '../../../logger'; import logger from '../../../logger';
import { getSettings, NotificationAgentSlack } from '../../settings'; import { getSettings, NotificationAgentSlack } from '../../settings';
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent'; import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
@ -187,10 +187,12 @@ class SlackAgent
}; };
} }
// TODO: Add checking for type here once we add notification type filters for agents public shouldSend(type: Notification): boolean {
// eslint-disable-next-line @typescript-eslint/no-unused-vars if (
public shouldSend(_type: Notification): boolean { this.getSettings().enabled &&
if (this.getSettings().enabled && this.getSettings().options.webhookUrl) { this.getSettings().options.webhookUrl &&
hasNotificationType(type, this.getSettings().types)
) {
return true; return true;
} }

@ -1,5 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import { Notification } from '..'; import { hasNotificationType, Notification } from '..';
import logger from '../../../logger'; import logger from '../../../logger';
import { getSettings, NotificationAgentTelegram } from '../../settings'; import { getSettings, NotificationAgentTelegram } from '../../settings';
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent'; import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
@ -25,13 +25,12 @@ class TelegramAgent
return settings.notifications.agents.telegram; return settings.notifications.agents.telegram;
} }
// TODO: Add checking for type here once we add notification type filters for agents public shouldSend(type: Notification): boolean {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public shouldSend(_type: Notification): boolean {
if ( if (
this.getSettings().enabled && this.getSettings().enabled &&
this.getSettings().options.botAPI && this.getSettings().options.botAPI &&
this.getSettings().options.chatId this.getSettings().options.chatId &&
hasNotificationType(type, this.getSettings().types)
) { ) {
return true; return true;
} }

@ -9,6 +9,27 @@ export enum Notification {
TEST_NOTIFICATION = 32, TEST_NOTIFICATION = 32,
} }
export const hasNotificationType = (
types: Notification | Notification[],
value: number
): boolean => {
let total = 0;
// If we are not checking any notifications, bail out and return true
if (types === 0) {
return true;
}
if (Array.isArray(types)) {
// Combine all notification values into one
total = types.reduce((a, v) => a + v, 0);
} else {
total = types;
}
return !!(value & total);
};
class NotificationManager { class NotificationManager {
private activeAgents: NotificationAgent[] = []; private activeAgents: NotificationAgent[] = [];

@ -0,0 +1,70 @@
import React from 'react';
import { NotificationItem, hasNotificationType } from '..';
interface NotificationTypeProps {
option: NotificationItem;
currentTypes: number;
parent?: NotificationItem;
onUpdate: (newTypes: number) => void;
}
const NotificationType: React.FC<NotificationTypeProps> = ({
option,
currentTypes,
onUpdate,
parent,
}) => {
return (
<>
<div
className={`relative flex items-start first:mt-0 mt-4 ${
!!parent?.value && hasNotificationType(parent.value, currentTypes)
? 'opacity-50'
: ''
}`}
>
<div className="flex items-center h-5">
<input
id={option.id}
name="permissions"
type="checkbox"
className="w-4 h-4 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
disabled={
!!parent?.value && hasNotificationType(parent.value, currentTypes)
}
onClick={() => {
onUpdate(
hasNotificationType(option.value, currentTypes)
? currentTypes - option.value
: currentTypes + option.value
);
}}
defaultChecked={
hasNotificationType(option.value, currentTypes) ||
(!!parent?.value &&
hasNotificationType(parent.value, currentTypes))
}
/>
</div>
<div className="ml-3 text-sm leading-5">
<label htmlFor={option.id} className="font-medium">
{option.name}
</label>
<p className="text-gray-500">{option.description}</p>
</div>
</div>
{(option.children ?? []).map((child) => (
<div key={`notification-type-child-${child.id}`} className="pl-6 mt-4">
<NotificationType
option={child}
currentTypes={currentTypes}
onUpdate={(newTypes) => onUpdate(newTypes)}
parent={option}
/>
</div>
))}
</>
);
};
export default NotificationType;

@ -0,0 +1,106 @@
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import NotificationType from './NotificationType';
const messages = defineMessages({
mediarequested: 'Media Requested',
mediarequestedDescription:
'Sends a notification when new media is requested. For certain agents, this will only send the notification to admins or users with the "Manage Requests" permission.',
mediaapproved: 'Media Approved',
mediaapprovedDescription: 'Sends a notification when media is approved.',
mediaavailable: 'Media Available',
mediaavailableDescription:
'Sends a notification when media becomes available.',
mediafailed: 'Media Failed',
mediafailedDescription:
'Sends a notification when media fails to be added to services (Radarr/Sonarr). For certain agents, this will only send the notification to admins or users with the "Manage Requests" permission.',
});
export const hasNotificationType = (
types: Notification | Notification[],
value: number
): boolean => {
let total = 0;
if (types === 0) {
return true;
}
if (Array.isArray(types)) {
total = types.reduce((a, v) => a + v, 0);
} else {
total = types;
}
return !!(value & total);
};
export enum Notification {
MEDIA_PENDING = 2,
MEDIA_APPROVED = 4,
MEDIA_AVAILABLE = 8,
MEDIA_FAILED = 16,
TEST_NOTIFICATION = 32,
}
export interface NotificationItem {
id: string;
name: string;
description: string;
value: Notification;
children?: NotificationItem[];
}
interface NotificationTypeSelectorProps {
currentTypes: number;
onUpdate: (newTypes: number) => void;
}
const NotificationTypeSelector: React.FC<NotificationTypeSelectorProps> = ({
currentTypes,
onUpdate,
}) => {
const intl = useIntl();
const types: NotificationItem[] = [
{
id: 'media-requested',
name: intl.formatMessage(messages.mediarequested),
description: intl.formatMessage(messages.mediarequestedDescription),
value: Notification.MEDIA_PENDING,
},
{
id: 'media-approved',
name: intl.formatMessage(messages.mediaapproved),
description: intl.formatMessage(messages.mediaapprovedDescription),
value: Notification.MEDIA_APPROVED,
},
{
id: 'media-available',
name: intl.formatMessage(messages.mediaavailable),
description: intl.formatMessage(messages.mediaavailableDescription),
value: Notification.MEDIA_AVAILABLE,
},
{
id: 'media-failed',
name: intl.formatMessage(messages.mediafailed),
description: intl.formatMessage(messages.mediafailedDescription),
value: Notification.MEDIA_FAILED,
},
];
return (
<>
{types.map((type) => (
<NotificationType
key={`notification-type-${type.id}`}
option={type}
currentTypes={currentTypes}
onUpdate={onUpdate}
/>
))}
</>
);
};
export default NotificationTypeSelector;

@ -7,6 +7,7 @@ import { defineMessages, useIntl } from 'react-intl';
import axios from 'axios'; import axios from 'axios';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
import NotificationTypeSelector from '../../NotificationTypeSelector';
const messages = defineMessages({ const messages = defineMessages({
save: 'Save Changes', save: 'Save Changes',
@ -19,6 +20,7 @@ const messages = defineMessages({
discordsettingsfailed: 'Discord notification settings failed to save.', discordsettingsfailed: 'Discord notification settings failed to save.',
testsent: 'Test notification sent!', testsent: 'Test notification sent!',
test: 'Test', test: 'Test',
notificationtypes: 'Notification Types',
}); });
const NotificationsDiscord: React.FC = () => { const NotificationsDiscord: React.FC = () => {
@ -69,7 +71,7 @@ const NotificationsDiscord: React.FC = () => {
} }
}} }}
> >
{({ errors, touched, isSubmitting, values, isValid }) => { {({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => {
const testSettings = async () => { const testSettings = async () => {
await axios.post('/api/v1/settings/notifications/discord/test', { await axios.post('/api/v1/settings/notifications/discord/test', {
enabled: true, enabled: true,
@ -99,7 +101,7 @@ const NotificationsDiscord: React.FC = () => {
type="checkbox" type="checkbox"
id="enabled" id="enabled"
name="enabled" name="enabled"
className="form-checkbox rounded-md h-6 w-6 text-indigo-600 transition duration-150 ease-in-out" className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/> />
</div> </div>
</div> </div>
@ -111,7 +113,7 @@ const NotificationsDiscord: React.FC = () => {
{intl.formatMessage(messages.webhookUrl)} {intl.formatMessage(messages.webhookUrl)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="max-w-lg flex rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="webhookUrl" id="webhookUrl"
name="webhookUrl" name="webhookUrl"
@ -119,17 +121,41 @@ const NotificationsDiscord: React.FC = () => {
placeholder={intl.formatMessage( placeholder={intl.formatMessage(
messages.webhookUrlPlaceholder messages.webhookUrlPlaceholder
)} )}
className="flex-1 form-input block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500" className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.webhookUrl && touched.webhookUrl && ( {errors.webhookUrl && touched.webhookUrl && (
<div className="text-red-500 mt-2">{errors.webhookUrl}</div> <div className="mt-2 text-red-500">{errors.webhookUrl}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-8 border-t border-gray-700 pt-5"> <div className="mt-6">
<div role="group" aria-labelledby="label-permissions">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline">
<div>
<div
className="text-base font-medium leading-6 text-gray-400 sm:text-sm sm:leading-5"
id="label-types"
>
{intl.formatMessage(messages.notificationtypes)}
</div>
</div>
<div className="mt-4 sm:mt-0 sm:col-span-2">
<div className="max-w-lg">
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) =>
setFieldValue('types', newTypes)
}
/>
</div>
</div>
</div>
</div>
</div>
<div className="pt-5 mt-8 border-t border-gray-700">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="ml-3 inline-flex 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}
@ -142,7 +168,7 @@ const NotificationsDiscord: React.FC = () => {
{intl.formatMessage(messages.test)} {intl.formatMessage(messages.test)}
</Button> </Button>
</span> </span>
<span className="ml-3 inline-flex rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="primary" buttonType="primary"
type="submit" type="submit"

@ -7,6 +7,7 @@ import { defineMessages, useIntl } from 'react-intl';
import axios from 'axios'; import axios from 'axios';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
import NotificationTypeSelector from '../../NotificationTypeSelector';
const messages = defineMessages({ const messages = defineMessages({
save: 'Save Changes', save: 'Save Changes',
@ -29,6 +30,7 @@ const messages = defineMessages({
ssldisabletip: ssldisabletip:
'SSL should be disabled on standard TLS connections (Port 587)', 'SSL should be disabled on standard TLS connections (Port 587)',
senderName: 'Sender Name', senderName: 'Sender Name',
notificationtypes: 'Notification Types',
}); });
const NotificationsEmail: React.FC = () => { const NotificationsEmail: React.FC = () => {
@ -99,7 +101,7 @@ const NotificationsEmail: React.FC = () => {
} }
}} }}
> >
{({ errors, touched, isSubmitting, values, isValid }) => { {({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => {
const testSettings = async () => { const testSettings = async () => {
await axios.post('/api/v1/settings/notifications/email/test', { await axios.post('/api/v1/settings/notifications/email/test', {
enabled: true, enabled: true,
@ -292,11 +294,36 @@ const NotificationsEmail: React.FC = () => {
id="authPass" id="authPass"
name="authPass" name="authPass"
type="password" type="password"
autoComplete="off"
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5" className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
</div> </div>
</div> </div>
<div className="mt-6">
<div role="group" aria-labelledby="label-permissions">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline">
<div>
<div
className="text-base font-medium leading-6 text-gray-400 sm:text-sm sm:leading-5"
id="label-types"
>
{intl.formatMessage(messages.notificationtypes)}
</div>
</div>
<div className="mt-4 sm:mt-0 sm:col-span-2">
<div className="max-w-lg">
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) =>
setFieldValue('types', newTypes)
}
/>
</div>
</div>
</div>
</div>
</div>
<div className="pt-5 mt-8 border-t border-gray-700"> <div className="pt-5 mt-8 border-t border-gray-700">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">

@ -8,6 +8,7 @@ import axios from 'axios';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
import Alert from '../../../Common/Alert'; import Alert from '../../../Common/Alert';
import NotificationTypeSelector from '../../../NotificationTypeSelector';
const messages = defineMessages({ const messages = defineMessages({
save: 'Save Changes', save: 'Save Changes',
@ -23,6 +24,7 @@ const messages = defineMessages({
settingupslack: 'Setting up Slack Notifications', settingupslack: 'Setting up Slack Notifications',
settingupslackDescription: settingupslackDescription:
'To use Slack notifications, you will need to create an <WebhookLink>Incoming Webhook</WebhookLink> integration and use the provided webhook URL below.', 'To use Slack notifications, you will need to create an <WebhookLink>Incoming Webhook</WebhookLink> integration and use the provided webhook URL below.',
notificationtypes: 'Notification Types',
}); });
const NotificationsSlack: React.FC = () => { const NotificationsSlack: React.FC = () => {
@ -44,24 +46,22 @@ const NotificationsSlack: React.FC = () => {
return ( return (
<> <>
<p className="mb-"> <Alert title={intl.formatMessage(messages.settingupslack)} type="info">
<Alert title={intl.formatMessage(messages.settingupslack)} type="info"> {intl.formatMessage(messages.settingupslackDescription, {
{intl.formatMessage(messages.settingupslackDescription, { WebhookLink: function WebhookLink(msg) {
WebhookLink: function WebhookLink(msg) { return (
return ( <a
<a href="https://my.slack.com/services/new/incoming-webhook/"
href="https://my.slack.com/services/new/incoming-webhook/" className="text-indigo-100 hover:text-white hover:underline"
className="text-indigo-100 hover:text-white hover:underline" target="_blank"
target="_blank" rel="noreferrer"
rel="noreferrer" >
> {msg}
{msg} </a>
</a> );
); },
}, })}
})} </Alert>
</Alert>
</p>
<Formik <Formik
initialValues={{ initialValues={{
enabled: data.enabled, enabled: data.enabled,
@ -92,7 +92,14 @@ const NotificationsSlack: React.FC = () => {
} }
}} }}
> >
{({ errors, touched, isSubmitting, values, isValid }) => { {({
errors,
touched,
isSubmitting,
values,
isValid,
setFieldValue,
}) => {
const testSettings = async () => { const testSettings = async () => {
await axios.post('/api/v1/settings/notifications/slack/test', { await axios.post('/api/v1/settings/notifications/slack/test', {
enabled: true, enabled: true,
@ -150,6 +157,30 @@ const NotificationsSlack: React.FC = () => {
)} )}
</div> </div>
</div> </div>
<div className="mt-6">
<div role="group" aria-labelledby="label-permissions">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline">
<div>
<div
className="text-base font-medium leading-6 text-gray-400 sm:text-sm sm:leading-5"
id="label-types"
>
{intl.formatMessage(messages.notificationtypes)}
</div>
</div>
<div className="mt-4 sm:mt-0 sm:col-span-2">
<div className="max-w-lg">
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) =>
setFieldValue('types', newTypes)
}
/>
</div>
</div>
</div>
</div>
</div>
<div className="pt-5 mt-8 border-t border-gray-700"> <div className="pt-5 mt-8 border-t border-gray-700">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">

@ -8,6 +8,7 @@ import axios from 'axios';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
import Alert from '../../Common/Alert'; import Alert from '../../Common/Alert';
import NotificationTypeSelector from '../../NotificationTypeSelector';
const messages = defineMessages({ const messages = defineMessages({
save: 'Save Changes', save: 'Save Changes',
@ -26,6 +27,7 @@ const messages = defineMessages({
'To setup Telegram you need to <CreateBotLink>create a bot</CreateBotLink> and get the bot API key.\ 'To setup Telegram you need to <CreateBotLink>create a bot</CreateBotLink> and get the bot API key.\
Additionally, you need the chat id for the chat you want the bot to send notifications to.\ Additionally, you need the chat id for the chat you want the bot to send notifications to.\
You can do this by adding <GetIdBotLink>@get_id_bot</GetIdBotLink> to the chat or group chat.', You can do this by adding <GetIdBotLink>@get_id_bot</GetIdBotLink> to the chat or group chat.',
notificationtypes: 'Notification Types',
}); });
const NotificationsTelegram: React.FC = () => { const NotificationsTelegram: React.FC = () => {
@ -81,7 +83,7 @@ const NotificationsTelegram: React.FC = () => {
} }
}} }}
> >
{({ errors, touched, isSubmitting, values, isValid }) => { {({ errors, touched, isSubmitting, values, isValid, setFieldValue }) => {
const testSettings = async () => { const testSettings = async () => {
await axios.post('/api/v1/settings/notifications/telegram/test', { await axios.post('/api/v1/settings/notifications/telegram/test', {
enabled: true, enabled: true,
@ -100,39 +102,37 @@ const NotificationsTelegram: React.FC = () => {
return ( return (
<> <>
<p className="mb-"> <Alert
<Alert title={intl.formatMessage(messages.settinguptelegram)}
title={intl.formatMessage(messages.settinguptelegram)} type="info"
type="info" >
> {intl.formatMessage(messages.settinguptelegramDescription, {
{intl.formatMessage(messages.settinguptelegramDescription, { CreateBotLink: function CreateBotLink(msg) {
CreateBotLink: function CreateBotLink(msg) { return (
return ( <a
<a href="https://core.telegram.org/bots#6-botfather"
href="https://core.telegram.org/bots#6-botfather" className="text-indigo-100 hover:text-white hover:underline"
className="text-indigo-100 hover:text-white hover:underline" target="_blank"
target="_blank" rel="noreferrer"
rel="noreferrer" >
> {msg}
{msg} </a>
</a> );
); },
}, GetIdBotLink: function GetIdBotLink(msg) {
GetIdBotLink: function GetIdBotLink(msg) { return (
return ( <a
<a href="https://telegram.me/get_id_bot"
href="https://telegram.me/get_id_bot" className="text-indigo-100 hover:text-white hover:underline"
className="text-indigo-100 hover:text-white hover:underline" target="_blank"
target="_blank" rel="noreferrer"
rel="noreferrer" >
> {msg}
{msg} </a>
</a> );
); },
}, })}
})} </Alert>
</Alert>
</p>
<Form> <Form>
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5"> <div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
<label <label
@ -158,17 +158,17 @@ const NotificationsTelegram: React.FC = () => {
{intl.formatMessage(messages.botAPI)} {intl.formatMessage(messages.botAPI)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="max-w-lg flex rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="botAPI" id="botAPI"
name="botAPI" name="botAPI"
type="text" type="text"
placeholder={intl.formatMessage(messages.botAPI)} placeholder={intl.formatMessage(messages.botAPI)}
className="flex-1 form-input block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500" className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.botAPI && touched.botAPI && ( {errors.botAPI && touched.botAPI && (
<div className="text-red-500 mt-2">{errors.botAPI}</div> <div className="mt-2 text-red-500">{errors.botAPI}</div>
)} )}
</div> </div>
<label <label
@ -178,23 +178,47 @@ const NotificationsTelegram: React.FC = () => {
{intl.formatMessage(messages.chatId)} {intl.formatMessage(messages.chatId)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="max-w-lg flex rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="chatId" id="chatId"
name="chatId" name="chatId"
type="text" type="text"
placeholder={intl.formatMessage(messages.chatId)} placeholder={intl.formatMessage(messages.chatId)}
className="flex-1 form-input block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500" className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.chatId && touched.chatId && ( {errors.chatId && touched.chatId && (
<div className="text-red-500 mt-2">{errors.chatId}</div> <div className="mt-2 text-red-500">{errors.chatId}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-8 border-t border-gray-700 pt-5"> <div className="mt-6">
<div role="group" aria-labelledby="label-permissions">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline">
<div>
<div
className="text-base font-medium leading-6 text-gray-400 sm:text-sm sm:leading-5"
id="label-types"
>
{intl.formatMessage(messages.notificationtypes)}
</div>
</div>
<div className="mt-4 sm:mt-0 sm:col-span-2">
<div className="max-w-lg">
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) =>
setFieldValue('types', newTypes)
}
/>
</div>
</div>
</div>
</div>
</div>
<div className="pt-5 mt-8 border-t border-gray-700">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="ml-3 inline-flex 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}
@ -207,7 +231,7 @@ const NotificationsTelegram: React.FC = () => {
{intl.formatMessage(messages.test)} {intl.formatMessage(messages.test)}
</Button> </Button>
</span> </span>
<span className="ml-3 inline-flex rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
buttonType="primary" buttonType="primary"
type="submit" type="submit"

@ -60,6 +60,14 @@
"components.MovieDetails.viewfullcrew": "View Full Crew", "components.MovieDetails.viewfullcrew": "View Full Crew",
"components.MovieDetails.viewrequest": "View Request", "components.MovieDetails.viewrequest": "View Request",
"components.MovieDetails.watchtrailer": "Watch Trailer", "components.MovieDetails.watchtrailer": "Watch Trailer",
"components.NotificationTypeSelector.mediaapproved": "Media Approved",
"components.NotificationTypeSelector.mediaapprovedDescription": "Sends a notification when media is approved.",
"components.NotificationTypeSelector.mediaavailable": "Media Available",
"components.NotificationTypeSelector.mediaavailableDescription": "Sends a notification when media becomes available.",
"components.NotificationTypeSelector.mediafailed": "Media Failed",
"components.NotificationTypeSelector.mediafailedDescription": "Sends a notification when media fails to be added to services (Radarr/Sonarr). For certain agents, this will only send the notification to admins or users with the \"Manage Requests\" permission.",
"components.NotificationTypeSelector.mediarequested": "Media Requested",
"components.NotificationTypeSelector.mediarequestedDescription": "Sends a notification when new media is requested. For certain agents, this will only send the notification to admins or users with the \"Manage Requests\" permission.",
"components.PersonDetails.appearsin": "Appears in", "components.PersonDetails.appearsin": "Appears in",
"components.PersonDetails.ascharacter": "as {character}", "components.PersonDetails.ascharacter": "as {character}",
"components.PersonDetails.crewmember": "Crew Member", "components.PersonDetails.crewmember": "Crew Member",
@ -105,6 +113,7 @@
"components.RequestModal.status": "Status", "components.RequestModal.status": "Status",
"components.Search.searchresults": "Search Results", "components.Search.searchresults": "Search Results",
"components.Settings.Notifications.NotificationsSlack.agentenabled": "Agent Enabled", "components.Settings.Notifications.NotificationsSlack.agentenabled": "Agent Enabled",
"components.Settings.Notifications.NotificationsSlack.notificationtypes": "Notification Types",
"components.Settings.Notifications.NotificationsSlack.save": "Save Changes", "components.Settings.Notifications.NotificationsSlack.save": "Save Changes",
"components.Settings.Notifications.NotificationsSlack.saving": "Saving...", "components.Settings.Notifications.NotificationsSlack.saving": "Saving...",
"components.Settings.Notifications.NotificationsSlack.settingupslack": "Setting up Slack Notifications", "components.Settings.Notifications.NotificationsSlack.settingupslack": "Setting up Slack Notifications",
@ -128,6 +137,7 @@
"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!", "components.Settings.Notifications.emailsettingssaved": "Email notification settings saved!",
"components.Settings.Notifications.enableSsl": "Enable SSL", "components.Settings.Notifications.enableSsl": "Enable SSL",
"components.Settings.Notifications.notificationtypes": "Notification Types",
"components.Settings.Notifications.save": "Save Changes", "components.Settings.Notifications.save": "Save Changes",
"components.Settings.Notifications.saving": "Saving…", "components.Settings.Notifications.saving": "Saving…",
"components.Settings.Notifications.senderName": "Sender Name", "components.Settings.Notifications.senderName": "Sender Name",

Loading…
Cancel
Save