mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 08:19:15 +08:00
feat: 支持容器镜像升级操作 (#1393)
This commit is contained in:
parent
e71f765f2a
commit
3ead633343
@ -245,6 +245,32 @@ func (b *BaseApi) ContainerCreate(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, nil)
|
helper.SuccessWithData(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Tags Container
|
||||||
|
// @Summary Upgrade container
|
||||||
|
// @Description 更新容器镜像
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.ContainerUpgrade true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /containers/upgrade [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["name","image"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新容器镜像 [name][image]","formatEN":"upgrade container image [name][image]"}
|
||||||
|
func (b *BaseApi) ContainerUpgrade(c *gin.Context) {
|
||||||
|
var req dto.ContainerUpgrade
|
||||||
|
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.ContainerUpgrade(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// @Tags Container
|
// @Tags Container
|
||||||
// @Summary Clean container
|
// @Summary Clean container
|
||||||
// @Description 容器清理
|
// @Description 容器清理
|
||||||
|
@ -41,7 +41,7 @@ type ContainerOperate struct {
|
|||||||
PublishAllPorts bool `json:"publishAllPorts"`
|
PublishAllPorts bool `json:"publishAllPorts"`
|
||||||
ExposedPorts []PortHelper `json:"exposedPorts"`
|
ExposedPorts []PortHelper `json:"exposedPorts"`
|
||||||
Cmd []string `json:"cmd"`
|
Cmd []string `json:"cmd"`
|
||||||
CPUShares int64 `josn:"cpuShares"`
|
CPUShares int64 `json:"cpuShares"`
|
||||||
NanoCPUs int64 `json:"nanoCPUs"`
|
NanoCPUs int64 `json:"nanoCPUs"`
|
||||||
Memory int64 `json:"memory"`
|
Memory int64 `json:"memory"`
|
||||||
AutoRemove bool `json:"autoRemove"`
|
AutoRemove bool `json:"autoRemove"`
|
||||||
@ -51,6 +51,11 @@ type ContainerOperate struct {
|
|||||||
RestartPolicy string `json:"restartPolicy"`
|
RestartPolicy string `json:"restartPolicy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ContainerUpgrade struct {
|
||||||
|
Name string `json:"name" validate:"required"`
|
||||||
|
Image string `json:"image" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
type ContainterStats struct {
|
type ContainterStats struct {
|
||||||
CPUPercent float64 `json:"cpuPercent"`
|
CPUPercent float64 `json:"cpuPercent"`
|
||||||
Memory float64 `json:"memory"`
|
Memory float64 `json:"memory"`
|
||||||
@ -83,7 +88,7 @@ type ContainerOperation struct {
|
|||||||
|
|
||||||
type ContainerPrune struct {
|
type ContainerPrune struct {
|
||||||
PruneType string `json:"pruneType" validate:"required,oneof=container image volume network"`
|
PruneType string `json:"pruneType" validate:"required,oneof=container image volume network"`
|
||||||
WithTagAll bool `josn:"withTagAll"`
|
WithTagAll bool `json:"withTagAll"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContainerPruneReport struct {
|
type ContainerPruneReport struct {
|
||||||
|
@ -10,35 +10,35 @@ type ImageInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ImageLoad struct {
|
type ImageLoad struct {
|
||||||
Path string `josn:"path" validate:"required"`
|
Path string `json:"path" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImageBuild struct {
|
type ImageBuild struct {
|
||||||
From string `josn:"from" validate:"required"`
|
From string `json:"from" validate:"required"`
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Dockerfile string `josn:"dockerfile" validate:"required"`
|
Dockerfile string `json:"dockerfile" validate:"required"`
|
||||||
Tags []string `josn:"tags"`
|
Tags []string `json:"tags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImagePull struct {
|
type ImagePull struct {
|
||||||
RepoID uint `josn:"repoID"`
|
RepoID uint `json:"repoID"`
|
||||||
ImageName string `josn:"imageName" validate:"required"`
|
ImageName string `json:"imageName" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImageTag struct {
|
type ImageTag struct {
|
||||||
RepoID uint `josn:"repoID"`
|
RepoID uint `json:"repoID"`
|
||||||
SourceID string `json:"sourceID" validate:"required"`
|
SourceID string `json:"sourceID" validate:"required"`
|
||||||
TargetName string `josn:"targetName" validate:"required"`
|
TargetName string `json:"targetName" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImagePush struct {
|
type ImagePush struct {
|
||||||
RepoID uint `josn:"repoID" validate:"required"`
|
RepoID uint `json:"repoID" validate:"required"`
|
||||||
TagName string `json:"tagName" validate:"required"`
|
TagName string `json:"tagName" validate:"required"`
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImageSave struct {
|
type ImageSave struct {
|
||||||
TagName string `json:"tagName" validate:"required"`
|
TagName string `json:"tagName" validate:"required"`
|
||||||
Path string `josn:"path" validate:"required"`
|
Path string `json:"path" validate:"required"`
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ type IContainerService interface {
|
|||||||
ComposeOperation(req dto.ComposeOperation) error
|
ComposeOperation(req dto.ComposeOperation) error
|
||||||
ContainerCreate(req dto.ContainerOperate) error
|
ContainerCreate(req dto.ContainerOperate) error
|
||||||
ContainerUpdate(req dto.ContainerOperate) error
|
ContainerUpdate(req dto.ContainerOperate) error
|
||||||
|
ContainerUpgrade(req dto.ContainerUpgrade) error
|
||||||
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
|
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error)
|
||||||
LoadResouceLimit() (*dto.ResourceLimit, error)
|
LoadResouceLimit() (*dto.ResourceLimit, error)
|
||||||
ContainerLogClean(req dto.OperationWithName) error
|
ContainerLogClean(req dto.OperationWithName) error
|
||||||
@ -241,9 +242,9 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var config *container.Config
|
var config container.Config
|
||||||
var hostConf *container.HostConfig
|
var hostConf container.HostConfig
|
||||||
if err := loadConfigInfo(req, config, hostConf); err != nil {
|
if err := loadConfigInfo(req, &config, &hostConf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,7 +256,7 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
container, err := client.ContainerCreate(ctx, config, hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name)
|
container, err := client.ContainerCreate(ctx, &config, &hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
|
_ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
|
||||||
return err
|
return err
|
||||||
@ -284,6 +285,7 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
|||||||
data.Image = oldContainer.Config.Image
|
data.Image = oldContainer.Config.Image
|
||||||
data.Cmd = oldContainer.Config.Cmd
|
data.Cmd = oldContainer.Config.Cmd
|
||||||
data.Env = oldContainer.Config.Env
|
data.Env = oldContainer.Config.Env
|
||||||
|
data.CPUShares = oldContainer.HostConfig.CPUShares
|
||||||
for key, val := range oldContainer.Config.Labels {
|
for key, val := range oldContainer.Config.Labels {
|
||||||
data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val))
|
data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val))
|
||||||
}
|
}
|
||||||
@ -357,6 +359,41 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
oldContainer, err := client.ContainerInspect(ctx, req.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !checkImageExist(client, req.Image) {
|
||||||
|
if err := pullImages(ctx, client, req.Image); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config := oldContainer.Config
|
||||||
|
config.Image = req.Image
|
||||||
|
hostConf := oldContainer.HostConfig
|
||||||
|
if err := client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{Force: true}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("recreate contianer failed, err: %v", err)
|
||||||
|
}
|
||||||
|
global.LOG.Infof("update container %s successful! now check if the container is started.", req.Name)
|
||||||
|
if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
|
||||||
|
return fmt.Errorf("update successful but start failed, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error {
|
func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error {
|
||||||
var err error
|
var err error
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@ -648,6 +685,7 @@ func loadConfigInfo(req dto.ContainerOperate, config *container.Config, hostConf
|
|||||||
config.ExposedPorts = exposeds
|
config.ExposedPorts = exposeds
|
||||||
|
|
||||||
hostConf.AutoRemove = req.AutoRemove
|
hostConf.AutoRemove = req.AutoRemove
|
||||||
|
hostConf.CPUShares = req.CPUShares
|
||||||
hostConf.PublishAllPorts = req.PublishAllPorts
|
hostConf.PublishAllPorts = req.PublishAllPorts
|
||||||
hostConf.RestartPolicy = container.RestartPolicy{Name: req.RestartPolicy}
|
hostConf.RestartPolicy = container.RestartPolicy{Name: req.RestartPolicy}
|
||||||
if req.RestartPolicy == "on-failure" {
|
if req.RestartPolicy == "on-failure" {
|
||||||
|
@ -535,7 +535,7 @@ func (u *SnapshotService) handleDaemonJson(fileOp files.FileOp, operation string
|
|||||||
if operation == "snapshot" || operation == "recover" {
|
if operation == "snapshot" || operation == "recover" {
|
||||||
_, err := os.Stat(daemonJsonPath)
|
_, err := os.Stat(daemonJsonPath)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
global.LOG.Info("no daemon.josn in snapshot and system now, nothing happened")
|
global.LOG.Info("no daemon.json in snapshot and system now, nothing happened")
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if err := fileOp.CopyFile(daemonJsonPath, target); err != nil {
|
if err := fileOp.CopyFile(daemonJsonPath, target); err != nil {
|
||||||
|
@ -20,6 +20,7 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
|
|||||||
|
|
||||||
baRouter.POST("", baseApi.ContainerCreate)
|
baRouter.POST("", baseApi.ContainerCreate)
|
||||||
baRouter.POST("/update", baseApi.ContainerUpdate)
|
baRouter.POST("/update", baseApi.ContainerUpdate)
|
||||||
|
baRouter.POST("/upgrade", baseApi.ContainerUpgrade)
|
||||||
baRouter.POST("/info", baseApi.ContainerInfo)
|
baRouter.POST("/info", baseApi.ContainerInfo)
|
||||||
baRouter.POST("/search", baseApi.SearchContainer)
|
baRouter.POST("/search", baseApi.SearchContainer)
|
||||||
baRouter.GET("/search/log", baseApi.ContainerLogs)
|
baRouter.GET("/search/log", baseApi.ContainerLogs)
|
||||||
|
@ -2695,6 +2695,49 @@ var doc = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/containers/upgrade": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "更新容器镜像",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Container"
|
||||||
|
],
|
||||||
|
"summary": "Upgrade container",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.ContainerUpgrade"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-panel-log": {
|
||||||
|
"BeforeFuntions": [],
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"image"
|
||||||
|
],
|
||||||
|
"formatEN": "upgrade container image [name][image]",
|
||||||
|
"formatZH": "更新容器镜像 [name][image]",
|
||||||
|
"paramKeys": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/containers/volume": {
|
"/containers/volume": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@ -10694,7 +10737,7 @@ var doc = `{
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cpushares": {
|
"cpuShares": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
@ -10800,6 +10843,21 @@ var doc = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.ContainerUpgrade": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"image",
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"image": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.ContainterStats": {
|
"dto.ContainterStats": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -2681,6 +2681,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/containers/upgrade": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "更新容器镜像",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Container"
|
||||||
|
],
|
||||||
|
"summary": "Upgrade container",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.ContainerUpgrade"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-panel-log": {
|
||||||
|
"BeforeFuntions": [],
|
||||||
|
"bodyKeys": [
|
||||||
|
"name",
|
||||||
|
"image"
|
||||||
|
],
|
||||||
|
"formatEN": "upgrade container image [name][image]",
|
||||||
|
"formatZH": "更新容器镜像 [name][image]",
|
||||||
|
"paramKeys": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/containers/volume": {
|
"/containers/volume": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@ -10680,7 +10723,7 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cpushares": {
|
"cpuShares": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
@ -10786,6 +10829,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.ContainerUpgrade": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"image",
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"image": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.ContainterStats": {
|
"dto.ContainterStats": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -261,7 +261,7 @@ definitions:
|
|||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
cpushares:
|
cpuShares:
|
||||||
type: integer
|
type: integer
|
||||||
env:
|
env:
|
||||||
items:
|
items:
|
||||||
@ -334,6 +334,16 @@ definitions:
|
|||||||
spaceReclaimed:
|
spaceReclaimed:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
dto.ContainerUpgrade:
|
||||||
|
properties:
|
||||||
|
image:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- image
|
||||||
|
- name
|
||||||
|
type: object
|
||||||
dto.ContainterStats:
|
dto.ContainterStats:
|
||||||
properties:
|
properties:
|
||||||
cache:
|
cache:
|
||||||
@ -5070,6 +5080,34 @@ paths:
|
|||||||
formatEN: update container [name][image]
|
formatEN: update container [name][image]
|
||||||
formatZH: 更新容器 [name][image]
|
formatZH: 更新容器 [name][image]
|
||||||
paramKeys: []
|
paramKeys: []
|
||||||
|
/containers/upgrade:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 更新容器镜像
|
||||||
|
parameters:
|
||||||
|
- description: request
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.ContainerUpgrade'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Upgrade container
|
||||||
|
tags:
|
||||||
|
- Container
|
||||||
|
x-panel-log:
|
||||||
|
BeforeFuntions: []
|
||||||
|
bodyKeys:
|
||||||
|
- name
|
||||||
|
- image
|
||||||
|
formatEN: upgrade container image [name][image]
|
||||||
|
formatZH: 更新容器镜像 [name][image]
|
||||||
|
paramKeys: []
|
||||||
/containers/volume:
|
/containers/volume:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
@ -14,6 +14,9 @@ export const createContainer = (params: Container.ContainerHelper) => {
|
|||||||
export const updateContainer = (params: Container.ContainerHelper) => {
|
export const updateContainer = (params: Container.ContainerHelper) => {
|
||||||
return http.post(`/containers/update`, params, 3000000);
|
return http.post(`/containers/update`, params, 3000000);
|
||||||
};
|
};
|
||||||
|
export const upgradeContainer = (name: string, image: string) => {
|
||||||
|
return http.post(`/containers/upgrade`, { name: name, image: image }, 3000000);
|
||||||
|
};
|
||||||
export const loadContainerInfo = (name: string) => {
|
export const loadContainerInfo = (name: string) => {
|
||||||
return http.post<Container.ContainerHelper>(`/containers/info`, { name: name });
|
return http.post<Container.ContainerHelper>(`/containers/info`, { name: name });
|
||||||
};
|
};
|
||||||
|
@ -491,11 +491,20 @@ const message = {
|
|||||||
|
|
||||||
user: 'User',
|
user: 'User',
|
||||||
command: 'Command',
|
command: 'Command',
|
||||||
commandHelper: 'Please enter the correct command, separated by spaces if there are multiple commands.',
|
|
||||||
custom: 'Custom',
|
custom: 'Custom',
|
||||||
emptyUser: 'When empty, you will log in as default',
|
emptyUser: 'When empty, you will log in as default',
|
||||||
containerTerminal: 'Terminal',
|
containerTerminal: 'Terminal',
|
||||||
|
|
||||||
|
upgrade: 'Upgrade',
|
||||||
|
upgradeHelper: 'This operation only supports upgrading container versions.',
|
||||||
|
upgradeWarning: 'The target version is lower than the original image version. Please try again!',
|
||||||
|
upgradeWarning2:
|
||||||
|
'The upgrade operation requires rebuilding the container, and any non-persistent data will be lost. Do you want to continue?',
|
||||||
|
oldImage: 'Current image',
|
||||||
|
targetImage: 'Target image',
|
||||||
|
appHelper:
|
||||||
|
'This container is sourced from the application store. Upgrading it may cause the service to be unavailable. Do you want to continue?',
|
||||||
|
|
||||||
port: 'Port',
|
port: 'Port',
|
||||||
server: 'Host',
|
server: 'Host',
|
||||||
serverExample: 'e.g. 80, 80-88, ip:80 or ip:80-88',
|
serverExample: 'e.g. 80, 80-88, ip:80 or ip:80-88',
|
||||||
|
@ -496,11 +496,18 @@ const message = {
|
|||||||
|
|
||||||
user: '用户',
|
user: '用户',
|
||||||
command: '命令',
|
command: '命令',
|
||||||
commandHelper: '请输入正确的命令,多个命令空格分割',
|
|
||||||
custom: '自定义',
|
custom: '自定义',
|
||||||
containerTerminal: '终端',
|
containerTerminal: '终端',
|
||||||
emptyUser: '为空时,将使用容器默认的用户登录',
|
emptyUser: '为空时,将使用容器默认的用户登录',
|
||||||
|
|
||||||
|
upgrade: '升级',
|
||||||
|
upgradeHelper: '该操作仅支持容器版本升级',
|
||||||
|
upgradeWarning: '当前目标版本低于原镜像版本,请重新输入!',
|
||||||
|
upgradeWarning2: '升级操作需要重建容器,任何未持久化的数据将会丢失,是否继续?',
|
||||||
|
oldImage: '当前镜像',
|
||||||
|
targetImage: '目标镜像',
|
||||||
|
appHelper: '该容器来源于应用商店,升级可能导致该服务不可用,是否继续?',
|
||||||
|
|
||||||
port: '端口',
|
port: '端口',
|
||||||
server: '服务器',
|
server: '服务器',
|
||||||
serverExample: '例: 80, 80-88, ip:80 或者 ip:80-88',
|
serverExample: '例: 80, 80-88, ip:80 或者 ip:80-88',
|
||||||
|
@ -16,25 +16,25 @@
|
|||||||
{{ $t('container.containerPrune') }}
|
{{ $t('container.containerPrune') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button-group style="margin-left: 10px">
|
<el-button-group style="margin-left: 10px">
|
||||||
<el-button :disabled="checkStatus('start')" @click="onOperate('start')">
|
<el-button :disabled="checkStatus('start', null)" @click="onOperate('start', null)">
|
||||||
{{ $t('container.start') }}
|
{{ $t('container.start') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button :disabled="checkStatus('stop')" @click="onOperate('stop')">
|
<el-button :disabled="checkStatus('stop', null)" @click="onOperate('stop', null)">
|
||||||
{{ $t('container.stop') }}
|
{{ $t('container.stop') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button :disabled="checkStatus('restart')" @click="onOperate('restart')">
|
<el-button :disabled="checkStatus('restart', null)" @click="onOperate('restart', null)">
|
||||||
{{ $t('container.restart') }}
|
{{ $t('container.restart') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button :disabled="checkStatus('kill')" @click="onOperate('kill')">
|
<el-button :disabled="checkStatus('kill', null)" @click="onOperate('kill', null)">
|
||||||
{{ $t('container.kill') }}
|
{{ $t('container.kill') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button :disabled="checkStatus('pause')" @click="onOperate('pause')">
|
<el-button :disabled="checkStatus('pause', null)" @click="onOperate('pause', null)">
|
||||||
{{ $t('container.pause') }}
|
{{ $t('container.pause') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button :disabled="checkStatus('unpause')" @click="onOperate('unpause')">
|
<el-button :disabled="checkStatus('unpause', null)" @click="onOperate('unpause', null)">
|
||||||
{{ $t('container.unpause') }}
|
{{ $t('container.unpause') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button :disabled="checkStatus('remove')" @click="onOperate('remove')">
|
<el-button :disabled="checkStatus('remove', null)" @click="onOperate('remove', null)">
|
||||||
{{ $t('container.remove') }}
|
{{ $t('container.remove') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-button-group>
|
</el-button-group>
|
||||||
@ -110,7 +110,7 @@
|
|||||||
/>
|
/>
|
||||||
<fu-table-operations
|
<fu-table-operations
|
||||||
width="300px"
|
width="300px"
|
||||||
:ellipsis="10"
|
:ellipsis="3"
|
||||||
:buttons="buttons"
|
:buttons="buttons"
|
||||||
:label="$t('commons.table.operate')"
|
:label="$t('commons.table.operate')"
|
||||||
fix
|
fix
|
||||||
@ -123,7 +123,8 @@
|
|||||||
|
|
||||||
<ReNameDialog @search="search" ref="dialogReNameRef" />
|
<ReNameDialog @search="search" ref="dialogReNameRef" />
|
||||||
<ContainerLogDialog ref="dialogContainerLogRef" />
|
<ContainerLogDialog ref="dialogContainerLogRef" />
|
||||||
<CreateDialog @search="search" ref="dialogOperateRef" />
|
<OperateDialog @search="search" ref="dialogOperateRef" />
|
||||||
|
<UpgraeDialog @search="search" ref="dialogUpgradeRef" />
|
||||||
<MonitorDialog ref="dialogMonitorRef" />
|
<MonitorDialog ref="dialogMonitorRef" />
|
||||||
<TerminalDialog ref="dialogTerminalRef" />
|
<TerminalDialog ref="dialogTerminalRef" />
|
||||||
</div>
|
</div>
|
||||||
@ -133,7 +134,8 @@
|
|||||||
import Tooltip from '@/components/tooltip/index.vue';
|
import Tooltip from '@/components/tooltip/index.vue';
|
||||||
import TableSetting from '@/components/table-setting/index.vue';
|
import TableSetting from '@/components/table-setting/index.vue';
|
||||||
import ReNameDialog from '@/views/container/container/rename/index.vue';
|
import ReNameDialog from '@/views/container/container/rename/index.vue';
|
||||||
import CreateDialog from '@/views/container/container/operate/index.vue';
|
import OperateDialog from '@/views/container/container/operate/index.vue';
|
||||||
|
import UpgraeDialog from '@/views/container/container/upgrade/index.vue';
|
||||||
import MonitorDialog from '@/views/container/container/monitor/index.vue';
|
import MonitorDialog from '@/views/container/container/monitor/index.vue';
|
||||||
import ContainerLogDialog from '@/views/container/container/log/index.vue';
|
import ContainerLogDialog from '@/views/container/container/log/index.vue';
|
||||||
import TerminalDialog from '@/views/container/container/terminal/index.vue';
|
import TerminalDialog from '@/views/container/container/terminal/index.vue';
|
||||||
@ -164,6 +166,7 @@ const paginationConfig = reactive({
|
|||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
const dialogUpgradeRef = ref();
|
||||||
|
|
||||||
const dockerStatus = ref('Running');
|
const dockerStatus = ref('Running');
|
||||||
const loadStatus = async () => {
|
const loadStatus = async () => {
|
||||||
@ -231,11 +234,10 @@ const onOpenDialog = async (
|
|||||||
cmd: [],
|
cmd: [],
|
||||||
cmdStr: '',
|
cmdStr: '',
|
||||||
exposedPorts: [],
|
exposedPorts: [],
|
||||||
|
cpuShares: 1024,
|
||||||
nanoCPUs: 0,
|
nanoCPUs: 0,
|
||||||
memory: 0,
|
memory: 0,
|
||||||
memoryItem: 0,
|
memoryItem: 0,
|
||||||
memoryUnit: 'MB',
|
|
||||||
cpuUnit: 'Core',
|
|
||||||
volumes: [],
|
volumes: [],
|
||||||
labels: [],
|
labels: [],
|
||||||
env: [],
|
env: [],
|
||||||
@ -297,34 +299,35 @@ const onClean = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkStatus = (operation: string) => {
|
const checkStatus = (operation: string, row: Container.ContainerInfo | null) => {
|
||||||
if (selects.value.length < 1) {
|
let opList = row ? [row] : selects.value;
|
||||||
|
if (opList.length < 1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
case 'start':
|
case 'start':
|
||||||
for (const item of selects.value) {
|
for (const item of opList) {
|
||||||
if (item.state === 'running') {
|
if (item.state === 'running') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
case 'stop':
|
case 'stop':
|
||||||
for (const item of selects.value) {
|
for (const item of opList) {
|
||||||
if (item.state === 'stopped' || item.state === 'exited') {
|
if (item.state === 'stopped' || item.state === 'exited') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
case 'pause':
|
case 'pause':
|
||||||
for (const item of selects.value) {
|
for (const item of opList) {
|
||||||
if (item.state === 'paused' || item.state === 'exited') {
|
if (item.state === 'paused' || item.state === 'exited') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
case 'unpause':
|
case 'unpause':
|
||||||
for (const item of selects.value) {
|
for (const item of opList) {
|
||||||
if (item.state !== 'paused') {
|
if (item.state !== 'paused') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -332,9 +335,10 @@ const checkStatus = (operation: string) => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const onOperate = async (operation: string) => {
|
const onOperate = async (operation: string, row: Container.ContainerInfo | null) => {
|
||||||
|
let opList = row ? [row] : selects.value;
|
||||||
let msg = i18n.global.t('container.operatorHelper', [i18n.global.t('container.' + operation)]);
|
let msg = i18n.global.t('container.operatorHelper', [i18n.global.t('container.' + operation)]);
|
||||||
for (const item of selects.value) {
|
for (const item of opList) {
|
||||||
if (item.isFromApp) {
|
if (item.isFromApp) {
|
||||||
msg = i18n.global.t('container.operatorAppHelper', [i18n.global.t('container.' + operation)]);
|
msg = i18n.global.t('container.operatorAppHelper', [i18n.global.t('container.' + operation)]);
|
||||||
break;
|
break;
|
||||||
@ -346,7 +350,7 @@ const onOperate = async (operation: string) => {
|
|||||||
type: 'info',
|
type: 'info',
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
let ps = [];
|
let ps = [];
|
||||||
for (const item of selects.value) {
|
for (const item of opList) {
|
||||||
const param = {
|
const param = {
|
||||||
name: item.name,
|
name: item.name,
|
||||||
operation: operation,
|
operation: operation,
|
||||||
@ -384,6 +388,12 @@ const buttons = [
|
|||||||
onTerminal(row);
|
onTerminal(row);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('commons.button.log'),
|
||||||
|
click: (row: Container.ContainerInfo) => {
|
||||||
|
dialogContainerLogRef.value!.acceptParams({ containerID: row.containerID, container: row.name });
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: i18n.global.t('container.monitor'),
|
label: i18n.global.t('container.monitor'),
|
||||||
disabled: (row: Container.ContainerInfo) => {
|
disabled: (row: Container.ContainerInfo) => {
|
||||||
@ -393,6 +403,12 @@ const buttons = [
|
|||||||
onMonitor(row);
|
onMonitor(row);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('container.upgrade'),
|
||||||
|
click: (row: Container.ContainerInfo) => {
|
||||||
|
dialogUpgradeRef.value!.acceptParams({ container: row.name, image: row.imageName, fromApp: row.isFromApp });
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: i18n.global.t('container.rename'),
|
label: i18n.global.t('container.rename'),
|
||||||
click: (row: Container.ContainerInfo) => {
|
click: (row: Container.ContainerInfo) => {
|
||||||
@ -403,9 +419,66 @@ const buttons = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: i18n.global.t('commons.button.log'),
|
label: i18n.global.t('container.start'),
|
||||||
click: (row: Container.ContainerInfo) => {
|
click: (row: Container.ContainerInfo) => {
|
||||||
dialogContainerLogRef.value!.acceptParams({ containerID: row.containerID, container: row.name });
|
onOperate('start', row);
|
||||||
|
},
|
||||||
|
disabled: (row: any) => {
|
||||||
|
return checkStatus('start', row);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('container.stop'),
|
||||||
|
click: (row: Container.ContainerInfo) => {
|
||||||
|
onOperate('stop', row);
|
||||||
|
},
|
||||||
|
disabled: (row: any) => {
|
||||||
|
return checkStatus('stop', row);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('container.restart'),
|
||||||
|
click: (row: Container.ContainerInfo) => {
|
||||||
|
onOperate('restart', row);
|
||||||
|
},
|
||||||
|
disabled: (row: any) => {
|
||||||
|
return checkStatus('restart', row);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('container.kill'),
|
||||||
|
click: (row: Container.ContainerInfo) => {
|
||||||
|
onOperate('kill', row);
|
||||||
|
},
|
||||||
|
disabled: (row: any) => {
|
||||||
|
return checkStatus('kill', row);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('container.pause'),
|
||||||
|
click: (row: Container.ContainerInfo) => {
|
||||||
|
onOperate('pause', row);
|
||||||
|
},
|
||||||
|
disabled: (row: any) => {
|
||||||
|
return checkStatus('pause', row);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('container.unpause'),
|
||||||
|
click: (row: Container.ContainerInfo) => {
|
||||||
|
onOperate('unpause', row);
|
||||||
|
},
|
||||||
|
disabled: (row: any) => {
|
||||||
|
return checkStatus('unpause', row);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('container.remove'),
|
||||||
|
click: (row: Container.ContainerInfo) => {
|
||||||
|
onOperate('remove', row);
|
||||||
|
},
|
||||||
|
disabled: (row: any) => {
|
||||||
|
return checkStatus('remove', row);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -121,7 +121,7 @@
|
|||||||
<el-form-item :label="$t('container.mount')">
|
<el-form-item :label="$t('container.mount')">
|
||||||
<el-card style="width: 100%">
|
<el-card style="width: 100%">
|
||||||
<table style="width: 100%" class="tab-table">
|
<table style="width: 100%" class="tab-table">
|
||||||
<tr v-if="dialogData.rowData!.volumes.length !== 0">
|
<tr v-if="dialogData.rowData!.volumes!.length !== 0">
|
||||||
<th scope="col" width="39%" align="left">
|
<th scope="col" width="39%" align="left">
|
||||||
<label>{{ $t('container.serverPath') }}</label>
|
<label>{{ $t('container.serverPath') }}</label>
|
||||||
</th>
|
</th>
|
||||||
@ -252,9 +252,12 @@ const acceptParams = (params: DialogProps): void => {
|
|||||||
dialogData.value.rowData.cmdStr = itemCmd ? itemCmd.substring(0, itemCmd.length - 1) : '';
|
dialogData.value.rowData.cmdStr = itemCmd ? itemCmd.substring(0, itemCmd.length - 1) : '';
|
||||||
dialogData.value.rowData.labelsStr = dialogData.value.rowData.labels.join('\n');
|
dialogData.value.rowData.labelsStr = dialogData.value.rowData.labels.join('\n');
|
||||||
dialogData.value.rowData.envStr = dialogData.value.rowData.env.join('\n');
|
dialogData.value.rowData.envStr = dialogData.value.rowData.env.join('\n');
|
||||||
|
dialogData.value.rowData.exposedPorts = dialogData.value.rowData.exposedPorts || [];
|
||||||
for (const item of dialogData.value.rowData.exposedPorts) {
|
for (const item of dialogData.value.rowData.exposedPorts) {
|
||||||
item.host = item.hostPort;
|
item.host = item.hostPort;
|
||||||
}
|
}
|
||||||
|
dialogData.value.rowData.volumes = dialogData.value.rowData.volumes || [];
|
||||||
|
console.log(dialogData.value.rowData.cpuShares);
|
||||||
}
|
}
|
||||||
loadLimit();
|
loadLimit();
|
||||||
loadImageOptions();
|
loadImageOptions();
|
||||||
@ -337,20 +340,18 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
|
|||||||
if (!formEl) return;
|
if (!formEl) return;
|
||||||
formEl.validate(async (valid) => {
|
formEl.validate(async (valid) => {
|
||||||
if (!valid) return;
|
if (!valid) return;
|
||||||
if (dialogData.value.rowData!.envStr.length !== 0) {
|
if (dialogData.value.rowData?.envStr) {
|
||||||
dialogData.value.rowData!.env = dialogData.value.rowData!.envStr.split('\n');
|
dialogData.value.rowData.env = dialogData.value.rowData!.envStr.split('\n');
|
||||||
}
|
}
|
||||||
if (dialogData.value.rowData!.labelsStr.length !== 0) {
|
if (dialogData.value.rowData?.labelsStr) {
|
||||||
dialogData.value.rowData!.labels = dialogData.value.rowData!.labelsStr.split('\n');
|
dialogData.value.rowData!.labels = dialogData.value.rowData!.labelsStr.split('\n');
|
||||||
}
|
}
|
||||||
if (dialogData.value.rowData!.cmdStr.length !== 0) {
|
dialogData.value.rowData!.cmd = [];
|
||||||
let itemCmd = dialogData.value.rowData!.cmdStr.split(' ');
|
if (dialogData.value.rowData?.cmdStr) {
|
||||||
|
let itemCmd = dialogData.value.rowData!.cmdStr.split(`'`);
|
||||||
for (const cmd of itemCmd) {
|
for (const cmd of itemCmd) {
|
||||||
if (cmd.startsWith(`'`) && cmd.endsWith(`'`) && cmd.length >= 3) {
|
if (cmd && cmd !== ' ') {
|
||||||
dialogData.value.rowData!.cmd.push(cmd.substring(1, cmd.length - 2));
|
dialogData.value.rowData!.cmd.push(cmd);
|
||||||
} else {
|
|
||||||
MsgError(i18n.global.t('container.commandHelper'));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
141
frontend/src/views/container/container/upgrade/index.vue
Normal file
141
frontend/src/views/container/container/upgrade/index.vue
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer v-model="drawerVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
|
||||||
|
<template #header>
|
||||||
|
<DrawerHeader :header="$t('container.upgrade')" :resource="form.name" :back="handleClose" />
|
||||||
|
</template>
|
||||||
|
<el-alert v-if="form.fromApp" style="margin-bottom: 20px" :closable="false" type="error">
|
||||||
|
<template #default>
|
||||||
|
<span>
|
||||||
|
<span>{{ $t('container.appHelper') }}</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-alert>
|
||||||
|
<el-form @submit.prevent ref="formRef" v-loading="loading" :model="form" label-position="top">
|
||||||
|
<el-row type="flex" justify="center">
|
||||||
|
<el-col :span="22">
|
||||||
|
<el-form-item :label="$t('container.oldImage')" prop="oldImage">
|
||||||
|
<el-tag>{{ form.imageName }}:{{ form.oldTag }}</el-tag>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('container.targetImage')" prop="newTag" :rules="Rules.requiredInput">
|
||||||
|
<el-input v-model="form.newTag">
|
||||||
|
<template #prefix>{{ form.imageName }}:</template>
|
||||||
|
</el-input>
|
||||||
|
<span class="input-help">{{ $t('container.upgradeHelper') }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button :disabled="loading" @click="drawerVisiable = false">
|
||||||
|
{{ $t('commons.button.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
|
||||||
|
{{ $t('commons.button.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { upgradeContainer } from '@/api/modules/container';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
import { MsgSuccess, MsgWarning } from '@/utils/message';
|
||||||
|
import { ElForm } from 'element-plus';
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
name: '',
|
||||||
|
imageName: '',
|
||||||
|
oldTag: '',
|
||||||
|
newTag: '',
|
||||||
|
fromApp: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const drawerVisiable = ref<boolean>(false);
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
container: string;
|
||||||
|
image: string;
|
||||||
|
fromApp: boolean;
|
||||||
|
}
|
||||||
|
const acceptParams = (props: DialogProps): void => {
|
||||||
|
form.name = props.container;
|
||||||
|
form.imageName = props.image.indexOf(':') !== -1 ? props.image.split(':')[0] : props.image;
|
||||||
|
form.oldTag = props.image.indexOf(':') !== -1 ? props.image.split(':')[1] : 'latest';
|
||||||
|
form.newTag = '';
|
||||||
|
form.fromApp = props.fromApp;
|
||||||
|
drawerVisiable.value = true;
|
||||||
|
};
|
||||||
|
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||||
|
|
||||||
|
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
if (!compareVersion(form.newTag, form.oldTag)) {
|
||||||
|
MsgWarning(i18n.global.t('container.upgradeWarning'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ElMessageBox.confirm(i18n.global.t('container.upgradeWarning2'), i18n.global.t('container.upgrade'), {
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
}).then(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
await upgradeContainer(form.name, form.imageName + ':' + form.newTag)
|
||||||
|
.then(() => {
|
||||||
|
loading.value = false;
|
||||||
|
emit('search');
|
||||||
|
drawerVisiable.value = false;
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleClose = async () => {
|
||||||
|
drawerVisiable.value = false;
|
||||||
|
emit('search');
|
||||||
|
};
|
||||||
|
|
||||||
|
function compareVersion(vNew, vOld) {
|
||||||
|
if (vNew === 'latest') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let v1 = vNew
|
||||||
|
.replace('-', '.')
|
||||||
|
.replace(/[^\d.]/g, '')
|
||||||
|
.split('.');
|
||||||
|
let v2 = vOld
|
||||||
|
.replace('-', '.')
|
||||||
|
.replace(/[^\d.]/g, '')
|
||||||
|
.split('.');
|
||||||
|
|
||||||
|
for (let i = 0; i < Math.max(v1.length, v2.length); i++) {
|
||||||
|
let num1 = parseInt(v1[i] || 0);
|
||||||
|
let num2 = parseInt(v2[i] || 0);
|
||||||
|
|
||||||
|
if (num1 > num2) {
|
||||||
|
return true;
|
||||||
|
} else if (num1 < num2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
acceptParams,
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
x
Reference in New Issue
Block a user