From 2cb1b57069b0cd17fec5f8f5015479fdcaa3706a Mon Sep 17 00:00:00 2001 From: ssongliu Date: Mon, 10 Oct 2022 15:14:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E9=95=9C=E5=83=8F?= =?UTF-8?q?=E6=8B=89=E5=8F=96=E3=80=81=E6=8E=A8=E9=80=81=E3=80=81=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E3=80=81=E5=AF=BC=E5=87=BA=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/entry.go | 1 + backend/app/api/v1/image.go | 127 ++++++ backend/app/api/v1/image_repo.go | 16 +- backend/app/dto/image.go | 36 ++ backend/app/dto/image_repo.go | 6 + backend/app/repo/image_repo.go | 13 + backend/app/service/entry.go | 1 + backend/app/service/image.go | 221 +++++++++++ backend/app/service/image_repo.go | 22 ++ backend/app/service/image_test.go | 30 ++ backend/router/ro_container.go | 10 +- frontend/src/api/interface/container.ts | 33 ++ frontend/src/api/modules/container.ts | 23 ++ frontend/src/lang/modules/en.ts | 20 +- frontend/src/lang/modules/zh.ts | 20 +- frontend/src/views/container/image/index.vue | 391 +++++++++++++++++++ frontend/src/views/container/index.vue | 4 +- 17 files changed, 968 insertions(+), 6 deletions(-) create mode 100644 backend/app/api/v1/image.go create mode 100644 backend/app/dto/image.go create mode 100644 backend/app/service/image.go create mode 100644 backend/app/service/image_test.go diff --git a/backend/app/api/v1/entry.go b/backend/app/api/v1/entry.go index 3fd42289c..94aa3d212 100644 --- a/backend/app/api/v1/entry.go +++ b/backend/app/api/v1/entry.go @@ -15,6 +15,7 @@ var ( groupService = service.ServiceGroupApp.GroupService containerService = service.ServiceGroupApp.ContainerService imageRepoService = service.ServiceGroupApp.ImageRepoService + imageService = service.ServiceGroupApp.ImageService commandService = service.ServiceGroupApp.CommandService operationService = service.ServiceGroupApp.OperationService fileService = service.ServiceGroupApp.FileService diff --git a/backend/app/api/v1/image.go b/backend/app/api/v1/image.go new file mode 100644 index 000000000..2f2577b85 --- /dev/null +++ b/backend/app/api/v1/image.go @@ -0,0 +1,127 @@ +package v1 + +import ( + "github.com/1Panel-dev/1Panel/app/api/v1/helper" + "github.com/1Panel-dev/1Panel/app/dto" + "github.com/1Panel-dev/1Panel/constant" + "github.com/1Panel-dev/1Panel/global" + "github.com/gin-gonic/gin" +) + +func (b *BaseApi) SearchImage(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 := imageService.Page(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) ImagePull(c *gin.Context) { + var req dto.ImagePull + 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.ImagePull(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, nil) +} + +func (b *BaseApi) ImagePush(c *gin.Context) { + var req dto.ImagePush + 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.ImagePush(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, nil) +} + +func (b *BaseApi) ImageRemove(c *gin.Context) { + var req dto.ImageRemove + 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.ImageRemove(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, nil) +} + +func (b *BaseApi) ImageSave(c *gin.Context) { + var req dto.ImageSave + 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.ImageSave(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 { + 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.ImageLoad(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, nil) +} diff --git a/backend/app/api/v1/image_repo.go b/backend/app/api/v1/image_repo.go index e17f09376..3065dd8a8 100644 --- a/backend/app/api/v1/image_repo.go +++ b/backend/app/api/v1/image_repo.go @@ -8,12 +8,16 @@ import ( "github.com/gin-gonic/gin" ) -func (b *BaseApi) GetRepoList(c *gin.Context) { +func (b *BaseApi) SearchRepo(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 := imageRepoService.Page(req) if err != nil { @@ -27,6 +31,16 @@ func (b *BaseApi) GetRepoList(c *gin.Context) { }) } +func (b *BaseApi) ListRepo(c *gin.Context) { + list, err := imageRepoService.List() + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, list) +} + func (b *BaseApi) CreateRepo(c *gin.Context) { var req dto.ImageRepoCreate if err := c.ShouldBindJSON(&req); err != nil { diff --git a/backend/app/dto/image.go b/backend/app/dto/image.go new file mode 100644 index 000000000..6e52ee972 --- /dev/null +++ b/backend/app/dto/image.go @@ -0,0 +1,36 @@ +package dto + +import "time" + +type ImageInfo struct { + ID string `json:"id"` + CreatedAt time.Time `json:"createdAt"` + Name string `json:"name"` + Version string `json:"version"` + Size string `json:"size"` +} + +type ImageLoad struct { + Path string `josn:"path" validate:"required"` +} + +type ImageRemove struct { + ImageName string `josn:"imageName" validate:"required"` +} + +type ImagePull struct { + RepoID uint `josn:"repoID" validate:"required"` + ImageName string `josn:"imageName" validate:"required"` +} + +type ImagePush struct { + RepoID uint `josn:"repoID" validate:"required"` + ImageName string `josn:"imageName" validate:"required"` + TagName string `json:"tagName" validate:"required"` +} + +type ImageSave struct { + ImageName string `josn:"imageName" validate:"required"` + Path string `josn:"path" validate:"required"` + Name string `json:"name" validate:"required"` +} diff --git a/backend/app/dto/image_repo.go b/backend/app/dto/image_repo.go index 57cd235ba..5a95a8500 100644 --- a/backend/app/dto/image_repo.go +++ b/backend/app/dto/image_repo.go @@ -29,3 +29,9 @@ type ImageRepoInfo struct { Username string `json:"username"` Auth bool `json:"auth"` } + +type ImageRepoOption struct { + ID uint `json:"id"` + Name string `json:"name"` + DownloadUrl string `json:"downloadUrl"` +} diff --git a/backend/app/repo/image_repo.go b/backend/app/repo/image_repo.go index 442763e7b..27cc52026 100644 --- a/backend/app/repo/image_repo.go +++ b/backend/app/repo/image_repo.go @@ -10,6 +10,7 @@ type ImageRepoRepo struct{} type IImageRepoRepo interface { Get(opts ...DBOption) (model.ImageRepo, error) Page(limit, offset int, opts ...DBOption) (int64, []model.ImageRepo, error) + List(opts ...DBOption) ([]model.ImageRepo, error) Create(imageRepo *model.ImageRepo) error Update(id uint, vars map[string]interface{}) error Delete(opts ...DBOption) error @@ -41,6 +42,18 @@ func (u *ImageRepoRepo) Page(page, size int, opts ...DBOption) (int64, []model.I return count, ops, err } +func (u *ImageRepoRepo) List(opts ...DBOption) ([]model.ImageRepo, error) { + var ops []model.ImageRepo + db := global.DB.Model(&model.ImageRepo{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Find(&ops).Error + return ops, err +} + func (u *ImageRepoRepo) Create(imageRepo *model.ImageRepo) error { return global.DB.Create(imageRepo).Error } diff --git a/backend/app/service/entry.go b/backend/app/service/entry.go index 539de4e01..9dc568f8f 100644 --- a/backend/app/service/entry.go +++ b/backend/app/service/entry.go @@ -7,6 +7,7 @@ type ServiceGroup struct { HostService BackupService GroupService + ImageService ImageRepoService ContainerService CommandService diff --git a/backend/app/service/image.go b/backend/app/service/image.go new file mode 100644 index 000000000..e38d7d676 --- /dev/null +++ b/backend/app/service/image.go @@ -0,0 +1,221 @@ +package service + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "os" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/app/dto" + "github.com/1Panel-dev/1Panel/global" + "github.com/1Panel-dev/1Panel/utils/docker" + "github.com/docker/docker/api/types" +) + +type ImageService struct{} + +type IImageService interface { + Page(req dto.PageInfo) (int64, interface{}, error) + ImagePull(req dto.ImagePull) error + ImageLoad(req dto.ImageLoad) error + ImageSave(req dto.ImageSave) error + ImagePush(req dto.ImagePush) error + ImageRemove(req dto.ImageRemove) error +} + +func NewIImageService() IImageService { + return &ImageService{} +} +func (u *ImageService) Page(req dto.PageInfo) (int64, interface{}, error) { + var ( + list []types.ImageSummary + records []dto.ImageInfo + backDatas []dto.ImageInfo + ) + client, err := docker.NewDockerClient() + if err != nil { + return 0, nil, err + } + list, err = client.ImageList(context.Background(), types.ImageListOptions{}) + if err != nil { + return 0, nil, err + } + + 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, + }) + } + } + total, start, end := len(records), (req.Page-1)*req.PageSize, req.Page*req.PageSize + if start > total { + backDatas = make([]dto.ImageInfo, 0) + } else { + if end >= total { + end = total + } + backDatas = records[start:end] + } + + return int64(total), backDatas, nil +} + +func (u *ImageService) ImagePull(req dto.ImagePull) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + ctx := context.Background() + repo, err := imageRepoRepo.Get(commonRepo.WithByID(req.RepoID)) + if err != nil { + return err + } + options := types.ImagePullOptions{} + if repo.Auth { + authConfig := types.AuthConfig{ + Username: repo.Username, + Password: repo.Password, + } + encodedJSON, err := json.Marshal(authConfig) + if err != nil { + return err + } + authStr := base64.URLEncoding.EncodeToString(encodedJSON) + options.RegistryAuth = authStr + } + image := repo.DownloadUrl + "/" + req.ImageName + if len(repo.RepoName) != 0 { + image = fmt.Sprintf("%s/%s/%s", repo.DownloadUrl, repo.RepoName, req.ImageName) + } + go func() { + out, err := client.ImagePull(ctx, image, options) + if err != nil { + global.LOG.Errorf("image %s pull failed, err: %v", image, err) + return + } + defer out.Close() + buf := new(bytes.Buffer) + _, _ = buf.ReadFrom(out) + global.LOG.Debugf("image %s pull stdout: %v", image, buf.String()) + }() + return nil +} + +func (u *ImageService) ImageLoad(req dto.ImageLoad) error { + file, err := os.Open(req.Path) + if err != nil { + return err + } + defer file.Close() + client, err := docker.NewDockerClient() + if err != nil { + return err + } + if _, err := client.ImageLoad(context.TODO(), file, true); err != nil { + return err + } + return nil +} + +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}) + if err != nil { + return err + } + defer out.Close() + if _, err = io.Copy(file, out); err != nil { + return err + } + return nil +} + +func (u *ImageService) ImagePush(req dto.ImagePush) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + repo, err := imageRepoRepo.Get(commonRepo.WithByID(req.RepoID)) + if err != nil { + return err + } + options := types.ImagePushOptions{} + if repo.Auth { + authConfig := types.AuthConfig{ + Username: repo.Username, + Password: repo.Password, + } + encodedJSON, err := json.Marshal(authConfig) + if err != nil { + return err + } + authStr := base64.URLEncoding.EncodeToString(encodedJSON) + 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 + } + 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) + return + } + defer out.Close() + buf := new(bytes.Buffer) + _, _ = buf.ReadFrom(out) + global.LOG.Debugf("image %s push stdout: %v", req.ImageName, buf.String()) + }() + + return nil +} + +func (u *ImageService) ImageRemove(req dto.ImageRemove) 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 + } + return nil +} + +func formatFileSize(fileSize int64) (size string) { + if fileSize < 1024 { + return fmt.Sprintf("%.2fB", float64(fileSize)/float64(1)) + } else if fileSize < (1024 * 1024) { + return fmt.Sprintf("%.2fKB", float64(fileSize)/float64(1024)) + } else if fileSize < (1024 * 1024 * 1024) { + return fmt.Sprintf("%.2fMB", float64(fileSize)/float64(1024*1024)) + } else if fileSize < (1024 * 1024 * 1024 * 1024) { + return fmt.Sprintf("%.2fGB", float64(fileSize)/float64(1024*1024*1024)) + } else if fileSize < (1024 * 1024 * 1024 * 1024 * 1024) { + return fmt.Sprintf("%.2fTB", float64(fileSize)/float64(1024*1024*1024*1024)) + } else { + return fmt.Sprintf("%.2fEB", float64(fileSize)/float64(1024*1024*1024*1024*1024)) + } +} diff --git a/backend/app/service/image_repo.go b/backend/app/service/image_repo.go index 7c92b4c12..20bc34301 100644 --- a/backend/app/service/image_repo.go +++ b/backend/app/service/image_repo.go @@ -11,6 +11,7 @@ type ImageRepoService struct{} type IImageRepoService interface { Page(search dto.PageInfo) (int64, interface{}, error) + List() ([]dto.ImageRepoOption, error) Create(imageRepoDto dto.ImageRepoCreate) error Update(id uint, upMap map[string]interface{}) error BatchDelete(ids []uint) error @@ -33,6 +34,19 @@ func (u *ImageRepoService) Page(search dto.PageInfo) (int64, interface{}, error) return total, dtoOps, err } +func (u *ImageRepoService) List() ([]dto.ImageRepoOption, error) { + ops, err := imageRepoRepo.List(commonRepo.WithOrderBy("created_at desc")) + var dtoOps []dto.ImageRepoOption + for _, op := range ops { + var item dto.ImageRepoOption + if err := copier.Copy(&item, &op); err != nil { + return nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) + } + dtoOps = append(dtoOps, item) + } + return dtoOps, err +} + func (u *ImageRepoService) Create(imageRepoDto dto.ImageRepoCreate) error { imageRepo, _ := imageRepoRepo.Get(commonRepo.WithByName(imageRepoDto.RepoName)) if imageRepo.ID != 0 { @@ -48,9 +62,17 @@ func (u *ImageRepoService) Create(imageRepoDto dto.ImageRepoCreate) error { } func (u *ImageRepoService) BatchDelete(ids []uint) error { + for _, id := range ids { + if id == 1 { + return errors.New("The default value cannot be edit !") + } + } return imageRepoRepo.Delete(commonRepo.WithIdsIn(ids)) } func (u *ImageRepoService) Update(id uint, upMap map[string]interface{}) error { + if id == 1 { + return errors.New("The default value cannot be deleted !") + } return imageRepoRepo.Update(id, upMap) } diff --git a/backend/app/service/image_test.go b/backend/app/service/image_test.go new file mode 100644 index 000000000..7f6adbb47 --- /dev/null +++ b/backend/app/service/image_test.go @@ -0,0 +1,30 @@ +package service + +import ( + "context" + "fmt" + "io" + "os" + "testing" + + "github.com/1Panel-dev/1Panel/utils/docker" +) + +func TestImage(t *testing.T) { + file, err := os.OpenFile(("/tmp/nginx.tar"), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666) + if err != nil { + fmt.Println(err) + } + defer file.Close() + + client, err := docker.NewDockerClient() + if err != nil { + fmt.Println(err) + } + out, err := client.ImageSave(context.TODO(), []string{"nginx:1.14.2"}) + fmt.Println(err) + defer out.Close() + if _, err = io.Copy(file, out); err != nil { + fmt.Println(err) + } +} diff --git a/backend/router/ro_container.go b/backend/router/ro_container.go index ffdcf956e..9d2b3969c 100644 --- a/backend/router/ro_container.go +++ b/backend/router/ro_container.go @@ -25,9 +25,17 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) { withRecordRouter.POST("operate", baseApi.ContainerOperation) withRecordRouter.POST("/log", baseApi.ContainerLogs) - baRouter.POST("/repo/search", baseApi.GetRepoList) + baRouter.POST("/repo/search", baseApi.SearchRepo) baRouter.PUT("/repo/:id", baseApi.UpdateRepo) + baRouter.GET("/repo", baseApi.ListRepo) withRecordRouter.POST("/repo", baseApi.CreateRepo) withRecordRouter.POST("/repo/del", baseApi.DeleteRepo) + + baRouter.POST("/image/search", baseApi.SearchImage) + baRouter.POST("/image/pull", baseApi.ImagePull) + baRouter.POST("/image/push", baseApi.ImagePush) + baRouter.POST("/image/save", baseApi.ImageSave) + baRouter.POST("/image/load", baseApi.ImageLoad) + baRouter.POST("/image/remove", baseApi.ImageRemove) } } diff --git a/frontend/src/api/interface/container.ts b/frontend/src/api/interface/container.ts index 418283ab2..3bf17f2ec 100644 --- a/frontend/src/api/interface/container.ts +++ b/frontend/src/api/interface/container.ts @@ -22,6 +22,34 @@ export namespace Container { mode: string; } + export interface ImageInfo { + id: string; + createdAt: Date; + name: string; + version: string; + size: string; + } + export interface ImagePull { + repoID: number; + imageName: 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; + path: string; + name: string; + } + export interface RepoCreate { name: string; downloadUrl: string; @@ -47,4 +75,9 @@ export namespace Container { password: string; auth: boolean; } + export interface RepoOptions { + id: number; + name: string; + downloadUrl: string; + } } diff --git a/frontend/src/api/modules/container.ts b/frontend/src/api/modules/container.ts index a6ad17a55..b322ca5d2 100644 --- a/frontend/src/api/modules/container.ts +++ b/frontend/src/api/modules/container.ts @@ -18,10 +18,33 @@ export const getContainerInspect = (containerID: string) => { return http.get(`/containers/detail/${containerID}`); }; +// image +export const getImagePage = (params: ReqPage) => { + return http.post>(`/containers/image/search`, params); +}; +export const imagePull = (params: Container.ImagePull) => { + return http.post(`/containers/image/pull`, params); +}; +export const imagePush = (params: Container.ImagePush) => { + return http.post(`/containers/image/push`, params); +}; +export const imageLoad = (params: Container.ImageLoad) => { + return http.post(`/containers/image/load`, params); +}; +export const imageSave = (params: Container.ImageSave) => { + return http.post(`/containers/image/save`, params); +}; +export const imageRemove = (params: Container.ImageRemove) => { + return http.post(`/containers/image/remove`, params); +}; + // repo export const getRepoPage = (params: ReqPage) => { return http.post>(`/containers/repo/search`, params); }; +export const getRepoOption = () => { + return http.get(`/containers/repo`); +}; export const repoCreate = (params: Container.RepoCreate) => { return http.post(`/containers/repo`, params); }; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index c8056cbdd..5158752e7 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -161,7 +161,6 @@ export default { reName: 'ReName', remove: 'Remove', container: 'Container', - image: 'Image', network: 'Network', storage: 'Storage', schedule: 'Schedule', @@ -172,6 +171,25 @@ export default { lastHour: 'Last Hour', last10Min: 'Last 10 Minutes', + image: 'Image', + pullFromRepo: 'Pull from repo', + imagePull: 'Image pull', + imagePush: 'Image push', + repoName: 'Repo Name', + imageName: 'Image name', + pull: 'Pull', + path: 'Path', + importImage: 'Import image', + import: 'Import', + build: 'Build', + label: 'Label', + push: 'Push', + fileName: 'FileName', + export: 'Export', + exportImage: 'ExportImage', + version: 'Version', + size: 'Size', + repo: 'Repo', name: 'Name', downloadUrl: 'Download URL', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 83e5c46a0..3485b8b1a 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -158,7 +158,6 @@ export default { reName: '重命名', remove: '移除', container: '容器', - image: '镜像', network: '网络', storage: '数据卷', schedule: '编排', @@ -169,6 +168,25 @@ export default { lastHour: '最近 1 小时', last10Min: '最近 10 分钟', + image: '镜像', + pullFromRepo: '从仓库中拉取', + imagePull: '镜像拉取', + imagePush: '镜像推送', + repoName: '仓库名', + imageName: '镜像名', + pull: '拉取', + path: '路径', + importImage: '导入镜像', + import: '导入', + build: '构建镜像', + label: '标签', + push: '推送', + fileName: '文件名', + export: '导出', + exportImage: '导出镜像', + version: '版本', + size: '大小', + repo: '仓库', name: '名称', downloadUrl: '下载地址', diff --git a/frontend/src/views/container/image/index.vue b/frontend/src/views/container/image/index.vue index e69de29bb..37ca959c2 100644 --- a/frontend/src/views/container/image/index.vue +++ b/frontend/src/views/container/image/index.vue @@ -0,0 +1,391 @@ + + + diff --git a/frontend/src/views/container/index.vue b/frontend/src/views/container/index.vue index 74f0b1460..9e1a650e0 100644 --- a/frontend/src/views/container/index.vue +++ b/frontend/src/views/container/index.vue @@ -24,7 +24,7 @@ - + @@ -34,7 +34,7 @@ import { ref } from 'vue'; import Container from '@/views/container/container/index.vue'; import Repo from '@/views/container/repo/index.vue'; -import Backup from '@/views/setting/tabs/backup.vue'; +import Image from '@/views/container/image/index.vue'; import Monitor from '@/views/setting/tabs/monitor.vue'; import About from '@/views/setting/tabs/about.vue';