feat(plex): add support for custom Plex Web App URLs (#1581)

* feat(plex): add support for custom Plex Web App URLs

* refactor: clean up Yup validation in *arr modals & email settings

* fix(lang): change Web App URL tip

* fix: remove web app URL validation and add 'Advanced' badge
pull/1584/head
TheCatLady 3 years ago committed by GitHub
parent 93c441ef66
commit a640a91390
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -171,6 +171,9 @@ components:
readOnly: true
items:
$ref: '#/components/schemas/PlexLibrary'
webAppUrl:
type: string
example: 'https://app.plex.tv/desktop'
required:
- name
- machineId

@ -147,12 +147,22 @@ class Media {
@AfterLoad()
public setPlexUrls(): void {
const machineId = getSettings().plex.machineId;
const { machineId, webAppUrl } = getSettings().plex;
if (this.ratingKey) {
this.plexUrl = `https://app.plex.tv/desktop#!/server/${machineId}/details?key=%2Flibrary%2Fmetadata%2F${this.ratingKey}`;
this.plexUrl = `${
webAppUrl ? webAppUrl : 'https://app.plex.tv/desktop'
}#!/server/${machineId}/details?key=%2Flibrary%2Fmetadata%2F${
this.ratingKey
}`;
}
if (this.ratingKey4k) {
this.plexUrl4k = `https://app.plex.tv/desktop#!/server/${machineId}/details?key=%2Flibrary%2Fmetadata%2F${this.ratingKey4k}`;
this.plexUrl4k = `${
webAppUrl ? webAppUrl : 'https://app.plex.tv/desktop'
}#!/server/${machineId}/details?key=%2Flibrary%2Fmetadata%2F${
this.ratingKey4k
}`;
}
}

@ -30,6 +30,7 @@ export interface PlexSettings {
port: number;
useSsl?: boolean;
libraries: Library[];
webAppUrl?: string;
}
export interface DVRSettings {

@ -92,15 +92,13 @@ const NotificationsEmail: React.FC = () => {
/^(([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])$/i,
intl.formatMessage(messages.validationSmtpHostRequired)
),
smtpPort: Yup.number()
.typeError(intl.formatMessage(messages.validationSmtpPortRequired))
.when('enabled', {
is: true,
then: Yup.number().required(
intl.formatMessage(messages.validationSmtpPortRequired)
),
otherwise: Yup.number().nullable(),
}),
smtpPort: Yup.number().when('enabled', {
is: true,
then: Yup.number()
.nullable()
.required(intl.formatMessage(messages.validationSmtpPortRequired)),
otherwise: Yup.number().nullable(),
}),
pgpPrivateKey: Yup.string()
.when('pgpPassword', {
is: (value: unknown) => !!value,

@ -41,7 +41,7 @@ const messages = defineMessages({
servername: 'Server Name',
hostname: 'Hostname or IP Address',
port: 'Port',
ssl: 'Enable SSL',
ssl: 'Use SSL',
apiKey: 'API Key',
baseUrl: 'URL Base',
syncEnabled: 'Enable Scan',
@ -116,7 +116,7 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
intl.formatMessage(messages.validationHostnameRequired)
),
port: Yup.number()
.typeError(intl.formatMessage(messages.validationPortRequired))
.nullable()
.required(intl.formatMessage(messages.validationPortRequired)),
apiKey: Yup.string().required(
intl.formatMessage(messages.validationApiKeyRequired)
@ -135,33 +135,18 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
.test(
'no-trailing-slash',
intl.formatMessage(messages.validationApplicationUrlTrailingSlash),
(value) => {
if (value?.substr(value.length - 1) === '/') {
return false;
}
return true;
}
(value) => !value || !value.endsWith('/')
),
baseUrl: Yup.string()
.test(
'leading-slash',
intl.formatMessage(messages.validationBaseUrlLeadingSlash),
(value) => {
if (value && value?.substr(0, 1) !== '/') {
return false;
}
return true;
}
(value) => !value || value.startsWith('/')
)
.test(
'no-trailing-slash',
intl.formatMessage(messages.validationBaseUrlTrailingSlash),
(value) => {
if (value?.substr(value.length - 1) === '/') {
return false;
}
return true;
}
(value) => !value || !value.endsWith('/')
),
});

@ -22,8 +22,6 @@ const messages = defineMessages({
plexsettings: 'Plex Settings',
plexsettingsDescription:
'Configure the settings for your Plex server. Overseerr scans your Plex libraries to determine content availability.',
servername: 'Server Name',
servernameTip: 'Automatically retrieved from Plex after saving',
serverpreset: 'Server',
serverLocal: 'local',
serverRemote: 'remote',
@ -41,7 +39,7 @@ const messages = defineMessages({
'To set up Plex, you can either enter the details manually or select a server retrieved from <RegisterPlexTVLink>plex.tv</RegisterPlexTVLink>. Press the button to the right of the dropdown to fetch the list of available servers.',
hostname: 'Hostname or IP Address',
port: 'Port',
enablessl: 'Enable SSL',
enablessl: 'Use SSL',
plexlibraries: 'Plex Libraries',
plexlibrariesDescription:
'The libraries Overseerr scans for titles. Set up and save your Plex connection settings, then click the button below if no libraries are listed.',
@ -57,6 +55,10 @@ const messages = defineMessages({
cancelscan: 'Cancel Scan',
validationHostnameRequired: 'You must provide a valid hostname or IP address',
validationPortRequired: 'You must provide a valid port number',
webAppUrl: '<WebAppLink>Web App</WebAppLink> URL',
webAppUrlTip:
'Optionally direct users to the web app on your server instead of the "hosted" web app',
validationWebAppUrl: 'You must provide a valid Plex Web App URL',
});
interface Library {
@ -108,14 +110,18 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
const { addToast, removeToast } = useToasts();
const PlexSettingsSchema = Yup.object().shape({
hostname: Yup.string()
.nullable()
.required(intl.formatMessage(messages.validationHostnameRequired))
.matches(
/^(([a-z]|\d|_|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])$/i,
intl.formatMessage(messages.validationHostnameRequired)
),
port: Yup.number()
.typeError(intl.formatMessage(messages.validationPortRequired))
.nullable()
.required(intl.formatMessage(messages.validationPortRequired)),
webAppUrl: Yup.string()
.nullable()
.url(intl.formatMessage(messages.validationWebAppUrl)),
});
const activeLibraries =
@ -282,6 +288,7 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
port: data?.port ?? 32400,
useSsl: data?.useSsl,
selectedPreset: undefined,
webAppUrl: data?.webAppUrl,
}}
validationSchema={PlexSettingsSchema}
onSubmit={async (values) => {
@ -301,6 +308,7 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
ip: values.hostname,
port: Number(values.port),
useSsl: values.useSsl,
webAppUrl: values.webAppUrl,
} as PlexSettings);
revalidate();
@ -336,34 +344,12 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
}) => {
return (
<form className="section" onSubmit={handleSubmit}>
<div className="form-row">
<label htmlFor="name" className="text-label">
<div className="flex flex-col">
<span>{intl.formatMessage(messages.servername)}</span>
<span className="text-gray-500">
{intl.formatMessage(messages.servernameTip)}
</span>
</div>
</label>
<div className="form-input">
<div className="form-input-field">
<input
type="text"
id="name"
name="name"
className="cursor-not-allowed"
value={data?.name}
readOnly
/>
</div>
</div>
</div>
<div className="form-row">
<label htmlFor="preset" className="text-label">
{intl.formatMessage(messages.serverpreset)}
</label>
<div className="form-input">
<div className="form-input-field input-group">
<div className="form-input-field">
<select
id="preset"
name="preset"
@ -489,6 +475,43 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
/>
</div>
</div>
<div className="form-row">
<label htmlFor="webAppUrl" className="text-label">
{intl.formatMessage(messages.webAppUrl, {
WebAppLink: function WebAppLink(msg) {
return (
<a
href="https://support.plex.tv/articles/200288666-opening-plex-web-app/"
target="_blank"
rel="noreferrer"
>
{msg}
</a>
);
},
})}
<Badge badgeType="danger" className="ml-2">
{intl.formatMessage(globalMessages.advanced)}
</Badge>
<span className="label-tip">
{intl.formatMessage(messages.webAppUrlTip)}
</span>
</label>
<div className="form-input">
<div className="form-input-field">
<Field
type="text"
inputMode="url"
id="webAppUrl"
name="webAppUrl"
placeholder="https://app.plex.tv/desktop"
/>
</div>
{errors.webAppUrl && touched.webAppUrl && (
<div className="error">{errors.webAppUrl}</div>
)}
</div>
</div>
<div className="actions">
<div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm">

@ -40,7 +40,7 @@ const messages = defineMessages({
servername: 'Server Name',
hostname: 'Hostname or IP Address',
port: 'Port',
ssl: 'Enable SSL',
ssl: 'Use SSL',
apiKey: 'API Key',
baseUrl: 'URL Base',
qualityprofile: 'Quality Profile',
@ -127,7 +127,7 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
intl.formatMessage(messages.validationHostnameRequired)
),
port: Yup.number()
.typeError(intl.formatMessage(messages.validationPortRequired))
.nullable()
.required(intl.formatMessage(messages.validationPortRequired)),
apiKey: Yup.string().required(
intl.formatMessage(messages.validationApiKeyRequired)
@ -146,33 +146,18 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
.test(
'no-trailing-slash',
intl.formatMessage(messages.validationApplicationUrlTrailingSlash),
(value) => {
if (value?.substr(value.length - 1) === '/') {
return false;
}
return true;
}
(value) => !value || !value.endsWith('/')
),
baseUrl: Yup.string()
.test(
'leading-slash',
intl.formatMessage(messages.validationBaseUrlLeadingSlash),
(value) => {
if (value && value?.substr(0, 1) !== '/') {
return false;
}
return true;
}
(value) => !value || value.startsWith('/')
)
.test(
'no-trailing-slash',
intl.formatMessage(messages.validationBaseUrlTrailingSlash),
(value) => {
if (value?.substr(value.length - 1) === '/') {
return false;
}
return true;
}
(value) => !value || !value.endsWith('/')
),
});

@ -389,7 +389,7 @@
"components.Settings.RadarrModal.selecttags": "Select tags",
"components.Settings.RadarrModal.server4k": "4K Server",
"components.Settings.RadarrModal.servername": "Server Name",
"components.Settings.RadarrModal.ssl": "Enable SSL",
"components.Settings.RadarrModal.ssl": "Use SSL",
"components.Settings.RadarrModal.syncEnabled": "Enable Scan",
"components.Settings.RadarrModal.tags": "Tags",
"components.Settings.RadarrModal.testFirstQualityProfiles": "Test connection to load quality profiles",
@ -519,7 +519,7 @@
"components.Settings.SonarrModal.selecttags": "Select tags",
"components.Settings.SonarrModal.server4k": "4K Server",
"components.Settings.SonarrModal.servername": "Server Name",
"components.Settings.SonarrModal.ssl": "Enable SSL",
"components.Settings.SonarrModal.ssl": "Use SSL",
"components.Settings.SonarrModal.syncEnabled": "Enable Scan",
"components.Settings.SonarrModal.tags": "Tags",
"components.Settings.SonarrModal.testFirstLanguageProfiles": "Test connection to load language profiles",
@ -558,7 +558,7 @@
"components.Settings.default4k": "Default 4K",
"components.Settings.deleteserverconfirm": "Are you sure you want to delete this server?",
"components.Settings.email": "Email",
"components.Settings.enablessl": "Enable SSL",
"components.Settings.enablessl": "Use SSL",
"components.Settings.general": "General",
"components.Settings.generalsettings": "General Settings",
"components.Settings.generalsettingsDescription": "Configure global and default settings for Overseerr.",
@ -603,8 +603,6 @@
"components.Settings.serverLocal": "local",
"components.Settings.serverRemote": "remote",
"components.Settings.serverSecure": "secure",
"components.Settings.servername": "Server Name",
"components.Settings.servernameTip": "Automatically retrieved from Plex after saving",
"components.Settings.serverpreset": "Server",
"components.Settings.serverpresetLoad": "Press the button to load available servers",
"components.Settings.serverpresetManualMessage": "Manual configuration",
@ -632,6 +630,9 @@
"components.Settings.validationApplicationUrlTrailingSlash": "URL must not end in a trailing slash",
"components.Settings.validationHostnameRequired": "You must provide a valid hostname or IP address",
"components.Settings.validationPortRequired": "You must provide a valid port number",
"components.Settings.validationWebAppUrl": "You must provide a valid Plex Web App URL",
"components.Settings.webAppUrl": "<WebAppLink>Web App</WebAppLink> URL",
"components.Settings.webAppUrlTip": "Optionally direct users to the web app on your server instead of the \"hosted\" web app",
"components.Settings.webhook": "Webhook",
"components.Settings.webpush": "Web Push",
"components.Setup.configureplex": "Configure Plex",

Loading…
Cancel
Save