diff --git a/web/console/src/modules/cluster/components/resource/resourceEdition/EditResourceContainerItem.tsx b/web/console/src/modules/cluster/components/resource/resourceEdition/EditResourceContainerItem.tsx index 9736b5760..a20eeb8d1 100644 --- a/web/console/src/modules/cluster/components/resource/resourceEdition/EditResourceContainerItem.tsx +++ b/web/console/src/modules/cluster/components/resource/resourceEdition/EditResourceContainerItem.tsx @@ -32,6 +32,7 @@ import { EditResourceContainerAdvancedPanel } from './EditResourceContainerAdvan import { EditResourceContainerEnvItem } from './EditResourceContainerEnvItem'; import { EditResourceContainerLimitItem } from './EditResourceContainerLimitItem'; import { EditResourceContainerMountItem } from './EditResourceContainerMountItem'; +import { RegistrySelectDialog, RegistryTagSelectDialog } from './registrySelect'; interface ContainerItemProps extends RootProps { /** 容器的id */ @@ -43,6 +44,7 @@ const mapDispatchToProps = dispatch => @connect(state => state, mapDispatchToProps) export class EditResourceContainerItem extends React.Component { + state: Readonly<{ tags: any[] }> = { tags: null }; render() { let { actions, subRoot, cKey, clusterVersion, cluster } = this.props, { workloadEdit, addons } = subRoot, @@ -132,22 +134,39 @@ export class EditResourceContainerItem extends React.Component { actions.editWorkload.updateContainer({ registry: e.target.value }, cKey); + + this.setState({ tags: null }); }} onBlur={e => actions.validate.workload.validateRegistrySelection(e.target.value, cKey)} /> + + { + actions.editWorkload.updateContainer({ registry }, cKey); + + this.setState({ tags }); + }} + />
{ actions.editWorkload.updateContainer({ tag: e.target.value }, cKey); }} /> + + {this.state.tags && ( + actions.editWorkload.updateContainer({ tag }, cKey)} + /> + )}
diff --git a/web/console/src/modules/cluster/components/resource/resourceEdition/registrySelect/index.ts b/web/console/src/modules/cluster/components/resource/resourceEdition/registrySelect/index.ts new file mode 100644 index 000000000..b46cf003b --- /dev/null +++ b/web/console/src/modules/cluster/components/resource/resourceEdition/registrySelect/index.ts @@ -0,0 +1,3 @@ +export { RegistrySelectDialog } from './registrySelectDialog'; + +export { RegistryTagSelectDialog } from './registryTagSelectDialog'; diff --git a/web/console/src/modules/cluster/components/resource/resourceEdition/registrySelect/registrySelectDialog.tsx b/web/console/src/modules/cluster/components/resource/resourceEdition/registrySelect/registrySelectDialog.tsx new file mode 100644 index 000000000..d9890312a --- /dev/null +++ b/web/console/src/modules/cluster/components/resource/resourceEdition/registrySelect/registrySelectDialog.tsx @@ -0,0 +1,158 @@ +import React, { useMemo, useState } from 'react'; +import { Button, Modal, Table, SearchBox, TableColumn, Text } from 'tea-component'; +import { fetchRepositoryList } from '@src/webApi/registry'; +import { useRequest } from 'ahooks'; +import { fetchDockerRegUrl } from '@src/modules/registry/WebAPI'; + +const { filterable, scrollable, radioable } = Table.addons; + +const ALL_VALUE = '__ALL__'; + +export const RegistrySelectDialog = ({ onConfirm }) => { + const [visible, setVisible] = useState(false); + + const [selectedRepo, setSelectedRepo] = useState(null); + + const [searchValue, setSearchValue] = useState(''); + + const [visibilityType, setVisibilityType] = useState(ALL_VALUE); + + const [selectedNamespaceList, setSelectedNamespaceList] = useState([]); + + const { data: repoBaseUrl } = useRequest(async () => { + const res = await fetchDockerRegUrl(); + + console.log('repoBaseUrl---->', res); + + return res; + }); + + const { data: _repositoryList } = useRequest(async () => { + const res = await fetchRepositoryList(); + + return res?.items ?? []; + }); + + const repositoryList = useMemo(() => { + return ( + _repositoryList + ?.filter(repo => repo?.spec?.name?.includes(searchValue)) + ?.filter(repo => visibilityType === ALL_VALUE || repo?.spec?.visibility === visibilityType) + ?.filter( + repo => selectedNamespaceList?.length === 0 || selectedNamespaceList?.includes(repo?.spec?.namespaceName) + ) ?? [] + ); + }, [_repositoryList, searchValue, visibilityType, selectedNamespaceList]); + + const namespaceOptions = useMemo(() => { + return _repositoryList?.map(repo => ({ value: repo?.spec?.namespaceName })) ?? []; + }, [_repositoryList]); + + const columns: TableColumn[] = [ + { + key: 'spec.name', + header: '名称' + }, + + { + key: 'spec.visibility', + header: '类型' + }, + + { + key: 'spec.namespaceName', + header: '命名空间' + }, + + { + key: 'spec.resourceVersion', + header: '仓库地址', + render(repo) { + return {`${repoBaseUrl}/${repo?.spec?.namespaceName}/${repo?.spec?.name}`}; + } + } + ]; + + return ( + <> + + + setVisible(false)}> + + + setSearchValue(value)} /> + + + setVisibilityType(value), + all: { + value: ALL_VALUE, + text: '全部' + }, + // 选项列表 + options: [ + { value: 'Public', text: '公有' }, + { value: 'Private', text: '私有' } + ] + }), + + filterable({ + type: 'multiple', + column: 'spec.namespaceName', + value: selectedNamespaceList, + onChange: value => { + setSelectedNamespaceList(value); + }, + all: { + value: ALL_VALUE, + text: '全部' + }, + options: namespaceOptions + }), + + scrollable({ + maxHeight: 500 + }), + + radioable({ + value: selectedRepo, + onChange(key) { + setSelectedRepo(key); + }, + rowSelect: true + }) + ]} + /> + + + + + + + + + + ); +}; diff --git a/web/console/src/modules/cluster/components/resource/resourceEdition/registrySelect/registryTagSelectDialog.tsx b/web/console/src/modules/cluster/components/resource/resourceEdition/registrySelect/registryTagSelectDialog.tsx new file mode 100644 index 000000000..34ac6b9c4 --- /dev/null +++ b/web/console/src/modules/cluster/components/resource/resourceEdition/registrySelect/registryTagSelectDialog.tsx @@ -0,0 +1,71 @@ +import React, { useState } from 'react'; +import { Button, Modal, Table, Checkbox, TableColumn, Text } from 'tea-component'; + +const { scrollable, radioable } = Table.addons; + +export const RegistryTagSelectDialog = ({ tags, onConfirm }) => { + const [visible, setVisible] = useState(false); + + const [selectedTag, setSelectedTag] = useState(null); + + const columns: TableColumn[] = [ + { + key: 'name', + header: '镜像版本' + }, + + { + key: 'digest', + header: '摘要(SHA256)', + render({ digest }) { + return {digest}; + } + } + ]; + + return ( + <> + + + setVisible(false)}> + +
+ + + + + + + + + + ); +}; diff --git a/web/console/src/modules/registry/components/repo/NamespaceDisplayNameEditor.tsx b/web/console/src/modules/registry/components/repo/NamespaceDisplayNameEditor.tsx new file mode 100644 index 000000000..f25fb4512 --- /dev/null +++ b/web/console/src/modules/registry/components/repo/NamespaceDisplayNameEditor.tsx @@ -0,0 +1,53 @@ +import React, { useState } from 'react'; +import { Button, Modal, Form, TextArea } from 'tea-component'; +import { registryApi } from '@src/webApi'; + +interface INamespaceDisplayNameEditorProps { + value: string; + name: string; + onSuccess: () => void; +} + +export const NamespaceDisplayNameEditor = ({ value, name, onSuccess }: INamespaceDisplayNameEditorProps) => { + const [visible, _setVisible] = useState(false); + + function setVisible(visible: boolean) { + _setVisible(visible); + + setDisplayName(value); + } + + const [displayName, setDisplayName] = useState(value); + + async function handleSubmit() { + await registryApi.modifyNamespaceDisplayName({ name, displayName }); + + onSuccess(); + + setVisible(false); + } + + return ( + <> +