Skip to content

Commit

Permalink
feat: keypair info/setting modal in credential page
Browse files Browse the repository at this point in the history
  • Loading branch information
ironAiken2 committed Dec 23, 2024
1 parent 5490140 commit 8d3b258
Show file tree
Hide file tree
Showing 28 changed files with 625 additions and 139 deletions.
214 changes: 116 additions & 98 deletions react/src/components/KeypairInfoModal.tsx
Original file line number Diff line number Diff line change
@@ -1,125 +1,143 @@
/**
@license
Copyright (c) 2015-2024 Lablup Inc. All rights reserved.
*/
import { useSuspendedBackendaiClient } from '../hooks';
import { useCurrentUserInfo } from '../hooks/backendai';
import { useTanQuery } from '../hooks/reactQueryAlias';
import BAIModal, { BAIModalProps } from './BAIModal';
import BAIModal from './BAIModal';
import Flex from './Flex';
import { KeypairInfoModalFragment$key } from './__generated__/KeypairInfoModalFragment.graphql';
import { KeypairInfoModalQuery } from './__generated__/KeypairInfoModalQuery.graphql';
import { Button, Table, Typography, Tag } from 'antd';
import { Descriptions, ModalProps, Tag, Typography, theme } from 'antd';
import graphql from 'babel-plugin-relay/macro';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useLazyLoadQuery } from 'react-relay';
import dayjs from 'dayjs';
import { t } from 'i18next';
import { useFragment, useLazyLoadQuery } from 'react-relay';

