mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
@@ -62,6 +62,7 @@ class PlexAPI {
|
|||||||
this.plexClient = new NodePlexAPI({
|
this.plexClient = new NodePlexAPI({
|
||||||
hostname: settings.plex.ip,
|
hostname: settings.plex.ip,
|
||||||
port: settings.plex.port,
|
port: settings.plex.port,
|
||||||
|
https: settings.plex.useSsl,
|
||||||
token: plexToken,
|
token: plexToken,
|
||||||
authenticator: {
|
authenticator: {
|
||||||
authenticate: (
|
authenticate: (
|
||||||
|
@@ -14,6 +14,7 @@ export interface PlexSettings {
|
|||||||
machineId?: string;
|
machineId?: string;
|
||||||
ip: string;
|
ip: string;
|
||||||
port: number;
|
port: number;
|
||||||
|
useSsl?: boolean;
|
||||||
libraries: Library[];
|
libraries: Library[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +110,7 @@ class Settings {
|
|||||||
name: '',
|
name: '',
|
||||||
ip: '127.0.0.1',
|
ip: '127.0.0.1',
|
||||||
port: 32400,
|
port: 32400,
|
||||||
|
useSsl: false,
|
||||||
libraries: [],
|
libraries: [],
|
||||||
},
|
},
|
||||||
radarr: [],
|
radarr: [],
|
||||||
|
1
server/types/plex-api.d.ts
vendored
1
server/types/plex-api.d.ts
vendored
@@ -4,6 +4,7 @@ declare module 'plex-api' {
|
|||||||
hostname: string;
|
hostname: string;
|
||||||
port: number;
|
port: number;
|
||||||
token?: string;
|
token?: string;
|
||||||
|
https?: boolean;
|
||||||
authenticator: {
|
authenticator: {
|
||||||
authenticate: (
|
authenticate: (
|
||||||
_plexApi: PlexAPI,
|
_plexApi: PlexAPI,
|
||||||
|
@@ -2,21 +2,23 @@ import React, { useState } from 'react';
|
|||||||
import LoadingSpinner from '../Common/LoadingSpinner';
|
import LoadingSpinner from '../Common/LoadingSpinner';
|
||||||
import type { PlexSettings } from '../../../server/lib/settings';
|
import type { PlexSettings } from '../../../server/lib/settings';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { useFormik } from 'formik';
|
import { Formik, Field } from 'formik';
|
||||||
import Button from '../Common/Button';
|
import Button from '../Common/Button';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import LibraryItem from './LibraryItem';
|
import LibraryItem from './LibraryItem';
|
||||||
import Badge from '../Common/Badge';
|
import Badge from '../Common/Badge';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
plexsettings: 'Plex Settings',
|
plexsettings: 'Plex Settings',
|
||||||
plexsettingsDescription:
|
plexsettingsDescription:
|
||||||
'Configure the settings for your Plex server. Overseerr uses your Plex server to scan your library at an interval and see what content is available.',
|
'Configure the settings for your Plex server. Overseerr uses your Plex server to scan your library at an interval and see what content is available.',
|
||||||
servername: 'Server Name (Automatically Set)',
|
servername: 'Server Name (Automatically set after you save)',
|
||||||
servernamePlaceholder: 'Plex Server Name',
|
servernamePlaceholder: 'Plex Server Name',
|
||||||
hostname: 'Hostname/IP',
|
hostname: 'Hostname/IP',
|
||||||
port: 'Port',
|
port: 'Port',
|
||||||
|
ssl: 'SSL',
|
||||||
save: 'Save Changes',
|
save: 'Save Changes',
|
||||||
saving: 'Saving...',
|
saving: 'Saving...',
|
||||||
plexlibraries: 'Plex Libraries',
|
plexlibraries: 'Plex Libraries',
|
||||||
@@ -32,6 +34,8 @@ const messages = defineMessages({
|
|||||||
librariesRemaining: 'Libraries Remaining: {count}',
|
librariesRemaining: 'Libraries Remaining: {count}',
|
||||||
startscan: 'Start Scan',
|
startscan: 'Start Scan',
|
||||||
cancelscan: 'Cancel Scan',
|
cancelscan: 'Cancel Scan',
|
||||||
|
validationHostnameRequired: 'You must provide a hostname/IP',
|
||||||
|
validationPortRequired: 'You must provide a port',
|
||||||
});
|
});
|
||||||
|
|
||||||
interface Library {
|
interface Library {
|
||||||
@@ -64,33 +68,15 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
const [isSyncing, setIsSyncing] = useState(false);
|
const [isSyncing, setIsSyncing] = useState(false);
|
||||||
const [isUpdating, setIsUpdating] = useState(false);
|
|
||||||
const [submitError, setSubmitError] = useState<string | null>(null);
|
const [submitError, setSubmitError] = useState<string | null>(null);
|
||||||
const formik = useFormik({
|
|
||||||
initialValues: {
|
|
||||||
hostname: data?.ip,
|
|
||||||
port: data?.port,
|
|
||||||
},
|
|
||||||
enableReinitialize: true,
|
|
||||||
onSubmit: async (values) => {
|
|
||||||
setSubmitError(null);
|
|
||||||
setIsUpdating(true);
|
|
||||||
try {
|
|
||||||
await axios.post('/api/v1/settings/plex', {
|
|
||||||
ip: values.hostname,
|
|
||||||
port: Number(values.port),
|
|
||||||
} as PlexSettings);
|
|
||||||
|
|
||||||
revalidate();
|
const PlexSettingsSchema = Yup.object().shape({
|
||||||
if (onComplete) {
|
hostname: Yup.string().required(
|
||||||
onComplete();
|
intl.formatMessage(messages.validationHostnameRequired)
|
||||||
}
|
),
|
||||||
} catch (e) {
|
port: Yup.number().required(
|
||||||
setSubmitError(e.response.data.message);
|
intl.formatMessage(messages.validationPortRequired)
|
||||||
} finally {
|
),
|
||||||
setIsUpdating(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const activeLibraries =
|
const activeLibraries =
|
||||||
@@ -164,91 +150,154 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
|
|||||||
<FormattedMessage {...messages.plexsettingsDescription} />
|
<FormattedMessage {...messages.plexsettingsDescription} />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={formik.handleSubmit}>
|
<Formik
|
||||||
<div className="mt-6 sm:mt-5">
|
initialValues={{
|
||||||
{submitError && (
|
hostname: data?.ip,
|
||||||
<div className="bg-red-700 text-white p-4 rounded-md mb-6">
|
port: data?.port,
|
||||||
{submitError}
|
useSsl: data?.useSsl,
|
||||||
</div>
|
}}
|
||||||
)}
|
enableReinitialize
|
||||||
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800 sm:pt-5">
|
validationSchema={PlexSettingsSchema}
|
||||||
<label
|
onSubmit={async (values) => {
|
||||||
htmlFor="name"
|
setSubmitError(null);
|
||||||
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px sm:pt-2"
|
try {
|
||||||
>
|
await axios.post('/api/v1/settings/plex', {
|
||||||
<FormattedMessage {...messages.servername} />
|
ip: values.hostname,
|
||||||
</label>
|
port: Number(values.port),
|
||||||
<div className="mt-1 sm:mt-0 sm:col-span-2">
|
useSsl: values.useSsl,
|
||||||
<div className="max-w-lg flex rounded-md shadow-sm">
|
} as PlexSettings);
|
||||||
<input
|
|
||||||
type="text"
|
revalidate();
|
||||||
id="name"
|
if (onComplete) {
|
||||||
name="name"
|
onComplete();
|
||||||
placeholder={intl.formatMessage(
|
}
|
||||||
messages.servernamePlaceholder
|
} catch (e) {
|
||||||
)}
|
setSubmitError(e.response.data.message);
|
||||||
value={data?.name}
|
}
|
||||||
readOnly
|
}}
|
||||||
className="flex-1 form-input block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500"
|
>
|
||||||
/>
|
{({
|
||||||
|
errors,
|
||||||
|
touched,
|
||||||
|
values,
|
||||||
|
handleSubmit,
|
||||||
|
setFieldValue,
|
||||||
|
isSubmitting,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="mt-6 sm:mt-5">
|
||||||
|
{submitError && (
|
||||||
|
<div className="bg-red-700 text-white p-4 rounded-md mb-6">
|
||||||
|
{submitError}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800 sm:pt-5">
|
||||||
|
<label
|
||||||
|
htmlFor="name"
|
||||||
|
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px sm:pt-2"
|
||||||
|
>
|
||||||
|
<FormattedMessage {...messages.servername} />
|
||||||
|
</label>
|
||||||
|
<div className="mt-1 sm:mt-0 sm:col-span-2">
|
||||||
|
<div className="max-w-lg flex rounded-md shadow-sm">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
placeholder={intl.formatMessage(
|
||||||
|
messages.servernamePlaceholder
|
||||||
|
)}
|
||||||
|
value={data?.name}
|
||||||
|
readOnly
|
||||||
|
className="flex-1 form-input block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800 sm:pt-5">
|
||||||
|
<label
|
||||||
|
htmlFor="hostname"
|
||||||
|
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px sm:pt-2"
|
||||||
|
>
|
||||||
|
<FormattedMessage {...messages.hostname} />
|
||||||
|
</label>
|
||||||
|
<div className="mt-1 sm:mt-0 sm:col-span-2">
|
||||||
|
<div className="max-w-lg flex rounded-md shadow-sm">
|
||||||
|
<Field
|
||||||
|
type="text"
|
||||||
|
id="hostname"
|
||||||
|
name="hostname"
|
||||||
|
placeholder="127.0.0.1"
|
||||||
|
className="flex-1 form-input block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{errors.hostname && touched.hostname && (
|
||||||
|
<div className="text-red-500 mt-2">{errors.hostname}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
|
||||||
|
<label
|
||||||
|
htmlFor="port"
|
||||||
|
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px sm:pt-2"
|
||||||
|
>
|
||||||
|
<FormattedMessage {...messages.port} />
|
||||||
|
</label>
|
||||||
|
<div className="mt-1 sm:mt-0 sm:col-span-2">
|
||||||
|
<div className="max-w-lg rounded-md shadow-sm sm:max-w-xs">
|
||||||
|
<Field
|
||||||
|
type="text"
|
||||||
|
id="port"
|
||||||
|
name="port"
|
||||||
|
placeholder="32400"
|
||||||
|
className="form-input block w-24 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{errors.port && touched.port && (
|
||||||
|
<div className="text-red-500 mt-2">{errors.port}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
|
||||||
</div>
|
<label
|
||||||
<div className="mt-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800 sm:pt-5">
|
htmlFor="ssl"
|
||||||
<label
|
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px sm:pt-2"
|
||||||
htmlFor="hostname"
|
>
|
||||||
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px sm:pt-2"
|
{intl.formatMessage(messages.ssl)}
|
||||||
>
|
</label>
|
||||||
<FormattedMessage {...messages.hostname} />
|
<div className="mt-1 sm:mt-0 sm:col-span-2">
|
||||||
</label>
|
<Field
|
||||||
<div className="mt-1 sm:mt-0 sm:col-span-2">
|
type="checkbox"
|
||||||
<div className="max-w-lg flex rounded-md shadow-sm">
|
id="useSsl"
|
||||||
<input
|
name="useSsl"
|
||||||
type="text"
|
onChange={() => {
|
||||||
id="hostname"
|
setFieldValue('useSsl', !values.useSsl);
|
||||||
name="hostname"
|
}}
|
||||||
placeholder="127.0.0.1"
|
className="form-checkbox h-6 w-6 rounded-md text-indigo-600 transition duration-150 ease-in-out"
|
||||||
value={formik.values.hostname}
|
/>
|
||||||
onChange={formik.handleChange}
|
</div>
|
||||||
className="flex-1 form-input block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="mt-8 border-t border-gray-700 pt-5">
|
||||||
</div>
|
<div className="flex justify-end">
|
||||||
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
|
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
||||||
<label
|
<Button
|
||||||
htmlFor="port"
|
buttonType="primary"
|
||||||
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px sm:pt-2"
|
type="submit"
|
||||||
>
|
disabled={isSubmitting}
|
||||||
<FormattedMessage {...messages.port} />
|
>
|
||||||
</label>
|
{isSubmitting
|
||||||
<div className="mt-1 sm:mt-0 sm:col-span-2">
|
? intl.formatMessage(messages.saving)
|
||||||
<div className="max-w-lg rounded-md shadow-sm sm:max-w-xs">
|
: intl.formatMessage(messages.save)}
|
||||||
<input
|
</Button>
|
||||||
type="text"
|
</span>
|
||||||
id="port"
|
</div>
|
||||||
name="port"
|
|
||||||
placeholder="32400"
|
|
||||||
value={formik.values.port}
|
|
||||||
onChange={formik.handleChange}
|
|
||||||
className="form-input block w-24 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</div>
|
);
|
||||||
</div>
|
}}
|
||||||
<div className="mt-8 border-t border-gray-700 pt-5">
|
</Formik>
|
||||||
<div className="flex justify-end">
|
|
||||||
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
|
||||||
<Button buttonType="primary" type="submit" disabled={isUpdating}>
|
|
||||||
{isUpdating
|
|
||||||
? intl.formatMessage(messages.saving)
|
|
||||||
: intl.formatMessage(messages.save)}
|
|
||||||
</Button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div className="mt-10">
|
<div className="mt-10">
|
||||||
<h3 className="text-lg leading-6 font-medium text-gray-200">
|
<h3 className="text-lg leading-6 font-medium text-gray-200">
|
||||||
<FormattedMessage {...messages.plexlibraries} />
|
<FormattedMessage {...messages.plexlibraries} />
|
||||||
|
@@ -210,7 +210,7 @@
|
|||||||
"components.Settings.runnow": "Run Now",
|
"components.Settings.runnow": "Run Now",
|
||||||
"components.Settings.save": "Save Changes",
|
"components.Settings.save": "Save Changes",
|
||||||
"components.Settings.saving": "Saving...",
|
"components.Settings.saving": "Saving...",
|
||||||
"components.Settings.servername": "Server Name (Automatically Set)",
|
"components.Settings.servername": "Server Name (Automatically set after you save)",
|
||||||
"components.Settings.servernamePlaceholder": "Plex Server Name",
|
"components.Settings.servernamePlaceholder": "Plex Server Name",
|
||||||
"components.Settings.sonarrSettingsDescription": "Set up your Sonarr connection below. You can have multiple, but only two active as defaults at any time (one for standard HD and one for 4K). Administrators can override which server is used for new requests.",
|
"components.Settings.sonarrSettingsDescription": "Set up your Sonarr connection below. You can have multiple, but only two active as defaults at any time (one for standard HD and one for 4K). Administrators can override which server is used for new requests.",
|
||||||
"components.Settings.sonarrsettings": "Sonarr Settings",
|
"components.Settings.sonarrsettings": "Sonarr Settings",
|
||||||
@@ -218,6 +218,8 @@
|
|||||||
"components.Settings.startscan": "Start Scan",
|
"components.Settings.startscan": "Start Scan",
|
||||||
"components.Settings.sync": "Sync Plex Libraries",
|
"components.Settings.sync": "Sync Plex Libraries",
|
||||||
"components.Settings.syncing": "Syncing…",
|
"components.Settings.syncing": "Syncing…",
|
||||||
|
"components.Settings.validationHostnameRequired": "You must provide a hostname/IP",
|
||||||
|
"components.Settings.validationPortRequired": "You must provide a port",
|
||||||
"components.Setup.configureplex": "Configure Plex",
|
"components.Setup.configureplex": "Configure Plex",
|
||||||
"components.Setup.configureservices": "Configure Services",
|
"components.Setup.configureservices": "Configure Services",
|
||||||
"components.Setup.continue": "Continue",
|
"components.Setup.continue": "Continue",
|
||||||
|
Reference in New Issue
Block a user