diff --git a/react/src/components/ImageEnvironmentSelectFormItems.tsx b/react/src/components/ImageEnvironmentSelectFormItems.tsx index d7b36d3f5f..8fc325c3a4 100644 --- a/react/src/components/ImageEnvironmentSelectFormItems.tsx +++ b/react/src/components/ImageEnvironmentSelectFormItems.tsx @@ -92,6 +92,7 @@ const ImageEnvironmentSelectFormItems: React.FC< const form = Form.useFormInstance(); const environments = Form.useWatch('environments', { form, preserve: true }); const baiClient = useSuspendedBackendaiClient(); + const supportExtendedImageInfo = baiClient?.supports('extended-image-info'); const [environmentSearch, setEnvironmentSearch] = useState(''); const [versionSearch, setVersionSearch] = useState(''); @@ -111,7 +112,7 @@ const ImageEnvironmentSelectFormItems: React.FC< query ImageEnvironmentSelectFormItemsQuery($installed: Boolean) { images(is_installed: $installed) { id - name + name @deprecatedSince(version: "24.09.1") humanized_name tag registry @@ -127,6 +128,7 @@ const ImageEnvironmentSelectFormItems: React.FC< key value } + namespace @since(version: "24.09.1") } } `, @@ -266,7 +268,9 @@ const ImageEnvironmentSelectFormItems: React.FC< // metadata?.imageInfo[ // getImageMeta(getImageFullName(image) || "").key // ]?.name || image?.name - image?.registry + '/' + image?.name + `${image?.registry}/${ + supportExtendedImageInfo ? image?.namespace : image?.name + }` ); }) .map((images, environmentName) => { @@ -365,7 +369,10 @@ const ImageEnvironmentSelectFormItems: React.FC< if (fullNameMatchedImage) { form.setFieldsValue({ environments: { - environment: fullNameMatchedImage?.name || '', + environment: + (supportExtendedImageInfo + ? fullNameMatchedImage?.namespace + : fullNameMatchedImage?.name) || '', version: getImageFullName(fullNameMatchedImage), image: fullNameMatchedImage, }, @@ -379,7 +386,10 @@ const ImageEnvironmentSelectFormItems: React.FC< .images[0]; form.setFieldsValue({ environments: { - environment: firstInListImage?.name || '', + environment: + (supportExtendedImageInfo + ? firstInListImage?.namespace + : firstInListImage?.name) || '', version: getImageFullName(firstInListImage), image: firstInListImage, }, @@ -393,7 +403,11 @@ const ImageEnvironmentSelectFormItems: React.FC< > {fullNameMatchedImage ? ( { const image_has_cuda_shares_min1_max1: Image = { id: 'id1', - name: 'image1', + namespace: 'image1', + name: undefined, digest: 'digest1', architecture: 'arm64', humanized_name: 'Image 1', diff --git a/react/src/helper/index.tsx b/react/src/helper/index.tsx index 34dca0e8c1..67ae1abee1 100644 --- a/react/src/helper/index.tsx +++ b/react/src/helper/index.tsx @@ -314,7 +314,7 @@ export const getImageFullName = ( image: Image | CommittedImage | EnvironmentImage, ) => { return image - ? `${image.registry}/${image.name}:${image.tag}@${image.architecture}` + ? `${image.registry}/${image.namespace ?? image.name}:${image.tag}@${image.architecture}` : undefined; }; diff --git a/react/src/hooks/useBackendAIImageMetaData.test.tsx b/react/src/hooks/useBackendAIImageMetaData.test.tsx index 1a553e871b..df4ced5a7f 100644 --- a/react/src/hooks/useBackendAIImageMetaData.test.tsx +++ b/react/src/hooks/useBackendAIImageMetaData.test.tsx @@ -108,7 +108,8 @@ describe('useBackendAIImageMetaData', () => { const { key, tags } = getImageMeta( getImageFullName({ - name: 'abc/def/training', + namespace: 'abc/def/training', + name: undefined, humanized_name: 'abc/def/training', tag: '01-py3-abc-v1', registry: '192.168.0.1:7080', diff --git a/react/src/pages/MyEnvironmentPage.tsx b/react/src/pages/MyEnvironmentPage.tsx index 2a573dcec2..2f9c895c19 100644 --- a/react/src/pages/MyEnvironmentPage.tsx +++ b/react/src/pages/MyEnvironmentPage.tsx @@ -7,8 +7,12 @@ import { LangTags, } from '../components/ImageTags'; import TableColumnsSettingModal from '../components/TableColumnsSettingModal'; -import { getImageFullName } from '../helper'; -import { useBackendAIImageMetaData, useUpdatableState } from '../hooks'; +import { getImageFullName, localeCompare } from '../helper'; +import { + useBackendAIImageMetaData, + useSuspendedBackendaiClient, + useUpdatableState, +} from '../hooks'; import { MyEnvironmentPageForgetAndUntagMutation } from './__generated__/MyEnvironmentPageForgetAndUntagMutation.graphql'; import { MyEnvironmentPageQuery, @@ -40,6 +44,8 @@ const MyEnvironmentPage: React.FC = ({ children }) => { const { t } = useTranslation(); const { token } = theme.useToken(); const { message } = App.useApp(); + const baiClient = useSuspendedBackendaiClient(); + const supportExtendedImageInfo = baiClient?.supports('extended-image-info'); const [isOpenColumnsSetting, setIsOpenColumnsSetting] = useState(false); const [selectedTab] = useState('images'); @@ -63,7 +69,7 @@ const MyEnvironmentPage: React.FC = ({ children }) => { query MyEnvironmentPageQuery { customized_images { id - name + name @deprecatedSince(version: "24.09.1") humanized_name tag registry @@ -74,6 +80,7 @@ const MyEnvironmentPage: React.FC = ({ children }) => { value } supported_accelerators + namespace @since(version: "24.09.1") } } `, @@ -106,40 +113,48 @@ const MyEnvironmentPage: React.FC = ({ children }) => { title: t('environment.Registry'), dataIndex: 'registry', key: 'registry', - sorter: (a, b) => - a?.registry && b?.registry ? a.registry.localeCompare(b.registry) : 0, + sorter: (a, b) => localeCompare(a?.registry, b?.registry), }, { title: t('environment.Architecture'), dataIndex: 'architecture', key: 'architecture', - sorter: (a, b) => - a?.architecture && b?.architecture - ? a.architecture.localeCompare(b.architecture) - : 0, - }, - { - title: t('environment.Namespace'), - key: 'namespace', - sorter: (a, b) => { - const namespaceA = getNamespace(getImageFullName(a) || ''); - const namespaceB = getNamespace(getImageFullName(b) || ''); - return namespaceA && namespaceB - ? namespaceA.localeCompare(namespaceB) - : 0; - }, - render: (text, row) => ( - {getNamespace(getImageFullName(row) || '')} - ), + sorter: (a, b) => localeCompare(a?.architecture, b?.architecture), }, + ...(supportExtendedImageInfo + ? [ + { + title: t('environment.Namespace'), + key: 'namespace', + dataIndex: 'namespace', + sorter: (a: CommittedImage, b: CommittedImage) => + localeCompare(a?.namespace, b?.namespace), + }, + ] + : [ + { + title: t('environment.Namespace'), + key: 'name', + dataIndex: 'name', + sorter: (a: CommittedImage, b: CommittedImage) => + localeCompare( + getNamespace(getImageFullName(a) || ''), + getNamespace(getImageFullName(b) || ''), + ), + render: (text: string, row: CommittedImage) => ( + {getNamespace(getImageFullName(row) || '')} + ), + }, + ]), { title: t('environment.Language'), key: 'lang', - sorter: (a, b) => { - const langA = getImageLang(getImageFullName(a) || ''); - const langB = getImageLang(getImageFullName(b) || ''); - return langA && langB ? langA.localeCompare(langB) : 0; - }, + sorter: (a, b) => + localeCompare( + getImageLang(getImageFullName(a) || ''), + getImageLang(getImageFullName(b) || ''), + ), + render: (text, row) => ( ), @@ -147,13 +162,11 @@ const MyEnvironmentPage: React.FC = ({ children }) => { { title: t('environment.Version'), key: 'baseversion', - sorter: (a, b) => { - const baseversionA = getBaseVersion(getImageFullName(a) || ''); - const baseversionB = getBaseVersion(getImageFullName(b) || ''); - return baseversionA && baseversionB - ? baseversionA.localeCompare(baseversionB) - : 0; - }, + sorter: (a, b) => + localeCompare( + getBaseVersion(getImageFullName(a) || ''), + getBaseVersion(getImageFullName(b) || ''), + ), render: (text, row) => ( ), @@ -161,13 +174,11 @@ const MyEnvironmentPage: React.FC = ({ children }) => { { title: t('environment.Base'), key: 'baseimage', - sorter: (a, b) => { - const baseimageA = getBaseImage(getImageFullName(a) || ''); - const baseimageB = getBaseImage(getImageFullName(b) || ''); - return baseimageA && baseimageB - ? baseimageA.localeCompare(baseimageB) - : 0; - }, + sorter: (a, b) => + localeCompare( + getBaseImage(getImageFullName(a) || ''), + getBaseImage(getImageFullName(b) || ''), + ), render: (text, row) => ( ), @@ -190,10 +201,7 @@ const MyEnvironmentPage: React.FC = ({ children }) => { b?.labels as { key: string; value: string }[], )[0] || '' : ''; - if (requirementA === '' && requirementB === '') return 0; - if (requirementA === '') return -1; - if (requirementB === '') return 1; - return requirementA.localeCompare(requirementB); + return localeCompare(requirementA, requirementB); }, render: (text, row) => row?.tag ? ( @@ -207,8 +215,7 @@ const MyEnvironmentPage: React.FC = ({ children }) => { title: t('environment.Digest'), dataIndex: 'digest', key: 'digest', - sorter: (a, b) => - a?.digest && b?.digest ? a.digest.localeCompare(b.digest) : 0, + sorter: (a, b) => localeCompare(a?.digest || '', b?.digest || ''), }, { title: t('general.Control'), diff --git a/src/lib/backend.ai-client-esm.ts b/src/lib/backend.ai-client-esm.ts index 015e95b573..cf1313405c 100644 --- a/src/lib/backend.ai-client-esm.ts +++ b/src/lib/backend.ai-client-esm.ts @@ -721,6 +721,9 @@ class Client { if (this.isManagerVersionCompatibleWith('24.09.1')) { this._features['extended-image-info'] = true; } + if (this.isManagerVersionCompatibleWith('24.09.1')) { + this._features['extended-image-info'] = true; + } } /**