|
- toggleUser(user.id)}
- onKeyDown={(e) => {
- if (e.key === 'Enter' || e.key === 'Space') {
- toggleUser(user.id);
- }
- }}
- className="relative inline-flex h-5 w-10 flex-shrink-0 cursor-pointer items-center justify-center pt-2 focus:outline-none"
- >
-
-
-
+ toggleUser(user.id)}
+ />
|
diff --git a/src/components/UserList/PlexImportModal.tsx b/src/components/UserList/PlexImportModal.tsx
index fcf5441b5c..deda67d572 100644
--- a/src/components/UserList/PlexImportModal.tsx
+++ b/src/components/UserList/PlexImportModal.tsx
@@ -1,5 +1,6 @@
import Alert from '@app/components/Common/Alert';
import Modal from '@app/components/Common/Modal';
+import ToggleSwitch from '@app/components/Common/ToggleSwitch';
import useSettings from '@app/hooks/useSettings';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
@@ -135,31 +136,10 @@ const PlexImportModal = ({ onCancel, onComplete }: PlexImportProps) => {
|
- toggleAllUsers()}
- onKeyDown={(e) => {
- if (e.key === 'Enter' || e.key === 'Space') {
- toggleAllUsers();
- }
- }}
- className="relative inline-flex h-5 w-10 flex-shrink-0 cursor-pointer items-center justify-center pt-2 focus:outline-none"
- >
-
-
-
+ toggleAllUsers()}
+ />
|
{intl.formatMessage(messages.user)}
@@ -170,35 +150,10 @@ const PlexImportModal = ({ onCancel, onComplete }: PlexImportProps) => {
{data?.map((user) => (
|
|
- toggleUser(user.id)}
- onKeyDown={(e) => {
- if (e.key === 'Enter' || e.key === 'Space') {
- toggleUser(user.id);
- }
- }}
- className="relative inline-flex h-5 w-10 flex-shrink-0 cursor-pointer items-center justify-center pt-2 focus:outline-none"
- >
-
-
-
+ toggleUser(user.id)}
+ />
|
From 47c38fc8db77b92068bfd31b3c0884632db3ad62 Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Sat, 14 Jun 2025 02:55:55 +0200
Subject: [PATCH 43/62] fix(notifications): remove unused NotificationsNtfy
component
---
.../Notifications/NotificationsNtfy.tsx | 368 ------------------
.../NotificationModal/LunaSeaModal.tsx | 222 -----------
.../NotificationModal/index.tsx | 12 -
.../Settings/SettingsNotifications/index.tsx | 13 -
4 files changed, 615 deletions(-)
delete mode 100644 src/components/Settings/Notifications/NotificationsNtfy.tsx
delete mode 100644 src/components/Settings/SettingsNotifications/NotificationModal/LunaSeaModal.tsx
diff --git a/src/components/Settings/Notifications/NotificationsNtfy.tsx b/src/components/Settings/Notifications/NotificationsNtfy.tsx
deleted file mode 100644
index 200c79731a..0000000000
--- a/src/components/Settings/Notifications/NotificationsNtfy.tsx
+++ /dev/null
@@ -1,368 +0,0 @@
-import Button from '@app/components/Common/Button';
-import LoadingSpinner from '@app/components/Common/LoadingSpinner';
-import SensitiveInput from '@app/components/Common/SensitiveInput';
-import NotificationTypeSelector from '@app/components/NotificationTypeSelector';
-import globalMessages from '@app/i18n/globalMessages';
-import defineMessages from '@app/utils/defineMessages';
-import { isValidURL } from '@app/utils/urlValidationHelper';
-import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
-import type { NotificationAgentNtfy } from '@server/interfaces/settings';
-import axios from 'axios';
-import { Field, Form, Formik } from 'formik';
-import { useState } from 'react';
-import { useIntl } from 'react-intl';
-import { useToasts } from 'react-toast-notifications';
-import useSWR from 'swr';
-import * as Yup from 'yup';
-
-const messages = defineMessages(
- 'components.Settings.Notifications.NotificationsNtfy',
- {
- agentenabled: 'Enable Agent',
- url: 'Server root URL',
- topic: 'Topic',
- usernamePasswordAuth: 'Username + Password authentication',
- username: 'Username',
- password: 'Password',
- tokenAuth: 'Token authentication',
- token: 'Token',
- ntfysettingssaved: 'Ntfy notification settings saved successfully!',
- ntfysettingsfailed: 'Ntfy notification settings failed to save.',
- toastNtfyTestSending: 'Sending ntfy test notification…',
- toastNtfyTestSuccess: 'Ntfy test notification sent!',
- toastNtfyTestFailed: 'Ntfy test notification failed to send.',
- validationNtfyUrl: 'You must provide a valid URL',
- validationNtfyTopic: 'You must provide a topic',
- validationTypes: 'You must select at least one notification type',
- }
-);
-
-const NotificationsNtfy = () => {
- const intl = useIntl();
- const { addToast, removeToast } = useToasts();
- const [isTesting, setIsTesting] = useState(false);
- const {
- data,
- error,
- mutate: revalidate,
- } = useSWR ('/api/v1/settings/notifications/ntfy');
-
- const NotificationsNtfySchema = Yup.object().shape({
- url: Yup.string()
- .when('enabled', {
- is: true,
- then: Yup.string()
- .nullable()
- .required(intl.formatMessage(messages.validationNtfyUrl)),
- otherwise: Yup.string().nullable(),
- })
- .test(
- 'valid-url',
- intl.formatMessage(messages.validationNtfyUrl),
- isValidURL
- ),
- topic: Yup.string()
- .when('enabled', {
- is: true,
- then: Yup.string()
- .nullable()
- .required(intl.formatMessage(messages.validationNtfyUrl)),
- otherwise: Yup.string().nullable(),
- })
- .defined(intl.formatMessage(messages.validationNtfyTopic)),
- });
-
- if (!data && !error) {
- return ;
- }
-
- return (
- {
- try {
- await axios.post('/api/v1/settings/notifications/ntfy', {
- enabled: values.enabled,
- types: values.types,
- options: {
- url: values.url,
- topic: values.topic,
- authMethodUsernamePassword: values.authMethodUsernamePassword,
- username: values.username,
- password: values.password,
- authMethodToken: values.authMethodToken,
- token: values.token,
- },
- });
-
- addToast(intl.formatMessage(messages.ntfysettingssaved), {
- appearance: 'success',
- autoDismiss: true,
- });
- } catch (e) {
- addToast(intl.formatMessage(messages.ntfysettingsfailed), {
- appearance: 'error',
- autoDismiss: true,
- });
- } finally {
- revalidate();
- }
- }}
- >
- {({
- errors,
- touched,
- isSubmitting,
- values,
- isValid,
- setFieldValue,
- setFieldTouched,
- }) => {
- const testSettings = async () => {
- setIsTesting(true);
- let toastId: string | undefined;
- try {
- addToast(
- intl.formatMessage(messages.toastNtfyTestSending),
- {
- autoDismiss: false,
- appearance: 'info',
- },
- (id) => {
- toastId = id;
- }
- );
- await axios.post('/api/v1/settings/notifications/ntfy/test', {
- enabled: true,
- types: values.types,
- options: {
- url: values.url,
- topic: values.topic,
- authMethodUsernamePassword: values.authMethodUsernamePassword,
- username: values.username,
- password: values.password,
- authMethodToken: values.authMethodToken,
- token: values.token,
- },
- });
-
- if (toastId) {
- removeToast(toastId);
- }
- addToast(intl.formatMessage(messages.toastNtfyTestSuccess), {
- autoDismiss: true,
- appearance: 'success',
- });
- } catch (e) {
- if (toastId) {
- removeToast(toastId);
- }
- addToast(intl.formatMessage(messages.toastNtfyTestFailed), {
- autoDismiss: true,
- appearance: 'error',
- });
- } finally {
- setIsTesting(false);
- }
- };
-
- return (
-
- );
- }}
-
- );
-};
-
-export default NotificationsNtfy;
diff --git a/src/components/Settings/SettingsNotifications/NotificationModal/LunaSeaModal.tsx b/src/components/Settings/SettingsNotifications/NotificationModal/LunaSeaModal.tsx
deleted file mode 100644
index 7a6f2384a9..0000000000
--- a/src/components/Settings/SettingsNotifications/NotificationModal/LunaSeaModal.tsx
+++ /dev/null
@@ -1,222 +0,0 @@
-import Modal from '@app/components/Common/Modal';
-import NotificationTypeSelector from '@app/components/NotificationTypeSelector';
-import { NotificationModalType } from '@app/components/Settings/SettingsNotifications/NotificationModal';
-import globalMessages from '@app/i18n/globalMessages';
-import defineMessages from '@app/utils/defineMessages';
-import type { NotificationAgentLunaSea } from '@server/interfaces/settings';
-import { Field, Form, Formik } from 'formik';
-import { useIntl } from 'react-intl';
-import * as Yup from 'yup';
-
-const messages = defineMessages(
- 'components.Settings.SettingsNotifications.NotificationModal',
- {
- editTitle: 'Edit Notification Instance',
- createTitle: 'Create Notification Instance',
- createInstance: 'Create Instance',
- instanceName: 'Name',
- lunaSeaWebhookUrl: 'Webhook URL',
- lunaSeaWebhookUrlTip:
- 'Your user- or device-based notification webhook URL',
- lunaSeaValidationWebhookUrl: 'You must provide a valid URL',
- lunaSeaProfileName: 'Profile Name',
- lunaSeaProfileNameTip:
- 'Only required if not using the default profile',
- validationTypes: 'You must select at least one notification type',
- }
-);
-
-interface LunaSeaModalProps {
- type: NotificationModalType;
- data: NotificationAgentLunaSea;
- onClose: () => void;
- onTest: (testData: NotificationAgentLunaSea) => void;
- onSave: (submitData: NotificationAgentLunaSea) => void;
-}
-
-const LunaSeaModal = ({
- type,
- data,
- onClose,
- onTest,
- onSave,
-}: LunaSeaModalProps) => {
- const intl = useIntl();
-
- const NotificationsLunaSeaSchema = Yup.object().shape({
- webhookUrl: Yup.string()
- .when('enabled', {
- is: true,
- then: Yup.string()
- .nullable()
- .required(intl.formatMessage(messages.lunaSeaValidationWebhookUrl)),
- otherwise: Yup.string().nullable(),
- })
- .url(intl.formatMessage(messages.lunaSeaValidationWebhookUrl)),
- });
-
- return (
- {
- await onSave({
- enabled: values.enabled,
- types: values.types,
- name: values.name,
- id: values.id,
- agent: values.agent,
- default: values.default,
- options: {
- webhookUrl: values.webhookUrl,
- profileName: values.profileName,
- },
- });
- }}
- >
- {({
- errors,
- touched,
- isSubmitting,
- values,
- isValid,
- setFieldValue,
- setFieldTouched,
- handleSubmit,
- }) => {
- const title =
- type === NotificationModalType.EDIT
- ? `${intl.formatMessage(messages.editTitle)} #${data?.id}`
- : intl.formatMessage(messages.createTitle);
-
- return (
- onClose()}
- secondaryButtonType="warning"
- secondaryText={intl.formatMessage(globalMessages.test)}
- secondaryDisabled={isSubmitting || !isValid}
- onSecondary={() =>
- onTest({
- enabled: values.enabled,
- types: values.types,
- name: values.name,
- id: values.id,
- agent: values.agent,
- default: values.default,
- options: {
- webhookUrl: values.webhookUrl,
- profileName: values.profileName,
- },
- })
- }
- okButtonType="primary"
- okText={
- isSubmitting
- ? intl.formatMessage(globalMessages.saving)
- : type === NotificationModalType.EDIT
- ? intl.formatMessage(globalMessages.save)
- : intl.formatMessage(messages.createInstance)
- }
- onOk={() => {
- handleSubmit();
- }}
- okDisabled={isSubmitting || !isValid}
- >
-
-
- );
- }}
-
- );
-};
-
-export default LunaSeaModal;
diff --git a/src/components/Settings/SettingsNotifications/NotificationModal/index.tsx b/src/components/Settings/SettingsNotifications/NotificationModal/index.tsx
index 952ec30e98..3d4fff857c 100644
--- a/src/components/Settings/SettingsNotifications/NotificationModal/index.tsx
+++ b/src/components/Settings/SettingsNotifications/NotificationModal/index.tsx
@@ -1,7 +1,6 @@
import DiscordModal from '@app/components/Settings/SettingsNotifications/NotificationModal/DiscordModal';
import EmailModal from '@app/components/Settings/SettingsNotifications/NotificationModal/EmailModal';
import GotifyModal from '@app/components/Settings/SettingsNotifications/NotificationModal/GotifyModal';
-import LunaSeaModal from '@app/components/Settings/SettingsNotifications/NotificationModal/LunaSeaModal';
import NtfyModal from '@app/components/Settings/SettingsNotifications/NotificationModal/NtfyModal';
import PushbulletModal from '@app/components/Settings/SettingsNotifications/NotificationModal/PushbulletModal';
import PushoverModal from '@app/components/Settings/SettingsNotifications/NotificationModal/PushoverModal';
@@ -15,7 +14,6 @@ import type {
NotificationAgentDiscord,
NotificationAgentEmail,
NotificationAgentGotify,
- NotificationAgentLunaSea,
NotificationAgentNtfy,
NotificationAgentPushbullet,
NotificationAgentPushover,
@@ -178,16 +176,6 @@ const NotificationModal = ({
onSave={type === NotificationModalType.EDIT ? onSave : onCreate}
/>
);
- case NotificationAgentKey.LUNASEA:
- return (
-
- );
case NotificationAgentKey.PUSHBULLET:
return (
{
Ntfy
-
- setNotificationModal({
- open: true,
- instance: data.agentTemplates.lunasea,
- type: NotificationModalType.CREATE,
- })
- }
- >
-
- LunaSea
-
setNotificationModal({
From ea2d245ac6d23a5f6caee0a69089bd2630be2354 Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Fri, 29 Aug 2025 19:52:17 +0200
Subject: [PATCH 44/62] feat(notifications): extract NotificationInstanceList
component
---
.../NotificationInstanceList.tsx | 349 ++++++++++++++++++
.../Settings/SettingsNotifications/index.tsx | 314 +---------------
2 files changed, 368 insertions(+), 295 deletions(-)
create mode 100644 src/components/Settings/SettingsNotifications/NotificationInstanceList.tsx
diff --git a/src/components/Settings/SettingsNotifications/NotificationInstanceList.tsx b/src/components/Settings/SettingsNotifications/NotificationInstanceList.tsx
new file mode 100644
index 0000000000..d45dcdac83
--- /dev/null
+++ b/src/components/Settings/SettingsNotifications/NotificationInstanceList.tsx
@@ -0,0 +1,349 @@
+import Button from '@app/components/Common/Button';
+import Table from '@app/components/Common/Table';
+import ToggleSwitch from '@app/components/Common/ToggleSwitch';
+import globalMessages from '@app/i18n/globalMessages';
+import defineMessages from '@app/utils/defineMessages';
+import {
+ BeakerIcon,
+ ChevronLeftIcon,
+ ChevronRightIcon,
+ PencilIcon,
+ TrashIcon,
+} from '@heroicons/react/24/solid';
+import type { NotificationAgentConfig } from '@server/interfaces/settings';
+import axios from 'axios';
+import { useRouter } from 'next/router';
+import { useIntl } from 'react-intl';
+import { useToasts } from 'react-toast-notifications';
+
+const messages = defineMessages(
+ 'components.Settings.SettingsNotifications.NotificationInstanceList',
+ {
+ noNotificationInstances: 'No Notification Instances existing yet.',
+ instanceName: 'Name',
+ instanceId: 'ID',
+ notificationAgent: 'Agent',
+ instanceEnabled: 'Enabled',
+ instanceDefault: 'Default',
+ instanceDeleted: 'Notification instance deleted successfully!',
+ instanceDeleteError:
+ 'Something went wrong while deleting the notification instance.',
+ toastTestSending: 'Sending test notification…',
+ toastTestSuccess: 'Test notification sent!',
+ toastTestFailed: 'Test notification failed to send.',
+ toastEnabledToggleSuccess:
+ 'Notification instance enabled toggled successfully!',
+ toastEnabledToggleFailed: 'Notification instance failed to toggle enabled!',
+ toastDefaultToggleSuccess:
+ 'Notification instance default toggled successfully!',
+ toastDefaultToggleFailed: 'Notification instance failed to toggle default!',
+ }
+);
+
+interface NotificationInstanceListProps {
+ instances: NotificationAgentConfig[];
+ revalidateInstances: () => void;
+ onEdit: (instance: NotificationAgentConfig) => void;
+ currentPageIndex: number;
+ currentPageSize: number;
+ setCurrentPageSize: (pageSize: number) => void;
+ hasPrevPage: boolean;
+ hasNextPage: boolean;
+ updateQueryParams: (key: string, value?: string | undefined) => void;
+ totalItemsSize: number;
+}
+
+function NotificationInstanceList({
+ instances,
+ revalidateInstances,
+ onEdit,
+ currentPageIndex,
+ currentPageSize,
+ setCurrentPageSize,
+ hasPrevPage,
+ hasNextPage,
+ updateQueryParams,
+ totalItemsSize: totalSize,
+}: NotificationInstanceListProps) {
+ const intl = useIntl();
+ const router = useRouter();
+ const { addToast, removeToast } = useToasts();
+
+ const deleteInstance = async (instanceId: number) => {
+ try {
+ await axios.delete(`/api/v1/settings/notification/${instanceId}`);
+
+ addToast(intl.formatMessage(messages.instanceDeleted), {
+ autoDismiss: true,
+ appearance: 'success',
+ });
+ } catch (e) {
+ addToast(intl.formatMessage(messages.instanceDeleteError), {
+ autoDismiss: true,
+ appearance: 'error',
+ });
+ } finally {
+ revalidateInstances();
+ }
+ };
+
+ const testInstance = async (instanceIndex: number) => {
+ let toastId: string | undefined;
+ try {
+ addToast(
+ intl.formatMessage(messages.toastTestSending),
+ {
+ autoDismiss: false,
+ appearance: 'info',
+ },
+ (id) => {
+ toastId = id;
+ }
+ );
+ await axios.post(
+ '/api/v1/settings/notification/test',
+ instances[instanceIndex]
+ );
+
+ if (toastId) {
+ removeToast(toastId);
+ }
+ addToast(intl.formatMessage(messages.toastTestSuccess), {
+ autoDismiss: true,
+ appearance: 'success',
+ });
+ } catch (e) {
+ if (toastId) {
+ removeToast(toastId);
+ }
+ addToast(intl.formatMessage(messages.toastTestFailed), {
+ autoDismiss: true,
+ appearance: 'error',
+ });
+ }
+ };
+
+ const toggleInstanceEnabled = async (instance: NotificationAgentConfig) => {
+ instance.enabled = !instance.enabled;
+
+ if (!instance.enabled) {
+ instance.default = false;
+ }
+
+ try {
+ await axios.post(
+ `/api/v1/settings/notification/${instance.id}`,
+ instance
+ );
+
+ addToast(intl.formatMessage(messages.toastEnabledToggleSuccess), {
+ appearance: 'success',
+ autoDismiss: true,
+ });
+ } catch (e) {
+ addToast(intl.formatMessage(messages.toastEnabledToggleFailed), {
+ appearance: 'error',
+ autoDismiss: true,
+ });
+ } finally {
+ revalidateInstances();
+ }
+ };
+
+ const toggleInstanceDefault = async (instance: NotificationAgentConfig) => {
+ const currentDefault = instances.find(
+ (result) =>
+ result.agent === instance.agent &&
+ result.default &&
+ result.id !== instance.id
+ );
+
+ if (currentDefault) {
+ return;
+ }
+
+ instance.default = !instance.default;
+
+ try {
+ await axios.post(
+ `/api/v1/settings/notification/${instance.id}`,
+ instance
+ );
+
+ addToast(intl.formatMessage(messages.toastDefaultToggleSuccess), {
+ appearance: 'success',
+ autoDismiss: true,
+ });
+ } catch (e) {
+ addToast(intl.formatMessage(messages.toastDefaultToggleFailed), {
+ appearance: 'error',
+ autoDismiss: true,
+ });
+ } finally {
+ revalidateInstances();
+ }
+ };
+
+ if (!instances.length) {
+ return (
+
+ {intl.formatMessage(messages.noNotificationInstances)}
+
+ );
+ }
+
+ return (
+
+
+
+ {intl.formatMessage(messages.instanceName)}
+ {intl.formatMessage(messages.instanceId)}
+ {intl.formatMessage(messages.notificationAgent)}
+ {intl.formatMessage(messages.instanceEnabled)}
+ {intl.formatMessage(messages.instanceDefault)}
+
+
+
+
+
+ {instances.map((instance, instanceIndex) => (
+
+ {instance.name}
+ {instance.id}
+ {instance.agent}
+
+ toggleInstanceEnabled(instance)}
+ highContrast
+ />
+
+
+ {
+ toggleInstanceDefault(instance);
+ }}
+ disabled={
+ !instance.enabled ||
+ instances.findIndex(
+ (result) =>
+ result.agent === instance.agent &&
+ result.default &&
+ result.id !== instance.id
+ ) !== -1
+ }
+ checked={instance.default}
+ />
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+ );
+}
+
+export default NotificationInstanceList;
diff --git a/src/components/Settings/SettingsNotifications/index.tsx b/src/components/Settings/SettingsNotifications/index.tsx
index b5a972efc5..ff8efc6878 100644
--- a/src/components/Settings/SettingsNotifications/index.tsx
+++ b/src/components/Settings/SettingsNotifications/index.tsx
@@ -5,13 +5,11 @@ import PushbulletLogo from '@app/assets/extlogos/pushbullet.svg';
import PushoverLogo from '@app/assets/extlogos/pushover.svg';
import SlackLogo from '@app/assets/extlogos/slack.svg';
import TelegramLogo from '@app/assets/extlogos/telegram.svg';
-import Button from '@app/components/Common/Button';
import Dropdown from '@app/components/Common/Dropdown';
import Header from '@app/components/Common/Header';
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import PageTitle from '@app/components/Common/PageTitle';
-import Table from '@app/components/Common/Table';
-import ToggleSwitch from '@app/components/Common/ToggleSwitch';
+import NotificationInstanceList from '@app/components/Settings/SettingsNotifications/NotificationInstanceList';
import NotificationModal, {
NotificationModalType,
} from '@app/components/Settings/SettingsNotifications/NotificationModal';
@@ -21,23 +19,16 @@ import defineMessages from '@app/utils/defineMessages';
import { Transition } from '@headlessui/react';
import {
BarsArrowDownIcon,
- BeakerIcon,
BoltIcon,
- ChevronLeftIcon,
- ChevronRightIcon,
CloudIcon,
EnvelopeIcon,
- PencilIcon,
PlusIcon,
- TrashIcon,
} from '@heroicons/react/24/solid';
import type { NotificationSettingsResultResponse } from '@server/interfaces/api/settingsInterfaces';
import type { NotificationAgentConfig } from '@server/interfaces/settings';
-import axios from 'axios';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
-import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr';
const messages = defineMessages('components.Settings.SettingsNotifications', {
@@ -47,20 +38,6 @@ const messages = defineMessages('components.Settings.SettingsNotifications', {
instanceName: 'Name',
instanceId: 'ID',
notificationAgent: 'Agent',
- instanceEnabled: 'Enabled',
- instanceDefault: 'Default',
- instanceDeleted: 'Notification instance deleted successfully!',
- instanceDeleteError:
- 'Something went wrong while deleting the notification instance.',
- toastTestSending: 'Sending test notification…',
- toastTestSuccess: 'Test notification sent!',
- toastTestFailed: 'Test notification failed to send.',
- toastEnabledToggleSuccess:
- 'Notification instance enabled toggled successfully!',
- toastEnabledToggleFailed: 'Notification instance failed to toggle enabled!',
- toastDefaultToggleSuccess:
- 'Notification instance default toggled successfully!',
- toastDefaultToggleFailed: 'Notification instance failed to toggle default!',
});
enum Sort {
@@ -72,7 +49,6 @@ enum Sort {
const SettingsNotifications = () => {
const intl = useIntl();
const router = useRouter();
- const { addToast, removeToast } = useToasts();
const [currentSort, setCurrentSort] = useState(Sort.ID);
const [currentPageSize, setCurrentPageSize] = useState(10);
const [notificationModal, setNotificationModal] = useState<{
@@ -116,119 +92,12 @@ const SettingsNotifications = () => {
);
}, [currentSort, currentPageSize]);
- const deleteInstance = async (instanceId: number) => {
- try {
- await axios.delete(`/api/v1/settings/notification/${instanceId}`);
-
- addToast(intl.formatMessage(messages.instanceDeleted), {
- autoDismiss: true,
- appearance: 'success',
- });
- } catch (e) {
- addToast(intl.formatMessage(messages.instanceDeleteError), {
- autoDismiss: true,
- appearance: 'error',
- });
- } finally {
- revalidate();
- }
- };
-
- const testInstance = async (instanceIndex: number) => {
- let toastId: string | undefined;
- try {
- addToast(
- intl.formatMessage(messages.toastTestSending),
- {
- autoDismiss: false,
- appearance: 'info',
- },
- (id) => {
- toastId = id;
- }
- );
- await axios.post(
- '/api/v1/settings/notification/test',
- data?.results[instanceIndex]
- );
-
- if (toastId) {
- removeToast(toastId);
- }
- addToast(intl.formatMessage(messages.toastTestSuccess), {
- autoDismiss: true,
- appearance: 'success',
- });
- } catch (e) {
- if (toastId) {
- removeToast(toastId);
- }
- addToast(intl.formatMessage(messages.toastTestFailed), {
- autoDismiss: true,
- appearance: 'error',
- });
- }
- };
-
- const toggleInstanceEnabled = async (instance: NotificationAgentConfig) => {
- instance.enabled = !instance.enabled;
-
- if (!instance.enabled) {
- instance.default = false;
- }
-
- try {
- await axios.post(
- `/api/v1/settings/notification/${instance.id}`,
- instance
- );
-
- addToast(intl.formatMessage(messages.toastEnabledToggleSuccess), {
- appearance: 'success',
- autoDismiss: true,
- });
- } catch (e) {
- addToast(intl.formatMessage(messages.toastEnabledToggleFailed), {
- appearance: 'error',
- autoDismiss: true,
- });
- } finally {
- revalidate();
- }
- };
-
- const toggleInstanceDefault = async (instance: NotificationAgentConfig) => {
- const currentDefault = data?.results.find(
- (result) =>
- result.agent === instance.agent &&
- result.default &&
- result.id !== instance.id
- );
-
- if (currentDefault) {
- return;
- }
-
- instance.default = !instance.default;
-
- try {
- await axios.post(
- `/api/v1/settings/notification/${instance.id}`,
- instance
- );
-
- addToast(intl.formatMessage(messages.toastDefaultToggleSuccess), {
- appearance: 'success',
- autoDismiss: true,
- });
- } catch (e) {
- addToast(intl.formatMessage(messages.toastDefaultToggleFailed), {
- appearance: 'error',
- autoDismiss: true,
- });
- } finally {
- revalidate();
- }
+ const onNotificationInstanceEdit = (instance: NotificationAgentConfig) => {
+ setNotificationModal({
+ open: true,
+ instance: instance,
+ type: NotificationModalType.EDIT,
+ });
};
if (!data) {
@@ -442,163 +311,18 @@ const SettingsNotifications = () => {
-
-
-
- {intl.formatMessage(messages.instanceName)}
- {intl.formatMessage(messages.instanceId)}
-
- {intl.formatMessage(messages.notificationAgent)}
-
- {intl.formatMessage(messages.instanceEnabled)}
- {intl.formatMessage(messages.instanceDefault)}
-
-
-
-
-
- {data.results.map((instance, instanceIndex) => (
-
- {instance.name}
- {instance.id}
- {instance.agent}
-
- toggleInstanceEnabled(instance)}
- highContrast
- />
-
-
- {
- toggleInstanceDefault(instance);
- }}
- disabled={
- !instance.enabled ||
- data.results.findIndex(
- (result) =>
- result.agent === instance.agent &&
- result.default &&
- result.id !== instance.id
- ) !== -1
- }
- checked={instance.default}
- />
-
-
-
-
-
-
-
- ))}
-
-
-
-
-
-
-
+
>
);
};
From 2af3d3747a010f26acb22b4ab95eb80b8ed67d69 Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Fri, 29 Aug 2025 20:16:58 +0200
Subject: [PATCH 45/62] feat(notifications): extract
NotificationInstanceDropdown component
---
.../NotificationInstanceDropdown.tsx | 94 ++++++++++
.../Settings/SettingsNotifications/index.tsx | 170 +++---------------
2 files changed, 114 insertions(+), 150 deletions(-)
create mode 100644 src/components/Settings/SettingsNotifications/NotificationInstanceDropdown.tsx
diff --git a/src/components/Settings/SettingsNotifications/NotificationInstanceDropdown.tsx b/src/components/Settings/SettingsNotifications/NotificationInstanceDropdown.tsx
new file mode 100644
index 0000000000..da3b198343
--- /dev/null
+++ b/src/components/Settings/SettingsNotifications/NotificationInstanceDropdown.tsx
@@ -0,0 +1,94 @@
+import DiscordLogo from '@app/assets/extlogos/discord.svg';
+import GotifyLogo from '@app/assets/extlogos/gotify.svg';
+import NtfyLogo from '@app/assets/extlogos/ntfy.svg';
+import PushbulletLogo from '@app/assets/extlogos/pushbullet.svg';
+import PushoverLogo from '@app/assets/extlogos/pushover.svg';
+import SlackLogo from '@app/assets/extlogos/slack.svg';
+import TelegramLogo from '@app/assets/extlogos/telegram.svg';
+import Dropdown from '@app/components/Common/Dropdown';
+import defineMessages from '@app/utils/defineMessages';
+import {
+ BoltIcon,
+ CloudIcon,
+ EnvelopeIcon,
+ PlusIcon,
+} from '@heroicons/react/24/solid';
+import { NotificationAgentKey } from '@server/interfaces/settings';
+import { useIntl } from 'react-intl';
+
+const messages = defineMessages(
+ 'components.Settings.SettingsNotifications.NotificationInstanceDropdown',
+ {
+ createNotificationInstance: 'Create Notification Instance',
+ }
+);
+
+interface NotificationInstanceDropdownProps {
+ onSelect: (agentKey: NotificationAgentKey) => void;
+}
+
+function NotificationInstanceDropdown({
+ onSelect,
+}: NotificationInstanceDropdownProps) {
+ const intl = useIntl();
+
+ return (
+
+
+
+ {intl.formatMessage(messages.createNotificationInstance)}
+ >
+ }
+ >
+ onSelect(NotificationAgentKey.DISCORD)}>
+
+ Discord
+
+ onSelect(NotificationAgentKey.EMAIL)}>
+
+ Email
+
+ onSelect(NotificationAgentKey.GOTIFY)}>
+
+ Gotify
+
+ onSelect(NotificationAgentKey.NTFY)}>
+
+ Ntfy
+
+ onSelect(NotificationAgentKey.PUSHBULLET)}
+ >
+
+ Pushbullet
+
+ onSelect(NotificationAgentKey.PUSHOVER)}>
+
+ Pushover
+
+ onSelect(NotificationAgentKey.SLACK)}>
+
+ Slack
+
+ onSelect(NotificationAgentKey.TELEGRAM)}>
+
+ Telegram
+
+ onSelect(NotificationAgentKey.WEBHOOK)}>
+
+ Webhook
+
+ onSelect(NotificationAgentKey.WEBPUSH)}>
+
+ WebPush
+
+
+
+ );
+}
+
+export default NotificationInstanceDropdown;
diff --git a/src/components/Settings/SettingsNotifications/index.tsx b/src/components/Settings/SettingsNotifications/index.tsx
index ff8efc6878..ea684259f1 100644
--- a/src/components/Settings/SettingsNotifications/index.tsx
+++ b/src/components/Settings/SettingsNotifications/index.tsx
@@ -1,14 +1,7 @@
-import DiscordLogo from '@app/assets/extlogos/discord.svg';
-import GotifyLogo from '@app/assets/extlogos/gotify.svg';
-import NtfyLogo from '@app/assets/extlogos/ntfy.svg';
-import PushbulletLogo from '@app/assets/extlogos/pushbullet.svg';
-import PushoverLogo from '@app/assets/extlogos/pushover.svg';
-import SlackLogo from '@app/assets/extlogos/slack.svg';
-import TelegramLogo from '@app/assets/extlogos/telegram.svg';
-import Dropdown from '@app/components/Common/Dropdown';
import Header from '@app/components/Common/Header';
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import PageTitle from '@app/components/Common/PageTitle';
+import NotificationInstanceDropdown from '@app/components/Settings/SettingsNotifications/NotificationInstanceDropdown';
import NotificationInstanceList from '@app/components/Settings/SettingsNotifications/NotificationInstanceList';
import NotificationModal, {
NotificationModalType,
@@ -17,15 +10,13 @@ import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { Transition } from '@headlessui/react';
-import {
- BarsArrowDownIcon,
- BoltIcon,
- CloudIcon,
- EnvelopeIcon,
- PlusIcon,
-} from '@heroicons/react/24/solid';
+import { BarsArrowDownIcon } from '@heroicons/react/24/solid';
import type { NotificationSettingsResultResponse } from '@server/interfaces/api/settingsInterfaces';
-import type { NotificationAgentConfig } from '@server/interfaces/settings';
+import type {
+ NotificationAgentConfig,
+ NotificationAgentKey,
+ NotificationAgentTemplates,
+} from '@server/interfaces/settings';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -34,7 +25,6 @@ import useSWR from 'swr';
const messages = defineMessages('components.Settings.SettingsNotifications', {
notifications: 'Notifications',
notificationInstanceList: 'Notification Instance List',
- createNotificationInstance: 'Create Notification Instance',
instanceName: 'Name',
instanceId: 'ID',
notificationAgent: 'Agent',
@@ -100,6 +90,15 @@ const SettingsNotifications = () => {
});
};
+ const onNotificationInstanceCreate = (agentKey: NotificationAgentKey) => {
+ setNotificationModal({
+ open: true,
+ instance:
+ data?.agentTemplates[agentKey as keyof NotificationAgentTemplates],
+ type: NotificationModalType.CREATE,
+ });
+ };
+
if (!data) {
return ;
}
@@ -150,139 +149,10 @@ const SettingsNotifications = () => {
{intl.formatMessage(messages.notificationInstanceList)}
-
-
-
- {intl.formatMessage(messages.createNotificationInstance)}
- >
- }
- >
-
- setNotificationModal({
- open: true,
- instance: data.agentTemplates.discord,
- type: NotificationModalType.CREATE,
- })
- }
- >
-
- Discord
-
-
- setNotificationModal({
- open: true,
- instance: data.agentTemplates.email,
- type: NotificationModalType.CREATE,
- })
- }
- >
-
- Email
-
-
- setNotificationModal({
- open: true,
- instance: data.agentTemplates.gotify,
- type: NotificationModalType.CREATE,
- })
- }
- >
-
- Gotify
-
-
- setNotificationModal({
- open: true,
- instance: data.agentTemplates.ntfy,
- type: NotificationModalType.CREATE,
- })
- }
- >
-
- Ntfy
-
-
- setNotificationModal({
- open: true,
- instance: data.agentTemplates.pushbullet,
- type: NotificationModalType.CREATE,
- })
- }
- >
-
- Pushbullet
-
-
- setNotificationModal({
- open: true,
- instance: data.agentTemplates.pushover,
- type: NotificationModalType.CREATE,
- })
- }
- >
-
- Pushover
-
-
- setNotificationModal({
- open: true,
- instance: data.agentTemplates.slack,
- type: NotificationModalType.CREATE,
- })
- }
- >
-
- Slack
-
-
- setNotificationModal({
- open: true,
- instance: data.agentTemplates.telegram,
- type: NotificationModalType.CREATE,
- })
- }
- >
-
- Telegram
-
-
- setNotificationModal({
- open: true,
- instance: data.agentTemplates.webhook,
- type: NotificationModalType.CREATE,
- })
- }
- >
-
- Webhook
-
-
- setNotificationModal({
- open: true,
- instance: data.agentTemplates.webpush,
- type: NotificationModalType.CREATE,
- })
- }
- >
-
- WebPush
-
-
-
+
+
From a3873aa0e269b101489e8f0f688d472258ada2f6 Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Wed, 3 Sep 2025 00:30:52 +0200
Subject: [PATCH 46/62] feat(notifications): generate translation keys
---
src/i18n/locale/en.json | 35 ++++++++++++++++++-----------------
1 file changed, 18 insertions(+), 17 deletions(-)
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json
index 7a0efee127..292c91b8f5 100644
--- a/src/i18n/locale/en.json
+++ b/src/i18n/locale/en.json
@@ -866,6 +866,22 @@
"components.Settings.SettingsNetwork.trustProxy": "Enable Proxy Support",
"components.Settings.SettingsNetwork.trustProxyTip": "Allow Jellyseerr to correctly register client IP addresses behind a proxy",
"components.Settings.SettingsNetwork.validationProxyPort": "You must provide a valid port",
+ "components.Settings.SettingsNotifications.NotificationInstanceDropdown.createNotificationInstance": "Create Notification Instance",
+ "components.Settings.SettingsNotifications.NotificationInstanceList.instanceDefault": "Default",
+ "components.Settings.SettingsNotifications.NotificationInstanceList.instanceDeleteError": "Something went wrong while deleting the notification instance.",
+ "components.Settings.SettingsNotifications.NotificationInstanceList.instanceDeleted": "Notification instance deleted successfully!",
+ "components.Settings.SettingsNotifications.NotificationInstanceList.instanceEnabled": "Enabled",
+ "components.Settings.SettingsNotifications.NotificationInstanceList.instanceId": "ID",
+ "components.Settings.SettingsNotifications.NotificationInstanceList.instanceName": "Name",
+ "components.Settings.SettingsNotifications.NotificationInstanceList.noNotificationInstances": "No Notification Instances existing yet.",
+ "components.Settings.SettingsNotifications.NotificationInstanceList.notificationAgent": "Agent",
+ "components.Settings.SettingsNotifications.NotificationInstanceList.toastDefaultToggleFailed": "Notification instance failed to toggle default!",
+ "components.Settings.SettingsNotifications.NotificationInstanceList.toastDefaultToggleSuccess": "Notification instance default toggled successfully!",
+ "components.Settings.SettingsNotifications.NotificationInstanceList.toastEnabledToggleFailed": "Notification instance failed to toggle enabled!",
+ "components.Settings.SettingsNotifications.NotificationInstanceList.toastEnabledToggleSuccess": "Notification instance enabled toggled successfully!",
+ "components.Settings.SettingsNotifications.NotificationInstanceList.toastTestFailed": "Test notification failed to send.",
+ "components.Settings.SettingsNotifications.NotificationInstanceList.toastTestSending": "Sending test notification…",
+ "components.Settings.SettingsNotifications.NotificationInstanceList.toastTestSuccess": "Test notification sent!",
"components.Settings.SettingsNotifications.NotificationModal.createInstance": "Create Instance",
"components.Settings.SettingsNotifications.NotificationModal.createTitle": "Create Notification Instance",
"components.Settings.SettingsNotifications.NotificationModal.discordBotAvatarUrl": "Bot Avatar URL",
@@ -910,11 +926,6 @@
"components.Settings.SettingsNotifications.NotificationModal.gotifyValidationUrlRequired": "You must provide a valid URL",
"components.Settings.SettingsNotifications.NotificationModal.gotifyValidationUrlTrailingSlash": "URL must not end in a trailing slash",
"components.Settings.SettingsNotifications.NotificationModal.instanceName": "Name",
- "components.Settings.SettingsNotifications.NotificationModal.lunaSeaProfileName": "Profile Name",
- "components.Settings.SettingsNotifications.NotificationModal.lunaSeaProfileNameTip": "Only required if not using the default profile",
- "components.Settings.SettingsNotifications.NotificationModal.lunaSeaValidationWebhookUrl": "You must provide a valid URL",
- "components.Settings.SettingsNotifications.NotificationModal.lunaSeaWebhookUrl": "Webhook URL",
- "components.Settings.SettingsNotifications.NotificationModal.lunaSeaWebhookUrlTip": "Your user- or device-based notification webhook URL",
"components.Settings.SettingsNotifications.NotificationModal.ntfyPassword": "Password",
"components.Settings.SettingsNotifications.NotificationModal.ntfyToken": "Token",
"components.Settings.SettingsNotifications.NotificationModal.ntfyTokenAuth": "Token authentication",
@@ -969,23 +980,11 @@
"components.Settings.SettingsNotifications.NotificationModal.webhookUrl": "Webhook URL",
"components.Settings.SettingsNotifications.NotificationModal.webhookValidationJsonPayloadRequired": "You must provide a valid JSON payload",
"components.Settings.SettingsNotifications.NotificationModal.webhookValidationWebhookUrl": "You must provide a valid URL",
- "components.Settings.SettingsNotifications.createNotificationInstance": "Create Notification Instance",
- "components.Settings.SettingsNotifications.instanceDefault": "Default",
- "components.Settings.SettingsNotifications.instanceDeleteError": "Something went wrong while deleting the notification instance.",
- "components.Settings.SettingsNotifications.instanceDeleted": "Notification instance deleted successfully!",
- "components.Settings.SettingsNotifications.instanceEnabled": "Enabled",
"components.Settings.SettingsNotifications.instanceId": "ID",
"components.Settings.SettingsNotifications.instanceName": "Name",
"components.Settings.SettingsNotifications.notificationAgent": "Agent",
"components.Settings.SettingsNotifications.notificationInstanceList": "Notification Instance List",
"components.Settings.SettingsNotifications.notifications": "Notifications",
- "components.Settings.SettingsNotifications.toastDefaultToggleFailed": "Notification instance failed to toggle default!",
- "components.Settings.SettingsNotifications.toastDefaultToggleSuccess": "Notification instance default toggled successfully!",
- "components.Settings.SettingsNotifications.toastEnabledToggleFailed": "Notification instance failed to toggle enabled!",
- "components.Settings.SettingsNotifications.toastEnabledToggleSuccess": "Notification instance enabled toggled successfully!",
- "components.Settings.SettingsNotifications.toastTestFailed": "Test notification failed to send.",
- "components.Settings.SettingsNotifications.toastTestSending": "Sending test notification…",
- "components.Settings.SettingsNotifications.toastTestSuccess": "Test notification sent!",
"components.Settings.SettingsUsers.atLeastOneAuth": "At least one authentication method must be selected.",
"components.Settings.SettingsUsers.defaultPermissions": "Default Permissions",
"components.Settings.SettingsUsers.defaultPermissionsTip": "Initial permissions assigned to new users",
@@ -1127,6 +1126,7 @@
"components.Settings.menuServices": "Services",
"components.Settings.menuUsers": "Users",
"components.Settings.metadataProviderSelection": "Metadata Provider Selection",
+ "components.Settings.metadataProviderSettings": "Metadata Providers",
"components.Settings.metadataSettings": "Settings for metadata provider",
"components.Settings.metadataSettingsSaved": "Metadata provider settings saved",
"components.Settings.no": "No",
@@ -1135,6 +1135,7 @@
"components.Settings.noDefaultServer": "At least one {serverType} server must be marked as default in order for {mediaType} requests to be processed.",
"components.Settings.noSpecialCharacters": "Configuration must be a comma delimited list of TMDB keyword ids, and must not start or end with a comma.",
"components.Settings.nooptions": "No results.",
+ "components.Settings.notTested": "Not Tested",
"components.Settings.notrunning": "Not Running",
"components.Settings.operational": "Operational",
"components.Settings.overrideRules": "Override Rules",
From 7950b0c3cd4cc99f16c12db2c48a89de34654c3b Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Thu, 11 Sep 2025 00:35:02 +0200
Subject: [PATCH 47/62] feat(notifications): fix issues created by branch
update
---
server/api/metadata.ts | 3 ++-
server/lib/notifications/agents/discord.ts | 5 ++---
server/lib/notifications/agents/email.ts | 5 ++---
server/lib/notifications/agents/ntfy.ts | 5 ++---
server/lib/notifications/agents/pushover.ts | 5 ++---
server/lib/notifications/agents/slack.ts | 5 ++---
server/lib/notifications/agents/telegram.ts | 5 ++---
server/lib/notifications/agents/webpush.ts | 2 +-
server/routes/settings/metadata.ts | 4 ++--
.../SettingsNotifications/NotificationModal/GotifyModal.tsx | 2 ++
.../NotificationModal/PushbulletModal.tsx | 2 ++
11 files changed, 21 insertions(+), 22 deletions(-)
diff --git a/server/api/metadata.ts b/server/api/metadata.ts
index cd1936cbf8..339e7daf91 100644
--- a/server/api/metadata.ts
+++ b/server/api/metadata.ts
@@ -1,7 +1,8 @@
import type { TvShowProvider } from '@server/api/provider';
import TheMovieDb from '@server/api/themoviedb';
import Tvdb from '@server/api/tvdb';
-import { getSettings, MetadataProviderType } from '@server/lib/settings';
+import { MetadataProviderType } from '@server/interfaces/settings';
+import { getSettings } from '@server/lib/settings';
import logger from '@server/logger';
export const getMetadataProvider = async (
diff --git a/server/lib/notifications/agents/discord.ts b/server/lib/notifications/agents/discord.ts
index 486100566e..4e0178ca5d 100644
--- a/server/lib/notifications/agents/discord.ts
+++ b/server/lib/notifications/agents/discord.ts
@@ -102,9 +102,8 @@ class DiscordAgent
type: Notification,
payload: NotificationPayload
): DiscordRichEmbed {
- const settings = getSettings();
- const { applicationUrl } = settings.main;
- const { embedPoster } = settings.notifications.agents.discord;
+ const { applicationUrl } = getSettings().main;
+ const embedPoster = this.getSettings().embedPoster;
const appUrl =
applicationUrl || `http://localhost:${process.env.port || 5055}`;
diff --git a/server/lib/notifications/agents/email.ts b/server/lib/notifications/agents/email.ts
index 9f560dac9b..a7eadf6da6 100644
--- a/server/lib/notifications/agents/email.ts
+++ b/server/lib/notifications/agents/email.ts
@@ -41,9 +41,8 @@ class EmailAgent
recipientEmail: string,
recipientName?: string
): EmailOptions | undefined {
- const settings = getSettings();
- const { applicationUrl, applicationTitle } = settings.main;
- const { embedPoster } = settings.notifications.agents.email;
+ const { applicationUrl, applicationTitle } = getSettings().main;
+ const embedPoster = this.getSettings().embedPoster;
if (type === Notification.TEST_NOTIFICATION) {
return {
diff --git a/server/lib/notifications/agents/ntfy.ts b/server/lib/notifications/agents/ntfy.ts
index 8e60c303c7..ede7ff567c 100644
--- a/server/lib/notifications/agents/ntfy.ts
+++ b/server/lib/notifications/agents/ntfy.ts
@@ -12,9 +12,8 @@ class NtfyAgent
implements NotificationAgent
{
private buildPayload(type: Notification, payload: NotificationPayload) {
- const settings = getSettings();
- const { applicationUrl } = settings.main;
- const { embedPoster } = settings.notifications.agents.ntfy;
+ const { applicationUrl } = getSettings().main;
+ const embedPoster = this.getSettings().embedPoster;
const topic = this.getSettings().options.topic;
const priority = 3;
diff --git a/server/lib/notifications/agents/pushover.ts b/server/lib/notifications/agents/pushover.ts
index 9c648a5862..dead4327fe 100644
--- a/server/lib/notifications/agents/pushover.ts
+++ b/server/lib/notifications/agents/pushover.ts
@@ -71,9 +71,8 @@ class PushoverAgent
type: Notification,
payload: NotificationPayload
): Promise> {
- const settings = getSettings();
- const { applicationUrl, applicationTitle } = settings.main;
- const { embedPoster } = settings.notifications.agents.pushover;
+ const { applicationUrl, applicationTitle } = getSettings().main;
+ const embedPoster = this.getSettings().embedPoster;
const title = payload.event ?? payload.subject;
let message = payload.event ? `${payload.subject}` : '';
diff --git a/server/lib/notifications/agents/slack.ts b/server/lib/notifications/agents/slack.ts
index 6282bb51e6..c63a8ad442 100644
--- a/server/lib/notifications/agents/slack.ts
+++ b/server/lib/notifications/agents/slack.ts
@@ -53,9 +53,8 @@ class SlackAgent
type: Notification,
payload: NotificationPayload
): SlackBlockEmbed {
- const settings = getSettings();
- const { applicationUrl, applicationTitle } = settings.main;
- const { embedPoster } = settings.notifications.agents.slack;
+ const { applicationUrl, applicationTitle } = getSettings().main;
+ const embedPoster = this.getSettings().embedPoster;
const fields: EmbedField[] = [];
diff --git a/server/lib/notifications/agents/telegram.ts b/server/lib/notifications/agents/telegram.ts
index 03be776bc5..58b4f6a465 100644
--- a/server/lib/notifications/agents/telegram.ts
+++ b/server/lib/notifications/agents/telegram.ts
@@ -58,9 +58,8 @@ class TelegramAgent
type: Notification,
payload: NotificationPayload
): Partial {
- const settings = getSettings();
- const { applicationUrl, applicationTitle } = settings.main;
- const { embedPoster } = settings.notifications.agents.telegram;
+ const { applicationUrl, applicationTitle } = getSettings().main;
+ const embedPoster = this.getSettings().embedPoster;
/* eslint-disable no-useless-escape */
let message = `\*${this.escapeText(
diff --git a/server/lib/notifications/agents/webpush.ts b/server/lib/notifications/agents/webpush.ts
index 5b60beacc9..667a2857b2 100644
--- a/server/lib/notifications/agents/webpush.ts
+++ b/server/lib/notifications/agents/webpush.ts
@@ -35,7 +35,7 @@ class WebPushAgent
type: Notification,
payload: NotificationPayload
): PushNotificationPayload {
- const { embedPoster } = getSettings().notifications.agents.webpush;
+ const embedPoster = this.getSettings().embedPoster;
const mediaType = payload.media
? payload.media.mediaType === MediaType.MOVIE
diff --git a/server/routes/settings/metadata.ts b/server/routes/settings/metadata.ts
index 8e007f0eda..8ee696a68a 100644
--- a/server/routes/settings/metadata.ts
+++ b/server/routes/settings/metadata.ts
@@ -1,10 +1,10 @@
import TheMovieDb from '@server/api/themoviedb';
import Tvdb from '@server/api/tvdb';
import {
- getSettings,
MetadataProviderType,
type MetadataSettings,
-} from '@server/lib/settings';
+} from '@server/interfaces/settings';
+import { getSettings } from '@server/lib/settings';
import logger from '@server/logger';
import { Router } from 'express';
diff --git a/src/components/Settings/SettingsNotifications/NotificationModal/GotifyModal.tsx b/src/components/Settings/SettingsNotifications/NotificationModal/GotifyModal.tsx
index 21f8e5a652..45b2236fb4 100644
--- a/src/components/Settings/SettingsNotifications/NotificationModal/GotifyModal.tsx
+++ b/src/components/Settings/SettingsNotifications/NotificationModal/GotifyModal.tsx
@@ -105,6 +105,7 @@ const GotifyModal = ({
id: values.id,
agent: values.agent,
default: values.default,
+ embedPoster: true,
options: {
url: values.url,
token: values.token,
@@ -143,6 +144,7 @@ const GotifyModal = ({
id: values.id,
agent: values.agent,
default: values.default,
+ embedPoster: true,
options: {
url: values.url,
token: values.token,
diff --git a/src/components/Settings/SettingsNotifications/NotificationModal/PushbulletModal.tsx b/src/components/Settings/SettingsNotifications/NotificationModal/PushbulletModal.tsx
index 26f8d789c9..f511b643e9 100644
--- a/src/components/Settings/SettingsNotifications/NotificationModal/PushbulletModal.tsx
+++ b/src/components/Settings/SettingsNotifications/NotificationModal/PushbulletModal.tsx
@@ -75,6 +75,7 @@ const PushbulletModal = ({
id: values.id,
agent: values.agent,
default: values.default,
+ embedPoster: true,
options: {
accessToken: values.accessToken,
channelTag: values.channelTag,
@@ -112,6 +113,7 @@ const PushbulletModal = ({
id: values.id,
agent: values.agent,
default: values.default,
+ embedPoster: true,
options: {
accessToken: values.accessToken,
channelTag: values.channelTag,
From 68cf8b2a2117b423d38663934f8d7e49c8e65849 Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Thu, 20 Nov 2025 22:27:48 +0100
Subject: [PATCH 48/62] feat(notifications): fix migration update build issues
---
server/lib/overseerrMerge.ts | 7 +-
.../migrations/0007_migrate_arr_tags.ts | 2 +-
src/i18n/locale/en.json | 167 +-----------------
3 files changed, 13 insertions(+), 163 deletions(-)
diff --git a/server/lib/overseerrMerge.ts b/server/lib/overseerrMerge.ts
index 43b83d32cc..61d31787f3 100644
--- a/server/lib/overseerrMerge.ts
+++ b/server/lib/overseerrMerge.ts
@@ -119,8 +119,11 @@ const checkOverseerrMerge = async (): Promise => {
if (settings.main.applicationTitle === 'Overseerr') {
settings.main.applicationTitle = 'Seerr';
}
- if (settings.notifications.agents.email.options.senderName === 'Overseerr') {
- settings.notifications.agents.email.options.senderName = 'Seerr';
+ if (
+ settings.notification.agentTemplates.email.options.senderName ===
+ 'Overseerr'
+ ) {
+ settings.notification.agentTemplates.email.options.senderName = 'Seerr';
}
// Save the updated settings
diff --git a/server/lib/settings/migrations/0007_migrate_arr_tags.ts b/server/lib/settings/migrations/0007_migrate_arr_tags.ts
index 25911f93b5..adbb85a9a7 100644
--- a/server/lib/settings/migrations/0007_migrate_arr_tags.ts
+++ b/server/lib/settings/migrations/0007_migrate_arr_tags.ts
@@ -2,7 +2,7 @@ import RadarrAPI from '@server/api/servarr/radarr';
import SonarrAPI from '@server/api/servarr/sonarr';
import { getRepository } from '@server/datasource';
import { User } from '@server/entity/User';
-import type { AllSettings } from '@server/lib/settings';
+import type { AllSettings } from '@server/interfaces/settings';
const migrationArrTags = async (settings: any): Promise => {
if (
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json
index 438b438018..373474afb3 100644
--- a/src/i18n/locale/en.json
+++ b/src/i18n/locale/en.json
@@ -610,163 +610,6 @@
"components.Selector.showless": "Show Less",
"components.Selector.showmore": "Show More",
"components.Selector.starttyping": "Starting typing to search.",
- "components.Settings.Notifications.NotificationsGotify.agentenabled": "Enable Agent",
- "components.Settings.Notifications.NotificationsGotify.gotifysettingsfailed": "Gotify notification settings failed to save.",
- "components.Settings.Notifications.NotificationsGotify.gotifysettingssaved": "Gotify notification settings saved successfully!",
- "components.Settings.Notifications.NotificationsGotify.priority": "Priority",
- "components.Settings.Notifications.NotificationsGotify.toastGotifyTestFailed": "Gotify test notification failed to send.",
- "components.Settings.Notifications.NotificationsGotify.toastGotifyTestSending": "Sending Gotify test notification…",
- "components.Settings.Notifications.NotificationsGotify.toastGotifyTestSuccess": "Gotify test notification sent!",
- "components.Settings.Notifications.NotificationsGotify.token": "Application Token",
- "components.Settings.Notifications.NotificationsGotify.url": "Server URL",
- "components.Settings.Notifications.NotificationsGotify.validationPriorityRequired": "You must set a priority number",
- "components.Settings.Notifications.NotificationsGotify.validationTokenRequired": "You must provide an application token",
- "components.Settings.Notifications.NotificationsGotify.validationTypes": "You must select at least one notification type",
- "components.Settings.Notifications.NotificationsGotify.validationUrlRequired": "You must provide a valid URL",
- "components.Settings.Notifications.NotificationsGotify.validationUrlTrailingSlash": "URL must not end in a trailing slash",
- "components.Settings.Notifications.NotificationsNtfy.agentenabled": "Enable Agent",
- "components.Settings.Notifications.NotificationsNtfy.embedPoster": "Embed Poster",
- "components.Settings.Notifications.NotificationsNtfy.ntfysettingsfailed": "Ntfy notification settings failed to save.",
- "components.Settings.Notifications.NotificationsNtfy.ntfysettingssaved": "Ntfy notification settings saved successfully!",
- "components.Settings.Notifications.NotificationsNtfy.password": "Password",
- "components.Settings.Notifications.NotificationsNtfy.toastNtfyTestFailed": "Ntfy test notification failed to send.",
- "components.Settings.Notifications.NotificationsNtfy.toastNtfyTestSending": "Sending ntfy test notification…",
- "components.Settings.Notifications.NotificationsNtfy.toastNtfyTestSuccess": "Ntfy test notification sent!",
- "components.Settings.Notifications.NotificationsNtfy.token": "Token",
- "components.Settings.Notifications.NotificationsNtfy.tokenAuth": "Token authentication",
- "components.Settings.Notifications.NotificationsNtfy.topic": "Topic",
- "components.Settings.Notifications.NotificationsNtfy.url": "Server root URL",
- "components.Settings.Notifications.NotificationsNtfy.username": "Username",
- "components.Settings.Notifications.NotificationsNtfy.usernamePasswordAuth": "Username + Password authentication",
- "components.Settings.Notifications.NotificationsNtfy.validationNtfyTopic": "You must provide a topic",
- "components.Settings.Notifications.NotificationsNtfy.validationNtfyUrl": "You must provide a valid URL",
- "components.Settings.Notifications.NotificationsNtfy.validationTypes": "You must select at least one notification type",
- "components.Settings.Notifications.NotificationsPushbullet.accessToken": "Access Token",
- "components.Settings.Notifications.NotificationsPushbullet.accessTokenTip": "Create a token from your Account Settings",
- "components.Settings.Notifications.NotificationsPushbullet.agentEnabled": "Enable Agent",
- "components.Settings.Notifications.NotificationsPushbullet.channelTag": "Channel Tag",
- "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.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.validationTypes": "You must select at least one notification type",
- "components.Settings.Notifications.NotificationsPushover.accessToken": "Application API Token",
- "components.Settings.Notifications.NotificationsPushover.accessTokenTip": "Register an application for use with Seerr",
- "components.Settings.Notifications.NotificationsPushover.agentenabled": "Enable Agent",
- "components.Settings.Notifications.NotificationsPushover.deviceDefault": "Device Default",
- "components.Settings.Notifications.NotificationsPushover.embedPoster": "Embed Poster",
- "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.sound": "Notification Sound",
- "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.userTokenTip": "Your 30-character user or group identifier",
- "components.Settings.Notifications.NotificationsPushover.validationAccessTokenRequired": "You must provide a valid application token",
- "components.Settings.Notifications.NotificationsPushover.validationTypes": "You must select at least one notification type",
- "components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "You must provide a valid user or group key",
- "components.Settings.Notifications.NotificationsSlack.agentenabled": "Enable Agent",
- "components.Settings.Notifications.NotificationsSlack.embedPoster": "Embed Poster",
- "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.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.validationTypes": "You must select at least one notification type",
- "components.Settings.Notifications.NotificationsSlack.validationWebhookUrl": "You must provide a valid URL",
- "components.Settings.Notifications.NotificationsSlack.webhookUrl": "Webhook URL",
- "components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "Create an Incoming Webhook integration",
- "components.Settings.Notifications.NotificationsWebhook.agentenabled": "Enable Agent",
- "components.Settings.Notifications.NotificationsWebhook.authheader": "Authorization Header",
- "components.Settings.Notifications.NotificationsWebhook.customJson": "JSON Payload",
- "components.Settings.Notifications.NotificationsWebhook.resetPayload": "Reset to Default",
- "components.Settings.Notifications.NotificationsWebhook.resetPayloadSuccess": "JSON payload reset successfully!",
- "components.Settings.Notifications.NotificationsWebhook.supportVariables": "Support URL Variables",
- "components.Settings.Notifications.NotificationsWebhook.supportVariablesTip": "Available variables are documented in the webhook template variables section",
- "components.Settings.Notifications.NotificationsWebhook.templatevariablehelp": "Template Variable Help",
- "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.validationTypes": "You must select at least one notification type",
- "components.Settings.Notifications.NotificationsWebhook.validationWebhookUrl": "You must provide a valid URL",
- "components.Settings.Notifications.NotificationsWebhook.webhookUrl": "Webhook URL",
- "components.Settings.Notifications.NotificationsWebhook.webhookUrlTip": "Test Notification URL is set to {testUrl} instead of the actual webhook URL.",
- "components.Settings.Notifications.NotificationsWebhook.webhooksettingsfailed": "Webhook notification settings failed to save.",
- "components.Settings.Notifications.NotificationsWebhook.webhooksettingssaved": "Webhook notification settings saved successfully!",
- "components.Settings.Notifications.NotificationsWebPush.agentenabled": "Enable Agent",
- "components.Settings.Notifications.NotificationsWebPush.embedPoster": "Embed Poster",
- "components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "In order to receive web push notifications, Seerr must be served over HTTPS.",
- "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.webpushsettingssaved": "Web push notification settings saved successfully!",
- "components.Settings.Notifications.agentenabled": "Enable Agent",
- "components.Settings.Notifications.allowselfsigned": "Allow Self-Signed Certificates",
- "components.Settings.Notifications.authPass": "SMTP Password",
- "components.Settings.Notifications.authUser": "SMTP Username",
- "components.Settings.Notifications.botAPI": "Bot Authorization Token",
- "components.Settings.Notifications.botApiTip": "Create a bot for use with Seerr",
- "components.Settings.Notifications.botAvatarUrl": "Bot Avatar URL",
- "components.Settings.Notifications.botUsername": "Bot Username",
- "components.Settings.Notifications.botUsernameTip": "Allow users to also start a chat with your bot and configure their own notifications",
- "components.Settings.Notifications.chatId": "Chat ID",
- "components.Settings.Notifications.chatIdTip": "Start a chat with your bot, add @get_id_bot, and issue the /my_id command",
- "components.Settings.Notifications.discordsettingsfailed": "Discord notification settings failed to save.",
- "components.Settings.Notifications.discordsettingssaved": "Discord notification settings saved successfully!",
- "components.Settings.Notifications.emailsender": "Sender Address",
- "components.Settings.Notifications.emailsettingsfailed": "Email notification settings failed to save.",
- "components.Settings.Notifications.emailsettingssaved": "Email notification settings saved successfully!",
- "components.Settings.Notifications.embedPoster": "Embed Poster",
- "components.Settings.Notifications.enableMentions": "Enable Mentions",
- "components.Settings.Notifications.encryption": "Encryption Method",
- "components.Settings.Notifications.encryptionDefault": "Use STARTTLS if available",
- "components.Settings.Notifications.encryptionImplicitTls": "Use Implicit TLS",
- "components.Settings.Notifications.encryptionNone": "None",
- "components.Settings.Notifications.encryptionOpportunisticTls": "Always use STARTTLS",
- "components.Settings.Notifications.encryptionTip": "In most cases, Implicit TLS uses port 465 and STARTTLS uses port 587",
- "components.Settings.Notifications.messageThreadId": "Thread/Topic ID",
- "components.Settings.Notifications.messageThreadIdTip": "If your group-chat has topics enabled, you can specify a thread/topic's ID here",
- "components.Settings.Notifications.pgpPassword": "PGP Password",
- "components.Settings.Notifications.pgpPasswordTip": "Sign encrypted email messages using OpenPGP",
- "components.Settings.Notifications.pgpPrivateKey": "PGP Private Key",
- "components.Settings.Notifications.pgpPrivateKeyTip": "Sign encrypted email messages using OpenPGP",
- "components.Settings.Notifications.sendSilently": "Send Silently",
- "components.Settings.Notifications.sendSilentlyTip": "Send notifications with no sound",
- "components.Settings.Notifications.senderName": "Sender Name",
- "components.Settings.Notifications.smtpHost": "SMTP Host",
- "components.Settings.Notifications.smtpPort": "SMTP Port",
- "components.Settings.Notifications.telegramsettingsfailed": "Telegram notification settings failed to save.",
- "components.Settings.Notifications.telegramsettingssaved": "Telegram notification settings saved successfully!",
- "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.userEmailRequired": "Require user email",
- "components.Settings.Notifications.validationBotAPIRequired": "You must provide a bot authorization token",
- "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.validationMessageThreadId": "The thread/topic ID must be a positive whole number",
- "components.Settings.Notifications.validationPgpPassword": "You must provide a PGP password",
- "components.Settings.Notifications.validationPgpPrivateKey": "You must provide a valid PGP private key",
- "components.Settings.Notifications.validationSmtpHostRequired": "You must provide a valid hostname or IP address",
- "components.Settings.Notifications.validationSmtpPortRequired": "You must provide a valid port number",
- "components.Settings.Notifications.validationTypes": "You must select at least one notification type",
- "components.Settings.Notifications.validationUrl": "You must provide a valid URL",
- "components.Settings.Notifications.validationWebhookRoleId": "You must provide a valid Discord Role ID",
- "components.Settings.Notifications.webhookRoleId": "Notification Role ID",
- "components.Settings.Notifications.webhookRoleIdTip": "The role ID to mention in the webhook message. Leave empty to disable mentions",
- "components.Settings.Notifications.webhookUrl": "Webhook URL",
- "components.Settings.Notifications.webhookUrlTip": "Create a webhook integration in your server",
"components.Settings.OverrideRuleModal.conditions": "Conditions",
"components.Settings.OverrideRuleModal.conditionsDescription": "Specifies conditions before applying parameter changes. Each field must be validated for the rules to be applied (AND operation). A field is considered verified if any of its properties match (OR operation).",
"components.Settings.OverrideRuleModal.create": "Create rule",
@@ -1075,6 +918,7 @@
"components.Settings.SettingsNotifications.NotificationModal.emailValidationPgpPrivateKey": "You must provide a valid PGP private key",
"components.Settings.SettingsNotifications.NotificationModal.emailValidationSmtpHostRequired": "You must provide a valid hostname or IP address",
"components.Settings.SettingsNotifications.NotificationModal.emailValidationSmtpPortRequired": "You must provide a valid port number",
+ "components.Settings.SettingsNotifications.NotificationModal.embedPoster": "Embed Poster",
"components.Settings.SettingsNotifications.NotificationModal.gotifyPriority": "Priority",
"components.Settings.SettingsNotifications.NotificationModal.gotifyToken": "Application Token",
"components.Settings.SettingsNotifications.NotificationModal.gotifyUrl": "Server URL",
@@ -1097,7 +941,7 @@
"components.Settings.SettingsNotifications.NotificationModal.pushbulletChannelTag": "Channel Tag",
"components.Settings.SettingsNotifications.NotificationModal.pushbulletValidationAccessTokenRequired": "You must provide an access token",
"components.Settings.SettingsNotifications.NotificationModal.pushoverAccessToken": "Application API Token",
- "components.Settings.SettingsNotifications.NotificationModal.pushoverAccessTokenTip": "Register an application for use with Jellyseerr",
+ "components.Settings.SettingsNotifications.NotificationModal.pushoverAccessTokenTip": "Register an application for use with Seerr",
"components.Settings.SettingsNotifications.NotificationModal.pushoverDeviceDefault": "Device Default",
"components.Settings.SettingsNotifications.NotificationModal.pushoverSound": "Notification Sound",
"components.Settings.SettingsNotifications.NotificationModal.pushoverUserToken": "User or Group Key",
@@ -1107,8 +951,10 @@
"components.Settings.SettingsNotifications.NotificationModal.slackValidationWebhookUrl": "You must provide a valid URL",
"components.Settings.SettingsNotifications.NotificationModal.slackWebhookUrl": "Webhook URL",
"components.Settings.SettingsNotifications.NotificationModal.slackWebhookUrlTip": "Create an Incoming Webhook integration",
+ "components.Settings.SettingsNotifications.NotificationModal.supportVariables": "Support URL Variables",
+ "components.Settings.SettingsNotifications.NotificationModal.supportVariablesTip": "Available variables are documented in the webhook template variables section",
"components.Settings.SettingsNotifications.NotificationModal.telegramBotAPI": "Bot Authorization Token",
- "components.Settings.SettingsNotifications.NotificationModal.telegramBotApiTip": "Create a bot for use with Jellyseerr",
+ "components.Settings.SettingsNotifications.NotificationModal.telegramBotApiTip": "Create a bot for use with Seerr",
"components.Settings.SettingsNotifications.NotificationModal.telegramBotUsername": "Bot Username",
"components.Settings.SettingsNotifications.NotificationModal.telegramBotUsernameTip": "Allow users to also start a chat with your bot and configure their own notifications",
"components.Settings.SettingsNotifications.NotificationModal.telegramChatId": "Chat ID",
@@ -1128,13 +974,14 @@
"components.Settings.SettingsNotifications.NotificationModal.toastTestSending": "Sending test notification…",
"components.Settings.SettingsNotifications.NotificationModal.toastTestSuccess": "Test notification sent!",
"components.Settings.SettingsNotifications.NotificationModal.validationTypes": "You must select at least one notification type",
- "components.Settings.SettingsNotifications.NotificationModal.webPushHttpsRequirement": "In order to receive web push notifications, Jellyseerr must be served over HTTPS.",
+ "components.Settings.SettingsNotifications.NotificationModal.webPushHttpsRequirement": "In order to receive web push notifications, Seerr must be served over HTTPS.",
"components.Settings.SettingsNotifications.NotificationModal.webhookAuthheader": "Authorization Header",
"components.Settings.SettingsNotifications.NotificationModal.webhookCustomJson": "JSON Payload",
"components.Settings.SettingsNotifications.NotificationModal.webhookResetPayload": "Reset to Default",
"components.Settings.SettingsNotifications.NotificationModal.webhookResetPayloadSuccess": "JSON payload reset successfully!",
"components.Settings.SettingsNotifications.NotificationModal.webhookTemplateVariableHelp": "Template Variable Help",
"components.Settings.SettingsNotifications.NotificationModal.webhookUrl": "Webhook URL",
+ "components.Settings.SettingsNotifications.NotificationModal.webhookUrlTip": "Test Notification URL is set to {testUrl} instead of the actual webhook URL.",
"components.Settings.SettingsNotifications.NotificationModal.webhookValidationJsonPayloadRequired": "You must provide a valid JSON payload",
"components.Settings.SettingsNotifications.NotificationModal.webhookValidationWebhookUrl": "You must provide a valid URL",
"components.Settings.SettingsNotifications.instanceId": "ID",
From 580b6b690bcb0484a702aed47d50588884512592 Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Sun, 1 Mar 2026 02:41:23 +0100
Subject: [PATCH 49/62] feat(notifications-rework): change disabled prop of
ToggleSwitch to boolean
---
src/components/Common/ToggleSwitch/index.tsx | 6 ++---
.../RequestModal/CollectionRequestModal.tsx | 16 +++++++------
.../RequestModal/TvRequestModal.tsx | 24 ++++++++++++-------
3 files changed, 27 insertions(+), 19 deletions(-)
diff --git a/src/components/Common/ToggleSwitch/index.tsx b/src/components/Common/ToggleSwitch/index.tsx
index dbffb6c6c8..c6892ed30e 100644
--- a/src/components/Common/ToggleSwitch/index.tsx
+++ b/src/components/Common/ToggleSwitch/index.tsx
@@ -1,7 +1,7 @@
interface ToggleSwitchProps {
isToggled?: boolean;
onToggle: () => void;
- disabled?: unknown;
+ disabled?: boolean;
highContrast?: boolean;
}
@@ -34,8 +34,8 @@ function ToggleSwitch({
isToggled
? 'bg-indigo-500'
: highContrast
- ? 'bg-gray-700'
- : 'bg-gray-800'
+ ? 'bg-gray-700'
+ : 'bg-gray-800'
} absolute mx-auto h-4 w-9 rounded-full transition-colors duration-200 ease-in-out`}
/>
togglePart(part.id)}
disabled={
- (!!partMedia &&
- partMedia.status !==
- MediaStatus.BLOCKLISTED) ||
- partRequest ||
- (quota?.movie.limit &&
- currentlyRemaining <= 0 &&
- !isSelectedPart(part.id))
+ !!(
+ (!!partMedia &&
+ partMedia.status !==
+ MediaStatus.BLOCKLISTED) ||
+ partRequest ||
+ (quota?.movie.limit &&
+ currentlyRemaining <= 0 &&
+ !isSelectedPart(part.id))
+ )
}
highContrast
/>
diff --git a/src/components/RequestModal/TvRequestModal.tsx b/src/components/RequestModal/TvRequestModal.tsx
index d4f64c2c5d..39221a8011 100644
--- a/src/components/RequestModal/TvRequestModal.tsx
+++ b/src/components/RequestModal/TvRequestModal.tsx
@@ -539,9 +539,11 @@ const TvRequestModal = ({
isToggled={isAllSeasons()}
onToggle={() => toggleAllSeasons()}
disabled={
- quota?.tv.remaining &&
- quota.tv.limit &&
- quota.tv.remaining < unrequestedSeasons.length
+ !!(
+ quota?.tv.remaining &&
+ quota.tv.limit &&
+ quota.tv.remaining < unrequestedSeasons.length
+ )
}
/>
@@ -599,12 +601,16 @@ const TvRequestModal = ({
}
onToggle={() => toggleSeason(season.seasonNumber)}
disabled={
- mediaSeason ||
- (quota?.tv.limit &&
- currentlyRemaining <= 0 &&
- !isSelectedSeason(season.seasonNumber)) ||
- (!!seasonRequest &&
- !editingSeasons.includes(season.seasonNumber))
+ !!(
+ mediaSeason ||
+ (quota?.tv.limit &&
+ currentlyRemaining <= 0 &&
+ !isSelectedSeason(season.seasonNumber)) ||
+ (!!seasonRequest &&
+ !editingSeasons.includes(
+ season.seasonNumber
+ ))
+ )
}
highContrast
/>
From 5a48498fcd748ac3c9951d9907313b7d7add998f Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Sun, 1 Mar 2026 16:26:53 +0100
Subject: [PATCH 50/62] feat(notifications): revert accidential readme change
---
README.md | 16 ----------------
1 file changed, 16 deletions(-)
diff --git a/README.md b/README.md
index 657b0ef628..aafdc0224d 100644
--- a/README.md
+++ b/README.md
@@ -36,22 +36,6 @@ Check out our documentation for instructions on how to install and run Seerr:
https://docs.seerr.dev/getting-started/
-> [!IMPORTANT] > **Seerr is not officially released yet.**
-> The project is currently available **only on the `develop` branch** and is intended for **beta testing only**.
-
-The documentation linked above is for running the **latest Jellyseerr** release.
-
-> [!WARNING]
-> If you are migrating from **Overseerr** to **Seerr** for beta testing, **do not follow the Jellyseerr latest setup guide**.
-
-Instead, follow the dedicated migration guide (with `:develop` tag):
-https://github.com/seerr-team/seerr/blob/develop/docs/migration-guide.mdx
-
-> [!CAUTION] > **DO NOT run Jellyseerr (latest) using an existing Overseerr database. This includes third-party images with `seerr:latest` (as it points to jellyseerr 2.7.3 and not seerr.**
-> Doing so **may cause database corruption and/or irreversible data loss and/or weird unintended behaviour**.
-
-For migration assistance, beta testing questions, or troubleshooting, please join our **Discord** and ask for support there.
-
## Preview
From 19cc336bfc52483996467d7eb29694bad06dce84 Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Sun, 1 Mar 2026 17:18:58 +0100
Subject: [PATCH 51/62] feat(notifications): move notification config casting
to getSettings BaseAgent
---
server/lib/notifications/agents/agent.ts | 4 ++--
server/lib/notifications/agents/discord.ts | 2 +-
server/lib/notifications/agents/email.ts | 4 ++--
server/lib/notifications/agents/gotify.ts | 2 +-
server/lib/notifications/agents/ntfy.ts | 2 +-
server/lib/notifications/agents/pushbullet.ts | 2 +-
server/lib/notifications/agents/pushover.ts | 2 +-
server/lib/notifications/agents/slack.ts | 2 +-
server/lib/notifications/agents/webhook.ts | 2 +-
9 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/server/lib/notifications/agents/agent.ts b/server/lib/notifications/agents/agent.ts
index 08799d411c..239e50e33e 100644
--- a/server/lib/notifications/agents/agent.ts
+++ b/server/lib/notifications/agents/agent.ts
@@ -33,7 +33,7 @@ export abstract class BaseAgent {
this.id = id;
}
- protected getSettings(): NotificationAgentConfig {
+ protected getSettings(): T {
if (this.settings) {
return this.settings;
}
@@ -44,7 +44,7 @@ export abstract class BaseAgent {
(instance) => instance.id === Number(this.id)
);
- return notificationInstance as NotificationAgentConfig;
+ return notificationInstance as T;
}
}
diff --git a/server/lib/notifications/agents/discord.ts b/server/lib/notifications/agents/discord.ts
index db74548c04..b62c5307dc 100644
--- a/server/lib/notifications/agents/discord.ts
+++ b/server/lib/notifications/agents/discord.ts
@@ -236,7 +236,7 @@ class DiscordAgent
type: Notification,
payload: NotificationPayload
): Promise {
- const settings = this.getSettings() as NotificationAgentDiscord;
+ const settings = this.getSettings();
if (
!payload.notifySystem ||
diff --git a/server/lib/notifications/agents/email.ts b/server/lib/notifications/agents/email.ts
index 25ea216b6f..d58c6e1116 100644
--- a/server/lib/notifications/agents/email.ts
+++ b/server/lib/notifications/agents/email.ts
@@ -210,7 +210,7 @@ class EmailAgent
try {
const email = new PreparedEmail(
- this.getSettings() as NotificationAgentEmail,
+ this.getSettings(),
payload.notifyUser.settings?.pgpKey
);
if (
@@ -274,7 +274,7 @@ class EmailAgent
try {
const email = new PreparedEmail(
- this.getSettings() as NotificationAgentEmail,
+ this.getSettings(),
user.settings?.pgpKey
);
if (validator.isEmail(user.email, { require_tld: false })) {
diff --git a/server/lib/notifications/agents/gotify.ts b/server/lib/notifications/agents/gotify.ts
index 557d149b24..3f890f46b2 100644
--- a/server/lib/notifications/agents/gotify.ts
+++ b/server/lib/notifications/agents/gotify.ts
@@ -38,7 +38,7 @@ class GotifyAgent
payload: NotificationPayload
): GotifyPayload {
const { applicationUrl, applicationTitle } = getSettings().main;
- const settings = this.getSettings() as NotificationAgentGotify;
+ const settings = this.getSettings();
const priority = settings.options.priority ?? 1;
const title = payload.event
diff --git a/server/lib/notifications/agents/ntfy.ts b/server/lib/notifications/agents/ntfy.ts
index 2e6137091c..6c5f9b29aa 100644
--- a/server/lib/notifications/agents/ntfy.ts
+++ b/server/lib/notifications/agents/ntfy.ts
@@ -94,7 +94,7 @@ class NtfyAgent
type: Notification,
payload: NotificationPayload
): Promise {
- const settings = this.getSettings() as NotificationAgentNtfy;
+ const settings = this.getSettings();
if (
!payload.notifySystem ||
diff --git a/server/lib/notifications/agents/pushbullet.ts b/server/lib/notifications/agents/pushbullet.ts
index c74c544f30..474c7101b9 100644
--- a/server/lib/notifications/agents/pushbullet.ts
+++ b/server/lib/notifications/agents/pushbullet.ts
@@ -95,7 +95,7 @@ class PushbulletAgent
type: Notification,
payload: NotificationPayload
): Promise {
- const settings = this.getSettings() as NotificationAgentPushbullet;
+ const settings = this.getSettings();
const endpoint = 'https://api.pushbullet.com/v2/pushes';
const notificationPayload = this.getNotificationPayload(type, payload);
diff --git a/server/lib/notifications/agents/pushover.ts b/server/lib/notifications/agents/pushover.ts
index 5ae752e0b9..059e7585ec 100644
--- a/server/lib/notifications/agents/pushover.ts
+++ b/server/lib/notifications/agents/pushover.ts
@@ -183,7 +183,7 @@ class PushoverAgent
type: Notification,
payload: NotificationPayload
): Promise {
- const settings = this.getSettings() as NotificationAgentPushover;
+ const settings = this.getSettings();
const endpoint = 'https://api.pushover.net/1/messages.json';
const notificationPayload = await this.getNotificationPayload(
type,
diff --git a/server/lib/notifications/agents/slack.ts b/server/lib/notifications/agents/slack.ts
index 565f83efee..3e7738fbf6 100644
--- a/server/lib/notifications/agents/slack.ts
+++ b/server/lib/notifications/agents/slack.ts
@@ -215,7 +215,7 @@ class SlackAgent
type: Notification,
payload: NotificationPayload
): Promise {
- const settings = this.getSettings() as NotificationAgentSlack;
+ const settings = this.getSettings();
if (
!payload.notifySystem ||
diff --git a/server/lib/notifications/agents/webhook.ts b/server/lib/notifications/agents/webhook.ts
index 84b14ee896..0d302de0d2 100644
--- a/server/lib/notifications/agents/webhook.ts
+++ b/server/lib/notifications/agents/webhook.ts
@@ -147,7 +147,7 @@ class WebhookAgent
type: Notification,
payload: NotificationPayload
): Promise {
- const settings = this.getSettings() as NotificationAgentWebhook;
+ const settings = this.getSettings();
if (
!payload.notifySystem ||
From 2c4147e944a19304692a58b2e056d9acf5e38f18 Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Sun, 1 Mar 2026 17:29:02 +0100
Subject: [PATCH 52/62] feat(notifications): adjust a11y of ToggleSwitch
component
---
src/components/Common/ToggleSwitch/index.tsx | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/components/Common/ToggleSwitch/index.tsx b/src/components/Common/ToggleSwitch/index.tsx
index c6892ed30e..4c22d4147f 100644
--- a/src/components/Common/ToggleSwitch/index.tsx
+++ b/src/components/Common/ToggleSwitch/index.tsx
@@ -20,13 +20,15 @@ function ToggleSwitch({
onToggle();
}}
onKeyDown={(e) => {
- if (e.key === 'Enter' || e.key === 'Space') {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
onToggle();
}
}}
className={`relative inline-flex h-5 w-10 flex-shrink-0 cursor-pointer items-center justify-center pt-2 focus:outline-none ${
- disabled ? 'opacity-50' : ''
+ disabled ? 'cursor-not-allowed opacity-50' : ''
}`}
+ aria-disabled={disabled ? true : undefined}
>
Date: Sun, 1 Mar 2026 17:36:54 +0100
Subject: [PATCH 53/62] feat(notifications): adjust email pgp private key regex
---
.../SettingsNotifications/NotificationModal/EmailModal.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/components/Settings/SettingsNotifications/NotificationModal/EmailModal.tsx b/src/components/Settings/SettingsNotifications/NotificationModal/EmailModal.tsx
index f7a84520ff..1d3b55b19a 100644
--- a/src/components/Settings/SettingsNotifications/NotificationModal/EmailModal.tsx
+++ b/src/components/Settings/SettingsNotifications/NotificationModal/EmailModal.tsx
@@ -117,7 +117,7 @@ const EmailModal = ({
otherwise: Yup.string().nullable(),
})
.matches(
- /-----BEGIN PGP PRIVATE KEY BLOCK-----.+-----END PGP PRIVATE KEY BLOCK-----/,
+ /-----BEGIN PGP PRIVATE KEY BLOCK-----.+-----END PGP PRIVATE KEY BLOCK-----/s,
intl.formatMessage(messages.emailValidationPgpPrivateKey)
),
pgpPassword: Yup.string().when('pgpPrivateKey', {
@@ -228,8 +228,8 @@ const EmailModal = ({
isSubmitting
? intl.formatMessage(globalMessages.saving)
: type === NotificationModalType.EDIT
- ? intl.formatMessage(globalMessages.save)
- : intl.formatMessage(messages.createInstance)
+ ? intl.formatMessage(globalMessages.save)
+ : intl.formatMessage(messages.createInstance)
}
onOk={() => {
handleSubmit();
From c8af502c3c29a110fa7b642bb44ae549787588a6 Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Sun, 1 Mar 2026 17:46:49 +0100
Subject: [PATCH 54/62] feat(notifications): adjust telegram messageThreadId
validation dependencies
---
.../NotificationModal/TelegramModal.tsx | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/components/Settings/SettingsNotifications/NotificationModal/TelegramModal.tsx b/src/components/Settings/SettingsNotifications/NotificationModal/TelegramModal.tsx
index 66c6c67a36..985315624d 100644
--- a/src/components/Settings/SettingsNotifications/NotificationModal/TelegramModal.tsx
+++ b/src/components/Settings/SettingsNotifications/NotificationModal/TelegramModal.tsx
@@ -21,7 +21,8 @@ const messages = defineMessages(
telegramBotUsernameTip:
'Allow users to also start a chat with your bot and configure their own notifications',
telegramBotAPI: 'Bot Authorization Token',
- telegramBotApiTip: 'Create a bot for use with Seerr',
+ telegramBotApiTip:
+ 'Create a bot for use with Seerr',
telegramChatId: 'Chat ID',
telegramChatIdTip:
'Start a chat with your bot, add @get_id_bot, and issue the /my_id command',
@@ -80,7 +81,7 @@ const TelegramModal = ({
intl.formatMessage(messages.telegramValidationChatIdRequired)
),
messageThreadId: Yup.string()
- .when(['types'], {
+ .when(['enabled', 'types'], {
is: (enabled: boolean, types: number) => enabled && !!types,
then: Yup.string()
.nullable()
@@ -176,8 +177,8 @@ const TelegramModal = ({
isSubmitting
? intl.formatMessage(globalMessages.saving)
: type === NotificationModalType.EDIT
- ? intl.formatMessage(globalMessages.save)
- : intl.formatMessage(messages.createInstance)
+ ? intl.formatMessage(globalMessages.save)
+ : intl.formatMessage(messages.createInstance)
}
onOk={() => {
handleSubmit();
From a9e83355b7675cc5862bdf2a3284bbb422d4348e Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Sun, 1 Mar 2026 18:03:43 +0100
Subject: [PATCH 55/62] feat(notifications): remove unneeded json payload type
assertion
---
server/lib/notifications/agents/webhook.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server/lib/notifications/agents/webhook.ts b/server/lib/notifications/agents/webhook.ts
index 0d302de0d2..d9dbb9a7ee 100644
--- a/server/lib/notifications/agents/webhook.ts
+++ b/server/lib/notifications/agents/webhook.ts
@@ -127,7 +127,7 @@ class WebhookAgent
}
private buildPayload(type: Notification, payload: NotificationPayload) {
- const payloadString = this.getSettings().options.jsonPayload as string;
+ const payloadString = this.getSettings().options.jsonPayload;
const parsedJSON = JSON.parse(payloadString);
return this.parseKeys(parsedJSON, payload, type);
From b8cab84da1651a6888b626d436128a489a960b29 Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Sun, 1 Mar 2026 18:25:00 +0100
Subject: [PATCH 56/62] feat(notifications): add readonly id to contract in
NotificationInstance
---
seerr-api.yml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/seerr-api.yml b/seerr-api.yml
index 749047e785..a3d64c7b67 100644
--- a/seerr-api.yml
+++ b/seerr-api.yml
@@ -1402,6 +1402,10 @@ components:
example: 0
name:
type: string
+ id:
+ type: integer
+ example: 0
+ readOnly: true
agent:
type: string
enum:
From 6399c927a493e531a622a1f8e6a2e5f2c6a9ea01 Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Sun, 26 Apr 2026 19:10:25 +0200
Subject: [PATCH 57/62] feat(notifications): set notification instance id to
readonly
---
seerr-api.yml | 1 +
.../NotificationModal/index.tsx | 21 ++++++++++++++++---
2 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/seerr-api.yml b/seerr-api.yml
index 305c52fb26..64e60fb934 100644
--- a/seerr-api.yml
+++ b/seerr-api.yml
@@ -1413,6 +1413,7 @@ components:
id:
type: integer
example: 0
+ readOnly: true
agent:
type: string
enum:
diff --git a/src/components/Settings/SettingsNotifications/NotificationModal/index.tsx b/src/components/Settings/SettingsNotifications/NotificationModal/index.tsx
index 127f2582aa..95c9d7450a 100644
--- a/src/components/Settings/SettingsNotifications/NotificationModal/index.tsx
+++ b/src/components/Settings/SettingsNotifications/NotificationModal/index.tsx
@@ -64,10 +64,15 @@ const NotificationModal = ({
const { addToast, removeToast } = useToasts();
const onSave = async (submitData: NotificationAgentConfig) => {
+ const instance = {
+ ...submitData,
+ id: undefined,
+ };
+
try {
await axios.post(
`/api/v1/settings/notification/${submitData.id}`,
- submitData
+ instance
);
addToast(intl.formatMessage(messages.toastSaveSuccess), {
@@ -85,8 +90,13 @@ const NotificationModal = ({
};
const onCreate = async (submitData: NotificationAgentConfig) => {
+ const instance = {
+ ...submitData,
+ id: undefined,
+ };
+
try {
- await axios.post(`/api/v1/settings/notification`, submitData);
+ await axios.post(`/api/v1/settings/notification`, instance);
addToast(intl.formatMessage(messages.toastCreateSuccess), {
appearance: 'success',
@@ -104,6 +114,11 @@ const NotificationModal = ({
const onTest = async (testData: NotificationAgentConfig) => {
let toastId: string | undefined;
+ const instance = {
+ ...testData,
+ id: undefined,
+ };
+
try {
addToast(
intl.formatMessage(messages.toastTestSending),
@@ -115,7 +130,7 @@ const NotificationModal = ({
toastId = id;
}
);
- await axios.post('/api/v1/settings/notification/test', testData);
+ await axios.post('/api/v1/settings/notification/test', instance);
if (toastId) {
removeToast(toastId);
From fea6192dc04c3fc5f2987ae266a32fef006bb152 Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Sun, 26 Apr 2026 20:12:56 +0200
Subject: [PATCH 58/62] feat(notifications): enhance notification agent
template api documentation
---
seerr-api.yml | 62 ++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 57 insertions(+), 5 deletions(-)
diff --git a/seerr-api.yml b/seerr-api.yml
index 64e60fb934..c2c171ffa0 100644
--- a/seerr-api.yml
+++ b/seerr-api.yml
@@ -1463,6 +1463,12 @@ components:
type: string
enableMentions:
type: boolean
+ locale:
+ type: string
+ example: en
+ useUserLocale:
+ type: boolean
+ example: true
slack:
type: object
properties:
@@ -1480,6 +1486,9 @@ components:
properties:
webhookUrl:
type: string
+ locale:
+ type: string
+ example: en
webpush:
type: object
properties:
@@ -1509,13 +1518,25 @@ components:
properties:
webhookUrl:
type: string
- authHeader:
- type: string
jsonPayload:
type: string
+ authHeader:
+ type: string
+ nullable: true
+ customHeaders:
+ type: array
+ nullable: true
+ items:
+ type: object
+ properties:
+ key:
+ type: string
+ value:
+ type: string
supportVariables:
type: boolean
example: false
+ nullable: true
telegram:
type: object
properties:
@@ -1601,6 +1622,14 @@ components:
type: string
token:
type: string
+ priority:
+ type: integer
+ minimum: 0
+ maximum: 9
+ example: 5
+ locale:
+ type: string
+ example: en
ntfy:
type: object
properties:
@@ -1622,14 +1651,28 @@ components:
type: string
authMethodUsernamePassword:
type: boolean
+ nullable: true
username:
type: string
+ nullable: true
password:
type: string
+ nullable: true
authMethodToken:
type: boolean
+ nullable: true
token:
type: string
+ nullable: true
+ priority:
+ type: integer
+ minimum: 1
+ maximum: 5
+ nullable: true
+ example: 3
+ locale:
+ type: string
+ example: en
lunasea:
type: object
properties:
@@ -1664,12 +1707,12 @@ components:
options:
type: object
properties:
+ userEmailRequired:
+ type: boolean
+ example: false
emailFrom:
type: string
example: no-reply@example.com
- senderName:
- type: string
- example: Jellyseerr
smtpHost:
type: string
example: 127.0.0.1
@@ -1694,6 +1737,15 @@ components:
allowSelfSigned:
type: boolean
example: false
+ senderName:
+ type: string
+ example: Jellyseerr
+ pgpPrivateKey:
+ type: string
+ nullable: true
+ pgpPassword:
+ type: string
+ nullable: true
Job:
type: object
properties:
From d56738e8d44a0c7b02d6187155164859c9fa904e Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Sun, 26 Apr 2026 20:31:44 +0200
Subject: [PATCH 59/62] feat(notifications): deny new notification instance
edit
---
server/routes/settings/notification.ts | 51 +++++++++-----------------
1 file changed, 18 insertions(+), 33 deletions(-)
diff --git a/server/routes/settings/notification.ts b/server/routes/settings/notification.ts
index 34db6fbcd8..a39808cd26 100644
--- a/server/routes/settings/notification.ts
+++ b/server/routes/settings/notification.ts
@@ -148,7 +148,7 @@ notificationRoutes.get<{ id: string }>('/:id', (req, res, next) => {
);
if (!notificationInstance) {
- return next({ status: '404', message: 'Notifications instance not found' });
+ return next({ status: '404', message: 'Notification instance not found' });
}
res.status(200).json(notificationInstance);
@@ -159,49 +159,34 @@ notificationRoutes.post<{ id: string }>('/:id', async (req, res, next) => {
const instances = settings.notification.instances;
const notificationInstanceId = Number(req.params.id);
- let notificationInstanceIndex = instances.findIndex(
+ const notificationInstanceIndex = instances.findIndex(
(instance) => instance.id === notificationInstanceId
);
const request = req.body;
request.id = notificationInstanceId;
- // instance was not found -> register new one with new id
if (notificationInstanceIndex === -1) {
- const notificationAgent = createAccordingNotificationAgent(
- request,
- notificationInstanceId
- );
-
- if (!notificationAgent) {
- return next({
- status: 500,
- message: 'A valid agent is missing from the request.',
- });
- }
-
- notificationManager.registerAgent(notificationAgent);
+ return next({ status: 404, message: 'Notification instance not found' });
+ }
- notificationInstanceIndex = instances.length;
- } else {
- const notificationAgent = createAccordingNotificationAgent(
- request,
- notificationInstanceId
- );
-
- if (!notificationAgent) {
- return next({
- status: 500,
- message: 'A valid agent is missing from the request.',
- });
- }
+ const notificationAgent = createAccordingNotificationAgent(
+ request,
+ notificationInstanceId
+ );
- notificationManager.reregisterAgent(
- notificationAgent,
- notificationInstanceId
- );
+ if (!notificationAgent) {
+ return next({
+ status: 500,
+ message: 'A valid agent is missing from the request.',
+ });
}
+ notificationManager.reregisterAgent(
+ notificationAgent,
+ notificationInstanceId
+ );
+
instances[notificationInstanceIndex] = request;
await settings.save();
From 23eb5c7699d671635821073cf28cbb5c37559c2d Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Sun, 26 Apr 2026 20:53:09 +0200
Subject: [PATCH 60/62] feat(notifications): remove id from notification
instance enabled and default toggle call
---
.../NotificationInstanceList.tsx | 39 +++++++++----------
1 file changed, 18 insertions(+), 21 deletions(-)
diff --git a/src/components/Settings/SettingsNotifications/NotificationInstanceList.tsx b/src/components/Settings/SettingsNotifications/NotificationInstanceList.tsx
index 257595a4f8..c1e1e386a3 100644
--- a/src/components/Settings/SettingsNotifications/NotificationInstanceList.tsx
+++ b/src/components/Settings/SettingsNotifications/NotificationInstanceList.tsx
@@ -77,7 +77,7 @@ function NotificationInstanceList({
autoDismiss: true,
appearance: 'success',
});
- } catch (e) {
+ } catch {
addToast(intl.formatMessage(messages.instanceDeleteError), {
autoDismiss: true,
appearance: 'error',
@@ -112,7 +112,7 @@ function NotificationInstanceList({
autoDismiss: true,
appearance: 'success',
});
- } catch (e) {
+ } catch {
if (toastId) {
removeToast(toastId);
}
@@ -123,8 +123,12 @@ function NotificationInstanceList({
}
};
- const toggleInstanceEnabled = async (instance: NotificationAgentConfig) => {
- instance.enabled = !instance.enabled;
+ const toggleInstanceEnabled = async (toggleData: NotificationAgentConfig) => {
+ const instance = {
+ ...toggleData,
+ id: undefined,
+ enabled: !toggleData.enabled,
+ };
if (!instance.enabled) {
instance.default = false;
@@ -132,7 +136,7 @@ function NotificationInstanceList({
try {
await axios.post(
- `/api/v1/settings/notification/${instance.id}`,
+ `/api/v1/settings/notification/${toggleData.id}`,
instance
);
@@ -140,7 +144,7 @@ function NotificationInstanceList({
appearance: 'success',
autoDismiss: true,
});
- } catch (e) {
+ } catch {
addToast(intl.formatMessage(messages.toastEnabledToggleFailed), {
appearance: 'error',
autoDismiss: true,
@@ -150,23 +154,16 @@ function NotificationInstanceList({
}
};
- const toggleInstanceDefault = async (instance: NotificationAgentConfig) => {
- const currentDefault = instances.find(
- (result) =>
- result.agent === instance.agent &&
- result.default &&
- result.id !== instance.id
- );
-
- if (currentDefault) {
- return;
- }
-
- instance.default = !instance.default;
+ const toggleInstanceDefault = async (toggleData: NotificationAgentConfig) => {
+ const instance = {
+ ...toggleData,
+ id: undefined,
+ default: !toggleData.default,
+ };
try {
await axios.post(
- `/api/v1/settings/notification/${instance.id}`,
+ `/api/v1/settings/notification/${toggleData.id}`,
instance
);
@@ -174,7 +171,7 @@ function NotificationInstanceList({
appearance: 'success',
autoDismiss: true,
});
- } catch (e) {
+ } catch {
addToast(intl.formatMessage(messages.toastDefaultToggleFailed), {
appearance: 'error',
autoDismiss: true,
From 2bbed112e262f65e9fd847fdaf61d22206cfeb85 Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Sun, 26 Apr 2026 20:58:11 +0200
Subject: [PATCH 61/62] feat(notifications): ensure only one default
notification instance exists per type
---
server/routes/settings/notification.ts | 18 ++++++++++++++++--
.../NotificationInstanceList.tsx | 10 +---------
2 files changed, 17 insertions(+), 11 deletions(-)
diff --git a/server/routes/settings/notification.ts b/server/routes/settings/notification.ts
index a39808cd26..cd4f1cabda 100644
--- a/server/routes/settings/notification.ts
+++ b/server/routes/settings/notification.ts
@@ -5,6 +5,7 @@ import type { NotificationAgentConfig } from '@server/interfaces/settings';
import notificationManager, {
Notification,
createAccordingNotificationAgent,
+ retrieveDefaultNotificationInstanceSettings,
} from '@server/lib/notifications';
import type { NotificationAgent } from '@server/lib/notifications/agents/agent';
import { getSettings } from '@server/lib/settings';
@@ -107,6 +108,13 @@ notificationRoutes.post('/', async (req, res, next) => {
notificationManager.registerAgent(notificationAgent);
+ if (request.default) {
+ const defaultInstance = retrieveDefaultNotificationInstanceSettings(
+ request.agent
+ );
+ defaultInstance.default = false;
+ }
+
const notificationInstanceIndex = instances.length;
instances[notificationInstanceIndex] = request;
await settings.save();
@@ -187,8 +195,14 @@ notificationRoutes.post<{ id: string }>('/:id', async (req, res, next) => {
notificationInstanceId
);
- instances[notificationInstanceIndex] = request;
+ if (request.default) {
+ const defaultInstance = retrieveDefaultNotificationInstanceSettings(
+ request.agent
+ );
+ defaultInstance.default = false;
+ }
+ instances[notificationInstanceIndex] = request;
await settings.save();
res.status(200).json(instances[notificationInstanceIndex]);
@@ -206,9 +220,9 @@ notificationRoutes.delete<{ id: string }>('/:id', async (req, res, next) => {
return next({ status: 404, message: 'Notifications instance not found' });
}
- instances.splice(notificationInstanceIndex, 1);
notificationManager.unregisterAgent(Number(req.params.id));
+ instances.splice(notificationInstanceIndex, 1);
await settings.save();
res.status(200).send();
diff --git a/src/components/Settings/SettingsNotifications/NotificationInstanceList.tsx b/src/components/Settings/SettingsNotifications/NotificationInstanceList.tsx
index c1e1e386a3..134e228d5d 100644
--- a/src/components/Settings/SettingsNotifications/NotificationInstanceList.tsx
+++ b/src/components/Settings/SettingsNotifications/NotificationInstanceList.tsx
@@ -224,15 +224,7 @@ function NotificationInstanceList({
onClick={() => {
toggleInstanceDefault(instance);
}}
- disabled={
- !instance.enabled ||
- instances.findIndex(
- (result) =>
- result.agent === instance.agent &&
- result.default &&
- result.id !== instance.id
- ) !== -1
- }
+ disabled={!instance.enabled}
checked={instance.default}
/>
From e83a9245e490e94162e158c7d2b307888976c6b7 Mon Sep 17 00:00:00 2001
From: Schrottfresser <39998368+Schrottfresser@users.noreply.github.com>
Date: Sun, 26 Apr 2026 21:02:29 +0200
Subject: [PATCH 62/62] feat(notifications): fix sucessfully typo in api docs
---
seerr-api.yml | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/seerr-api.yml b/seerr-api.yml
index c2c171ffa0..d000b4ab94 100644
--- a/seerr-api.yml
+++ b/seerr-api.yml
@@ -2361,7 +2361,7 @@ paths:
$ref: '#/components/schemas/MainSettings'
responses:
'200':
- description: 'Values were sucessfully updated'
+ description: 'Values were successfully updated'
content:
application/json:
schema:
@@ -2392,7 +2392,7 @@ paths:
$ref: '#/components/schemas/NetworkSettings'
responses:
'200':
- description: 'Values were sucessfully updated'
+ description: 'Values were successfully updated'
content:
application/json:
schema:
@@ -3466,7 +3466,7 @@ paths:
$ref: '#/components/schemas/NotificationInstance'
responses:
'200':
- description: 'Notification instance was sucessfully added'
+ description: 'Notification instance was successfully added'
content:
application/json:
schema:
@@ -3526,7 +3526,7 @@ paths:
$ref: '#/components/schemas/NotificationInstance'
responses:
'200':
- description: 'Notification instance was sucessfully added or updated'
+ description: 'Notification instance was successfully added or updated'
content:
application/json:
schema:
@@ -3545,7 +3545,7 @@ paths:
example: 0
responses:
'200':
- description: 'Notification instance was sucessfully deleted'
+ description: 'Notification instance was successfully deleted'
/settings/discover:
get:
summary: Get all discover sliders
| |