interface KeypairInfoModalProps extends BAIModalProps {
interface KeypairInfoModalProps extends ModalProps {
keypairInfoModalFrgmt: KeypairInfoModalFragment$key | null;
onRequestClose: () => void;
}

const KeypairInfoModal: React.FC<KeypairInfoModalProps> = ({
keypairInfoModalFrgmt = null,
onRequestClose,
...baiModalProps
...modalProps
}) => {
const { t } = useTranslation();
const [userInfo] = useCurrentUserInfo();
const baiClient = useSuspendedBackendaiClient();
const { data: keypairs } = useTanQuery({
queryKey: ['baiClient.keypair.list', baiModalProps.open], // refetch on open state
queryFn: () => {
return baiModalProps.open
? baiClient.keypair
.list(
userInfo.email,
['access_key', 'secret_key', 'is_active'],
true,
)
.then((res: any) => res.keypairs)
: null;
},
staleTime: 0,
});
const { token } = theme.useToken();

const supportMainAccessKey = baiClient?.supports('main-access-key');
const keypair = useFragment(
graphql`
fragment KeypairInfoModalFragment on KeyPair {
user_id
access_key
secret_key
is_admin
created_at
last_used
resource_policy
num_queries
rate_limit
concurrency_used @since(version: "24.09.0")
}
`,
keypairInfoModalFrgmt,
);

// FIXME: Keypair query does not support main_access_key info.
const { user } = useLazyLoadQuery<KeypairInfoModalQuery>(
graphql`
query KeypairInfoModalQuery($email: String) {
user(email: $email) {
email
main_access_key @since(version: "23.09.7")
query KeypairInfoModalQuery($domain_name: String, $email: String) {
user(domain_name: $domain_name, email: $email) {
main_access_key @since(version: "24.03.0")
}
}
`,
{
email: userInfo.email,
email: keypair?.user_id,
},
{
fetchPolicy:
modalProps.open && keypair?.user_id ? 'network-only' : 'store-only',
},
);

return (
<BAIModal
{...baiModalProps}
title={t('usersettings.MyKeypairInfo')}
centered
onCancel={onRequestClose}
destroyOnClose
width={'auto'}
footer={[
<Button
key="keypairInfoClose"
onClick={() => {
onRequestClose();
}}
>
{t('button.Close')}
</Button>,
]}
title={
<Flex gap={'xs'}>
<Typography.Text style={{ fontSize: token.fontSizeHeading5 }}>
{t('credential.KeypairDetail')}
</Typography.Text>
{user?.main_access_key === keypair?.access_key && (
<Tag color="red">{t('credential.MainAccessKey')}</Tag>
)}
</Flex>
}
onCancel={() => onRequestClose()}
footer={null}
{...modalProps}
>
<Table
scroll={{ x: 'max-content' }}
rowKey={'access_key'}
dataSource={keypairs}
columns={[
{
title: '#',
fixed: 'left',
render: (id, record, index) => {
++index;
return index;
},
showSorterTooltip: false,
rowScope: 'row',
},
{
title: t('general.AccessKey'),
key: 'accessKey',
dataIndex: 'access_key',
fixed: 'left',
render: (value) => (
<Flex direction="column" align="start">
<Typography.Text ellipsis copyable>
{value}
</Typography.Text>
{supportMainAccessKey && value === user?.main_access_key && (
<Tag color="#457b3b">{t('credential.MainAccessKey')}</Tag>
)}
</Flex>
),
},
{
title: t('general.SecretKey'),
key: 'secretKey',
dataIndex: 'secret_key',
fixed: 'left',
render: (value) => (
<Typography.Text ellipsis copyable>
{value}
</Typography.Text>
),
},
]}
/>
<Descriptions
title={t('credential.Information')}
size="small"
column={1}
labelStyle={{ width: '40%' }}
>
<Descriptions.Item label={t('credential.UserID')}>
{keypair?.user_id}
</Descriptions.Item>
<Descriptions.Item label={t('credential.AccessKey')}>
{keypair?.access_key}
</Descriptions.Item>
<Descriptions.Item label={t('credential.SecretKey')}>
<Typography.Text copyable={{ text: keypair?.secret_key ?? '' }}>
<span
style={{
filter: 'blur(3px)',
userSelect: 'none',
WebkitUserSelect: 'none',
msUserSelect: 'none',
MozUserSelect: 'none',
}}
>
{keypair?.secret_key}
</span>
</Typography.Text>
</Descriptions.Item>
<Descriptions.Item label={t('credential.Permission')}>
{keypair?.is_admin ? (
<>
<Tag color="red">admin</Tag>
<Tag color="green">user</Tag>
</>
) : (
<Tag color="green">user</Tag>
)}
</Descriptions.Item>
<Descriptions.Item label={t('credential.CreatedAt')}>
{dayjs(keypair?.created_at).format('lll')}
</Descriptions.Item>
<Descriptions.Item label={t('credential.LastUsed')}>
{keypair?.last_used ? dayjs(keypair?.last_used).format('lll') : '-'}
</Descriptions.Item>
</Descriptions>
<br />
<Descriptions
title={t('credential.Allocation')}
size="small"
column={1}
labelStyle={{ width: '40%' }}
>
<Descriptions.Item label={t('credential.ResourcePolicy')}>
{keypair?.resource_policy}
</Descriptions.Item>
<Descriptions.Item label={t('credential.NumberOfQueries')}>
{keypair?.num_queries}
</Descriptions.Item>
<Descriptions.Item label={t('credential.ConcurrentSessions')}>
{keypair?.concurrency_used}
</Descriptions.Item>
<Descriptions.Item
label={`${t('credential.RateLimit')} ${t('credential.for900seconds')}`}
>
{keypair?.rate_limit}
</Descriptions.Item>
</Descriptions>
</BAIModal>
);
};
Expand Down
53 changes: 53 additions & 0 deletions react/src/components/KeypairResourcePolicySelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { localeCompare } from '../helper';
import useControllableState from '../hooks/useControllableState';
import { KeypairResourcePolicySelectorQuery } from './__generated__/KeypairResourcePolicySelectorQuery.graphql';
import { Select, SelectProps } from 'antd';
import graphql from 'babel-plugin-relay/macro';
import _ from 'lodash';
import { useTranslation } from 'react-i18next';
import { useLazyLoadQuery } from 'react-relay';

interface KeypairResourcePolicySelectorProps extends SelectProps {}

const KeypairResourcePolicySelector: React.FC<
KeypairResourcePolicySelectorProps
> = ({ ...selectProps }) => {
const { t } = useTranslation();
const [value, setValue] = useControllableState<string>({
value: selectProps.value,
onChange: selectProps.onChange,
});

const { keypair_resource_policies } =
useLazyLoadQuery<KeypairResourcePolicySelectorQuery>(
graphql`
query KeypairResourcePolicySelectorQuery {
keypair_resource_policies {
name
}
}
`,
{},
{ fetchPolicy: 'store-and-network' },
);

return (
<Select
showSearch
placeholder={t('credential.SelectPolicy')}
options={_.map(keypair_resource_policies, (policy) => {
return {
value: policy?.name,
label: policy?.name,
};
}).sort((a, b) => localeCompare(a?.label, b?.label))}
{...selectProps}
value={value}
onChange={(value, option) => {
setValue(value, option);
}}
/>
);
};

export default KeypairResourcePolicySelector;
Loading

0 comments on commit 8d3b258

Please sign in to comment.