diff --git a/frontend/src/component/admin/Admin.tsx b/frontend/src/component/admin/Admin.tsx
index dce5b15f0f..f8ec1a544c 100644
--- a/frontend/src/component/admin/Admin.tsx
+++ b/frontend/src/component/admin/Admin.tsx
@@ -1,5 +1,5 @@
import { Routes, Route } from 'react-router-dom';
-import { ApiPage } from './api';
+import { ApiTokenPage } from './apiToken/ApiTokenPage/ApiTokenPage';
import { CreateApiToken } from './apiToken/CreateApiToken/CreateApiToken';
import { AuthSettings } from './auth/AuthSettings';
import { Billing } from './billing/Billing';
@@ -29,7 +29,7 @@ export const Admin = () => (
} />
} />
} />
- } />
+ } />
} />
} />
} />
diff --git a/frontend/src/component/admin/api/index.tsx b/frontend/src/component/admin/api/index.tsx
deleted file mode 100644
index 65ddf60ed5..0000000000
--- a/frontend/src/component/admin/api/index.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import { ApiTokenPage } from 'component/admin/apiToken/ApiTokenPage/ApiTokenPage';
-
-export const ApiPage = () => {
- return (
-
- );
-};
-
-export default ApiPage;
diff --git a/frontend/src/component/admin/apiToken/ApiTokenPage/ApiTokenPage.tsx b/frontend/src/component/admin/apiToken/ApiTokenPage/ApiTokenPage.tsx
index 6db09abf5d..83b583af44 100644
--- a/frontend/src/component/admin/apiToken/ApiTokenPage/ApiTokenPage.tsx
+++ b/frontend/src/component/admin/apiToken/ApiTokenPage/ApiTokenPage.tsx
@@ -1,19 +1,93 @@
import { useContext } from 'react';
import AccessContext from 'contexts/AccessContext';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
-import { READ_API_TOKEN } from 'component/providers/AccessProvider/permissions';
+import {
+ CREATE_API_TOKEN,
+ DELETE_API_TOKEN,
+ READ_API_TOKEN,
+} from 'component/providers/AccessProvider/permissions';
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
-import { ApiTokenTable } from 'component/admin/apiToken/ApiTokenTable/ApiTokenTable';
+import { ApiTokenTable } from 'component/common/ApiTokenTable/ApiTokenTable';
+import { PageContent } from 'component/common/PageContent/PageContent';
+import { PageHeader } from 'component/common/PageHeader/PageHeader';
+import { CreateApiTokenButton } from 'component/common/ApiTokenTable/CreateApiTokenButton/CreateApiTokenButton';
+import { Search } from 'component/common/Search/Search';
+import { useApiTokenTable } from 'component/common/ApiTokenTable/useApiTokenTable';
import { useApiTokens } from 'hooks/api/getters/useApiTokens/useApiTokens';
+import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
+import { CopyApiTokenButton } from 'component/common/ApiTokenTable/CopyApiTokenButton/CopyApiTokenButton';
+import { RemoveApiTokenButton } from 'component/common/ApiTokenTable/RemoveApiTokenButton/RemoveApiTokenButton';
+import useApiTokensApi from 'hooks/api/actions/useApiTokensApi/useApiTokensApi';
export const ApiTokenPage = () => {
const { hasAccess } = useContext(AccessContext);
- const { tokens, loading } = useApiTokens();
+ const { tokens, loading, refetch } = useApiTokens();
+ const { deleteToken } = useApiTokensApi();
+
+ const {
+ getTableProps,
+ getTableBodyProps,
+ headerGroups,
+ rows,
+ prepareRow,
+ state: { globalFilter },
+ setGlobalFilter,
+ setHiddenColumns,
+ columns,
+ } = useApiTokenTable(tokens, props => (
+
+
+ {
+ await deleteToken(props.row.original.secret);
+ refetch();
+ }}
+ />
+
+ ));
return (
}
+ show={() => (
+
+
+
+
+ >
+ }
+ />
+ }
+ >
+
+
+ )}
elseShow={() => }
/>
);
diff --git a/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx b/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx
deleted file mode 100644
index b63896ac7c..0000000000
--- a/frontend/src/component/admin/apiToken/ApiTokenTable/ApiTokenTable.tsx
+++ /dev/null
@@ -1,264 +0,0 @@
-import { IApiToken } from 'hooks/api/getters/useApiTokens/useApiTokens';
-import { useGlobalFilter, useSortBy, useTable } from 'react-table';
-import { PageContent } from 'component/common/PageContent/PageContent';
-import {
- SortableTableHeader,
- TableCell,
- TablePlaceholder,
-} from 'component/common/Table';
-import { Box, Table, TableBody, TableRow, useMediaQuery } from '@mui/material';
-import { PageHeader } from 'component/common/PageHeader/PageHeader';
-import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
-import { ApiTokenDocs } from 'component/admin/apiToken/ApiTokenDocs/ApiTokenDocs';
-import { CreateApiTokenButton } from 'component/admin/apiToken/CreateApiTokenButton/CreateApiTokenButton';
-import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
-import { Key } from '@mui/icons-material';
-import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
-import { CopyApiTokenButton } from 'component/admin/apiToken/CopyApiTokenButton/CopyApiTokenButton';
-import { RemoveApiTokenButton } from 'component/admin/apiToken/RemoveApiTokenButton/RemoveApiTokenButton';
-import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
-import { sortTypes } from 'utils/sortTypes';
-import { useMemo } from 'react';
-import theme from 'themes/theme';
-import { ProjectsList } from 'component/admin/apiToken/ProjectsList/ProjectsList';
-import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
-import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
-import { Search } from 'component/common/Search/Search';
-import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
-import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell';
-
-const hiddenColumnsSmall = ['Icon', 'createdAt'];
-const hiddenColumnsCompact = ['Icon', 'project', 'seenAt'];
-
-interface IApiTokenTableProps {
- compact?: boolean;
- filterForProject?: string;
- tokens: IApiToken[];
- loading: boolean;
-}
-export const ApiTokenTable = ({
- compact = false,
- filterForProject,
- tokens,
- loading,
-}: IApiTokenTableProps) => {
- const initialState = useMemo(() => ({ sortBy: [{ id: 'createdAt' }] }), []);
- const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
-
- const COLUMNS = useMemo(() => {
- return [
- {
- id: 'Icon',
- width: '1%',
- Cell: () => } />,
- disableSortBy: true,
- disableGlobalFilter: true,
- },
- {
- Header: 'Username',
- accessor: 'username',
- Cell: HighlightCell,
- },
- {
- Header: 'Type',
- accessor: 'type',
- Cell: ({
- value,
- }: {
- value: 'client' | 'admin' | 'frontend';
- }) => (
-
- ),
- minWidth: 280,
- },
- {
- Header: 'Project',
- accessor: 'project',
- Cell: (props: any) => (
-
- ),
- minWidth: 120,
- },
- {
- Header: 'Environment',
- accessor: 'environment',
- Cell: HighlightCell,
- minWidth: 120,
- },
- {
- Header: 'Created',
- accessor: 'createdAt',
- Cell: DateCell,
- minWidth: 150,
- disableGlobalFilter: true,
- },
- {
- Header: 'Last seen',
- accessor: 'seenAt',
- Cell: TimeAgoCell,
- minWidth: 150,
- disableGlobalFilter: true,
- },
- {
- Header: 'Actions',
- id: 'Actions',
- align: 'center',
- width: '1%',
- disableSortBy: true,
- disableGlobalFilter: true,
- Cell: (props: any) => (
-
-
-
-
- ),
- },
- ];
- }, [filterForProject]);
-
- const {
- getTableProps,
- getTableBodyProps,
- headerGroups,
- rows,
- prepareRow,
- state: { globalFilter },
- setGlobalFilter,
- setHiddenColumns,
- } = useTable(
- {
- columns: COLUMNS as any,
- data: tokens as any,
- initialState,
- sortTypes,
- autoResetHiddenColumns: false,
- disableSortRemove: true,
- },
- useGlobalFilter,
- useSortBy
- );
-
- useConditionallyHiddenColumns(
- [
- {
- condition: isSmallScreen,
- columns: hiddenColumnsSmall,
- },
- {
- condition: compact,
- columns: hiddenColumnsCompact,
- },
- ],
- setHiddenColumns,
- COLUMNS
- );
-
- return (
-
-
-
-
- >
- }
- />
- }
- >
- 0}
- show={
-
-
-
- }
- />
-
-
-
-
-
- {rows.map(row => {
- prepareRow(row);
- return (
-
- {row.cells.map(cell => (
-
- {cell.render('Cell')}
-
- ))}
-
- );
- })}
-
-
-
-
- 0}
- show={
-
- No tokens found matching “
- {globalFilter}
- ”
-
- }
- elseShow={
-
-
- {'No tokens available. Read '}
-
- API How-to guides
- {' '}
- {' to learn more.'}
-
-
- }
- />
- }
- />
-
- );
-};
-const tokenDescriptions: { [index: string]: { label: string; title: string } } =
- {
- client: {
- label: 'CLIENT',
- title: 'Connect server-side SDK or Unleash Proxy',
- },
- frontend: {
- label: 'FRONTEND',
- title: 'Connect web and mobile SDK',
- },
- admin: {
- label: 'ADMIN',
- title: 'Full access for managing Unleash',
- },
- };
diff --git a/frontend/src/component/admin/apiToken/RemoveApiTokenButton/RemoveApiTokenButton.tsx b/frontend/src/component/admin/apiToken/RemoveApiTokenButton/RemoveApiTokenButton.tsx
deleted file mode 100644
index d34a3b9320..0000000000
--- a/frontend/src/component/admin/apiToken/RemoveApiTokenButton/RemoveApiTokenButton.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import {
- DELETE_API_TOKEN,
- DELETE_PROJECT_API_TOKEN,
-} from 'component/providers/AccessProvider/permissions';
-import { Delete } from '@mui/icons-material';
-import { styled } from '@mui/material';
-import {
- IApiToken,
- useApiTokens,
-} from 'hooks/api/getters/useApiTokens/useApiTokens';
-import AccessContext from 'contexts/AccessContext';
-import { useContext, useState } from 'react';
-import { Dialogue } from 'component/common/Dialogue/Dialogue';
-import useToast from 'hooks/useToast';
-import useApiTokensApi from 'hooks/api/actions/useApiTokensApi/useApiTokensApi';
-import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
-import useProjectApiTokensApi from '../../../../hooks/api/actions/useProjectApiTokensApi/useProjectApiTokensApi';
-
-const StyledUl = styled('ul')({
- marginBottom: 0,
-});
-
-interface IRemoveApiTokenButtonProps {
- token: IApiToken;
- project?: string;
-}
-
-export const RemoveApiTokenButton = ({
- token,
- project,
-}: IRemoveApiTokenButtonProps) => {
- const { hasAccess, isAdmin } = useContext(AccessContext);
- const { deleteToken } = useApiTokensApi();
- const { deleteToken: deleteProjectToken } = useProjectApiTokensApi();
- const [open, setOpen] = useState(false);
- const { setToastData } = useToast();
- const { refetch } = useApiTokens();
-
- const permission = Boolean(project)
- ? DELETE_PROJECT_API_TOKEN
- : DELETE_API_TOKEN;
-
- const canRemove = () => {
- if (isAdmin) {
- return true;
- }
- if (token && token.projects && project && permission) {
- const { projects } = token;
- for (const tokenProject of projects) {
- if (!hasAccess(permission, tokenProject)) {
- return false;
- }
- }
- return true;
- }
- };
-
- const onRemove = async () => {
- if (project) {
- await deleteProjectToken(token.secret, project);
- } else {
- await deleteToken(token.secret);
- }
- setOpen(false);
- refetch();
- setToastData({
- type: 'success',
- title: 'API token removed',
- });
- };
-
- return (
- <>
- setOpen(true)}
- size="large"
- disabled={!canRemove()}
- >
-
-
- setOpen(false)}
- title="Confirm deletion"
- >
-
- Are you sure you want to delete the following API token?
-
-
-
- username:{' '}
- {token.username}
-
-
- type: {token.type}
-
-
-
-
- >
- );
-};
diff --git a/frontend/src/component/common/ApiTokenTable/ApiTokenTable.tsx b/frontend/src/component/common/ApiTokenTable/ApiTokenTable.tsx
new file mode 100644
index 0000000000..fa48c50764
--- /dev/null
+++ b/frontend/src/component/common/ApiTokenTable/ApiTokenTable.tsx
@@ -0,0 +1,136 @@
+import {
+ Row,
+ TablePropGetter,
+ TableProps,
+ TableBodyPropGetter,
+ TableBodyProps,
+ HeaderGroup,
+} from 'react-table';
+import {
+ SortableTableHeader,
+ TableCell,
+ TablePlaceholder,
+} from 'component/common/Table';
+import { Box, Table, TableBody, TableRow, useMediaQuery } from '@mui/material';
+import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
+import { ApiTokenDocs } from 'component/admin/apiToken/ApiTokenDocs/ApiTokenDocs';
+
+import theme from 'themes/theme';
+import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
+
+import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
+
+const hiddenColumnsSmall = ['Icon', 'createdAt'];
+const hiddenColumnsCompact = ['Icon', 'project', 'seenAt'];
+
+interface IApiTokenTableProps {
+ compact?: boolean;
+ loading: boolean;
+ setHiddenColumns: (param: any) => void;
+ columns: any[];
+ rows: Row