mirror of
https://github.com/logto-io/logto.git
synced 2025-03-31 22:51:25 -05:00
refactor(console): support pagination on rbac-related pages (#2934)
This commit is contained in:
parent
5211fa1e0c
commit
ef795ac592
8 changed files with 163 additions and 21 deletions
|
@ -1,6 +1,7 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.permissionTable {
|
||||
flex: 1;
|
||||
margin-bottom: _.unit(6);
|
||||
color: var(--color-text);
|
||||
|
||||
|
|
|
@ -9,18 +9,27 @@ import { ApiResourceDetailsTabs } from '@/consts/page-tabs';
|
|||
|
||||
import Button from '../Button';
|
||||
import IconButton from '../IconButton';
|
||||
import type { Props as PaginationProps } from '../Pagination';
|
||||
import Search from '../Search';
|
||||
import Table from '../Table';
|
||||
import type { Column } from '../Table/types';
|
||||
import TextLink from '../TextLink';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type SearchProps = {
|
||||
keyword: string;
|
||||
searchHandler: (value: string) => void;
|
||||
clearSearchHandler: () => void;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
scopes?: ScopeResponse[];
|
||||
isLoading: boolean;
|
||||
errorMessage?: string;
|
||||
createButtonTitle: AdminConsoleKey;
|
||||
isApiColumnVisible?: boolean;
|
||||
pagination?: PaginationProps;
|
||||
search: SearchProps;
|
||||
createHandler: () => void;
|
||||
deleteHandler: (ScopeResponse: ScopeResponse) => void;
|
||||
retryHandler: () => void;
|
||||
|
@ -32,6 +41,8 @@ const PermissionsTable = ({
|
|||
errorMessage,
|
||||
createButtonTitle,
|
||||
isApiColumnVisible = false,
|
||||
pagination,
|
||||
search: { keyword, searchHandler, clearSearchHandler },
|
||||
createHandler,
|
||||
deleteHandler,
|
||||
retryHandler,
|
||||
|
@ -97,7 +108,12 @@ const PermissionsTable = ({
|
|||
columns={columns}
|
||||
filter={
|
||||
<div className={styles.filter}>
|
||||
<Search />
|
||||
<Search
|
||||
defaultValue={keyword}
|
||||
isClearable={Boolean(keyword)}
|
||||
onSearch={searchHandler}
|
||||
onClearSearch={clearSearchHandler}
|
||||
/>
|
||||
<Button
|
||||
title={createButtonTitle}
|
||||
type="primary"
|
||||
|
@ -110,6 +126,7 @@ const PermissionsTable = ({
|
|||
</div>
|
||||
}
|
||||
isLoading={isLoading}
|
||||
pagination={pagination}
|
||||
placeholder={{
|
||||
content: (
|
||||
<Button
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { Scope, ScopeResponse } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -9,6 +10,8 @@ import ConfirmModal from '@/components/ConfirmModal';
|
|||
import PermissionsTable from '@/components/PermissionsTable';
|
||||
import type { RequestError } from '@/hooks/use-api';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import useTableSearchParams, { formatKeyword } from '@/hooks/use-table-search-params';
|
||||
import { buildUrl } from '@/utilities/url';
|
||||
|
||||
import type { ApiResourceDetailsOutletContext } from '../types';
|
||||
import CreatePermissionModal from './components/CreatePermissionModal';
|
||||
|
@ -21,12 +24,21 @@ const ApiResourcePermissions = () => {
|
|||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const {
|
||||
data: scopes,
|
||||
error,
|
||||
mutate,
|
||||
} = useSWR<ScopeResponse[], RequestError>(resourceId && `/api/resources/${resourceId}/scopes`);
|
||||
pagination: { pageIndex, pageSize, setPageIndex },
|
||||
search: { keyword, setKeyword },
|
||||
} = useTableSearchParams();
|
||||
|
||||
const isLoading = !scopes && !error;
|
||||
const { data, error, mutate } = useSWR<[ScopeResponse[], number], RequestError>(
|
||||
resourceId &&
|
||||
buildUrl(`/api/resources/${resourceId}/scopes`, {
|
||||
page: String(pageIndex),
|
||||
page_size: String(pageSize),
|
||||
...conditional(keyword && { search: formatKeyword(keyword) }),
|
||||
})
|
||||
);
|
||||
|
||||
const isLoading = !data && !error;
|
||||
const [scopes, totalCount] = data ?? [];
|
||||
|
||||
const api = useApi();
|
||||
|
||||
|
@ -62,6 +74,23 @@ const ApiResourcePermissions = () => {
|
|||
deleteHandler={setScopeToBeDeleted}
|
||||
errorMessage={error?.body?.message ?? error?.message}
|
||||
retryHandler={async () => mutate(undefined, true)}
|
||||
pagination={{
|
||||
pageIndex,
|
||||
pageSize,
|
||||
totalCount,
|
||||
onChange: setPageIndex,
|
||||
}}
|
||||
search={{
|
||||
keyword,
|
||||
searchHandler: (value) => {
|
||||
setKeyword(value);
|
||||
setPageIndex(1);
|
||||
},
|
||||
clearSearchHandler: () => {
|
||||
setKeyword('');
|
||||
setPageIndex(1);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{isCreateFormOpen && (
|
||||
<CreatePermissionModal
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { Scope, ScopeResponse } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -9,6 +10,8 @@ import ConfirmModal from '@/components/ConfirmModal';
|
|||
import PermissionsTable from '@/components/PermissionsTable';
|
||||
import type { RequestError } from '@/hooks/use-api';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import useTableSearchParams, { formatKeyword } from '@/hooks/use-table-search-params';
|
||||
import { buildUrl } from '@/utilities/url';
|
||||
|
||||
import type { RoleDetailsOutletContext } from '../types';
|
||||
import AssignPermissionsModal from './components/AssignPermissionsModal';
|
||||
|
@ -21,12 +24,22 @@ const RolePermissions = () => {
|
|||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const {
|
||||
data: scopes,
|
||||
error,
|
||||
mutate,
|
||||
} = useSWR<ScopeResponse[], RequestError>(roleId && `/api/roles/${roleId}/scopes`);
|
||||
pagination: { pageIndex, pageSize, setPageIndex },
|
||||
search: { keyword, setKeyword },
|
||||
} = useTableSearchParams();
|
||||
|
||||
const isLoading = !scopes && !error;
|
||||
const { data, error, mutate } = useSWR<[ScopeResponse[], number], RequestError>(
|
||||
roleId &&
|
||||
buildUrl(`/api/roles/${roleId}/scopes`, {
|
||||
page: String(pageIndex),
|
||||
page_size: String(pageSize),
|
||||
...conditional(keyword && { search: formatKeyword(keyword) }),
|
||||
})
|
||||
);
|
||||
|
||||
const isLoading = !data && !error;
|
||||
|
||||
const [scopes, totalCount] = data ?? [];
|
||||
|
||||
const [isAssignPermissionsModalOpen, setIsAssignPermissionsModalOpen] = useState(false);
|
||||
const [scopeToBeDeleted, setScopeToBeDeleted] = useState<Scope>();
|
||||
|
@ -65,6 +78,23 @@ const RolePermissions = () => {
|
|||
deleteHandler={setScopeToBeDeleted}
|
||||
errorMessage={error?.body?.message ?? error?.message}
|
||||
retryHandler={async () => mutate(undefined, true)}
|
||||
pagination={{
|
||||
pageIndex,
|
||||
pageSize,
|
||||
totalCount,
|
||||
onChange: setPageIndex,
|
||||
}}
|
||||
search={{
|
||||
keyword,
|
||||
searchHandler: (value) => {
|
||||
setKeyword(value);
|
||||
setPageIndex(1);
|
||||
},
|
||||
clearSearchHandler: () => {
|
||||
setKeyword('');
|
||||
setPageIndex(1);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{scopeToBeDeleted && (
|
||||
<ConfirmModal
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.usersTable {
|
||||
flex: 1;
|
||||
margin-bottom: _.unit(6);
|
||||
color: var(--color-text);
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { User } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -18,6 +19,8 @@ import Table from '@/components/Table';
|
|||
import UserAvatar from '@/components/UserAvatar';
|
||||
import type { RequestError } from '@/hooks/use-api';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import useTableSearchParams, { formatKeyword } from '@/hooks/use-table-search-params';
|
||||
import { buildUrl } from '@/utilities/url';
|
||||
|
||||
import type { RoleDetailsOutletContext } from '../types';
|
||||
import AssignUsersModal from './components/AssignUsersModal';
|
||||
|
@ -31,12 +34,22 @@ const RoleUsers = () => {
|
|||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const {
|
||||
data: users,
|
||||
error,
|
||||
mutate,
|
||||
} = useSWR<User[], RequestError>(roleId && `/api/roles/${roleId}/users`);
|
||||
pagination: { pageIndex, pageSize, setPageIndex },
|
||||
search: { keyword, setKeyword },
|
||||
} = useTableSearchParams();
|
||||
|
||||
const isLoading = !users && !error;
|
||||
const { data, error, mutate } = useSWR<[User[], number], RequestError>(
|
||||
roleId &&
|
||||
buildUrl(`/api/roles/${roleId}/users`, {
|
||||
page: String(pageIndex),
|
||||
page_size: String(pageSize),
|
||||
...conditional(keyword && { search: formatKeyword(keyword) }),
|
||||
})
|
||||
);
|
||||
|
||||
const isLoading = !data && !error;
|
||||
|
||||
const [users, totalCount] = data ?? [];
|
||||
|
||||
const [isAssignModalOpen, setIsAssignModalOpen] = useState(false);
|
||||
const [userToBeDeleted, setUserToBeDeleted] = useState<User>();
|
||||
|
@ -84,7 +97,7 @@ const RoleUsers = () => {
|
|||
},
|
||||
{
|
||||
title: t('role_details.users.app_column'),
|
||||
dataIndex: 'name',
|
||||
dataIndex: 'app',
|
||||
colSpan: 5,
|
||||
render: ({ applicationId }) =>
|
||||
applicationId ? <ApplicationName applicationId={applicationId} /> : '-',
|
||||
|
@ -112,7 +125,18 @@ const RoleUsers = () => {
|
|||
]}
|
||||
filter={
|
||||
<div className={styles.filter}>
|
||||
<Search />
|
||||
<Search
|
||||
defaultValue={keyword}
|
||||
isClearable={Boolean(keyword)}
|
||||
onSearch={(value) => {
|
||||
setKeyword(value);
|
||||
setPageIndex(1);
|
||||
}}
|
||||
onClearSearch={() => {
|
||||
setKeyword('');
|
||||
setPageIndex(1);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
title="role_details.users.assign_button"
|
||||
type="primary"
|
||||
|
@ -124,6 +148,12 @@ const RoleUsers = () => {
|
|||
/>
|
||||
</div>
|
||||
}
|
||||
pagination={{
|
||||
pageIndex,
|
||||
pageSize,
|
||||
totalCount,
|
||||
onChange: setPageIndex,
|
||||
}}
|
||||
placeholder={{
|
||||
content: (
|
||||
<Button
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.rolesTable {
|
||||
flex: 1;
|
||||
margin-bottom: _.unit(6);
|
||||
color: var(--color-text);
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { Role } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import { useState } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -15,6 +16,8 @@ import Table from '@/components/Table';
|
|||
import TextLink from '@/components/TextLink';
|
||||
import type { RequestError } from '@/hooks/use-api';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import useTableSearchParams, { formatKeyword } from '@/hooks/use-table-search-params';
|
||||
import { buildUrl } from '@/utilities/url';
|
||||
|
||||
import type { UserDetailsOutletContext } from '../types';
|
||||
import AssignRolesModal from './components/AssignRolesModal';
|
||||
|
@ -26,9 +29,22 @@ const UserRoles = () => {
|
|||
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const { data: roles, error, mutate } = useSWR<Role[], RequestError>(`/api/users/${userId}/roles`);
|
||||
const {
|
||||
pagination: { pageIndex, pageSize, setPageIndex },
|
||||
search: { keyword, setKeyword },
|
||||
} = useTableSearchParams();
|
||||
|
||||
const isLoading = !roles && !error;
|
||||
const { data, error, mutate } = useSWR<[Role[], number], RequestError>(
|
||||
buildUrl(`/api/users/${userId}/roles`, {
|
||||
page: String(pageIndex),
|
||||
page_size: String(pageSize),
|
||||
...conditional(keyword && { search: formatKeyword(keyword) }),
|
||||
})
|
||||
);
|
||||
|
||||
const isLoading = !data && !error;
|
||||
|
||||
const [roles, totalCount] = data ?? [];
|
||||
|
||||
const [isAssignRolesModalOpen, setIsAssignRolesModalOpen] = useState(false);
|
||||
const [roleToBeDeleted, setRoleToBeDeleted] = useState<Role>();
|
||||
|
@ -93,7 +109,18 @@ const UserRoles = () => {
|
|||
]}
|
||||
filter={
|
||||
<div className={styles.filter}>
|
||||
<Search />
|
||||
<Search
|
||||
defaultValue={keyword}
|
||||
isClearable={Boolean(keyword)}
|
||||
onSearch={(value) => {
|
||||
setKeyword(value);
|
||||
setPageIndex(1);
|
||||
}}
|
||||
onClearSearch={() => {
|
||||
setKeyword('');
|
||||
setPageIndex(1);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
title="user_details.roles.assign_button"
|
||||
type="primary"
|
||||
|
@ -105,6 +132,12 @@ const UserRoles = () => {
|
|||
/>
|
||||
</div>
|
||||
}
|
||||
pagination={{
|
||||
pageIndex,
|
||||
pageSize,
|
||||
totalCount,
|
||||
onChange: setPageIndex,
|
||||
}}
|
||||
placeholder={{
|
||||
content: (
|
||||
<Button
|
||||
|
|
Loading…
Add table
Reference in a new issue