mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
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
This commit is contained in:
@@ -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",
|
||||
|
Reference in New Issue
Block a user