From 6c80e2eaa4afc9ffc410e3b2413e20baad351fd9 Mon Sep 17 00:00:00 2001 From: agatha197 Date: Tue, 29 Oct 2024 13:43:11 +0900 Subject: [PATCH] feat: new image parsing --- .cspell.json | 10 +- react/data/schema.graphql | 41 +++- react/src/components/DoubleTag.tsx | 4 +- react/src/components/ImageList.tsx | 316 ++++++++++++++++------------- react/src/components/ImageTags.tsx | 13 +- react/src/hooks/index.tsx | 21 +- resources/i18n/de.json | 5 +- resources/i18n/el.json | 5 +- resources/i18n/en.json | 7 +- resources/i18n/es.json | 5 +- resources/i18n/fi.json | 7 +- resources/i18n/fr.json | 5 +- resources/i18n/id.json | 7 +- resources/i18n/it.json | 7 +- resources/i18n/ja.json | 5 +- resources/i18n/ko.json | 5 +- resources/i18n/mn.json | 7 +- resources/i18n/ms.json | 7 +- resources/i18n/pl.json | 5 +- resources/i18n/pt-BR.json | 5 +- resources/i18n/pt.json | 5 +- resources/i18n/ru.json | 5 +- resources/i18n/th.json | 5 +- resources/i18n/tr.json | 7 +- resources/i18n/vi.json | 5 +- resources/i18n/zh-CN.json | 5 +- resources/i18n/zh-TW.json | 5 +- resources/image_metadata.json | 4 +- src/lib/backend.ai-client-esm.ts | 3 + 29 files changed, 338 insertions(+), 193 deletions(-) diff --git a/.cspell.json b/.cspell.json index 2391993bdb..04a05ad133 100644 --- a/.cspell.json +++ b/.cspell.json @@ -5,6 +5,7 @@ "Backendai", "backendaiclient", "backendaioptions", + "baseversion", "cssinjs", "cuda", "FGPU", @@ -19,17 +20,14 @@ "RNGD", "shmem", "superadmin", + "textbox", + "vaadin", "vfolder", "vfolders", "Warboy", "webcomponent", "webui", - "wsproxy", - "vfolders", - "vfolder", - "filebrowser", - "vaadin", - "textbox" + "wsproxy" ], "flagWords": [ "데이터레이크", diff --git a/react/data/schema.graphql b/react/data/schema.graphql index 18912ef865..a2a1eab78b 100644 --- a/react/data/schema.graphql +++ b/react/data/schema.graphql @@ -21,7 +21,18 @@ type Queries { group_node(id: String!): GroupNode """Added in 24.03.0.""" - group_nodes(filter: String, order: String, offset: Int, before: String, after: String, first: Int, last: Int): GroupConnection + group_nodes( + """Added in 24.09.0.""" + filter: String + + """Added in 24.09.0.""" + order: String + offset: Int + before: String + after: String + first: Int + last: Int + ): GroupConnection group( id: UUID! domain_name: String @@ -287,12 +298,24 @@ type ImageNode implements Node { """Added in 24.03.4. The undecoded id value stored in DB.""" row_id: UUID - name: String + name: String @deprecated(reason: "Deprecated since 24.09.1. use `namespace` instead") + + """Added in 24.09.1.""" + namespace: String + + """Added in 24.09.1.""" + base_image_name: String """Added in 24.03.10.""" project: String humanized_name: String tag: String + + """Added in 24.09.1.""" + tags: [KVPair] + + """Added in 24.09.1.""" + version: String registry: String architecture: String is_local: Boolean @@ -504,12 +527,24 @@ type Group { type Image { id: UUID - name: String + name: String @deprecated(reason: "Deprecated since 24.09.1. use `namespace` instead") + + """Added in 24.09.1.""" + namespace: String + + """Added in 24.09.1.""" + base_image_name: String """Added in 24.03.10.""" project: String humanized_name: String tag: String + + """Added in 24.09.1.""" + tags: [KVPair] + + """Added in 24.09.1.""" + version: String registry: String architecture: String is_local: Boolean diff --git a/react/src/components/DoubleTag.tsx b/react/src/components/DoubleTag.tsx index 17725b0db3..289d145dd3 100644 --- a/react/src/components/DoubleTag.tsx +++ b/react/src/components/DoubleTag.tsx @@ -32,7 +32,7 @@ const DoubleTag: React.FC<{ return ( {_.map(objectValues, (objValue, idx) => { - return ( + return !_.isEmpty(objValue.label) ? ( {objValue.label} - ); + ) : null; })} ); diff --git a/react/src/components/ImageList.tsx b/react/src/components/ImageList.tsx index 53a556f165..c3787453f5 100644 --- a/react/src/components/ImageList.tsx +++ b/react/src/components/ImageList.tsx @@ -1,8 +1,13 @@ import Flex from '../components/Flex'; -import { filterNonNullItems, getImageFullName } from '../helper'; -import { useBackendAIImageMetaData, useUpdatableState } from '../hooks'; +import { filterNonNullItems, getImageFullName, localeCompare } from '../helper'; +import { + useBackendAIImageMetaData, + useSuspendedBackendaiClient, + useUpdatableState, +} from '../hooks'; +import DoubleTag from './DoubleTag'; import ImageInstallModal from './ImageInstallModal'; -import { ConstraintTags } from './ImageTags'; +import { BaseImageTags, ConstraintTags, LangTags } from './ImageTags'; import ManageAppsModal from './ManageAppsModal'; import ManageImageResourceLimitModal from './ManageImageResourceLimitModal'; import ResourceNumber from './ResourceNumber'; @@ -14,7 +19,6 @@ import { import CopyButton from './lablupTalkativotUI/CopyButton'; import { AppstoreOutlined, - CopyOutlined, ReloadOutlined, SearchOutlined, SettingOutlined, @@ -37,7 +41,14 @@ const ImageList: React.FC<{ style?: React.CSSProperties }> = ({ style }) => { const [selectedRows, setSelectedRows] = useState([]); const [ , - { getNamespace, getBaseVersion, getLang, getBaseImages, getConstraints }, + { + getNamespace, + getBaseVersion, + getLang, + getBaseImages, + getConstraints, + getBaseImage, + }, ] = useBackendAIImageMetaData(); const { token } = theme.useToken(); const [managingApp, setManagingApp] = useState(null); @@ -52,13 +63,16 @@ const ImageList: React.FC<{ style?: React.CSSProperties }> = ({ style }) => { const [imageSearch, setImageSearch] = useState(''); const [isPendingRefreshTransition, startRefreshTransition] = useTransition(); const [isPendingSearchTransition, startSearchTransition] = useTransition(); + const baiClient = useSuspendedBackendaiClient(); + const supportExtendedImageInfo = + baiClient?.supports('extended-image-info') ?? false; const { images } = useLazyLoadQuery( graphql` query ImageListQuery { images { id - name + name @deprecatedSince(version: "24.09.1.") tag registry architecture @@ -74,6 +88,13 @@ const ImageList: React.FC<{ style?: React.CSSProperties }> = ({ style }) => { min max } + namespace @since(version: "24.09.1.") + base_image_name @since(version: "24.09.1.") + tags @since(version: "24.09.1.") { + key + value + } + version @since(version: "24.09.1.") } } `, @@ -118,140 +139,171 @@ const ImageList: React.FC<{ style?: React.CSSProperties }> = ({ style }) => { ) : null, }, + { + title: t('environment.FullImagePath'), + key: 'fullImagePath', + render: (row) => ( + + + {getImageFullName(row) || ''} + + + + ), + sorter: (a, b) => localeCompare(getImageFullName(a), getImageFullName(b)), + }, { title: t('environment.Registry'), dataIndex: 'registry', key: 'registry', - sorter: (a, b) => - a?.registry && b?.registry ? a.registry.localeCompare(b.registry) : 0, - render: (text, row) => ( - {row.registry} + sorter: (a, b) => localeCompare(a?.registry, b?.registry), + render: (text) => ( + {text} ), }, { title: t('environment.Architecture'), dataIndex: 'architecture', key: 'architecture', - sorter: (a, b) => - a?.architecture && b?.architecture - ? a.architecture.localeCompare(b.architecture) - : 0, - render: (text, row) => ( - - {row.architecture} - - ), - }, - { - title: t('environment.Namespace'), - key: 'namespace', - dataIndex: '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) || '')} - - ), - }, - { - title: t('environment.Language'), - key: 'lang', - dataIndex: 'lang', - sorter: (a, b) => { - const langA = a?.name ? getLang(a?.name) : ''; - const langB = b?.name ? getLang(b?.name) : ''; - return langA && langB ? langA.localeCompare(langB) : 0; - }, - render: (text, row) => ( - - {row.name ? getLang(row.name) : null} - - ), - }, - { - title: t('environment.Version'), - key: 'baseversion', - dataIndex: 'baseversion', - sorter: (a, b) => { - const baseversionA = getBaseVersion(getImageFullName(a) || ''); - const baseversionB = getBaseVersion(getImageFullName(b) || ''); - return baseversionA && baseversionB - ? baseversionA.localeCompare(baseversionB) - : 0; - }, - render: (text, row) => ( - - {getBaseVersion(getImageFullName(row) || '')} - + sorter: (a, b) => localeCompare(a?.architecture, b?.architecture), + render: (text) => ( + {text} ), }, - { - title: t('environment.Base'), - key: 'baseimage', - dataIndex: 'baseimage', - sorter: (a, b) => { - const baseimageA = - !a?.tag || !a?.name ? '' : getBaseImages(a?.tag, a?.name)[0] || ''; - const baseimageB = - !b?.tag || !b?.name ? '' : getBaseImages(b?.tag, b?.name)[0] || ''; - if (baseimageA === '' && baseimageB === '') return 0; - if (baseimageA === '') return -1; - if (baseimageB === '') return 1; - return baseimageA.localeCompare(baseimageB); - }, - render: (text, row) => ( - - {row?.tag && row?.name - ? getBaseImages(row.tag, row.name).map((baseImage) => ( - - - {baseImage} - - - )) - : null} - - ), - }, - { - title: t('environment.Constraint'), - key: 'constraint', - dataIndex: 'constraint', - sorter: (a, b) => { - const requirementA = - a?.tag && b?.labels - ? getConstraints( - a?.tag, - a?.labels as { key: string; value: string }[], - )[0] || '' - : ''; - const requirementB = - b?.tag && b?.labels - ? getConstraints( - b?.tag, - 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); - }, - render: (text, row) => - row?.tag ? ( - - ) : null, - }, + ...(supportExtendedImageInfo + ? [ + { + title: t('environment.Namespace'), + key: 'namespace', + dataIndex: 'namespace', + sorter: (a: EnvironmentImage, b: EnvironmentImage) => + localeCompare(a?.namespace, b?.namespace), + render: (text: string) => ( + {text} + ), + }, + { + title: t('environment.BaseImageName'), + key: 'base_image_name', + dataIndex: 'base_image_name', + sorter: (a: EnvironmentImage, b: EnvironmentImage) => + localeCompare(a?.base_image_name, b?.base_image_name), + render: (text: string, row: EnvironmentImage) => ( + {text} + ), + }, + { + title: t('environment.Version'), + key: 'version', + dataIndex: 'version', + sorter: (a: EnvironmentImage, b: EnvironmentImage) => + localeCompare(a?.version, b?.version), + render: (text: string) => ( + {text} + ), + }, + { + title: t('environment.Tags'), + key: 'tags', + dataIndex: 'tags', + render: (text: Array<{ key: string; value: string }>) => { + return ( + + {_.map(text, (tag) => ( + + ))} + + ); + }, + }, + ] + : [ + { + title: t('environment.Namespace'), + key: 'name', + dataIndex: 'name', + sorter: (a: EnvironmentImage, b: EnvironmentImage) => + localeCompare(getImageFullName(a), getImageFullName(b)), + render: (text: string, row: EnvironmentImage) => ( + + {getNamespace(getImageFullName(row) || '')} + + ), + }, + { + title: t('environment.Language'), + key: 'lang', + dataIndex: 'lang', + sorter: (a: EnvironmentImage, b: EnvironmentImage) => + localeCompare(getLang(a.name ?? ''), getLang(b.name ?? '')), + render: (text: string, row: EnvironmentImage) => ( + + ), + }, + { + title: t('environment.Version'), + key: 'baseversion', + dataIndex: 'baseversion', + sorter: (a: EnvironmentImage, b: EnvironmentImage) => + localeCompare( + getBaseVersion(getImageFullName(a) || ''), + getBaseVersion(getImageFullName(b) || ''), + ), + render: (text: string, row: EnvironmentImage) => ( + + {getBaseVersion(getImageFullName(row) || '')} + + ), + }, + { + title: t('environment.Base'), + key: 'baseimage', + dataIndex: 'baseimage', + sorter: (a: EnvironmentImage, b: EnvironmentImage) => + localeCompare( + getBaseImage(getBaseImage(getImageFullName(a) || '')), + getBaseImage(getBaseImage(getImageFullName(b) || '')), + ), + render: (text: string, row: EnvironmentImage) => ( + + ), + }, + { + title: t('environment.Constraint'), + key: 'constraint', + dataIndex: 'constraint', + sorter: (a: EnvironmentImage, b: EnvironmentImage) => { + const requirementA = + a?.tag && b?.labels + ? getConstraints( + a?.tag, + a?.labels as { key: string; value: string }[], + )[0] || '' + : ''; + const requirementB = + b?.tag && b?.labels + ? getConstraints( + b?.tag, + b?.labels as { key: string; value: string }[], + )[0] || '' + : ''; + return localeCompare(requirementA, requirementB); + }, + render: (text: string, row: EnvironmentImage) => + row?.tag ? ( + + ) : null, + }, + ]), { title: t('environment.Digest'), dataIndex: 'digest', @@ -294,14 +346,6 @@ const ImageList: React.FC<{ style?: React.CSSProperties }> = ({ style }) => { e.stopPropagation(); }} > - } - style={{ color: token.colorPrimary }} - copyable={{ - text: getImageFullName(row) || '', - }} - >