mirror of
https://github.com/sct/overseerr.git
synced 2025-12-29 00:56:55 +01:00
Merge remote-tracking branch 'overseerr/develop' into develop
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import { PencilIcon } from '@heroicons/react/solid';
|
||||
import Modal from '@app/components/Common/Modal';
|
||||
import PermissionEdit from '@app/components/PermissionEdit';
|
||||
import type { User } from '@app/hooks/useUser';
|
||||
import { useUser } from '@app/hooks/useUser';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import axios from 'axios';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import { User, useUser } from '../../hooks/useUser';
|
||||
import globalMessages from '../../i18n/globalMessages';
|
||||
import Modal from '../Common/Modal';
|
||||
import PermissionEdit from '../PermissionEdit';
|
||||
|
||||
interface BulkEditProps {
|
||||
selectedUserIds: number[];
|
||||
@@ -22,13 +22,13 @@ const messages = defineMessages({
|
||||
edituser: 'Edit User Permissions',
|
||||
});
|
||||
|
||||
const BulkEditModal: React.FC<BulkEditProps> = ({
|
||||
const BulkEditModal = ({
|
||||
selectedUserIds,
|
||||
users,
|
||||
onCancel,
|
||||
onComplete,
|
||||
onSaving,
|
||||
}) => {
|
||||
}: BulkEditProps) => {
|
||||
const { user: currentUser } = useUser();
|
||||
const intl = useIntl();
|
||||
const { addToast } = useToasts();
|
||||
@@ -85,7 +85,6 @@ const BulkEditModal: React.FC<BulkEditProps> = ({
|
||||
return (
|
||||
<Modal
|
||||
title={intl.formatMessage(messages.edituser)}
|
||||
iconSvg={<PencilIcon />}
|
||||
onOk={() => {
|
||||
updateUsers();
|
||||
}}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { InboxInIcon } from '@heroicons/react/solid';
|
||||
import Alert from '@app/components/Common/Alert';
|
||||
import Modal from '@app/components/Common/Modal';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import type { UserResultsResponse } from '@server/interfaces/api/userInterfaces';
|
||||
import axios from 'axios';
|
||||
import React, { useState } from 'react';
|
||||
import getConfig from 'next/config';
|
||||
import type React from 'react';
|
||||
import { useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR from 'swr';
|
||||
import useSettings from '../../hooks/useSettings';
|
||||
import globalMessages from '../../i18n/globalMessages';
|
||||
import Alert from '../Common/Alert';
|
||||
import Modal from '../Common/Modal';
|
||||
import getConfig from 'next/config';
|
||||
import { UserResultsResponse } from '../../../server/interfaces/api/userInterfaces';
|
||||
|
||||
interface JellyfinImportProps {
|
||||
onCancel?: () => void;
|
||||
onComplete?: () => void;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
@@ -79,9 +80,7 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
|
||||
addToast(
|
||||
intl.formatMessage(messages.importedfromJellyfin, {
|
||||
userCount: createdUsers.length,
|
||||
strong: function strong(msg) {
|
||||
return <strong>{msg}</strong>;
|
||||
},
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
mediaServerName:
|
||||
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
|
||||
}),
|
||||
@@ -138,7 +137,6 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
|
||||
mediaServerName:
|
||||
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
|
||||
})}
|
||||
iconSvg={<InboxInIcon />}
|
||||
onOk={() => {
|
||||
importUsers();
|
||||
}}
|
||||
@@ -157,11 +155,9 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
|
||||
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? 'Emby'
|
||||
: 'Jellyfin',
|
||||
strong: function strong(msg) {
|
||||
return (
|
||||
<strong className="font-semibold text-white">{msg}</strong>
|
||||
);
|
||||
},
|
||||
strong: (msg: React.ReactNode) => (
|
||||
<strong className="font-semibold text-white">{msg}</strong>
|
||||
),
|
||||
})}
|
||||
type="info"
|
||||
/>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { InboxInIcon } from '@heroicons/react/solid';
|
||||
import Alert from '@app/components/Common/Alert';
|
||||
import Modal from '@app/components/Common/Modal';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import axios from 'axios';
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR from 'swr';
|
||||
import useSettings from '../../hooks/useSettings';
|
||||
import globalMessages from '../../i18n/globalMessages';
|
||||
import Alert from '../Common/Alert';
|
||||
import Modal from '../Common/Modal';
|
||||
|
||||
interface PlexImportProps {
|
||||
onCancel?: () => void;
|
||||
@@ -25,10 +24,7 @@ const messages = defineMessages({
|
||||
'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.',
|
||||
});
|
||||
|
||||
const PlexImportModal: React.FC<PlexImportProps> = ({
|
||||
onCancel,
|
||||
onComplete,
|
||||
}) => {
|
||||
const PlexImportModal = ({ onCancel, onComplete }: PlexImportProps) => {
|
||||
const intl = useIntl();
|
||||
const settings = useSettings();
|
||||
const { addToast } = useToasts();
|
||||
@@ -62,9 +58,7 @@ const PlexImportModal: React.FC<PlexImportProps> = ({
|
||||
addToast(
|
||||
intl.formatMessage(messages.importedfromplex, {
|
||||
userCount: createdUsers.length,
|
||||
strong: function strong(msg) {
|
||||
return <strong>{msg}</strong>;
|
||||
},
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
}),
|
||||
{
|
||||
autoDismiss: true,
|
||||
@@ -110,7 +104,6 @@ const PlexImportModal: React.FC<PlexImportProps> = ({
|
||||
<Modal
|
||||
loading={!data && !error}
|
||||
title={intl.formatMessage(messages.importfromplex)}
|
||||
iconSvg={<InboxInIcon />}
|
||||
onOk={() => {
|
||||
importUsers();
|
||||
}}
|
||||
@@ -125,11 +118,9 @@ const PlexImportModal: React.FC<PlexImportProps> = ({
|
||||
{settings.currentSettings.newPlexLogin && (
|
||||
<Alert
|
||||
title={intl.formatMessage(messages.newplexsigninenabled, {
|
||||
strong: function strong(msg) {
|
||||
return (
|
||||
<strong className="font-semibold text-white">{msg}</strong>
|
||||
);
|
||||
},
|
||||
strong: (msg: React.ReactNode) => (
|
||||
<strong className="font-semibold text-white">{msg}</strong>
|
||||
),
|
||||
})}
|
||||
type="info"
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
import { TrashIcon } from '@heroicons/react/outline';
|
||||
import Alert from '@app/components/Common/Alert';
|
||||
import Badge from '@app/components/Common/Badge';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import Header from '@app/components/Common/Header';
|
||||
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
||||
import Modal from '@app/components/Common/Modal';
|
||||
import PageTitle from '@app/components/Common/PageTitle';
|
||||
import SensitiveInput from '@app/components/Common/SensitiveInput';
|
||||
import Table from '@app/components/Common/Table';
|
||||
import BulkEditModal from '@app/components/UserList/BulkEditModal';
|
||||
import PlexImportModal from '@app/components/UserList/PlexImportModal';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams';
|
||||
import type { User } from '@app/hooks/useUser';
|
||||
import { Permission, UserType, useUser } from '@app/hooks/useUser';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
@@ -7,36 +23,20 @@ import {
|
||||
SortDescendingIcon,
|
||||
UserAddIcon,
|
||||
} from '@heroicons/react/solid';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import type { UserResultsResponse } from '@server/interfaces/api/userInterfaces';
|
||||
import { hasPermission } from '@server/lib/permissions';
|
||||
import axios from 'axios';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import getConfig from 'next/config';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR from 'swr';
|
||||
import * as Yup from 'yup';
|
||||
import { MediaServerType } from '../../../server/constants/server';
|
||||
import type { UserResultsResponse } from '../../../server/interfaces/api/userInterfaces';
|
||||
import { hasPermission } from '../../../server/lib/permissions';
|
||||
import useSettings from '../../hooks/useSettings';
|
||||
import { useUpdateQueryParams } from '../../hooks/useUpdateQueryParams';
|
||||
import { Permission, User, UserType, useUser } from '../../hooks/useUser';
|
||||
import globalMessages from '../../i18n/globalMessages';
|
||||
import Alert from '../Common/Alert';
|
||||
import Badge from '../Common/Badge';
|
||||
import Button from '../Common/Button';
|
||||
import Header from '../Common/Header';
|
||||
import LoadingSpinner from '../Common/LoadingSpinner';
|
||||
import Modal from '../Common/Modal';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
import SensitiveInput from '../Common/SensitiveInput';
|
||||
import Table from '../Common/Table';
|
||||
import Transition from '../Transition';
|
||||
import BulkEditModal from './BulkEditModal';
|
||||
import JellyfinImportModal from './JellyfinImportModal';
|
||||
import PlexImportModal from './PlexImportModal';
|
||||
|
||||
const messages = defineMessages({
|
||||
users: 'Users',
|
||||
@@ -84,7 +84,7 @@ const messages = defineMessages({
|
||||
|
||||
type Sort = 'created' | 'updated' | 'requests' | 'displayname';
|
||||
|
||||
const UserList: React.FC = () => {
|
||||
const UserList = () => {
|
||||
const intl = useIntl();
|
||||
const router = useRouter();
|
||||
const settings = useSettings();
|
||||
@@ -187,7 +187,7 @@ const UserList: React.FC = () => {
|
||||
autoDismiss: true,
|
||||
appearance: 'success',
|
||||
});
|
||||
setDeleteModal({ isOpen: false });
|
||||
setDeleteModal({ isOpen: false, user: deleteModal.user });
|
||||
} catch (e) {
|
||||
addToast(intl.formatMessage(messages.userdeleteerror), {
|
||||
autoDismiss: true,
|
||||
@@ -232,6 +232,7 @@ const UserList: React.FC = () => {
|
||||
<>
|
||||
<PageTitle title={intl.formatMessage(messages.users)} />
|
||||
<Transition
|
||||
as="div"
|
||||
enter="opacity-0 transition duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
@@ -249,15 +250,18 @@ const UserList: React.FC = () => {
|
||||
}
|
||||
okDisabled={isDeleting}
|
||||
okButtonType="danger"
|
||||
onCancel={() => setDeleteModal({ isOpen: false })}
|
||||
onCancel={() =>
|
||||
setDeleteModal({ isOpen: false, user: deleteModal.user })
|
||||
}
|
||||
title={intl.formatMessage(messages.deleteuser)}
|
||||
iconSvg={<TrashIcon />}
|
||||
subTitle={deleteModal.user?.displayName}
|
||||
>
|
||||
{intl.formatMessage(messages.deleteconfirm)}
|
||||
</Modal>
|
||||
</Transition>
|
||||
|
||||
<Transition
|
||||
as="div"
|
||||
enter="opacity-0 transition duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
@@ -315,7 +319,6 @@ const UserList: React.FC = () => {
|
||||
return (
|
||||
<Modal
|
||||
title={intl.formatMessage(messages.createlocaluser)}
|
||||
iconSvg={<UserAddIcon />}
|
||||
onOk={() => handleSubmit()}
|
||||
okText={
|
||||
isSubmitting
|
||||
@@ -329,18 +332,16 @@ const UserList: React.FC = () => {
|
||||
{!settings.currentSettings.localLogin && (
|
||||
<Alert
|
||||
title={intl.formatMessage(messages.localLoginDisabled, {
|
||||
strong: function strong(msg) {
|
||||
return (
|
||||
<strong className="font-semibold text-white">
|
||||
{msg}
|
||||
</strong>
|
||||
);
|
||||
},
|
||||
strong: (msg: React.ReactNode) => (
|
||||
<strong className="font-semibold text-white">
|
||||
{msg}
|
||||
</strong>
|
||||
),
|
||||
})}
|
||||
type="warning"
|
||||
/>
|
||||
)}
|
||||
{currentHasPermission(Permission.MANAGE_SETTINGS) &&
|
||||
{currentHasPermission(Permission.ADMIN) &&
|
||||
!passwordGenerationEnabled && (
|
||||
<Alert
|
||||
title={intl.formatMessage(
|
||||
@@ -378,9 +379,11 @@ const UserList: React.FC = () => {
|
||||
inputMode="email"
|
||||
/>
|
||||
</div>
|
||||
{errors.email && touched.email && (
|
||||
<div className="error">{errors.email}</div>
|
||||
)}
|
||||
{errors.email &&
|
||||
touched.email &&
|
||||
typeof errors.email === 'string' && (
|
||||
<div className="error">{errors.email}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -426,9 +429,11 @@ const UserList: React.FC = () => {
|
||||
disabled={values.genpassword}
|
||||
/>
|
||||
</div>
|
||||
{errors.password && touched.password && (
|
||||
<div className="error">{errors.password}</div>
|
||||
)}
|
||||
{errors.password &&
|
||||
touched.password &&
|
||||
typeof errors.password === 'string' && (
|
||||
<div className="error">{errors.password}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
@@ -439,6 +444,7 @@ const UserList: React.FC = () => {
|
||||
</Transition>
|
||||
|
||||
<Transition
|
||||
as="div"
|
||||
enter="opacity-0 transition duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
@@ -459,6 +465,7 @@ const UserList: React.FC = () => {
|
||||
</Transition>
|
||||
|
||||
<Transition
|
||||
as="div"
|
||||
enter="opacity-0 transition duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
@@ -586,7 +593,7 @@ const UserList: React.FC = () => {
|
||||
</thead>
|
||||
<Table.TBody>
|
||||
{data?.results.map((user) => (
|
||||
<tr key={`user-list-${user.id}`}>
|
||||
<tr key={`user-list-${user.id}`} data-testid="user-list-row">
|
||||
<Table.TD>
|
||||
{isUserPermsEditable(user.id) && (
|
||||
<input
|
||||
@@ -613,7 +620,10 @@ const UserList: React.FC = () => {
|
||||
</Link>
|
||||
<div className="ml-4">
|
||||
<Link href={`/users/${user.id}`}>
|
||||
<a className="text-base font-bold leading-5 transition duration-300 hover:underline">
|
||||
<a
|
||||
className="text-base font-bold leading-5 transition duration-300 hover:underline"
|
||||
data-testid="user-list-username-link"
|
||||
>
|
||||
{user.displayName}
|
||||
</a>
|
||||
</Link>
|
||||
@@ -721,9 +731,9 @@ const UserList: React.FC = () => {
|
||||
? pageIndex * currentPageSize + data.results.length
|
||||
: (pageIndex + 1) * currentPageSize,
|
||||
total: data.pageInfo.results,
|
||||
strong: function strong(msg) {
|
||||
return <span className="font-medium">{msg}</span>;
|
||||
},
|
||||
strong: (msg: React.ReactNode) => (
|
||||
<span className="font-medium">{msg}</span>
|
||||
),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user