Skip to content

Commit

Permalink
feat(FR-367): add keypair resource policy info modal
Browse files Browse the repository at this point in the history
  • Loading branch information
agatha197 committed Jan 14, 2025
1 parent b8ee0c8 commit c4ba20a
Show file tree
Hide file tree
Showing 26 changed files with 448 additions and 42 deletions.
121 changes: 121 additions & 0 deletions react/src/components/AllowedVfolderHostsWithPermissionModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import Flex from './Flex';
import { AllowedVfolderHostsWithPermissionModalQuery } from './__generated__/AllowedVfolderHostsWithPermissionModalQuery.graphql';
import { CheckCircleFilled, StopFilled } from '@ant-design/icons';
import { Modal, ModalProps, Table, Tag, theme } from 'antd';
import graphql from 'babel-plugin-relay/macro';
import _ from 'lodash';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useLazyLoadQuery } from 'react-relay';

interface AllowedVfolderHostsWithPermissionModalProps extends ModalProps {
allowedVfolderHosts: {
[key: string]: Array<string>;
};
}

const AllowedVfolderHostsWithPermissionModal: React.FC<
AllowedVfolderHostsWithPermissionModalProps
> = ({ allowedVfolderHosts }) => {
const { t } = useTranslation();
const { token } = theme.useToken();
const [storageHost, setStorageHost] = React.useState<string | null>();

const { vfolder_host_permissions } =
useLazyLoadQuery<AllowedVfolderHostsWithPermissionModalQuery>(
graphql`
query AllowedVfolderHostsWithPermissionModalQuery {
vfolder_host_permissions {
vfolder_host_permission_list
}
}
`,
{},
);

const getColor = (vfolderHost: string) => {
if (
_.isEqual(
new Set(allowedVfolderHosts[vfolderHost]),
new Set(vfolder_host_permissions?.vfolder_host_permission_list || null),
)
) {
return 'green';
} else if (allowedVfolderHosts[vfolderHost]?.length > 0) {
return 'gold';
} else {
return;
}
};

return (
<>
{_.map(_.keys(allowedVfolderHosts), (storageHost) => (
<Tag
key={storageHost}
color={getColor(storageHost)}
onClick={() => {
setStorageHost(storageHost);
}}
style={{ cursor: 'pointer' }}
>
{storageHost}
</Tag>
))}
<Modal
title={`${storageHost} ${t('data.explorer.Permission')}`}
open={!_.isEmpty(storageHost)}
onCancel={() => setStorageHost(null)}
footer={null}
>
<Table
pagination={false}
size="small"
dataSource={_.map(
vfolder_host_permissions?.vfolder_host_permission_list,
(permission) => ({
key: permission,
permission,
isAllowed: _.includes(
_.get(allowedVfolderHosts, storageHost || ''),
permission,
) ? (
<Flex justify="center">
<CheckCircleFilled
style={{
color: token.colorSuccess,
fontSize: token.fontSizeLG,
}}
/>
</Flex>
) : (
<Flex justify="center">
<StopFilled
style={{
color: token.colorError,
fontSize: token.fontSizeLG,
}}
/>
</Flex>
),
}),
)}
columns={[
{
title: t('data.explorer.Permission'),
dataIndex: 'permission',
key: 'permission',
},
{
title: t('data.explorer.Allowed'),
dataIndex: 'isAllowed',
key: 'isAllowed',
},
]}
/>
</Modal>
</>
);
};

export default AllowedVfolderHostsWithPermissionModal;
194 changes: 194 additions & 0 deletions react/src/components/KeypairResourcePolicyInfoModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { filterEmptyItem } from '../helper';
import { useSuspendedBackendaiClient } from '../hooks';
import AllowedVfolderHostsWithPermissionModal from './AllowedVfolderHostsWithPermissionModal';
import BAIModal, { BAIModalProps } from './BAIModal';
import Flex from './Flex';
import ResourceNumber from './ResourceNumber';
import { KeypairResourcePolicyInfoModalFragment$key } from './__generated__/KeypairResourcePolicyInfoModalFragment.graphql';
import { Descriptions, Typography } from 'antd';
import { createStyles } from 'antd-style';
import { DescriptionsItemType } from 'antd/es/descriptions';
import graphql from 'babel-plugin-relay/macro';
import dayjs from 'dayjs';
import _ from 'lodash';
import { useTranslation } from 'react-i18next';
import { useFragment } from 'react-relay';

