From 0e5d6e825e2798a5a1dc486a364a1a6aeaf05d3c Mon Sep 17 00:00:00 2001 From: zhengkunwang223 Date: Mon, 29 Jul 2024 18:09:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9B=E5=BB=BA=E7=BD=91=E7=AB=99?= =?UTF-8?q?=E3=80=81=E5=BA=94=E7=94=A8=E4=BB=BB=E5=8A=A1=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + agent/app/api/v1/app.go | 5 +- agent/app/api/v1/website.go | 10 - agent/app/dto/request/file.go | 1 + agent/app/dto/request/task.go | 1 + agent/app/dto/request/website.go | 3 +- agent/app/dto/response/task.go | 1 + agent/app/model/task.go | 17 + agent/app/repo/task.go | 55 +++ agent/app/service/app.go | 78 ++-- agent/app/service/app_utils.go | 359 +++++++++--------- agent/app/service/entry.go | 2 + agent/app/service/file.go | 8 +- agent/app/service/website.go | 174 +++++---- agent/app/service/website_utils.go | 11 +- agent/app/task/task.go | 167 ++++++-- agent/constant/app.go | 1 + agent/constant/common.go | 13 +- agent/constant/task.go | 6 + agent/i18n/lang/en.yaml | 40 +- agent/i18n/lang/zh-Hant.yaml | 31 +- agent/i18n/lang/zh.yaml | 29 +- agent/init/migration/migrate.go | 1 + agent/init/migration/migrations/init.go | 8 + agent/server/server.go | 39 +- core/app/service/logs.go | 6 +- core/middleware/operation.go | 2 +- core/middleware/proxy.go | 2 +- frontend/package.json | 1 + frontend/src/api/interface/file.ts | 1 + frontend/src/api/interface/website.ts | 1 + frontend/src/api/modules/website.ts | 2 +- frontend/src/components/log-file/index.vue | 2 +- frontend/src/components/task-log/index.vue | 212 +++++++++++ frontend/src/store/modules/global.ts | 2 +- .../host/firewall/forward/operate/index.vue | 1 - .../setting/backup-account/kodo/index.vue | 2 +- .../src/views/toolbox/clam/record/index.vue | 2 +- .../src/views/website/runtime/go/index.vue | 29 -- .../src/views/website/runtime/java/index.vue | 29 -- .../src/views/website/runtime/node/index.vue | 29 -- .../views/website/website/create/index.vue | 12 + 42 files changed, 925 insertions(+), 471 deletions(-) create mode 100644 agent/app/dto/request/task.go create mode 100644 agent/app/dto/response/task.go create mode 100644 agent/app/model/task.go create mode 100644 agent/app/repo/task.go create mode 100644 agent/constant/task.go create mode 100644 frontend/src/components/task-log/index.vue diff --git a/.gitignore b/.gitignore index 7df50e585..87a51f9cb 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ agent/utils/xpack/xpack_xpack.go core/xpack core/router/entry_xpack.go core/server/init_xpack.go +xpack .history/ dist/ diff --git a/agent/app/api/v1/app.go b/agent/app/api/v1/app.go index c07cc3cfb..cbeb07c5f 100644 --- a/agent/app/api/v1/app.go +++ b/agent/app/api/v1/app.go @@ -161,14 +161,11 @@ func (b *BaseApi) InstallApp(c *gin.Context) { if err := helper.CheckBindAndValidate(&req, c); err != nil { return } - tx, ctx := helper.GetTxAndContext() - install, err := appService.Install(ctx, req) + install, err := appService.Install(req) if err != nil { - tx.Rollback() helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) return } - tx.Commit() helper.SuccessWithData(c, install) } diff --git a/agent/app/api/v1/website.go b/agent/app/api/v1/website.go index b5c6be084..4e0372ab0 100644 --- a/agent/app/api/v1/website.go +++ b/agent/app/api/v1/website.go @@ -1,8 +1,6 @@ package v1 import ( - "encoding/base64" - "github.com/1Panel-dev/1Panel/agent/app/api/v1/helper" "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto/request" @@ -78,14 +76,6 @@ func (b *BaseApi) CreateWebsite(c *gin.Context) { if err := helper.CheckBindAndValidate(&req, c); err != nil { return } - if len(req.FtpPassword) != 0 { - pass, err := base64.StdEncoding.DecodeString(req.FtpPassword) - if err != nil { - helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) - return - } - req.FtpPassword = string(pass) - } err := websiteService.CreateWebsite(req) if err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) diff --git a/agent/app/dto/request/file.go b/agent/app/dto/request/file.go index ea31c8bc6..9707a109e 100644 --- a/agent/app/dto/request/file.go +++ b/agent/app/dto/request/file.go @@ -128,6 +128,7 @@ type FileReadByLineReq struct { ID uint `json:"ID"` Name string `json:"name"` Latest bool `json:"latest"` + TaskID string `json:"taskID"` } type FileExistReq struct { diff --git a/agent/app/dto/request/task.go b/agent/app/dto/request/task.go new file mode 100644 index 000000000..725b8fc21 --- /dev/null +++ b/agent/app/dto/request/task.go @@ -0,0 +1 @@ +package request diff --git a/agent/app/dto/request/website.go b/agent/app/dto/request/website.go index 839673375..da4111fee 100644 --- a/agent/app/dto/request/website.go +++ b/agent/app/dto/request/website.go @@ -30,7 +30,8 @@ type WebsiteCreate struct { FtpUser string `json:"ftpUser"` FtpPassword string `json:"ftpPassword"` - RuntimeID uint `json:"runtimeID"` + RuntimeID uint `json:"runtimeID"` + TaskID string `json:"taskID"` RuntimeConfig } diff --git a/agent/app/dto/response/task.go b/agent/app/dto/response/task.go new file mode 100644 index 000000000..a467149d4 --- /dev/null +++ b/agent/app/dto/response/task.go @@ -0,0 +1 @@ +package response diff --git a/agent/app/model/task.go b/agent/app/model/task.go new file mode 100644 index 000000000..4126400aa --- /dev/null +++ b/agent/app/model/task.go @@ -0,0 +1,17 @@ +package model + +import "time" + +type Task struct { + ID string `gorm:"primarykey;" json:"id"` + Name string `json:"name"` + Type string `json:"type"` + LogFile string `json:"logFile"` + Status string `json:"status"` + ErrorMsg string `json:"errorMsg"` + OperationLogID uint `json:"operationLogID"` + ResourceID uint `json:"resourceID"` + CurrentStep string `json:"currentStep"` + EndAt time.Time `json:"endAt"` + CreatedAt time.Time `json:"createdAt"` +} diff --git a/agent/app/repo/task.go b/agent/app/repo/task.go new file mode 100644 index 000000000..0f19c50aa --- /dev/null +++ b/agent/app/repo/task.go @@ -0,0 +1,55 @@ +package repo + +import ( + "context" + "github.com/1Panel-dev/1Panel/agent/app/model" + "gorm.io/gorm" +) + +type TaskRepo struct { +} + +type ITaskRepo interface { + Create(ctx context.Context, task *model.Task) error + GetFirst(opts ...DBOption) (model.Task, error) + Page(page, size int, opts ...DBOption) (int64, []model.Task, error) + Update(ctx context.Context, task *model.Task) error + + WithByID(id string) DBOption +} + +func NewITaskRepo() ITaskRepo { + return &TaskRepo{} +} + +func (t TaskRepo) WithByID(id string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id = ?", id) + } +} + +func (t TaskRepo) Create(ctx context.Context, task *model.Task) error { + return getTx(ctx).Create(&task).Error +} + +func (t TaskRepo) GetFirst(opts ...DBOption) (model.Task, error) { + var task model.Task + db := getDb(opts...).Model(&model.Task{}) + if err := db.First(&task).Error; err != nil { + return task, err + } + return task, nil +} + +func (t TaskRepo) Page(page, size int, opts ...DBOption) (int64, []model.Task, error) { + var tasks []model.Task + db := getDb(opts...).Model(&model.Task{}) + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&tasks).Error + return count, tasks, err +} + +func (t TaskRepo) Update(ctx context.Context, task *model.Task) error { + return getTx(ctx).Save(&task).Error +} diff --git a/agent/app/service/app.go b/agent/app/service/app.go index 2d58e6ddd..ade6f36ca 100644 --- a/agent/app/service/app.go +++ b/agent/app/service/app.go @@ -5,18 +5,12 @@ import ( "encoding/base64" "encoding/json" "fmt" - "net/http" - "os" - "path/filepath" - "reflect" - "strconv" - "strings" - "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto/request" "github.com/1Panel-dev/1Panel/agent/app/dto/response" "github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/repo" + "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" @@ -27,7 +21,14 @@ import ( http2 "github.com/1Panel-dev/1Panel/agent/utils/http" httpUtil "github.com/1Panel-dev/1Panel/agent/utils/http" "github.com/1Panel-dev/1Panel/agent/utils/xpack" + "github.com/google/uuid" "gopkg.in/yaml.v3" + "net/http" + "os" + "path/filepath" + "reflect" + "strconv" + "strings" ) type AppService struct { @@ -38,7 +39,7 @@ type IAppService interface { GetAppTags() ([]response.TagDTO, error) GetApp(key string) (*response.AppDTO, error) GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error) - Install(ctx context.Context, req request.AppInstallCreate) (*model.AppInstall, error) + Install(req request.AppInstallCreate) (*model.AppInstall, error) SyncAppListFromRemote() error GetAppUpdate() (*response.AppUpdateRes, error) GetAppDetailByID(id uint) (*response.AppDetailDTO, error) @@ -295,7 +296,7 @@ func (a AppService) GetIgnoredApp() ([]response.IgnoredApp, error) { return res, nil } -func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (appInstall *model.AppInstall, err error) { +func (a AppService) Install(req request.AppInstallCreate) (appInstall *model.AppInstall, err error) { if err = docker.CreateDefaultDockerNetwork(); err != nil { err = buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil) return @@ -423,14 +424,6 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) ( } appInstall.DockerCompose = string(composeByte) - defer func() { - if err != nil { - hErr := handleAppInstallErr(ctx, appInstall) - if hErr != nil { - global.LOG.Errorf("delete app dir error %s", hErr.Error()) - } - } - }() if hostName, ok := req.Params["PANEL_DB_HOST"]; ok { database, _ := databaseRepo.Get(commonRepo.WithByName(hostName.(string))) if !reflect.DeepEqual(database, model.Database{}) { @@ -445,29 +438,48 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) ( } appInstall.Env = string(paramByte) - if err = appInstallRepo.Create(ctx, appInstall); err != nil { + if err = appInstallRepo.Create(context.Background(), appInstall); err != nil { return } - if err = createLink(ctx, app, appInstall, req.Params); err != nil { + + taskID := uuid.New().String() + installTask, err := task.NewTaskWithOps(appInstall.Name, task.TaskCreate, task.TaskScopeApp, taskID) + if err != nil { return } + + if err = createLink(context.Background(), installTask, app, appInstall, req.Params); err != nil { + return + } + + installApp := func(t *task.Task) error { + if err = copyData(t, app, appDetail, appInstall, req); err != nil { + return err + } + if err = runScript(t, appInstall, "init"); err != nil { + return err + } + upApp(t, appInstall, req.PullImage) + updateToolApp(appInstall) + return nil + } + + handleAppStatus := func() { + appInstall.Status = constant.UpErr + appInstall.Message = installTask.Task.ErrorMsg + _ = appInstallRepo.Save(context.Background(), appInstall) + } + + installTask.AddSubTask(task.GetTaskName(appInstall.Name, task.TaskInstall, task.TaskScopeApp), installApp, handleAppStatus) + go func() { - defer func() { - if err != nil { - appInstall.Status = constant.UpErr - appInstall.Message = err.Error() - _ = appInstallRepo.Save(context.Background(), appInstall) - } - }() - if err = copyData(app, appDetail, appInstall, req); err != nil { - return + if taskErr := installTask.Execute(); taskErr != nil { + appInstall.Status = constant.InstallErr + appInstall.Message = taskErr.Error() + _ = appInstallRepo.Save(context.Background(), appInstall) } - if err = runScript(appInstall, "init"); err != nil { - return - } - upApp(appInstall, req.PullImage) }() - go updateToolApp(appInstall) + return } diff --git a/agent/app/service/app_utils.go b/agent/app/service/app_utils.go index f28a1d82f..c7ed9548b 100644 --- a/agent/app/service/app_utils.go +++ b/agent/app/service/app_utils.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "github.com/1Panel-dev/1Panel/agent/app/task" "math" "net/http" "os" @@ -130,78 +131,82 @@ var ToolKeys = map[string]uint{ "minio": 9001, } -func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall, params map[string]interface{}) error { +func createLink(ctx context.Context, installTask *task.Task, app model.App, appInstall *model.AppInstall, params map[string]interface{}) error { + deleteAppLink := func() { + _ = deleteLink(ctx, appInstall, true, true, true) + } var dbConfig dto.AppDatabase if DatabaseKeys[app.Key] > 0 { - database := &model.Database{ - AppInstallID: appInstall.ID, - Name: appInstall.Name, - Type: app.Key, - Version: appInstall.Version, - From: "local", - Address: appInstall.ServiceName, - Port: DatabaseKeys[app.Key], - } - detail, err := appDetailRepo.GetFirst(commonRepo.WithByID(appInstall.AppDetailId)) - if err != nil { - return err - } - - formFields := &dto.AppForm{} - if err := json.Unmarshal([]byte(detail.Params), formFields); err != nil { - return err - } - for _, form := range formFields.FormFields { - if form.EnvKey == "PANEL_APP_PORT_HTTP" { - portFloat, ok := form.Default.(float64) - if ok { - database.Port = uint(int(portFloat)) - } - break + handleDataBaseApp := func(task *task.Task) error { + database := &model.Database{ + AppInstallID: appInstall.ID, + Name: appInstall.Name, + Type: app.Key, + Version: appInstall.Version, + From: "local", + Address: appInstall.ServiceName, + Port: DatabaseKeys[app.Key], + } + detail, err := appDetailRepo.GetFirst(commonRepo.WithByID(appInstall.AppDetailId)) + if err != nil { + return err } - } - switch app.Key { - case constant.AppMysql, constant.AppMariaDB, constant.AppPostgresql, constant.AppMongodb: - if password, ok := params["PANEL_DB_ROOT_PASSWORD"]; ok { - if password != "" { + formFields := &dto.AppForm{} + if err := json.Unmarshal([]byte(detail.Params), formFields); err != nil { + return err + } + for _, form := range formFields.FormFields { + if form.EnvKey == "PANEL_APP_PORT_HTTP" { + portFloat, ok := form.Default.(float64) + if ok { + database.Port = uint(int(portFloat)) + } + break + } + } + + switch app.Key { + case constant.AppMysql, constant.AppMariaDB, constant.AppPostgresql, constant.AppMongodb: + if password, ok := params["PANEL_DB_ROOT_PASSWORD"]; ok { + if password != "" { + database.Password = password.(string) + if app.Key == "mysql" || app.Key == "mariadb" { + database.Username = "root" + } + if rootUser, ok := params["PANEL_DB_ROOT_USER"]; ok { + database.Username = rootUser.(string) + } + authParam := dto.AuthParam{ + RootPassword: password.(string), + RootUser: database.Username, + } + authByte, err := json.Marshal(authParam) + if err != nil { + return err + } + appInstall.Param = string(authByte) + + } + } + case constant.AppRedis: + if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok { + if password != "" { + authParam := dto.RedisAuthParam{ + RootPassword: password.(string), + } + authByte, err := json.Marshal(authParam) + if err != nil { + return err + } + appInstall.Param = string(authByte) + } database.Password = password.(string) - if app.Key == "mysql" || app.Key == "mariadb" { - database.Username = "root" - } - if rootUser, ok := params["PANEL_DB_ROOT_USER"]; ok { - database.Username = rootUser.(string) - } - authParam := dto.AuthParam{ - RootPassword: password.(string), - RootUser: database.Username, - } - authByte, err := json.Marshal(authParam) - if err != nil { - return err - } - appInstall.Param = string(authByte) - } } - case constant.AppRedis: - if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok { - if password != "" { - authParam := dto.RedisAuthParam{ - RootPassword: password.(string), - } - authByte, err := json.Marshal(authParam) - if err != nil { - return err - } - appInstall.Param = string(authByte) - } - database.Password = password.(string) - } - } - if err := databaseRepo.Create(ctx, database); err != nil { - return err + return databaseRepo.Create(ctx, database) } + installTask.AddSubTask(i18n.GetMsgByKey("HandleDatabaseApp"), handleDataBaseApp, deleteAppLink) } if ToolKeys[app.Key] > 0 { if app.Key == "minio" { @@ -231,95 +236,79 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall } if !reflect.DeepEqual(dbConfig, dto.AppDatabase{}) && dbConfig.ServiceName != "" { - hostName := params["PANEL_DB_HOST_NAME"] - if hostName == nil || hostName.(string) == "" { - return nil - } - database, _ := databaseRepo.Get(commonRepo.WithByName(hostName.(string))) - if database.ID == 0 { - return nil - } - var resourceId uint - if dbConfig.DbName != "" && dbConfig.DbUser != "" && dbConfig.Password != "" { - switch database.Type { - case constant.AppPostgresql, constant.AppPostgres: - iPostgresqlRepo := repo.NewIPostgresqlRepo() - oldPostgresqlDb, _ := iPostgresqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iPostgresqlRepo.WithByFrom(constant.ResourceLocal)) - resourceId = oldPostgresqlDb.ID - if oldPostgresqlDb.ID > 0 { - if oldPostgresqlDb.Username != dbConfig.DbUser || oldPostgresqlDb.Password != dbConfig.Password { - return buserr.New(constant.ErrDbUserNotValid) + createAppDataBase := func(rootTask *task.Task) error { + hostName := params["PANEL_DB_HOST_NAME"] + if hostName == nil || hostName.(string) == "" { + return nil + } + database, _ := databaseRepo.Get(commonRepo.WithByName(hostName.(string))) + if database.ID == 0 { + return nil + } + var resourceId uint + if dbConfig.DbName != "" && dbConfig.DbUser != "" && dbConfig.Password != "" { + switch database.Type { + case constant.AppPostgresql, constant.AppPostgres: + iPostgresqlRepo := repo.NewIPostgresqlRepo() + oldPostgresqlDb, _ := iPostgresqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iPostgresqlRepo.WithByFrom(constant.ResourceLocal)) + resourceId = oldPostgresqlDb.ID + if oldPostgresqlDb.ID > 0 { + if oldPostgresqlDb.Username != dbConfig.DbUser || oldPostgresqlDb.Password != dbConfig.Password { + return buserr.New(constant.ErrDbUserNotValid) + } + } else { + var createPostgresql dto.PostgresqlDBCreate + createPostgresql.Name = dbConfig.DbName + createPostgresql.Username = dbConfig.DbUser + createPostgresql.Database = database.Name + createPostgresql.Format = "UTF8" + createPostgresql.Password = dbConfig.Password + createPostgresql.From = database.From + createPostgresql.SuperUser = true + pgdb, err := NewIPostgresqlService().Create(ctx, createPostgresql) + if err != nil { + return err + } + resourceId = pgdb.ID } - } else { - var createPostgresql dto.PostgresqlDBCreate - createPostgresql.Name = dbConfig.DbName - createPostgresql.Username = dbConfig.DbUser - createPostgresql.Database = database.Name - createPostgresql.Format = "UTF8" - createPostgresql.Password = dbConfig.Password - createPostgresql.From = database.From - createPostgresql.SuperUser = true - pgdb, err := NewIPostgresqlService().Create(ctx, createPostgresql) - if err != nil { - return err + case constant.AppMysql, constant.AppMariaDB: + iMysqlRepo := repo.NewIMysqlRepo() + oldMysqlDb, _ := iMysqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iMysqlRepo.WithByFrom(constant.ResourceLocal)) + resourceId = oldMysqlDb.ID + if oldMysqlDb.ID > 0 { + if oldMysqlDb.Username != dbConfig.DbUser || oldMysqlDb.Password != dbConfig.Password { + return buserr.New(constant.ErrDbUserNotValid) + } + } else { + var createMysql dto.MysqlDBCreate + createMysql.Name = dbConfig.DbName + createMysql.Username = dbConfig.DbUser + createMysql.Database = database.Name + createMysql.Format = "utf8mb4" + createMysql.Permission = "%" + createMysql.Password = dbConfig.Password + createMysql.From = database.From + mysqldb, err := NewIMysqlService().Create(ctx, createMysql) + if err != nil { + return err + } + resourceId = mysqldb.ID } - resourceId = pgdb.ID - } - case constant.AppMysql, constant.AppMariaDB: - iMysqlRepo := repo.NewIMysqlRepo() - oldMysqlDb, _ := iMysqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iMysqlRepo.WithByFrom(constant.ResourceLocal)) - resourceId = oldMysqlDb.ID - if oldMysqlDb.ID > 0 { - if oldMysqlDb.Username != dbConfig.DbUser || oldMysqlDb.Password != dbConfig.Password { - return buserr.New(constant.ErrDbUserNotValid) - } - } else { - var createMysql dto.MysqlDBCreate - createMysql.Name = dbConfig.DbName - createMysql.Username = dbConfig.DbUser - createMysql.Database = database.Name - createMysql.Format = "utf8mb4" - createMysql.Permission = "%" - createMysql.Password = dbConfig.Password - createMysql.From = database.From - mysqldb, err := NewIMysqlService().Create(ctx, createMysql) - if err != nil { - return err - } - resourceId = mysqldb.ID } } - + var installResource model.AppInstallResource + installResource.ResourceId = resourceId + installResource.AppInstallId = appInstall.ID + if database.AppInstallID > 0 { + installResource.LinkId = database.AppInstallID + } else { + installResource.LinkId = database.ID + } + installResource.Key = database.Type + installResource.From = database.From + return appInstallResourceRepo.Create(ctx, &installResource) } - var installResource model.AppInstallResource - installResource.ResourceId = resourceId - installResource.AppInstallId = appInstall.ID - if database.AppInstallID > 0 { - installResource.LinkId = database.AppInstallID - } else { - installResource.LinkId = database.ID - } - installResource.Key = database.Type - installResource.From = database.From - if err := appInstallResourceRepo.Create(ctx, &installResource); err != nil { - return err - } - } - return nil -} - -func handleAppInstallErr(ctx context.Context, install *model.AppInstall) error { - op := files.NewFileOp() - appDir := install.GetPath() - dir, _ := os.Stat(appDir) - if dir != nil { - _, _ = compose.Down(install.GetComposePath()) - if err := op.DeleteDir(appDir); err != nil { - return err - } - } - if err := deleteLink(ctx, install, true, true, true); err != nil { - return err + installTask.AddSubTask(task.GetTaskName(dbConfig.DbName, task.TaskCreate, task.TaskScopeDatabase), createAppDataBase, deleteAppLink) } return nil } @@ -333,7 +322,8 @@ func deleteAppInstall(install model.AppInstall, deleteBackup bool, forceDelete b if err != nil && !forceDelete { return handleErr(install, err, out) } - if err = runScript(&install, "uninstall"); err != nil { + //TODO use task + if err = runScript(nil, &install, "uninstall"); err != nil { _, _ = compose.Up(install.GetComposePath()) return err } @@ -652,7 +642,8 @@ func upgradeInstall(req request.AppInstallUpgrade) error { return } - if upErr = runScript(&install, "upgrade"); upErr != nil { + //TODO use task + if upErr = runScript(nil, &install, "upgrade"); upErr != nil { return } @@ -800,7 +791,7 @@ func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.App return } -func copyData(app model.App, appDetail model.AppDetail, appInstall *model.AppInstall, req request.AppInstallCreate) (err error) { +func copyData(task *task.Task, app model.App, appDetail model.AppDetail, appInstall *model.AppInstall, req request.AppInstallCreate) (err error) { fileOp := files.NewFileOp() appResourceDir := path.Join(constant.AppResourceDir, app.Resource) @@ -853,7 +844,7 @@ func copyData(app model.App, appDetail model.AppDetail, appInstall *model.AppIns return } -func runScript(appInstall *model.AppInstall, operate string) error { +func runScript(task *task.Task, appInstall *model.AppInstall, operate string) error { workDir := appInstall.GetPath() scriptPath := "" switch operate { @@ -867,15 +858,17 @@ func runScript(appInstall *model.AppInstall, operate string) error { if !files.NewFileOp().Stat(scriptPath) { return nil } + logStr := i18n.GetWithName("ExecShell", operate) + task.LogStart(logStr) out, err := cmd.ExecScript(scriptPath, workDir) if err != nil { if out != "" { - errMsg := fmt.Sprintf("run script %s error %s", scriptPath, out) - global.LOG.Error(errMsg) - return errors.New(errMsg) + err = errors.New(out) } + task.LogFailedWithErr(logStr, err) return err } + task.LogSuccess(logStr) return nil } @@ -905,38 +898,59 @@ func checkContainerNameIsExist(containerName, appDir string) (bool, error) { return false, nil } -func upApp(appInstall *model.AppInstall, pullImages bool) { +func upApp(task *task.Task, appInstall *model.AppInstall, pullImages bool) { upProject := func(appInstall *model.AppInstall) (err error) { var ( out string errMsg string ) if pullImages && appInstall.App.Type != "php" { - out, err = compose.Pull(appInstall.GetComposePath()) + projectName := strings.ToLower(appInstall.Name) + envByte, err := files.NewFileOp().GetContent(appInstall.GetEnvPath()) if err != nil { - if out != "" { - if strings.Contains(out, "no such host") { - errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":" - } - if strings.Contains(out, "timeout") { - errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":" - } - appInstall.Message = errMsg + out - } return err } + images, err := composeV2.GetDockerComposeImages(projectName, envByte, []byte(appInstall.DockerCompose)) + if err != nil { + return err + } + for _, image := range images { + task.Log(i18n.GetWithName("PullImageStart", image)) + if out, err = cmd.ExecWithTimeOut("docker pull "+image, 20*time.Minute); err != nil { + if out != "" { + if strings.Contains(out, "no such host") { + errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":" + } + if strings.Contains(out, "timeout") { + errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":" + } + } + appInstall.Message = errMsg + out + task.LogFailedWithErr(i18n.GetMsgByKey("PullImage"), err) + return err + } else { + task.Log(i18n.GetMsgByKey("PullImageSuccess")) + } + } } - + logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("Run"), i18n.GetMsgByKey("App")) + task.Log(logStr) out, err = compose.Up(appInstall.GetComposePath()) if err != nil { if out != "" { appInstall.Message = errMsg + out + err = errors.New(out) } + task.LogFailedWithErr(logStr, err) return err } + task.LogSuccess(logStr) return } if err := upProject(appInstall); err != nil { + if appInstall.Message == "" { + appInstall.Message = err.Error() + } appInstall.Status = constant.UpErr } else { appInstall.Status = constant.Running @@ -944,13 +958,12 @@ func upApp(appInstall *model.AppInstall, pullImages bool) { exist, _ := appInstallRepo.GetFirst(commonRepo.WithByID(appInstall.ID)) if exist.ID > 0 { containerNames, err := getContainerNames(*appInstall) - if err != nil { - return + if err == nil { + if len(containerNames) > 0 { + appInstall.ContainerName = strings.Join(containerNames, ",") + } + _ = appInstallRepo.Save(context.Background(), appInstall) } - if len(containerNames) > 0 { - appInstall.ContainerName = strings.Join(containerNames, ",") - } - _ = appInstallRepo.Save(context.Background(), appInstall) } } diff --git a/agent/app/service/entry.go b/agent/app/service/entry.go index 25b3289a5..7b6c5d6d0 100644 --- a/agent/app/service/entry.go +++ b/agent/app/service/entry.go @@ -43,4 +43,6 @@ var ( phpExtensionsRepo = repo.NewIPHPExtensionsRepo() favoriteRepo = repo.NewIFavoriteRepo() + + taskRepo = repo.NewITaskRepo() ) diff --git a/agent/app/service/file.go b/agent/app/service/file.go index 869ee7ec4..07555d9b8 100644 --- a/agent/app/service/file.go +++ b/agent/app/service/file.go @@ -466,7 +466,13 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi return nil, fmt.Errorf("handle ungzip file %s failed, err: %v", fileGzPath, err) } } - case "image-pull", "image-push", "image-build", "compose-create": + case constant.TypeTask: + task, err := taskRepo.GetFirst(taskRepo.WithByID(req.TaskID)) + if err != nil { + return nil, err + } + logFilePath = task.LogFile + case constant.TypeImagePull, constant.TypeImagePush, constant.TypeImageBuild, constant.TypeComposeCreate: logFilePath = path.Join(global.CONF.System.TmpDir, fmt.Sprintf("docker_logs/%s", req.Name)) } diff --git a/agent/app/service/website.go b/agent/app/service/website.go index debcc3664..95d0e6c97 100644 --- a/agent/app/service/website.go +++ b/agent/app/service/website.go @@ -5,10 +5,12 @@ import ( "bytes" "context" "crypto/x509" + "encoding/base64" "encoding/json" "encoding/pem" "errors" "fmt" + "github.com/1Panel-dev/1Panel/agent/app/task" "os" "path" "reflect" @@ -206,6 +208,13 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error) if exist, _ := websiteRepo.GetBy(websiteRepo.WithAlias(alias)); len(exist) > 0 { return buserr.New(constant.ErrAliasIsExist) } + if len(create.FtpPassword) != 0 { + pass, err := base64.StdEncoding.DecodeString(create.FtpPassword) + if err != nil { + return err + } + create.FtpPassword = string(pass) + } nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { @@ -249,23 +258,15 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error) runtime *model.Runtime ) - defer func() { - if err != nil { - if website.AppInstallID > 0 { - req := request.AppInstalledOperate{ - InstallId: website.AppInstallID, - Operate: constant.Delete, - ForceDelete: true, - } - if err := NewIAppInstalledService().Operate(req); err != nil { - global.LOG.Errorf(err.Error()) - } - } - } - }() + createTask, err := task.NewTaskWithOps(create.PrimaryDomain, task.TaskCreate, task.TaskScopeWebsite, create.TaskID) + if err != nil { + return err + } + var proxy string switch create.Type { + case constant.Deployment: if create.AppType == constant.NewApp { var ( @@ -276,13 +277,10 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error) req.AppDetailId = create.AppInstall.AppDetailId req.Params = create.AppInstall.Params req.AppContainerConfig = create.AppInstall.AppContainerConfig - tx, installCtx := getTxAndContext() - install, err = NewIAppService().Install(installCtx, req) + install, err = NewIAppService().Install(req) if err != nil { - tx.Rollback() return err } - tx.Commit() appInstall = install website.AppInstallID = install.ID website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort) @@ -292,9 +290,13 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error) if err != nil { return err } - appInstall = &install - website.AppInstallID = appInstall.ID - website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort) + configApp := func(t *task.Task) error { + appInstall = &install + website.AppInstallID = appInstall.ID + website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort) + return nil + } + createTask.AddSubTask(i18n.GetMsgByKey("ConfigApp"), configApp, nil) } case constant.Runtime: runtime, err = runtimeRepo.GetFirst(commonRepo.WithByID(create.RuntimeID)) @@ -302,75 +304,89 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error) return err } website.RuntimeID = runtime.ID - switch runtime.Type { - case constant.RuntimePHP: - if runtime.Resource == constant.ResourceAppstore { - var ( - req request.AppInstallCreate - install *model.AppInstall - ) - reg, _ := regexp.Compile(`[^a-z0-9_-]+`) - req.Name = reg.ReplaceAllString(strings.ToLower(alias), "") - req.AppDetailId = create.AppInstall.AppDetailId - req.Params = create.AppInstall.Params - req.Params["IMAGE_NAME"] = runtime.Image - req.AppContainerConfig = create.AppInstall.AppContainerConfig - req.Params["PANEL_WEBSITE_DIR"] = path.Join(nginxInstall.GetPath(), "/www") - tx, installCtx := getTxAndContext() - install, err = NewIAppService().Install(installCtx, req) - if err != nil { - tx.Rollback() - return err - } - tx.Commit() - website.AppInstallID = install.ID - appInstall = install - website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort) - } else { - website.ProxyType = create.ProxyType - if website.ProxyType == constant.RuntimeProxyUnix { - proxy = fmt.Sprintf("unix:%s", path.Join("/www/sites", website.Alias, "php-pool", "php-fpm.sock")) - } - if website.ProxyType == constant.RuntimeProxyTcp { - proxy = fmt.Sprintf("127.0.0.1:%d", create.Port) - } - website.Proxy = proxy + if runtime.Type == constant.RuntimePHP { + var ( + req request.AppInstallCreate + install *model.AppInstall + ) + reg, _ := regexp.Compile(`[^a-z0-9_-]+`) + req.Name = reg.ReplaceAllString(strings.ToLower(alias), "") + req.AppDetailId = create.AppInstall.AppDetailId + req.Params = create.AppInstall.Params + req.Params["IMAGE_NAME"] = runtime.Image + req.AppContainerConfig = create.AppInstall.AppContainerConfig + req.Params["PANEL_WEBSITE_DIR"] = path.Join(nginxInstall.GetPath(), "/www") + install, err = NewIAppService().Install(req) + if err != nil { + return err } - case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo: - website.Proxy = fmt.Sprintf("127.0.0.1:%d", runtime.Port) + website.AppInstallID = install.ID + appInstall = install + website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort) + } else { + website.ProxyType = create.ProxyType + if website.ProxyType == constant.RuntimeProxyUnix { + proxy = fmt.Sprintf("unix:%s", path.Join("/www/sites", website.Alias, "php-pool", "php-fpm.sock")) + } + if website.ProxyType == constant.RuntimeProxyTcp { + proxy = fmt.Sprintf("127.0.0.1:%d", create.Port) + } + website.Proxy = proxy } - } - - if err = configDefaultNginx(website, domains, appInstall, runtime); err != nil { - return err + case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo: + website.Proxy = fmt.Sprintf("127.0.0.1:%d", runtime.Port) } if len(create.FtpUser) != 0 && len(create.FtpPassword) != 0 { - indexDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "index") - itemID, err := NewIFtpService().Create(dto.FtpCreate{User: create.FtpUser, Password: create.FtpPassword, Path: indexDir}) - if err != nil { - global.LOG.Errorf("create ftp for website failed, err: %v", err) + createFtpUser := func(t *task.Task) error { + indexDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "index") + itemID, err := NewIFtpService().Create(dto.FtpCreate{User: create.FtpUser, Password: create.FtpPassword, Path: indexDir}) + if err != nil { + createTask.Log(fmt.Sprintf("create ftp for website failed, err: %v", err)) + } + website.FtpID = itemID + return nil } - website.FtpID = itemID + deleteFtpUser := func() { + if website.FtpID > 0 { + req := dto.BatchDeleteReq{Ids: []uint{website.FtpID}} + if err = NewIFtpService().Delete(req); err != nil { + createTask.Log(err.Error()) + } + } + } + createTask.AddSubTask(i18n.GetWithName("ConfigFTP", create.FtpUser), createFtpUser, deleteFtpUser) } - if err = createWafConfig(website, domains); err != nil { - return err + configNginx := func(t *task.Task) error { + if err = configDefaultNginx(website, domains, appInstall, runtime); err != nil { + return err + } + if err = createWafConfig(website, domains); err != nil { + return err + } + tx, ctx := helper.GetTxAndContext() + defer tx.Rollback() + if err = websiteRepo.Create(ctx, website); err != nil { + return err + } + for i := range domains { + domains[i].WebsiteID = website.ID + } + if err = websiteDomainRepo.BatchCreate(ctx, domains); err != nil { + return err + } + tx.Commit() + return nil } - tx, ctx := helper.GetTxAndContext() - defer tx.Rollback() - if err = websiteRepo.Create(ctx, website); err != nil { - return err + deleteWebsite := func() { + _ = deleteWebsiteFolder(nginxInstall, website) } - for i := range domains { - domains[i].WebsiteID = website.ID - } - if err = websiteDomainRepo.BatchCreate(ctx, domains); err != nil { - return err - } - tx.Commit() - return nil + + createTask.AddSubTask(i18n.GetMsgByKey("ConfigOpenresty"), configNginx, deleteWebsite) + + return createTask.Execute() } func (w WebsiteService) OpWebsite(req request.WebsiteOp) error { diff --git a/agent/app/service/website_utils.go b/agent/app/service/website_utils.go index 92525d9a8..d3732d44b 100644 --- a/agent/app/service/website_utils.go +++ b/agent/app/service/website_utils.go @@ -220,10 +220,9 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a if err != nil { return err } - if err := createWebsiteFolder(nginxInstall, website, runtime); err != nil { + if err = createWebsiteFolder(nginxInstall, website, runtime); err != nil { return err } - nginxFileName := website.Alias + ".conf" configPath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "conf.d", nginxFileName) nginxContent := string(nginx_conf.WebsiteDefault) @@ -284,15 +283,13 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a } config.FilePath = configPath - if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { return err } - if err := opNginx(nginxInstall.ContainerName, constant.NginxCheck); err != nil { - _ = deleteWebsiteFolder(nginxInstall, website) + if err = opNginx(nginxInstall.ContainerName, constant.NginxCheck); err != nil { return err } - if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil { - _ = deleteWebsiteFolder(nginxInstall, website) + if err = opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil { return err } return nil diff --git a/agent/app/task/task.go b/agent/app/task/task.go index c1d3ede44..019f590cf 100644 --- a/agent/app/task/task.go +++ b/agent/app/task/task.go @@ -3,28 +3,35 @@ package task import ( "context" "fmt" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/google/uuid" "log" "os" "path" "strconv" "time" - - "github.com/1Panel-dev/1Panel/agent/constant" - "github.com/1Panel-dev/1Panel/agent/i18n" ) -type ActionFunc func() error +type ActionFunc func(*Task) error type RollbackFunc func() type Task struct { Name string + TaskID string Logger *log.Logger SubTasks []*SubTask Rollbacks []RollbackFunc logFile *os.File + taskRepo repo.ITaskRepo + Task *model.Task + ParentID string } type SubTask struct { + RootTask *Task Name string Retry int Timeout time.Duration @@ -33,51 +40,107 @@ type SubTask struct { Error error } -func NewTask(name string, taskType string) (*Task, error) { - logPath := path.Join(constant.LogDir, taskType) - //TODO 增加插入到日志表的逻辑 +const ( + TaskInstall = "TaskInstall" + TaskUninstall = "TaskUninstall" + TaskCreate = "TaskCreate" + TaskDelete = "TaskDelete" + TaskUpgrade = "TaskUpgrade" + TaskUpdate = "TaskUpdate" + TaskRestart = "TaskRestart" +) + +const ( + TaskScopeWebsite = "Website" + TaskScopeApp = "App" + TaskScopeRuntime = "Runtime" + TaskScopeDatabase = "Database" +) + +const ( + TaskSuccess = "Success" + TaskFailed = "Failed" +) + +func GetTaskName(resourceName, operate, scope string) string { + return fmt.Sprintf("%s%s [%s]", i18n.GetMsgByKey(operate), i18n.GetMsgByKey(scope), resourceName) +} + +func NewTaskWithOps(resourceName, operate, scope, taskID string) (*Task, error) { + return NewTask(GetTaskName(resourceName, operate, scope), scope, taskID) +} + +func NewChildTask(name, taskType, parentTaskID string) (*Task, error) { + task, err := NewTask(name, taskType, "") + if err != nil { + return nil, err + } + task.ParentID = parentTaskID + return task, nil +} + +func NewTask(name, taskType, taskID string) (*Task, error) { + if taskID == "" { + taskID = uuid.New().String() + } + logDir := path.Join(constant.LogDir, taskType) + if _, err := os.Stat(logDir); os.IsNotExist(err) { + if err = os.MkdirAll(logDir, 0755); err != nil { + return nil, fmt.Errorf("failed to create log directory: %w", err) + } + } + logPath := path.Join(constant.LogDir, taskType, taskID+".log") file, err := os.OpenFile(logPath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0666) if err != nil { return nil, fmt.Errorf("failed to open log file: %w", err) } logger := log.New(file, "", log.LstdFlags) - return &Task{Name: name, logFile: file, Logger: logger}, nil + taskModel := &model.Task{ + ID: taskID, + Name: name, + Type: taskType, + LogFile: logPath, + Status: constant.StatusRunning, + } + taskRepo := repo.NewITaskRepo() + task := &Task{Name: name, logFile: file, Logger: logger, taskRepo: taskRepo, Task: taskModel} + return task, nil } func (t *Task) AddSubTask(name string, action ActionFunc, rollback RollbackFunc) { - subTask := &SubTask{Name: name, Retry: 0, Timeout: 10 * time.Minute, Action: action, Rollback: rollback} + subTask := &SubTask{RootTask: t, Name: name, Retry: 0, Timeout: 10 * time.Minute, Action: action, Rollback: rollback} t.SubTasks = append(t.SubTasks, subTask) } func (t *Task) AddSubTaskWithOps(name string, action ActionFunc, rollback RollbackFunc, retry int, timeout time.Duration) { - subTask := &SubTask{Name: name, Retry: retry, Timeout: timeout, Action: action, Rollback: rollback} + subTask := &SubTask{RootTask: t, Name: name, Retry: retry, Timeout: timeout, Action: action, Rollback: rollback} t.SubTasks = append(t.SubTasks, subTask) } -func (s *SubTask) Execute(logger *log.Logger) bool { - logger.Printf(i18n.GetWithName("SubTaskStart", s.Name)) +func (s *SubTask) Execute() error { + s.RootTask.Log(s.Name) + var err error for i := 0; i < s.Retry+1; i++ { if i > 0 { - logger.Printf(i18n.GetWithName("TaskRetry", strconv.Itoa(i))) + s.RootTask.Log(i18n.GetWithName("TaskRetry", strconv.Itoa(i))) } ctx, cancel := context.WithTimeout(context.Background(), s.Timeout) defer cancel() done := make(chan error) go func() { - done <- s.Action() + done <- s.Action(s.RootTask) }() select { case <-ctx.Done(): - logger.Printf(i18n.GetWithName("TaskTimeout", s.Name)) - case err := <-done: + s.RootTask.Log(i18n.GetWithName("TaskTimeout", s.Name)) + case err = <-done: if err != nil { - s.Error = err - logger.Printf(i18n.GetWithNameAndErr("TaskFailed", s.Name, err)) + s.RootTask.Log(i18n.GetWithNameAndErr("SubTaskFailed", s.Name, err)) } else { - logger.Printf(i18n.GetWithName("TaskSuccess", s.Name)) - return true + s.RootTask.Log(i18n.GetWithName("SubTaskSuccess", s.Name)) + return nil } } @@ -88,29 +151,77 @@ func (s *SubTask) Execute(logger *log.Logger) bool { } time.Sleep(1 * time.Second) } - if s.Error != nil { - s.Error = fmt.Errorf(i18n.GetWithName("TaskFailed", s.Name)) - } - return false + return err +} + +func (t *Task) updateTask(task *model.Task) { + _ = t.taskRepo.Update(context.Background(), task) } func (t *Task) Execute() error { - t.Logger.Printf(i18n.GetWithName("TaskStart", t.Name)) + if err := t.taskRepo.Create(context.Background(), t.Task); err != nil { + return err + } var err error + t.Log(i18n.GetWithName("TaskStart", t.Name)) for _, subTask := range t.SubTasks { - if subTask.Execute(t.Logger) { + t.Task.CurrentStep = subTask.Name + t.updateTask(t.Task) + if err = subTask.Execute(); err == nil { if subTask.Rollback != nil { t.Rollbacks = append(t.Rollbacks, subTask.Rollback) } } else { - err = subTask.Error + t.Task.ErrorMsg = err.Error() + t.Task.Status = constant.StatusFailed for _, rollback := range t.Rollbacks { rollback() } + t.updateTask(t.Task) break } } - t.Logger.Printf(i18n.GetWithName("TaskEnd", t.Name)) + if t.Task.Status == constant.Running { + t.Task.Status = constant.StatusSuccess + t.Log(i18n.GetWithName("TaskSuccess", t.Name)) + } else { + t.Log(i18n.GetWithName("TaskFailed", t.Name)) + } + t.Log("[TASK-END]") + t.Task.EndAt = time.Now() + t.updateTask(t.Task) _ = t.logFile.Close() return err } + +func (t *Task) DeleteLogFile() { + _ = os.Remove(t.Task.LogFile) +} + +func (t *Task) LogWithStatus(msg string, err error) { + if err != nil { + t.Logger.Printf(i18n.GetWithNameAndErr("FailedStatus", msg, err)) + } else { + t.Logger.Printf(i18n.GetWithName("SuccessStatus", msg)) + } +} + +func (t *Task) Log(msg string) { + t.Logger.Printf(msg) +} + +func (t *Task) LogFailed(msg string) { + t.Logger.Printf(msg + i18n.GetMsgByKey("Failed")) +} + +func (t *Task) LogFailedWithErr(msg string, err error) { + t.Logger.Printf(fmt.Sprintf("%s %s : %s", msg, i18n.GetMsgByKey("Failed"), err.Error())) +} + +func (t *Task) LogSuccess(msg string) { + t.Logger.Printf(msg + i18n.GetMsgByKey("Success")) +} + +func (t *Task) LogStart(msg string) { + t.Logger.Printf(fmt.Sprintf("%s%s", i18n.GetMsgByKey("Start"), msg)) +} diff --git a/agent/constant/app.go b/agent/constant/app.go index 9a6f5c825..44f696cc2 100644 --- a/agent/constant/app.go +++ b/agent/constant/app.go @@ -14,6 +14,7 @@ const ( SyncSuccess = "SyncSuccess" Paused = "Paused" UpErr = "UpErr" + InstallErr = "InstallErr" ContainerPrefix = "1Panel-" diff --git a/agent/constant/common.go b/agent/constant/common.go index cca64e21a..e8f7a04d2 100644 --- a/agent/constant/common.go +++ b/agent/constant/common.go @@ -7,10 +7,15 @@ const ( SystemRestart = "systemRestart" - TypeWebsite = "website" - TypePhp = "php" - TypeSSL = "ssl" - TypeSystem = "system" + TypeWebsite = "website" + TypePhp = "php" + TypeSSL = "ssl" + TypeSystem = "system" + TypeTask = "task" + TypeImagePull = "image-pull" + TypeImagePush = "image-push" + TypeImageBuild = "image-build" + TypeComposeCreate = "compose-create" ) const ( diff --git a/agent/constant/task.go b/agent/constant/task.go new file mode 100644 index 000000000..67e84335b --- /dev/null +++ b/agent/constant/task.go @@ -0,0 +1,6 @@ +package constant + +const ( + TaskInstall = "installApp" + TaskCreateWebsite = "createWebsite" +) diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index 69b99e474..81a63bf5c 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -198,10 +198,36 @@ ErrXpackNotActive: "This section is a professional edition feature, please synch ErrXpackOutOfDate: "The current license has expired, please re-import the license in Panel Settings-License interface" #task -TaskStart: "{{.name}} started [START]" -TaskEnd: "{{.name}} ended [COMPLETED]" -TaskFailed: "{{.name}} failed: {{.err}}" -TaskTimeout: "{{.name}} timed out" -TaskSuccess: "{{.name}} succeeded" -TaskRetry: "Start {{.name}} retry" -SubTaskStart: "Start {{.name}}" +TaskStart: "{{.name}} Start [START]" +TaskEnd: "{{.name}} End [COMPLETED]" +TaskFailed: "{{.name}} Failed" +TaskTimeout: "{{.name}} Timeout" +TaskSuccess: "{{.name}} Success" +TaskRetry: "Starting {{.name}} Retry" +SubTaskSuccess: "{{ .name }} Success" +SubTaskFailed: "{{ .name }} Failed: {{ .err }}" +TaskInstall: "Install" +TaskUninstall: "Uninstall" +TaskCreate: "Create" +TaskDelete: "Delete" +TaskUpgrade: "Upgrade" +TaskUpdate: "Update" +TaskRestart: "Restart" +Website: "Website" +App: "App" +Runtime: "Runtime" +Database: "Database" +ConfigFTP: "Create FTP User {{ .name }}" +ConfigOpenresty: "Create Openresty Configuration File" +InstallAppSuccess: "App {{ .name }} Installed Successfully" +ConfigRuntime: "Configure Runtime" +ConfigApp: "Configure App" +SuccessStatus: "{{ .name }} Success" +FailedStatus: "{{ .name }} Failed {{.err}}" +HandleLink: "Handle App Link" +HandleDatabaseApp: "Handle App Parameters" +ExecShell: "Execute {{ .name }} Script" +PullImage: "Pull Image" +Start: "Start" +Run: "Run" + diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index 817f38e23..8dd9ba51f 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -202,8 +202,33 @@ ErrXpackOutOfDate: "當前許可證已過期,請重新在 面板設置-許可 #task TaskStart: "{{.name}} 開始 [START]" TaskEnd: "{{.name}} 結束 [COMPLETED]" -TaskFailed: "{{.name}} 失敗: {{.err}}" -TaskTimeout: "{{.name}} 逾時" +TaskFailed: "{{.name}} 失敗" +TaskTimeout: "{{.name}} 超時" TaskSuccess: "{{.name}} 成功" TaskRetry: "開始第 {{.name}} 次重試" -SubTaskStart: "開始 {{.name}}" +SubTaskSuccess: "{{ .name }} 成功" +SubTaskFailed: "{{ .name }} 失敗: {{ .err }}" +TaskInstall: "安裝" +TaskUninstall: "卸載" +TaskCreate: "創建" +TaskDelete: "刪除" +TaskUpgrade: "升級" +TaskUpdate: "更新" +TaskRestart: "重啟" +Website: "網站" +App: "應用" +Runtime: "運行環境" +Database: "數據庫" +ConfigFTP: "創建 FTP 用戶 {{ .name }}" +ConfigOpenresty: "創建 Openresty 配置文件" +InstallAppSuccess: "應用 {{ .name }} 安裝成功" +ConfigRuntime: "配置運行環境" +ConfigApp: "配置應用" +SuccessStatus: "{{ .name }} 成功" +FailedStatus: "{{ .name }} 失敗 {{.err}}" +HandleLink: "處理應用關聯" +HandleDatabaseApp: "處理應用參數" +ExecShell: "執行 {{ .name }} 腳本" +PullImage: "拉取鏡像" +Start: "開始" +Run: "啟動" diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index ff3bb1346..d68bb8c21 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -203,8 +203,33 @@ ErrXpackOutOfDate: "当前许可证已过期,请重新在 面板设置-许可 #task TaskStart: "{{.name}} 开始 [START]" TaskEnd: "{{.name}} 结束 [COMPLETED]" -TaskFailed: "{{.name}} 失败: {{.err}}" +TaskFailed: "{{.name}} 失败" TaskTimeout: "{{.name}} 超时" TaskSuccess: "{{.name}} 成功" TaskRetry: "开始第 {{.name}} 次重试" -SubTaskStart: "开始 {{.name}}" +SubTaskSuccess: "{{ .name }} 成功" +SubTaskFailed: "{{ .name }} 失败: {{ .err }}" +TaskInstall: "安装" +TaskUninstall: "卸载" +TaskCreate: "创建" +TaskDelete: "删除" +TaskUpgrade: "升级" +TaskUpdate: "更新" +TaskRestart: "重启" +Website: "网站" +App: "应用" +Runtime: "运行环境" +Database: "数据库" +ConfigFTP: "创建 FTP 用户 {{ .name }}" +ConfigOpenresty: "创建 Openresty 配置文件" +InstallAppSuccess: "应用 {{ .name }} 安装成功" +ConfigRuntime: "配置运行环境" +ConfigApp: "配置应用" +SuccessStatus: "{{ .name }} 成功" +FailedStatus: "{{ .name }} 失败 {{.err}}" +HandleLink: "处理应用关联" +HandleDatabaseApp: "处理应用参数" +ExecShell: "执行 {{ .name }} 脚本" +PullImage: "拉取镜像" +Start: "开始" +Run: "启动" \ No newline at end of file diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go index 1d05e8ab7..2fc823c6e 100644 --- a/agent/init/migration/migrate.go +++ b/agent/init/migration/migrate.go @@ -17,6 +17,7 @@ func Init() { migrations.InitDefaultGroup, migrations.InitDefaultCA, migrations.InitPHPExtensions, + migrations.AddTask, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index d7b95c332..aa7ba3413 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -253,3 +253,11 @@ var InitPHPExtensions = &gormigrate.Migration{ return nil }, } + +var AddTask = &gormigrate.Migration{ + ID: "20240724-add-task", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate( + &model.Task{}) + }, +} diff --git a/agent/server/server.go b/agent/server/server.go index 1872ffeba..76b2b48d5 100644 --- a/agent/server/server.go +++ b/agent/server/server.go @@ -6,7 +6,6 @@ import ( "os" "github.com/1Panel-dev/1Panel/agent/cron" - "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/i18n" "github.com/1Panel-dev/1Panel/agent/init/app" "github.com/1Panel-dev/1Panel/agent/init/business" @@ -40,25 +39,23 @@ func Start() { server := &http.Server{ Handler: rootRouter, } - if len(global.CurrentNode) == 0 || global.CurrentNode == "127.0.0.1" { - _ = os.Remove("/tmp/agent.sock") - listener, err := net.Listen("unix", "/tmp/agent.sock") - if err != nil { - panic(err) - } - _ = server.Serve(listener) - } else { - server.Addr = "0.0.0.0:9999" - type tcpKeepAliveListener struct { - *net.TCPListener - } - ln, err := net.Listen("tcp4", "0.0.0.0:9999") - if err != nil { - panic(err) - } - global.LOG.Info("listen at http://0.0.0.0:9999") - if err := server.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}); err != nil { - panic(err) - } + //ln, err := net.Listen("tcp4", "0.0.0.0:9998") + //if err != nil { + // panic(err) + //} + //type tcpKeepAliveListener struct { + // *net.TCPListener + //} + // + //global.LOG.Info("listen at http://0.0.0.0:9998") + //if err := server.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}); err != nil { + // panic(err) + //} + + os.Remove("/tmp/agent.sock") + listener, err := net.Listen("unix", "/tmp/agent.sock") + if err != nil { + panic(err) } + server.Serve(listener) } diff --git a/core/app/service/logs.go b/core/app/service/logs.go index 1cd6afa96..3b9dab6e5 100644 --- a/core/app/service/logs.go +++ b/core/app/service/logs.go @@ -26,7 +26,7 @@ type ILogService interface { CreateLoginLog(operation model.LoginLog) error PageLoginLog(search dto.SearchLgLogWithPage) (int64, interface{}, error) - CreateOperationLog(operation model.OperationLog) error + CreateOperationLog(operation *model.OperationLog) error PageOperationLog(search dto.SearchOpLogWithPage) (int64, interface{}, error) CleanLogs(logtype string) error @@ -92,8 +92,8 @@ func (u *LogService) PageLoginLog(req dto.SearchLgLogWithPage) (int64, interface return total, dtoOps, err } -func (u *LogService) CreateOperationLog(operation model.OperationLog) error { - return logRepo.CreateOperationLog(&operation) +func (u *LogService) CreateOperationLog(operation *model.OperationLog) error { + return logRepo.CreateOperationLog(operation) } func (u *LogService) PageOperationLog(req dto.SearchOpLogWithPage) (int64, interface{}, error) { diff --git a/core/middleware/operation.go b/core/middleware/operation.go index 535b24960..74f4f3269 100644 --- a/core/middleware/operation.go +++ b/core/middleware/operation.go @@ -27,7 +27,7 @@ func OperationLog() gin.HandlerFunc { } source := loadLogInfo(c.Request.URL.Path) - record := model.OperationLog{ + record := &model.OperationLog{ Source: source, IP: c.ClientIP(), Method: strings.ToLower(c.Request.Method), diff --git a/core/middleware/proxy.go b/core/middleware/proxy.go index 0f4cf4522..518713b96 100644 --- a/core/middleware/proxy.go +++ b/core/middleware/proxy.go @@ -22,7 +22,7 @@ func Proxy() gin.HandlerFunc { return } currentNode := c.Request.Header.Get("CurrentNode") - if currentNode == "127.0.0.1" { + if len(currentNode) == 0 || currentNode == "127.0.0.1" { sockPath := "/tmp/agent.sock" if _, err := os.Stat(sockPath); err != nil { helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrProxy, err) diff --git a/frontend/package.json b/frontend/package.json index a31a02763..625c38e40 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -49,6 +49,7 @@ "qs": "^6.12.1", "screenfull": "^6.0.2", "unplugin-vue-define-options": "^0.7.3", + "uuid": "^10.0.0", "vue": "^3.4.27", "vue-clipboard3": "^2.0.0", "vue-codemirror": "^6.1.1", diff --git a/frontend/src/api/interface/file.ts b/frontend/src/api/interface/file.ts index 7329c9667..0ad33750f 100644 --- a/frontend/src/api/interface/file.ts +++ b/frontend/src/api/interface/file.ts @@ -174,6 +174,7 @@ export namespace File { name?: string; page: number; pageSize: number; + taskID?: string; } export interface Favorite extends CommonModel { diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index 797b9b885..fddf41a7a 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -79,6 +79,7 @@ export namespace Website { proxyType: string; ftpUser: string; ftpPassword: string; + taskID: string; } export interface WebSiteUpdateReq { diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts index f07984b98..de1251059 100644 --- a/frontend/src/api/modules/website.ts +++ b/frontend/src/api/modules/website.ts @@ -19,7 +19,7 @@ export const CreateWebsite = (req: Website.WebSiteCreateReq) => { if (request.ftpPassword) { request.ftpPassword = Base64.encode(request.ftpPassword); } - return http.post(`/websites`, request); + return http.post(`/websites`, request, TimeoutEnum.T_10M); }; export const OpWebsite = (req: Website.WebSiteOp) => { diff --git a/frontend/src/components/log-file/index.vue b/frontend/src/components/log-file/index.vue index 14f3161ad..b2ccf3e6c 100644 --- a/frontend/src/components/log-file/index.vue +++ b/frontend/src/components/log-file/index.vue @@ -246,7 +246,7 @@ const initCodemirror = () => { } }); let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement; - hljsDom.style['min-height'] = '500px'; + hljsDom.style['min-height'] = '100px'; } }); }; diff --git a/frontend/src/components/task-log/index.vue b/frontend/src/components/task-log/index.vue new file mode 100644 index 000000000..1e3d56dab --- /dev/null +++ b/frontend/src/components/task-log/index.vue @@ -0,0 +1,212 @@ + + + diff --git a/frontend/src/store/modules/global.ts b/frontend/src/store/modules/global.ts index a4550f534..efded6287 100644 --- a/frontend/src/store/modules/global.ts +++ b/frontend/src/store/modules/global.ts @@ -42,7 +42,7 @@ const GlobalStore = defineStore({ errStatus: '', - currentNode: '', + currentNode: '127.0.0.1', }), getters: { isDarkTheme: (state) => diff --git a/frontend/src/views/host/firewall/forward/operate/index.vue b/frontend/src/views/host/firewall/forward/operate/index.vue index 79d971aae..9b1fc9a2f 100644 --- a/frontend/src/views/host/firewall/forward/operate/index.vue +++ b/frontend/src/views/host/firewall/forward/operate/index.vue @@ -40,7 +40,6 @@ import { reactive, ref } from 'vue'; import { Rules } from '@/global/form-rules'; import i18n from '@/lang'; import { ElForm } from 'element-plus'; -import DrawerHeader from '@/components/drawer-header/index.vue'; import { MsgSuccess } from '@/utils/message'; import { Host } from '@/api/interface/host'; import { operateForwardRule } from '@/api/modules/host'; diff --git a/frontend/src/views/setting/backup-account/kodo/index.vue b/frontend/src/views/setting/backup-account/kodo/index.vue index 29a431d2e..771f8c478 100644 --- a/frontend/src/views/setting/backup-account/kodo/index.vue +++ b/frontend/src/views/setting/backup-account/kodo/index.vue @@ -29,7 +29,7 @@ {{ $t('commons.rule.requiredSelect') }} - +