{errors.host && touched.host && (
@@ -185,9 +199,7 @@ const JellyfinLogin: React.FC
= ({
username: Yup.string().required(
intl.formatMessage(messages.validationusernamerequired)
),
- password: Yup.string().required(
- intl.formatMessage(messages.validationpasswordrequired)
- ),
+ password: Yup.string(),
});
return (
@@ -266,8 +278,11 @@ const JellyfinLogin: React.FC
= ({
as="a"
buttonType="ghost"
href={
- settings.currentSettings.jellyfinHost +
- '/web/#!/forgotpassword.html'
+ process.env.JELLYFIN_TYPE == 'emby'
+ ? settings.currentSettings.jellyfinHost +
+ '/web/index.html#!/startup/forgotpassword.html'
+ : settings.currentSettings.jellyfinHost +
+ '/web/index.html#!/forgotpassword.html'
}
>
{intl.formatMessage(messages.forgotpassword)}
diff --git a/src/components/Login/index.tsx b/src/components/Login/index.tsx
index 5d3e7a3d..eb8f368b 100644
--- a/src/components/Login/index.tsx
+++ b/src/components/Login/index.tsx
@@ -15,12 +15,13 @@ import PlexLoginButton from '../PlexLoginButton';
import Transition from '../Transition';
import JellyfinLogin from './JellyfinLogin';
import LocalLogin from './LocalLogin';
+import getConfig from 'next/config';
const messages = defineMessages({
signin: 'Sign In',
signinheader: 'Sign in to continue',
signinwithplex: 'Use your Plex account',
- signinwithjellyfin: 'Use your Jellyfin account',
+ signinwithjellyfin: 'Use your {mediaServerName} account',
signinwithoverseerr: 'Use your {applicationTitle} account',
});
@@ -32,6 +33,7 @@ const Login: React.FC = () => {
const { user, revalidate } = useUser();
const router = useRouter();
const settings = useSettings();
+ const { publicRuntimeConfig } = getConfig();
// Effect that is triggered when the `authToken` comes back from the Plex OAuth
// We take the token and attempt to sign in. If we get a success message, we will
@@ -133,7 +135,12 @@ const Login: React.FC = () => {
{settings.currentSettings.mediaServerType ==
MediaServerType.PLEX
? intl.formatMessage(messages.signinwithplex)
- : intl.formatMessage(messages.signinwithjellyfin)}
+ : intl.formatMessage(messages.signinwithjellyfin, {
+ mediaServerName:
+ publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
+ ? 'Emby'
+ : 'Jellyfin',
+ })}
diff --git a/src/components/ManageSlideOver/index.tsx b/src/components/ManageSlideOver/index.tsx
index 8c6f2791..a1e9bab1 100644
--- a/src/components/ManageSlideOver/index.tsx
+++ b/src/components/ManageSlideOver/index.tsx
@@ -210,7 +210,7 @@ const ManageSlideOver: React.FC<
{hasPermission(Permission.ADMIN) &&
(data.mediaInfo?.serviceUrl ||
data.mediaInfo?.tautulliUrl ||
- watchData?.data?.playCount) && (
+ !!watchData?.data?.playCount) && (
{intl.formatMessage(messages.manageModalMedia)}
@@ -272,7 +272,7 @@ const ManageSlideOver: React.FC<
@@ -325,7 +325,7 @@ const ManageSlideOver: React.FC<
{hasPermission(Permission.ADMIN) &&
(data.mediaInfo?.serviceUrl4k ||
data.mediaInfo?.tautulliUrl4k ||
- watchData?.data4k?.playCount) && (
+ !!watchData?.data4k?.playCount) && (
{intl.formatMessage(messages.manageModalMedia4k)}
@@ -387,7 +387,7 @@ const ManageSlideOver: React.FC<
diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx
index fbbad4bd..3e33c4b8 100644
--- a/src/components/MovieDetails/index.tsx
+++ b/src/components/MovieDetails/index.tsx
@@ -48,6 +48,7 @@ import PersonCard from '../PersonCard';
import RequestButton from '../RequestButton';
import Slider from '../Slider';
import StatusBadge from '../StatusBadge';
+import getConfig from 'next/config';
const messages = defineMessages({
originaltitle: 'Original Title',
@@ -95,6 +96,7 @@ const MovieDetails: React.FC = ({ movie }) => {
const minStudios = 3;
const [showMoreStudios, setShowMoreStudios] = useState(false);
const [showIssueModal, setShowIssueModal] = useState(false);
+ const { publicRuntimeConfig } = getConfig();
const {
data,
@@ -130,10 +132,7 @@ const MovieDetails: React.FC = ({ movie }) => {
if (data.mediaInfo?.mediaUrl) {
mediaLinks.push({
- text:
- settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
- ? intl.formatMessage(messages.play, { mediaServerName: 'Jellyfin' })
- : intl.formatMessage(messages.play, { mediaServerName: 'Plex' }),
+ text: getAvalaibleMediaServerName(),
url: data.mediaInfo?.mediaUrl,
svg: ,
});
@@ -146,10 +145,7 @@ const MovieDetails: React.FC = ({ movie }) => {
})
) {
mediaLinks.push({
- text:
- settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
- ? intl.formatMessage(messages.play4k, { mediaServerName: 'Jellyfin' })
- : intl.formatMessage(messages.play4k, { mediaServerName: 'Plex' }),
+ text: getAvalaible4kMediaServerName(),
url: data.mediaInfo?.mediaUrl4k,
svg: ,
});
@@ -228,6 +224,30 @@ const MovieDetails: React.FC = ({ movie }) => {
data?.watchProviders?.find((provider) => provider.iso_3166_1 === region)
?.flatrate ?? [];
+ function getAvalaibleMediaServerName() {
+ if (publicRuntimeConfig.JELLYFIN_TYPE === 'emby') {
+ return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
+ }
+
+ if (settings.currentSettings.mediaServerType === MediaServerType.PLEX) {
+ return intl.formatMessage(messages.play, { mediaServerName: 'Plex' });
+ }
+
+ return intl.formatMessage(messages.play, { mediaServerName: 'Jellyfin' });
+ }
+
+ function getAvalaible4kMediaServerName() {
+ if (publicRuntimeConfig.JELLYFIN_TYPE === 'emby') {
+ return intl.formatMessage(messages.play4k, { mediaServerName: 'Emby' });
+ }
+
+ if (settings.currentSettings.mediaServerType === MediaServerType.PLEX) {
+ return intl.formatMessage(messages.play4k, { mediaServerName: 'Plex' });
+ }
+
+ return intl.formatMessage(messages.play4k, { mediaServerName: 'Jellyfin' });
+ }
+
return (
= ({ request, onUpdate }) => {
const intl = useIntl();
const [isUpdating, setIsUpdating] = useState(false);
const [showEditModal, setShowEditModal] = useState(false);
- const { profile, rootFolder, server } = useRequestOverride(request);
+ const { profile, rootFolder, server, languageProfile } =
+ useRequestOverride(request);
const updateRequest = async (type: 'approve' | 'decline'): Promise => {
setIsUpdating(true);
@@ -209,7 +211,7 @@ const RequestBlock: React.FC = ({ request, onUpdate }) => {
)}
- {(server || profile !== null || rootFolder) && (
+ {(server || profile || rootFolder || languageProfile) && (
<>
{intl.formatMessage(messages.requestoverrides)}
@@ -223,12 +225,12 @@ const RequestBlock: React.FC
= ({ request, onUpdate }) => {
{server}
)}
- {profile !== null && (
+ {profile && (
{intl.formatMessage(messages.profilechanged)}
- ID {profile}
+ {profile}
)}
{rootFolder && (
@@ -239,6 +241,14 @@ const RequestBlock: React.FC = ({ request, onUpdate }) => {
{rootFolder}
)}
+ {languageProfile && (
+
+
+ {intl.formatMessage(messages.languageprofile)}
+
+ {languageProfile}
+
+ )}
>
)}
diff --git a/src/components/RequestCard/index.tsx b/src/components/RequestCard/index.tsx
index cc52f155..4ac1bfe9 100644
--- a/src/components/RequestCard/index.tsx
+++ b/src/components/RequestCard/index.tsx
@@ -231,7 +231,7 @@ const RequestCard: React.FC = ({ request, onTitleData }) => {
{requestData.requestedBy.displayName}
diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx
index b3c18e8f..6c98281e 100644
--- a/src/components/RequestList/RequestItem/index.tsx
+++ b/src/components/RequestList/RequestItem/index.tsx
@@ -336,7 +336,7 @@ const RequestItem: React.FC = ({
{requestData.requestedBy.displayName}
@@ -390,7 +390,7 @@ const RequestItem: React.FC = ({
{requestData.modifiedBy.displayName}
diff --git a/src/components/RequestModal/AdvancedRequester/index.tsx b/src/components/RequestModal/AdvancedRequester/index.tsx
index d4cead4a..58bbe777 100644
--- a/src/components/RequestModal/AdvancedRequester/index.tsx
+++ b/src/components/RequestModal/AdvancedRequester/index.tsx
@@ -534,7 +534,7 @@ const AdvancedRequester: React.FC = ({
{selectedUser.displayName}
@@ -584,7 +584,7 @@ const AdvancedRequester: React.FC = ({
{user.displayName}
diff --git a/src/components/Settings/Notifications/NotificationsEmail.tsx b/src/components/Settings/Notifications/NotificationsEmail.tsx
index 40308390..e20a1305 100644
--- a/src/components/Settings/Notifications/NotificationsEmail.tsx
+++ b/src/components/Settings/Notifications/NotificationsEmail.tsx
@@ -16,6 +16,7 @@ const messages = defineMessages({
validationSmtpHostRequired: 'You must provide a valid hostname or IP address',
validationSmtpPortRequired: 'You must provide a valid port number',
agentenabled: 'Enable Agent',
+ userEmailRequired: 'Require user email',
emailsender: 'Sender Address',
smtpHost: 'SMTP Host',
smtpPort: 'SMTP Port',
@@ -125,6 +126,7 @@ const NotificationsEmail: React.FC = () => {
{
await axios.post('/api/v1/settings/notifications/email', {
enabled: values.enabled,
options: {
+ userEmailRequired: values.userEmailRequired,
emailFrom: values.emailFrom,
smtpHost: values.smtpHost,
smtpPort: Number(values.smtpPort),
@@ -241,6 +244,18 @@ const NotificationsEmail: React.FC = () => {
+
+
+
+
+
+
-
diff --git a/src/components/Settings/SettingsJellyfin.tsx b/src/components/Settings/SettingsJellyfin.tsx
index 9740762c..a8cd2627 100644
--- a/src/components/Settings/SettingsJellyfin.tsx
+++ b/src/components/Settings/SettingsJellyfin.tsx
@@ -12,30 +12,31 @@ import Badge from '../Common/Badge';
import Button from '../Common/Button';
import LoadingSpinner from '../Common/LoadingSpinner';
import LibraryItem from './LibraryItem';
+import getConfig from 'next/config';
const messages = defineMessages({
- jellyfinsettings: 'Jellyfin Settings',
+ jellyfinsettings: '{mediaServerName} Settings',
jellyfinsettingsDescription:
- 'Configure the settings for your Jellyfin server. Jellyfin scans your Jellyfin libraries to see what content is available.',
+ 'Configure the settings for your {mediaServerName} server. {mediaServerName} scans your {mediaServerName} libraries to see what content is available.',
timeout: 'Timeout',
save: 'Save Changes',
saving: 'Saving…',
- jellyfinlibraries: 'Jellyfin Libraries',
+ jellyfinlibraries: '{mediaServerName} Libraries',
jellyfinlibrariesDescription:
- 'The libraries Jellyfin scans for titles. Click the button below if no libraries are listed.',
+ 'The libraries {mediaServerName} scans for titles. Click the button below if no libraries are listed.',
jellyfinSettingsFailure:
- 'Something went wrong while saving Jellyfin settings.',
- jellyfinSettingsSuccess: 'Jellyfin settings saved successfully!',
- jellyfinSettings: 'Jellyfin Settings',
+ 'Something went wrong while saving {mediaServerName} settings.',
+ jellyfinSettingsSuccess: '{mediaServerName} settings saved successfully!',
+ jellyfinSettings: '{mediaServerName} Settings',
jellyfinSettingsDescription:
- 'Optionally configure an external player endpoint for your jellyfin server that is different to the internal URL used during setup',
+ 'Optionally configure an external player endpoint for your {mediaServerName} server that is different to the internal URL used during setup',
externalUrl: 'External URL',
validationUrl: 'You must provide a valid URL',
syncing: 'Syncing',
syncJellyfin: 'Sync Libraries',
manualscanJellyfin: 'Manual Library Scan',
manualscanDescriptionJellyfin:
- "Normally, this will only be run once every 24 hours. Jellyfin will check your Jellyfin server's recently added more aggressively. If this is your first time configuring Jellyfin, a one-time full manual library scan is recommended!",
+ "Normally, this will only be run once every 24 hours. Jellyseerr will check your {mediaServerName} server's recently added more aggressively. If this is your first time configuring Jellyseerr, a one-time full manual library scan is recommended!",
notrunning: 'Not Running',
currentlibrary: 'Current Library: {name}',
librariesRemaining: 'Libraries Remaining: {count}',
@@ -80,6 +81,7 @@ const SettingsJellyfin: React.FC
= ({
);
const intl = useIntl();
const { addToast } = useToasts();
+ const { publicRuntimeConfig } = getConfig();
const JellyfinSettingsSchema = Yup.object().shape({
jellyfinExternalUrl: Yup.string().matches(
@@ -161,10 +163,22 @@ const SettingsJellyfin: React.FC = ({
<>
-
+ {publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
+ ? intl.formatMessage(messages.jellyfinlibraries, {
+ mediaServerName: 'Emby',
+ })
+ : intl.formatMessage(messages.jellyfinlibraries, {
+ mediaServerName: 'Jellyfin',
+ })}
-
+ {publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
+ ? intl.formatMessage(messages.jellyfinlibrariesDescription, {
+ mediaServerName: 'Emby',
+ })
+ : intl.formatMessage(messages.jellyfinlibrariesDescription, {
+ mediaServerName: 'Jellyfin',
+ })}
@@ -201,7 +215,13 @@ const SettingsJellyfin: React.FC
= ({
-
+ {publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
+ ? intl.formatMessage(messages.manualscanDescriptionJellyfin, {
+ mediaServerName: 'Emby',
+ })
+ : intl.formatMessage(messages.manualscanDescriptionJellyfin, {
+ mediaServerName: 'Jellyfin',
+ })}
@@ -305,10 +325,22 @@ const SettingsJellyfin: React.FC
= ({
<>
- {intl.formatMessage(messages.jellyfinSettings)}
+ {publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
+ ? intl.formatMessage(messages.jellyfinSettings, {
+ mediaServerName: 'Emby',
+ })
+ : intl.formatMessage(messages.jellyfinSettings, {
+ mediaServerName: 'Jellyfin',
+ })}
- {intl.formatMessage(messages.jellyfinSettingsDescription)}
+ {publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
+ ? intl.formatMessage(messages.jellyfinSettingsDescription, {
+ mediaServerName: 'Emby',
+ })
+ : intl.formatMessage(messages.jellyfinSettingsDescription, {
+ mediaServerName: 'Jellyfin',
+ })}
= ({
externalHostname: values.jellyfinExternalUrl,
} as JellyfinSettings);
- addToast(intl.formatMessage(messages.jellyfinSettingsSuccess), {
- autoDismiss: true,
- appearance: 'success',
- });
+ addToast(
+ intl.formatMessage(messages.jellyfinSettingsSuccess, {
+ mediaServerName:
+ publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
+ ? 'Emby'
+ : 'Jellyfin',
+ }),
+ {
+ autoDismiss: true,
+ appearance: 'success',
+ }
+ );
} catch (e) {
- addToast(intl.formatMessage(messages.jellyfinSettingsFailure), {
- autoDismiss: true,
- appearance: 'error',
- });
+ addToast(
+ intl.formatMessage(messages.jellyfinSettingsFailure, {
+ mediaServerName:
+ publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
+ ? 'Emby'
+ : 'Jellyfin',
+ }),
+ {
+ autoDismiss: true,
+ appearance: 'error',
+ }
+ );
} finally {
revalidate();
}
diff --git a/src/components/Settings/SettingsJobsCache/index.tsx b/src/components/Settings/SettingsJobsCache/index.tsx
index 35e445cb..cfe8c4bf 100644
--- a/src/components/Settings/SettingsJobsCache/index.tsx
+++ b/src/components/Settings/SettingsJobsCache/index.tsx
@@ -10,9 +10,11 @@ import {
} from 'react-intl';
import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr';
+import { MediaServerType } from '../../../../server/constants/server';
import { CacheItem } from '../../../../server/interfaces/api/settingsInterfaces';
import { JobId } from '../../../../server/lib/settings';
import Spinner from '../../../assets/spinner.svg';
+import useSettings from '../../../hooks/useSettings';
import globalMessages from '../../../i18n/globalMessages';
import { formatBytes } from '../../../utils/numberHelpers';
import Badge from '../../Common/Badge';
@@ -102,6 +104,7 @@ const SettingsJobs: React.FC = () => {
const [isSaving, setIsSaving] = useState(false);
const [jobScheduleMinutes, setJobScheduleMinutes] = useState(5);
const [jobScheduleHours, setJobScheduleHours] = useState(1);
+ const settings = useSettings();
if (!data && !error) {
return ;
@@ -369,22 +372,33 @@ const SettingsJobs: React.FC = () => {
- {cacheData?.map((cache) => (
-
- {cache.name}
- {intl.formatNumber(cache.stats.hits)}
- {intl.formatNumber(cache.stats.misses)}
- {intl.formatNumber(cache.stats.keys)}
- {formatBytes(cache.stats.ksize)}
- {formatBytes(cache.stats.vsize)}
-
-
-
-
- ))}
+ {cacheData
+ ?.filter(
+ (cache) =>
+ !(
+ settings.currentSettings.mediaServerType !==
+ MediaServerType.PLEX && cache.id === 'plexguid'
+ )
+ )
+ .map((cache) => (
+
+ {cache.name}
+ {intl.formatNumber(cache.stats.hits)}
+ {intl.formatNumber(cache.stats.misses)}
+ {intl.formatNumber(cache.stats.keys)}
+ {formatBytes(cache.stats.ksize)}
+ {formatBytes(cache.stats.vsize)}
+
+
+
+
+ ))}
diff --git a/src/components/Settings/SettingsLayout.tsx b/src/components/Settings/SettingsLayout.tsx
index f2fa9078..3de72f4a 100644
--- a/src/components/Settings/SettingsLayout.tsx
+++ b/src/components/Settings/SettingsLayout.tsx
@@ -1,5 +1,8 @@
+import getConfig from 'next/config';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
+import { MediaServerType } from '../../../server/constants/server';
+import useSettings from '../../hooks/useSettings';
import globalMessages from '../../i18n/globalMessages';
import PageTitle from '../Common/PageTitle';
import SettingsTabs, { SettingsRoute } from '../Common/SettingsTabs';
@@ -8,7 +11,7 @@ const messages = defineMessages({
menuGeneralSettings: 'General',
menuUsers: 'Users',
menuPlexSettings: 'Plex',
- menuJellyfinSettings: 'Jellyfin',
+ menuJellyfinSettings: '{mediaServerName}',
menuServices: 'Services',
menuNotifications: 'Notifications',
menuLogs: 'Logs',
@@ -18,7 +21,8 @@ const messages = defineMessages({
const SettingsLayout: React.FC = ({ children }) => {
const intl = useIntl();
-
+ const { publicRuntimeConfig } = getConfig();
+ const settings = useSettings();
const settingsRoutes: SettingsRoute[] = [
{
text: intl.formatMessage(messages.menuGeneralSettings),
@@ -30,16 +34,17 @@ const SettingsLayout: React.FC = ({ children }) => {
route: '/settings/users',
regex: /^\/settings\/users/,
},
- {
- text: intl.formatMessage(messages.menuPlexSettings),
- route: '/settings/plex',
- regex: /^\/settings\/plex/,
- },
- {
- text: intl.formatMessage(messages.menuJellyfinSettings),
- route: '/settings/jellyfin',
- regex: /^\/settings\/jellyfin/,
- },
+ settings.currentSettings.mediaServerType === MediaServerType.PLEX
+ ? {
+ text: intl.formatMessage(messages.menuPlexSettings),
+ route: '/settings/plex',
+ regex: /^\/settings\/plex/,
+ }
+ : {
+ text: getAvailableMediaServerName(),
+ route: '/settings/jellyfin',
+ regex: /^\/settings\/jellyfin/,
+ },
{
text: intl.formatMessage(messages.menuServices),
route: '/settings/services',
@@ -76,6 +81,12 @@ const SettingsLayout: React.FC = ({ children }) => {
{children}
>
);
+ function getAvailableMediaServerName() {
+ return intl.formatMessage(messages.menuJellyfinSettings, {
+ mediaServerName:
+ publicRuntimeConfig.JELLYFIN_TYPE === 'emby' ? 'Emby' : 'Jellyfin',
+ });
+ }
};
export default SettingsLayout;
diff --git a/src/components/Settings/SettingsUsers/index.tsx b/src/components/Settings/SettingsUsers/index.tsx
index d53a619b..89c89673 100644
--- a/src/components/Settings/SettingsUsers/index.tsx
+++ b/src/components/Settings/SettingsUsers/index.tsx
@@ -14,6 +14,7 @@ import LoadingSpinner from '../../Common/LoadingSpinner';
import PageTitle from '../../Common/PageTitle';
import PermissionEdit from '../../PermissionEdit';
import QuotaSelector from '../../QuotaSelector';
+import getConfig from 'next/config';
const messages = defineMessages({
users: 'Users',
@@ -42,6 +43,7 @@ const SettingsUsers: React.FC = () => {
mutate: revalidate,
} = useSWR('/api/v1/settings/main');
const settings = useSettings();
+ const { publicRuntimeConfig } = getConfig();
if (!data && !error) {
return ;
@@ -131,16 +133,20 @@ const SettingsUsers: React.FC = () => {
+
+
+
+
+ w === 'userEmailRequired')
+ ? 'border-2 border-red-400 focus:border-blue-600'
+ : ''
+ }
+ />
+
+ {errors.email && touched.email && (
+
{errors.email}
+ )}
+
+