const useStyles = createStyles(({ css }) => ({
description: css`
.ant-descriptions-row {
border: none !important;
}
.ant-descriptions-view > table {
width: fit-content !important;
}
`,
}));

interface InfoModalProps extends BAIModalProps {
open: boolean;
onRequestClose: () => void;
resourcePolicyFrgmt: KeypairResourcePolicyInfoModalFragment$key | null;
}

const KeypairResourcePoliciesInfoModal: React.FC<InfoModalProps> = ({
open,
onRequestClose,
resourcePolicyFrgmt,
...modalProps
}) => {
const { styles } = useStyles();
const { t } = useTranslation();
const baiClient = useSuspendedBackendaiClient();

const resourcePolicy = useFragment(
graphql`
fragment KeypairResourcePolicyInfoModalFragment on KeyPairResourcePolicy {
name
created_at
default_for_unspecified
total_resource_slots
max_session_lifetime
max_concurrent_sessions
max_containers_per_session
idle_timeout
allowed_vfolder_hosts
max_pending_session_count @since(version: "24.03.4")
max_concurrent_sftp_sessions @since(version: "24.03.4")
max_pending_session_resource_slots @since(version: "24.03.4")
}
`,
resourcePolicyFrgmt,
);

const descriptionItems: DescriptionsItemType[] = filterEmptyItem([
{
label: t('resourcePolicy.Name'),
children: (
<Typography.Text copyable>{resourcePolicy?.name}</Typography.Text>
),
},
{
label: t('resourcePolicy.DefaultForUnspecified'),
children: resourcePolicy?.default_for_unspecified || '∞',
},
{
label: t('resourcePolicy.CreatedAt'),
children: dayjs(resourcePolicy?.created_at).format('lll') || '∞',
},
{
label: t('resourcePolicy.Resources'),
children: resourcePolicy?.total_resource_slots ? (
!_.isEmpty(JSON.parse(resourcePolicy?.total_resource_slots)) ? (
<Descriptions className={styles.description} size="small" column={2}>
<Descriptions.Item>
<Flex direction="column" align="start" style={{ width: '100%' }}>
{_.map(
JSON.parse(resourcePolicy?.total_resource_slots),
(v, type) => (
<ResourceNumber
key={type}
type={type}
value={_.toString(v)}
/>
),
)}
</Flex>
</Descriptions.Item>
</Descriptions>
) : (
'∞'
)
) : (
'∞'
),
},
{
label: t('resourcePolicy.StorageNodes'),
children: !_.isEmpty(
JSON.parse(resourcePolicy?.allowed_vfolder_hosts || '{}'),
) ? (
<AllowedVfolderHostsWithPermissionModal
allowedVfolderHosts={JSON.parse(
resourcePolicy?.allowed_vfolder_hosts || '{}',
)}
/>
) : (
'∞'
),
},
{
label: t('resourcePolicy.Concurrency'),
children: resourcePolicy?.max_concurrent_sessions || '∞',
},
{
label: t('resourcePolicy.ClusterSize'),
children: resourcePolicy?.max_containers_per_session,
},
{
label: t('resourcePolicy.IdleTimeout'),
children: resourcePolicy?.idle_timeout || '∞',
},
{
label: t('session.MaxSessionLifetime'),
children: resourcePolicy?.max_session_lifetime || '∞',
},
baiClient?.supports('max-pending-session-count') && {
label: t('resourcePolicy.MaxPendingSessionCount'),
children:
_.isNaN(resourcePolicy?.max_pending_session_count) ||
_.isEmpty(resourcePolicy?.max_pending_session_count)
? '∞'
: resourcePolicy?.max_pending_session_count,
},
baiClient?.supports('max-concurrent-sftp-sessions') && {
label: t('resourcePolicy.MaxConcurrentSFTPSessions'),
children: resourcePolicy?.max_concurrent_sftp_sessions || '∞',
},
baiClient?.supports('max-pending-session-resource-slots') && {
label: t('resourcePolicy.MaxPendingSessionResourceSlots'),
children: resourcePolicy?.max_pending_session_resource_slots ? (
!_.isEmpty(
JSON.parse(resourcePolicy?.max_pending_session_resource_slots),
) ? (
<Descriptions className={styles.description} size="small" column={2}>
<Descriptions.Item>
<Flex direction="column" align="start">
{_.map(
JSON.parse(
resourcePolicy?.max_pending_session_resource_slots,
),
(v, type) => (
<ResourceNumber
key={type}
type={type}
value={_.toString(v)}
/>
),
)}
</Flex>
</Descriptions.Item>
</Descriptions>
) : (
'∞'
)
) : (
'∞'
),
},
]);

return (
<BAIModal
open={open}
onCancel={() => onRequestClose()}
footer={null}
title={`${t('resourcePolicy.ResourcePolicy')}: '${resourcePolicy?.name}'`}
{...modalProps}
>
<Descriptions bordered column={1} items={descriptionItems} />
</BAIModal>
);
};

