feat: 镜像拉取、推送、构建改为任务方式实现 (#6598)

Co-authored-by: ssonglius11 <ssonglius11@163.com>
This commit is contained in:
ssongliu 2024-09-27 14:33:32 +08:00 committed by GitHub
parent c101dfb694
commit 157fff8481
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 488 additions and 563 deletions

View file

@ -54,7 +54,6 @@ func (b *BaseApi) ListContainer(c *gin.Context) {
helper.SuccessWithData(c, list)
}
// @Tags Container
// @Summary Load containers status
// @Description 获取容器状态
@ -135,12 +134,11 @@ func (b *BaseApi) CreateCompose(c *gin.Context) {
return
}
log, err := containerService.CreateCompose(req)
if err != nil {
if err := containerService.CreateCompose(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, log)
helper.SuccessWithOutData(c)
}
// @Tags Container Compose

View file

@ -81,13 +81,12 @@ func (b *BaseApi) ImageBuild(c *gin.Context) {
return
}
log, err := imageService.ImageBuild(req)
if err != nil {
if err := imageService.ImageBuild(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, log)
helper.SuccessWithOutData(c)
}
// @Tags Container Image
@ -105,13 +104,12 @@ func (b *BaseApi) ImagePull(c *gin.Context) {
return
}
logPath, err := imageService.ImagePull(req)
if err != nil {
if err := imageService.ImagePull(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, logPath)
helper.SuccessWithOutData(c)
}
// @Tags Container Image
@ -129,13 +127,12 @@ func (b *BaseApi) ImagePush(c *gin.Context) {
return
}
logPath, err := imageService.ImagePush(req)
if err != nil {
if err := imageService.ImagePush(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, logPath)
helper.SuccessWithOutData(c)
}
// @Tags Container Image

View file

@ -55,6 +55,7 @@ type ResourceLimit struct {
}
type ContainerOperate struct {
TaskID string `json:"taskID"`
ContainerID string `json:"containerID"`
ForcePull bool `json:"forcePull"`
Name string `json:"name" validate:"required"`
@ -218,6 +219,7 @@ type ComposeContainer struct {
State string `json:"state"`
}
type ComposeCreate struct {
TaskID string `json:"taskID"`
Name string `json:"name"`
From string `json:"from" validate:"required,oneof=edit path template"`
File string `json:"file"`

View file

@ -15,6 +15,7 @@ type ImageLoad struct {
}
type ImageBuild struct {
TaskID string `json:"taskID"`
From string `json:"from" validate:"required"`
Name string `json:"name" validate:"required"`
Dockerfile string `json:"dockerfile" validate:"required"`
@ -22,6 +23,7 @@ type ImageBuild struct {
}
type ImagePull struct {
TaskID string `json:"taskID"`
RepoID uint `json:"repoID"`
ImageName string `json:"imageName" validate:"required"`
}
@ -32,6 +34,7 @@ type ImageTag struct {
}
type ImagePush struct {
TaskID string `json:"taskID"`
RepoID uint `json:"repoID" validate:"required"`
TagName string `json:"tagName" validate:"required"`
Name string `json:"name" validate:"required"`

View file

@ -25,9 +25,11 @@ import (
"github.com/pkg/errors"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
@ -57,7 +59,7 @@ type IContainerService interface {
PageVolume(req dto.SearchWithPage) (int64, interface{}, error)
ListVolume() ([]dto.Options, error)
PageCompose(req dto.SearchWithPage) (int64, interface{}, error)
CreateCompose(req dto.ComposeCreate) (string, error)
CreateCompose(req dto.ComposeCreate) error
ComposeOperation(req dto.ComposeOperation) error
ContainerCreate(req dto.ContainerOperate) error
ContainerUpdate(req dto.ContainerOperate) error
@ -412,39 +414,64 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate) error {
return buserr.New(constant.ErrContainerName)
}
if !checkImageExist(client, req.Image) || req.ForcePull {
if err := pullImages(ctx, client, req.Image); err != nil {
if !req.ForcePull {
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1)
if err != nil {
global.LOG.Errorf("new task for create container failed, err: %v", err)
return err
}
go func() {
taskItem.AddSubTask(i18n.GetWithName("ContainerImagePull", req.Image), func(t *task.Task) error {
if !checkImageExist(client, req.Image) || req.ForcePull {
if err := pullImages(ctx, client, req.Image); err != nil {
if !req.ForcePull {
return err
}
}
}
return nil
}, nil)
taskItem.AddSubTask(i18n.GetMsgByKey("ContainerImageCheck"), func(t *task.Task) error {
imageInfo, _, err := client.ImageInspectWithRaw(ctx, req.Image)
if err != nil {
return err
}
global.LOG.Errorf("force pull image %s failed, err: %v", req.Image, err)
if len(req.Entrypoint) == 0 {
req.Entrypoint = imageInfo.Config.Entrypoint
}
if len(req.Cmd) == 0 {
req.Cmd = imageInfo.Config.Cmd
}
return nil
}, nil)
taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", req.Name), func(t *task.Task) error {
config, hostConf, networkConf, err := loadConfigInfo(true, req, nil)
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerLoadInfo"), err)
if err != nil {
return err
}
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerCreate"), err)
if err != nil {
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
return err
}
err = client.ContainerStart(ctx, con.ID, container.StartOptions{})
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerStartCheck"), err)
if err != nil {
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
return fmt.Errorf("create successful but start failed, err: %v", err)
}
return nil
}, nil)
if err := taskItem.Execute(); err != nil {
global.LOG.Error(err.Error())
}
}
imageInfo, _, err := client.ImageInspectWithRaw(ctx, req.Image)
if err != nil {
return err
}
if len(req.Entrypoint) == 0 {
req.Entrypoint = imageInfo.Config.Entrypoint
}
if len(req.Cmd) == 0 {
req.Cmd = imageInfo.Config.Cmd
}
config, hostConf, networkConf, err := loadConfigInfo(true, req, nil)
if err != nil {
return err
}
global.LOG.Infof("new container info %s has been made, now start to create", req.Name)
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
if err != nil {
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
return err
}
global.LOG.Infof("create container %s successful! now check if the container is started and delete the container information if it is not.", req.Name)
if err := client.ContainerStart(ctx, con.ID, container.StartOptions{}); err != nil {
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
return fmt.Errorf("create successful but start failed, err: %v", err)
}
}()
return nil
}

View file

@ -4,7 +4,6 @@ import (
"bufio"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path"
@ -16,9 +15,11 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/compose"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
@ -149,48 +150,36 @@ func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
return true, nil
}
func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error) {
func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
if cmd.CheckIllegal(req.Name, req.Path) {
return "", buserr.New(constant.ErrCmdIllegal)
return buserr.New(constant.ErrCmdIllegal)
}
if err := u.loadPath(&req); err != nil {
return "", err
return err
}
global.LOG.Infof("docker-compose.yml %s create successful, start to docker-compose up", req.Name)
if req.From == "path" {
req.Name = path.Base(path.Dir(req.Path))
}
dockerLogDir := path.Join(global.CONF.System.TmpDir, "docker_logs")
if _, err := os.Stat(dockerLogDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dockerLogDir, os.ModePerm); err != nil {
return "", err
}
}
logItem := fmt.Sprintf("%s/compose_create_%s_%s.log", dockerLogDir, req.Name, time.Now().Format(constant.DateTimeSlimLayout))
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeCompose, req.TaskID, 1)
if err != nil {
return "", err
return fmt.Errorf("new task for image build failed, err: %v", err)
}
go func() {
defer file.Close()
cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d")
multiWriter := io.MultiWriter(os.Stdout, file)
cmd.Stdout = multiWriter
cmd.Stderr = multiWriter
if err := cmd.Run(); err != nil {
global.LOG.Errorf("docker-compose up %s failed, err: %v", req.Name, err)
_, _ = compose.Down(req.Path)
_, _ = file.WriteString("docker-compose up failed!")
return
}
global.LOG.Infof("docker-compose up %s successful!", req.Name)
_ = composeRepo.CreateRecord(&model.Compose{Name: req.Name})
_, _ = file.WriteString("docker-compose up successful!")
taskItem.AddSubTask(i18n.GetMsgByKey("ComposeCreate"), func(t *task.Task) error {
cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d")
out, err := cmd.CombinedOutput()
taskItem.Log(i18n.GetWithName("ComposeCreateRes", string(out)))
if err != nil {
_, _ = compose.Down(req.Path)
return err
}
_ = composeRepo.CreateRecord(&model.Compose{Name: req.Name})
return nil
}, nil)
_ = taskItem.Execute()
}()
return path.Base(logItem), nil
return nil
}
func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {

View file

@ -35,7 +35,6 @@ const (
uploadPath = "1panel/uploads"
downloadPath = "1panel/download"
logPath = "1panel/log"
dockerLogPath = "1panel/tmp/docker_logs"
taskPath = "1panel/task"
)
@ -252,8 +251,6 @@ func (u *DeviceService) Clean(req []dto.Clean) {
} else {
dropFileOrDir(path.Join(global.CONF.System.BaseDir, logPath, item.Name))
}
case "docker_log":
dropFileOrDir(path.Join(global.CONF.System.BaseDir, dockerLogPath, item.Name))
case "task_log":
pathItem := path.Join(global.CONF.System.BaseDir, taskPath, item.Name)
dropFileOrDir(path.Join(global.CONF.System.BaseDir, taskPath, item.Name))
@ -344,7 +341,6 @@ func (u *DeviceService) CleanForCronjob() (string, error) {
}
}
timeNow := time.Now().Format(constant.DateTimeLayout)
dropFileOrDirWithLog(path.Join(global.CONF.System.BaseDir, dockerLogPath), &logs, &size, &fileCount)
logs += fmt.Sprintf("\n%s: total clean: %s, total count: %d", timeNow, common.LoadSizeUnit2F(float64(size)), fileCount)
_ = settingRepo.Update("LastCleanTime", timeNow)
@ -501,15 +497,10 @@ func loadLogTree(fileOp fileUtils.FileOp) []dto.CleanTree {
}
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "system_log", Size: uint64(size), Children: list1, Type: "system_log", IsRecommend: true})
path2 := path.Join(global.CONF.System.BaseDir, dockerLogPath)
list2 := loadTreeWithAllFile(true, path2, "docker_log", path2, fileOp)
path2 := path.Join(global.CONF.System.BaseDir, taskPath)
list2 := loadTreeWithAllFile(false, path2, "task_log", path2, fileOp)
size2, _ := fileOp.GetDirSize(path2)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "docker_log", Size: uint64(size2), Children: list2, Type: "docker_log", IsRecommend: true})
path3 := path.Join(global.CONF.System.BaseDir, taskPath)
list3 := loadTreeWithAllFile(false, path3, "task_log", path3, fileOp)
size3, _ := fileOp.GetDirSize(path3)
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "task_log", Size: uint64(size3), Children: list3, Type: "task_log"})
treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "task_log", Size: uint64(size2), Children: list2, Type: "task_log"})
return treeData
}

