Compare commits
9 Commits
develop
...
feat/disab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bba921333b | ||
|
|
8bc0ee83b2 | ||
|
|
1d6f8c5ceb | ||
|
|
1dc6def596 | ||
|
|
3b05472ff4 | ||
|
|
a63e2b2c83 | ||
|
|
dc23d7698f | ||
|
|
09e6d8e8c0 | ||
|
|
bfbed0f709 |
@@ -28,9 +28,9 @@ describe('User List', () => {
|
|||||||
it('can create a local user', () => {
|
it('can create a local user', () => {
|
||||||
cy.visit('/users');
|
cy.visit('/users');
|
||||||
|
|
||||||
cy.contains('Create Local User').click();
|
cy.contains('Create User').click();
|
||||||
|
|
||||||
cy.get('[data-testid=modal-title]').should('contain', 'Create Local User');
|
cy.get('[data-testid=modal-title]').should('contain', 'Create User');
|
||||||
|
|
||||||
cy.get('#displayName').type(testUser.displayName);
|
cy.get('#displayName').type(testUser.displayName);
|
||||||
cy.get('#email').type(testUser.emailAddress);
|
cy.get('#email').type(testUser.emailAddress);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Cypress.Commands.add('login', (email, password) => {
|
|||||||
[email, password],
|
[email, password],
|
||||||
() => {
|
() => {
|
||||||
cy.visit('/login');
|
cy.visit('/login');
|
||||||
cy.contains('Use your Overseerr account').click();
|
cy.contains(/^Use your .+ password$/).click();
|
||||||
|
|
||||||
cy.get('[data-testid=email]').type(email);
|
cy.get('[data-testid=email]').type(email);
|
||||||
cy.get('[data-testid=password]').type(password);
|
cy.get('[data-testid=password]').type(password);
|
||||||
|
|||||||
@@ -64,15 +64,19 @@ This setting is **enabled** by default.
|
|||||||
|
|
||||||
## Users
|
## Users
|
||||||
|
|
||||||
### Enable Local Sign-In
|
### Sign-In Methods
|
||||||
|
|
||||||
When enabled, users who have configured passwords will be allowed to sign in using their email address.
|
Select the sign-in methods you would like to allow.
|
||||||
|
|
||||||
When disabled, Plex OAuth becomes the only sign-in option, and any "local users" you have created will not be able to sign in to Overseerr.
|
In order to disable Plex OAuth:
|
||||||
|
|
||||||
This setting is **enabled** by default.
|
- an [application title](#application-title) must be set,
|
||||||
|
- [email notifications](../notifications/email.md) must be enabled,
|
||||||
|
- and the server owner must have a password configured for their account.
|
||||||
|
|
||||||
### Enable New Plex Sign-In
|
Both Plex OAuth and password sign-in are enabled by default.
|
||||||
|
|
||||||
|
### Enable New Plex Sign-Ins
|
||||||
|
|
||||||
When enabled, users with access to your Plex server will be able to sign in to Overseerr even if they have not yet been imported. Users will be automatically assigned the permissions configured in the [Default Permissions](#default-permissions) setting upon first sign-in.
|
When enabled, users with access to your Plex server will be able to sign in to Overseerr even if they have not yet been imported. Users will be automatically assigned the permissions configured in the [Default Permissions](#default-permissions) setting upon first sign-in.
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ There are currently two methods to add users to Overseerr: importing Plex users
|
|||||||
|
|
||||||
Clicking the **Import Plex Users** button on the **User List** page will fetch the list of users with access to the Plex server from [plex.tv](https://www.plex.tv/), and add them to Overseerr automatically.
|
Clicking the **Import Plex Users** button on the **User List** page will fetch the list of users with access to the Plex server from [plex.tv](https://www.plex.tv/), and add them to Overseerr automatically.
|
||||||
|
|
||||||
Importing Plex users is not required, however. Any user with access to the Plex server can log in to Overseerr even if they have not been imported, and will be assigned the configured [default permissions](../settings/README.md#default-permissions) upon their first login.
|
Importing Plex users is not required, however. If the [Enable New Plex Sign-In](../settings/README.md#enable-new-plex-sign-in) setting is enabled, any user with access to the Plex server can log in to Overseerr even if they have not been imported. New users will be assigned the configured [default permissions](../settings/README.md#default-permissions) upon their first login.
|
||||||
|
|
||||||
### Creating Local Users
|
### Creating Local Users
|
||||||
|
|
||||||
If you would like to grant Overseerr access to a user who doesn't have their own Plex account and/or access to the Plex server, you can manually add them by clicking the **Create Local User** button.
|
If you would like to grant Overseerr access to a user who doesn't have their own Plex account and/or access to the Plex server, you can manually add them by clicking the **Create User** button.
|
||||||
|
|
||||||
#### Email Address
|
#### Email Address
|
||||||
|
|
||||||
|
|||||||
@@ -127,6 +127,9 @@ components:
|
|||||||
localLogin:
|
localLogin:
|
||||||
type: boolean
|
type: boolean
|
||||||
example: true
|
example: true
|
||||||
|
plexLogin:
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
newPlexLogin:
|
newPlexLogin:
|
||||||
type: boolean
|
type: boolean
|
||||||
example: true
|
example: true
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export interface PublicSettingsResponse {
|
|||||||
applicationUrl: string;
|
applicationUrl: string;
|
||||||
hideAvailable: boolean;
|
hideAvailable: boolean;
|
||||||
localLogin: boolean;
|
localLogin: boolean;
|
||||||
|
plexLogin: boolean;
|
||||||
movie4kEnabled: boolean;
|
movie4kEnabled: boolean;
|
||||||
series4kEnabled: boolean;
|
series4kEnabled: boolean;
|
||||||
region: string;
|
region: string;
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ export interface MainSettings {
|
|||||||
};
|
};
|
||||||
hideAvailable: boolean;
|
hideAvailable: boolean;
|
||||||
localLogin: boolean;
|
localLogin: boolean;
|
||||||
|
plexLogin: boolean;
|
||||||
newPlexLogin: boolean;
|
newPlexLogin: boolean;
|
||||||
region: string;
|
region: string;
|
||||||
originalLanguage: string;
|
originalLanguage: string;
|
||||||
@@ -112,6 +113,7 @@ interface FullPublicSettings extends PublicSettings {
|
|||||||
applicationUrl: string;
|
applicationUrl: string;
|
||||||
hideAvailable: boolean;
|
hideAvailable: boolean;
|
||||||
localLogin: boolean;
|
localLogin: boolean;
|
||||||
|
plexLogin: boolean;
|
||||||
movie4kEnabled: boolean;
|
movie4kEnabled: boolean;
|
||||||
series4kEnabled: boolean;
|
series4kEnabled: boolean;
|
||||||
region: string;
|
region: string;
|
||||||
@@ -288,6 +290,7 @@ class Settings {
|
|||||||
},
|
},
|
||||||
hideAvailable: false,
|
hideAvailable: false,
|
||||||
localLogin: true,
|
localLogin: true,
|
||||||
|
plexLogin: true,
|
||||||
newPlexLogin: true,
|
newPlexLogin: true,
|
||||||
region: '',
|
region: '',
|
||||||
originalLanguage: '',
|
originalLanguage: '',
|
||||||
@@ -480,6 +483,7 @@ class Settings {
|
|||||||
applicationUrl: this.data.main.applicationUrl,
|
applicationUrl: this.data.main.applicationUrl,
|
||||||
hideAvailable: this.data.main.hideAvailable,
|
hideAvailable: this.data.main.hideAvailable,
|
||||||
localLogin: this.data.main.localLogin,
|
localLogin: this.data.main.localLogin,
|
||||||
|
plexLogin: this.data.main.plexLogin,
|
||||||
movie4kEnabled: this.data.radarr.some(
|
movie4kEnabled: this.data.radarr.some(
|
||||||
(radarr) => radarr.is4k && radarr.isDefault
|
(radarr) => radarr.is4k && radarr.isDefault
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -30,11 +30,12 @@ authRoutes.post('/plex', async (req, res, next) => {
|
|||||||
const userRepository = getRepository(User);
|
const userRepository = getRepository(User);
|
||||||
const body = req.body as { authToken?: string };
|
const body = req.body as { authToken?: string };
|
||||||
|
|
||||||
if (!body.authToken) {
|
if (!settings.main.plexLogin) {
|
||||||
return next({
|
return res.status(500).json({ error: 'Plex sign-in is disabled.' });
|
||||||
status: 500,
|
} else if (!body.authToken) {
|
||||||
message: 'Authentication token required.',
|
return res
|
||||||
});
|
.status(500)
|
||||||
|
.json({ error: 'You must provide an authentication token' });
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// First we need to use this auth token to get the user's email from plex.tv
|
// First we need to use this auth token to get the user's email from plex.tv
|
||||||
|
|||||||
@@ -19,13 +19,19 @@ export interface AccordionChildProps {
|
|||||||
type AccordionContentProps = {
|
type AccordionContentProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AccordionContent = ({
|
export const AccordionContent = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
children,
|
children,
|
||||||
|
className,
|
||||||
}: AccordionContentProps) => {
|
}: AccordionContentProps) => {
|
||||||
return <AnimateHeight height={isOpen ? 'auto' : 0}>{children}</AnimateHeight>;
|
return (
|
||||||
|
<AnimateHeight height={isOpen ? 'auto' : 0} className={className}>
|
||||||
|
{children}
|
||||||
|
</AnimateHeight>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Accordion = ({
|
const Accordion = ({
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const messages = defineMessages({
|
|||||||
signin: 'Sign In',
|
signin: 'Sign In',
|
||||||
signinheader: 'Sign in to continue',
|
signinheader: 'Sign in to continue',
|
||||||
signinwithplex: 'Use your Plex account',
|
signinwithplex: 'Use your Plex account',
|
||||||
signinwithoverseerr: 'Use your {applicationTitle} account',
|
signinwithoverseerr: 'Use your {applicationTitle} password',
|
||||||
});
|
});
|
||||||
|
|
||||||
const Login = () => {
|
const Login = () => {
|
||||||
@@ -67,6 +67,32 @@ const Login = () => {
|
|||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const signinMethods: {
|
||||||
|
buttonText: string;
|
||||||
|
content: React.ReactNode;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
if (settings.currentSettings.plexLogin) {
|
||||||
|
signinMethods.push({
|
||||||
|
buttonText: intl.formatMessage(messages.signinwithplex),
|
||||||
|
content: (
|
||||||
|
<PlexLoginButton
|
||||||
|
isProcessing={isProcessing}
|
||||||
|
onAuthToken={(authToken) => setAuthToken(authToken)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.currentSettings.localLogin) {
|
||||||
|
signinMethods.push({
|
||||||
|
buttonText: intl.formatMessage(messages.signinwithoverseerr, {
|
||||||
|
applicationTitle: settings.currentSettings.applicationTitle,
|
||||||
|
}),
|
||||||
|
content: <LocalLogin revalidate={revalidate} />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex min-h-screen flex-col bg-gray-900 py-14">
|
<div className="relative flex min-h-screen flex-col bg-gray-900 py-14">
|
||||||
<PageTitle title={intl.formatMessage(messages.signin)} />
|
<PageTitle title={intl.formatMessage(messages.signin)} />
|
||||||
@@ -118,48 +144,41 @@ const Login = () => {
|
|||||||
<Accordion single atLeastOne>
|
<Accordion single atLeastOne>
|
||||||
{({ openIndexes, handleClick, AccordionContent }) => (
|
{({ openIndexes, handleClick, AccordionContent }) => (
|
||||||
<>
|
<>
|
||||||
<button
|
{signinMethods.map((signinMethod, index) => (
|
||||||
className={`w-full cursor-default bg-gray-800 bg-opacity-70 py-2 text-center text-sm font-bold text-gray-400 transition-colors duration-200 focus:outline-none sm:rounded-t-lg ${
|
<div
|
||||||
openIndexes.includes(0) && 'text-indigo-500'
|
key={`accordion-${index}`}
|
||||||
} ${
|
className="ring-1 ring-gray-700 sm:first:rounded-t-lg sm:last:rounded-b-lg"
|
||||||
settings.currentSettings.localLogin &&
|
>
|
||||||
'hover:cursor-pointer hover:bg-gray-700'
|
|
||||||
}`}
|
|
||||||
onClick={() => handleClick(0)}
|
|
||||||
disabled={!settings.currentSettings.localLogin}
|
|
||||||
>
|
|
||||||
{intl.formatMessage(messages.signinwithplex)}
|
|
||||||
</button>
|
|
||||||
<AccordionContent isOpen={openIndexes.includes(0)}>
|
|
||||||
<div className="px-10 py-8">
|
|
||||||
<PlexLoginButton
|
|
||||||
isProcessing={isProcessing}
|
|
||||||
onAuthToken={(authToken) => setAuthToken(authToken)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
{settings.currentSettings.localLogin && (
|
|
||||||
<div>
|
|
||||||
<button
|
<button
|
||||||
className={`w-full cursor-default bg-gray-800 bg-opacity-70 py-2 text-center text-sm font-bold text-gray-400 transition-colors duration-200 hover:cursor-pointer hover:bg-gray-700 focus:outline-none ${
|
className={`w-full cursor-default bg-gray-800 bg-opacity-70 py-2 text-center text-base font-bold focus:outline-none ${
|
||||||
openIndexes.includes(1)
|
index === 0 ? 'sm:rounded-t-lg' : ''
|
||||||
|
} ${
|
||||||
|
openIndexes.includes(index)
|
||||||
? 'text-indigo-500'
|
? 'text-indigo-500'
|
||||||
: 'sm:rounded-b-lg'
|
: `text-gray-400 ${
|
||||||
|
index === signinMethods.length - 1
|
||||||
|
? 'sm:rounded-b-lg'
|
||||||
|
: ''
|
||||||
|
} ${
|
||||||
|
signinMethods.length > 1
|
||||||
|
? 'transition-colors duration-200 hover:cursor-pointer hover:bg-gray-700'
|
||||||
|
: ''
|
||||||
|
}`
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleClick(1)}
|
onClick={() => handleClick(index)}
|
||||||
>
|
>
|
||||||
{intl.formatMessage(messages.signinwithoverseerr, {
|
{signinMethod.buttonText}
|
||||||
applicationTitle:
|
|
||||||
settings.currentSettings.applicationTitle,
|
|
||||||
})}
|
|
||||||
</button>
|
</button>
|
||||||
<AccordionContent isOpen={openIndexes.includes(1)}>
|
<AccordionContent
|
||||||
<div className="px-10 py-8">
|
isOpen={openIndexes.includes(index)}
|
||||||
<LocalLogin revalidate={revalidate} />
|
className={
|
||||||
</div>
|
index < signinMethods.length - 1 ? 'mb-px' : ''
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="px-10 py-8">{signinMethod.content}</div>
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</div>
|
</div>
|
||||||
)}
|
))}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|||||||
@@ -45,16 +45,14 @@ const NotificationType = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3 text-sm leading-6">
|
<label htmlFor={option.id} className="ml-3 flex flex-col text-sm">
|
||||||
<label htmlFor={option.id} className="block">
|
<span className="font-semibold leading-6 text-white">
|
||||||
<div className="flex flex-col">
|
{option.name}
|
||||||
<span className="font-medium text-white">{option.name}</span>
|
</span>
|
||||||
<span className="font-normal text-gray-400">
|
<span className="font-normal text-gray-400">
|
||||||
{option.description}
|
{option.description}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</label>
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{(option.children ?? []).map((child) => (
|
{(option.children ?? []).map((child) => (
|
||||||
<div key={`notification-type-child-${child.id}`} className="mt-4 pl-6">
|
<div key={`notification-type-child-${child.id}`} className="mt-4 pl-6">
|
||||||
|
|||||||
@@ -381,17 +381,15 @@ const NotificationTypeSelector = ({
|
|||||||
{intl.formatMessage(messages.notificationTypes)}
|
{intl.formatMessage(messages.notificationTypes)}
|
||||||
{!user && <span className="label-required">*</span>}
|
{!user && <span className="label-required">*</span>}
|
||||||
</span>
|
</span>
|
||||||
<div className="form-input-area">
|
<div className="form-input-area max-w-xl">
|
||||||
<div className="max-w-lg">
|
{availableTypes.map((type) => (
|
||||||
{availableTypes.map((type) => (
|
<NotificationType
|
||||||
<NotificationType
|
key={`notification-type-${type.id}`}
|
||||||
key={`notification-type-${type.id}`}
|
option={type}
|
||||||
option={type}
|
currentTypes={currentTypes}
|
||||||
currentTypes={currentTypes}
|
onUpdate={onUpdate}
|
||||||
onUpdate={onUpdate}
|
/>
|
||||||
/>
|
))}
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
{error && <div className="error">{error}</div>}
|
{error && <div className="error">{error}</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -122,16 +122,14 @@ const PermissionOption = ({
|
|||||||
checked={checked}
|
checked={checked}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3 text-sm leading-6">
|
<label htmlFor={option.id} className="ml-3 flex flex-col text-sm">
|
||||||
<label htmlFor={option.id} className="block">
|
<span className="font-semibold leading-6 text-white">
|
||||||
<div className="flex flex-col">
|
{option.name}
|
||||||
<span className="font-medium text-white">{option.name}</span>
|
</span>
|
||||||
<span className="font-normal text-gray-400">
|
<span className="font-normal text-gray-400">
|
||||||
{option.description}
|
{option.description}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</label>
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{(option.children ?? []).map((child) => (
|
{(option.children ?? []).map((child) => (
|
||||||
<div key={`permission-child-${child.id}`} className="mt-4 pl-10">
|
<div key={`permission-child-${child.id}`} className="mt-4 pl-10">
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const QuotaSelector = ({
|
|||||||
}, [limitFieldName, onChange, quotaLimit]);
|
}, [limitFieldName, onChange, quotaLimit]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${isDisabled ? 'opacity-50' : ''}`}>
|
<span className={isDisabled ? 'opacity-50' : ''}>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
mediaType === 'movie' ? messages.movieRequests : messages.tvRequests,
|
mediaType === 'movie' ? messages.movieRequests : messages.tvRequests,
|
||||||
{
|
{
|
||||||
@@ -97,7 +97,7 @@ const QuotaSelector = ({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
</div>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
|||||||
import PageTitle from '@app/components/Common/PageTitle';
|
import PageTitle from '@app/components/Common/PageTitle';
|
||||||
import PermissionEdit from '@app/components/PermissionEdit';
|
import PermissionEdit from '@app/components/PermissionEdit';
|
||||||
import QuotaSelector from '@app/components/QuotaSelector';
|
import QuotaSelector from '@app/components/QuotaSelector';
|
||||||
|
import useSettings from '@app/hooks/useSettings';
|
||||||
import globalMessages from '@app/i18n/globalMessages';
|
import globalMessages from '@app/i18n/globalMessages';
|
||||||
import { SaveIcon } from '@heroicons/react/outline';
|
import { SaveIcon } from '@heroicons/react/outline';
|
||||||
import type { MainSettings } from '@server/lib/settings';
|
import type { MainSettings } from '@server/lib/settings';
|
||||||
@@ -11,6 +12,7 @@ import { Field, Form, Formik } from 'formik';
|
|||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { useToasts } from 'react-toast-notifications';
|
import { useToasts } from 'react-toast-notifications';
|
||||||
import useSWR, { mutate } from 'swr';
|
import useSWR, { mutate } from 'swr';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
users: 'Users',
|
users: 'Users',
|
||||||
@@ -18,30 +20,62 @@ const messages = defineMessages({
|
|||||||
userSettingsDescription: 'Configure global and default user settings.',
|
userSettingsDescription: 'Configure global and default user settings.',
|
||||||
toastSettingsSuccess: 'User settings saved successfully!',
|
toastSettingsSuccess: 'User settings saved successfully!',
|
||||||
toastSettingsFailure: 'Something went wrong while saving settings.',
|
toastSettingsFailure: 'Something went wrong while saving settings.',
|
||||||
localLogin: 'Enable Local Sign-In',
|
newPlexLogin: 'Enable New Plex Sign-Ins',
|
||||||
localLoginTip:
|
newPlexLoginTip:
|
||||||
'Allow users to sign in using their email address and password, instead of Plex OAuth',
|
'Allow Plex users with access to the media server to sign in without being imported',
|
||||||
newPlexLogin: 'Enable New Plex Sign-In',
|
|
||||||
newPlexLoginTip: 'Allow Plex users to sign in without first being imported',
|
|
||||||
movieRequestLimitLabel: 'Global Movie Request Limit',
|
movieRequestLimitLabel: 'Global Movie Request Limit',
|
||||||
tvRequestLimitLabel: 'Global Series Request Limit',
|
tvRequestLimitLabel: 'Global Series Request Limit',
|
||||||
defaultPermissions: 'Default Permissions',
|
defaultPermissions: 'Default Permissions',
|
||||||
defaultPermissionsTip: 'Initial permissions assigned to new users',
|
defaultPermissionsTip: 'Initial permissions assigned to new users',
|
||||||
|
signinMethods: 'Sign-In Methods',
|
||||||
|
passwordSignin: '{applicationTitle} Password',
|
||||||
|
validationSigninMethods: 'At least one sign-in method must be selected',
|
||||||
});
|
});
|
||||||
|
|
||||||
const SettingsUsers = () => {
|
const SettingsUsers = () => {
|
||||||
const { addToast } = useToasts();
|
const { addToast } = useToasts();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const settings = useSettings();
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
error,
|
error,
|
||||||
mutate: revalidate,
|
mutate: revalidate,
|
||||||
} = useSWR<MainSettings>('/api/v1/settings/main');
|
} = useSWR<MainSettings>('/api/v1/settings/main');
|
||||||
|
const { data: ownerData } = useSWR<{ hasPassword: boolean }>(
|
||||||
|
'/api/v1/user/1/settings/password'
|
||||||
|
);
|
||||||
|
|
||||||
|
const SettingsUsersSchema = Yup.object().shape(
|
||||||
|
{
|
||||||
|
plexLogin: Yup.boolean().when('localLogin', {
|
||||||
|
is: false,
|
||||||
|
then: Yup.boolean().oneOf(
|
||||||
|
[true],
|
||||||
|
intl.formatMessage(messages.validationSigninMethods)
|
||||||
|
),
|
||||||
|
otherwise: Yup.boolean(),
|
||||||
|
}),
|
||||||
|
localLogin: Yup.boolean().when('plexLogin', {
|
||||||
|
is: false,
|
||||||
|
then: Yup.boolean().oneOf(
|
||||||
|
[true],
|
||||||
|
intl.formatMessage(messages.validationSigninMethods)
|
||||||
|
),
|
||||||
|
otherwise: Yup.boolean(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
[['plexLogin', 'localLogin']]
|
||||||
|
);
|
||||||
|
|
||||||
if (!data && !error) {
|
if (!data && !error) {
|
||||||
return <LoadingSpinner />;
|
return <LoadingSpinner />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const allowPlexSigninDisable =
|
||||||
|
ownerData?.hasPassword &&
|
||||||
|
!!settings.currentSettings.applicationUrl &&
|
||||||
|
settings.currentSettings.emailEnabled;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageTitle
|
<PageTitle
|
||||||
@@ -60,6 +94,7 @@ const SettingsUsers = () => {
|
|||||||
<Formik
|
<Formik
|
||||||
initialValues={{
|
initialValues={{
|
||||||
localLogin: data?.localLogin,
|
localLogin: data?.localLogin,
|
||||||
|
plexLogin: data?.plexLogin,
|
||||||
newPlexLogin: data?.newPlexLogin,
|
newPlexLogin: data?.newPlexLogin,
|
||||||
movieQuotaLimit: data?.defaultQuotas.movie.quotaLimit ?? 0,
|
movieQuotaLimit: data?.defaultQuotas.movie.quotaLimit ?? 0,
|
||||||
movieQuotaDays: data?.defaultQuotas.movie.quotaDays ?? 7,
|
movieQuotaDays: data?.defaultQuotas.movie.quotaDays ?? 7,
|
||||||
@@ -68,10 +103,12 @@ const SettingsUsers = () => {
|
|||||||
defaultPermissions: data?.defaultPermissions ?? 0,
|
defaultPermissions: data?.defaultPermissions ?? 0,
|
||||||
}}
|
}}
|
||||||
enableReinitialize
|
enableReinitialize
|
||||||
|
validationSchema={SettingsUsersSchema}
|
||||||
onSubmit={async (values) => {
|
onSubmit={async (values) => {
|
||||||
try {
|
try {
|
||||||
await axios.post('/api/v1/settings/main', {
|
await axios.post('/api/v1/settings/main', {
|
||||||
localLogin: values.localLogin,
|
localLogin: values.localLogin,
|
||||||
|
plexLogin: values.plexLogin,
|
||||||
newPlexLogin: values.newPlexLogin,
|
newPlexLogin: values.newPlexLogin,
|
||||||
defaultQuotas: {
|
defaultQuotas: {
|
||||||
movie: {
|
movie: {
|
||||||
@@ -101,28 +138,82 @@ const SettingsUsers = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ isSubmitting, values, setFieldValue }) => {
|
{({
|
||||||
|
isSubmitting,
|
||||||
|
values,
|
||||||
|
setFieldValue,
|
||||||
|
errors,
|
||||||
|
touched,
|
||||||
|
isValid,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Form className="section">
|
<Form className="section">
|
||||||
<div className="form-row">
|
<div
|
||||||
<label htmlFor="localLogin" className="checkbox-label">
|
role="group"
|
||||||
{intl.formatMessage(messages.localLogin)}
|
aria-labelledby="group-label"
|
||||||
<span className="label-tip">
|
className="form-group"
|
||||||
{intl.formatMessage(messages.localLoginTip)}
|
>
|
||||||
|
<div className="form-row">
|
||||||
|
<span id="group-label" className="group-label">
|
||||||
|
{intl.formatMessage(messages.signinMethods)}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
<div className="form-input-area max-w-xl space-y-1.5">
|
||||||
<div className="form-input-area">
|
<div
|
||||||
<Field
|
className={`relative flex items-start ${
|
||||||
type="checkbox"
|
allowPlexSigninDisable ? '' : 'opacity-50'
|
||||||
id="localLogin"
|
}`}
|
||||||
name="localLogin"
|
>
|
||||||
onChange={() => {
|
<div className="flex h-6 items-center">
|
||||||
setFieldValue('localLogin', !values.localLogin);
|
<Field
|
||||||
}}
|
type="checkbox"
|
||||||
/>
|
id="plexLogin"
|
||||||
|
name="plexLogin"
|
||||||
|
onChange={() => {
|
||||||
|
setFieldValue('plexLogin', !values.plexLogin);
|
||||||
|
}}
|
||||||
|
disabled={!allowPlexSigninDisable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
htmlFor="plexLogin"
|
||||||
|
className="ml-3 block text-sm font-semibold leading-6 text-white"
|
||||||
|
>
|
||||||
|
Plex OAuth
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="relative flex items-start">
|
||||||
|
<div className="flex h-6 items-center">
|
||||||
|
<Field
|
||||||
|
type="checkbox"
|
||||||
|
id="localLogin"
|
||||||
|
name="localLogin"
|
||||||
|
onChange={() => {
|
||||||
|
setFieldValue('localLogin', !values.localLogin);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
htmlFor="localLogin"
|
||||||
|
className="ml-3 block text-sm font-semibold leading-6 text-white"
|
||||||
|
>
|
||||||
|
{intl.formatMessage(messages.passwordSignin, {
|
||||||
|
applicationTitle:
|
||||||
|
settings.currentSettings.applicationTitle,
|
||||||
|
})}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{(touched.plexLogin || touched.localLogin) &&
|
||||||
|
(errors.plexLogin || errors.localLogin) && (
|
||||||
|
<div className="error">
|
||||||
|
{errors.plexLogin ?? errors.localLogin}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-row">
|
<div
|
||||||
|
className={`form-row ${values.plexLogin ? '' : 'opacity-50'}`}
|
||||||
|
>
|
||||||
<label htmlFor="newPlexLogin" className="checkbox-label">
|
<label htmlFor="newPlexLogin" className="checkbox-label">
|
||||||
{intl.formatMessage(messages.newPlexLogin)}
|
{intl.formatMessage(messages.newPlexLogin)}
|
||||||
<span className="label-tip">
|
<span className="label-tip">
|
||||||
@@ -137,6 +228,8 @@ const SettingsUsers = () => {
|
|||||||
onChange={() => {
|
onChange={() => {
|
||||||
setFieldValue('newPlexLogin', !values.newPlexLogin);
|
setFieldValue('newPlexLogin', !values.newPlexLogin);
|
||||||
}}
|
}}
|
||||||
|
checked={values.newPlexLogin && values.plexLogin}
|
||||||
|
disabled={!values.plexLogin}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -182,15 +275,13 @@ const SettingsUsers = () => {
|
|||||||
{intl.formatMessage(messages.defaultPermissionsTip)}
|
{intl.formatMessage(messages.defaultPermissionsTip)}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<div className="form-input-area">
|
<div className="form-input-area max-w-xl">
|
||||||
<div className="max-w-lg">
|
<PermissionEdit
|
||||||
<PermissionEdit
|
currentPermission={values.defaultPermissions}
|
||||||
currentPermission={values.defaultPermissions}
|
onUpdate={(newPermissions) =>
|
||||||
onUpdate={(newPermissions) =>
|
setFieldValue('defaultPermissions', newPermissions)
|
||||||
setFieldValue('defaultPermissions', newPermissions)
|
}
|
||||||
}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -200,7 +291,7 @@ const SettingsUsers = () => {
|
|||||||
<Button
|
<Button
|
||||||
buttonType="primary"
|
buttonType="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting || !isValid}
|
||||||
>
|
>
|
||||||
<SaveIcon />
|
<SaveIcon />
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const messages = defineMessages({
|
|||||||
accounttype: 'Type',
|
accounttype: 'Type',
|
||||||
role: 'Role',
|
role: 'Role',
|
||||||
created: 'Joined',
|
created: 'Joined',
|
||||||
bulkedit: 'Bulk Edit',
|
bulkedit: 'Edit Permissions',
|
||||||
owner: 'Owner',
|
owner: 'Owner',
|
||||||
admin: 'Admin',
|
admin: 'Admin',
|
||||||
plexuser: 'Plex User',
|
plexuser: 'Plex User',
|
||||||
@@ -54,9 +54,7 @@ const messages = defineMessages({
|
|||||||
deleteconfirm:
|
deleteconfirm:
|
||||||
'Are you sure you want to delete this user? All of their request data will be permanently removed.',
|
'Are you sure you want to delete this user? All of their request data will be permanently removed.',
|
||||||
localuser: 'Local User',
|
localuser: 'Local User',
|
||||||
createlocaluser: 'Create Local User',
|
createlocaluser: 'Create User',
|
||||||
creating: 'Creating…',
|
|
||||||
create: 'Create',
|
|
||||||
validationpasswordminchars:
|
validationpasswordminchars:
|
||||||
'Password is too short; should be a minimum of 8 characters',
|
'Password is too short; should be a minimum of 8 characters',
|
||||||
usercreatedfailed: 'Something went wrong while creating the user.',
|
usercreatedfailed: 'Something went wrong while creating the user.',
|
||||||
@@ -75,7 +73,7 @@ const messages = defineMessages({
|
|||||||
sortDisplayName: 'Display Name',
|
sortDisplayName: 'Display Name',
|
||||||
sortRequests: 'Request Count',
|
sortRequests: 'Request Count',
|
||||||
localLoginDisabled:
|
localLoginDisabled:
|
||||||
'The <strong>Enable Local Sign-In</strong> setting is currently disabled.',
|
'The <strong>{applicationTitle} Password</strong> sign-in method is currently disabled.',
|
||||||
});
|
});
|
||||||
|
|
||||||
type Sort = 'created' | 'updated' | 'requests' | 'displayname';
|
type Sort = 'created' | 'updated' | 'requests' | 'displayname';
|
||||||
@@ -317,8 +315,8 @@ const UserList = () => {
|
|||||||
onOk={() => handleSubmit()}
|
onOk={() => handleSubmit()}
|
||||||
okText={
|
okText={
|
||||||
isSubmitting
|
isSubmitting
|
||||||
? intl.formatMessage(messages.creating)
|
? intl.formatMessage(globalMessages.creating)
|
||||||
: intl.formatMessage(messages.create)
|
: intl.formatMessage(globalMessages.create)
|
||||||
}
|
}
|
||||||
okDisabled={isSubmitting || !isValid}
|
okDisabled={isSubmitting || !isValid}
|
||||||
okButtonType="primary"
|
okButtonType="primary"
|
||||||
@@ -327,6 +325,8 @@ const UserList = () => {
|
|||||||
{!settings.currentSettings.localLogin && (
|
{!settings.currentSettings.localLogin && (
|
||||||
<Alert
|
<Alert
|
||||||
title={intl.formatMessage(messages.localLoginDisabled, {
|
title={intl.formatMessage(messages.localLoginDisabled, {
|
||||||
|
applicationTitle:
|
||||||
|
settings.currentSettings.applicationTitle,
|
||||||
strong: (msg: React.ReactNode) => (
|
strong: (msg: React.ReactNode) => (
|
||||||
<strong className="font-semibold text-white">
|
<strong className="font-semibold text-white">
|
||||||
{msg}
|
{msg}
|
||||||
|
|||||||
@@ -345,13 +345,13 @@ const UserGeneralSettings = () => {
|
|||||||
</label>
|
</label>
|
||||||
<div className="form-input-area">
|
<div className="form-input-area">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="mb-4 flex items-center">
|
<div className="mb-1.5 flex items-center">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={movieQuotaEnabled}
|
checked={movieQuotaEnabled}
|
||||||
onChange={() => setMovieQuotaEnabled((s) => !s)}
|
onChange={() => setMovieQuotaEnabled((s) => !s)}
|
||||||
/>
|
/>
|
||||||
<span className="ml-2 text-gray-300">
|
<span className="ml-2 font-semibold text-gray-300">
|
||||||
{intl.formatMessage(messages.enableOverride)}
|
{intl.formatMessage(messages.enableOverride)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -385,13 +385,13 @@ const UserGeneralSettings = () => {
|
|||||||
</label>
|
</label>
|
||||||
<div className="form-input-area">
|
<div className="form-input-area">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="mb-4 flex items-center">
|
<div className="mb-1.5 flex items-center">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={tvQuotaEnabled}
|
checked={tvQuotaEnabled}
|
||||||
onChange={() => setTvQuotaEnabled((s) => !s)}
|
onChange={() => setTvQuotaEnabled((s) => !s)}
|
||||||
/>
|
/>
|
||||||
<span className="ml-2 text-gray-300">
|
<span className="ml-2 font-semibold text-gray-300">
|
||||||
{intl.formatMessage(messages.enableOverride)}
|
{intl.formatMessage(messages.enableOverride)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,10 +30,9 @@ const messages = defineMessages({
|
|||||||
'Password is too short; should be a minimum of 8 characters',
|
'Password is too short; should be a minimum of 8 characters',
|
||||||
validationConfirmPassword: 'You must confirm the new password',
|
validationConfirmPassword: 'You must confirm the new password',
|
||||||
validationConfirmPasswordSame: 'Passwords must match',
|
validationConfirmPasswordSame: 'Passwords must match',
|
||||||
noPasswordSet:
|
noPasswordSet: 'This user account currently does not have a password set.',
|
||||||
'This user account currently does not have a password set. Configure a password below to enable this account to sign in as a "local user."',
|
|
||||||
noPasswordSetOwnAccount:
|
noPasswordSetOwnAccount:
|
||||||
'Your account currently does not have a password set. Configure a password below to enable sign-in as a "local user" using your email address.',
|
'Your account currently does not have a password set.',
|
||||||
nopermissionDescription:
|
nopermissionDescription:
|
||||||
"You do not have permission to modify this user's password.",
|
"You do not have permission to modify this user's password.",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const defaultSettings = {
|
|||||||
applicationUrl: '',
|
applicationUrl: '',
|
||||||
hideAvailable: false,
|
hideAvailable: false,
|
||||||
localLogin: true,
|
localLogin: true,
|
||||||
|
plexLogin: true,
|
||||||
movie4kEnabled: false,
|
movie4kEnabled: false,
|
||||||
series4kEnabled: false,
|
series4kEnabled: false,
|
||||||
region: '',
|
region: '',
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ const globalMessages = defineMessages({
|
|||||||
saving: 'Saving…',
|
saving: 'Saving…',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
importing: 'Importing…',
|
importing: 'Importing…',
|
||||||
|
create: 'Create',
|
||||||
|
creating: 'Creating…',
|
||||||
close: 'Close',
|
close: 'Close',
|
||||||
edit: 'Edit',
|
edit: 'Edit',
|
||||||
areyousure: 'Are you sure?',
|
areyousure: 'Are you sure?',
|
||||||
|
|||||||
@@ -133,7 +133,7 @@
|
|||||||
"components.Login.signin": "Sign In",
|
"components.Login.signin": "Sign In",
|
||||||
"components.Login.signingin": "Signing In…",
|
"components.Login.signingin": "Signing In…",
|
||||||
"components.Login.signinheader": "Sign in to continue",
|
"components.Login.signinheader": "Sign in to continue",
|
||||||
"components.Login.signinwithoverseerr": "Use your {applicationTitle} account",
|
"components.Login.signinwithoverseerr": "Use your {applicationTitle} password",
|
||||||
"components.Login.signinwithplex": "Use your Plex account",
|
"components.Login.signinwithplex": "Use your Plex account",
|
||||||
"components.Login.validationemailrequired": "You must provide a valid email address",
|
"components.Login.validationemailrequired": "You must provide a valid email address",
|
||||||
"components.Login.validationpasswordrequired": "You must provide a password",
|
"components.Login.validationpasswordrequired": "You must provide a password",
|
||||||
@@ -682,17 +682,18 @@
|
|||||||
"components.Settings.SettingsLogs.viewdetails": "View Details",
|
"components.Settings.SettingsLogs.viewdetails": "View Details",
|
||||||
"components.Settings.SettingsUsers.defaultPermissions": "Default Permissions",
|
"components.Settings.SettingsUsers.defaultPermissions": "Default Permissions",
|
||||||
"components.Settings.SettingsUsers.defaultPermissionsTip": "Initial permissions assigned to new users",
|
"components.Settings.SettingsUsers.defaultPermissionsTip": "Initial permissions assigned to new users",
|
||||||
"components.Settings.SettingsUsers.localLogin": "Enable Local Sign-In",
|
|
||||||
"components.Settings.SettingsUsers.localLoginTip": "Allow users to sign in using their email address and password, instead of Plex OAuth",
|
|
||||||
"components.Settings.SettingsUsers.movieRequestLimitLabel": "Global Movie Request Limit",
|
"components.Settings.SettingsUsers.movieRequestLimitLabel": "Global Movie Request Limit",
|
||||||
"components.Settings.SettingsUsers.newPlexLogin": "Enable New Plex Sign-In",
|
"components.Settings.SettingsUsers.newPlexLogin": "Enable New Plex Sign-Ins",
|
||||||
"components.Settings.SettingsUsers.newPlexLoginTip": "Allow Plex users to sign in without first being imported",
|
"components.Settings.SettingsUsers.newPlexLoginTip": "Allow Plex users with access to the media server to sign in without being imported",
|
||||||
|
"components.Settings.SettingsUsers.passwordSignin": "{applicationTitle} Password",
|
||||||
|
"components.Settings.SettingsUsers.signinMethods": "Sign-In Methods",
|
||||||
"components.Settings.SettingsUsers.toastSettingsFailure": "Something went wrong while saving settings.",
|
"components.Settings.SettingsUsers.toastSettingsFailure": "Something went wrong while saving settings.",
|
||||||
"components.Settings.SettingsUsers.toastSettingsSuccess": "User settings saved successfully!",
|
"components.Settings.SettingsUsers.toastSettingsSuccess": "User settings saved successfully!",
|
||||||
"components.Settings.SettingsUsers.tvRequestLimitLabel": "Global Series Request Limit",
|
"components.Settings.SettingsUsers.tvRequestLimitLabel": "Global Series Request Limit",
|
||||||
"components.Settings.SettingsUsers.userSettings": "User Settings",
|
"components.Settings.SettingsUsers.userSettings": "User Settings",
|
||||||
"components.Settings.SettingsUsers.userSettingsDescription": "Configure global and default user settings.",
|
"components.Settings.SettingsUsers.userSettingsDescription": "Configure global and default user settings.",
|
||||||
"components.Settings.SettingsUsers.users": "Users",
|
"components.Settings.SettingsUsers.users": "Users",
|
||||||
|
"components.Settings.SettingsUsers.validationSigninMethods": "At least one sign-in method must be selected",
|
||||||
"components.Settings.SonarrModal.add": "Add Server",
|
"components.Settings.SonarrModal.add": "Add Server",
|
||||||
"components.Settings.SonarrModal.animeTags": "Anime Tags",
|
"components.Settings.SonarrModal.animeTags": "Anime Tags",
|
||||||
"components.Settings.SonarrModal.animelanguageprofile": "Anime Language Profile",
|
"components.Settings.SonarrModal.animelanguageprofile": "Anime Language Profile",
|
||||||
@@ -918,11 +919,9 @@
|
|||||||
"components.UserList.admin": "Admin",
|
"components.UserList.admin": "Admin",
|
||||||
"components.UserList.autogeneratepassword": "Automatically Generate Password",
|
"components.UserList.autogeneratepassword": "Automatically Generate Password",
|
||||||
"components.UserList.autogeneratepasswordTip": "Email a server-generated password to the user",
|
"components.UserList.autogeneratepasswordTip": "Email a server-generated password to the user",
|
||||||
"components.UserList.bulkedit": "Bulk Edit",
|
"components.UserList.bulkedit": "Edit Permissions",
|
||||||
"components.UserList.create": "Create",
|
|
||||||
"components.UserList.created": "Joined",
|
"components.UserList.created": "Joined",
|
||||||
"components.UserList.createlocaluser": "Create Local User",
|
"components.UserList.createlocaluser": "Create User",
|
||||||
"components.UserList.creating": "Creating…",
|
|
||||||
"components.UserList.deleteconfirm": "Are you sure you want to delete this user? All of their request data will be permanently removed.",
|
"components.UserList.deleteconfirm": "Are you sure you want to delete this user? All of their request data will be permanently removed.",
|
||||||
"components.UserList.deleteuser": "Delete User",
|
"components.UserList.deleteuser": "Delete User",
|
||||||
"components.UserList.displayName": "Display Name",
|
"components.UserList.displayName": "Display Name",
|
||||||
@@ -931,7 +930,7 @@
|
|||||||
"components.UserList.importedfromplex": "<strong>{userCount}</strong> Plex {userCount, plural, one {user} other {users}} imported successfully!",
|
"components.UserList.importedfromplex": "<strong>{userCount}</strong> Plex {userCount, plural, one {user} other {users}} imported successfully!",
|
||||||
"components.UserList.importfromplex": "Import Plex Users",
|
"components.UserList.importfromplex": "Import Plex Users",
|
||||||
"components.UserList.importfromplexerror": "Something went wrong while importing Plex users.",
|
"components.UserList.importfromplexerror": "Something went wrong while importing Plex users.",
|
||||||
"components.UserList.localLoginDisabled": "The <strong>Enable Local Sign-In</strong> setting is currently disabled.",
|
"components.UserList.localLoginDisabled": "The <strong>{applicationTitle} Password</strong> sign-in method is currently disabled.",
|
||||||
"components.UserList.localuser": "Local User",
|
"components.UserList.localuser": "Local User",
|
||||||
"components.UserList.newplexsigninenabled": "The <strong>Enable New Plex Sign-In</strong> setting is currently enabled. Plex users with library access do not need to be imported in order to sign in.",
|
"components.UserList.newplexsigninenabled": "The <strong>Enable New Plex Sign-In</strong> setting is currently enabled. Plex users with library access do not need to be imported in order to sign in.",
|
||||||
"components.UserList.nouserstoimport": "There are no Plex users to import.",
|
"components.UserList.nouserstoimport": "There are no Plex users to import.",
|
||||||
@@ -1027,8 +1026,8 @@
|
|||||||
"components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "Confirm Password",
|
"components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "Confirm Password",
|
||||||
"components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "Current Password",
|
"components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "Current Password",
|
||||||
"components.UserProfile.UserSettings.UserPasswordChange.newpassword": "New Password",
|
"components.UserProfile.UserSettings.UserPasswordChange.newpassword": "New Password",
|
||||||
"components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "This user account currently does not have a password set. Configure a password below to enable this account to sign in as a \"local user.\"",
|
"components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "This user account currently does not have a password set.",
|
||||||
"components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "Your account currently does not have a password set. Configure a password below to enable sign-in as a \"local user\" using your email address.",
|
"components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "Your account currently does not have a password set.",
|
||||||
"components.UserProfile.UserSettings.UserPasswordChange.nopermissionDescription": "You do not have permission to modify this user's password.",
|
"components.UserProfile.UserSettings.UserPasswordChange.nopermissionDescription": "You do not have permission to modify this user's password.",
|
||||||
"components.UserProfile.UserSettings.UserPasswordChange.password": "Password",
|
"components.UserProfile.UserSettings.UserPasswordChange.password": "Password",
|
||||||
"components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailure": "Something went wrong while saving the password.",
|
"components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailure": "Something went wrong while saving the password.",
|
||||||
@@ -1069,6 +1068,8 @@
|
|||||||
"i18n.cancel": "Cancel",
|
"i18n.cancel": "Cancel",
|
||||||
"i18n.canceling": "Canceling…",
|
"i18n.canceling": "Canceling…",
|
||||||
"i18n.close": "Close",
|
"i18n.close": "Close",
|
||||||
|
"i18n.create": "Create",
|
||||||
|
"i18n.creating": "Creating…",
|
||||||
"i18n.decline": "Decline",
|
"i18n.decline": "Decline",
|
||||||
"i18n.declined": "Declined",
|
"i18n.declined": "Declined",
|
||||||
"i18n.delete": "Delete",
|
"i18n.delete": "Delete",
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ CoreApp.getInitialProps = async (initialProps) => {
|
|||||||
movie4kEnabled: false,
|
movie4kEnabled: false,
|
||||||
series4kEnabled: false,
|
series4kEnabled: false,
|
||||||
localLogin: true,
|
localLogin: true,
|
||||||
|
plexLogin: true,
|
||||||
region: '',
|
region: '',
|
||||||
originalLanguage: '',
|
originalLanguage: '',
|
||||||
partialRequestsEnabled: true,
|
partialRequestsEnabled: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user