From dd0ca4bcaf8a012b5fbd1e6de118a5d8493e339d Mon Sep 17 00:00:00 2001 From: ssongliu <73214554+ssongliu@users.noreply.github.com> Date: Wed, 26 Apr 2023 15:18:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9B=E5=BB=BA=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E7=AB=AF=E5=8F=A3=E6=94=AF=E6=8C=81=E5=A4=9A?= =?UTF-8?q?=E7=A7=8D=E5=BD=A2=E5=BC=8F=20(#790)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/dto/container.go | 6 +- backend/app/service/container.go | 19 +++- frontend/src/api/interface/container.ts | 7 +- frontend/src/lang/modules/en.ts | 5 +- frontend/src/lang/modules/zh.ts | 5 +- .../container/container/create/index.vue | 96 +++++++++++++++---- 6 files changed, 108 insertions(+), 30 deletions(-) diff --git a/backend/app/dto/container.go b/backend/app/dto/container.go index 323c95456..40035333b 100644 --- a/backend/app/dto/container.go +++ b/backend/app/dto/container.go @@ -63,8 +63,10 @@ type VolumeHelper struct { Mode string `json:"mode"` } type PortHelper struct { - ContainerPort int `json:"containerPort"` - HostPort int `json:"hostPort"` + HostIP string `json:"hostIP"` + HostPort string `json:"hostPort"` + ContainerPort string `json:"containerPort"` + Protocol string `json:"protocol"` } type ContainerLog struct { diff --git a/backend/app/service/container.go b/backend/app/service/container.go index a8fb16631..b96d43847 100644 --- a/backend/app/service/container.go +++ b/backend/app/service/container.go @@ -168,8 +168,19 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) { func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error { if len(req.ExposedPorts) != 0 { for _, port := range req.ExposedPorts { - if common.ScanPort(port.HostPort) { - return buserr.WithDetail(constant.ErrPortInUsed, port.HostPort, nil) + if strings.Contains(port.HostPort, "-") { + portStart, _ := strconv.Atoi(strings.Split(port.HostPort, "-")[0]) + portEnd, _ := strconv.Atoi(strings.Split(port.HostPort, "-")[1]) + for i := portStart; i <= portEnd; i++ { + if common.ScanPort(i) { + return buserr.WithDetail(constant.ErrPortInUsed, i, nil) + } + } + } else { + portItem, _ := strconv.Atoi(port.HostPort) + if common.ScanPort(portItem) { + return buserr.WithDetail(constant.ErrPortInUsed, portItem, nil) + } } } } @@ -202,8 +213,8 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error { if len(req.ExposedPorts) != 0 { hostConf.PortBindings = make(nat.PortMap) for _, port := range req.ExposedPorts { - bindItem := nat.PortBinding{HostPort: strconv.Itoa(port.HostPort)} - hostConf.PortBindings[nat.Port(fmt.Sprintf("%d/tcp", port.ContainerPort))] = []nat.PortBinding{bindItem} + bindItem := nat.PortBinding{HostPort: port.HostPort, HostIP: port.HostIP} + hostConf.PortBindings[nat.Port(fmt.Sprintf("%s/%s", port.ContainerPort, port.Protocol))] = []nat.PortBinding{bindItem} } } if len(req.Volumes) != 0 { diff --git a/frontend/src/api/interface/container.ts b/frontend/src/api/interface/container.ts index b6e4c2648..8453c2d99 100644 --- a/frontend/src/api/interface/container.ts +++ b/frontend/src/api/interface/container.ts @@ -27,8 +27,11 @@ export namespace Container { restartPolicy: string; } export interface Port { - containerPort: number; - hostPort: number; + host: string; + hostIP: string; + containerPort: string; + hostPort: string; + protocol: string; } export interface Volume { sourceDir: string; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index b5cc7df46..eec7a8cdc 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -455,10 +455,11 @@ const message = { containerTerminal: 'Terminal', port: 'Port', + server: 'Host', + serverExample: 'e.g. 80, 80-88, ip:80 or ip:80-88', + contianerExample: 'e.g. 80 or 80-88', exposePort: 'Expose port', exposeAll: 'Expose all', - containerPort: 'Container port', - serverPort: 'Host port', cmd: 'Command', cmdHelper: 'Example: echo "hello"', autoRemove: 'Auto remove', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 0ac9d4927..fa96d2d89 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -472,10 +472,11 @@ const message = { emptyUser: '为空时,将使用容器默认的用户登录', port: '端口', + server: '服务器', + serverExample: '例如: 80, 80-88, ip:80 或者 ip:80-88', + contianerExample: '例如: 80 或者 80-88', exposePort: '暴露端口', exposeAll: '暴露所有', - containerPort: '容器端口', - serverPort: '服务器端口', cmd: '启动命令', cmdHelper: '例:echo "hello"', autoRemove: '容器退出后自动删除容器', diff --git a/frontend/src/views/container/container/create/index.vue b/frontend/src/views/container/container/create/index.vue index 2895caaa7..3cefcda64 100644 --- a/frontend/src/views/container/container/create/index.vue +++ b/frontend/src/views/container/container/create/index.vue @@ -29,33 +29,38 @@ - - + - - +
- + + - + + + +
- + - + + + + + + {{ $t('commons.button.delete') }} @@ -204,6 +209,7 @@ import DrawerHeader from '@/components/drawer-header/index.vue'; import { listImage, listVolume, createContainer } from '@/api/modules/container'; import { Container } from '@/api/interface/container'; import { MsgError, MsgSuccess } from '@/utils/message'; +import { checkIp, checkPort } from '@/utils/util'; const loading = ref(false); @@ -277,8 +283,11 @@ const formRef = ref(); const handlePortsAdd = () => { let item = { - containerPort: null, - hostPort: null, + host: '', + hostIP: '', + containerPort: '', + hostPort: '', + protocol: 'tcp', }; form.exposedPorts.push(item); }; @@ -327,6 +336,9 @@ const onSubmit = async (formEl: FormInstance | undefined) => { if (form.cmdStr.length !== 0) { form.cmd = form.cmdStr.split(' '); } + if (!checkPortValid()) { + return; + } switch (form.memoryUnit) { case 'KB': form.memory = form.memoryItem * 1024; @@ -352,6 +364,54 @@ const onSubmit = async (formEl: FormInstance | undefined) => { }); }; +const checkPortValid = async () => { + if (form.exposedPorts.length === 0) { + return true; + } + for (const port of form.exposedPorts) { + if (port.host.indexOf(':') !== -1) { + port.hostIP = port.host.split(':')[0]; + if (checkIp(port.hostIP)) { + MsgError(i18n.global.t('firewall.addressFormatError')); + return false; + } + port.hostPort = port.host.split(':')[1]; + } else { + port.hostPort = port.host; + } + if (port.hostPort.indexOf('-') !== -1) { + if (checkPort(port.hostPort.split('-')[0])) { + MsgError(i18n.global.t('firewall.portFormatError')); + return false; + } + if (checkPort(port.hostPort.split('-')[1])) { + MsgError(i18n.global.t('firewall.portFormatError')); + return false; + } + } else { + if (checkPort(port.hostPort)) { + MsgError(i18n.global.t('firewall.portFormatError')); + return false; + } + } + if (port.containerPort.indexOf('-') !== -1) { + if (checkPort(port.containerPort.split('-')[0])) { + MsgError(i18n.global.t('firewall.portFormatError')); + return false; + } + if (checkPort(port.containerPort.split('-')[1])) { + MsgError(i18n.global.t('firewall.portFormatError')); + return false; + } + } else { + if (checkPort(port.containerPort)) { + MsgError(i18n.global.t('firewall.portFormatError')); + return false; + } + } + } + return true; +}; defineExpose({ acceptParams, });