export default KeypairResourcePoliciesInfoModal;
28 changes: 28 additions & 0 deletions react/src/components/KeypairResourcePolicyList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import { exportCSVWithFormattingRules } from '../helper/csv-util';
import { useSuspendedBackendaiClient, useUpdatableState } from '../hooks';
import { useHiddenColumnKeysSetting } from '../hooks/useHiddenColumnKeysSetting';
import Flex from './Flex';
import KeypairResourcePolicyInfoModal from './KeypairResourcePolicyInfoModal';
import KeypairResourcePolicySettingModal from './KeypairResourcePolicySettingModal';
import ResourceNumber from './ResourceNumber';
import TableColumnsSettingModal from './TableColumnsSettingModal';
import { KeypairResourcePolicyInfoModalFragment$key } from './__generated__/KeypairResourcePolicyInfoModalFragment.graphql';
import { KeypairResourcePolicyListMutation } from './__generated__/KeypairResourcePolicyListMutation.graphql';
import {
KeypairResourcePolicyListQuery,
Expand All @@ -20,6 +22,7 @@ import { KeypairResourcePolicySettingModalFragment$key } from './__generated__/K
import {
DeleteOutlined,
DownOutlined,
InfoCircleOutlined,
PlusOutlined,
ReloadOutlined,
SettingOutlined,
Expand Down Expand Up @@ -66,6 +69,10 @@ const KeypairResourcePolicyList: React.FC<KeypairResourcePolicyListProps> = (
useState<string>();
const [editingKeypairResourcePolicy, setEditingKeypairResourcePolicy] =
useState<KeypairResourcePolicySettingModalFragment$key | null>();
const [currentResourcePolicy, setCurrentResourcePolicy] =
useState<KeypairResourcePolicyInfoModalFragment$key | null>(null);
const [isPendingInfoModalOpen, startInfoModalOpenTransition] =
useTransition();

const baiClient = useSuspendedBackendaiClient();

Expand All @@ -84,6 +91,7 @@ const KeypairResourcePolicyList: React.FC<KeypairResourcePolicyListProps> = (
max_pending_session_count @since(version: "24.03.4")
max_concurrent_sftp_sessions @since(version: "24.03.4")
...KeypairResourcePolicySettingModalFragment
...KeypairResourcePolicyInfoModalFragment
}
}
`,
Expand Down Expand Up @@ -230,6 +238,16 @@ const KeypairResourcePolicyList: React.FC<KeypairResourcePolicyListProps> = (
fixed: 'right',
render: (text, row) => (
<Flex direction="row" align="stretch">
<Button
type="text"
size="large"
icon={<InfoCircleOutlined style={{ color: token.colorSuccess }} />}
onClick={() => {
startInfoModalOpenTransition(() => {
setCurrentResourcePolicy(row || null);
});
}}
/>
<Button
type="text"
size="large"
Expand Down Expand Up @@ -472,6 +490,16 @@ const KeypairResourcePolicyList: React.FC<KeypairResourcePolicyListProps> = (
}}
/>
</Suspense>
<Suspense>
<KeypairResourcePolicyInfoModal
open={!!currentResourcePolicy || isPendingInfoModalOpen}
onRequestClose={() => {
setCurrentResourcePolicy(null);
}}
loading={isPendingInfoModalOpen}
resourcePolicyFrgmt={currentResourcePolicy || null}
/>
</Suspense>
</Flex>
);
};
Expand Down
2 changes: 1 addition & 1 deletion react/src/components/ResourceNumber.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const ResourceNumber: React.FC<ResourceNumberProps> = ({
? '~∞'
: `~${formatAmount(max)}`}
</Typography.Text>
<Typography.Text type="secondary">
<Typography.Text type="secondary" style={{ whiteSpace: 'nowrap' }}>
{mergedResourceSlots?.[type]?.display_unit || ''}
</Typography.Text>
{type === 'mem' && opts?.shmem && opts?.shmem > 0 ? (
Expand Down
Loading

0 comments on commit c4ba20a

Please sign in to comment.