diff --git a/backend/app/api/v1/image.go b/backend/app/api/v1/image.go index 3d9e6d97b..a253de785 100644 --- a/backend/app/api/v1/image.go +++ b/backend/app/api/v1/image.go @@ -89,7 +89,7 @@ func (b *BaseApi) ImagePush(c *gin.Context) { } func (b *BaseApi) ImageRemove(c *gin.Context) { - var req dto.ImageRemove + var req dto.BatchDelete if err := c.ShouldBindJSON(&req); err != nil { helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) return @@ -126,6 +126,25 @@ func (b *BaseApi) ImageSave(c *gin.Context) { helper.SuccessWithData(c, nil) } +func (b *BaseApi) ImageTag(c *gin.Context) { + var req dto.ImageTag + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := global.VALID.Struct(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + + if err := imageService.ImageTag(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, nil) +} + func (b *BaseApi) ImageLoad(c *gin.Context) { var req dto.ImageLoad if err := c.ShouldBindJSON(&req); err != nil { diff --git a/backend/app/dto/container.go b/backend/app/dto/container.go index 519f34b33..d13d68454 100644 --- a/backend/app/dto/container.go +++ b/backend/app/dto/container.go @@ -29,26 +29,24 @@ type ContainerOperation struct { } type Network struct { - ID string `json:"id"` - Name string `json:"name"` - Labels []string `json:"labels"` - Driver string `json:"driver"` - IPAMDriver string `json:"ipamDriver"` - IPV4Subnet string `json:"ipv4Subnet"` - IPV4Gateway string `json:"ipv4Gateway"` - IPV6Subnet string `json:"ipv6Subnet"` - IPV6Gateway string `json:"ipv6Gateway"` - CreatedAt time.Time `json:"createdAt"` - Attachable bool `json:"attachable"` + ID string `json:"id"` + Name string `json:"name"` + Labels []string `json:"labels"` + Driver string `json:"driver"` + IPAMDriver string `json:"ipamDriver"` + Subnet string `json:"subnet"` + Gateway string `json:"gateway"` + CreatedAt time.Time `json:"createdAt"` + Attachable bool `json:"attachable"` } type NetworkCreat struct { - Name string `json:"name"` - Driver string `json:"driver"` - Options []string `json:"options"` - IPV4Subnet string `json:"ipv4Subnet"` - IPV4Gateway string `json:"ipv4Gateway"` - Scope string `json:"scope"` - Labels []string `json:"labels"` + Name string `json:"name"` + Driver string `json:"driver"` + Options []string `json:"options"` + Subnet string `json:"subnet"` + Gateway string `json:"gateway"` + IPRange string `json:"ipRange"` + Labels []string `json:"labels"` } type Volume struct { diff --git a/backend/app/dto/image.go b/backend/app/dto/image.go index 0b9b8c8a2..ee511fe24 100644 --- a/backend/app/dto/image.go +++ b/backend/app/dto/image.go @@ -5,8 +5,7 @@ import "time" type ImageInfo struct { ID string `json:"id"` CreatedAt time.Time `json:"createdAt"` - Name string `json:"name"` - Version string `json:"version"` + Tags []string `json:"tags"` Size string `json:"size"` } @@ -14,10 +13,6 @@ type ImageLoad struct { Path string `josn:"path" validate:"required"` } -type ImageRemove struct { - ImageName string `josn:"imageName" validate:"required"` -} - type ImageBuild struct { From string `josn:"from" validate:"required"` Dockerfile string `josn:"dockerfile" validate:"required"` @@ -29,14 +24,20 @@ type ImagePull struct { ImageName string `josn:"imageName" validate:"required"` } +type ImageTag struct { + RepoID uint `josn:"repoID"` + SourceID string `json:"sourceID" validate:"required"` + TargetName string `josn:"targetName" validate:"required"` +} + type ImagePush struct { - RepoID uint `josn:"repoID" validate:"required"` - ImageName string `josn:"imageName" validate:"required"` - TagName string `json:"tagName" validate:"required"` + RepoID uint `josn:"repoID" validate:"required"` + TagName string `json:"tagName" validate:"required"` + Name string `json:"name" validate:"required"` } type ImageSave struct { - ImageName string `josn:"imageName" validate:"required"` - Path string `josn:"path" validate:"required"` - Name string `json:"name" validate:"required"` + TagName string `json:"tagName" validate:"required"` + Path string `josn:"path" validate:"required"` + Name string `json:"name" validate:"required"` } diff --git a/backend/app/service/container.go b/backend/app/service/container.go index fae8af159..6b41184a9 100644 --- a/backend/app/service/container.go +++ b/backend/app/service/container.go @@ -178,28 +178,20 @@ func (u *ContainerService) PageNetwork(req dto.PageInfo) (int64, interface{}, er for key, val := range item.Labels { tag = append(tag, fmt.Sprintf("%s=%s", key, val)) } - var ( - ipv4 network.IPAMConfig - ipv6 network.IPAMConfig - ) - if len(item.IPAM.Config) > 1 { - ipv4 = item.IPAM.Config[0] - ipv6 = item.IPAM.Config[1] - } else if len(item.IPAM.Config) > 0 { - ipv4 = item.IPAM.Config[0] + var ipam network.IPAMConfig + if len(item.IPAM.Config) > 0 { + ipam = item.IPAM.Config[0] } data = append(data, dto.Network{ - ID: item.ID, - CreatedAt: item.Created, - Name: item.Name, - Driver: item.Driver, - IPAMDriver: item.IPAM.Driver, - IPV4Subnet: ipv4.Subnet, - IPV4Gateway: ipv4.Gateway, - IPV6Subnet: ipv6.Subnet, - IPV6Gateway: ipv6.Gateway, - Attachable: item.Attachable, - Labels: tag, + ID: item.ID, + CreatedAt: item.Created, + Name: item.Name, + Driver: item.Driver, + IPAMDriver: item.IPAM.Driver, + Subnet: ipam.Subnet, + Gateway: ipam.Gateway, + Attachable: item.Attachable, + Labels: tag, }) } @@ -222,19 +214,31 @@ func (u *ContainerService) CreateNetwork(req dto.NetworkCreat) error { if err != nil { return err } - ipv4 := network.IPAMConfig{ - Subnet: req.IPV4Subnet, - Gateway: req.IPV4Gateway, + var ( + ipam network.IPAMConfig + hasConf bool + ) + if len(req.Subnet) != 0 { + ipam.Subnet = req.Subnet + hasConf = true } + if len(req.Gateway) != 0 { + ipam.Gateway = req.Gateway + hasConf = true + } + if len(req.IPRange) != 0 { + ipam.IPRange = req.IPRange + hasConf = true + } + options := types.NetworkCreate{ - Driver: req.Driver, - Scope: req.Scope, - IPAM: &network.IPAM{ - Config: []network.IPAMConfig{ipv4}, - }, + Driver: req.Driver, Options: stringsToMap(req.Options), Labels: stringsToMap(req.Labels), } + if hasConf { + options.IPAM = &network.IPAM{Config: []network.IPAMConfig{ipam}} + } if _, err := client.NetworkCreate(context.TODO(), req.Name, options); err != nil { return err } diff --git a/backend/app/service/image.go b/backend/app/service/image.go index 60cbaeef1..cebf98cf0 100644 --- a/backend/app/service/image.go +++ b/backend/app/service/image.go @@ -8,7 +8,6 @@ import ( "fmt" "io" "os" - "strings" "time" "github.com/1Panel-dev/1Panel/app/dto" @@ -25,7 +24,7 @@ type IImageService interface { ImageLoad(req dto.ImageLoad) error ImageSave(req dto.ImageSave) error ImagePush(req dto.ImagePush) error - ImageRemove(req dto.ImageRemove) error + ImageRemove(req dto.BatchDelete) error } func NewIImageService() IImageService { @@ -48,17 +47,12 @@ func (u *ImageService) Page(req dto.PageInfo) (int64, interface{}, error) { for _, image := range list { size := formatFileSize(image.Size) - for _, item := range image.RepoTags { - name := item[0:strings.LastIndex(item, ":")] - tag := strings.ReplaceAll(item[strings.LastIndex(item, ":"):], ":", "") - records = append(records, dto.ImageInfo{ - ID: image.ID, - Name: name, - Version: tag, - CreatedAt: time.Unix(image.Created, 0), - Size: size, - }) - } + records = append(records, dto.ImageInfo{ + ID: image.ID, + Tags: image.RepoTags, + CreatedAt: time.Unix(image.Created, 0), + Size: size, + }) } total, start, end := len(records), (req.Page-1)*req.PageSize, req.Page*req.PageSize if start > total { @@ -164,27 +158,39 @@ func (u *ImageService) ImageLoad(req dto.ImageLoad) error { } func (u *ImageService) ImageSave(req dto.ImageSave) error { - file, err := os.OpenFile(fmt.Sprintf("%s/%s.tar", req.Path, req.Name), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666) - if err != nil { - return err - } - defer file.Close() client, err := docker.NewDockerClient() if err != nil { return err } - out, err := client.ImageSave(context.TODO(), []string{req.ImageName}) + out, err := client.ImageSave(context.TODO(), []string{req.TagName}) if err != nil { return err } defer out.Close() + file, err := os.OpenFile(fmt.Sprintf("%s/%s.tar", req.Path, req.Name), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666) + if err != nil { + return err + } + defer file.Close() if _, err = io.Copy(file, out); err != nil { return err } return nil } +func (u *ImageService) ImageTag(req dto.ImageTag) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + + if err := client.ImageTag(context.TODO(), req.SourceID, req.TargetName); err != nil { + return err + } + return nil +} + func (u *ImageService) ImagePush(req dto.ImagePush) error { client, err := docker.NewDockerClient() if err != nil { @@ -207,34 +213,36 @@ func (u *ImageService) ImagePush(req dto.ImagePush) error { authStr := base64.URLEncoding.EncodeToString(encodedJSON) options.RegistryAuth = authStr } - newName := fmt.Sprintf("%s/%s", repo.DownloadUrl, req.TagName) - if newName != req.ImageName { - if err := client.ImageTag(context.TODO(), req.ImageName, newName); err != nil { + newName := fmt.Sprintf("%s/%s", repo.DownloadUrl, req.Name) + if newName != req.TagName { + if err := client.ImageTag(context.TODO(), req.TagName, newName); err != nil { return err } } go func() { out, err := client.ImagePush(context.TODO(), newName, options) if err != nil { - global.LOG.Errorf("image %s push failed, err: %v", req.ImageName, err) + global.LOG.Errorf("image %s push failed, err: %v", req.TagName, err) return } defer out.Close() buf := new(bytes.Buffer) _, _ = buf.ReadFrom(out) - global.LOG.Debugf("image %s push stdout: %v", req.ImageName, buf.String()) + global.LOG.Debugf("image %s push stdout: %v", req.TagName, buf.String()) }() return nil } -func (u *ImageService) ImageRemove(req dto.ImageRemove) error { +func (u *ImageService) ImageRemove(req dto.BatchDelete) error { client, err := docker.NewDockerClient() if err != nil { return err } - if _, err := client.ImageRemove(context.TODO(), req.ImageName, types.ImageRemoveOptions{Force: true}); err != nil { - return err + for _, ids := range req.Ids { + if _, err := client.ImageRemove(context.TODO(), ids, types.ImageRemoveOptions{Force: true}); err != nil { + return err + } } return nil } diff --git a/backend/app/service/image_test.go b/backend/app/service/image_test.go index df6ebcabf..634ea9f13 100644 --- a/backend/app/service/image_test.go +++ b/backend/app/service/image_test.go @@ -8,13 +8,10 @@ import ( "io/ioutil" "os" "testing" - "time" - "github.com/1Panel-dev/1Panel/app/dto" "github.com/1Panel-dev/1Panel/constant" "github.com/1Panel-dev/1Panel/utils/docker" "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/filters" "github.com/docker/docker/pkg/archive" ) @@ -95,24 +92,8 @@ func TestNetwork(t *testing.T) { if err != nil { fmt.Println(err) } - var data []dto.Volume - list, err := client.VolumeList(context.TODO(), filters.NewArgs()) + _, err = client.NetworkCreate(context.TODO(), "test", types.NetworkCreate{}) if err != nil { fmt.Println(err) } - for _, item := range list.Volumes { - tag := make([]string, 0) - for _, val := range item.Labels { - tag = append(tag, val) - } - createTime, _ := time.Parse("2006-01-02T15:04:05Z", item.CreatedAt) - data = append(data, dto.Volume{ - CreatedAt: createTime, - Name: item.Name, - Driver: item.Driver, - Mountpoint: item.Mountpoint, - Labels: tag, - }) - } - fmt.Println(data) } diff --git a/backend/router/ro_container.go b/backend/router/ro_container.go index 97e750238..9c9ba8014 100644 --- a/backend/router/ro_container.go +++ b/backend/router/ro_container.go @@ -37,6 +37,7 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) { baRouter.POST("/image/save", baseApi.ImageSave) baRouter.POST("/image/load", baseApi.ImageLoad) baRouter.POST("/image/remove", baseApi.ImageRemove) + baRouter.POST("/image/tag", baseApi.ImageTag) baRouter.POST("/network/del", baseApi.DeleteNetwork) baRouter.POST("/network/search", baseApi.SearchNetwork) diff --git a/frontend/src/api/interface/container.ts b/frontend/src/api/interface/container.ts index 4b8038766..0f69a4cb2 100644 --- a/frontend/src/api/interface/container.ts +++ b/frontend/src/api/interface/container.ts @@ -21,7 +21,7 @@ export namespace Container { id: string; createdAt: Date; name: string; - version: string; + tags: Array; size: string; } export interface ImageBuild { @@ -32,19 +32,20 @@ export namespace Container { repoID: number; imageName: string; } + export interface ImageTag { + repoID: number; + sourceID: string; + targetName: string; + } export interface ImagePush { repoID: number; - imageName: string; tagName: string; } - export interface ImageRemove { - imageName: string; - } export interface ImageLoad { path: string; } export interface ImageSave { - imageName: string; + tagName: string; path: string; name: string; } @@ -55,10 +56,8 @@ export namespace Container { labels: Array; driver: string; ipamDriver: string; - ipv4Subnet: string; - ipv4Gateway: string; - ipv6Subnet: string; - ipv6Gateway: string; + subnet: string; + gateway: string; createdAt: string; attachable: string; } @@ -67,8 +66,8 @@ export namespace Container { labels: Array; options: Array; driver: string; - ipv4Subnet: string; - ipv4Gateway: string; + subnet: string; + gateway: string; scope: string; } @@ -83,7 +82,7 @@ export namespace Container { name: string; driver: string; options: Array; - label: Array; + labels: Array; } export interface RepoCreate { diff --git a/frontend/src/api/modules/container.ts b/frontend/src/api/modules/container.ts index 5d5a3ad52..b3486d201 100644 --- a/frontend/src/api/modules/container.ts +++ b/frontend/src/api/modules/container.ts @@ -23,21 +23,24 @@ export const getImagePage = (params: ReqPage) => { return http.post>(`/containers/image/search`, params); }; export const imageBuild = (params: Container.ImageBuild) => { - return http.post(`/containers/image/build`, params); + return http.post(`/containers/image/build`, params); }; export const imagePull = (params: Container.ImagePull) => { - return http.post(`/containers/image/pull`, params); + return http.post(`/containers/image/pull`, params); }; export const imagePush = (params: Container.ImagePush) => { - return http.post(`/containers/image/push`, params); + return http.post(`/containers/image/push`, params); }; export const imageLoad = (params: Container.ImageLoad) => { - return http.post(`/containers/image/load`, params); + return http.post(`/containers/image/load`, params); }; export const imageSave = (params: Container.ImageSave) => { - return http.post(`/containers/image/save`, params); + return http.post(`/containers/image/save`, params); }; -export const imageRemove = (params: Container.ImageRemove) => { +export const imageTag = (params: Container.ImageTag) => { + return http.post(`/containers/image/tag`, params); +}; +export const imageRemove = (params: Container.BatchDelete) => { return http.post(`/containers/image/remove`, params); }; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 56d05000b..3e1908a82 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -161,8 +161,6 @@ export default { reName: 'ReName', remove: 'Remove', container: 'Container', - network: 'Network', - storage: 'Storage', schedule: 'Schedule', upTime: 'UpTime', all: 'All', @@ -178,18 +176,33 @@ export default { imageName: 'Image name', pull: 'Pull', path: 'Path', - importImage: 'Import image', + importImage: 'Image import', import: 'Import', build: 'Build', label: 'Label', push: 'Push', fileName: 'FileName', export: 'Export', - exportImage: 'ExportImage', + exportImage: 'Image export', version: 'Version', size: 'Size', from: 'From', + network: 'Network', + createNetwork: 'Create network', + networkName: 'Name', + driver: 'Driver', + option: 'Option', + attachable: 'Attachable', + subnet: 'Subnet', + scope: 'IP Scope', + gateway: 'Gateway', + + volume: 'Volume', + volumeName: 'Name', + mountpoint: 'Mountpoint', + createVolume: 'Create volume', + repo: 'Repo', name: 'Name', protocol: 'protocol', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index f99f66cf8..538b6a6e7 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -158,7 +158,6 @@ export default { reName: '重命名', remove: '移除', container: '容器', - storage: '数据卷', schedule: '编排', upTime: '运行时长', all: '全部', @@ -168,8 +167,9 @@ export default { last10Min: '最近 10 分钟', image: '镜像', - imagePull: '镜像拉取', - imagePush: '镜像推送', + imagePull: '拉取镜像', + imagePush: '推送镜像', + imageDelete: '删除镜像', repoName: '仓库名', imageName: '镜像名', pull: '拉取', @@ -199,6 +199,11 @@ export default { scope: 'IP 范围', gateway: '网关', + volume: '存储卷', + volumeName: '名称', + mountpoint: '挂载点', + createVolume: '创建存储卷', + repo: '仓库', name: '名称', protocol: '协议', diff --git a/frontend/src/views/container/image/build/index.vue b/frontend/src/views/container/image/build/index.vue new file mode 100644 index 000000000..feb340ed3 --- /dev/null +++ b/frontend/src/views/container/image/build/index.vue @@ -0,0 +1,86 @@ + + + diff --git a/frontend/src/views/container/image/index.vue b/frontend/src/views/container/image/index.vue index 1d4a44bf6..28cb49e5d 100644 --- a/frontend/src/views/container/image/index.vue +++ b/frontend/src/views/container/image/index.vue @@ -12,14 +12,19 @@ {{ $t('container.build') }} - + {{ $t('commons.button.delete') }} - - + + + @@ -37,7 +37,7 @@ import Container from '@/views/container/container/index.vue'; import Repo from '@/views/container/repo/index.vue'; import Image from '@/views/container/image/index.vue'; import Network from '@/views/container/network/index.vue'; -import Monitor from '@/views/setting/tabs/monitor.vue'; +import Volume from '@/views/container/volume/index.vue'; import About from '@/views/setting/tabs/about.vue'; const activeNames = ref('container'); diff --git a/frontend/src/views/container/network/create/index.vue b/frontend/src/views/container/network/create/index.vue index 54fa406a2..b59b7105c 100644 --- a/frontend/src/views/container/network/create/index.vue +++ b/frontend/src/views/container/network/create/index.vue @@ -20,11 +20,11 @@ - - + + - - + + @@ -59,8 +59,8 @@ const form = reactive({ optionStr: '', options: [] as Array, driver: '', - ipv4Subnet: '', - ipv4Gateway: '', + subnet: '', + gateway: '', scope: '', }); @@ -73,9 +73,6 @@ const emit = defineEmits<{ (e: 'search'): void }>(); const rules = reactive({ name: [Rules.requiredInput, Rules.name], driver: [Rules.requiredSelect], - ipv4Subnet: [Rules.requiredInput], - ipv4Gateway: [Rules.requiredInput], - scope: [Rules.requiredInput], }); type FormInstance = InstanceType; diff --git a/frontend/src/views/container/network/index.vue b/frontend/src/views/container/network/index.vue index 41817c94e..9c1bd87c5 100644 --- a/frontend/src/views/container/network/index.vue +++ b/frontend/src/views/container/network/index.vue @@ -25,30 +25,8 @@ - - - - - - + +