mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-31 14:08:06 +08:00
feat: 容器创建编辑支持自定义网络 (#1582)
This commit is contained in:
parent
28bd0e3cc2
commit
7b297e824c
@ -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 {
|
||||
|
@ -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"`
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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
|
||||
|
@ -20,6 +20,7 @@ export namespace Container {
|
||||
containerID: string;
|
||||
name: string;
|
||||
image: string;
|
||||
network: string;
|
||||
cmdStr: string;
|
||||
memoryItem: number;
|
||||
cmd: Array<string>;
|
||||
|
@ -75,6 +75,9 @@ export const imageRemove = (params: Container.BatchDelete) => {
|
||||
export const searchNetwork = (params: SearchWithPage) => {
|
||||
return http.post<ResPage<Container.NetworkInfo>>(`/containers/network/search`, params);
|
||||
};
|
||||
export const listNetwork = () => {
|
||||
return http.get<Array<Container.Options>>(`/containers/network`);
|
||||
};
|
||||
export const deleteNetwork = (params: Container.BatchDelete) => {
|
||||
return http.post(`/containers/network/del`, params);
|
||||
};
|
||||
|
@ -82,6 +82,16 @@
|
||||
</table>
|
||||
</el-card>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.network')" prop="network">
|
||||
<el-select v-model="dialogData.rowData!.network">
|
||||
<el-option
|
||||
v-for="(item, indexV) of networks"
|
||||
:key="indexV"
|
||||
:value="item.option"
|
||||
:label="item.option"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.cmd')" prop="cmdStr">
|
||||
<el-input :placeholder="$t('container.cmdHelper')" v-model="dialogData.rowData!.cmdStr" />
|
||||
</el-form-item>
|
||||
@ -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<Container.ResourceLimit>({
|
||||
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) {
|
||||
|
@ -12,6 +12,7 @@
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="80px"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input clearable v-model.trim="form.name" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user