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({
|
||||
hostname: settings.plex.ip,
|
||||
port: settings.plex.port,
|
||||
https: settings.plex.useSsl,
|
||||
token: plexToken,
|
||||
authenticator: {
|
||||
authenticate: (
|
||||
|
@@ -14,6 +14,7 @@ export interface PlexSettings {
|
||||
machineId?: string;
|
||||
ip: string;
|
||||
port: number;
|
||||
useSsl?: boolean;
|
||||
libraries: Library[];
|
||||
}
|
||||
|
||||
@@ -109,6 +110,7 @@ class Settings {
|
||||
name: '',
|
||||
ip: '127.0.0.1',
|
||||
port: 32400,
|
||||
useSsl: false,
|
||||
libraries: [],
|
||||
},
|
||||
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;
|
||||
port: number;
|
||||
token?: string;
|
||||
https?: boolean;
|
||||
authenticator: {
|
||||
authenticate: (
|
||||
_plexApi: PlexAPI,
|
||||
|
@@ -2,21 +2,23 @@ import React, { useState } from 'react';
|
||||
import LoadingSpinner from '../Common/LoadingSpinner';
|
||||
import type { PlexSettings } from '../../../server/lib/settings';
|
||||
import useSWR from 'swr';
|
||||
import { useFormik } from 'formik';
|
||||
import { Formik, Field } from 'formik';
|
||||
import Button from '../Common/Button';
|
||||
import axios from 'axios';
|
||||
import LibraryItem from './LibraryItem';
|
||||
import Badge from '../Common/Badge';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const messages = defineMessages({
|
||||
plexsettings: 'Plex Settings',
|
||||
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.',
|
||||
servername: 'Server Name (Automatically Set)',
|
||||
servername: 'Server Name (Automatically set after you save)',
|
||||
servernamePlaceholder: 'Plex Server Name',
|
||||
hostname: 'Hostname/IP',
|
||||
port: 'Port',
|
||||
ssl: 'SSL',
|
||||
save: 'Save Changes',
|
||||
saving: 'Saving...',
|
||||
plexlibraries: 'Plex Libraries',
|
||||
@@ -32,6 +34,8 @@ const messages = defineMessages({
|
||||
librariesRemaining: 'Libraries Remaining: {count}',
|
||||
startscan: 'Start Scan',
|
||||
cancelscan: 'Cancel Scan',
|
||||
validationHostnameRequired: 'You must provide a hostname/IP',
|
||||
validationPortRequired: 'You must provide a port',
|
||||
});
|
||||
|
||||
interface Library {
|
||||
@@ -64,33 +68,15 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
|
||||
}
|
||||
);
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
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();
|
||||
if (onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
} catch (e) {
|
||||
setSubmitError(e.response.data.message);
|
||||
} finally {
|
||||
setIsUpdating(false);
|
||||
}
|
||||
},
|
||||
const PlexSettingsSchema = Yup.object().shape({
|
||||
hostname: Yup.string().required(
|
||||
intl.formatMessage(messages.validationHostnameRequired)
|
||||
),
|
||||
port: Yup.number().required(
|
||||
intl.formatMessage(messages.validationPortRequired)
|
||||
),
|
||||
});
|
||||
|
||||
const activeLibraries =
|
||||
@@ -164,91 +150,154 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
|
||||
<FormattedMessage {...messages.plexsettingsDescription} />
|
||||
</p>
|
||||
</div>
|
||||
<form onSubmit={formik.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"
|
||||
/>
|
||||
<Formik
|
||||
initialValues={{
|
||||
hostname: data?.ip,
|
||||
port: data?.port,
|
||||
useSsl: data?.useSsl,
|
||||
}}
|
||||
enableReinitialize
|
||||
validationSchema={PlexSettingsSchema}
|
||||
onSubmit={async (values) => {
|
||||
setSubmitError(null);
|
||||
try {
|
||||
await axios.post('/api/v1/settings/plex', {
|
||||
ip: values.hostname,
|
||||
port: Number(values.port),
|
||||
useSsl: values.useSsl,
|
||||
} as PlexSettings);
|
||||
|
||||
revalidate();
|
||||
if (onComplete) {
|
||||
onComplete();
|
||||
}
|
||||
} catch (e) {
|
||||
setSubmitError(e.response.data.message);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{({
|
||||
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: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">
|
||||
<input
|
||||
type="text"
|
||||
id="hostname"
|
||||
name="hostname"
|
||||
placeholder="127.0.0.1"
|
||||
value={formik.values.hostname}
|
||||
onChange={formik.handleChange}
|
||||
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 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="ssl"
|
||||
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px sm:pt-2"
|
||||
>
|
||||
{intl.formatMessage(messages.ssl)}
|
||||
</label>
|
||||
<div className="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<Field
|
||||
type="checkbox"
|
||||
id="useSsl"
|
||||
name="useSsl"
|
||||
onChange={() => {
|
||||
setFieldValue('useSsl', !values.useSsl);
|
||||
}}
|
||||
className="form-checkbox h-6 w-6 rounded-md text-indigo-600 transition duration-150 ease-in-out"
|
||||
/>
|
||||
</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">
|
||||
<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">
|
||||
<input
|
||||
type="text"
|
||||
id="port"
|
||||
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 className="mt-8 border-t border-gray-700 pt-5">
|
||||
<div className="flex justify-end">
|
||||
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
||||
<Button
|
||||
buttonType="primary"
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting
|
||||
? intl.formatMessage(messages.saving)
|
||||
: intl.formatMessage(messages.save)}
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 border-t border-gray-700 pt-5">
|
||||
<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>
|
||||
</form>
|
||||
);
|
||||
}}
|
||||
</Formik>
|
||||
<div className="mt-10">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-200">
|
||||
<FormattedMessage {...messages.plexlibraries} />
|
||||
|
@@ -210,7 +210,7 @@
|
||||
"components.Settings.runnow": "Run Now",
|
||||
"components.Settings.save": "Save Changes",
|
||||
"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.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",
|
||||
@@ -218,6 +218,8 @@
|
||||
"components.Settings.startscan": "Start Scan",
|
||||
"components.Settings.sync": "Sync Plex Libraries",
|
||||
"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.configureservices": "Configure Services",
|
||||
"components.Setup.continue": "Continue",
|
||||
|
Reference in New Issue
Block a user