diff --git a/agent/app/api/v2/container.go b/agent/app/api/v2/container.go index 02d8de4fc..bdf7eae01 100644 --- a/agent/app/api/v2/container.go +++ b/agent/app/api/v2/container.go @@ -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 diff --git a/agent/app/api/v2/image.go b/agent/app/api/v2/image.go index 84ed17cdc..a906f4366 100644 --- a/agent/app/api/v2/image.go +++ b/agent/app/api/v2/image.go @@ -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 diff --git a/agent/app/dto/container.go b/agent/app/dto/container.go index e5c06c764..0520e153d 100644 --- a/agent/app/dto/container.go +++ b/agent/app/dto/container.go @@ -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"` diff --git a/agent/app/dto/image.go b/agent/app/dto/image.go index 541c5ffb0..bd4e1f8cd 100644 --- a/agent/app/dto/image.go +++ b/agent/app/dto/image.go @@ -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"` diff --git a/agent/app/service/container.go b/agent/app/service/container.go index 0c8f241ee..242bcf6e6 100644 --- a/agent/app/service/container.go +++ b/agent/app/service/container.go @@ -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 } diff --git a/agent/app/service/container_compose.go b/agent/app/service/container_compose.go index 78ca8a479..2c1769498 100644 --- a/agent/app/service/container_compose.go +++ b/agent/app/service/container_compose.go @@ -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 { diff --git a/agent/app/service/device_clean.go b/agent/app/service/device_clean.go index 4b36b997c..2a456d789 100644 --- a/agent/app/service/device_clean.go +++ b/agent/app/service/device_clean.go @@ -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 } diff --git a/agent/app/service/file.go b/agent/app/service/file.go index 74abceed5..1008df7d2 100644 --- a/agent/app/service/file.go +++ b/agent/app/service/file.go @@ -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) diff --git a/agent/app/service/image.go b/agent/app/service/image.go index affecd1d6..3aa7a4e9f 100644 --- a/agent/app/service/image.go +++ b/agent/app/service/image.go @@ -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 { diff --git a/agent/app/task/task.go b/agent/app/task/task.go index c129ae96f..e17f42b52 100644 --- a/agent/app/task/task.go +++ b/agent/app/task/task.go @@ -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" ) diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index 23268989d..b958dfd44 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -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 }}" \ No newline at end of file +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 }}" \ No newline at end of file diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index 2c3fec5d1..224ad8dab 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -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 }}" \ No newline at end of file +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 }}" \ No newline at end of file diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index 99dade255..2b8ed3f3f 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -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 }}" \ No newline at end of file diff --git a/frontend/src/api/interface/container.ts b/frontend/src/api/interface/container.ts index 6a4a1e209..b85b39be1 100644 --- a/frontend/src/api/interface/container.ts +++ b/frontend/src/api/interface/container.ts @@ -143,12 +143,14 @@ export namespace Container { isUsed: boolean; } export interface ImageBuild { + taskID: string; from: string; name: string; dockerfile: string; tags: Array; } 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; diff --git a/frontend/src/views/container/compose/create/index.vue b/frontend/src/views/container/compose/create/index.vue index 5f831c70f..e8f23174e 100644 --- a/frontend/src/views/container/compose/create/index.vue +++ b/frontend/src/views/container/compose/create/index.vue @@ -37,12 +37,7 @@
- - {{ $t('commons.button.edit') }} - {{ $t('commons.button.log') }} -
-
- -
+