Compare commits

...

9 Commits

Author SHA1 Message Date
TheCatLady
bba921333b test: update local user test to reflect string change 2022-08-28 23:21:30 -07:00
TheCatLady
8bc0ee83b2 test: update login command to reflect string change 2022-08-28 23:21:29 -07:00
TheCatLady
1d6f8c5ceb refactor: appease Prettier 2022-08-28 23:21:28 -07:00
TheCatLady
1dc6def596 fix(lang): string edits 2022-08-28 23:21:27 -07:00
TheCatLady
3b05472ff4 docs: add app title req for disabling Plex OAuth 2022-08-28 23:21:25 -07:00
TheCatLady
a63e2b2c83 fix: consistently style login page 2022-08-28 23:21:24 -07:00
TheCatLady
dc23d7698f fix: correctly set opacity when checkbox disabled 2022-08-28 23:21:23 -07:00
TheCatLady
09e6d8e8c0 refactor: clean up Login component 2022-08-28 23:21:22 -07:00
TheCatLady
bfbed0f709 feat(login): allow Plex OAuth to be disabled when reqs are met 2022-08-28 23:21:21 -07:00
22 changed files with 270 additions and 143 deletions

View File

@@ -28,9 +28,9 @@ describe('User List', () => {
it('can create a local user', () => {
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('#email').type(testUser.emailAddress);

View File

@@ -5,7 +5,7 @@ Cypress.Commands.add('login', (email, password) => {
[email, password],
() => {
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=password]').type(password);

View File

@@ -64,15 +64,19 @@ This setting is **enabled** by default.
## 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.

View File

@@ -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.
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
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

View File

@@ -127,6 +127,9 @@ components:
localLogin:
type: boolean
example: true
plexLogin:
type: boolean
example: true
newPlexLogin:
type: boolean
example: true

View File

@@ -26,6 +26,7 @@ export interface PublicSettingsResponse {
applicationUrl: string;
hideAvailable: boolean;
localLogin: boolean;
plexLogin: boolean;
movie4kEnabled: boolean;
series4kEnabled: boolean;
region: string;

View File

@@ -95,6 +95,7 @@ export interface MainSettings {
};
hideAvailable: boolean;
localLogin: boolean;
plexLogin: boolean;
newPlexLogin: boolean;
region: string;
originalLanguage: string;
@@ -112,6 +113,7 @@ interface FullPublicSettings extends PublicSettings {
applicationUrl: string;
hideAvailable: boolean;
localLogin: boolean;
plexLogin: boolean;
movie4kEnabled: boolean;
series4kEnabled: boolean;
region: string;
@@ -288,6 +290,7 @@ class Settings {
},
hideAvailable: false,
localLogin: true,
plexLogin: true,
newPlexLogin: true,
region: '',
originalLanguage: '',
@@ -480,6 +483,7 @@ class Settings {
applicationUrl: this.data.main.applicationUrl,
hideAvailable: this.data.main.hideAvailable,
localLogin: this.data.main.localLogin,
plexLogin: this.data.main.plexLogin,
movie4kEnabled: this.data.radarr.some(
(radarr) => radarr.is4k && radarr.isDefault
),

View File

@@ -30,11 +30,12 @@ authRoutes.post('/plex', async (req, res, next) => {
const userRepository = getRepository(User);
const body = req.body as { authToken?: string };
if (!body.authToken) {
return next({
status: 500,
message: 'Authentication token required.',
});
if (!settings.main.plexLogin) {
return res.status(500).json({ error: 'Plex sign-in is disabled.' });
} else if (!body.authToken) {
return res
.status(500)
.json({ error: 'You must provide an authentication token' });
}
try {
// First we need to use this auth token to get the user's email from plex.tv

View File

@@ -19,13 +19,19 @@ export interface AccordionChildProps {
type AccordionContentProps = {
isOpen: boolean;
children: React.ReactNode;
className?: string;
};
export const AccordionContent = ({
isOpen,
children,
className,
}: AccordionContentProps) => {
return <AnimateHeight height={isOpen ? 'auto' : 0}>{children}</AnimateHeight>;
return (
<AnimateHeight height={isOpen ? 'auto' : 0} className={className}>
{children}
</AnimateHeight>
);
};
const Accordion = ({

View File

@@ -18,7 +18,7 @@ const messages = defineMessages({
signin: 'Sign In',
signinheader: 'Sign in to continue',
signinwithplex: 'Use your Plex account',
signinwithoverseerr: 'Use your {applicationTitle} account',
signinwithoverseerr: 'Use your {applicationTitle} password',
});
const Login = () => {
@@ -67,6 +67,32 @@ const Login = () => {
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 (
<div className="relative flex min-h-screen flex-col bg-gray-900 py-14">
<PageTitle title={intl.formatMessage(messages.signin)} />
@@ -118,48 +144,41 @@ const Login = () => {
<Accordion single atLeastOne>
{({ openIndexes, handleClick, AccordionContent }) => (
<>
<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 focus:outline-none sm:rounded-t-lg ${
openIndexes.includes(0) && 'text-indigo-500'
} ${
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>
{signinMethods.map((signinMethod, index) => (
<div
key={`accordion-${index}`}
className="ring-1 ring-gray-700 sm:first:rounded-t-lg sm:last:rounded-b-lg"
>
<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 ${
openIndexes.includes(1)
className={`w-full cursor-default bg-gray-800 bg-opacity-70 py-2 text-center text-base font-bold focus:outline-none ${
index === 0 ? 'sm:rounded-t-lg' : ''
} ${
openIndexes.includes(index)
? '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, {
applicationTitle:
settings.currentSettings.applicationTitle,
})}
{signinMethod.buttonText}
</button>
<AccordionContent isOpen={openIndexes.includes(1)}>
<div className="px-10 py-8">
<LocalLogin revalidate={revalidate} />
</div>
<AccordionContent
isOpen={openIndexes.includes(index)}
className={
index < signinMethods.length - 1 ? 'mb-px' : ''
}
>
<div className="px-10 py-8">{signinMethod.content}</div>
</AccordionContent>
</div>
)}
))}
</>
)}
</Accordion>

View File

@@ -45,16 +45,14 @@ const NotificationType = ({
}
/>
</div>
<div className="ml-3 text-sm leading-6">
<label htmlFor={option.id} className="block">
<div className="flex flex-col">
<span className="font-medium text-white">{option.name}</span>
<span className="font-normal text-gray-400">
{option.description}
</span>
</div>
</label>
</div>
<label htmlFor={option.id} className="ml-3 flex flex-col text-sm">
<span className="font-semibold leading-6 text-white">
{option.name}
</span>
<span className="font-normal text-gray-400">
{option.description}
</span>
</label>
</div>
{(option.children ?? []).map((child) => (
<div key={`notification-type-child-${child.id}`} className="mt-4 pl-6">

View File

@@ -381,17 +381,15 @@ const NotificationTypeSelector = ({
{intl.formatMessage(messages.notificationTypes)}
{!user && <span className="label-required">*</span>}
</span>
<div className="form-input-area">
<div className="max-w-lg">
{availableTypes.map((type) => (
<NotificationType
key={`notification-type-${type.id}`}
option={type}
currentTypes={currentTypes}
onUpdate={onUpdate}
/>
))}
</div>
<div className="form-input-area max-w-xl">
{availableTypes.map((type) => (
<NotificationType
key={`notification-type-${type.id}`}
option={type}
currentTypes={currentTypes}
onUpdate={onUpdate}
/>
))}
{error && <div className="error">{error}</div>}
</div>
</div>

View File

@@ -122,16 +122,14 @@ const PermissionOption = ({
checked={checked}
/>
</div>
<div className="ml-3 text-sm leading-6">
<label htmlFor={option.id} className="block">
<div className="flex flex-col">
<span className="font-medium text-white">{option.name}</span>
<span className="font-normal text-gray-400">
{option.description}
</span>
</div>
</label>
</div>
<label htmlFor={option.id} className="ml-3 flex flex-col text-sm">
<span className="font-semibold leading-6 text-white">
{option.name}
</span>
<span className="font-normal text-gray-400">
{option.description}
</span>
</label>
</div>
{(option.children ?? []).map((child) => (
<div key={`permission-child-${child.id}`} className="mt-4 pl-10">

View File

@@ -50,7 +50,7 @@ const QuotaSelector = ({
}, [limitFieldName, onChange, quotaLimit]);
return (
<div className={`${isDisabled ? 'opacity-50' : ''}`}>
<span className={isDisabled ? 'opacity-50' : ''}>
{intl.formatMessage(
mediaType === 'movie' ? messages.movieRequests : messages.tvRequests,
{
@@ -97,7 +97,7 @@ const QuotaSelector = ({
},
}
)}
</div>
</span>
);
};

View File

@@ -3,6 +3,7 @@ import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import PageTitle from '@app/components/Common/PageTitle';
import PermissionEdit from '@app/components/PermissionEdit';
import QuotaSelector from '@app/components/QuotaSelector';
import useSettings from '@app/hooks/useSettings';
import globalMessages from '@app/i18n/globalMessages';
import { SaveIcon } from '@heroicons/react/outline';
import type { MainSettings } from '@server/lib/settings';
@@ -11,6 +12,7 @@ import { Field, Form, Formik } from 'formik';
import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
import useSWR, { mutate } from 'swr';
import * as Yup from 'yup';
const messages = defineMessages({
users: 'Users',
@@ -18,30 +20,62 @@ const messages = defineMessages({
userSettingsDescription: 'Configure global and default user settings.',
toastSettingsSuccess: 'User settings saved successfully!',
toastSettingsFailure: 'Something went wrong while saving settings.',
localLogin: 'Enable Local Sign-In',
localLoginTip:
'Allow users to sign in using their email address and password, instead of Plex OAuth',
newPlexLogin: 'Enable New Plex Sign-In',
newPlexLoginTip: 'Allow Plex users to sign in without first being imported',
newPlexLogin: 'Enable New Plex Sign-Ins',
newPlexLoginTip:
'Allow Plex users with access to the media server to sign in without being imported',
movieRequestLimitLabel: 'Global Movie Request Limit',
tvRequestLimitLabel: 'Global Series Request Limit',
defaultPermissions: 'Default Permissions',
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 { addToast } = useToasts();
const intl = useIntl();
const settings = useSettings();
const {
data,
error,
mutate: revalidate,
} = 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) {
return <LoadingSpinner />;
}
const allowPlexSigninDisable =
ownerData?.hasPassword &&
!!settings.currentSettings.applicationUrl &&
settings.currentSettings.emailEnabled;
return (
<>
<PageTitle
@@ -60,6 +94,7 @@ const SettingsUsers = () => {
<Formik
initialValues={{
localLogin: data?.localLogin,
plexLogin: data?.plexLogin,
newPlexLogin: data?.newPlexLogin,
movieQuotaLimit: data?.defaultQuotas.movie.quotaLimit ?? 0,
movieQuotaDays: data?.defaultQuotas.movie.quotaDays ?? 7,
@@ -68,10 +103,12 @@ const SettingsUsers = () => {
defaultPermissions: data?.defaultPermissions ?? 0,
}}
enableReinitialize
validationSchema={SettingsUsersSchema}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/settings/main', {
localLogin: values.localLogin,
plexLogin: values.plexLogin,
newPlexLogin: values.newPlexLogin,
defaultQuotas: {
movie: {
@@ -101,28 +138,82 @@ const SettingsUsers = () => {
}
}}
>
{({ isSubmitting, values, setFieldValue }) => {
{({
isSubmitting,
values,
setFieldValue,
errors,
touched,
isValid,
}) => {
return (
<Form className="section">
<div className="form-row">
<label htmlFor="localLogin" className="checkbox-label">
{intl.formatMessage(messages.localLogin)}
<span className="label-tip">
{intl.formatMessage(messages.localLoginTip)}
<div
role="group"
aria-labelledby="group-label"
className="form-group"
>
<div className="form-row">
<span id="group-label" className="group-label">
{intl.formatMessage(messages.signinMethods)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="localLogin"
name="localLogin"
onChange={() => {
setFieldValue('localLogin', !values.localLogin);
}}
/>
<div className="form-input-area max-w-xl space-y-1.5">
<div
className={`relative flex items-start ${
allowPlexSigninDisable ? '' : 'opacity-50'
}`}
>
<div className="flex h-6 items-center">
<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 className="form-row">
<div
className={`form-row ${values.plexLogin ? '' : 'opacity-50'}`}
>
<label htmlFor="newPlexLogin" className="checkbox-label">
{intl.formatMessage(messages.newPlexLogin)}
<span className="label-tip">
@@ -137,6 +228,8 @@ const SettingsUsers = () => {
onChange={() => {
setFieldValue('newPlexLogin', !values.newPlexLogin);
}}
checked={values.newPlexLogin && values.plexLogin}
disabled={!values.plexLogin}
/>
</div>
</div>
@@ -182,15 +275,13 @@ const SettingsUsers = () => {
{intl.formatMessage(messages.defaultPermissionsTip)}
</span>
</span>
<div className="form-input-area">
<div className="max-w-lg">
<PermissionEdit
currentPermission={values.defaultPermissions}
onUpdate={(newPermissions) =>
setFieldValue('defaultPermissions', newPermissions)
}
/>
</div>
<div className="form-input-area max-w-xl">
<PermissionEdit
currentPermission={values.defaultPermissions}
onUpdate={(newPermissions) =>
setFieldValue('defaultPermissions', newPermissions)
}
/>
</div>
</div>
</div>
@@ -200,7 +291,7 @@ const SettingsUsers = () => {
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting}
disabled={isSubmitting || !isValid}
>
<SaveIcon />
<span>

View File

@@ -44,7 +44,7 @@ const messages = defineMessages({
accounttype: 'Type',
role: 'Role',
created: 'Joined',
bulkedit: 'Bulk Edit',
bulkedit: 'Edit Permissions',
owner: 'Owner',
admin: 'Admin',
plexuser: 'Plex User',
@@ -54,9 +54,7 @@ const messages = defineMessages({
deleteconfirm:
'Are you sure you want to delete this user? All of their request data will be permanently removed.',
localuser: 'Local User',
createlocaluser: 'Create Local User',
creating: 'Creating…',
create: 'Create',
createlocaluser: 'Create User',
validationpasswordminchars:
'Password is too short; should be a minimum of 8 characters',
usercreatedfailed: 'Something went wrong while creating the user.',
@@ -75,7 +73,7 @@ const messages = defineMessages({
sortDisplayName: 'Display Name',
sortRequests: 'Request Count',
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';
@@ -317,8 +315,8 @@ const UserList = () => {
onOk={() => handleSubmit()}
okText={
isSubmitting
? intl.formatMessage(messages.creating)
: intl.formatMessage(messages.create)
? intl.formatMessage(globalMessages.creating)
: intl.formatMessage(globalMessages.create)
}
okDisabled={isSubmitting || !isValid}
okButtonType="primary"
@@ -327,6 +325,8 @@ const UserList = () => {
{!settings.currentSettings.localLogin && (
<Alert
title={intl.formatMessage(messages.localLoginDisabled, {
applicationTitle:
settings.currentSettings.applicationTitle,
strong: (msg: React.ReactNode) => (
<strong className="font-semibold text-white">
{msg}

View File

@@ -345,13 +345,13 @@ const UserGeneralSettings = () => {
</label>
<div className="form-input-area">
<div className="flex flex-col">
<div className="mb-4 flex items-center">
<div className="mb-1.5 flex items-center">
<input
type="checkbox"
checked={movieQuotaEnabled}
onChange={() => setMovieQuotaEnabled((s) => !s)}
/>
<span className="ml-2 text-gray-300">
<span className="ml-2 font-semibold text-gray-300">
{intl.formatMessage(messages.enableOverride)}
</span>
</div>
@@ -385,13 +385,13 @@ const UserGeneralSettings = () => {
</label>
<div className="form-input-area">
<div className="flex flex-col">
<div className="mb-4 flex items-center">
<div className="mb-1.5 flex items-center">
<input
type="checkbox"
checked={tvQuotaEnabled}
onChange={() => setTvQuotaEnabled((s) => !s)}
/>
<span className="ml-2 text-gray-300">
<span className="ml-2 font-semibold text-gray-300">
{intl.formatMessage(messages.enableOverride)}
</span>
</div>

View File

@@ -30,10 +30,9 @@ const messages = defineMessages({
'Password is too short; should be a minimum of 8 characters',
validationConfirmPassword: 'You must confirm the new password',
validationConfirmPasswordSame: 'Passwords must match',
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."',
noPasswordSet: 'This user account currently does not have a password set.',
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:
"You do not have permission to modify this user's password.",
});

View File

@@ -13,6 +13,7 @@ const defaultSettings = {
applicationUrl: '',
hideAvailable: false,
localLogin: true,
plexLogin: true,
movie4kEnabled: false,
series4kEnabled: false,
region: '',

View File

@@ -33,6 +33,8 @@ const globalMessages = defineMessages({
saving: 'Saving…',
import: 'Import',
importing: 'Importing…',
create: 'Create',
creating: 'Creating…',
close: 'Close',
edit: 'Edit',
areyousure: 'Are you sure?',

View File

@@ -133,7 +133,7 @@
"components.Login.signin": "Sign In",
"components.Login.signingin": "Signing In…",
"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.validationemailrequired": "You must provide a valid email address",
"components.Login.validationpasswordrequired": "You must provide a password",
@@ -682,17 +682,18 @@
"components.Settings.SettingsLogs.viewdetails": "View Details",
"components.Settings.SettingsUsers.defaultPermissions": "Default Permissions",
"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.newPlexLogin": "Enable New Plex Sign-In",
"components.Settings.SettingsUsers.newPlexLoginTip": "Allow Plex users to sign in without first being imported",
"components.Settings.SettingsUsers.newPlexLogin": "Enable New Plex Sign-Ins",
"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.toastSettingsSuccess": "User settings saved successfully!",
"components.Settings.SettingsUsers.tvRequestLimitLabel": "Global Series Request Limit",
"components.Settings.SettingsUsers.userSettings": "User Settings",
"components.Settings.SettingsUsers.userSettingsDescription": "Configure global and default user settings.",
"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.animeTags": "Anime Tags",
"components.Settings.SonarrModal.animelanguageprofile": "Anime Language Profile",
@@ -918,11 +919,9 @@
"components.UserList.admin": "Admin",
"components.UserList.autogeneratepassword": "Automatically Generate Password",
"components.UserList.autogeneratepasswordTip": "Email a server-generated password to the user",
"components.UserList.bulkedit": "Bulk Edit",
"components.UserList.create": "Create",
"components.UserList.bulkedit": "Edit Permissions",
"components.UserList.created": "Joined",
"components.UserList.createlocaluser": "Create Local User",
"components.UserList.creating": "Creating…",
"components.UserList.createlocaluser": "Create User",
"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.displayName": "Display Name",
@@ -931,7 +930,7 @@
"components.UserList.importedfromplex": "<strong>{userCount}</strong> Plex {userCount, plural, one {user} other {users}} imported successfully!",
"components.UserList.importfromplex": "Import 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.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.",
@@ -1027,8 +1026,8 @@
"components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "Confirm Password",
"components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "Current 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.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.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.",
"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.toastSettingsFailure": "Something went wrong while saving the password.",
@@ -1069,6 +1068,8 @@
"i18n.cancel": "Cancel",
"i18n.canceling": "Canceling…",
"i18n.close": "Close",
"i18n.create": "Create",
"i18n.creating": "Creating…",
"i18n.decline": "Decline",
"i18n.declined": "Declined",
"i18n.delete": "Delete",

View File

@@ -173,6 +173,7 @@ CoreApp.getInitialProps = async (initialProps) => {
movie4kEnabled: false,
series4kEnabled: false,
localLogin: true,
plexLogin: true,
region: '',
originalLanguage: '',
partialRequestsEnabled: true,