@ -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 { use Formik } 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 } / >