mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
@@ -58,6 +58,9 @@ components:
|
|||||||
applicationUrl:
|
applicationUrl:
|
||||||
type: string
|
type: string
|
||||||
example: https://os.example.com
|
example: https://os.example.com
|
||||||
|
defaultPermissions:
|
||||||
|
type: number
|
||||||
|
example: 32
|
||||||
PlexLibrary:
|
PlexLibrary:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@@ -2,6 +2,7 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { merge } from 'lodash';
|
import { merge } from 'lodash';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { Permission } from './permissions';
|
||||||
|
|
||||||
export interface Library {
|
export interface Library {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -47,6 +48,7 @@ export interface SonarrSettings extends DVRSettings {
|
|||||||
export interface MainSettings {
|
export interface MainSettings {
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
applicationUrl: string;
|
applicationUrl: string;
|
||||||
|
defaultPermissions: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PublicSettings {
|
interface PublicSettings {
|
||||||
@@ -105,6 +107,7 @@ class Settings {
|
|||||||
main: {
|
main: {
|
||||||
apiKey: '',
|
apiKey: '',
|
||||||
applicationUrl: '',
|
applicationUrl: '',
|
||||||
|
defaultPermissions: Permission.REQUEST,
|
||||||
},
|
},
|
||||||
plex: {
|
plex: {
|
||||||
name: '',
|
name: '',
|
||||||
|
@@ -5,6 +5,7 @@ import PlexTvAPI from '../api/plextv';
|
|||||||
import { isAuthenticated } from '../middleware/auth';
|
import { isAuthenticated } from '../middleware/auth';
|
||||||
import { Permission } from '../lib/permissions';
|
import { Permission } from '../lib/permissions';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
|
import { getSettings } from '../lib/settings';
|
||||||
|
|
||||||
const authRoutes = Router();
|
const authRoutes = Router();
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ authRoutes.get('/me', isAuthenticated(), async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
authRoutes.post('/login', async (req, res, next) => {
|
authRoutes.post('/login', async (req, res, next) => {
|
||||||
|
const settings = getSettings();
|
||||||
const userRepository = getRepository(User);
|
const userRepository = getRepository(User);
|
||||||
const body = req.body as { authToken?: string };
|
const body = req.body as { authToken?: string };
|
||||||
|
|
||||||
@@ -82,7 +84,7 @@ authRoutes.post('/login', async (req, res, next) => {
|
|||||||
username: account.username,
|
username: account.username,
|
||||||
plexId: account.id,
|
plexId: account.id,
|
||||||
plexToken: account.authToken,
|
plexToken: account.authToken,
|
||||||
permissions: Permission.REQUEST,
|
permissions: settings.main.defaultPermissions,
|
||||||
avatar: account.thumb,
|
avatar: account.thumb,
|
||||||
});
|
});
|
||||||
await userRepository.save(user);
|
await userRepository.save(user);
|
||||||
|
@@ -16,7 +16,7 @@ import logger from '../logger';
|
|||||||
import { scheduledJobs } from '../job/schedule';
|
import { scheduledJobs } from '../job/schedule';
|
||||||
import { Permission } from '../lib/permissions';
|
import { Permission } from '../lib/permissions';
|
||||||
import { isAuthenticated } from '../middleware/auth';
|
import { isAuthenticated } from '../middleware/auth';
|
||||||
import { merge } from 'lodash';
|
import { merge, omit } from 'lodash';
|
||||||
import Media from '../entity/Media';
|
import Media from '../entity/Media';
|
||||||
import { MediaRequest } from '../entity/MediaRequest';
|
import { MediaRequest } from '../entity/MediaRequest';
|
||||||
import { getAppVersion } from '../utils/appVersion';
|
import { getAppVersion } from '../utils/appVersion';
|
||||||
@@ -32,9 +32,7 @@ const filteredMainSettings = (
|
|||||||
main: MainSettings
|
main: MainSettings
|
||||||
): Partial<MainSettings> => {
|
): Partial<MainSettings> => {
|
||||||
if (!user?.hasPermission(Permission.ADMIN)) {
|
if (!user?.hasPermission(Permission.ADMIN)) {
|
||||||
return {
|
return omit(main, 'apiKey');
|
||||||
applicationUrl: main.applicationUrl,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return main;
|
return main;
|
||||||
|
@@ -9,6 +9,8 @@ import Button from '../Common/Button';
|
|||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { useUser, Permission } from '../../hooks/useUser';
|
import { useUser, Permission } from '../../hooks/useUser';
|
||||||
import { useToasts } from 'react-toast-notifications';
|
import { useToasts } from 'react-toast-notifications';
|
||||||
|
import { messages as permissionMessages } from '../UserEdit';
|
||||||
|
import { hasPermission } from '../../../server/lib/permissions';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
generalsettings: 'General Settings',
|
generalsettings: 'General Settings',
|
||||||
@@ -22,11 +24,19 @@ const messages = defineMessages({
|
|||||||
toastApiKeyFailure: 'Something went wrong generating a new API Key.',
|
toastApiKeyFailure: 'Something went wrong generating a new API Key.',
|
||||||
toastSettingsSuccess: 'Settings saved.',
|
toastSettingsSuccess: 'Settings saved.',
|
||||||
toastSettingsFailure: 'Something went wrong saving settings.',
|
toastSettingsFailure: 'Something went wrong saving settings.',
|
||||||
|
defaultPermissions: 'Default User Permissions',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
interface PermissionOption {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
permission: Permission;
|
||||||
|
}
|
||||||
|
|
||||||
const SettingsMain: React.FC = () => {
|
const SettingsMain: React.FC = () => {
|
||||||
const { addToast } = useToasts();
|
const { addToast } = useToasts();
|
||||||
const { hasPermission } = useUser();
|
const { hasPermission: userHasPermission } = useUser();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { data, error, revalidate } = useSWR<MainSettings>(
|
const { data, error, revalidate } = useSWR<MainSettings>(
|
||||||
'/api/v1/settings/main'
|
'/api/v1/settings/main'
|
||||||
@@ -53,13 +63,62 @@ const SettingsMain: React.FC = () => {
|
|||||||
return <LoadingSpinner />;
|
return <LoadingSpinner />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const permissionList: PermissionOption[] = [
|
||||||
|
{
|
||||||
|
id: 'admin',
|
||||||
|
name: intl.formatMessage(permissionMessages.admin),
|
||||||
|
description: intl.formatMessage(permissionMessages.adminDescription),
|
||||||
|
permission: Permission.ADMIN,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'settings',
|
||||||
|
name: intl.formatMessage(permissionMessages.settings),
|
||||||
|
description: intl.formatMessage(permissionMessages.settingsDescription),
|
||||||
|
permission: Permission.MANAGE_SETTINGS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'users',
|
||||||
|
name: intl.formatMessage(permissionMessages.users),
|
||||||
|
description: intl.formatMessage(permissionMessages.usersDescription),
|
||||||
|
permission: Permission.MANAGE_USERS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'managerequest',
|
||||||
|
name: intl.formatMessage(permissionMessages.managerequests),
|
||||||
|
description: intl.formatMessage(
|
||||||
|
permissionMessages.managerequestsDescription
|
||||||
|
),
|
||||||
|
permission: Permission.MANAGE_REQUESTS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'request',
|
||||||
|
name: intl.formatMessage(permissionMessages.request),
|
||||||
|
description: intl.formatMessage(permissionMessages.requestDescription),
|
||||||
|
permission: Permission.REQUEST,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'vote',
|
||||||
|
name: intl.formatMessage(permissionMessages.vote),
|
||||||
|
description: intl.formatMessage(permissionMessages.voteDescription),
|
||||||
|
permission: Permission.VOTE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'autoapprove',
|
||||||
|
name: intl.formatMessage(permissionMessages.autoapprove),
|
||||||
|
description: intl.formatMessage(
|
||||||
|
permissionMessages.autoapproveDescription
|
||||||
|
),
|
||||||
|
permission: Permission.AUTO_APPROVE,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg leading-6 font-medium text-gray-200">
|
<h3 className="text-lg font-medium leading-6 text-gray-200">
|
||||||
{intl.formatMessage(messages.generalsettings)}
|
{intl.formatMessage(messages.generalsettings)}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="mt-1 max-w-2xl text-sm leading-5 text-gray-500">
|
<p className="max-w-2xl mt-1 text-sm leading-5 text-gray-500">
|
||||||
{intl.formatMessage(messages.generalsettingsDescription)}
|
{intl.formatMessage(messages.generalsettingsDescription)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -67,11 +126,14 @@ const SettingsMain: React.FC = () => {
|
|||||||
<Formik
|
<Formik
|
||||||
initialValues={{
|
initialValues={{
|
||||||
applicationUrl: data?.applicationUrl,
|
applicationUrl: data?.applicationUrl,
|
||||||
|
defaultPermissions: data?.defaultPermissions ?? 0,
|
||||||
}}
|
}}
|
||||||
|
enableReinitialize
|
||||||
onSubmit={async (values) => {
|
onSubmit={async (values) => {
|
||||||
try {
|
try {
|
||||||
await axios.post('/api/v1/settings/main', {
|
await axios.post('/api/v1/settings/main', {
|
||||||
applicationUrl: values.applicationUrl,
|
applicationUrl: values.applicationUrl,
|
||||||
|
defaultPermissions: values.defaultPermissions,
|
||||||
});
|
});
|
||||||
|
|
||||||
addToast(intl.formatMessage(messages.toastSettingsSuccess), {
|
addToast(intl.formatMessage(messages.toastSettingsSuccess), {
|
||||||
@@ -88,10 +150,10 @@ const SettingsMain: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ isSubmitting }) => {
|
{({ isSubmitting, values, setFieldValue }) => {
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
{hasPermission(Permission.ADMIN) && (
|
{userHasPermission(Permission.ADMIN) && (
|
||||||
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800 sm:pt-5">
|
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800 sm:pt-5">
|
||||||
<label
|
<label
|
||||||
htmlFor="username"
|
htmlFor="username"
|
||||||
@@ -100,11 +162,11 @@ const SettingsMain: React.FC = () => {
|
|||||||
{intl.formatMessage(messages.apikey)}
|
{intl.formatMessage(messages.apikey)}
|
||||||
</label>
|
</label>
|
||||||
<div className="mt-1 sm:mt-0 sm:col-span-2">
|
<div className="mt-1 sm:mt-0 sm:col-span-2">
|
||||||
<div className="max-w-lg flex rounded-md shadow-sm">
|
<div className="flex max-w-lg rounded-md shadow-sm">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="apiKey"
|
id="apiKey"
|
||||||
className="flex-1 form-input block w-full min-w-0 rounded-none rounded-l-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500"
|
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-none form-input rounded-l-md sm:text-sm sm:leading-5"
|
||||||
value={data?.apiKey}
|
value={data?.apiKey}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
@@ -117,7 +179,7 @@ const SettingsMain: React.FC = () => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
regenerate();
|
regenerate();
|
||||||
}}
|
}}
|
||||||
className="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-500 text-sm leading-5 font-medium rounded-r-md text-white bg-indigo-500 hover:bg-indigo-400 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150"
|
className="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium leading-5 text-white transition duration-150 ease-in-out bg-indigo-500 border border-gray-500 rounded-r-md hover:bg-indigo-400 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
className="w-5 h-5"
|
className="w-5 h-5"
|
||||||
@@ -144,20 +206,98 @@ const SettingsMain: React.FC = () => {
|
|||||||
{intl.formatMessage(messages.applicationurl)}
|
{intl.formatMessage(messages.applicationurl)}
|
||||||
</label>
|
</label>
|
||||||
<div className="mt-1 sm:mt-0 sm:col-span-2">
|
<div className="mt-1 sm:mt-0 sm:col-span-2">
|
||||||
<div className="max-w-lg flex rounded-md shadow-sm">
|
<div className="flex max-w-lg rounded-md shadow-sm">
|
||||||
<Field
|
<Field
|
||||||
id="applicationUrl"
|
id="applicationUrl"
|
||||||
name="applicationUrl"
|
name="applicationUrl"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="https://os.example.com"
|
placeholder="https://os.example.com"
|
||||||
className="flex-1 form-input block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500"
|
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-8 border-t border-gray-700 pt-5">
|
<div className="mt-6">
|
||||||
|
<div role="group" aria-labelledby="label-permissions">
|
||||||
|
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline">
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
className="text-base font-medium leading-6 text-gray-400 sm:text-sm sm:leading-5"
|
||||||
|
id="label-permissions"
|
||||||
|
>
|
||||||
|
{intl.formatMessage(messages.defaultPermissions)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 sm:mt-0 sm:col-span-2">
|
||||||
|
<div className="max-w-lg">
|
||||||
|
{permissionList.map((permissionOption) => (
|
||||||
|
<div
|
||||||
|
className={`relative flex items-start first:mt-0 mt-4 ${
|
||||||
|
permissionOption.permission !==
|
||||||
|
Permission.ADMIN &&
|
||||||
|
hasPermission(
|
||||||
|
Permission.ADMIN,
|
||||||
|
values.defaultPermissions
|
||||||
|
)
|
||||||
|
? 'opacity-50'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
key={`permission-option-${permissionOption.id}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center h-5">
|
||||||
|
<input
|
||||||
|
id={permissionOption.id}
|
||||||
|
name="permissions"
|
||||||
|
type="checkbox"
|
||||||
|
className="w-4 h-4 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
|
||||||
|
disabled={
|
||||||
|
permissionOption.permission !==
|
||||||
|
Permission.ADMIN &&
|
||||||
|
hasPermission(
|
||||||
|
Permission.ADMIN,
|
||||||
|
values.defaultPermissions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
setFieldValue(
|
||||||
|
'defaultPermissions',
|
||||||
|
hasPermission(
|
||||||
|
permissionOption.permission,
|
||||||
|
values.defaultPermissions
|
||||||
|
)
|
||||||
|
? values.defaultPermissions -
|
||||||
|
permissionOption.permission
|
||||||
|
: values.defaultPermissions +
|
||||||
|
permissionOption.permission
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
checked={hasPermission(
|
||||||
|
permissionOption.permission,
|
||||||
|
values.defaultPermissions
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="ml-3 text-sm leading-5">
|
||||||
|
<label
|
||||||
|
htmlFor={permissionOption.id}
|
||||||
|
className="font-medium"
|
||||||
|
>
|
||||||
|
{permissionOption.name}
|
||||||
|
</label>
|
||||||
|
<p className="text-gray-500">
|
||||||
|
{permissionOption.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="pt-5 mt-8 border-t border-gray-700">
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
<span className="inline-flex ml-3 rounded-md shadow-sm">
|
||||||
<Button
|
<Button
|
||||||
buttonType="primary"
|
buttonType="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
|
@@ -9,7 +9,7 @@ import axios from 'axios';
|
|||||||
import { useToasts } from 'react-toast-notifications';
|
import { useToasts } from 'react-toast-notifications';
|
||||||
import Header from '../Common/Header';
|
import Header from '../Common/Header';
|
||||||
|
|
||||||
const messages = defineMessages({
|
export const messages = defineMessages({
|
||||||
edituser: 'Edit User',
|
edituser: 'Edit User',
|
||||||
username: 'Username',
|
username: 'Username',
|
||||||
avatar: 'Avatar',
|
avatar: 'Avatar',
|
||||||
@@ -148,7 +148,7 @@ const UserEdit: React.FC = () => {
|
|||||||
<FormattedMessage {...messages.edituser} />
|
<FormattedMessage {...messages.edituser} />
|
||||||
</Header>
|
</Header>
|
||||||
<div className="px-4 space-y-6 sm:p-6 lg:pb-8">
|
<div className="px-4 space-y-6 sm:p-6 lg:pb-8">
|
||||||
<div className="flex flex-col space-y-6 lg:flex-row lg:space-y-0 lg:space-x-6 text-white">
|
<div className="flex flex-col space-y-6 text-white lg:flex-row lg:space-y-0 lg:space-x-6">
|
||||||
<div className="flex-grow space-y-6">
|
<div className="flex-grow space-y-6">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label
|
<label
|
||||||
@@ -157,11 +157,11 @@ const UserEdit: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<FormattedMessage {...messages.username} />
|
<FormattedMessage {...messages.username} />
|
||||||
</label>
|
</label>
|
||||||
<div className="rounded-md shadow-sm flex">
|
<div className="flex rounded-md shadow-sm">
|
||||||
<input
|
<input
|
||||||
id="username"
|
id="username"
|
||||||
type="text"
|
type="text"
|
||||||
className="form-input flex-grow block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500"
|
className="flex-grow block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
|
||||||
value={user?.username}
|
value={user?.username}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
@@ -174,11 +174,11 @@ const UserEdit: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<FormattedMessage {...messages.email} />
|
<FormattedMessage {...messages.email} />
|
||||||
</label>
|
</label>
|
||||||
<div className="rounded-md shadow-sm flex">
|
<div className="flex rounded-md shadow-sm">
|
||||||
<input
|
<input
|
||||||
id="email"
|
id="email"
|
||||||
type="text"
|
type="text"
|
||||||
className="form-input flex-grow block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500"
|
className="flex-grow block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
|
||||||
value={user?.email}
|
value={user?.email}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
@@ -188,7 +188,7 @@ const UserEdit: React.FC = () => {
|
|||||||
|
|
||||||
<div className="flex-grow space-y-1 lg:flex-grow-0 lg:flex-shrink-0">
|
<div className="flex-grow space-y-1 lg:flex-grow-0 lg:flex-shrink-0">
|
||||||
<p
|
<p
|
||||||
className="block text-sm leading-5 font-medium text-gray-400"
|
className="block text-sm font-medium leading-5 text-gray-400"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<FormattedMessage {...messages.avatar} />
|
<FormattedMessage {...messages.avatar} />
|
||||||
@@ -196,11 +196,11 @@ const UserEdit: React.FC = () => {
|
|||||||
<div className="lg:hidden">
|
<div className="lg:hidden">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div
|
<div
|
||||||
className="flex-shrink-0 inline-block rounded-full overflow-hidden h-12 w-12"
|
className="flex-shrink-0 inline-block w-12 h-12 overflow-hidden rounded-full"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
className="rounded-full h-full w-full"
|
className="w-full h-full rounded-full"
|
||||||
src={user?.avatar}
|
src={user?.avatar}
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
@@ -208,9 +208,9 @@ const UserEdit: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="hidden relative rounded-full overflow-hidden lg:block transition duration-150 ease-in-out">
|
<div className="relative hidden overflow-hidden transition duration-150 ease-in-out rounded-full lg:block">
|
||||||
<img
|
<img
|
||||||
className="relative rounded-full w-40 h-40"
|
className="relative w-40 h-40 rounded-full"
|
||||||
src={user?.avatar}
|
src={user?.avatar}
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
@@ -223,7 +223,7 @@ const UserEdit: React.FC = () => {
|
|||||||
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline">
|
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline">
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
className="text-base leading-6 font-medium sm:text-sm sm:leading-5"
|
className="text-base font-medium leading-6 sm:text-sm sm:leading-5"
|
||||||
id="label-permissions"
|
id="label-permissions"
|
||||||
>
|
>
|
||||||
<FormattedMessage {...messages.permissions} />
|
<FormattedMessage {...messages.permissions} />
|
||||||
@@ -254,7 +254,7 @@ const UserEdit: React.FC = () => {
|
|||||||
id={permissionOption.id}
|
id={permissionOption.id}
|
||||||
name="permissions"
|
name="permissions"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="form-checkbox h-4 w-4 rounded-md text-indigo-600 transition duration-150 ease-in-out"
|
className="w-4 h-4 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
|
||||||
disabled={
|
disabled={
|
||||||
(permissionOption.permission !==
|
(permissionOption.permission !==
|
||||||
Permission.ADMIN &&
|
Permission.ADMIN &&
|
||||||
@@ -305,9 +305,9 @@ const UserEdit: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-8 border-t border-gray-700 pt-5">
|
<div className="pt-5 mt-8 border-t border-gray-700">
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
<span className="inline-flex ml-3 rounded-md shadow-sm">
|
||||||
<Button
|
<Button
|
||||||
buttonType="primary"
|
buttonType="primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
|
@@ -214,6 +214,7 @@
|
|||||||
"components.Settings.currentlibrary": "Current Library: {name}",
|
"components.Settings.currentlibrary": "Current Library: {name}",
|
||||||
"components.Settings.default": "Default",
|
"components.Settings.default": "Default",
|
||||||
"components.Settings.default4k": "Default 4K",
|
"components.Settings.default4k": "Default 4K",
|
||||||
|
"components.Settings.defaultPermissions": "Default User Permissions",
|
||||||
"components.Settings.delete": "Delete",
|
"components.Settings.delete": "Delete",
|
||||||
"components.Settings.deleteserverconfirm": "Are you sure you want to delete this server?",
|
"components.Settings.deleteserverconfirm": "Are you sure you want to delete this server?",
|
||||||
"components.Settings.edit": "Edit",
|
"components.Settings.edit": "Edit",
|
||||||
|
Reference in New Issue
Block a user