diff --git a/backend/app/api/v1/container.go b/backend/app/api/v1/container.go index 83c93b880..cdcc3e534 100644 --- a/backend/app/api/v1/container.go +++ b/backend/app/api/v1/container.go @@ -489,6 +489,23 @@ func (b *BaseApi) SearchNetwork(c *gin.Context) { }) } +// @Tags Container Network +// @Summary List networks +// @Description 获取容器网络列表 +// @Accept json +// @Produce json +// @Success 200 {array} dto.Options +// @Security ApiKeyAuth +// @Router /containers/network [get] +func (b *BaseApi) ListNetwork(c *gin.Context) { + list, err := containerService.ListNetwork() + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, list) +} + // @Tags Container Network // @Summary Delete network // @Description 删除容器网络 @@ -578,11 +595,10 @@ func (b *BaseApi) SearchVolume(c *gin.Context) { // @Summary List volumes // @Description 获取容器存储卷列表 // @Accept json -// @Param request body dto.PageInfo true "request" // @Produce json -// @Success 200 {object} dto.PageResult +// @Success 200 {array} dto.Options // @Security ApiKeyAuth -// @Router /containers/volume/search [get] +// @Router /containers/volume [get] func (b *BaseApi) ListVolume(c *gin.Context) { list, err := containerService.ListVolume() if err != nil { diff --git a/backend/app/dto/container.go b/backend/app/dto/container.go index eb7b2027b..ca4bc9993 100644 --- a/backend/app/dto/container.go +++ b/backend/app/dto/container.go @@ -39,6 +39,7 @@ type ContainerOperate struct { ContainerID string `json:"containerID"` Name string `json:"name"` Image string `json:"image"` + Network string `json:"network"` PublishAllPorts bool `json:"publishAllPorts"` ExposedPorts []PortHelper `json:"exposedPorts"` Cmd []string `json:"cmd"` diff --git a/backend/app/service/container.go b/backend/app/service/container.go index 2fb124f2c..a0816aeb0 100644 --- a/backend/app/service/container.go +++ b/backend/app/service/container.go @@ -37,6 +37,7 @@ type IContainerService interface { Page(req dto.PageContainer) (int64, interface{}, error) List() ([]string, error) PageNetwork(req dto.SearchWithPage) (int64, interface{}, error) + ListNetwork() ([]dto.Options, error) PageVolume(req dto.SearchWithPage) (int64, interface{}, error) ListVolume() ([]dto.Options, error) PageCompose(req dto.SearchWithPage) (int64, interface{}, error) @@ -309,7 +310,8 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate) error { var config container.Config var hostConf container.HostConfig - if err := loadConfigInfo(req, &config, &hostConf); err != nil { + var networkConf network.NetworkingConfig + if err := loadConfigInfo(req, &config, &hostConf, &networkConf); err != nil { return err } @@ -320,7 +322,7 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate) error { return err } } - container, err := client.ContainerCreate(ctx, &config, &hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name) + container, err := client.ContainerCreate(ctx, &config, &hostConf, &networkConf, &v1.Platform{}, req.Name) if err != nil { _ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true}) return err @@ -348,6 +350,12 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai data.ContainerID = oldContainer.ID data.Name = strings.ReplaceAll(oldContainer.Name, "/", "") data.Image = oldContainer.Config.Image + if oldContainer.NetworkSettings != nil { + for network := range oldContainer.NetworkSettings.Networks { + data.Network = network + break + } + } data.Cmd = oldContainer.Config.Cmd data.Env = oldContainer.Config.Env data.CPUShares = oldContainer.HostConfig.CPUShares @@ -409,7 +417,8 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error { } config := oldContainer.Config hostConf := oldContainer.HostConfig - if err := loadConfigInfo(req, config, hostConf); err != nil { + var networkConf network.NetworkingConfig + if err := loadConfigInfo(req, config, hostConf, &networkConf); err != nil { return err } if err := client.ContainerRemove(ctx, req.ContainerID, types.ContainerRemoveOptions{Force: true}); err != nil { @@ -417,7 +426,7 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error { } global.LOG.Infof("new container info %s has been update, now start to recreate", req.Name) - container, err := client.ContainerCreate(ctx, config, hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name) + container, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name) if err != nil { return fmt.Errorf("recreate contianer failed, err: %v", err) } @@ -447,6 +456,13 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error { config := oldContainer.Config config.Image = req.Image hostConf := oldContainer.HostConfig + var networkConf network.NetworkingConfig + if oldContainer.NetworkSettings != nil { + for networkKey := range oldContainer.NetworkSettings.Networks { + networkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}} + break + } + } if err := client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{Force: true}); err != nil { return err } @@ -743,7 +759,7 @@ func checkPortStats(ports []dto.PortHelper) (nat.PortMap, error) { return portMap, nil } -func loadConfigInfo(req dto.ContainerOperate, config *container.Config, hostConf *container.HostConfig) error { +func loadConfigInfo(req dto.ContainerOperate, config *container.Config, hostConf *container.HostConfig, networkConf *network.NetworkingConfig) error { portMap, err := checkPortStats(req.ExposedPorts) if err != nil { return err @@ -758,6 +774,8 @@ func loadConfigInfo(req dto.ContainerOperate, config *container.Config, hostConf config.Labels = stringsToMap(req.Labels) config.ExposedPorts = exposeds + networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}} + hostConf.AutoRemove = req.AutoRemove hostConf.CPUShares = req.CPUShares hostConf.PublishAllPorts = req.PublishAllPorts diff --git a/backend/app/service/container_network.go b/backend/app/service/container_network.go index 46932e0ac..da28edf47 100644 --- a/backend/app/service/container_network.go +++ b/backend/app/service/container_network.go @@ -75,6 +75,23 @@ func (u *ContainerService) PageNetwork(req dto.SearchWithPage) (int64, interface return int64(total), data, nil } + +func (u *ContainerService) ListNetwork() ([]dto.Options, error) { + client, err := docker.NewDockerClient() + if err != nil { + return nil, err + } + list, err := client.NetworkList(context.TODO(), types.NetworkListOptions{}) + if err != nil { + return nil, err + } + var datas []dto.Options + for _, item := range list { + datas = append(datas, dto.Options{Option: item.Name}) + } + return datas, nil +} + func (u *ContainerService) DeleteNetwork(req dto.BatchDelete) error { client, err := docker.NewDockerClient() if err != nil { diff --git a/backend/router/ro_container.go b/backend/router/ro_container.go index a36069179..1c8129ca1 100644 --- a/backend/router/ro_container.go +++ b/backend/router/ro_container.go @@ -61,10 +61,11 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) { baRouter.POST("/image/tag", baseApi.ImageTag) baRouter.POST("/image/build", baseApi.ImageBuild) - baRouter.GET("/volume", baseApi.ListVolume) + baRouter.GET("/network", baseApi.ListNetwork) baRouter.POST("/network/del", baseApi.DeleteNetwork) baRouter.POST("/network/search", baseApi.SearchNetwork) baRouter.POST("/network", baseApi.CreateNetwork) + baRouter.GET("/volume", baseApi.ListVolume) baRouter.POST("/volume/del", baseApi.DeleteVolume) baRouter.POST("/volume/search", baseApi.SearchVolume) baRouter.POST("/volume", baseApi.CreateVolume) diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index 049892688..0399621dd 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -1,5 +1,5 @@ -// Package docs GENERATED BY SWAG; DO NOT EDIT -// This file was generated by swaggo/swag +// Code generated by swaggo/swag. DO NOT EDIT. + package docs import "github.com/swaggo/swag" @@ -1992,6 +1992,35 @@ const docTemplate = `{ } }, "/containers/network": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取容器网络列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Container Network" + ], + "summary": "List networks", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.Options" + } + } + } + } + }, "post": { "security": [ { @@ -2864,6 +2893,35 @@ const docTemplate = `{ } }, "/containers/volume": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取容器存储卷列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Container Volume" + ], + "summary": "List volumes", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.Options" + } + } + } + } + }, "post": { "security": [ { @@ -2948,43 +3006,6 @@ const docTemplate = `{ } }, "/containers/volume/search": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "获取容器存储卷列表", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Container Volume" - ], - "summary": "List volumes", - "parameters": [ - { - "description": "request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/dto.PageInfo" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/dto.PageResult" - } - } - } - }, "post": { "security": [ { @@ -11065,6 +11086,9 @@ const docTemplate = `{ "nanoCPUs": { "type": "integer" }, + "network": { + "type": "string" + }, "publishAllPorts": { "type": "boolean" }, diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index 6be898f40..f54021896 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -1985,6 +1985,35 @@ } }, "/containers/network": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取容器网络列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Container Network" + ], + "summary": "List networks", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.Options" + } + } + } + } + }, "post": { "security": [ { @@ -2857,6 +2886,35 @@ } }, "/containers/volume": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取容器存储卷列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Container Volume" + ], + "summary": "List volumes", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.Options" + } + } + } + } + }, "post": { "security": [ { @@ -2941,43 +2999,6 @@ } }, "/containers/volume/search": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "获取容器存储卷列表", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Container Volume" - ], - "summary": "List volumes", - "parameters": [ - { - "description": "request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/dto.PageInfo" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/dto.PageResult" - } - } - } - }, "post": { "security": [ { @@ -11058,6 +11079,9 @@ "nanoCPUs": { "type": "integer" }, + "network": { + "type": "string" + }, "publishAllPorts": { "type": "boolean" }, diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index 0b633686b..a433e4863 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -339,6 +339,8 @@ definitions: type: string nanoCPUs: type: integer + network: + type: string publishAllPorts: type: boolean restartPolicy: @@ -4817,6 +4819,24 @@ paths: - ApiKeyAuth: [] summary: Load container stats /containers/network: + get: + consumes: + - application/json + description: 获取容器网络列表 + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/dto.Options' + type: array + security: + - ApiKeyAuth: [] + summary: List networks + tags: + - Container Network post: consumes: - application/json @@ -5372,6 +5392,24 @@ paths: formatZH: 更新容器镜像 [name][image] paramKeys: [] /containers/volume: + get: + consumes: + - application/json + description: 获取容器存储卷列表 + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/dto.Options' + type: array + security: + - ApiKeyAuth: [] + summary: List volumes + tags: + - Container Volume post: consumes: - application/json @@ -5426,29 +5464,6 @@ paths: formatZH: 删除容器存储卷 [names] paramKeys: [] /containers/volume/search: - get: - consumes: - - application/json - description: 获取容器存储卷列表 - parameters: - - description: request - in: body - name: request - required: true - schema: - $ref: '#/definitions/dto.PageInfo' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/dto.PageResult' - security: - - ApiKeyAuth: [] - summary: List volumes - tags: - - Container Volume post: consumes: - application/json diff --git a/frontend/src/api/interface/container.ts b/frontend/src/api/interface/container.ts index 5c295c1fb..cd3ce21d9 100644 --- a/frontend/src/api/interface/container.ts +++ b/frontend/src/api/interface/container.ts @@ -20,6 +20,7 @@ export namespace Container { containerID: string; name: string; image: string; + network: string; cmdStr: string; memoryItem: number; cmd: Array; diff --git a/frontend/src/api/modules/container.ts b/frontend/src/api/modules/container.ts index 76c731b53..de4d919ed 100644 --- a/frontend/src/api/modules/container.ts +++ b/frontend/src/api/modules/container.ts @@ -75,6 +75,9 @@ export const imageRemove = (params: Container.BatchDelete) => { export const searchNetwork = (params: SearchWithPage) => { return http.post>(`/containers/network/search`, params); }; +export const listNetwork = () => { + return http.get>(`/containers/network`); +}; export const deleteNetwork = (params: Container.BatchDelete) => { return http.post(`/containers/network/del`, params); }; diff --git a/frontend/src/views/container/container/operate/index.vue b/frontend/src/views/container/container/operate/index.vue index a36692272..ff3c7c28e 100644 --- a/frontend/src/views/container/container/operate/index.vue +++ b/frontend/src/views/container/container/operate/index.vue @@ -82,6 +82,16 @@ + + + + + @@ -222,7 +232,14 @@ import { Rules, checkNumberRange } from '@/global/form-rules'; import i18n from '@/lang'; import { ElForm } from 'element-plus'; import DrawerHeader from '@/components/drawer-header/index.vue'; -import { listImage, listVolume, createContainer, updateContainer, loadResourceLimit } from '@/api/modules/container'; +import { + listImage, + listVolume, + createContainer, + updateContainer, + loadResourceLimit, + listNetwork, +} from '@/api/modules/container'; import { Container } from '@/api/interface/container'; import { MsgError, MsgSuccess } from '@/utils/message'; import { checkIpV4V6, checkPort } from '@/utils/util'; @@ -263,12 +280,14 @@ const acceptParams = (params: DialogProps): void => { loadLimit(); loadImageOptions(); loadVolumeOptions(); + loadNetworkOptions(); drawerVisiable.value = true; }; const emit = defineEmits<{ (e: 'search'): void }>(); const images = ref(); const volumes = ref(); +const networks = ref(); const limits = ref({ cpu: null as number, memory: null as number, @@ -279,6 +298,7 @@ const handleClose = () => { }; const rules = reactive({ + network: [Rules.requiredSelect], cpuShares: [Rules.number, checkNumberRange(0, 262144)], name: [Rules.requiredInput, Rules.name], image: [Rules.requiredSelect], @@ -329,6 +349,10 @@ const loadVolumeOptions = async () => { const res = await listVolume(); volumes.value = res.data; }; +const loadNetworkOptions = async () => { + const res = await listNetwork(); + networks.value = res.data; +}; const onSubmit = async (formEl: FormInstance | undefined) => { if (dialogData.value.rowData!.volumes.length !== 0) { for (const item of dialogData.value.rowData!.volumes) { diff --git a/frontend/src/views/container/volume/create/index.vue b/frontend/src/views/container/volume/create/index.vue index 72a0d352e..8443ee5e5 100644 --- a/frontend/src/views/container/volume/create/index.vue +++ b/frontend/src/views/container/volume/create/index.vue @@ -12,6 +12,7 @@ :model="form" :rules="rules" label-width="80px" + @submit.prevent >