View file

@ -475,8 +475,6 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
return nil, err
}
logFilePath = taskModel.LogFile
case constant.TypeImagePull, constant.TypeImagePush, constant.TypeImageBuild, constant.TypeComposeCreate:
logFilePath = path.Join(global.CONF.System.TmpDir, fmt.Sprintf("docker_logs/%s", req.Name))
}
lines, isEndOfFile, total, err := files.ReadFileByLine(logFilePath, req.Page, req.PageSize, req.Latest)

View file

@ -17,9 +17,11 @@ import (
"github.com/docker/docker/api/types/image"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/task"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/registry"
@ -33,11 +35,11 @@ type IImageService interface {
Page(req dto.SearchWithPage) (int64, interface{}, error)
List() ([]dto.Options, error)
ListAll() ([]dto.ImageInfo, error)
ImageBuild(req dto.ImageBuild) (string, error)
ImagePull(req dto.ImagePull) (string, error)
ImageBuild(req dto.ImageBuild) error
ImagePull(req dto.ImagePull) error
ImageLoad(req dto.ImageLoad) error
ImageSave(req dto.ImageSave) error
ImagePush(req dto.ImagePush) (string, error)
ImagePush(req dto.ImagePush) error
ImageRemove(req dto.BatchDelete) error
ImageTag(req dto.ImageTag) error
}
@ -152,10 +154,10 @@ func (u *ImageService) List() ([]dto.Options, error) {
return backDatas, nil
}
func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
func (u *ImageService) ImageBuild(req dto.ImageBuild) error {
client, err := docker.NewDockerClient()
if err != nil {
return "", err
return err
}
defer client.Close()
fileName := "Dockerfile"
@ -163,14 +165,14 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
dir := fmt.Sprintf("%s/docker/build/%s", constant.DataDir, strings.ReplaceAll(req.Name, ":", "_"))
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
return "", err
return err
}
}
pathItem := fmt.Sprintf("%s/Dockerfile", dir)
file, err := os.OpenFile(pathItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return "", err
return err
}
defer file.Close()
write := bufio.NewWriter(file)
@ -183,7 +185,7 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
}
tar, err := archive.TarWithOptions(req.Dockerfile+"/", &archive.TarOptions{})
if err != nil {
return "", err
return err
}
opts := types.ImageBuildOptions{
@ -192,118 +194,89 @@ func (u *ImageService) ImageBuild(req dto.ImageBuild) (string, error) {
Remove: true,
Labels: stringsToMap(req.Tags),
}
dockerLogDir := path.Join(global.CONF.System.TmpDir, "docker_logs")
if _, err := os.Stat(dockerLogDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dockerLogDir, os.ModePerm); err != nil {
return "", err
}
}
logItem := fmt.Sprintf("%s/image_build_%s_%s.log", dockerLogDir, strings.ReplaceAll(req.Name, ":", "_"), time.Now().Format(constant.DateTimeSlimLayout))
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskBuild, task.TaskScopeImage, req.TaskID, 1)
if err != nil {
return "", err
return fmt.Errorf("new task for image build failed, err: %v", err)
}
go func() {
defer file.Close()
defer tar.Close()
res, err := client.ImageBuild(context.Background(), tar, opts)
if err != nil {
global.LOG.Errorf("build image %s failed, err: %v", req.Name, err)
_, _ = file.WriteString("image build failed!")
return
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
global.LOG.Errorf("build image %s failed, err: %v", req.Name, err)
_, _ = file.WriteString(fmt.Sprintf("build image %s failed, err: %v", req.Name, err))
_, _ = file.WriteString("image build failed!")
return
}
if strings.Contains(string(body), "errorDetail") || strings.Contains(string(body), "error:") {
global.LOG.Errorf("build image %s failed", req.Name)
_, _ = file.Write(body)
_, _ = file.WriteString("image build failed!")
return
}
global.LOG.Infof("build image %s successful!", req.Name)
_, _ = file.Write(body)
_, _ = file.WriteString("image build successful!")
go func() {
defer tar.Close()
taskItem.AddSubTask(i18n.GetMsgByKey("ImageBuild"), func(t *task.Task) error {
res, err := client.ImageBuild(context.Background(), tar, opts)
taskItem.LogWithStatus(i18n.GetMsgByKey("TaskBuild"), err)
if err != nil {
return err
}
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
if strings.Contains(string(body), "errorDetail") || strings.Contains(string(body), "error:") {
taskItem.LogWithStatus(i18n.GetMsgByKey("ImageBuildStdoutCheck"), fmt.Errorf("build image %s failed", req.Name))
return err
}
taskItem.LogSuccess(i18n.GetWithName("ImaegBuildRes", "\n"+string(body)))
return nil
}, nil)
_ = taskItem.Execute()
}()
return path.Base(logItem), nil
return nil
}
func (u *ImageService) ImagePull(req dto.ImagePull) (string, error) {
func (u *ImageService) ImagePull(req dto.ImagePull) error {
client, err := docker.NewDockerClient()
if err != nil {
return "", err
return err
}
defer client.Close()
dockerLogDir := path.Join(global.CONF.System.TmpDir, "docker_logs")
if _, err := os.Stat(dockerLogDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dockerLogDir, os.ModePerm); err != nil {
return "", err
}
}
imageItemName := strings.ReplaceAll(path.Base(req.ImageName), ":", "_")
logItem := fmt.Sprintf("%s/image_pull_%s_%s.log", dockerLogDir, imageItemName, time.Now().Format(constant.DateTimeSlimLayout))
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
taskItem, err := task.NewTaskWithOps(imageItemName, task.TaskPull, task.TaskScopeImage, req.TaskID, 1)
if err != nil {
return "", err
return fmt.Errorf("new task for image pull failed, err: %v", err)
}
options := image.PullOptions{}
if req.RepoID == 0 {
hasAuth, authStr := loadAuthInfo(req.ImageName)
if hasAuth {
options.RegistryAuth = authStr
}
go func() {
defer file.Close()
out, err := client.ImagePull(context.TODO(), req.ImageName, options)
go func() {
taskItem.AddSubTask(i18n.GetWithName("ImagePull", req.ImageName), func(t *task.Task) error {
options := image.PullOptions{}
imageName := req.ImageName
if req.RepoID == 0 {
hasAuth, authStr := loadAuthInfo(req.ImageName)
if hasAuth {
options.RegistryAuth = authStr
}
} else {
repo, err := imageRepoRepo.Get(commonRepo.WithByID(req.RepoID))
taskItem.LogWithStatus(i18n.GetMsgByKey("ImageRepoAuthFromDB"), err)
if err != nil {
return err
}
if repo.Auth {
authConfig := registry.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
}
imageName = repo.DownloadUrl + "/" + req.ImageName
}
out, err := client.ImagePull(context.TODO(), imageName, options)
taskItem.LogWithStatus(i18n.GetMsgByKey("TaskPull"), err)
if err != nil {
global.LOG.Errorf("image %s pull failed, err: %v", req.ImageName, err)
return
return err
}
defer out.Close()
global.LOG.Infof("pull image %s successful!", req.ImageName)
_, _ = io.Copy(file, out)
}()
return path.Base(logItem), nil
}
repo, err := imageRepoRepo.Get(commonRepo.WithByID(req.RepoID))
if err != nil {
return "", err
}
if repo.Auth {
authConfig := registry.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
go func() {
defer file.Close()
out, err := client.ImagePull(context.TODO(), image, options)
if err != nil {
_, _ = file.WriteString("image pull failed!")
global.LOG.Errorf("image %s pull failed, err: %v", image, err)
return
}
defer out.Close()
global.LOG.Infof("pull image %s successful!", req.ImageName)
_, _ = io.Copy(file, out)
_, _ = file.WriteString("image pull successful!")
body, _ := io.ReadAll(out)
taskItem.LogSuccess(i18n.GetWithName("ImaegPullRes", "\n"+string(body)))
return nil
}, nil)
_ = taskItem.Execute()
}()
return path.Base(logItem), nil
return nil
}
func (u *ImageService) ImageLoad(req dto.ImageLoad) error {
@ -368,61 +341,62 @@ func (u *ImageService) ImageTag(req dto.ImageTag) error {
return nil
}
func (u *ImageService) ImagePush(req dto.ImagePush) (string, error) {
func (u *ImageService) ImagePush(req dto.ImagePush) error {
client, err := docker.NewDockerClient()
if err != nil {
return "", err
return err
}
defer client.Close()
repo, err := imageRepoRepo.Get(commonRepo.WithByID(req.RepoID))
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskPush, task.TaskScopeImage, req.TaskID, 1)
if err != nil {
return "", err
}
options := image.PushOptions{All: true}
authConfig := registry.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.Name)
if newName != req.TagName {
if err := client.ImageTag(context.TODO(), req.TagName, newName); err != nil {
return "", err
}
return fmt.Errorf("new task for image push failed, err: %v", err)
}
dockerLogDir := global.CONF.System.TmpDir + "/docker_logs"
if _, err := os.Stat(dockerLogDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dockerLogDir, os.ModePerm); err != nil {
return "", err
}
}
imageItemName := strings.ReplaceAll(path.Base(req.Name), ":", "_")
logItem := fmt.Sprintf("%s/image_push_%s_%s.log", dockerLogDir, imageItemName, time.Now().Format(constant.DateTimeSlimLayout))
file, err := os.OpenFile(logItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return "", err
}
go func() {
defer file.Close()
out, err := client.ImagePush(context.TODO(), newName, options)
if err != nil {
global.LOG.Errorf("image %s push failed, err: %v", req.TagName, err)
_, _ = file.WriteString("image push failed!")
return
}
defer out.Close()
global.LOG.Infof("push image %s successful!", req.Name)
_, _ = io.Copy(file, out)
_, _ = file.WriteString("image push successful!")
options := image.PushOptions{All: true}
var repo model.ImageRepo
newName := ""
taskItem.AddSubTask(i18n.GetMsgByKey("ImagePush"), func(t *task.Task) error {
repo, err = imageRepoRepo.Get(commonRepo.WithByID(req.RepoID))
newName = fmt.Sprintf("%s/%s", repo.DownloadUrl, req.Name)
taskItem.LogWithStatus(i18n.GetMsgByKey("ImageRepoAuthFromDB"), err)
if err != nil {
return err
}
options = image.PushOptions{All: true}
authConfig := registry.AuthConfig{
Username: repo.Username,
Password: repo.Password,
}
encodedJSON, _ := json.Marshal(authConfig)
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
options.RegistryAuth = authStr
return nil
}, nil)
taskItem.AddSubTask(i18n.GetMsgByKey("ImageRenameTag"), func(t *task.Task) error {
taskItem.Log(i18n.GetWithName("ImageNewTag", newName))
if newName != req.TagName {
if err := client.ImageTag(context.TODO(), req.TagName, newName); err != nil {
return err
}
}
return nil
}, nil)
taskItem.AddSubTask(i18n.GetMsgByKey("TaskPush"), func(t *task.Task) error {
out, err := client.ImagePush(context.TODO(), newName, options)
if err != nil {
return err
}
defer out.Close()
body, _ := io.ReadAll(out)
taskItem.Log(i18n.GetWithName("ImaegPushRes", "\n"+string(body)))
return nil
}, nil)
_ = taskItem.Execute()
}()
return path.Base(logItem), nil
return nil
}
func (u *ImageService) ImageRemove(req dto.BatchDelete) error {

View file

@ -56,6 +56,8 @@ const (
TaskRollback = "TaskRollback"
TaskSync = "TaskSync"
TaskBuild = "TaskBuild"
TaskPull = "TaskPull"
TaskPush = "TaskPush"
)
const (
@ -65,6 +67,9 @@ const (
TaskScopeDatabase = "Database"
TaskScopeAppStore = "AppStore"
TaskScopeSnapshot = "Snapshot"
TaskScopeContainer = "Container"
TaskScopeCompose = "Compose"
TaskScopeImage = "Image"
TaskScopeRuntimeExtension = "RuntimeExtension"
)

View file

@ -10,11 +10,11 @@ ErrNameIsExist: "Name is already exist"
ErrDemoEnvironment: "Demo server, prohibit this operation!"
ErrCmdTimeout: "Command execution timed out"
ErrCmdIllegal: "The command contains illegal characters. Please modify and try again!"
ErrPortExist: '{{ .port }} port is already occupied by {{ .type }} [{{ .name }}]'
ErrPortExist: "{{ .port }} port is already occupied by {{ .type }} [{{ .name }}]"
TYPE_APP: "Application"
TYPE_RUNTIME: "Runtime environment"
TYPE_DOMAIN: "Domain name"
ErrTypePort: 'Port {{ .name }} format error'
ErrTypePort: "Port {{ .name }} format error"
Success: "Success"
Failed: "Failed"
SystemRestart: "System restart causes task interruption"
@ -27,16 +27,16 @@ ErrNotInstall: "App not installed"
ErrPortInOtherApp: "{{ .port }} port already in use by app {{ .apps }}"
ErrDbUserNotValid: "Stock database, username and password do not match"
ErrDockerComposeNotValid: "docker-compose file format error!"
ErrUpdateBuWebsite: 'The application was updated successfully, but the modification of the website configuration file failed, please check the configuration!'
Err1PanelNetworkFailed: 'Default container network creation failed! {{ .detail }}'
ErrFileParse: 'Application docker-compose file parsing failed!'
ErrInstallDirNotFound: 'installation directory does not exist'
AppStoreIsUpToDate: 'The app store is already up to date'
LocalAppVersionNull: 'The {{.name}} app is not synced to version! Could not add to application list'
LocalAppVersionErr: '{{.name}} failed to sync version {{.version}}! {{.err}}'
ErrFileNotFound: '{{.name}} file does not exist'
ErrFileParseApp: 'Failed to parse {{.name}} file {{.err}}'
ErrAppDirNull: 'version folder does not exist'
ErrUpdateBuWebsite: "The application was updated successfully, but the modification of the website configuration file failed, please check the configuration!"
Err1PanelNetworkFailed: "Default container network creation failed! {{ .detail }}"
ErrFileParse: "Application docker-compose file parsing failed!"
ErrInstallDirNotFound: "installation directory does not exist"
AppStoreIsUpToDate: "The app store is already up to date"
LocalAppVersionNull: "The {{.name}} app is not synced to version! Could not add to application list"
LocalAppVersionErr: "{{.name}} failed to sync version {{.version}}! {{.err}}"
ErrFileNotFound: "{{.name}} file does not exist"
ErrFileParseApp: "Failed to parse {{.name}} file {{.err}}"
ErrAppDirNull: "version folder does not exist"
LocalAppErr: "App {{.name}} sync failed! {{.err}}"
ErrContainerName: "ContainerName is already exist"
ErrAppSystemRestart: "1Panel restart causes the task to terminate"
@ -45,14 +45,14 @@ ErrHttpReqTimeOut: "Request timed out {{.err}}"
ErrHttpReqFailed: "Request failed {{.err}}"
ErrHttpReqNotFound: "The file does not exist"
ErrNoSuchHost: "Network connection failed"
ErrImagePullTimeOut: 'Image pull timeout'
ErrContainerNotFound: '{{ .name }} container does not exist'
ErrContainerMsg: '{{ .name }} container is abnormal, please check the log on the container page for details'
ErrAppBackup: '{{ .name }} application backup failed err {{.err}}'
ErrImagePull: '{{ .name }} image pull failed err {{.err}}'
ErrVersionTooLow: 'The current 1Panel version is too low to update the app store, please upgrade the version'
ErrAppNameExist: 'App name is already exist'
AppStoreIsSyncing: 'The App Store is syncing, please try again later'
ErrImagePullTimeOut: "Image pull timeout"
ErrContainerNotFound: "{{ .name }} container does not exist"
ErrContainerMsg: "{{ .name }} container is abnormal, please check the log on the container page for details"
ErrAppBackup: "{{ .name }} application backup failed err {{.err}}"
ErrImagePull: "{{ .name }} image pull failed err {{.err}}"
ErrVersionTooLow: "The current 1Panel version is too low to update the app store, please upgrade the version"
ErrAppNameExist: "App name is already exist"
AppStoreIsSyncing: "The App Store is syncing, please try again later"
ErrGetCompose: "Failed to obtain docker-compose.yml file! {{ .detail }}"
ErrAppWarn: "Abnormal status, please check the log"
ErrAppParamKey: "Parameter {{ .name }} field exception"
@ -89,12 +89,12 @@ ErrInvalidChar: "Illegal characters are prohibited"
#website
ErrDomainIsExist: "Domain is already exist"
ErrAliasIsExist: "Alias is already exist"
ErrAppDelete: 'Other Website use this App'
ErrGroupIsUsed: 'The group is in use and cannot be deleted'
ErrBackupMatch: 'the backup file does not match the current partial data of the website: {{ .detail}}'
ErrBackupExist: 'the backup file corresponds to a portion of the original data that does not exist: {{ .detail}}'
ErrPHPResource: 'The local runtime does not support switching'
ErrPathPermission: 'A folder with non-1000:1000 permissions was detected in the index directory, which may cause an Access denied error when accessing the website. Please click the save button above'
ErrAppDelete: "Other Website use this App"
ErrGroupIsUsed: "The group is in use and cannot be deleted"
ErrBackupMatch: "the backup file does not match the current partial data of the website: {{ .detail}}"
ErrBackupExist: "the backup file corresponds to a portion of the original data that does not exist: {{ .detail}}"
ErrPHPResource: "The local runtime does not support switching"
ErrPathPermission: "A folder with non-1000:1000 permissions was detected in the index directory, which may cause an Access denied error when accessing the website. Please click the save button above"
ErrDomainIsUsed: "Domain is already used by website {{ .name }}"
ErrDomainFormat: "{{ .name }} domain format error"
ErrDefaultAlias: "default is a reserved code name, please use another code name"
@ -105,21 +105,21 @@ ErrBuildDirNotFound: "Build directory does not exist"
ErrSSLCannotDelete: "The certificate {{ .name }} is being used by the website and cannot be removed"
ErrAccountCannotDelete: "The certificate associated with the account cannot be deleted"
ErrSSLApply: "The certificate continues to be signed successfully, but openresty reload fails, please check the configuration"
ErrEmailIsExist: 'Email is already exist'
ErrSSLKeyNotFound: 'The private key file does not exist'
ErrSSLCertificateNotFound: 'The certificate file does not exist'
ErrSSLKeyFormat: 'Private key file verification error'
ErrSSLCertificateFormat: 'Certificate file format error, please use pem format'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid or EabHmacKey cannot be empty'
ErrOpenrestyNotFound: 'Http mode requires Openresty to be installed first'
ApplySSLStart: 'Start applying for certificate, domain name [{{ .domain }}] application method [{{ .type }}] '
ErrEmailIsExist: "Email is already exist"
ErrSSLKeyNotFound: "The private key file does not exist"
ErrSSLCertificateNotFound: "The certificate file does not exist"
ErrSSLKeyFormat: "Private key file verification error"
ErrSSLCertificateFormat: "Certificate file format error, please use pem format"
ErrEabKidOrEabHmacKeyCannotBlank: "EabKid or EabHmacKey cannot be empty"
ErrOpenrestyNotFound: "Http mode requires Openresty to be installed first"
ApplySSLStart: "Start applying for certificate, domain name [{{ .domain }}] application method [{{ .type }}] "
dnsAccount: "DNS Automatic"
dnsManual: "DNS Manual"
http: "HTTP"
ApplySSLFailed: 'Application for [{{ .domain }}] certificate failed, {{.detail}} '
ApplySSLSuccess: 'Application for [{{ .domain }}] certificate successful! ! '
DNSAccountName: 'DNS account [{{ .name }}] manufacturer [{{.type}}]'
PushDirLog: 'Certificate pushed to directory [{{ .path }}] {{ .status }}'
ApplySSLFailed: "Application for [{{ .domain }}] certificate failed, {{.detail}} "
ApplySSLSuccess: "Application for [{{ .domain }}] certificate successful! ! "
DNSAccountName: "DNS account [{{ .name }}] manufacturer [{{.type}}]"
PushDirLog: "Certificate pushed to directory [{{ .path }}] {{ .status }}"
ErrDeleteCAWithSSL: "There is an issued certificate under the current organization and cannot be deleted"
ErrDeleteWithPanelSSL: "Panel SSL configuration uses this certificate and cannot be deleted"
ErrDefaultCA: "The default organization cannot be deleted"
@ -220,6 +220,9 @@ TaskUpgrade: "Upgrade"
TaskUpdate: "Update"
TaskRestart: "Restart"
TaskRollback: "Rollback"
TaskPull: "Pull"
TaskBuild: "Build"
TaskPush: "Push"
Website: "Website"
App: "App"
Runtime: "Runtime"
@ -237,9 +240,11 @@ ExecShell: "Execute {{ .name }} Script"
PullImage: "Pull Image"
Start: "Start"
Run: "Run"
Stop: 'Stop',
Image: 'Image',
AppLink: 'Associated Application'
Stop: "Stop"
Image: "Image"
Container: "Container"
Compose: "Compose"
AppLink: "Associated Application"
EnableSSL: "Enable HTTPS"
AppStore: "App Store"
TaskSync: "Sync"
@ -272,4 +277,27 @@ SnapCompressSize: "Snapshot file size {{ .name }}"
SnapUpload: "Upload snapshot file"
SnapLoadBackup: "Get backup account information"
SnapUploadTo: "Upload snapshot file to {{ .name }}"
SnapUploadRes: "Upload snapshot file to {{ .name }}"
SnapUploadRes: "Upload snapshot file to {{ .name }}"
# task - container
ContainerNewCliet: "Initialize Docker Client"
ContainerImagePull: "Pull container image {{ .name }}"
ContainerImageCheck: "Check if the image is pulled successfully"
ContainerLoadInfo: "Get basic information of the container"
ContainerCreate: "Create new container {{ .name }}"
ContainerCreateFailed: "Container creation failed, delete the failed container"
ContainerStartCheck: "Check if the container has started"
# task - image
ImageBuild: "Image build"
ImageBuildStdoutCheck: "Parse image output"
ImaegBuildRes: "Image build output: {{ .name }}"
ImagePull: "Pull image"
ImageRepoAuthFromDB: "Get repository authentication information from database"
ImaegPullRes: "Image pull output: {{ .name }}"
ImagePush: "Push image"
ImageRenameTag: "Rename image tag"
ImageNewTag: "New image tag {{ .name }}"
ImaegPushRes: "Image push output: {{ .name }}"
ComposeCreate: "Create compose"
ComposeCreateRes: "Compose creation output: {{ .name }}"

View file

@ -10,11 +10,11 @@ ErrNameIsExist: "名稱已存在"
ErrDemoEnvironment: "演示伺服器,禁止此操作!"
ErrCmdTimeout: "指令執行超時!"
ErrCmdIllegal: "執行命令中存在不合法字符,請修改後重試!"
ErrPortExist: '{{ .port }} 埠已被 {{ .type }} [{{ .name }}] 佔用'
ErrPortExist: "{{ .port }} 埠已被 {{ .type }} [{{ .name }}] 佔用"
TYPE_APP: "應用"
TYPE_RUNTIME: "運作環境"
TYPE_DOMAIN: "網域名稱"
ErrTypePort: '埠 {{ .name }} 格式錯誤'
ErrTypePort: "埠 {{ .name }} 格式錯誤"
Success: "成功"
Failed: "失敗"
SystemRestart: "系統重啟導致任務中斷"
@ -28,16 +28,16 @@ ErrNotInstall: "應用未安裝"
ErrPortInOtherApp: "{{ .port }} 端口已被應用 {{ .apps }} 佔用!"
ErrDbUserNotValid: "儲存資料庫,用戶名密碼不匹配!"
ErrDockerComposeNotValid: "docker-compose 文件格式錯誤"
ErrUpdateBuWebsite: '應用更新成功,但是網站配置文件修改失敗,請檢查配置!'
Err1PanelNetworkFailed: '默認容器網絡創建失敗!{{ .detail }}'
ErrFileParse: '應用 docker-compose 文件解析失敗!'
ErrInstallDirNotFound: '安裝目錄不存在'
AppStoreIsUpToDate: '應用商店已經是最新版本'
LocalAppVersionNull: '{{.name}} 應用未同步到版本!無法添加到應用列表'
LocalAppVersionErr: '{{.name}} 同步版本 {{.version}} 失敗!{{.err}}'
ErrFileNotFound: '{{.name}} 文件不存在'
ErrFileParseApp: '{{.name}} 文件解析失敗 {{.err}}'
ErrAppDirNull: '版本資料夾不存在'
ErrUpdateBuWebsite: "應用更新成功,但是網站配置文件修改失敗,請檢查配置!"
Err1PanelNetworkFailed: "默認容器網絡創建失敗!{{ .detail }}"
ErrFileParse: "應用 docker-compose 文件解析失敗!"
ErrInstallDirNotFound: "安裝目錄不存在"
AppStoreIsUpToDate: "應用商店已經是最新版本"
LocalAppVersionNull: "{{.name}} 應用未同步到版本!無法添加到應用列表"
LocalAppVersionErr: "{{.name}} 同步版本 {{.version}} 失敗!{{.err}}"
ErrFileNotFound: "{{.name}} 文件不存在"
ErrFileParseApp: "{{.name}} 文件解析失敗 {{.err}}"
ErrAppDirNull: "版本資料夾不存在"
LocalAppErr: "應用 {{.name}} 同步失敗!{{.err}}"
ErrContainerName: "容器名稱已存在"
ErrAppSystemRestart: "1Panel 重啟導致任務中斷"
@ -47,13 +47,13 @@ ErrHttpReqFailed: "請求失敗 {{.err}}"
ErrHttpReqNotFound: "文件不存在"
ErrNoSuchHost: "網路連接失敗"
ErrImagePullTimeOut: "鏡像拉取超時"
ErrContainerNotFound: '{{ .name }} 容器不存在'
ErrContainerMsg: '{{ .name }} 容器異常,具體請在容器頁面查看日誌'
ErrAppBackup: '{{ .name }} 應用備份失敗 err {{.err}}'
ErrImagePull: '{{ .name }} 鏡像拉取失敗 err {{.err}}'
ErrVersionTooLow: '當前 1Panel 版本過低,無法更新應用商店,請升級版本之後操作'
ErrAppNameExist: '應用名稱已存在'
AppStoreIsSyncing: '應用程式商店正在同步中,請稍後再試'
ErrContainerNotFound: "{{ .name }} 容器不存在"
ErrContainerMsg: "{{ .name }} 容器異常,具體請在容器頁面查看日誌"
ErrAppBackup: "{{ .name }} 應用備份失敗 err {{.err}}"
ErrImagePull: "{{ .name }} 鏡像拉取失敗 err {{.err}}"
ErrVersionTooLow: "當前 1Panel 版本過低,無法更新應用商店,請升級版本之後操作"
ErrAppNameExist: "應用名稱已存在"
AppStoreIsSyncing: "應用程式商店正在同步中,請稍後再試"
ErrGetCompose: "docker-compose.yml 檔案取得失敗!{{ .detail }}"
ErrAppWarn: "狀態異常,請查看日誌"
ErrAppParamKey: "參數 {{ .name }} 欄位異常"
@ -89,12 +89,12 @@ ErrFavoriteExist: "已收藏此路徑"
#website
ErrDomainIsExist: "域名已存在"
ErrAliasIsExist: "代號已存在"
ErrAppDelete: '其他網站使用此應用,無法刪除'
ErrGroupIsUsed: '分組正在使用中,無法刪除'
ErrBackupMatch: '該備份文件與當前網站部分數據不匹配: {{ .detail}}'
ErrBackupExist: '該備份文件對應部分原數據不存在: {{ .detail}}'
ErrPHPResource: '本地運行環境不支持切換!'
ErrPathPermission: 'index 目錄下偵測到非 1000:1000 權限資料夾,可能導致網站存取 Access denied 錯誤,請點擊上方儲存按鈕'
ErrAppDelete: "其他網站使用此應用,無法刪除"
ErrGroupIsUsed: "分組正在使用中,無法刪除"
ErrBackupMatch: "該備份文件與當前網站部分數據不匹配: {{ .detail}}"
ErrBackupExist: "該備份文件對應部分原數據不存在: {{ .detail}}"
ErrPHPResource: "本地運行環境不支持切換!"
ErrPathPermission: "index 目錄下偵測到非 1000:1000 權限資料夾,可能導致網站存取 Access denied 錯誤,請點擊上方儲存按鈕"
ErrDomainIsUsed: "域名已被網站【{{ .name }}】使用"
ErrDomainFormat: "{{ .name }} 域名格式不正確"
ErrDefaultAlias: "default 為保留代號,請使用其他代號"
@ -105,21 +105,21 @@ ErrBuildDirNotFound: "編譯目錄不存在"
ErrSSLCannotDelete: "{{ .name }} 證書正在被網站使用,無法刪除"
ErrAccountCannotDelete: "帳號關聯證書,無法刪除"
ErrSSLApply: "證書續簽成功openresty reload失敗請檢查配置"
ErrEmailIsExist: '郵箱已存在'
ErrSSLKeyNotFound: '私鑰文件不存在'
ErrSSLCertificateNotFound: '證書文件不存在'
ErrSSLKeyFormat: '私鑰文件校驗錯誤'
ErrSSLCertificateFormat: '證書文件格式錯誤,請使用 pem 格式'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 或 EabHmacKey 不能為空'
ErrOpenrestyNotFound: 'Http 模式需要先安裝 Openresty'
ApplySSLStart: '開始申請憑證,網域 [{{ .domain }}] 申請方式 [{{ .type }}] '
ErrEmailIsExist: "郵箱已存在"
ErrSSLKeyNotFound: "私鑰文件不存在"
ErrSSLCertificateNotFound: "證書文件不存在"
ErrSSLKeyFormat: "私鑰文件校驗錯誤"
ErrSSLCertificateFormat: "證書文件格式錯誤,請使用 pem 格式"
ErrEabKidOrEabHmacKeyCannotBlank: "EabKid 或 EabHmacKey 不能為空"
ErrOpenrestyNotFound: "Http 模式需要先安裝 Openresty"
ApplySSLStart: "開始申請憑證,網域 [{{ .domain }}] 申請方式 [{{ .type }}] "
dnsAccount: "DNS 自動"
dnsManual: "DNS 手排"
http: "HTTP"
ApplySSLFailed: '申請 [{{ .domain }}] 憑證失敗, {{.detail}} '
ApplySSLSuccess: '申請 [{{ .domain }}] 憑證成功! '
DNSAccountName: 'DNS 帳號 [{{ .name }}] 廠商 [{{.type}}]'
PushDirLog: '憑證推送到目錄 [{{ .path }}] {{ .status }}'
ApplySSLFailed: "申請 [{{ .domain }}] 憑證失敗, {{.detail}} "
ApplySSLSuccess: "申請 [{{ .domain }}] 憑證成功! "
DNSAccountName: "DNS 帳號 [{{ .name }}] 廠商 [{{.type}}]"
PushDirLog: "憑證推送到目錄 [{{ .path }}] {{ .status }}"
ErrDeleteCAWithSSL: "目前機構下存在已簽發證書,無法刪除"
ErrDeleteWithPanelSSL: "面板 SSL 配置使用此證書,無法刪除"
ErrDefaultCA: "默認機構不能刪除"
@ -222,6 +222,9 @@ TaskUpgrade: "升級"
TaskUpdate: "更新"
TaskRestart: "重啟"
TaskRollback: "回滚"
TaskPull: "拉取"
TaskBuild: "建構"
TaskPush: "推送"
Website: "網站"
App: "應用"
Runtime: "運行環境"
@ -239,9 +242,11 @@ ExecShell: "執行 {{ .name }} 腳本"
PullImage: "拉取鏡像"
Start: "開始"
Run: "啟動"
Stop: '停止',
Image: '鏡像',
AppLink: '關聯應用'
Stop: "停止"
Image: "鏡像"
Container: "容器"
Compose: "編排"
AppLink: "關聯應用"
EnableSSL: "開啟 HTTPS"
AppStore: "應用商店"
TaskSync: "同步"
@ -275,4 +280,27 @@ SnapCompressSize: "快照檔案大小 {{ .name }}"
SnapUpload: "上傳快照檔案"
SnapLoadBackup: "獲取備份帳號資訊"
SnapUploadTo: "上傳快照檔案到 {{ .name }}"
SnapUploadRes: "上傳快照檔案到 {{ .name }}"
SnapUploadRes: "上傳快照檔案到 {{ .name }}"
# task - container
ContainerNewCliet: "初始化 Docker 客戶端"
ContainerImagePull: "拉取容器鏡像 {{ .name }}"
ContainerImageCheck: "檢查鏡像是否正常拉取"
ContainerLoadInfo: "獲取容器基本信息"
ContainerCreate: "創建新容器 {{ .name }}"
ContainerCreateFailed: "容器創建失敗,刪除失敗容器"
ContainerStartCheck: "檢查容器是否已啟動"
# task - image
ImageBuild: "鏡像構建"
ImageBuildStdoutCheck: "解析鏡像輸出內容"
ImaegBuildRes: "鏡像構建輸出:{{ .name }}"
ImagePull: "拉取鏡像"
ImageRepoAuthFromDB: "從數據庫獲取倉庫認證信息"
ImaegPullRes: "鏡像拉取輸出:{{ .name }}"
ImagePush: "推送鏡像"
ImageRenameTag: "修改鏡像 Tag"
ImageNewTag: "新鏡像 Tag {{ .name }}"
ImaegPushRes: "鏡像推送輸出:{{ .name }}"
ComposeCreate: "創建編排"
ComposeCreateRes: "編排創建輸出:{{ .name }}"

View file

@ -10,11 +10,11 @@ ErrNameIsExist: "名称已存在"
ErrDemoEnvironment: "演示服务器,禁止此操作!"
ErrCmdTimeout: "命令执行超时!"
ErrCmdIllegal: "执行命令中存在不合法字符,请修改后重试!"
ErrPortExist: '{{ .port }} 端口已被 {{ .type }} [{{ .name }}] 占用'
ErrPortExist: "{{ .port }} 端口已被 {{ .type }} [{{ .name }}] 占用"
TYPE_APP: "应用"
TYPE_RUNTIME: "运行环境"
TYPE_DOMAIN: "域名"
ErrTypePort: '端口 {{ .name }} 格式错误'
ErrTypePort: "端口 {{ .name }} 格式错误"
Success: "成功"
Failed: "失败"
SystemRestart: "系统重启导致任务中断"
@ -27,16 +27,16 @@ ErrNotInstall: "应用未安装"
ErrPortInOtherApp: "{{ .port }} 端口已被应用 {{ .apps }} 占用!"
ErrDbUserNotValid: "存量数据库,用户名密码不匹配!"
ErrDockerComposeNotValid: "docker-compose 文件格式错误"
ErrUpdateBuWebsite: '应用更新成功,但是网站配置文件修改失败,请检查配置!'
Err1PanelNetworkFailed: '默认容器网络创建失败!{{ .detail }}'
ErrFileParse: '应用 docker-compose 文件解析失败!'
ErrInstallDirNotFound: '安装目录不存在'
AppStoreIsUpToDate: '应用商店已经是最新版本'
LocalAppVersionNull: '{{.name}} 应用未同步到版本!无法添加到应用列表'
LocalAppVersionErr: '{{.name}} 同步版本 {{.version}} 失败!{{.err}}'
ErrFileNotFound: '{{.name}} 文件不存在'
ErrFileParseApp: '{{.name}} 文件解析失败 {{.err}}'
ErrAppDirNull: '版本文件夹不存在'
ErrUpdateBuWebsite: "应用更新成功,但是网站配置文件修改失败,请检查配置!"
Err1PanelNetworkFailed: "默认容器网络创建失败!{{ .detail }}"
ErrFileParse: "应用 docker-compose 文件解析失败!"
ErrInstallDirNotFound: "安装目录不存在"
AppStoreIsUpToDate: "应用商店已经是最新版本"
LocalAppVersionNull: "{{.name}} 应用未同步到版本!无法添加到应用列表"
LocalAppVersionErr: "{{.name}} 同步版本 {{.version}} 失败!{{.err}}"
ErrFileNotFound: "{{.name}} 文件不存在"
ErrFileParseApp: "{{.name}} 文件解析失败 {{.err}}"
ErrAppDirNull: "版本文件夹不存在"
LocalAppErr: "应用 {{.name}} 同步失败!{{.err}}"
ErrContainerName: "容器名称已存在"
ErrAppSystemRestart: "1Panel 重启导致任务终止"
@ -45,14 +45,14 @@ ErrHttpReqTimeOut: "请求超时 {{.err}}"
ErrHttpReqFailed: "请求失败 {{.err}}"
ErrHttpReqNotFound: "文件不存在"
ErrNoSuchHost: "网络连接失败"
ErrImagePullTimeOut: '镜像拉取超时'
ErrContainerNotFound: '{{ .name }} 容器不存在'
ErrContainerMsg: '{{ .name }} 容器异常,具体请在容器页面查看日志'
ErrAppBackup: '{{ .name }} 应用备份失败 err {{.err}}'
ErrImagePull: '镜像拉取失败 {{.err}}'
ErrVersionTooLow: '当前 1Panel 版本过低,无法更新应用商店,请升级版本之后操作'
ErrAppNameExist: '应用名称已存在'
AppStoreIsSyncing: '应用商店正在同步中,请稍后再试'
ErrImagePullTimeOut: "镜像拉取超时"
ErrContainerNotFound: "{{ .name }} 容器不存在"
ErrContainerMsg: "{{ .name }} 容器异常,具体请在容器页面查看日志"
ErrAppBackup: "{{ .name }} 应用备份失败 err {{.err}}"
ErrImagePull: "镜像拉取失败 {{.err}}"
ErrVersionTooLow: "当前 1Panel 版本过低,无法更新应用商店,请升级版本之后操作"
ErrAppNameExist: "应用名称已存在"
AppStoreIsSyncing: "应用商店正在同步中,请稍后再试"
ErrGetCompose: "docker-compose.yml 文件获取失败!{{ .detail }}"
ErrAppWarn: "状态异常,请查看日志"
ErrAppParamKey: "参数 {{ .name }} 字段异常"
@ -89,12 +89,12 @@ ErrInvalidChar: "禁止使用非法字符"
#website
ErrDomainIsExist: "域名已存在"
ErrAliasIsExist: "代号已存在"
ErrAppDelete: '其他网站使用此应用,无法删除'
ErrGroupIsUsed: '分组正在使用中,无法删除'
ErrBackupMatch: '该备份文件与当前网站部分数据不匹配 {{ .detail}}'
ErrBackupExist: '该备份文件对应部分源数据不存在 {{ .detail}}'
ErrPHPResource: '本地运行环境不支持切换!'
ErrPathPermission: 'index 目录下检测到非 1000:1000 权限文件夹,可能导致网站访问 Access denied 错误,请点击上方保存按钮'
ErrAppDelete: "其他网站使用此应用,无法删除"
ErrGroupIsUsed: "分组正在使用中,无法删除"
ErrBackupMatch: "该备份文件与当前网站部分数据不匹配 {{ .detail}}"
ErrBackupExist: "该备份文件对应部分源数据不存在 {{ .detail}}"
ErrPHPResource: "本地运行环境不支持切换!"
ErrPathPermission: "index 目录下检测到非 1000:1000 权限文件夹,可能导致网站访问 Access denied 错误,请点击上方保存按钮"
ErrDomainIsUsed: "域名已被网站【{{ .name }}】使用"
ErrDomainFormat: "{{ .name }} 域名格式不正确"
ErrDefaultAlias: "default 为保留代号,请使用其他代号"
@ -105,21 +105,21 @@ ErrBuildDirNotFound: "构建目录不存在"
ErrSSLCannotDelete: "{{ .name }} 证书正在被网站使用,无法删除"
ErrAccountCannotDelete: "账号关联证书,无法删除"
ErrSSLApply: "证书续签成功openresty reload失败请检查配置"
ErrEmailIsExist: '邮箱已存在'
ErrSSLKeyNotFound: '私钥文件不存在'
ErrSSLCertificateNotFound: '证书文件不存在'
ErrSSLKeyFormat: '私钥文件校验失败'
ErrSSLCertificateFormat: '证书文件格式错误,请使用 pem 格式'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 或 EabHmacKey 不能为空'
ErrOpenrestyNotFound: 'Http 模式需要首先安装 Openresty'
ApplySSLStart: '开始申请证书,域名 [{{ .domain }}] 申请方式 [{{ .type }}] '
ErrEmailIsExist: "邮箱已存在"
ErrSSLKeyNotFound: "私钥文件不存在"
ErrSSLCertificateNotFound: "证书文件不存在"
ErrSSLKeyFormat: "私钥文件校验失败"
ErrSSLCertificateFormat: "证书文件格式错误,请使用 pem 格式"
ErrEabKidOrEabHmacKeyCannotBlank: "EabKid 或 EabHmacKey 不能为空"
ErrOpenrestyNotFound: "Http 模式需要首先安装 Openresty"
ApplySSLStart: "开始申请证书,域名 [{{ .domain }}] 申请方式 [{{ .type }}] "
dnsAccount: "DNS 自动"
dnsManual: "DNS 手动"
http: "HTTP"
ApplySSLFailed: '申请 [{{ .domain }}] 证书失败, {{.detail}} '
ApplySSLSuccess: '申请 [{{ .domain }}] 证书成功!!'
DNSAccountName: 'DNS 账号 [{{ .name }}] 厂商 [{{.type}}]'
PushDirLog: '证书推送到目录 [{{ .path }}] {{ .status }}'
ApplySSLFailed: "申请 [{{ .domain }}] 证书失败, {{.detail}} "
ApplySSLSuccess: "申请 [{{ .domain }}] 证书成功!!"
DNSAccountName: "DNS 账号 [{{ .name }}] 厂商 [{{.type}}]"
PushDirLog: "证书推送到目录 [{{ .path }}] {{ .status }}"
ErrDeleteCAWithSSL: "当前机构下存在已签发证书,无法删除"
ErrDeleteWithPanelSSL: "面板 SSL 配置使用此证书,无法删除"
ErrDefaultCA: "默认机构不能删除"
@ -225,6 +225,9 @@ TaskRestart: "重启"
TaskBackup: "备份"
TaskRecover: "恢复"
TaskRollback: "回滚"
TaskPull: "拉取"
TaskBuild: "构建"
TaskPush: "推送"
Website: "网站"
App: "应用"
Runtime: "运行环境"
@ -244,6 +247,8 @@ Start: "开始"
Run: "启动"
Stop: "停止"
Image: "镜像"
Compose: "编排"
Container: "容器"
AppLink: "关联应用"
EnableSSL: "开启 HTTPS"
AppStore: "应用商店"
@ -303,3 +308,25 @@ RecoverDBData: "恢复数据库数据"
RecoverBackups: "恢复本地备份目录"
RecoverPanelData: "恢复数据目录"
# task - container
ContainerNewCliet: "初始化 Docker Client"
ContainerImagePull: "拉取容器镜像 {{ .name }}"
ContainerImageCheck: "检查镜像是否正常拉取"
ContainerLoadInfo: "获取容器基本信息"
ContainerCreate: "创建新容器 {{ .name }}"
ContainerCreateFailed: "容器创建失败,删除失败容器"
ContainerStartCheck: "检查容器是否已启动"
# task - image
ImageBuild: "镜像构建"
ImageBuildStdoutCheck: "解析镜像输出内容"
ImaegBuildRes: "镜像构建输出:{{ .name }}"
ImagePull: "拉取镜像"
ImageRepoAuthFromDB: "从数据库获取仓库认证信息"
ImaegPullRes: "镜像拉取输出:{{ .name }}"
ImagePush: "推送镜像"
ImageRenameTag: "修改镜像 Tag"
ImageNewTag: "新镜像 Tag {{ .name }}"
ImaegPushRes: "镜像推送输出:{{ .name }}"
ComposeCreate: "创建编排"
ComposeCreateRes: "编排创建输出:{{ .name }}"

View file

@ -143,12 +143,14 @@ export namespace Container {
isUsed: boolean;
}
export interface ImageBuild {
taskID: string;
from: string;
name: string;
dockerfile: string;
tags: Array<string>;
}
export interface ImagePull {
taskID: string;
repoID: number;
imageName: string;
}
@ -157,6 +159,7 @@ export namespace Container {
targetName: string;
}
export interface ImagePush {
taskID: string;
repoID: number;
tagName: string;
}
@ -259,6 +262,7 @@ export namespace Container {
state: string;
}
export interface ComposeCreate {
taskID: string;
name: string;
from: string;
file: string;

View file

@ -37,12 +37,7 @@
</el-form-item>
<el-form-item>
<div v-if="form.from === 'edit' || form.from === 'template'" class="w-full">
<el-radio-group v-model="mode" size="small">
<el-radio-button label="edit">{{ $t('commons.button.edit') }}</el-radio-button>
<el-radio-button label="log">{{ $t('commons.button.log') }}</el-radio-button>
</el-radio-group>
<CodemirrorPro
v-if="mode === 'edit'"
v-model="form.file"
placeholder="#Define or paste the content of your docker-compose file here"
mode="yaml"
@ -50,60 +45,44 @@
></CodemirrorPro>
</div>
</el-form-item>
<div class="w-full h-32">
<LogFile
ref="logRef"
v-model:is-reading="isReading"
:config="logConfig"
:default-button="false"
v-if="mode === 'log' && showLog"
:height-diff="370"
/>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" :disabled="isStartReading || isReading" @click="onSubmit(formRef)">
<el-button type="primary" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</DrawerPro>
<TaskLog ref="taskLogRef" width="70%" />
</template>
<script lang="ts" setup>
import { nextTick, onBeforeUnmount, reactive, ref } from 'vue';
import { reactive, ref } from 'vue';
import FileList from '@/components/file-list/index.vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm, ElMessageBox } from 'element-plus';
import { ElForm, ElMessage, ElMessageBox } from 'element-plus';
import { loadBaseDir } from '@/api/modules/setting';
import { MsgError } from '@/utils/message';
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
import TaskLog from '@/components/task-log/index.vue';
import { listComposeTemplate, testCompose, upCompose } from '@/api/modules/container';
import { newUUID } from '@/utils/util';
const showLog = ref(false);
const loading = ref();
const mode = ref('edit');
const oldFrom = ref('edit');
const drawerVisible = ref(false);
const templateOptions = ref();
const baseDir = ref();
const composeFile = ref();
let timer: NodeJS.Timer | null = null;
const logRef = ref();
const isStartReading = ref(false);
const isReading = ref();
const logConfig = reactive({
type: 'compose-create',
name: '',
});
const taskLogRef = ref();
const form = reactive({
taskID: '',
name: '',
from: 'edit',
path: '',
@ -122,7 +101,6 @@ const loadTemplates = async () => {
};
const acceptParams = (): void => {
mode.value = 'edit';
drawerVisible.value = true;
form.name = '';
form.from = 'edit';
@ -131,7 +109,6 @@ const acceptParams = (): void => {
form.template = null;
loadTemplates();
loadPath();
isStartReading.value = false;
};
const emit = defineEmits<{ (e: 'search'): void }>();
@ -171,8 +148,6 @@ const changeFrom = () => {
const handleClose = () => {
emit('search');
clearInterval(Number(timer));
timer = null;
drawerVisible.value = false;
};
@ -196,9 +171,6 @@ const onEdit = (item: string) => {
if (item === 'form') {
changeFrom();
}
if (!isReading.value && isStartReading.value) {
isStartReading.value = false;
}
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
@ -213,16 +185,10 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
.then(async (res) => {
loading.value = false;
if (res.data) {
mode.value = 'log';
await upCompose(form)
.then((res) => {
logConfig.name = res.data;
loadLogs();
isStartReading.value = true;
})
.catch(() => {
loading.value = false;
});
form.taskID = newUUID();
await upCompose(form);
openTaskLog(form.taskID);
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
}
})
.catch(() => {
@ -230,26 +196,14 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
});
});
};
const loadLogs = () => {
showLog.value = false;
nextTick(() => {
showLog.value = true;
nextTick(() => {
logRef.value.changeTail(true);
});
});
const openTaskLog = (taskID: string) => {
taskLogRef.value.openWithTaskID(taskID);
};
const loadDir = async (path: string) => {
form.path = path;
};
onBeforeUnmount(() => {
clearInterval(Number(timer));
timer = null;
});
defineExpose({
acceptParams,
});

View file

@ -5,7 +5,7 @@
<el-input :placeholder="$t('container.imageNameHelper')" v-model.trim="form.name" clearable />
</el-form-item>
<el-form-item label="Dockerfile" prop="from">
<el-radio-group @change="onEdit()" v-model="form.from">
<el-radio-group v-model="form.from">
<el-radio value="edit">{{ $t('commons.button.edit') }}</el-radio>
<el-radio value="path">{{ $t('container.pathSelect') }}</el-radio>
</el-radio-group>
@ -18,62 +18,44 @@
></CodemirrorPro>
</el-form-item>
<el-form-item v-else :rules="Rules.requiredSelect" prop="dockerfile">
<el-input @change="onEdit()" clearable v-model="form.dockerfile">
<el-input clearable v-model="form.dockerfile">
<template #prepend>
<FileList @choose="loadBuildDir"></FileList>
</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('container.tag')">
<el-input
@change="onEdit()"
:placeholder="$t('container.tagHelper')"
type="textarea"
:rows="3"
v-model="form.tagStr"
/>
<el-input :placeholder="$t('container.tagHelper')" type="textarea" :rows="3" v-model="form.tagStr" />
</el-form-item>
</el-form>
<LogFile
ref="logRef"
:config="logConfig"
:default-button="false"
v-model:is-reading="isReading"
v-if="logVisible"
:height-diff="370"
/>
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button :disabled="isStartReading || isReading" type="primary" @click="onSubmit(formRef)">
<el-button type="primary" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</DrawerPro>
<TaskLog ref="taskLogRef" width="70%" />
</template>
<script lang="ts" setup>
import FileList from '@/components/file-list/index.vue';
import { nextTick, reactive, ref } from 'vue';
import { reactive, ref } from 'vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm, ElMessage } from 'element-plus';
import { imageBuild } from '@/api/modules/container';
import TaskLog from '@/components/task-log/index.vue';
import { newUUID } from '@/utils/util';
const logVisible = ref<boolean>(false);
const drawerVisible = ref(false);
const logRef = ref();
const isStartReading = ref(false);
const isReading = ref(false);
const taskLogRef = ref();
const logConfig = reactive({
type: 'image-build',
name: '',
});
const form = reactive({
taskID: '',
from: 'path',
dockerfile: '',
name: '',
@ -87,13 +69,11 @@ const rules = reactive({
dockerfile: [Rules.requiredInput],
});
const acceptParams = async () => {
logVisible.value = false;
drawerVisible.value = true;
form.from = 'path';
form.dockerfile = '';
form.tagStr = '';
form.name = '';
isStartReading.value = false;
};
const emit = defineEmits<{ (e: 'search'): void }>();
@ -105,11 +85,6 @@ const handleClose = () => {
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
const onEdit = () => {
if (!isReading.value && isStartReading.value) {
isStartReading.value = false;
}
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
@ -117,22 +92,14 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
if (form.tagStr !== '') {
form.tags = form.tagStr.split('\n');
}
const res = await imageBuild(form);
isStartReading.value = true;
logConfig.name = res.data;
loadLogs();
form.taskID = newUUID();
await imageBuild(form);
openTaskLog(form.taskID);
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
});
};
const loadLogs = () => {
logVisible.value = false;
nextTick(() => {
logVisible.value = true;
nextTick(() => {
logRef.value.changeTail(true);
});
});
const openTaskLog = (taskID: string) => {
taskLogRef.value.openWithTaskID(taskID);
};
const loadBuildDir = async (path: string) => {

View file

@ -2,7 +2,7 @@
<DrawerPro v-model="drawerVisible" :header="$t('container.imagePull')" :back="onCloseLog" size="large">
<el-form ref="formRef" label-position="top" :model="form">
<el-form-item :label="$t('container.from')">
<el-checkbox @change="onEdit()" v-model="form.fromRepo">
<el-checkbox v-model="form.fromRepo">
{{ $t('container.imageRepo') }}
</el-checkbox>
</el-form-item>
@ -12,62 +12,49 @@
:rules="Rules.requiredSelect"
prop="repoID"
>
<el-select @change="onEdit()" clearable style="width: 100%" filterable v-model="form.repoID">
<el-select clearable style="width: 100%" filterable v-model="form.repoID">
<el-option v-for="item in repos" :key="item.id" :value="item.id" :label="item.name" />
</el-select>
</el-form-item>
<el-form-item :label="$t('container.imageName')" :rules="Rules.imageName" prop="imageName">
<el-input @change="onEdit()" v-model.trim="form.imageName">
<el-input v-model.trim="form.imageName">
<template v-if="form.fromRepo" #prepend>{{ loadDetailInfo(form.repoID) }}/</template>
</el-input>
</el-form-item>
</el-form>
<LogFile
ref="logRef"
:config="logConfig"
:default-button="false"
v-model:is-reading="isReading"
v-if="showLog"
:height-diff="420"
/>
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="isStartReading || isReading" type="primary" @click="onSubmit(formRef)">
<el-button type="primary" @click="onSubmit(formRef)">
{{ $t('container.pull') }}
</el-button>
</span>
</template>
</DrawerPro>
<TaskLog ref="taskLogRef" width="70%" />
</template>
<script lang="ts" setup>
import { nextTick, reactive, ref } from 'vue';
import { reactive, ref } from 'vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { imagePull } from '@/api/modules/container';
import { Container } from '@/api/interface/container';
import TaskLog from '@/components/task-log/index.vue';
import { MsgSuccess } from '@/utils/message';
import { newUUID } from '@/utils/util';
const drawerVisible = ref(false);
const form = reactive({
taskID: '',
fromRepo: true,
repoID: null as number,
imageName: '',
});
const logConfig = reactive({
type: 'image-pull',
name: '',
});
const showLog = ref(false);
const logRef = ref();
const logVisible = ref(false);
const logInfo = ref();
const isStartReading = ref(false);
const isReading = ref(false);
const taskLogRef = ref();
interface DialogProps {
repos: Array<Container.RepoOptions>;
@ -75,7 +62,6 @@ interface DialogProps {
const repos = ref();
const acceptParams = async (params: DialogProps): Promise<void> => {
logVisible.value = false;
drawerVisible.value = true;
form.fromRepo = true;
form.imageName = '';
@ -87,20 +73,12 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
break;
}
}
isStartReading.value = false;
logInfo.value = '';
showLog.value = false;
};
const emit = defineEmits<{ (e: 'search'): void }>();
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
const onEdit = () => {
if (!isReading.value && isStartReading.value) {
isStartReading.value = false;
}
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
@ -108,23 +86,14 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
if (!form.fromRepo) {
form.repoID = 0;
}
const res = await imagePull(form);
logVisible.value = true;
isStartReading.value = true;
logConfig.name = res.data;
search();
form.taskID = newUUID();
await imagePull(form);
openTaskLog(form.taskID);
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
});
};
const search = () => {
showLog.value = false;
nextTick(() => {
showLog.value = true;
nextTick(() => {
logRef.value.changeTail(true);
});
});
const openTaskLog = (taskID: string) => {
taskLogRef.value.openWithTaskID(taskID);
};
const onCloseLog = async () => {

View file

@ -2,73 +2,57 @@
<DrawerPro v-model="drawerVisible" :header="$t('container.imagePush')" :back="onCloseLog" size="large">
<el-form ref="formRef" label-position="top" :model="form" label-width="80px">
<el-form-item :label="$t('container.tag')" :rules="Rules.requiredSelect" prop="tagName">
<el-select @change="onEdit(true)" filterable v-model="form.tagName">
<el-select filterable v-model="form.tagName">
<el-option v-for="item in form.tags" :key="item" :value="item" :label="item" />
</el-select>
</el-form-item>
<el-form-item :label="$t('container.repoName')" :rules="Rules.requiredSelect" prop="repoID">
<el-select @change="onEdit()" clearable style="width: 100%" filterable v-model="form.repoID">
<el-select clearable style="width: 100%" filterable v-model="form.repoID">
<el-option v-for="item in dialogData.repos" :key="item.id" :value="item.id" :label="item.name" />
</el-select>
</el-form-item>
<el-form-item :label="$t('container.image')" :rules="Rules.imageName" prop="name">
<el-input @change="onEdit()" v-model.trim="form.name">
<el-input v-model.trim="form.name">
<template #prepend>{{ loadDetailInfo(form.repoID) }}/</template>
</el-input>
</el-form-item>
</el-form>
<LogFile
ref="logRef"
:config="logConfig"
:default-button="false"
v-model:is-reading="isReading"
v-if="logVisible"
:height-diff="420"
v-model:loading="loading"
/>
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="isStartReading || isReading" type="primary" @click="onSubmit(formRef)">
<el-button type="primary" @click="onSubmit(formRef)">
{{ $t('container.push') }}
</el-button>
</span>
</template>
</DrawerPro>
<TaskLog ref="taskLogRef" width="70%" />
</template>
<script lang="ts" setup>
import { nextTick, reactive, ref } from 'vue';
import { reactive, ref } from 'vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { imagePush } from '@/api/modules/container';
import { Container } from '@/api/interface/container';
import { MsgSuccess } from '@/utils/message';
import TaskLog from '@/components/task-log/index.vue';
import { newUUID } from '@/utils/util';
const drawerVisible = ref(false);
const taskLogRef = ref();
const form = reactive({
taskID: '',
tags: [] as Array<string>,
tagName: '',
repoID: 1,
name: '',
});
const logVisible = ref(false);
const loading = ref(false);
const isStartReading = ref(false);
const isReading = ref(false);
const logRef = ref();
const logConfig = reactive({
type: 'image-push',
name: '',
});
interface DialogProps {
repos: Array<Container.RepoOptions>;
tags: Array<string>;
@ -79,50 +63,30 @@ const dialogData = ref<DialogProps>({
});
const acceptParams = async (params: DialogProps): Promise<void> => {
logVisible.value = false;
loading.value = false;
drawerVisible.value = true;
form.tags = params.tags;
form.repoID = 1;
form.tagName = form.tags.length !== 0 ? form.tags[0] : '';
form.name = form.tags.length !== 0 ? form.tags[0] : '';
dialogData.value.repos = params.repos;
isStartReading.value = false;
};
const emit = defineEmits<{ (e: 'search'): void }>();
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
const onEdit = (isName?: boolean) => {
if (!isReading.value && isStartReading.value) {
isStartReading.value = false;
}
if (isName) {
form.name = form.tagName;
}
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
const res = await imagePush(form);
logVisible.value = true;
isStartReading.value = true;
logConfig.name = res.data;
loadLogs();
form.taskID = newUUID();
await imagePush(form);
openTaskLog(form.taskID);
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
});
};
const loadLogs = () => {
logVisible.value = false;
nextTick(() => {
logVisible.value = true;
nextTick(() => {
logRef.value.changeTail(true);
});
});
const openTaskLog = (taskID: string) => {
taskLogRef.value.openWithTaskID(taskID);
};
const onCloseLog = async () => {