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', () => {
|
||||
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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -127,6 +127,9 @@ components:
|
||||
localLogin:
|
||||
type: boolean
|
||||
example: true
|
||||
plexLogin:
|
||||
type: boolean
|
||||
example: true
|
||||
newPlexLogin:
|
||||
type: boolean
|
||||
example: true
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface PublicSettingsResponse {
|
||||
applicationUrl: string;
|
||||
hideAvailable: boolean;
|
||||
localLogin: boolean;
|
||||
plexLogin: boolean;
|
||||
movie4kEnabled: boolean;
|
||||
series4kEnabled: boolean;
|
||||
region: string;
|
||||
|
||||
@@ -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
|
||||
),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = ({
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.",
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ const defaultSettings = {
|
||||
applicationUrl: '',
|
||||
hideAvailable: false,
|
||||
localLogin: true,
|
||||
plexLogin: true,
|
||||
movie4kEnabled: false,
|
||||
series4kEnabled: false,
|
||||
region: '',
|
||||
|
||||
@@ -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?',
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -173,6 +173,7 @@ CoreApp.getInitialProps = async (initialProps) => {
|
||||
movie4kEnabled: false,
|
||||
series4kEnabled: false,
|
||||
localLogin: true,
|
||||
plexLogin: true,
|
||||
region: '',
|
||||
originalLanguage: '',
|
||||
partialRequestsEnabled: true,
|
||||
|
||||
Reference in New Issue
Block a user