2022-06-06 20:45:00 +05:30
|
|
|
import React, { useState, useMemo } from 'react';
|
2021-11-22 16:33:14 -08:00
|
|
|
import styled from 'styled-components';
|
2022-06-06 20:45:00 +05:30
|
|
|
import { Alert, Button, Divider, Empty, message, Modal, Pagination, Typography } from 'antd';
|
|
|
|
import { DeleteOutlined, InfoCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
|
|
|
|
|
|
|
import { FacetFilterInput } from '../../types.generated';
|
|
|
|
import { useListAccessTokensQuery, useRevokeAccessTokenMutation } from '../../graphql/auth.generated';
|
|
|
|
import { Message } from '../shared/Message';
|
|
|
|
import TabToolbar from '../entity/shared/components/styled/TabToolbar';
|
|
|
|
import { StyledTable } from '../entity/shared/components/styled/StyledTable';
|
2021-11-22 16:33:14 -08:00
|
|
|
import { useGetAuthenticatedUser } from '../useGetAuthenticatedUser';
|
2022-06-06 20:45:00 +05:30
|
|
|
import CreateTokenModal from './CreateTokenModal';
|
|
|
|
import { useAppConfigQuery } from '../../graphql/app.generated';
|
|
|
|
import { getLocaleTimezone } from '../shared/time/timeUtils';
|
2021-11-22 16:33:14 -08:00
|
|
|
|
2022-06-06 20:45:00 +05:30
|
|
|
const SourceContainer = styled.div`
|
|
|
|
width: 100%;
|
2021-11-22 16:33:14 -08:00
|
|
|
padding-top: 20px;
|
|
|
|
padding-right: 40px;
|
|
|
|
padding-left: 40px;
|
|
|
|
`;
|
|
|
|
|
2022-06-06 20:45:00 +05:30
|
|
|
const TokensContainer = styled.div`
|
|
|
|
padding-top: 0px;
|
2021-11-22 16:33:14 -08:00
|
|
|
`;
|
|
|
|
|
2022-06-06 20:45:00 +05:30
|
|
|
const TokensHeaderContainer = styled.div`
|
2021-11-22 16:33:14 -08:00
|
|
|
&& {
|
2022-06-06 20:45:00 +05:30
|
|
|
padding-left: 0px;
|
2021-11-22 16:33:14 -08:00
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
2022-06-06 20:45:00 +05:30
|
|
|
const TokensTitle = styled(Typography.Title)`
|
|
|
|
&& {
|
|
|
|
margin-bottom: 8px;
|
|
|
|
}
|
2021-11-22 16:33:14 -08:00
|
|
|
`;
|
|
|
|
|
2022-04-22 12:09:05 -07:00
|
|
|
const StyledAlert = styled(Alert)`
|
|
|
|
padding-top: 12px;
|
|
|
|
padding-bottom: 12px;
|
|
|
|
margin-bottom: 20px;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const StyledInfoCircleOutlined = styled(InfoCircleOutlined)`
|
|
|
|
margin-right: 8px;
|
|
|
|
`;
|
|
|
|
|
2022-06-06 20:45:00 +05:30
|
|
|
const PersonTokenDescriptionText = styled(Typography.Paragraph)`
|
2021-11-22 16:33:14 -08:00
|
|
|
&& {
|
2022-06-06 20:45:00 +05:30
|
|
|
max-width: 700px;
|
|
|
|
margin-top: 12px;
|
|
|
|
margin-bottom: 16px;
|
2021-11-22 16:33:14 -08:00
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
2022-06-06 20:45:00 +05:30
|
|
|
const ActionButtonContainer = styled.div`
|
|
|
|
display: flex;
|
|
|
|
justify-content: right;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const PaginationContainer = styled.div`
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const DEFAULT_PAGE_SIZE = 10;
|
2021-11-22 16:33:14 -08:00
|
|
|
|
|
|
|
export const AccessTokens = () => {
|
2022-06-06 20:45:00 +05:30
|
|
|
const [isCreatingToken, setIsCreatingToken] = useState(false);
|
|
|
|
const [removedTokens, setRemovedTokens] = useState<string[]>([]);
|
|
|
|
|
|
|
|
// Current User Urn
|
2021-11-22 16:33:14 -08:00
|
|
|
const authenticatedUser = useGetAuthenticatedUser();
|
2022-06-06 20:45:00 +05:30
|
|
|
const currentUserUrn = authenticatedUser?.corpUser.urn || '';
|
|
|
|
|
2022-04-22 12:09:05 -07:00
|
|
|
const isTokenAuthEnabled = useAppConfigQuery().data?.appConfig?.authConfig?.tokenAuthEnabled;
|
|
|
|
const canGeneratePersonalAccessTokens =
|
|
|
|
isTokenAuthEnabled && authenticatedUser?.platformPrivileges.generatePersonalAccessTokens;
|
2021-11-22 16:33:14 -08:00
|
|
|
|
2022-06-06 20:45:00 +05:30
|
|
|
// Access Tokens list paging.
|
|
|
|
const [page, setPage] = useState(1);
|
|
|
|
const pageSize = DEFAULT_PAGE_SIZE;
|
|
|
|
const start = (page - 1) * pageSize;
|
|
|
|
|
|
|
|
// Filters for Access Tokens list
|
|
|
|
const filters: Array<FacetFilterInput> = [
|
|
|
|
{
|
|
|
|
field: 'ownerUrn',
|
|
|
|
value: currentUserUrn,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
// Call list Access Token Mutation
|
|
|
|
const {
|
|
|
|
loading: tokensLoading,
|
|
|
|
error: tokensError,
|
|
|
|
data: tokensData,
|
|
|
|
refetch: tokensRefetch,
|
|
|
|
} = useListAccessTokensQuery({
|
2022-06-09 12:20:47 -04:00
|
|
|
skip: !canGeneratePersonalAccessTokens,
|
2022-06-06 20:45:00 +05:30
|
|
|
variables: {
|
|
|
|
input: {
|
|
|
|
start,
|
|
|
|
count: pageSize,
|
|
|
|
filters,
|
|
|
|
},
|
|
|
|
},
|
2021-11-22 16:33:14 -08:00
|
|
|
});
|
|
|
|
|
2022-06-06 20:45:00 +05:30
|
|
|
const totalTokens = tokensData?.listAccessTokens.total || 0;
|
|
|
|
const tokens = useMemo(() => tokensData?.listAccessTokens.tokens || [], [tokensData]);
|
|
|
|
const filteredTokens = tokens.filter((token) => !removedTokens.includes(token.id));
|
2021-11-22 16:33:14 -08:00
|
|
|
|
2022-06-06 20:45:00 +05:30
|
|
|
// Any time a access token is removed or created, refetch the list.
|
|
|
|
const [revokeAccessToken, { error: revokeTokenError }] = useRevokeAccessTokenMutation();
|
|
|
|
|
|
|
|
// Revoke token Handler
|
|
|
|
const onRemoveToken = (token: any) => {
|
|
|
|
Modal.confirm({
|
|
|
|
title: 'Are you sure you want to revoke this token?',
|
|
|
|
content: `Anyone using this token will no longer be able to access the DataHub API. You cannot undo this action.`,
|
|
|
|
onOk() {
|
|
|
|
// Hack to deal with eventual consistency.
|
|
|
|
const newTokenIds = [...removedTokens, token.id];
|
|
|
|
setRemovedTokens(newTokenIds);
|
|
|
|
revokeAccessToken({ variables: { tokenId: token.id } })
|
|
|
|
.catch((e) => {
|
|
|
|
message.destroy();
|
|
|
|
message.error({ content: `Failed to revoke Token!: \n ${e.message || ''}`, duration: 3 });
|
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
setTimeout(function () {
|
|
|
|
tokensRefetch?.();
|
|
|
|
}, 3000);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
onCancel() {},
|
|
|
|
okText: 'Yes',
|
|
|
|
maskClosable: true,
|
|
|
|
closable: true,
|
|
|
|
});
|
2021-11-22 16:33:14 -08:00
|
|
|
};
|
|
|
|
|
2022-06-06 20:45:00 +05:30
|
|
|
const tableData = filteredTokens?.map((token) => ({
|
|
|
|
urn: token.urn,
|
|
|
|
type: token.type,
|
|
|
|
id: token.id,
|
|
|
|
name: token.name,
|
|
|
|
description: token.description,
|
|
|
|
actorUrn: token.actorUrn,
|
|
|
|
ownerUrn: token.ownerUrn,
|
|
|
|
createdAt: token.createdAt,
|
|
|
|
expiresAt: token.expiresAt,
|
|
|
|
}));
|
|
|
|
|
|
|
|
const tableColumns = [
|
|
|
|
{
|
|
|
|
title: 'Name',
|
|
|
|
dataIndex: 'name',
|
|
|
|
key: 'name',
|
|
|
|
render: (name: string) => <b>{name}</b>,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: 'Description',
|
|
|
|
dataIndex: 'description',
|
|
|
|
key: 'description',
|
|
|
|
render: (description: string) => description || '',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: 'Expires At',
|
|
|
|
dataIndex: 'expiresAt',
|
|
|
|
key: 'expiresAt',
|
|
|
|
render: (expiresAt: string) => {
|
|
|
|
const localeTimezone = getLocaleTimezone();
|
|
|
|
const formattedExpireAt = new Date(expiresAt);
|
|
|
|
return (
|
|
|
|
<span>{`${formattedExpireAt.toLocaleDateString()} at ${formattedExpireAt.toLocaleTimeString()} (${localeTimezone})`}</span>
|
|
|
|
);
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: '',
|
|
|
|
dataIndex: '',
|
|
|
|
key: 'x',
|
|
|
|
render: (_, record: any) => (
|
|
|
|
<ActionButtonContainer>
|
|
|
|
<Button onClick={() => onRemoveToken(record)} icon={<DeleteOutlined />} danger>
|
|
|
|
Revoke
|
|
|
|
</Button>
|
|
|
|
</ActionButtonContainer>
|
|
|
|
),
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
const onChangePage = (newPage: number) => {
|
|
|
|
setPage(newPage);
|
|
|
|
};
|
2021-11-22 16:33:14 -08:00
|
|
|
|
|
|
|
return (
|
2022-06-06 20:45:00 +05:30
|
|
|
<SourceContainer>
|
|
|
|
{tokensLoading && !tokensData && (
|
|
|
|
<Message type="loading" content="Loading tokens..." style={{ marginTop: '10%' }} />
|
|
|
|
)}
|
|
|
|
{tokensError && message.error('Failed to load tokens :(')}
|
|
|
|
{revokeTokenError && message.error('Failed to update the Token :(')}
|
|
|
|
<TokensContainer>
|
|
|
|
<TokensHeaderContainer>
|
|
|
|
<TokensTitle level={2}>Manage Access Tokens</TokensTitle>
|
|
|
|
<Typography.Paragraph type="secondary">
|
|
|
|
Manage Access Tokens for use with DataHub APIs.
|
|
|
|
</Typography.Paragraph>
|
|
|
|
</TokensHeaderContainer>
|
|
|
|
</TokensContainer>
|
2021-11-22 16:33:14 -08:00
|
|
|
<Divider />
|
2022-04-22 12:09:05 -07:00
|
|
|
{isTokenAuthEnabled === false && (
|
|
|
|
<StyledAlert
|
|
|
|
type="error"
|
|
|
|
message={
|
|
|
|
<span>
|
|
|
|
<StyledInfoCircleOutlined />
|
|
|
|
Token based authentication is currently disabled. Contact your DataHub administrator to
|
|
|
|
enable this feature.
|
|
|
|
</span>
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
)}
|
2021-11-22 16:33:14 -08:00
|
|
|
<Typography.Title level={5}>Personal Access Tokens</Typography.Title>
|
|
|
|
<PersonTokenDescriptionText type="secondary">
|
|
|
|
Personal Access Tokens allow you to make programmatic requests to DataHub's APIs. They inherit your
|
2022-06-06 20:45:00 +05:30
|
|
|
privileges and have a finite lifespan. Do not share Personal Access Tokens.
|
2021-11-22 16:33:14 -08:00
|
|
|
</PersonTokenDescriptionText>
|
2022-06-06 20:45:00 +05:30
|
|
|
<TabToolbar>
|
|
|
|
<div>
|
2021-11-22 16:33:14 -08:00
|
|
|
<Button
|
2022-06-06 20:45:00 +05:30
|
|
|
type="text"
|
|
|
|
onClick={() => setIsCreatingToken(true)}
|
|
|
|
data-testid="add-token-button"
|
2021-11-22 16:33:14 -08:00
|
|
|
disabled={!canGeneratePersonalAccessTokens}
|
|
|
|
>
|
2022-06-06 20:45:00 +05:30
|
|
|
<PlusOutlined /> Generate new token
|
2021-11-22 16:33:14 -08:00
|
|
|
</Button>
|
2022-06-06 20:45:00 +05:30
|
|
|
</div>
|
|
|
|
</TabToolbar>
|
|
|
|
<StyledTable
|
|
|
|
columns={tableColumns}
|
|
|
|
dataSource={tableData}
|
|
|
|
rowKey="urn"
|
|
|
|
locale={{
|
2022-06-08 00:09:26 +05:30
|
|
|
emptyText: <Empty description="No Access Tokens!" image={Empty.PRESENTED_IMAGE_SIMPLE} />,
|
2022-06-06 20:45:00 +05:30
|
|
|
}}
|
|
|
|
pagination={false}
|
|
|
|
/>
|
|
|
|
<PaginationContainer>
|
|
|
|
<Pagination
|
|
|
|
style={{ margin: 40 }}
|
|
|
|
current={page}
|
|
|
|
pageSize={pageSize}
|
|
|
|
total={totalTokens}
|
|
|
|
showLessItems
|
|
|
|
onChange={onChangePage}
|
|
|
|
showSizeChanger={false}
|
|
|
|
/>
|
|
|
|
</PaginationContainer>
|
|
|
|
<CreateTokenModal
|
|
|
|
currentUserUrn={currentUserUrn}
|
|
|
|
visible={isCreatingToken}
|
|
|
|
onClose={() => setIsCreatingToken(false)}
|
|
|
|
onCreateToken={() => {
|
|
|
|
// Hack to deal with eventual consistency.
|
|
|
|
setTimeout(function () {
|
|
|
|
tokensRefetch?.();
|
|
|
|
}, 3000);
|
|
|
|
}}
|
2021-11-22 16:33:14 -08:00
|
|
|
/>
|
2022-06-06 20:45:00 +05:30
|
|
|
</SourceContainer>
|
2021-11-22 16:33:14 -08:00
|
|
|
);
|
|
|
|
};
|