mirror of
https://github.com/seerr-team/seerr.git
synced 2026-10-12 02:22:17 +00:00
fix: stop masking connection failures across media server sync and login (#3324)
This commit is contained in:
+74
-14
@@ -233,7 +233,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
}
|
||||
);
|
||||
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.Unknown);
|
||||
if (!e.response) {
|
||||
throw new ApiError(502, ApiErrorCode.ConnectionError);
|
||||
}
|
||||
|
||||
throw new ApiError(e.response.status, ApiErrorCode.Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,7 +254,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.Unknown);
|
||||
if (!e.response) {
|
||||
throw new ApiError(502, ApiErrorCode.ConnectionError);
|
||||
}
|
||||
|
||||
throw new ApiError(e.response.status, ApiErrorCode.Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +278,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.Unknown);
|
||||
if (!e.response) {
|
||||
throw new ApiError(502, ApiErrorCode.ConnectionError);
|
||||
}
|
||||
|
||||
throw new ApiError(e.response.status, ApiErrorCode.Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,7 +301,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.Unknown);
|
||||
if (!e.response) {
|
||||
throw new ApiError(502, ApiErrorCode.ConnectionError);
|
||||
}
|
||||
|
||||
throw new ApiError(e.response.status, ApiErrorCode.Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +320,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
|
||||
return systemInfoResponse;
|
||||
} catch (e) {
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
|
||||
if (!e.response) {
|
||||
throw new ApiError(502, ApiErrorCode.ConnectionError);
|
||||
}
|
||||
|
||||
throw new ApiError(e.response.status, ApiErrorCode.InvalidAuthToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,7 +341,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.Unknown);
|
||||
if (!e.response) {
|
||||
throw new ApiError(502, ApiErrorCode.ConnectionError);
|
||||
}
|
||||
|
||||
throw new ApiError(e.response.status, ApiErrorCode.Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,7 +360,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
|
||||
if (!e.response) {
|
||||
throw new ApiError(502, ApiErrorCode.ConnectionError);
|
||||
}
|
||||
|
||||
throw new ApiError(e.response.status, ApiErrorCode.InvalidAuthToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +380,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
|
||||
if (!e.response) {
|
||||
throw new ApiError(502, ApiErrorCode.ConnectionError);
|
||||
}
|
||||
|
||||
throw new ApiError(e.response.status, ApiErrorCode.InvalidAuthToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,6 +411,10 @@ class JellyfinAPI extends ExternalAPI {
|
||||
}
|
||||
);
|
||||
|
||||
if (!e.response) {
|
||||
throw new ApiError(502, ApiErrorCode.ConnectionError);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -425,7 +461,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
{ label: 'Jellyfin API', error: e?.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
|
||||
if (!e.response) {
|
||||
throw new ApiError(502, ApiErrorCode.ConnectionError);
|
||||
}
|
||||
|
||||
throw new ApiError(e.response.status, ApiErrorCode.InvalidAuthToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,7 +490,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
|
||||
if (!e.response) {
|
||||
throw new ApiError(502, ApiErrorCode.ConnectionError);
|
||||
}
|
||||
|
||||
throw new ApiError(e.response.status, ApiErrorCode.InvalidAuthToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,7 +521,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
`Something went wrong while getting library content from the Jellyfin server: ${e.message}`,
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
|
||||
if (!e.response) {
|
||||
throw new ApiError(502, ApiErrorCode.ConnectionError);
|
||||
}
|
||||
|
||||
throw new ApiError(e.response.status, ApiErrorCode.InvalidAuthToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,7 +540,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
|
||||
if (!e.response) {
|
||||
throw new ApiError(502, ApiErrorCode.ConnectionError);
|
||||
}
|
||||
|
||||
throw new ApiError(e.response.status, ApiErrorCode.InvalidAuthToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,7 +575,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
|
||||
if (!e.response) {
|
||||
throw new ApiError(502, ApiErrorCode.ConnectionError);
|
||||
}
|
||||
|
||||
throw new ApiError(e.response.status, ApiErrorCode.InvalidAuthToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,7 +596,11 @@ class JellyfinAPI extends ExternalAPI {
|
||||
{ label: 'Jellyfin API', error: e.response?.status }
|
||||
);
|
||||
|
||||
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
|
||||
if (!e.response) {
|
||||
throw new ApiError(502, ApiErrorCode.ConnectionError);
|
||||
}
|
||||
|
||||
throw new ApiError(e.response.status, ApiErrorCode.InvalidAuthToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import ExternalAPI from '@server/api/externalapi';
|
||||
import { ApiErrorCode } from '@server/constants/error';
|
||||
import type { Library, PlexSettings } from '@server/lib/settings';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
import { ApiError } from '@server/types/error';
|
||||
|
||||
interface PlexStatusResponse {
|
||||
MediaContainer: {
|
||||
@@ -167,7 +169,11 @@ class PlexAPI extends ExternalAPI {
|
||||
message: e.message,
|
||||
});
|
||||
|
||||
settings.plex.libraries = [];
|
||||
if (!e.response) {
|
||||
throw new ApiError(502, ApiErrorCode.ConnectionError);
|
||||
}
|
||||
|
||||
throw new ApiError(e.response.status, ApiErrorCode.Unknown);
|
||||
}
|
||||
|
||||
await settings.save();
|
||||
|
||||
@@ -5,6 +5,7 @@ export enum ApiErrorCode {
|
||||
InvalidEmail = 'INVALID_EMAIL',
|
||||
NotAdmin = 'NOT_ADMIN',
|
||||
NoAdminUser = 'NO_ADMIN_USER',
|
||||
ConnectionError = 'CONNECTION_ERROR',
|
||||
SyncErrorGroupedFolders = 'SYNC_ERROR_GROUPED_FOLDERS',
|
||||
SyncErrorNoLibraries = 'SYNC_ERROR_NO_LIBRARIES',
|
||||
Unauthorized = 'UNAUTHORIZED',
|
||||
|
||||
+11
-1
@@ -120,7 +120,17 @@ app
|
||||
});
|
||||
|
||||
const plexapi = new PlexAPI({ plexToken: admin.plexToken });
|
||||
await plexapi.syncLibraries();
|
||||
|
||||
try {
|
||||
await plexapi.syncLibraries();
|
||||
} catch {
|
||||
// Leave the existing libraries untouched so the migration retries on
|
||||
// the next startup instead of discarding the user's configuration
|
||||
logger.warn(
|
||||
'Failed to migrate Plex libraries; will retry on next startup',
|
||||
{ label: 'Settings' }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -543,6 +543,30 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
message: e.errorCode,
|
||||
});
|
||||
|
||||
case ApiErrorCode.ConnectionError:
|
||||
logger.error(
|
||||
`Unable to reach the ${
|
||||
settings.main.mediaServerType === MediaServerType.JELLYFIN
|
||||
? ServerType.JELLYFIN
|
||||
: ServerType.EMBY
|
||||
} server.`,
|
||||
{
|
||||
label: 'Auth',
|
||||
error: e.errorCode,
|
||||
status: e.statusCode,
|
||||
hostname: getHostname({
|
||||
useSsl: body.useSsl,
|
||||
ip: body.hostname,
|
||||
port: body.port,
|
||||
urlBase: body.urlBase,
|
||||
}),
|
||||
}
|
||||
);
|
||||
return next({
|
||||
status: e.statusCode,
|
||||
message: e.errorCode,
|
||||
});
|
||||
|
||||
case ApiErrorCode.InvalidCredentials:
|
||||
logger.warn(
|
||||
'Failed sign-in attempt from user with incorrect Jellyfin credentials',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { JellyfinLibrary } from '@server/api/jellyfin';
|
||||
import JellyfinAPI from '@server/api/jellyfin';
|
||||
import PlexAPI from '@server/api/plexapi';
|
||||
import PlexTvAPI from '@server/api/plextv';
|
||||
@@ -263,7 +264,7 @@ settingsRoutes.put('/plex/library/:libraryId', async (req, res, next) => {
|
||||
return res.status(200).json(library);
|
||||
});
|
||||
|
||||
settingsRoutes.post('/plex/library/sync', async (_req, res) => {
|
||||
settingsRoutes.post('/plex/library/sync', async (_req, res, next) => {
|
||||
const settings = getSettings();
|
||||
|
||||
const userRepository = getRepository(User);
|
||||
@@ -273,7 +274,14 @@ settingsRoutes.post('/plex/library/sync', async (_req, res) => {
|
||||
});
|
||||
const plexapi = new PlexAPI({ plexToken: admin.plexToken });
|
||||
|
||||
await plexapi.syncLibraries();
|
||||
try {
|
||||
await plexapi.syncLibraries();
|
||||
} catch (e) {
|
||||
return next({
|
||||
status: e.statusCode ?? 500,
|
||||
message: e.errorCode ?? ApiErrorCode.Unknown,
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).json(settings.plex.libraries);
|
||||
});
|
||||
@@ -400,22 +408,31 @@ settingsRoutes.post('/jellyfin/library/sync', async (_req, res, next) => {
|
||||
|
||||
jellyfinClient.setUserId(admin.jellyfinUserId ?? '');
|
||||
|
||||
const libraries = await jellyfinClient.getLibraries();
|
||||
let libraries: JellyfinLibrary[];
|
||||
|
||||
if (libraries.length === 0) {
|
||||
// Check if no libraries are found due to the fallback to user views
|
||||
// This only affects LDAP users
|
||||
const account = await jellyfinClient.getUser();
|
||||
try {
|
||||
libraries = await jellyfinClient.getLibraries();
|
||||
|
||||
// Automatic Library grouping is not supported when user views are used to get library
|
||||
if (account.Configuration.GroupedFolders?.length > 0) {
|
||||
return next({
|
||||
status: 501,
|
||||
message: ApiErrorCode.SyncErrorGroupedFolders,
|
||||
});
|
||||
if (libraries.length === 0) {
|
||||
// Check if no libraries are found due to the fallback to user views
|
||||
// This only affects LDAP users
|
||||
const account = await jellyfinClient.getUser();
|
||||
|
||||
// Automatic Library grouping is not supported when user views are used to get library
|
||||
if (account.Configuration.GroupedFolders?.length > 0) {
|
||||
return next({
|
||||
status: 501,
|
||||
message: ApiErrorCode.SyncErrorGroupedFolders,
|
||||
});
|
||||
}
|
||||
|
||||
return next({ status: 404, message: ApiErrorCode.SyncErrorNoLibraries });
|
||||
}
|
||||
|
||||
return next({ status: 404, message: ApiErrorCode.SyncErrorNoLibraries });
|
||||
} catch (e) {
|
||||
return next({
|
||||
status: e.statusCode ?? 500,
|
||||
message: e.errorCode ?? ApiErrorCode.Unknown,
|
||||
});
|
||||
}
|
||||
|
||||
const newLibraries: Library[] = libraries.map((library) => {
|
||||
|
||||
@@ -98,6 +98,7 @@ const JellyfinLogin = ({ revalidate, serverType }: JellyfinLoginProps) => {
|
||||
let errorMessage = messages.loginerror;
|
||||
switch (e?.response?.data?.message) {
|
||||
case ApiErrorCode.InvalidUrl:
|
||||
case ApiErrorCode.ConnectionError:
|
||||
errorMessage = messages.invalidurlerror;
|
||||
break;
|
||||
case ApiErrorCode.InvalidCredentials:
|
||||
|
||||
@@ -47,6 +47,8 @@ const messages = defineMessages('components.Settings', {
|
||||
'Custom authentication with Automatic Library Grouping not supported',
|
||||
jellyfinSyncFailedGenericError:
|
||||
'Something went wrong while syncing libraries',
|
||||
jellyfinSyncFailedConnectionError:
|
||||
'Unable to reach the {mediaServerName} server. Check that it is running and reachable from Seerr.',
|
||||
toggleLibraryFailure: 'Failed to update library.',
|
||||
invalidurlerror: 'Unable to connect to {mediaServerName} server.',
|
||||
syncing: 'Syncing',
|
||||
@@ -86,11 +88,9 @@ interface SyncStatus {
|
||||
|
||||
interface SettingsJellyfinProps {
|
||||
isSetupSettings?: boolean;
|
||||
onComplete?: () => void;
|
||||
}
|
||||
|
||||
const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
onComplete,
|
||||
isSetupSettings,
|
||||
}) => {
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
@@ -102,7 +102,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
const { data: dataSync, mutate: revalidateSync } = useSWR<SyncStatus>(
|
||||
'/api/v1/settings/jellyfin/sync',
|
||||
{
|
||||
refreshInterval: 1000,
|
||||
refreshInterval: (latestData) => (latestData?.running ? 1000 : 10000),
|
||||
}
|
||||
);
|
||||
const intl = useIntl();
|
||||
@@ -177,6 +177,19 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
appearance: 'warning',
|
||||
}
|
||||
);
|
||||
} else if (e?.response?.data?.message === 'CONNECTION_ERROR') {
|
||||
addToast(
|
||||
intl.formatMessage(messages.jellyfinSyncFailedConnectionError, {
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType === MediaServerType.EMBY
|
||||
? 'Emby'
|
||||
: 'Jellyfin',
|
||||
}),
|
||||
{
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
}
|
||||
);
|
||||
} else if (e?.response?.data?.message === 'SYNC_ERROR_NO_LIBRARIES') {
|
||||
addToast(
|
||||
intl.formatMessage(messages.jellyfinSyncFailedNoLibrariesFound),
|
||||
@@ -216,10 +229,6 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
await axios.put(`/api/v1/settings/jellyfin/library/${libraryId}`, {
|
||||
enabled: !activeLibraries.includes(libraryId),
|
||||
});
|
||||
|
||||
if (onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
} catch {
|
||||
addToast(intl.formatMessage(messages.toggleLibraryFailure), {
|
||||
autoDismiss: true,
|
||||
|
||||
@@ -42,6 +42,7 @@ const messages = defineMessages('components.Settings', {
|
||||
toastPlexRefreshSuccess: 'Plex server list retrieved successfully!',
|
||||
toastPlexRefreshFailure: 'Failed to retrieve Plex server list.',
|
||||
toastPlexSyncFailure: 'Failed to sync Plex libraries.',
|
||||
invalidurlerror: 'Unable to connect to {mediaServerName} server.',
|
||||
toggleLibraryFailure: 'Failed to update library.',
|
||||
toastPlexConnecting: 'Attempting to connect to Plex…',
|
||||
toastPlexConnectingSuccess: 'Plex connection established successfully!',
|
||||
@@ -110,10 +111,10 @@ interface PresetServerDisplay {
|
||||
message?: string;
|
||||
}
|
||||
interface SettingsPlexProps {
|
||||
onComplete?: () => void;
|
||||
isSetupSettings?: boolean;
|
||||
}
|
||||
|
||||
const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
|
||||
const SettingsPlex = ({ isSetupSettings }: SettingsPlexProps) => {
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
const [isRefreshingPresets, setIsRefreshingPresets] = useState(false);
|
||||
const [availableServers, setAvailableServers] = useState<PlexDevice[] | null>(
|
||||
@@ -124,12 +125,17 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
|
||||
error,
|
||||
mutate: revalidate,
|
||||
} = useSWR<PlexSettings>('/api/v1/settings/plex');
|
||||
const { data: dataTautulli, mutate: revalidateTautulli } =
|
||||
useSWR<TautulliSettings>('/api/v1/settings/tautulli');
|
||||
const {
|
||||
data: dataTautulli,
|
||||
error: errorTautulli,
|
||||
mutate: revalidateTautulli,
|
||||
} = useSWR<TautulliSettings>(
|
||||
isSetupSettings ? null : '/api/v1/settings/tautulli'
|
||||
);
|
||||
const { data: dataSync, mutate: revalidateSync } = useSWR<SyncStatus>(
|
||||
'/api/v1/settings/plex/sync',
|
||||
{
|
||||
refreshInterval: 1000,
|
||||
refreshInterval: (latestData) => (latestData?.running ? 1000 : 10000),
|
||||
}
|
||||
);
|
||||
const intl = useIntl();
|
||||
@@ -244,11 +250,18 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
|
||||
|
||||
try {
|
||||
await axios.post('/api/v1/settings/plex/library/sync');
|
||||
} catch {
|
||||
addToast(intl.formatMessage(messages.toastPlexSyncFailure), {
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
});
|
||||
} catch (e) {
|
||||
addToast(
|
||||
e?.response?.data?.message === 'CONNECTION_ERROR'
|
||||
? intl.formatMessage(messages.invalidurlerror, {
|
||||
mediaServerName: 'Plex',
|
||||
})
|
||||
: intl.formatMessage(messages.toastPlexSyncFailure),
|
||||
{
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
}
|
||||
);
|
||||
} finally {
|
||||
setIsSyncing(false);
|
||||
revalidate();
|
||||
@@ -315,10 +328,6 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
|
||||
await axios.put(`/api/v1/settings/plex/library/${libraryId}`, {
|
||||
enabled: !activeLibraries.includes(libraryId),
|
||||
});
|
||||
|
||||
if (onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
} catch {
|
||||
addToast(intl.formatMessage(messages.toggleLibraryFailure), {
|
||||
autoDismiss: true,
|
||||
@@ -330,7 +339,11 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
if ((!data || !dataTautulli) && !error) {
|
||||
if (
|
||||
(!data || (!isSetupSettings && !dataTautulli)) &&
|
||||
!error &&
|
||||
!errorTautulli
|
||||
) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
return (
|
||||
@@ -346,7 +359,7 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
|
||||
<p className="description">
|
||||
{intl.formatMessage(messages.plexsettingsDescription)}
|
||||
</p>
|
||||
{!!onComplete && (
|
||||
{isSetupSettings && (
|
||||
<div className="section">
|
||||
<Alert
|
||||
title={intl.formatMessage(messages.settingUpPlexDescription, {
|
||||
@@ -730,7 +743,7 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{!onComplete && (
|
||||
{!isSetupSettings && (
|
||||
<>
|
||||
<div className="mb-6 mt-10">
|
||||
<h3 className="heading">
|
||||
|
||||
@@ -133,6 +133,7 @@ function JellyfinSetup({
|
||||
let errorMessage = messages.loginerror;
|
||||
switch (e?.response?.data?.message) {
|
||||
case ApiErrorCode.InvalidUrl:
|
||||
case ApiErrorCode.ConnectionError:
|
||||
errorMessage = messages.invalidurlerror;
|
||||
break;
|
||||
case ApiErrorCode.InvalidCredentials:
|
||||
|
||||
@@ -2,6 +2,7 @@ import EmbyLogo from '@app/assets/services/emby.svg';
|
||||
import JellyfinLogo from '@app/assets/services/jellyfin.svg';
|
||||
import PlexLogo from '@app/assets/services/plex.svg';
|
||||
import AppDataWarning from '@app/components/AppDataWarning';
|
||||
import Alert from '@app/components/Common/Alert';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import ImageFader from '@app/components/Common/ImageFader';
|
||||
import PageTitle from '@app/components/Common/PageTitle';
|
||||
@@ -12,14 +13,13 @@ import SettingsServices from '@app/components/Settings/SettingsServices';
|
||||
import SetupSteps from '@app/components/Setup/SetupSteps';
|
||||
import useLocale from '@app/hooks/useLocale';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import useToasts from '@app/hooks/useToasts';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import type { Library } from '@server/lib/settings';
|
||||
import axios from 'axios';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
import SetupLogin from './SetupLogin';
|
||||
@@ -39,22 +39,19 @@ const messages = defineMessages('components.Setup', {
|
||||
configuremediaserver: 'Configure Media Server',
|
||||
configureservices: 'Configure Services',
|
||||
librarieserror:
|
||||
'Validation failed. Please toggle the libraries again to continue.',
|
||||
'Unable to load libraries from your media server. Check that it is reachable and still configured correctly.',
|
||||
});
|
||||
|
||||
const Setup = () => {
|
||||
const intl = useIntl();
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [mediaServerSettingsComplete, setMediaServerSettingsComplete] =
|
||||
useState(false);
|
||||
const [mediaServerType, setMediaServerType] = useState(
|
||||
MediaServerType.NOT_CONFIGURED
|
||||
);
|
||||
const router = useRouter();
|
||||
const { locale } = useLocale();
|
||||
const settings = useSettings();
|
||||
const toasts = useToasts();
|
||||
|
||||
const finishSetup = async () => {
|
||||
setIsUpdating(true);
|
||||
@@ -71,34 +68,21 @@ const Setup = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const validateLibraries = useCallback(async () => {
|
||||
try {
|
||||
const endpointMap: Record<MediaServerType, string> = {
|
||||
[MediaServerType.JELLYFIN]: '/api/v1/settings/jellyfin',
|
||||
[MediaServerType.EMBY]: '/api/v1/settings/jellyfin',
|
||||
[MediaServerType.PLEX]: '/api/v1/settings/plex',
|
||||
[MediaServerType.NOT_CONFIGURED]: '',
|
||||
};
|
||||
const mediaServerSettingsEndpoint: Record<MediaServerType, string | null> = {
|
||||
[MediaServerType.JELLYFIN]: '/api/v1/settings/jellyfin',
|
||||
[MediaServerType.EMBY]: '/api/v1/settings/jellyfin',
|
||||
[MediaServerType.PLEX]: '/api/v1/settings/plex',
|
||||
[MediaServerType.NOT_CONFIGURED]: null,
|
||||
};
|
||||
|
||||
const endpoint = endpointMap[mediaServerType];
|
||||
if (!endpoint) return;
|
||||
const { data: mediaServerSettings, error: mediaServerSettingsError } =
|
||||
useSWR<{ libraries: Library[] }>(
|
||||
currentStep === 3 ? mediaServerSettingsEndpoint[mediaServerType] : null
|
||||
);
|
||||
|
||||
const response = await axios.get(endpoint);
|
||||
|
||||
const hasEnabledLibraries = response.data?.libraries?.some(
|
||||
(library: Library) => library.enabled
|
||||
);
|
||||
|
||||
setMediaServerSettingsComplete(hasEnabledLibraries);
|
||||
} catch {
|
||||
toasts.addToast(intl.formatMessage(messages.librarieserror), {
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
});
|
||||
|
||||
setMediaServerSettingsComplete(false);
|
||||
}
|
||||
}, [intl, mediaServerType, toasts]);
|
||||
const mediaServerSettingsComplete = !!mediaServerSettings?.libraries?.some(
|
||||
(library) => library.enabled
|
||||
);
|
||||
|
||||
const { data: backdrops } = useSWR<string[]>('/api/v1/backdrops', {
|
||||
refreshInterval: 0,
|
||||
@@ -124,24 +108,11 @@ const Setup = () => {
|
||||
settings.currentSettings.mediaServerType,
|
||||
settings.currentSettings.initialized,
|
||||
router,
|
||||
toasts,
|
||||
intl,
|
||||
currentStep,
|
||||
mediaServerType,
|
||||
validateLibraries,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentStep === 3) {
|
||||
validateLibraries();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentStep]);
|
||||
|
||||
const handleComplete = () => {
|
||||
validateLibraries();
|
||||
};
|
||||
|
||||
if (settings.currentSettings.initialized) return <></>;
|
||||
|
||||
return (
|
||||
@@ -266,10 +237,16 @@ const Setup = () => {
|
||||
)}
|
||||
{currentStep === 3 && (
|
||||
<div className="p-2">
|
||||
{!!mediaServerSettingsError && (
|
||||
<Alert
|
||||
title={intl.formatMessage(messages.librarieserror)}
|
||||
type="error"
|
||||
/>
|
||||
)}
|
||||
{mediaServerType === MediaServerType.PLEX ? (
|
||||
<SettingsPlex onComplete={handleComplete} />
|
||||
<SettingsPlex isSetupSettings />
|
||||
) : (
|
||||
<SettingsJellyfin isSetupSettings onComplete={handleComplete} />
|
||||
<SettingsJellyfin isSetupSettings />
|
||||
)}
|
||||
<div className="actions">
|
||||
<div className="flex justify-end">
|
||||
|
||||
@@ -1215,6 +1215,7 @@
|
||||
"components.Settings.jellyfinSettingsFailure": "Something went wrong while saving {mediaServerName} settings.",
|
||||
"components.Settings.jellyfinSettingsSuccess": "{mediaServerName} settings saved successfully!",
|
||||
"components.Settings.jellyfinSyncFailedAutomaticGroupedFolders": "Custom authentication with Automatic Library Grouping not supported",
|
||||
"components.Settings.jellyfinSyncFailedConnectionError": "Unable to reach the {mediaServerName} server. Check that it is running and reachable from Seerr.",
|
||||
"components.Settings.jellyfinSyncFailedGenericError": "Something went wrong while syncing libraries",
|
||||
"components.Settings.jellyfinSyncFailedNoLibrariesFound": "No libraries were found",
|
||||
"components.Settings.jellyfinlibraries": "{mediaServerName} Libraries",
|
||||
@@ -1330,7 +1331,7 @@
|
||||
"components.Setup.continue": "Continue",
|
||||
"components.Setup.finish": "Finish Setup",
|
||||
"components.Setup.finishing": "Finishing…",
|
||||
"components.Setup.librarieserror": "Validation failed. Please toggle the libraries again to continue.",
|
||||
"components.Setup.librarieserror": "Unable to load libraries from your media server. Check that it is reachable and still configured correctly.",
|
||||
"components.Setup.servertype": "Choose Server Type",
|
||||
"components.Setup.setup": "Setup",
|
||||
"components.Setup.signin": "Sign In",
|
||||
|
||||
Reference in New Issue
Block a user