From 1d2a00cc6e1be7120e34ae6056be2b2ad38b4444 Mon Sep 17 00:00:00 2001 From: ssongliu Date: Tue, 11 Oct 2022 14:20:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E7=BD=91=E7=BB=9C=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/v1/container.go | 112 +++ backend/app/api/v1/image.go | 19 + backend/app/dto/container.go | 43 ++ backend/app/dto/image.go | 6 + backend/app/service/container.go | 182 +++++ backend/app/service/image.go | 29 +- backend/app/service/image_test.go | 54 ++ backend/router/ro_container.go | 7 + frontend/src/api/interface/container.ts | 50 +- frontend/src/api/modules/container.ts | 27 +- frontend/src/lang/modules/zh.ts | 14 +- frontend/src/views/container/image/index.vue | 73 +- frontend/src/views/container/index.vue | 2 + .../views/container/network/create/index.vue | 110 +++ .../src/views/container/network/index.vue | 140 ++++ go.mod | 19 +- go.sum | 654 +++++++++++++++++- 17 files changed, 1518 insertions(+), 23 deletions(-) create mode 100644 frontend/src/views/container/network/create/index.vue create mode 100644 frontend/src/views/container/network/index.vue diff --git a/backend/app/api/v1/container.go b/backend/app/api/v1/container.go index dbeab9af8..5a2ba4710 100644 --- a/backend/app/api/v1/container.go +++ b/backend/app/api/v1/container.go @@ -80,3 +80,115 @@ func (b *BaseApi) ContainerLogs(c *gin.Context) { } helper.SuccessWithData(c, logs) } + +func (b *BaseApi) SearchNetwork(c *gin.Context) { + var req dto.PageInfo + 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 + } + + total, list, err := containerService.PageNetwork(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} +func (b *BaseApi) DeleteNetwork(c *gin.Context) { + var req dto.BatchDelete + 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 := containerService.DeleteNetwork(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} +func (b *BaseApi) CreateNetwork(c *gin.Context) { + var req dto.NetworkCreat + 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 := containerService.CreateNetwork(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} + +func (b *BaseApi) SearchVolume(c *gin.Context) { + var req dto.PageInfo + 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 + } + + total, list, err := containerService.PageVolume(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} +func (b *BaseApi) DeleteVolume(c *gin.Context) { + var req dto.BatchDelete + 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 := containerService.DeleteVolume(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} +func (b *BaseApi) CreateVolume(c *gin.Context) { + var req dto.VolumeCreat + 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 := containerService.CreateVolume(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} diff --git a/backend/app/api/v1/image.go b/backend/app/api/v1/image.go index 2f2577b85..3d9e6d97b 100644 --- a/backend/app/api/v1/image.go +++ b/backend/app/api/v1/image.go @@ -31,6 +31,25 @@ func (b *BaseApi) SearchImage(c *gin.Context) { }) } +func (b *BaseApi) ImageBuild(c *gin.Context) { + var req dto.ImageBuild + 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.ImageBuild(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, nil) +} + func (b *BaseApi) ImagePull(c *gin.Context) { var req dto.ImagePull if err := c.ShouldBindJSON(&req); err != nil { diff --git a/backend/app/dto/container.go b/backend/app/dto/container.go index 48a0e3424..519f34b33 100644 --- a/backend/app/dto/container.go +++ b/backend/app/dto/container.go @@ -1,5 +1,7 @@ package dto +import "time" + type PageContainer struct { PageInfo Status string `json:"status" validate:"required,oneof=all running"` @@ -25,3 +27,44 @@ type ContainerOperation struct { Operation string `json:"operation" validate:"required,oneof=start stop reStart kill pause unPause reName remove"` NewName string `json:"newName"` } + +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"` +} +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"` +} + +type Volume struct { + Name string `json:"name"` + Labels []string `json:"labels"` + Driver string `json:"driver"` + Mountpoint string `json:"mountpoint"` + CreatedAt time.Time `json:"createdAt"` +} +type VolumeCreat struct { + Name string `json:"name"` + Driver string `json:"driver"` + Options []string `json:"options"` + Labels []string `json:"labels"` +} + +type BatchDelete struct { + Ids []string `json:"ids" validate:"required"` +} diff --git a/backend/app/dto/image.go b/backend/app/dto/image.go index b6c98fbfd..0b9b8c8a2 100644 --- a/backend/app/dto/image.go +++ b/backend/app/dto/image.go @@ -18,6 +18,12 @@ type ImageRemove struct { ImageName string `josn:"imageName" validate:"required"` } +type ImageBuild struct { + From string `josn:"from" validate:"required"` + Dockerfile string `josn:"dockerfile" validate:"required"` + Tags string `josn:"tags" validate:"required"` +} + type ImagePull struct { RepoID uint `josn:"repoID"` ImageName string `josn:"imageName" validate:"required"` diff --git a/backend/app/service/container.go b/backend/app/service/container.go index 5d01817d0..fae8af159 100644 --- a/backend/app/service/container.go +++ b/backend/app/service/container.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "fmt" "io" "strings" "time" @@ -12,6 +13,9 @@ import ( "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/api/types/network" + "github.com/docker/docker/api/types/volume" "github.com/docker/docker/pkg/stdcopy" ) @@ -19,14 +23,21 @@ type ContainerService struct{} type IContainerService interface { Page(req dto.PageContainer) (int64, interface{}, error) + PageNetwork(req dto.PageInfo) (int64, interface{}, error) + PageVolume(req dto.PageInfo) (int64, interface{}, error) ContainerOperation(req dto.ContainerOperation) error ContainerLogs(param dto.ContainerLog) (string, error) ContainerInspect(id string) (string, error) + DeleteNetwork(req dto.BatchDelete) error + CreateNetwork(req dto.NetworkCreat) error + DeleteVolume(req dto.BatchDelete) error + CreateVolume(req dto.VolumeCreat) error } func NewIContainerService() IContainerService { return &ContainerService{} } + func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) { var ( records []types.Container @@ -138,3 +149,174 @@ func (u *ContainerService) ContainerLogs(req dto.ContainerLog) (string, error) { } return buf.String(), nil } + +func (u *ContainerService) PageNetwork(req dto.PageInfo) (int64, interface{}, error) { + client, err := docker.NewDockerClient() + if err != nil { + return 0, nil, err + } + list, err := client.NetworkList(context.TODO(), types.NetworkListOptions{}) + if err != nil { + return 0, nil, err + } + var ( + data []dto.Network + records []types.NetworkResource + ) + total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize + if start > total { + records = make([]types.NetworkResource, 0) + } else { + if end >= total { + end = total + } + records = list[start:end] + } + + for _, item := range records { + tag := make([]string, 0) + 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] + } + 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, + }) + } + + return int64(total), data, nil +} +func (u *ContainerService) DeleteNetwork(req dto.BatchDelete) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + for _, id := range req.Ids { + if err := client.NetworkRemove(context.TODO(), id); err != nil { + return err + } + } + return nil +} +func (u *ContainerService) CreateNetwork(req dto.NetworkCreat) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + ipv4 := network.IPAMConfig{ + Subnet: req.IPV4Subnet, + Gateway: req.IPV4Gateway, + } + options := types.NetworkCreate{ + Driver: req.Driver, + Scope: req.Scope, + IPAM: &network.IPAM{ + Config: []network.IPAMConfig{ipv4}, + }, + Options: stringsToMap(req.Options), + Labels: stringsToMap(req.Labels), + } + if _, err := client.NetworkCreate(context.TODO(), req.Name, options); err != nil { + return err + } + return nil +} + +func (u *ContainerService) PageVolume(req dto.PageInfo) (int64, interface{}, error) { + client, err := docker.NewDockerClient() + if err != nil { + return 0, nil, err + } + list, err := client.VolumeList(context.TODO(), filters.NewArgs()) + if err != nil { + return 0, nil, err + } + var ( + data []dto.Volume + records []*types.Volume + ) + total, start, end := len(list.Volumes), (req.Page-1)*req.PageSize, req.Page*req.PageSize + if start > total { + records = make([]*types.Volume, 0) + } else { + if end >= total { + end = total + } + records = list.Volumes[start:end] + } + + for _, item := range records { + 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, + }) + } + + return int64(total), data, nil +} +func (u *ContainerService) DeleteVolume(req dto.BatchDelete) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + for _, id := range req.Ids { + if err := client.VolumeRemove(context.TODO(), id, true); err != nil { + return err + } + } + return nil +} +func (u *ContainerService) CreateVolume(req dto.VolumeCreat) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + options := volume.VolumeCreateBody{ + Name: req.Name, + Driver: req.Driver, + DriverOpts: stringsToMap(req.Options), + Labels: stringsToMap(req.Labels), + } + if _, err := client.VolumeCreate(context.TODO(), options); err != nil { + return err + } + return nil +} + +func stringsToMap(list []string) map[string]string { + var lableMap = make(map[string]string) + for _, label := range list { + sps := strings.Split(label, "=") + if len(sps) > 1 { + lableMap[sps[0]] = sps[1] + } + } + return lableMap +} diff --git a/backend/app/service/image.go b/backend/app/service/image.go index 18958502d..60cbaeef1 100644 --- a/backend/app/service/image.go +++ b/backend/app/service/image.go @@ -73,6 +73,29 @@ func (u *ImageService) Page(req dto.PageInfo) (int64, interface{}, error) { return int64(total), backDatas, nil } +func (u *ImageService) ImageBuild(req dto.ImageBuild) error { + // client, err := docker.NewDockerClient() + // if err != nil { + // return err + // } + // if req.From == "path" { + // tar, err := archive.TarWithOptions("node-hello/", &archive.TarOptions{}) + // if err != nil { + // return err + // } + + // opts := types.ImageBuildOptions{ + // Dockerfile: "Dockerfile", + // Tags: []string{dockerRegistryUserID + "/node-hello"}, + // Remove: true, + // } + // if _, err := client.ImageBuild(context.TODO(), tar, opts); err != nil { + // return err + // } + // } + return nil +} + func (u *ImageService) ImagePull(req dto.ImagePull) error { client, err := docker.NewDockerClient() if err != nil { @@ -185,8 +208,10 @@ func (u *ImageService) ImagePush(req dto.ImagePush) error { options.RegistryAuth = authStr } newName := fmt.Sprintf("%s/%s", repo.DownloadUrl, req.TagName) - if err := client.ImageTag(context.TODO(), req.ImageName, newName); err != nil { - return err + if newName != req.ImageName { + if err := client.ImageTag(context.TODO(), req.ImageName, newName); err != nil { + return err + } } go func() { out, err := client.ImagePush(context.TODO(), newName, options) diff --git a/backend/app/service/image_test.go b/backend/app/service/image_test.go index 912b22c37..df6ebcabf 100644 --- a/backend/app/service/image_test.go +++ b/backend/app/service/image_test.go @@ -8,9 +8,14 @@ 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" ) func TestImage(t *testing.T) { @@ -32,6 +37,28 @@ func TestImage(t *testing.T) { } } +func TestBuild(t *testing.T) { + client, err := docker.NewDockerClient() + if err != nil { + fmt.Println(err) + } + tar, err := archive.TarWithOptions("/Users/slooop/Documents/neeko/", &archive.TarOptions{}) + if err != nil { + fmt.Println(err) + } + + opts := types.ImageBuildOptions{ + Dockerfile: "Dockerfile", + Tags: []string{"neeko" + "/test"}, + Remove: true, + } + res, err := client.ImageBuild(context.TODO(), tar, opts) + if err != nil { + fmt.Println(err) + } + defer res.Body.Close() +} + func TestDeam(t *testing.T) { file, err := ioutil.ReadFile(constant.DaemonJsonDir) if err != nil { @@ -62,3 +89,30 @@ func TestDeam(t *testing.T) { fmt.Println(err) } } + +func TestNetwork(t *testing.T) { + client, err := docker.NewDockerClient() + if err != nil { + fmt.Println(err) + } + var data []dto.Volume + list, err := client.VolumeList(context.TODO(), filters.NewArgs()) + 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 9d2b3969c..97e750238 100644 --- a/backend/router/ro_container.go +++ b/backend/router/ro_container.go @@ -37,5 +37,12 @@ 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("/network/del", baseApi.DeleteNetwork) + baRouter.POST("/network/search", baseApi.SearchNetwork) + baRouter.POST("/network", baseApi.CreateNetwork) + baRouter.POST("/volume/del", baseApi.DeleteVolume) + baRouter.POST("/volume/search", baseApi.SearchVolume) + baRouter.POST("/volume", baseApi.CreateVolume) } } diff --git a/frontend/src/api/interface/container.ts b/frontend/src/api/interface/container.ts index 6ce69085f..4b8038766 100644 --- a/frontend/src/api/interface/container.ts +++ b/frontend/src/api/interface/container.ts @@ -1,14 +1,9 @@ -import { ReqPage } from '.'; - export namespace Container { export interface ContainerOperate { containerID: string; operation: string; newName: string; } - export interface ContainerSearch extends ReqPage { - status: string; - } export interface ContainerInfo { containerID: string; name: string; @@ -29,6 +24,10 @@ export namespace Container { version: string; size: string; } + export interface ImageBuild { + from: string; + dockerfile: string; + } export interface ImagePull { repoID: number; imageName: string; @@ -50,6 +49,43 @@ export namespace Container { name: string; } + export interface NetworkInfo { + id: string; + name: string; + labels: Array; + driver: string; + ipamDriver: string; + ipv4Subnet: string; + ipv4Gateway: string; + ipv6Subnet: string; + ipv6Gateway: string; + createdAt: string; + attachable: string; + } + export interface NetworkCreate { + name: string; + labels: Array; + options: Array; + driver: string; + ipv4Subnet: string; + ipv4Gateway: string; + scope: string; + } + + export interface VolumeInfo { + name: string; + labels: Array; + driver: string; + mountpoint: string; + createdAt: string; + } + export interface VolumeCreate { + name: string; + driver: string; + options: Array; + label: Array; + } + export interface RepoCreate { name: string; downloadUrl: string; @@ -81,4 +117,8 @@ export namespace Container { name: string; downloadUrl: string; } + + export interface BatchDelete { + ids: Array; + } } diff --git a/frontend/src/api/modules/container.ts b/frontend/src/api/modules/container.ts index b322ca5d2..5d5a3ad52 100644 --- a/frontend/src/api/modules/container.ts +++ b/frontend/src/api/modules/container.ts @@ -2,7 +2,7 @@ import http from '@/api'; import { ResPage, ReqPage } from '../interface'; import { Container } from '../interface/container'; -export const getContainerPage = (params: Container.ContainerSearch) => { +export const getContainerPage = (params: ReqPage) => { return http.post>(`/containers/search`, params); }; @@ -22,6 +22,9 @@ export const getContainerInspect = (containerID: string) => { 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); +}; export const imagePull = (params: Container.ImagePull) => { return http.post(`/containers/image/pull`, params); }; @@ -38,6 +41,28 @@ export const imageRemove = (params: Container.ImageRemove) => { return http.post(`/containers/image/remove`, params); }; +// network +export const getNetworkPage = (params: ReqPage) => { + return http.post>(`/containers/network/search`, params); +}; +export const deleteNetwork = (params: Container.BatchDelete) => { + return http.post(`/containers/network/del`, params); +}; +export const createNetwork = (params: Container.NetworkCreate) => { + return http.post(`/containers/network`, params); +}; + +// volume +export const getVolumePage = (params: ReqPage) => { + return http.post>(`/containers/volume/search`, params); +}; +export const deleteVolume = (params: Container.BatchDelete) => { + return http.post(`/containers/volume/del`, params); +}; +export const createVolume = (params: Container.VolumeCreate) => { + return http.post(`/containers/volume`, params); +}; + // repo export const getRepoPage = (params: ReqPage) => { return http.post>(`/containers/repo/search`, params); diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 4fac0ff8e..f99f66cf8 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -158,7 +158,6 @@ export default { reName: '重命名', remove: '移除', container: '容器', - network: '网络', storage: '数据卷', schedule: '编排', upTime: '运行时长', @@ -178,6 +177,8 @@ export default { importImage: '导入镜像', import: '导入', build: '构建镜像', + edit: '编辑', + pathSelect: '路径选择', label: '标签', push: '推送', fileName: '文件名', @@ -186,6 +187,17 @@ export default { version: '版本', size: '大小', from: '来源', + tag: '标签', + + network: '网络', + createNetwork: '添加网络', + networkName: '网络名', + driver: '模式', + option: '参数', + attachable: '可用', + subnet: '子网', + scope: 'IP 范围', + gateway: '网关', repo: '仓库', name: '名称', diff --git a/frontend/src/views/container/image/index.vue b/frontend/src/views/container/image/index.vue index fe9ab272a..1d4a44bf6 100644 --- a/frontend/src/views/container/image/index.vue +++ b/frontend/src/views/container/image/index.vue @@ -9,7 +9,7 @@ {{ $t('container.importImage') }} - + {{ $t('container.build') }} @@ -30,6 +30,41 @@ + + + + + + {{ $t('container.edit') }} + {{ $t('container.pathSelect') }} + + + + + + + + + + + + + + + + +