diff --git a/react/data/schema.graphql b/react/data/schema.graphql index ab1837274d..7d9e413b00 100644 --- a/react/data/schema.graphql +++ b/react/data/schema.graphql @@ -34,7 +34,7 @@ type Queries { domain_name: String is_active: Boolean - """Added in 24.03.0.""" + """Added in 24.03.0. Available values: GENERAL, MODEL_STORE""" type: [String] = ["GENERAL"] ): [Group] image( @@ -76,6 +76,12 @@ type Queries { storage_volume(id: String): StorageVolume storage_volume_list(limit: Int!, offset: Int!, filter: String, order: String): StorageVolumeList vfolder(id: String): VirtualFolder + + """Added in 24.03.4.""" + vfolder_node(id: String!): VirtualFolderNode + + """Added in 24.03.4.""" + vfolder_nodes(filter: String, order: String, offset: Int, before: String, after: String, first: Int, last: Int): VirtualFolderConnection vfolder_list(limit: Int!, offset: Int!, filter: String, order: String, domain_name: String, group_id: UUID, access_key: String): VirtualFolderList vfolder_permission_list(limit: Int!, offset: Int!, filter: String, order: String): VirtualFolderPermissionList vfolder_own_list(limit: Int!, offset: Int!, filter: String, order: String, domain_name: String, access_key: String): VirtualFolderList @@ -184,7 +190,7 @@ type ComputeContainer implements Item { """Deprecated since 24.03.0; use image_object.name""" image: String - """Added since 24.03.0""" + """Added in 24.03.0.""" image_object: ImageNode architecture: String registry: String @@ -406,10 +412,10 @@ type Group { integration_id: String resource_policy: String - """Added since 24.03.0.""" + """Added in 24.03.0.""" type: String - """Added since 24.03.0.""" + """Added in 24.03.0.""" container_registry: JSONString scaling_groups: [String] } @@ -565,7 +571,7 @@ type ComputeSession implements Item { occupying_slots: JSONString occupied_slots: JSONString - """Added in 24.03.0""" + """Added in 24.03.0.""" requested_slots: JSONString num_queries: BigInt containers: [ComputeContainer] @@ -593,9 +599,12 @@ type KeyPairResourcePolicy { max_containers_per_session: Int idle_timeout: BigInt allowed_vfolder_hosts: JSONString - max_vfolder_count: Int @deprecated(reason: "Deprecated since 23.09.4") - max_vfolder_size: BigInt @deprecated(reason: "Deprecated since 23.09.4") - max_quota_scope_size: BigInt @deprecated(reason: "Deprecated since 23.09.4") + max_vfolder_count: Int @deprecated(reason: "Deprecated since 23.09.4.") + max_vfolder_size: BigInt @deprecated(reason: "Deprecated since 23.09.4.") + max_quota_scope_size: BigInt @deprecated(reason: "Deprecated since 23.09.6.") + + """Added in 23.03.3.""" + max_concurrent_sftp_sessions: Int """Added in 24.03.4.""" max_pending_session_count: Int @@ -609,20 +618,24 @@ type UserResourcePolicy { name: String! created_at: DateTime! - """Added since 24.03.1. Limitation of the number of user vfolders.""" + """ + Added in 24.03.1 and 23.09.6. Limitation of the number of user vfolders. + """ max_vfolder_count: Int - """Added since 24.03.1. Limitation of the quota size of user vfolders.""" + """ + Added in 24.03.1 and 23.09.2. Limitation of the quota size of user vfolders. + """ max_quota_scope_size: BigInt - max_vfolder_size: BigInt @deprecated(reason: "Deprecated since 23.09.1") + max_vfolder_size: BigInt @deprecated(reason: "Deprecated since 23.09.2.") """ - Added since 23.09.10. Maximum available number of sessions per single model service which the user is in charge of. + Added in 24.03.1 and 23.09.10. Maximum available number of sessions per single model service which the user is in charge of. """ max_session_count_per_model_session: Int """ - Added since 24.03.0. Maximum available number of customized images one can publish to. + Added in 24.03.0. Maximum available number of customized images one can publish to. """ max_customized_image_count: Int } @@ -632,12 +645,16 @@ type ProjectResourcePolicy { name: String! created_at: DateTime! - """Added since 24.03.1. Limitation of the number of project vfolders.""" + """ + Added in 24.03.1 and 23.09.6. Limitation of the number of project vfolders. + """ max_vfolder_count: Int - """Added since 24.03.1. Limitation of the quota size of project vfolders.""" + """ + Added in 24.03.1 and 23.09.2. Limitation of the quota size of project vfolders. + """ max_quota_scope_size: BigInt - max_vfolder_size: BigInt @deprecated(reason: "Deprecated since 23.09.1") + max_vfolder_size: BigInt @deprecated(reason: "Deprecated since 23.09.2.") } type ResourcePreset { @@ -671,10 +688,10 @@ type StorageVolume implements Item { performance_metric: JSONString usage: JSONString - """Added since 24.03.0. Name of the proxy which this volume belongs to.""" + """Added in 24.03.0. Name of the proxy which this volume belongs to.""" proxy: String - """Added since 24.03.0. Name of the storage.""" + """Added in 24.03.0. Name of the storage.""" name: String } @@ -683,6 +700,55 @@ type StorageVolumeList implements PaginatedList { total_count: Int! } +"""Added in 24.03.4.""" +type VirtualFolderNode implements Node { + """The ID of the object""" + id: ID! + + """Added in 24.03.4. UUID type id of DB vfolders row""" + row_id: UUID + host: String + quota_scope_id: String + name: String + user: UUID + user_email: String + group: UUID + group_name: String + creator: String + unmanaged_path: String + usage_mode: String + permission: String + ownership_type: String + max_files: Int + max_size: BigInt + created_at: DateTime + last_used: DateTime + num_files: Int + cur_size: BigInt + cloneable: Boolean + status: String +} + +type VirtualFolderConnection { + """Pagination data for this connection.""" + pageInfo: PageInfo! + + """Contains the nodes in this connection.""" + edges: [VirtualFolderEdge]! + + """Total count of the GQL nodes of the query.""" + count: Int +} + +"""A Relay edge containing a `VirtualFolder` and its cursor.""" +type VirtualFolderEdge { + """The item at the end of the edge""" + node: VirtualFolderNode + + """A cursor for use in pagination""" + cursor: String! +} + type VirtualFolderList implements PaginatedList { items: [VirtualFolder]! total_count: Int! @@ -777,9 +843,9 @@ type PredefinedAtomicPermission { type Endpoint implements Item { id: ID endpoint_id: UUID - image: String @deprecated(reason: "Deprecated since 23.09.9; use `image_object`") + image: String @deprecated(reason: "Deprecated since 23.09.9. use `image_object`") - """Added at 23.09.9""" + """Added in 23.09.9.""" image_object: ImageNode domain: String project: String @@ -787,20 +853,29 @@ type Endpoint implements Item { resource_slots: JSONString url: String model: UUID - model_mount_destiation: String - created_user: UUID @deprecated(reason: "Deprecated since 23.09.8; use `created_user_id`") - """Added at 23.09.8""" + """Added in 24.03.4.""" + model_definition_path: String + model_mount_destiation: String @deprecated(reason: "Deprecated since 24.03.4; use `model_mount_destination` instead") + + """Added in 24.03.4.""" + model_mount_destination: String + + """Added in 24.03.4.""" + extra_mounts: [VirtualFolderNode] + created_user: UUID @deprecated(reason: "Deprecated since 23.09.8. use `created_user_id`") + + """Added in 23.09.8.""" created_user_email: String - """Added at 23.09.8""" + """Added in 23.09.8.""" created_user_id: UUID - session_owner: UUID @deprecated(reason: "Deprecated since 23.09.8; use `session_owner_id`") + session_owner: UUID @deprecated(reason: "Deprecated since 23.09.8. use `session_owner_id`") - """Added at 23.09.8""" + """Added in 23.09.8.""" session_owner_email: String - """Added at 23.09.8""" + """Added in 23.09.8.""" session_owner_id: UUID tag: String startup_command: String @@ -1119,7 +1194,7 @@ type CreateGroup { } input GroupInput { - """Added in 24.03.0.""" + """Added in 24.03.0. Available values: GENERAL, MODEL_STORE""" type: String = "GENERAL" description: String = "" is_active: Boolean = true @@ -1334,7 +1409,7 @@ input ResourceLimitInput { max: String } -"""Added since 24.03.0.""" +"""Added in 24.03.0.""" type ForgetImageById { ok: Boolean msg: String @@ -1390,9 +1465,9 @@ input CreateKeyPairResourcePolicyInput { max_containers_per_session: Int! idle_timeout: BigInt! allowed_vfolder_hosts: JSONString - max_vfolder_count: Int @deprecated(reason: "Deprecated since 23.09.4") - max_vfolder_size: BigInt @deprecated(reason: "Deprecated since 23.09.4") - max_quota_scope_size: BigInt @deprecated(reason: "Deprecated since 23.09.4") + max_vfolder_count: Int @deprecated(reason: "Deprecated since 23.09.4.") + max_vfolder_size: BigInt @deprecated(reason: "Deprecated since 23.09.4.") + max_quota_scope_size: BigInt @deprecated(reason: "Deprecated since 23.09.6.") """Added in 24.03.4.""" max_pending_session_count: Int @@ -1415,9 +1490,9 @@ input ModifyKeyPairResourcePolicyInput { max_containers_per_session: Int idle_timeout: BigInt allowed_vfolder_hosts: JSONString - max_vfolder_count: Int @deprecated(reason: "Deprecated since 23.09.4") - max_vfolder_size: BigInt @deprecated(reason: "Deprecated since 23.09.4") - max_quota_scope_size: BigInt @deprecated(reason: "Deprecated since 23.09.4") + max_vfolder_count: Int @deprecated(reason: "Deprecated since 23.09.4.") + max_vfolder_size: BigInt @deprecated(reason: "Deprecated since 23.09.4.") + max_quota_scope_size: BigInt @deprecated(reason: "Deprecated since 23.09.6.") """Added in 24.03.4.""" max_pending_session_count: Int @@ -1438,19 +1513,24 @@ type CreateUserResourcePolicy { } input CreateUserResourcePolicyInput { - """Added since 24.03.1. Limitation of the number of user vfolders.""" + """ + Added in 24.03.1 and 23.09.6. Limitation of the number of user vfolders. + """ max_vfolder_count: Int - """Added since 24.03.1. Limitation of the quota size of user vfolders.""" + """ + Added in 24.03.1 and 23.09.2. Limitation of the quota size of user vfolders. + """ max_quota_scope_size: BigInt """ - Added since 24.03.1. Maximum available number of sessions per single model service which the user is in charge of. + Added in 24.03.1 and 23.09.10. Maximum available number of sessions per single model service which the user is in charge of. """ max_session_count_per_model_session: Int + max_vfolder_size: BigInt @deprecated(reason: "Deprecated since 23.09.2.") """ - Added since 24.03.0. Maximum available number of customized images one can publish to. + Added in 24.03.0. Maximum available number of customized images one can publish to. """ max_customized_image_count: Int } @@ -1461,19 +1541,23 @@ type ModifyUserResourcePolicy { } input ModifyUserResourcePolicyInput { - """Added since 24.03.1. Limitation of the number of user vfolders.""" + """ + Added in 24.03.1 and 23.09.6. Limitation of the number of user vfolders. + """ max_vfolder_count: Int - """Added since 24.03.1. Limitation of the quota size of user vfolders.""" + """ + Added in 24.03.1 and 23.09.2. Limitation of the quota size of user vfolders. + """ max_quota_scope_size: BigInt """ - Added since 24.03.1. Maximum available number of sessions per single model service which the user is in charge of. + Added in 24.03.1 and 23.09.10. Maximum available number of sessions per single model service which the user is in charge of. """ max_session_count_per_model_session: Int """ - Added since 24.03.0. Maximum available number of customized images one can publish to. + Added in 24.03.0. Maximum available number of customized images one can publish to. """ max_customized_image_count: Int } @@ -1490,11 +1574,16 @@ type CreateProjectResourcePolicy { } input CreateProjectResourcePolicyInput { - """Added since 24.03.1. Limitation of the number of project vfolders.""" + """ + Added in 24.03.1 and 23.09.6. Limitation of the number of project vfolders. + """ max_vfolder_count: Int - """Added since 24.03.1. Limitation of the quota size of project vfolders.""" + """ + Added in 24.03.1 and 23.09.2. Limitation of the quota size of project vfolders. + """ max_quota_scope_size: BigInt + max_vfolder_size: BigInt @deprecated(reason: "Deprecated since 23.09.2.") } type ModifyProjectResourcePolicy { @@ -1503,11 +1592,16 @@ type ModifyProjectResourcePolicy { } input ModifyProjectResourcePolicyInput { - """Added since 24.03.1. Limitation of the number of project vfolders.""" + """ + Added in 24.03.1 and 23.09.6. Limitation of the number of project vfolders. + """ max_vfolder_count: Int - """Added since 24.03.1. Limitation of the quota size of project vfolders.""" + """ + Added in 24.03.1 and 23.09.2. Limitation of the quota size of project vfolders. + """ max_quota_scope_size: BigInt + max_vfolder_size: BigInt @deprecated(reason: "Deprecated since 23.09.2.") } type DeleteProjectResourcePolicy { @@ -1669,7 +1763,7 @@ type ModifyEndpoint { ok: Boolean msg: String - """Added at 23.09.8""" + """Added in 23.09.8.""" endpoint: Endpoint } @@ -1682,11 +1776,23 @@ input ModifyEndpointInput { image: ImageRefType name: String resource_group: String + + """Added in 24.03.4.""" + model_definition_path: String open_to_public: Boolean + + """Added in 24.03.4.""" + extra_mounts: [ExtraMountInput] } input ImageRefType { name: String! registry: String architecture: String -} \ No newline at end of file +} + +"""Added in 24.03.4.""" +input ExtraMountInput { + vfolder_id: String + mount_destination: String +} diff --git a/react/src/components/BAIModal.tsx b/react/src/components/BAIModal.tsx index 7e1d4516ef..eed0564512 100644 --- a/react/src/components/BAIModal.tsx +++ b/react/src/components/BAIModal.tsx @@ -43,6 +43,7 @@ const BAIModal: React.FC = ({ styles, ...modalProps }) => { <> - {notification.message} + {_.isString(notification.message) + ? _.truncate(notification.message, { + length: 200, + }) + : notification.message} - - {notification.description} + + {_.isString(notification.description) + ? _.truncate(notification.description, { + length: 300, + }) + : notification.description} {notification.to ? ( diff --git a/react/src/components/BAIPropertyFilter.tsx b/react/src/components/BAIPropertyFilter.tsx index 0e15f8bc8e..3e13ea06be 100644 --- a/react/src/components/BAIPropertyFilter.tsx +++ b/react/src/components/BAIPropertyFilter.tsx @@ -149,43 +149,45 @@ const BAIPropertyFilter: React.FC = ({ {...otherProps} /> - - {_.map(list, (item, index) => { - return ( - { - remove(index); - }} - style={{ margin: 0 }} - > - {item.propertyLabel}: {trimFilterValue(item.value)} - - ); - })} - {list.length > 1 && ( - - + + + + + displayedColumnKeys?.includes(_.toString(column.key)), + ) as ColumnType[] + } + dataSource={ + project_resource_policies as readonly AnyObject[] | undefined + } + scroll={{ x: 'max-content' }} + pagination={false} + /> + + - - - ) : null - } - title={t('storageHost.ResourcePolicy')} - // bordered={false} - headStyle={{ borderBottom: 'none' }} - style={{ marginBottom: 10 }} - > - {project_resource_policy || user_resource_policy ? ( - - - {project_resource_policy - ? project_resource_policy && - project_resource_policy?.max_quota_scope_size !== -1 - ? humanReadableDecimalSize( - project_resource_policy?.max_quota_scope_size, - ) - : '-' - : user_resource_policy && - user_resource_policy?.max_quota_scope_size !== -1 - ? humanReadableDecimalSize( - user_resource_policy?.max_quota_scope_size, - ) - : '-'} - - - ) : null} - - { - onChangePolicy(); - toggleProjectResourcePolicySettingModal(); - }} - projectResourcePolicyFrgmt={project_resource_policy || null} - /> - { - onChangePolicy(); - toggleUserResourcePolicySettingModal(); - }} - /> - {contextHolder} - - ); -}; - -export default ResourcePolicyCard; diff --git a/react/src/components/ResourcePresetSelect.tsx b/react/src/components/ResourcePresetSelect.tsx index cb3a94f640..f219ded25c 100644 --- a/react/src/components/ResourcePresetSelect.tsx +++ b/react/src/components/ResourcePresetSelect.tsx @@ -1,3 +1,4 @@ +import { localeCompare } from '../helper'; import { useUpdatableState } from '../hooks'; import { useResourceSlots } from '../hooks/backendai'; import Flex from './Flex'; @@ -164,10 +165,14 @@ const ResourcePresetSelect: React.FC = ({ preset, disabled: disabled, }; - // sort by disabled - }).sort((a, b) => - a.disabled === b.disabled ? 0 : a.disabled ? 1 : -1, - ), + }) + .sort( + ( + a, + b, // by disabled + ) => (a.disabled === b.disabled ? 0 : a.disabled ? 1 : -1), + ) + .sort((a, b) => localeCompare(a.value, b.value)), // by name }, ]} showSearch diff --git a/react/src/components/ServiceLauncherModal.tsx b/react/src/components/ServiceLauncherModal.tsx index bdf1294dd6..0d973ab876 100644 --- a/react/src/components/ServiceLauncherModal.tsx +++ b/react/src/components/ServiceLauncherModal.tsx @@ -20,8 +20,10 @@ import ImageEnvironmentSelectFormItems, { import InputNumberWithSlider from './InputNumberWithSlider'; import VFolderLazyView from './VFolderLazyView'; import VFolderSelect from './VFolderSelect'; +import VFolderTableFormItem from './VFolderTableFormItem'; import { ServiceLauncherModalFragment$key } from './__generated__/ServiceLauncherModalFragment.graphql'; import { ServiceLauncherModalModifyMutation } from './__generated__/ServiceLauncherModalModifyMutation.graphql'; +import { MinusOutlined } from '@ant-design/icons'; import { App, Button, @@ -61,15 +63,22 @@ interface ServiceCreateConfigResourceType { 'warboy.device'?: number | string; 'hyperaccel-lpu.device'?: number | string; } +export interface MountOptionType { + mount_destination?: string; + type?: string; + permission?: string; +} interface ServiceCreateConfigType { model: string; - model_version?: string; - model_mount_destination: string; // default == "/models" + model_version?: number; + model_mount_destination?: string; // default == "/models" + model_definition_path?: string; // default == "model-definition.yaml" environ: object; // environment variable scaling_group: string; resources: ServiceCreateConfigResourceType; resource_opts?: ServiceCreateConfigResourceOptsType; + extra_mounts?: Record; } export interface ServiceCreateType { name: string; @@ -99,6 +108,10 @@ interface ServiceLauncherInput extends ImageEnvironmentFormInput { vFolderName: string; desiredRoutingCount: number; openToPublic: boolean; + modelMountDestination: string; + modelDefinitionPath: string; + vfoldersAliasMap: Record; + mounts: Array; } export type ServiceLauncherFormValue = ServiceLauncherInput & @@ -120,6 +133,7 @@ const ServiceLauncherModal: React.FC = ({ const currentDomain = useCurrentDomainValue(); const formRef = useRef>(null); + const endpoint = useFragment( graphql` fragment ServiceLauncherModalFragment on Endpoint { @@ -132,6 +146,11 @@ const ServiceLauncherModal: React.FC = ({ cluster_size open_to_public model + model_mount_destination @since(version: "24.03.4") + model_definition_path @since(version: "24.03.4") + extra_mounts @since(version: "24.03.4") { + row_id + } image_object @since(version: "23.09.9") { name humanized_name @@ -239,7 +258,28 @@ const ServiceLauncherModal: React.FC = ({ open_to_public: values.openToPublic, config: { model: values.vFolderName, - model_mount_destination: '/models', // FIXME: hardcoded. change it with option later + model_version: 1, // FIXME: hardcoded. change it with option later + ...(baiClient.supports('endpoint-extra-mounts') && { + extra_mounts: _.reduce( + values.mounts, + (acc, key: string) => { + acc[key] = { + ...(values.vfoldersAliasMap[key] && { + mount_destination: values.vfoldersAliasMap[key], + }), + type: 'bind', // FIXME: hardcoded. change it with option later + }; + return acc; + }, + {} as Record, + ), + model_definition_path: values.modelDefinitionPath, + }), + model_mount_destination: + baiClient.supports('endpoint-extra-mounts') && + values.modelMountDestination !== '' + ? values.modelMountDestination + : '/models', environ: {}, // FIXME: hardcoded. change it with option later scaling_group: values.resourceGroup, resources: { @@ -314,6 +354,31 @@ const ServiceLauncherModal: React.FC = ({ supported_accelerators } name + model_definition_path + model_mount_destination + extra_mounts @since(version: "24.03.4") { + id + host + quota_scope_id + name + user + user_email + group + group_name + creator + unmanaged_path + usage_mode + permission + ownership_type + max_files + max_size + created_at + last_used + num_files + cur_size + cloneable + status + } } } } @@ -354,13 +419,26 @@ const ServiceLauncherModal: React.FC = ({ ), values, ), + extra_mounts: values.mounts.map((vfolder) => { + return { + vfolder_id: vfolder, + ...(values.vfoldersAliasMap[vfolder] && { + mount_destination: values.vfoldersAliasMap[vfolder], + }), + }; + }), name: values.serviceName, resource_group: values.resourceGroup, + model_definition_path: values.modelDefinitionPath, }, }; commitModifyEndpoint({ variables: mutationVariables, onCompleted: (res, errors) => { + if (!res.modify_endpoint?.ok) { + message.error(res.modify_endpoint?.msg); + return; + } if (errors && errors?.length > 0) { const errorMsgList = errors.map((error) => error.message); for (let error of errorMsgList) { @@ -444,7 +522,6 @@ const ServiceLauncherModal: React.FC = ({ // Apply any operation after clicking Cancel or close button button const handleCancel = () => { - formRef.current?.resetFields(); onRequestClose(); }; @@ -475,6 +552,7 @@ const ServiceLauncherModal: React.FC = ({ ? t('modelService.EditModelService') : t('modelService.StartNewServing') } + width={700} destroyOnClose onOk={handleOk} onCancel={handleCancel} @@ -524,7 +602,6 @@ const ServiceLauncherModal: React.FC = ({
= ({ version: `${endpoint?.image_object?.registry}/${endpoint?.image_object?.name}:${endpoint?.image_object?.tag}@${endpoint?.image_object?.architecture}`, image: endpoint?.image_object, }, + vFolderName: endpoint?.model_mount_destination, + mounts: _.map(endpoint?.extra_mounts, (item) => + item?.row_id?.replaceAll('-', ''), + ), + modelMountDestination: endpoint?.model_mount_destination, + modelDefinitionPath: endpoint?.model_definition_path, + // TODO: set mounts alias map according to extra_mounts if possible } : { desiredRoutingCount: 1, @@ -605,7 +689,6 @@ const ServiceLauncherModal: React.FC = ({ > - {/* */} {!endpoint ? ( = ({ ) )} + {baiClient.supports('endpoint-extra-mounts') ? ( + <> + + + + + + + + + + + prev.vFolderName !== cur.vFolderName + } + > + {() => { + return ( + + vf.name !== + formRef.current?.getFieldValue('vFolderName') && + vf.status === 'ready' && + vf.usage_mode !== 'model' + } + tableProps={{ + size: 'small', + }} + /> + ); + }} + + + ) : null} )} = ({ open_to_public: values.openToPublic, config: { model: values.vFolderName, - model_mount_destination: '/models', // FIXME: hardcoded. change it with option later + model_version: 1, // FIXME: hardcoded. change it with option later + ...(baiClient.supports('endpoint-extra-mounts') && { + extra_mounts: _.reduce( + values.mounts, + (acc, key: string) => { + acc[key] = { + ...(values.vfoldersAliasMap[key] && { + mount_destination: values.vfoldersAliasMap[key], + }), + type: 'bind', // FIXME: hardcoded. change it with option later + }; + return acc; + }, + {} as Record, + ), + }), + model_definition_path: values.modelDefinitionPath, + model_mount_destination: baiClient.supports('endpoint-extra-mounts') + ? values.modelMountDestination + : '/models', environ: {}, // FIXME: hardcoded. change it with option later scaling_group: values.resourceGroup, resources: { diff --git a/react/src/components/UserResourcePolicyList.tsx b/react/src/components/UserResourcePolicyList.tsx index 363eb4a532..d78ac9fd87 100644 --- a/react/src/components/UserResourcePolicyList.tsx +++ b/react/src/components/UserResourcePolicyList.tsx @@ -1,15 +1,331 @@ -import React from 'react'; +import { + bytesToGB, + localeCompare, + numberSorterWithInfinityValue, +} from '../helper'; +import { useSuspendedBackendaiClient, useUpdatableState } from '../hooks'; +import Flex from './Flex'; +import TableColumnsSettingModal from './TableColumnsSettingModal'; +import UserResourcePolicySettingModal from './UserResourcePolicySettingModal'; +import { UserResourcePolicyListMutation } from './__generated__/UserResourcePolicyListMutation.graphql'; +import { + UserResourcePolicyListQuery, + UserResourcePolicyListQuery$data, +} from './__generated__/UserResourcePolicyListQuery.graphql'; +import { UserResourcePolicySettingModalFragment$key } from './__generated__/UserResourcePolicySettingModalFragment.graphql'; +import { + DeleteOutlined, + PlusOutlined, + ReloadOutlined, + SettingOutlined, +} from '@ant-design/icons'; +import { useLocalStorageState } from 'ahooks'; +import { App, Button, Popconfirm, Table, theme, Typography } from 'antd'; +import { AnyObject } from 'antd/es/_util/type'; +import { ColumnType } from 'antd/es/table'; +import graphql from 'babel-plugin-relay/macro'; +import dayjs from 'dayjs'; +import _ from 'lodash'; +import React, { useState, useTransition } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useLazyLoadQuery, useMutation } from 'react-relay'; -interface UserResourcePolicyListProps { - // Define your prop types here -} +type UserResourcePolicies = NonNullable< + UserResourcePolicyListQuery$data['user_resource_policies'] +>[number]; -const UserResourcePolicyList: React.FC = ( - props, -) => { - // Component logic goes here +interface UserResourcePolicyListProps {} - return
User
; +const UserResourcePolicyList: React.FC = () => { + const { token } = theme.useToken(); + const { t } = useTranslation(); + const { message } = App.useApp(); + + const [isRefetchPending, startRefetchTransition] = useTransition(); + const [userResourcePolicyFetchKey, updateUserResourcePolicyFetchKey] = + useUpdatableState('initial-fetch'); + const [isCreatingPolicySetting, setIsCreatingPolicySetting] = useState(false); + const [isOpenColumnsSetting, setIsOpenColumnsSetting] = useState(false); + const [inFlightResourcePolicyName, setInFlightResourcePolicyName] = + useState(); + const [editingUserResourcePolicy, setEditingUserResourcePolicy] = + useState(); + + const baiClient = useSuspendedBackendaiClient(); + const supportMaxVfolderCount = baiClient?.supports( + 'max-vfolder-count-in-user-and-project-resource-policy', + ); + const supportMaxQuotaScopeSize = baiClient?.supports('max-quota-scope-size'); + const supportMaxSessionCountPerModelSession = baiClient?.supports( + 'max-session-count-per-model-session', + ); + const supportMaxCustomizedImageCount = baiClient?.supports( + 'max-customized-image-count', + ); + + const { user_resource_policies } = + useLazyLoadQuery( + graphql` + query UserResourcePolicyListQuery { + user_resource_policies { + id + name + created_at + # follows version of https://github.com/lablup/backend.ai/pull/1993 + # --------------- START -------------------- + max_vfolder_count @since(version: "23.09.6") + max_session_count_per_model_session @since(version: "23.09.10") + max_quota_scope_size @since(version: "23.09.2") + # ---------------- END --------------------- + max_customized_image_count @since(version: "24.03.0") + ...UserResourcePolicySettingModalFragment + } + } + `, + {}, + { + fetchPolicy: + userResourcePolicyFetchKey === 'initial-fetch' + ? 'store-and-network' + : 'network-only', + fetchKey: userResourcePolicyFetchKey, + }, + ); + + const [commitDelete, isInflightDelete] = + useMutation(graphql` + mutation UserResourcePolicyListMutation($name: String!) { + delete_user_resource_policy(name: $name) { + ok + msg + } + } + `); + + const columns = _.filter>([ + { + title: t('resourcePolicy.Name'), + dataIndex: 'name', + key: 'name', + fixed: 'left', + sorter: (a, b) => localeCompare(a?.name, b?.name), + }, + supportMaxVfolderCount && { + title: t('resourcePolicy.MaxVFolderCount'), + dataIndex: 'max_vfolder_count', + render: (text) => (_.toNumber(text) === 0 ? '∞' : text), + sorter: (a, b) => + numberSorterWithInfinityValue( + a?.max_vfolder_count, + b?.max_vfolder_count, + 0, + ), + }, + supportMaxSessionCountPerModelSession && { + title: t('resourcePolicy.MaxSessionCountPerModelSession'), + dataIndex: 'max_session_count_per_model_session', + sorter: (a, b) => + (a?.max_session_count_per_model_session ?? 0) - + (b?.max_session_count_per_model_session ?? 0), + }, + supportMaxQuotaScopeSize && { + title: t('resourcePolicy.MaxQuotaScopeSize'), + dataIndex: 'max_quota_scope_size', + render: (text) => (text === -1 ? '∞' : bytesToGB(text)), + sorter: (a, b) => + numberSorterWithInfinityValue( + a?.max_quota_scope_size, + b?.max_quota_scope_size, + -1, + ), + }, + supportMaxCustomizedImageCount && { + title: t('resourcePolicy.MaxCustomizedImageCount'), + dataIndex: 'max_customized_image_count', + sorter: (a, b) => + (a?.max_customized_image_count ?? 0) - + (b?.max_customized_image_count ?? 0), + }, + { + title: 'ID', + dataIndex: 'id', + sorter: (a, b) => localeCompare(a?.id, b?.id), + }, + { + title: t('resourcePolicy.CreatedAt'), + dataIndex: 'created_at', + render: (text) => dayjs(text).format('lll'), + sorter: (a, b) => localeCompare(a?.created_at, b?.created_at), + }, + { + title: t('general.Control'), + fixed: 'right', + render: (text: any, row: UserResourcePolicies) => ( + + + + + +
+ displayedColumnKeys?.includes(_.toString(column.key)), + ) as ColumnType[] + } + dataSource={user_resource_policies as readonly AnyObject[] | undefined} + scroll={{ x: 'max-content' }} + pagination={false} + /> + +