From 65e69278499d49f99f336779775c4f5846459b15 Mon Sep 17 00:00:00 2001 From: ssongliu Date: Mon, 25 Nov 2024 21:56:35 +0800 Subject: [PATCH] feat: Add supports of task display for node upgrade --- agent/app/service/upgrade.go | 4 +- agent/init/hook/hook.go | 4 +- agent/init/migration/migrate.go | 2 +- agent/init/migration/migrations/init.go | 19 +- agent/router/common.go | 1 - agent/router/ro_setting.go | 3 + agent/router/ro_upgrade.go | 17 -- core/app/dto/task.go | 13 ++ core/app/model/task.go | 18 ++ core/app/model/upgrade_log.go | 9 + core/app/repo/common.go | 6 + core/app/repo/task.go | 90 +++++++++ core/app/repo/upgrade_log.go | 93 +++++++++ core/app/service/entry.go | 2 + core/app/service/task.go | 42 ++++ core/app/task/task.go | 253 ++++++++++++++++++++++++ core/constant/status.go | 1 + core/global/global.go | 1 + core/i18n/i18n.go | 48 ++++- core/i18n/lang/en.yaml | 39 ++++ core/i18n/lang/zh-Hant.yaml | 41 +++- core/i18n/lang/zh.yaml | 42 +++- core/init/db/db.go | 64 ++++-- core/init/migration/migrate.go | 1 + core/init/migration/migrations/init.go | 12 +- frontend/src/lang/modules/en.ts | 2 + frontend/src/lang/modules/tw.ts | 1 + frontend/src/lang/modules/zh.ts | 1 + 28 files changed, 768 insertions(+), 61 deletions(-) delete mode 100644 agent/router/ro_upgrade.go create mode 100644 core/app/dto/task.go create mode 100644 core/app/model/task.go create mode 100644 core/app/model/upgrade_log.go create mode 100644 core/app/repo/task.go create mode 100644 core/app/repo/upgrade_log.go create mode 100644 core/app/service/task.go create mode 100644 core/app/task/task.go diff --git a/agent/app/service/upgrade.go b/agent/app/service/upgrade.go index 7db74c0fe3fd9..da3dc2cbe6648 100644 --- a/agent/app/service/upgrade.go +++ b/agent/app/service/upgrade.go @@ -39,7 +39,7 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error { if err := u.handleBackup(backupDir, fileOP); err != nil { return err } - if err := fileOP.CopyFile(path.Join(req.UpgradePath, "1panel-agent.service"), "/etc/systemd/system/1panel-agent.service"); err != nil { + if err := fileOP.CopyFile(path.Join(req.UpgradePath, "1panel-agent.service"), "/etc/systemd/system"); err != nil { _ = u.handleRollback(backupDir, 1, fileOP) global.LOG.Errorf("handle upgrade 1panel service file failed, err: %v", err) return err @@ -83,7 +83,7 @@ func (u *UpgradeService) handleBackup(backupDir string, fileOP files.FileOp) err func (u *UpgradeService) handleRollback(backupDir string, errStep int, fileOP files.FileOp) error { if errStep == 1 { - if err := fileOP.CopyFile(path.Join(backupDir, "1panel-agent.service"), "/etc/systemd/system/1panel-agent.service"); err != nil { + if err := fileOP.CopyFile(path.Join(backupDir, "1panel-agent.service"), "/etc/systemd/system"); err != nil { global.LOG.Errorf("handle recover 1panel service file failed, err: %v", err) return err } diff --git a/agent/init/hook/hook.go b/agent/init/hook/hook.go index df16a64049ad4..bf202d53664fb 100644 --- a/agent/init/hook/hook.go +++ b/agent/init/hook/hook.go @@ -31,9 +31,9 @@ func Init() { global.LOG.Fatalf("load base dir before start failed, err: %v", err) } global.CONF.System.BaseDir = baseDir.Value - version, err := settingRepo.Get(settingRepo.WithByKey("Version")) + version, err := settingRepo.Get(settingRepo.WithByKey("SystemVersion")) if err != nil { - global.LOG.Fatalf("load base dir before start failed, err: %v", err) + global.LOG.Fatalf("load system version before start failed, err: %v", err) } global.CONF.System.Version = version.Value diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go index 328fc7812c0b7..95f44a6c13e00 100644 --- a/agent/init/migration/migrate.go +++ b/agent/init/migration/migrate.go @@ -15,7 +15,6 @@ func Init() { migrations.InitImageRepo, migrations.InitDefaultCA, migrations.InitPHPExtensions, - migrations.AddTask, migrations.UpdateWebsite, migrations.UpdateWebsiteDomain, migrations.UpdateApp, @@ -23,6 +22,7 @@ func Init() { migrations.UpdateAppInstall, migrations.UpdateSnapshot, migrations.UpdateCronjob, + migrations.InitBaseDir, }) 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 e6b0a73eac02d..6be726073c37d 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -53,7 +53,6 @@ var AddTable = &gormigrate.Migration{ &model.WebsiteDnsAccount{}, &model.WebsiteDomain{}, &model.WebsiteSSL{}, - &model.Task{}, ) }, } @@ -217,14 +216,6 @@ var InitPHPExtensions = &gormigrate.Migration{ }, } -var AddTask = &gormigrate.Migration{ - ID: "20240802-add-task", - Migrate: func(tx *gorm.DB) error { - return tx.AutoMigrate( - &model.Task{}) - }, -} - var UpdateWebsite = &gormigrate.Migration{ ID: "20240812-update-website", Migrate: func(tx *gorm.DB) error { @@ -279,3 +270,13 @@ var UpdateCronjob = &gormigrate.Migration{ return tx.AutoMigrate(&model.Cronjob{}, &model.JobRecords{}) }, } + +var InitBaseDir = &gormigrate.Migration{ + ID: "20241122-init-setting", + Migrate: func(tx *gorm.DB) error { + if err := tx.Create(&model.Setting{Key: "BaseDir", Value: global.CONF.System.BaseDir}).Error; err != nil { + return err + } + return nil + }, +} diff --git a/agent/router/common.go b/agent/router/common.go index f729001cc15e3..f54a07ddc8bf9 100644 --- a/agent/router/common.go +++ b/agent/router/common.go @@ -21,6 +21,5 @@ func commonGroups() []CommonRouter { &RuntimeRouter{}, &ProcessRouter{}, &WebsiteCARouter{}, - &UpgradeRouter{}, } } diff --git a/agent/router/ro_setting.go b/agent/router/ro_setting.go index 1247b3dcc5415..d2aad8543e566 100644 --- a/agent/router/ro_setting.go +++ b/agent/router/ro_setting.go @@ -27,5 +27,8 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) { settingRouter.POST("/snapshot/description/update", baseApi.UpdateSnapDescription) settingRouter.GET("/basedir", baseApi.LoadBaseDir) + + settingRouter.POST("/upgrade", baseApi.Upgrade) + settingRouter.POST("/rollback", baseApi.Rollback) } } diff --git a/agent/router/ro_upgrade.go b/agent/router/ro_upgrade.go deleted file mode 100644 index b5b378d361fd3..0000000000000 --- a/agent/router/ro_upgrade.go +++ /dev/null @@ -1,17 +0,0 @@ -package router - -import ( - v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" - "github.com/gin-gonic/gin" -) - -type UpgradeRouter struct{} - -func (s *UpgradeRouter) InitRouter(Router *gin.RouterGroup) { - upgradeRouter := Router.Group("upgrades") - baseApi := v2.ApiGroupApp.BaseApi - { - upgradeRouter.POST("/upgrade", baseApi.Upgrade) - upgradeRouter.POST("/rollback", baseApi.Rollback) - } -} diff --git a/core/app/dto/task.go b/core/app/dto/task.go new file mode 100644 index 0000000000000..f88baf6602255 --- /dev/null +++ b/core/app/dto/task.go @@ -0,0 +1,13 @@ +package dto + +import "github.com/1Panel-dev/1Panel/core/app/model" + +type SearchTaskLogReq struct { + Status string `json:"status"` + Type string `json:"type"` + PageInfo +} + +type TaskDTO struct { + model.Task +} diff --git a/core/app/model/task.go b/core/app/model/task.go new file mode 100644 index 0000000000000..46d003237b899 --- /dev/null +++ b/core/app/model/task.go @@ -0,0 +1,18 @@ +package model + +import "time" + +type Task struct { + ID string `gorm:"primarykey;" json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Operate string `json:"operate"` + 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/core/app/model/upgrade_log.go b/core/app/model/upgrade_log.go new file mode 100644 index 0000000000000..11230b9a6665f --- /dev/null +++ b/core/app/model/upgrade_log.go @@ -0,0 +1,9 @@ +package model + +type UpgradeLog struct { + BaseModel + NodeID uint `json:"nodeID"` + OldVersion string `json:"oldVersion"` + NewVersion string `json:"newVersion"` + BackupFile string `json:"backupFile"` +} diff --git a/core/app/repo/common.go b/core/app/repo/common.go index 048c2b3ed9e89..d1b34c78f6510 100644 --- a/core/app/repo/common.go +++ b/core/app/repo/common.go @@ -17,6 +17,7 @@ type ICommonRepo interface { WithByType(ty string) DBOption WithByKey(key string) DBOption WithOrderBy(orderStr string) DBOption + WithByStatus(status string) DBOption WithOrderRuleBy(orderBy, order string) DBOption } @@ -66,6 +67,11 @@ func (c *CommonRepo) WithByKey(key string) DBOption { return g.Where("key = ?", key) } } +func (c *CommonRepo) WithByStatus(status string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("status = ?", status) + } +} func (c *CommonRepo) WithOrderBy(orderStr string) DBOption { return func(g *gorm.DB) *gorm.DB { return g.Order(orderStr) diff --git a/core/app/repo/task.go b/core/app/repo/task.go new file mode 100644 index 0000000000000..0d7edcef585cb --- /dev/null +++ b/core/app/repo/task.go @@ -0,0 +1,90 @@ +package repo + +import ( + "context" + + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/global" + "gorm.io/gorm" +) + +type TaskRepo struct { +} + +type ITaskRepo interface { + Save(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 + WithResourceID(id uint) DBOption + WithOperate(taskOperate string) DBOption +} + +func NewITaskRepo() ITaskRepo { + return &TaskRepo{} +} + +func getTaskDb(opts ...DBOption) *gorm.DB { + db := global.TaskDB + for _, opt := range opts { + db = opt(db) + } + return db +} + +func getTaskTx(ctx context.Context, opts ...DBOption) *gorm.DB { + tx, ok := ctx.Value("db").(*gorm.DB) + if ok { + for _, opt := range opts { + tx = opt(tx) + } + return tx + } + return getTaskDb(opts...) +} + +func (t TaskRepo) WithByID(id string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id = ?", id) + } +} + +func (t TaskRepo) WithOperate(taskOperate string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("operate = ?", taskOperate) + } +} + +func (t TaskRepo) WithResourceID(id uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("resource_id = ?", id) + } +} + +func (t TaskRepo) Save(ctx context.Context, task *model.Task) error { + return getTaskTx(ctx).Save(&task).Error +} + +func (t TaskRepo) GetFirst(opts ...DBOption) (model.Task, error) { + var task model.Task + db := getTaskDb(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 := getTaskDb(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 getTaskTx(ctx).Save(&task).Error +} diff --git a/core/app/repo/upgrade_log.go b/core/app/repo/upgrade_log.go new file mode 100644 index 0000000000000..1a9d27b5df2cb --- /dev/null +++ b/core/app/repo/upgrade_log.go @@ -0,0 +1,93 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/global" + "gorm.io/gorm" +) + +type UpgradeLogRepo struct{} + +type IUpgradeLogRepo interface { + Get(opts ...DBOption) (model.UpgradeLog, error) + List(opts ...DBOption) ([]model.UpgradeLog, error) + Create(log *model.UpgradeLog) error + Page(limit, offset int, opts ...DBOption) (int64, []model.UpgradeLog, error) + Delete(opts ...DBOption) error + + WithByNodeID(nodeID uint) DBOption + WithByIDs(ids []uint) DBOption + WithByID(id uint) DBOption +} + +func NewIUpgradeLogRepo() IUpgradeLogRepo { + return &UpgradeLogRepo{} +} + +func (u *UpgradeLogRepo) Get(opts ...DBOption) (model.UpgradeLog, error) { + var log model.UpgradeLog + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&log).Error + return log, err +} + +func (u *UpgradeLogRepo) List(opts ...DBOption) ([]model.UpgradeLog, error) { + var logs []model.UpgradeLog + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&logs).Error + return logs, err +} + +func (u *UpgradeLogRepo) Clean() error { + return global.DB.Exec("delete from upgrade_logs;").Error +} + +func (u *UpgradeLogRepo) Create(log *model.UpgradeLog) error { + return global.DB.Create(log).Error +} + +func (u *UpgradeLogRepo) Save(log *model.UpgradeLog) error { + return global.DB.Save(log).Error +} + +func (u *UpgradeLogRepo) Delete(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.UpgradeLog{}).Error +} + +func (u *UpgradeLogRepo) Page(page, size int, opts ...DBOption) (int64, []model.UpgradeLog, error) { + var ops []model.UpgradeLog + db := global.DB.Model(&model.UpgradeLog{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&ops).Error + return count, ops, err +} + +func (c *UpgradeLogRepo) WithByNodeID(nodeID uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("node_id = ?", nodeID) + } +} +func (c *UpgradeLogRepo) WithByID(id uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id = ?", id) + } +} +func (c *UpgradeLogRepo) WithByIDs(ids []uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id in (?)", ids) + } +} diff --git a/core/app/service/entry.go b/core/app/service/entry.go index 5a27332c78880..6ab8eea8cc340 100644 --- a/core/app/service/entry.go +++ b/core/app/service/entry.go @@ -11,4 +11,6 @@ var ( logRepo = repo.NewILogRepo() groupRepo = repo.NewIGroupRepo() launcherRepo = repo.NewILauncherRepo() + + taskRepo = repo.NewITaskRepo() ) diff --git a/core/app/service/task.go b/core/app/service/task.go new file mode 100644 index 0000000000000..1b291cf80dcd7 --- /dev/null +++ b/core/app/service/task.go @@ -0,0 +1,42 @@ +package service + +import ( + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/repo" +) + +type TaskLogService struct{} + +type ITaskLogService interface { + Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, error) +} + +func NewITaskService() ITaskLogService { + return &TaskLogService{} +} + +func (u *TaskLogService) Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, error) { + opts := []repo.DBOption{ + commonRepo.WithOrderBy("created_at desc"), + } + if req.Status != "" { + opts = append(opts, commonRepo.WithByStatus(req.Status)) + } + if req.Type != "" { + opts = append(opts, commonRepo.WithByType(req.Type)) + } + + total, tasks, err := taskRepo.Page( + req.Page, + req.PageSize, + opts..., + ) + var items []dto.TaskDTO + for _, t := range tasks { + item := dto.TaskDTO{ + Task: t, + } + items = append(items, item) + } + return total, items, err +} diff --git a/core/app/task/task.go b/core/app/task/task.go new file mode 100644 index 0000000000000..dd6fbb136371c --- /dev/null +++ b/core/app/task/task.go @@ -0,0 +1,253 @@ +package task + +import ( + "context" + "fmt" + "log" + "os" + "path" + "strconv" + "time" + + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/i18n" + "github.com/google/uuid" +) + +type ActionFunc func(*Task) error +type RollbackFunc func(*Task) + +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 + StepAlias string + Retry int + Timeout time.Duration + Action ActionFunc + Rollback RollbackFunc + Error error + IgnoreErr bool +} + +const ( + TaskUpgrade = "TaskUpgrade" + TaskAddNode = "TaskAddNode" +) + +const ( + TaskScopeSystem = "System" +) + +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, resourceID uint) (*Task, error) { + return NewTask(GetTaskName(resourceName, operate, scope), operate, scope, taskID, resourceID) +} + +func NewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, error) { + if taskID == "" { + taskID = uuid.New().String() + } + logItem := path.Join(global.CONF.System.BaseDir, "1panel/log") + logDir := path.Join(logItem, taskScope) + 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(logItem, taskScope, 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) + taskModel := &model.Task{ + ID: taskID, + Name: name, + Type: taskScope, + LogFile: logPath, + Status: constant.StatusRunning, + ResourceID: resourceID, + Operate: operate, + } + 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{RootTask: t, Name: name, Retry: 0, Timeout: 10 * time.Minute, Action: action, Rollback: rollback} + t.SubTasks = append(t.SubTasks, subTask) +} + +func (t *Task) AddSubTaskWithAlias(key string, action ActionFunc, rollback RollbackFunc) { + subTask := &SubTask{RootTask: t, Name: i18n.GetMsgByKey(key), StepAlias: key, 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{RootTask: t, Name: name, Retry: retry, Timeout: timeout, Action: action, Rollback: rollback} + t.SubTasks = append(t.SubTasks, subTask) +} + +func (t *Task) AddSubTaskWithIgnoreErr(name string, action ActionFunc) { + subTask := &SubTask{RootTask: t, Name: name, Retry: 0, Timeout: 10 * time.Minute, Action: action, Rollback: nil, IgnoreErr: true} + t.SubTasks = append(t.SubTasks, subTask) +} + +func (s *SubTask) Execute() error { + subTaskName := s.Name + if s.Name == "" { + subTaskName = i18n.GetMsgByKey("SubTask") + } + var err error + for i := 0; i < s.Retry+1; i++ { + if i > 0 { + 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(s.RootTask) + }() + + select { + case <-ctx.Done(): + s.RootTask.Log(i18n.GetWithName("TaskTimeout", subTaskName)) + case err = <-done: + if err != nil { + s.RootTask.Log(i18n.GetWithNameAndErr("SubTaskFailed", subTaskName, err)) + } else { + s.RootTask.Log(i18n.GetWithName("SubTaskSuccess", subTaskName)) + return nil + } + } + + if i == s.Retry { + if s.Rollback != nil { + s.Rollback(s.RootTask) + } + } + time.Sleep(1 * time.Second) + } + return err +} + +func (t *Task) updateTask(task *model.Task) { + _ = t.taskRepo.Update(context.Background(), task) +} + +func (t *Task) Execute() error { + if err := t.taskRepo.Save(context.Background(), t.Task); err != nil { + return err + } + var err error + t.Log(i18n.GetWithName("TaskStart", t.Name)) + for _, subTask := range t.SubTasks { + t.Task.CurrentStep = subTask.StepAlias + t.updateTask(t.Task) + if err = subTask.Execute(); err == nil { + if subTask.Rollback != nil { + t.Rollbacks = append(t.Rollbacks, subTask.Rollback) + } + } else { + if subTask.IgnoreErr { + err = nil + continue + } + t.Task.ErrorMsg = err.Error() + t.Task.Status = constant.StatusFailed + for _, rollback := range t.Rollbacks { + rollback(t) + } + t.updateTask(t.Task) + break + } + } + if t.Task.Status == constant.StatusRunning { + 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) Logf(format string, v ...any) { + t.Logger.Printf(format, v...) +} + +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) LogSuccessF(format string, v ...any) { + t.Logger.Printf(fmt.Sprintf(format, v...) + i18n.GetMsgByKey("Success")) +} + +func (t *Task) LogStart(msg string) { + t.Logger.Printf(fmt.Sprintf("%s%s", i18n.GetMsgByKey("Start"), msg)) +} + +func (t *Task) LogWithOps(operate, msg string) { + t.Logger.Printf("%s%s", i18n.GetMsgByKey(operate), msg) +} + +func (t *Task) LogSuccessWithOps(operate, msg string) { + t.Logger.Printf("%s%s%s", i18n.GetMsgByKey(operate), msg, i18n.GetMsgByKey("Success")) +} + +func (t *Task) LogFailedWithOps(operate, msg string, err error) { + t.Logger.Printf("%s%s%s : %s ", i18n.GetMsgByKey(operate), msg, i18n.GetMsgByKey("Failed"), err.Error()) +} diff --git a/core/constant/status.go b/core/constant/status.go index 9a8d9c7318e8e..3df82969e80e3 100644 --- a/core/constant/status.go +++ b/core/constant/status.go @@ -13,4 +13,5 @@ const ( StatusHealthy = "healthy" StatusUnhealthy = "unhealthy" StatusUpgrading = "upgrading" + StatusRunning = "running" ) diff --git a/core/global/global.go b/core/global/global.go index 36765352d14de..73560d5cdd085 100644 --- a/core/global/global.go +++ b/core/global/global.go @@ -15,6 +15,7 @@ import ( var ( DB *gorm.DB + TaskDB *gorm.DB LOG *logrus.Logger CONF configs.ServerConfig VALID *validator.Validate diff --git a/core/i18n/i18n.go b/core/i18n/i18n.go index db686728533da..dfd7d20bb2bd4 100644 --- a/core/i18n/i18n.go +++ b/core/i18n/i18n.go @@ -32,25 +32,20 @@ func GetMsgWithMap(key string, maps map[string]interface{}) string { } } -func GetMsgWithName(key string, name string, err error) string { +func GetMsgWithDetail(key string, detail string) string { var ( content string dataMap = make(map[string]interface{}) ) - dataMap["name"] = name - if err != nil { - dataMap["err"] = err.Error() - } + dataMap["detail"] = detail content, _ = global.I18n.Localize(&i18n.LocalizeConfig{ MessageID: key, TemplateData: dataMap, }) - content = strings.ReplaceAll(content, "", "") - if content == "" { - return key - } else { + if content != "" { return content } + return key } func GetErrMsg(key string, maps map[string]interface{}) string { @@ -75,6 +70,41 @@ func GetMsgByKey(key string) string { return content } +func Get(key string) string { + content, _ := global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + }) + if content != "" { + return content + } + return key +} + +func GetWithName(key string, name string) string { + var ( + dataMap = make(map[string]interface{}) + ) + dataMap["name"] = name + content, _ := global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + TemplateData: dataMap, + }) + return content +} + +func GetWithNameAndErr(key string, name string, err error) string { + var ( + dataMap = make(map[string]interface{}) + ) + dataMap["name"] = name + dataMap["err"] = err.Error() + content, _ := global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + TemplateData: dataMap, + }) + return content +} + //go:embed lang/* var fs embed.FS var bundle *i18n.Bundle diff --git a/core/i18n/lang/en.yaml b/core/i18n/lang/en.yaml index 7b7ed8d257df2..3aa83105ae9bc 100644 --- a/core/i18n/lang/en.yaml +++ b/core/i18n/lang/en.yaml @@ -29,3 +29,42 @@ ErrNoSuchHost: "Network connection failed" ErrBackupInUsed: "The backup account is currently in use in a scheduled task and cannot be deleted." ErrBackupCheck: "Backup account test connection failed {{.err}}" ErrBackupLocalDelete: "Deleting local server backup accounts is not currently supported." + +#task +TaskStart: "{{.name}} Task Start [START]" +TaskEnd: "{{.name}} Task End [COMPLETED]" +TaskFailed: "{{.name}} Task Failed" +TaskTimeout: "{{.name}} Timeout" +TaskSuccess: "{{.name}} Task Successful" +TaskRetry: "Starting the {{.name}} retry" +SubTaskSuccess: "{{ .name }} Successful" +SubTaskFailed: "{{ .name }} Failed: {{ .err }}" +TaskInstall: "Install" +TaskUninstall: "Uninstall" +TaskCreate: "Create" +TaskDelete: "Delete" +TaskUpgrade: "Upgrade" +TaskUpdate: "Update" +TaskRestart: "Restart" +TaskRollback: "Rollback" +SuccessStatus: "{{ .name }} Successful" +FailedStatus: "{{ .name }} Failed {{.err}}" +PullImage: "Pull Image" +Start: "Start" +Run: "Launch" +Stop: "Stop" +SubTask: "Subtask" + +#upgrade node +NodeUpgrade: "Upgrade Node {name}" +NewSSHClient: "Initialize SSH Connection" +BackupBeforeUpgrade: "Backup Data Before Upgrade" +UploadUpgradeFile: "Distribute Upgrade Required Files" +RestartAfterUpgrade: "Start Service After Upgrade" + +#add node +TaskAddNode: "Add Node" +GenerateSSLInfo: "Generate Node SSL Information" +MakeAgentPackage: "Generate Node Installation Package" +SendAgent: "Distribute Node Installation Package" +StartService: "Start Service" \ No newline at end of file diff --git a/core/i18n/lang/zh-Hant.yaml b/core/i18n/lang/zh-Hant.yaml index e1c529cd57632..85e257fb17837 100644 --- a/core/i18n/lang/zh-Hant.yaml +++ b/core/i18n/lang/zh-Hant.yaml @@ -28,4 +28,43 @@ ErrNoSuchHost: "網路連接失敗" #backup ErrBackupInUsed: "該備份帳號已在計劃任務中使用,無法刪除" ErrBackupCheck: "備份帳號測試連接失敗 {{.err}}" -ErrBackupLocalDelete: "暫不支持刪除本地伺服器備份帳號" \ No newline at end of file +ErrBackupLocalDelete: "暫不支持刪除本地伺服器備份帳號" + +#task +TaskStart: "{{.name}} 任務開始 [START]" +TaskEnd: "{{.name}} 任務結束 [COMPLETED]" +TaskFailed: "{{.name}} 任務失敗" +TaskTimeout: "{{.name}} 超時" +TaskSuccess: "{{.name}} 任務成功" +TaskRetry: "開始第 {{.name}} 次重試" +SubTaskSuccess: "{{ .name }} 成功" +SubTaskFailed: "{{ .name }} 失敗: {{ .err }}" +TaskInstall: "安裝" +TaskUninstall: "卸載" +TaskCreate: "創建" +TaskDelete: "刪除" +TaskUpgrade: "升級" +TaskUpdate: "更新" +TaskRestart: "重啟" +TaskRollback: "回滾" +SuccessStatus: "{{ .name }} 成功" +FailedStatus: "{{ .name }} 失敗 {{.err}}" +PullImage: "拉取鏡像" +Start: "開始" +Run: "啟動" +Stop: "停止" +SubTask: "子任務" + +#node upgrade +NodeUpgrade: "升級節點 {name}" +NewSSHClient: "初始化 SSH 連接" +BackupBeforeUpgrade: "升級前備份數據" +UploadUpgradeFile: "下發升級所需文件" +RestartAfterUpgrade: "升級後啟動服務" + +#node create +TaskAddNode: "添加節點" +GenerateSSLInfo: "生成節點 SSL 信息" +MakeAgentPackage: "生成節點安裝包" +SendAgent: "下發節點安裝包" +StartService: "啟動服務" \ No newline at end of file diff --git a/core/i18n/lang/zh.yaml b/core/i18n/lang/zh.yaml index 273ea35f0f3c3..fc66b1b288fc6 100644 --- a/core/i18n/lang/zh.yaml +++ b/core/i18n/lang/zh.yaml @@ -30,4 +30,44 @@ ErrNoSuchHost: "网络连接失败" #backup ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除" ErrBackupCheck: "备份账号测试连接失败 {{ .err}}" -ErrBackupLocalDelete: "暂不支持删除本地服务器备份账号" \ No newline at end of file +ErrBackupLocalDelete: "暂不支持删除本地服务器备份账号" + +#task +#task +TaskStart: "{{.name}} 任务开始 [START]" +TaskEnd: "{{.name}} 任务结束 [COMPLETED]" +TaskFailed: "{{.name}} 任务失败" +TaskTimeout: "{{.name}} 超时" +TaskSuccess: "{{.name}} 任务成功" +TaskRetry: "开始第 {{.name}} 次重试" +SubTaskSuccess: "{{ .name }} 成功" +SubTaskFailed: "{{ .name }} 失败: {{ .err }}" +TaskInstall: "安装" +TaskUninstall: "卸载" +TaskCreate: "创建" +TaskDelete: "删除" +TaskUpgrade: "升级" +TaskUpdate: "更新" +TaskRestart: "重启" +TaskRollback: "回滚" +SuccessStatus: "{{ .name }} 成功" +FailedStatus: "{{ .name }} 失败 {{.err}}" +PullImage: "拉取镜像" +Start: "开始" +Run: "启动" +Stop: "停止" +SubTask: "子任务" + +#upgrade node +NodeUpgrade: "升级节点 {name}" +NewSSHClient: "初始化 SSH 连接" +BackupBeforeUpgrade: "升级前备份数据" +UploadUpgradeFile: "下发升级所需文件" +RestartAfterUpgrade: "升级后启动服务" + +#add node +TaskAddNode: "添加节点" +GenerateSSLInfo: "生成节点 SSL 信息" +MakeAgentPackage: "生成节点安装包" +SendAgent: "下发节点安装包" +StartService: "启动服务" \ No newline at end of file diff --git a/core/init/db/db.go b/core/init/db/db.go index f69d2ad839910..2432fa18a8c73 100644 --- a/core/init/db/db.go +++ b/core/init/db/db.go @@ -14,6 +14,11 @@ import ( ) func Init() { + initDB() + initTaskDB() +} + +func initDB() { dbPath := path.Join(global.CONF.System.BaseDir, "1panel/db") if _, err := os.Stat(dbPath); err != nil { if err := os.MkdirAll(dbPath, os.ModePerm); err != nil { @@ -29,31 +34,56 @@ func Init() { _ = f.Close() } - newLogger := logger.New( - log.New(os.Stdout, "\r\n", log.LstdFlags), - logger.Config{ - SlowThreshold: time.Second, - LogLevel: logger.Silent, - IgnoreRecordNotFoundError: true, - Colorful: false, - }, - ) + db, err := NewDBWithPath(fullPath) + if err != nil { + panic(err) + } - db, err := gorm.Open(sqlite.Open(fullPath), &gorm.Config{ - DisableForeignKeyConstraintWhenMigrating: true, - Logger: newLogger, - }) + global.DB = db + global.LOG.Info("init db successfully") +} + +func initTaskDB() { + fullPath := path.Join(global.CONF.System.BaseDir, "1panel/db/task.db") + if _, err := os.Stat(fullPath); err != nil { + f, err := os.Create(fullPath) + if err != nil { + panic(fmt.Errorf("init task db file failed, err: %v", err)) + } + _ = f.Close() + } + + db, err := NewDBWithPath(fullPath) if err != nil { panic(err) } + + global.TaskDB = db + global.LOG.Info("init task db successfully") +} + +func NewDBWithPath(dbPath string) (*gorm.DB, error) { + db, _ := gorm.Open(sqlite.Open(dbPath), &gorm.Config{ + DisableForeignKeyConstraintWhenMigrating: true, + Logger: getLogger(), + }) sqlDB, dbError := db.DB() if dbError != nil { - panic(dbError) + return nil, dbError } sqlDB.SetConnMaxIdleTime(10) sqlDB.SetMaxOpenConns(100) sqlDB.SetConnMaxLifetime(time.Hour) - - global.DB = db - global.LOG.Info("init db successfully") + return db, nil +} +func getLogger() logger.Interface { + return logger.New( + log.New(os.Stdout, "\r\n", log.LstdFlags), + logger.Config{ + SlowThreshold: time.Second, + LogLevel: logger.Silent, + IgnoreRecordNotFoundError: true, + Colorful: false, + }, + ) } diff --git a/core/init/migration/migrate.go b/core/init/migration/migrate.go index 060fbd27b85bc..162961815d3ba 100644 --- a/core/init/migration/migrate.go +++ b/core/init/migration/migrate.go @@ -18,6 +18,7 @@ func Init() { migrations.InitAppLauncher, migrations.InitBackup, migrations.InitGoogle, + migrations.AddTaskDB, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/core/init/migration/migrations/init.go b/core/init/migration/migrations/init.go index 8a8288f02b012..59ad67c62515b 100644 --- a/core/init/migration/migrations/init.go +++ b/core/init/migration/migrations/init.go @@ -15,7 +15,7 @@ import ( ) var AddTable = &gormigrate.Migration{ - ID: "20240819-add-table", + ID: "20241224-add-table", Migrate: func(tx *gorm.DB) error { return tx.AutoMigrate( &model.OperationLog{}, @@ -25,6 +25,7 @@ var AddTable = &gormigrate.Migration{ &model.Group{}, &model.Host{}, &model.Command{}, + &model.UpgradeLog{}, ) }, } @@ -260,3 +261,12 @@ var InitGoogle = &gormigrate.Migration{ return nil }, } + +var AddTaskDB = &gormigrate.Migration{ + ID: "20241125-add-task-table", + Migrate: func(tx *gorm.DB) error { + return global.TaskDB.AutoMigrate( + &model.Task{}, + ) + }, +} diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index b9b01e58d98cb..0bd8bb3e21d38 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1689,6 +1689,8 @@ const message = { rollbackHelper: 'Rolling back this recovery will replace all files from this recovery, and may require restarting Docker and 1Panel services. Do you want to continue?', + upgrading: 'upgrading, please wait...', + upgradeRecord: 'Upgrade Record', upgradeHelper: 'The upgrade requires restarting the 1Panel service. Do you want to continue?', noUpgrade: 'It is currently the latest version', versionHelper: diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 412fba75a178a..c5c466a5aff1c 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -1501,6 +1501,7 @@ const message = { '即將回滾本次恢復,回滾將替換所有本次恢復的檔案,過程中可能需要重新啟動 Docker 以及 1Panel 服務,是否繼續?', upgrading: '正在升級中,請稍候...', + upgradeRecord: '升級記錄', upgradeHelper: '升級操作需要重啟 1Panel 服務,是否繼續?', noUpgrade: '當前已經是最新版本', versionHelper: '1Panel 版本號命名規則為: [大版本].[功能版本].[Bug 修復版本],例:', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 11c51f06e6693..1de40a364b4df 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1500,6 +1500,7 @@ const message = { '即将回滚本次恢复,回滚将替换所有本次恢复的文件,过程中可能需要重启 Docker 以及 1Panel 服务,是否继续?', upgrading: '正在升级中,请稍候...', + upgradeRecord: '升级记录', upgradeHelper: '升级操作需要重启 1Panel 服务,是否继续?', noUpgrade: '当前已经是最新版本', versionHelper: '1Panel 版本号命名规则为: [大版本].[功能版本].[Bug 修复版本],例:',