Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: keypair info/setting modal in credential page #2940

Merged
merged 1 commit into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 106 additions & 98 deletions react/src/components/KeypairInfoModal.tsx
Original file line number Diff line number Diff line change
@@ -1,125 +1,133 @@
/**
@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',
},
);
ironAiken2 marked this conversation as resolved.
Show resolved Hide resolved

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}
ironAiken2 marked this conversation as resolved.
Show resolved Hide resolved
{...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 ?? '' }}>
{keypair?.secret_key ? '********' : ''}
</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
Loading