mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-03-01 11:34:13 +08:00
feat: 网站备份/恢复增加任务日志 (#6603)
This commit is contained in:
parent
4f1bdf5338
commit
ed75ecc63b
@ -9,6 +9,7 @@ type CommonBackup struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
DetailName string `json:"detailName"`
|
DetailName string `json:"detailName"`
|
||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
|
TaskID string `json:"taskID"`
|
||||||
}
|
}
|
||||||
type CommonRecover struct {
|
type CommonRecover struct {
|
||||||
DownloadAccountID uint `json:"downloadAccountID" validate:"required"`
|
DownloadAccountID uint `json:"downloadAccountID" validate:"required"`
|
||||||
@ -17,6 +18,8 @@ type CommonRecover struct {
|
|||||||
DetailName string `json:"detailName"`
|
DetailName string `json:"detailName"`
|
||||||
File string `json:"file"`
|
File string `json:"file"`
|
||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
|
TaskID string `json:"taskID"`
|
||||||
|
BackupRecordID uint `json:"backupRecordID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RecordSearch struct {
|
type RecordSearch struct {
|
||||||
|
@ -72,3 +72,13 @@ func (u *BackupRepo) WithByCronID(cronjobID uint) DBOption {
|
|||||||
return g.Where("cronjob_id = ?", cronjobID)
|
return g.Where("cronjob_id = ?", cronjobID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *BackupRepo) GetRecord(opts ...DBOption) (*model.BackupRecord, error) {
|
||||||
|
var record *model.BackupRecord
|
||||||
|
db := global.DB.Model(&model.BackupRecord{})
|
||||||
|
for _, opt := range opts {
|
||||||
|
db = opt(db)
|
||||||
|
}
|
||||||
|
err := db.Find(record).Error
|
||||||
|
return record, err
|
||||||
|
}
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@ -36,7 +38,7 @@ func (u *BackupService) AppBackup(req dto.CommonBackup) (*model.BackupRecord, er
|
|||||||
backupDir := path.Join(global.CONF.System.Backup, itemDir)
|
backupDir := path.Join(global.CONF.System.Backup, itemDir)
|
||||||
|
|
||||||
fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
|
fileName := fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
|
||||||
if err := handleAppBackup(&install, backupDir, fileName, "", req.Secret); err != nil {
|
if err := handleAppBackup(&install, nil, backupDir, fileName, "", req.Secret, ""); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,57 +82,88 @@ func (u *BackupService) AppRecover(req dto.CommonRecover) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleAppBackup(install *model.AppInstall, backupDir, fileName string, excludes string, secret string) error {
|
func backupDatabaseWithTask(parentTask *task.Task, resourceKey, tmpDir, name string, databaseID uint) error {
|
||||||
fileOp := files.NewFileOp()
|
switch resourceKey {
|
||||||
tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", ""))
|
case constant.AppMysql, constant.AppMariaDB:
|
||||||
if !fileOp.Stat(tmpDir) {
|
db, err := mysqlRepo.Get(commonRepo.WithByID(databaseID))
|
||||||
if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
parentTask.LogStart(task.GetTaskName(db.Name, task.TaskBackup, task.TaskScopeDatabase))
|
||||||
defer func() {
|
if err := handleMysqlBackup(db.MysqlName, resourceKey, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", name)); err != nil {
|
||||||
_ = os.RemoveAll(tmpDir)
|
return err
|
||||||
}()
|
|
||||||
|
|
||||||
remarkInfo, _ := json.Marshal(install)
|
|
||||||
remarkInfoPath := fmt.Sprintf("%s/app.json", tmpDir)
|
|
||||||
if err := fileOp.SaveFile(remarkInfoPath, string(remarkInfo), fs.ModePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
appPath := install.GetPath()
|
|
||||||
if err := handleTar(appPath, tmpDir, "app.tar.gz", excludes, ""); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
|
|
||||||
for _, resource := range resources {
|
|
||||||
switch resource.Key {
|
|
||||||
case constant.AppMysql, constant.AppMariaDB:
|
|
||||||
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := handleMysqlBackup(db.MysqlName, resource.Key, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case constant.AppPostgresql:
|
|
||||||
db, err := postgresqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := handlePostgresqlBackup(db.PostgresqlName, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
parentTask.LogSuccess(task.GetTaskName(db.Name, task.TaskBackup, task.TaskScopeDatabase))
|
||||||
|
case constant.AppPostgresql:
|
||||||
if err := handleTar(tmpDir, backupDir, fileName, "", secret); err != nil {
|
db, err := postgresqlRepo.Get(commonRepo.WithByID(databaseID))
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
parentTask.LogStart(task.GetTaskName(db.Name, task.TaskBackup, task.TaskScopeDatabase))
|
||||||
|
if err := handlePostgresqlBackup(db.PostgresqlName, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", name)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
parentTask.LogSuccess(task.GetTaskName(db.Name, task.TaskBackup, task.TaskScopeDatabase))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleAppBackup(install *model.AppInstall, parentTask *task.Task, backupDir, fileName, excludes, secret, taskID string) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
backupTask *task.Task
|
||||||
|
)
|
||||||
|
backupTask = parentTask
|
||||||
|
if parentTask == nil {
|
||||||
|
backupTask, err = task.NewTaskWithOps(install.Name, task.TaskBackup, task.TaskScopeApp, taskID, install.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
backupApp := func(t *task.Task) error {
|
||||||
|
fileOp := files.NewFileOp()
|
||||||
|
tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", ""))
|
||||||
|
if !fileOp.Stat(tmpDir) {
|
||||||
|
if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil {
|
||||||
|
return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = os.RemoveAll(tmpDir)
|
||||||
|
}()
|
||||||
|
|
||||||
|
remarkInfo, _ := json.Marshal(install)
|
||||||
|
remarkInfoPath := fmt.Sprintf("%s/app.json", tmpDir)
|
||||||
|
if err := fileOp.SaveFile(remarkInfoPath, string(remarkInfo), fs.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
appPath := install.GetPath()
|
||||||
|
if err := handleTar(appPath, tmpDir, "app.tar.gz", excludes, ""); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||||
|
for _, resource := range resources {
|
||||||
|
if err = backupDatabaseWithTask(t, resource.Key, tmpDir, install.Name, resource.ResourceId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.LogStart(i18n.GetMsgByKey("CompressDir"))
|
||||||
|
if err := handleTar(tmpDir, backupDir, fileName, "", secret); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.Log(i18n.GetWithName("CompressFileSuccess", fileName))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
backupTask.AddSubTask(task.GetTaskName(install.Name, task.TaskBackup, task.TaskScopeApp), backupApp, nil)
|
||||||
|
if parentTask != nil {
|
||||||
|
return backupApp(parentTask)
|
||||||
|
}
|
||||||
|
return backupTask.Execute()
|
||||||
|
}
|
||||||
|
|
||||||
func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback bool, secret string) error {
|
func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback bool, secret string) error {
|
||||||
isOk := false
|
isOk := false
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
@ -160,7 +193,7 @@ func handleAppRecover(install *model.AppInstall, recoverFile string, isRollback
|
|||||||
|
|
||||||
if !isRollback {
|
if !isRollback {
|
||||||
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("app/%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout)))
|
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("app/%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout)))
|
||||||
if err := handleAppBackup(install, path.Dir(rollbackFile), path.Base(rollbackFile), "", ""); err != nil {
|
if err := handleAppBackup(install, nil, path.Dir(rollbackFile), path.Base(rollbackFile), "", "", ""); err != nil {
|
||||||
return fmt.Errorf("backup app %s for rollback before recover failed, err: %v", install.Name, err)
|
return fmt.Errorf("backup app %s for rollback before recover failed, err: %v", install.Name, err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -3,6 +3,11 @@ package service
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
|
"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/pkg/errors"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@ -14,11 +19,8 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/compose"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
|
func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
|
||||||
@ -31,214 +33,234 @@ func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error {
|
|||||||
itemDir := fmt.Sprintf("website/%s", req.Name)
|
itemDir := fmt.Sprintf("website/%s", req.Name)
|
||||||
backupDir := path.Join(global.CONF.System.Backup, itemDir)
|
backupDir := path.Join(global.CONF.System.Backup, itemDir)
|
||||||
fileName := fmt.Sprintf("%s_%s.tar.gz", website.PrimaryDomain, timeNow+common.RandStrAndNum(5))
|
fileName := fmt.Sprintf("%s_%s.tar.gz", website.PrimaryDomain, timeNow+common.RandStrAndNum(5))
|
||||||
if err := handleWebsiteBackup(&website, backupDir, fileName, "", req.Secret); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
record := &model.BackupRecord{
|
go func() {
|
||||||
Type: "website",
|
if err = handleWebsiteBackup(&website, backupDir, fileName, "", req.Secret, req.TaskID); err != nil {
|
||||||
Name: website.PrimaryDomain,
|
global.LOG.Errorf("backup website %s failed, err: %v", website.Alias, err)
|
||||||
DetailName: req.DetailName,
|
return
|
||||||
SourceAccountIDs: "1",
|
}
|
||||||
DownloadAccountID: 1,
|
record := &model.BackupRecord{
|
||||||
FileDir: itemDir,
|
Type: "website",
|
||||||
FileName: fileName,
|
Name: website.PrimaryDomain,
|
||||||
}
|
DetailName: req.DetailName,
|
||||||
if err := backupRepo.CreateRecord(record); err != nil {
|
SourceAccountIDs: "1",
|
||||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
DownloadAccountID: 1,
|
||||||
return err
|
FileDir: itemDir,
|
||||||
}
|
FileName: fileName,
|
||||||
|
}
|
||||||
|
if err = backupRepo.CreateRecord(record); err != nil {
|
||||||
|
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *BackupService) WebsiteRecover(req dto.CommonRecover) error {
|
func (u *BackupService) WebsiteRecover(req dto.CommonRecover) error {
|
||||||
fileOp := files.NewFileOp()
|
|
||||||
if !fileOp.Stat(req.File) {
|
|
||||||
return buserr.WithName("ErrFileNotFound", req.File)
|
|
||||||
}
|
|
||||||
website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(req.DetailName))
|
website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(req.DetailName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
global.LOG.Infof("recover website %s from backup file %s", req.Name, req.File)
|
go func() {
|
||||||
if err := handleWebsiteRecover(&website, req.File, false, req.Secret); err != nil {
|
if err := handleWebsiteRecover(&website, req.File, false, req.Secret, req.TaskID); err != nil {
|
||||||
return err
|
global.LOG.Errorf("recover website %s failed, err: %v", website.Alias, err)
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback bool, secret string) error {
|
func handleWebsiteRecover(website *model.Website, recoverFile string, isRollback bool, secret, taskID string) error {
|
||||||
fileOp := files.NewFileOp()
|
recoverTask, err := task.NewTaskWithOps(website.PrimaryDomain, task.TaskRecover, task.TaskScopeWebsite, taskID, website.ID)
|
||||||
tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "")
|
|
||||||
if err := handleUnTar(recoverFile, path.Dir(recoverFile), secret); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = os.RemoveAll(tmpPath)
|
|
||||||
}()
|
|
||||||
|
|
||||||
var oldWebsite model.Website
|
|
||||||
websiteJson, err := os.ReadFile(tmpPath + "/website.json")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(websiteJson, &oldWebsite); err != nil {
|
recoverTask.AddSubTask(task.GetTaskName(website.PrimaryDomain, task.TaskRecover, task.TaskScopeWebsite), func(t *task.Task) error {
|
||||||
return fmt.Errorf("unmarshal app.json failed, err: %v", err)
|
fileOp := files.NewFileOp()
|
||||||
}
|
tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "")
|
||||||
|
t.Log(i18n.GetWithName("DeCompressFile", recoverFile))
|
||||||
if err := checkValidOfWebsite(&oldWebsite, website); err != nil {
|
if err = handleUnTar(recoverFile, path.Dir(recoverFile), secret); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
temPathWithName := tmpPath + "/" + website.Alias
|
|
||||||
if !fileOp.Stat(tmpPath+"/website.json") || !fileOp.Stat(temPathWithName+".conf") || !fileOp.Stat(temPathWithName+".web.tar.gz") {
|
|
||||||
return buserr.WithDetail(constant.ErrBackupExist, ".conf or .web.tar.gz", nil)
|
|
||||||
}
|
|
||||||
if website.Type == constant.Deployment {
|
|
||||||
if !fileOp.Stat(temPathWithName + ".app.tar.gz") {
|
|
||||||
return buserr.WithDetail(constant.ErrBackupExist, ".app.tar.gz", nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isOk := false
|
|
||||||
if !isRollback {
|
|
||||||
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("website/%s_%s.tar.gz", website.Alias, time.Now().Format(constant.DateTimeSlimLayout)))
|
|
||||||
if err := handleWebsiteBackup(website, path.Dir(rollbackFile), path.Base(rollbackFile), "", ""); err != nil {
|
|
||||||
return fmt.Errorf("backup website %s for rollback before recover failed, err: %v", website.Alias, err)
|
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if !isOk {
|
_ = os.RemoveAll(tmpPath)
|
||||||
global.LOG.Info("recover failed, start to rollback now")
|
|
||||||
if err := handleWebsiteRecover(website, rollbackFile, true, ""); err != nil {
|
|
||||||
global.LOG.Errorf("rollback website %s from %s failed, err: %v", website.Alias, rollbackFile, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
global.LOG.Infof("rollback website %s from %s successful", website.Alias, rollbackFile)
|
|
||||||
_ = os.RemoveAll(rollbackFile)
|
|
||||||
} else {
|
|
||||||
_ = os.RemoveAll(rollbackFile)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
}
|
|
||||||
|
|
||||||
nginxInfo, err := appInstallRepo.LoadBaseInfo(constant.AppOpenresty, "")
|
var oldWebsite model.Website
|
||||||
if err != nil {
|
websiteJson, err := os.ReadFile(tmpPath + "/website.json")
|
||||||
return err
|
|
||||||
}
|
|
||||||
nginxConfPath := fmt.Sprintf("%s/openresty/%s/conf/conf.d", constant.AppInstallDir, nginxInfo.Name)
|
|
||||||
if err := fileOp.CopyFile(fmt.Sprintf("%s/%s.conf", tmpPath, website.Alias), nginxConfPath); err != nil {
|
|
||||||
global.LOG.Errorf("handle recover from conf.d failed, err: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch website.Type {
|
|
||||||
case constant.Deployment:
|
|
||||||
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := handleAppRecover(&app, fmt.Sprintf("%s/%s.app.tar.gz", tmpPath, website.Alias), true, ""); err != nil {
|
if err = json.Unmarshal(websiteJson, &oldWebsite); err != nil {
|
||||||
global.LOG.Errorf("handle recover from app.tar.gz failed, err: %v", err)
|
return fmt.Errorf("unmarshal app.json failed, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = checkValidOfWebsite(&oldWebsite, website); err != nil {
|
||||||
|
t.Log(i18n.GetWithName("ErrCheckValid", err.Error()))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", constant.AppInstallDir, app.App.Key, app.Name)); err != nil {
|
|
||||||
global.LOG.Errorf("docker-compose restart failed, err: %v", err)
|
temPathWithName := tmpPath + "/" + website.Alias
|
||||||
return err
|
if !fileOp.Stat(tmpPath+"/website.json") || !fileOp.Stat(temPathWithName+".conf") || !fileOp.Stat(temPathWithName+".web.tar.gz") {
|
||||||
|
return buserr.WithDetail(constant.ErrBackupExist, ".conf or .web.tar.gz", nil)
|
||||||
}
|
}
|
||||||
case constant.Runtime:
|
if website.Type == constant.Deployment {
|
||||||
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID))
|
if !fileOp.Stat(temPathWithName + ".app.tar.gz") {
|
||||||
|
return buserr.WithDetail(constant.ErrBackupExist, ".app.tar.gz", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isOk := false
|
||||||
|
if !isRollback {
|
||||||
|
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("website/%s_%s.tar.gz", website.Alias, time.Now().Format(constant.DateTimeSlimLayout)))
|
||||||
|
if err := handleWebsiteBackup(website, path.Dir(rollbackFile), path.Base(rollbackFile), "", "", ""); err != nil {
|
||||||
|
return fmt.Errorf("backup website %s for rollback before recover failed, err: %v", website.Alias, err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if !isOk {
|
||||||
|
t.LogStart(i18n.GetMsgByKey("Rollback"))
|
||||||
|
if err := handleWebsiteRecover(website, rollbackFile, true, "", taskID); err != nil {
|
||||||
|
t.LogFailedWithErr(i18n.GetMsgByKey("Rollback"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.LogSuccess(i18n.GetMsgByKey("Rollback"))
|
||||||
|
_ = os.RemoveAll(rollbackFile)
|
||||||
|
} else {
|
||||||
|
_ = os.RemoveAll(rollbackFile)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
nginxInfo, err := appInstallRepo.LoadBaseInfo(constant.AppOpenresty, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if runtime.Type == constant.RuntimeNode || runtime.Type == constant.RuntimeJava || runtime.Type == constant.RuntimeGo {
|
if err = fileOp.CopyFile(fmt.Sprintf("%s/%s.conf", tmpPath, website.Alias), GetSitePath(*website, SiteConfDir)); err != nil {
|
||||||
if err := handleRuntimeRecover(runtime, fmt.Sprintf("%s/%s.runtime.tar.gz", tmpPath, website.Alias), true, ""); err != nil {
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch website.Type {
|
||||||
|
case constant.Deployment:
|
||||||
|
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
global.LOG.Info("put runtime.tar.gz into tmp dir successful")
|
taskName := task.GetTaskName(app.Name, task.TaskRecover, task.TaskScopeApp)
|
||||||
|
t.LogStart(taskName)
|
||||||
|
if err := handleAppRecover(&app, fmt.Sprintf("%s/%s.app.tar.gz", tmpPath, website.Alias), true, ""); err != nil {
|
||||||
|
t.LogFailedWithErr(taskName, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.LogSuccess(taskName)
|
||||||
|
if _, err = compose.DownAndUp(fmt.Sprintf("%s/%s/%s/docker-compose.yml", constant.AppInstallDir, app.App.Key, app.Name)); err != nil {
|
||||||
|
t.LogFailedWithErr("Run", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case constant.Runtime:
|
||||||
|
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
taskName := task.GetTaskName(runtime.Name, task.TaskRecover, task.TaskScopeRuntime)
|
||||||
|
t.LogStart(taskName)
|
||||||
|
if err := handleRuntimeRecover(runtime, fmt.Sprintf("%s/%s.runtime.tar.gz", tmpPath, website.Alias), true, ""); err != nil {
|
||||||
|
t.LogFailedWithErr(taskName, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.LogSuccess(taskName)
|
||||||
}
|
}
|
||||||
}
|
taskName := i18n.GetMsgByKey("TaskRecover") + i18n.GetMsgByKey("websiteDir")
|
||||||
|
t.Log(taskName)
|
||||||
siteDir := fmt.Sprintf("%s/openresty/%s/www/sites", constant.AppInstallDir, nginxInfo.Name)
|
if err = handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", tmpPath, website.Alias), GetWebSiteRootDir(), ""); err != nil {
|
||||||
if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", tmpPath, website.Alias), siteDir, ""); err != nil {
|
t.LogFailedWithErr(taskName, err)
|
||||||
global.LOG.Errorf("handle recover from web.tar.gz failed, err: %v", err)
|
return err
|
||||||
return err
|
}
|
||||||
}
|
stdout, err := cmd.Execf("docker exec -i %s nginx -s reload", nginxInfo.ContainerName)
|
||||||
stdout, err := cmd.Execf("docker exec -i %s nginx -s reload", nginxInfo.ContainerName)
|
if err != nil {
|
||||||
if err != nil {
|
return errors.New(stdout)
|
||||||
global.LOG.Errorf("nginx -s reload failed, err: %s", stdout)
|
}
|
||||||
return errors.New(string(stdout))
|
oldWebsite.ID = website.ID
|
||||||
}
|
if err := websiteRepo.SaveWithoutCtx(&oldWebsite); err != nil {
|
||||||
|
return err
|
||||||
oldWebsite.ID = website.ID
|
}
|
||||||
if err := websiteRepo.SaveWithoutCtx(&oldWebsite); err != nil {
|
isOk = true
|
||||||
global.LOG.Errorf("handle save website data failed, err: %v", err)
|
return nil
|
||||||
return err
|
}, nil)
|
||||||
}
|
return recoverTask.Execute()
|
||||||
isOk = true
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWebsiteBackup(website *model.Website, backupDir, fileName string, excludes string, secret string) error {
|
func handleWebsiteBackup(website *model.Website, backupDir, fileName, excludes, secret, taskID string) error {
|
||||||
fileOp := files.NewFileOp()
|
backupTask, err := task.NewTaskWithOps(website.PrimaryDomain, task.TaskBackup, task.TaskScopeWebsite, taskID, website.ID)
|
||||||
tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", ""))
|
|
||||||
if !fileOp.Stat(tmpDir) {
|
|
||||||
if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil {
|
|
||||||
return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = os.RemoveAll(tmpDir)
|
|
||||||
}()
|
|
||||||
|
|
||||||
remarkInfo, _ := json.Marshal(website)
|
|
||||||
if err := fileOp.SaveFile(tmpDir+"/website.json", string(remarkInfo), fs.ModePerm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
global.LOG.Info("put website.json into tmp dir successful")
|
|
||||||
|
|
||||||
nginxInfo, err := appInstallRepo.LoadBaseInfo(constant.AppOpenresty, "")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
nginxConfFile := fmt.Sprintf("%s/openresty/%s/conf/conf.d/%s.conf", constant.AppInstallDir, nginxInfo.Name, website.Alias)
|
backupTask.AddSubTask(task.GetTaskName(website.PrimaryDomain, task.TaskBackup, task.TaskScopeWebsite), func(t *task.Task) error {
|
||||||
if err := fileOp.CopyFile(nginxConfFile, tmpDir); err != nil {
|
fileOp := files.NewFileOp()
|
||||||
return err
|
tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", ""))
|
||||||
}
|
if !fileOp.Stat(tmpDir) {
|
||||||
global.LOG.Info("put openresty conf into tmp dir successful")
|
if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil {
|
||||||
|
return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = os.RemoveAll(tmpDir)
|
||||||
|
}()
|
||||||
|
|
||||||
switch website.Type {
|
remarkInfo, _ := json.Marshal(website)
|
||||||
case constant.Deployment:
|
if err = fileOp.SaveFile(tmpDir+"/website.json", string(remarkInfo), fs.ModePerm); err != nil {
|
||||||
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := handleAppBackup(&app, tmpDir, fmt.Sprintf("%s.app.tar.gz", website.Alias), excludes, ""); err != nil {
|
nginxConfFile := GetSitePath(*website, SiteConf)
|
||||||
|
if err = fileOp.CopyFile(nginxConfFile, tmpDir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
global.LOG.Info("put app.tar.gz into tmp dir successful")
|
t.Log(i18n.GetMsgByKey("BackupNginxConfig"))
|
||||||
case constant.Runtime:
|
|
||||||
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID))
|
switch website.Type {
|
||||||
if err != nil {
|
case constant.Deployment:
|
||||||
return err
|
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
|
||||||
}
|
if err != nil {
|
||||||
if runtime.Type == constant.RuntimeNode || runtime.Type == constant.RuntimeJava || runtime.Type == constant.RuntimeGo {
|
|
||||||
if err := handleRuntimeBackup(runtime, tmpDir, fmt.Sprintf("%s.runtime.tar.gz", website.Alias), excludes, ""); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
global.LOG.Info("put runtime.tar.gz into tmp dir successful")
|
t.LogStart(task.GetTaskName(app.Name, task.TaskBackup, task.TaskScopeApp))
|
||||||
|
if err = handleAppBackup(&app, backupTask, tmpDir, fmt.Sprintf("%s.app.tar.gz", website.Alias), excludes, "", ""); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.LogSuccess(task.GetTaskName(app.Name, task.TaskBackup, task.TaskScopeApp))
|
||||||
|
case constant.Runtime:
|
||||||
|
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.LogStart(task.GetTaskName(runtime.Name, task.TaskBackup, task.TaskScopeRuntime))
|
||||||
|
if err = handleRuntimeBackup(runtime, tmpDir, fmt.Sprintf("%s.runtime.tar.gz", website.Alias), excludes, ""); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.LogSuccess(task.GetTaskName(runtime.Name, task.TaskBackup, task.TaskScopeRuntime))
|
||||||
|
if website.DbID > 0 {
|
||||||
|
if err = backupDatabaseWithTask(t, website.DbType, tmpDir, website.PrimaryDomain, website.DbID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case constant.Static:
|
||||||
|
if website.DbID > 0 {
|
||||||
|
if err = backupDatabaseWithTask(t, website.DbType, tmpDir, website.PrimaryDomain, website.DbID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
websiteDir := fmt.Sprintf("%s/openresty/%s/www/sites/%s", constant.AppInstallDir, nginxInfo.Name, website.Alias)
|
websiteDir := GetSitePath(*website, SiteDir)
|
||||||
if err := handleTar(websiteDir, tmpDir, fmt.Sprintf("%s.web.tar.gz", website.Alias), excludes, ""); err != nil {
|
t.LogStart(i18n.GetMsgByKey("CompressDir"))
|
||||||
return err
|
if err = handleTar(websiteDir, tmpDir, fmt.Sprintf("%s.web.tar.gz", website.Alias), excludes, ""); err != nil {
|
||||||
}
|
return err
|
||||||
global.LOG.Info("put web.tar.gz into tmp dir successful, now start to tar tmp dir")
|
}
|
||||||
if err := handleTar(tmpDir, backupDir, fileName, "", secret); err != nil {
|
if err = handleTar(tmpDir, backupDir, fileName, "", secret); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
t.Log(i18n.GetWithName("CompressFileSuccess", fileName))
|
||||||
return nil
|
return nil
|
||||||
|
}, nil)
|
||||||
|
return backupTask.Execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkValidOfWebsite(oldWebsite, website *model.Website) error {
|
func checkValidOfWebsite(oldWebsite, website *model.Website) error {
|
||||||
|
@ -41,7 +41,7 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) e
|
|||||||
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
|
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
|
||||||
backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("app/%s/%s", app.App.Key, app.Name))
|
backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("app/%s/%s", app.App.Key, app.Name))
|
||||||
record.FileName = fmt.Sprintf("app_%s_%s.tar.gz", app.Name, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
|
record.FileName = fmt.Sprintf("app_%s_%s.tar.gz", app.Name, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
|
||||||
if err := handleAppBackup(&app, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret); err != nil {
|
if err := handleAppBackup(&app, nil, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret, ""); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName))
|
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName))
|
||||||
@ -74,7 +74,7 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim
|
|||||||
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
|
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
|
||||||
backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("website/%s", web.PrimaryDomain))
|
backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("website/%s", web.PrimaryDomain))
|
||||||
record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", web.PrimaryDomain, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
|
record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", web.PrimaryDomain, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
|
||||||
if err := handleWebsiteBackup(&web, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret); err != nil {
|
if err := handleWebsiteBackup(&web, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret, ""); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName))
|
downloadPath, err := u.uploadCronjobBackFile(cronjob, accountMap, path.Join(backupDir, record.FileName))
|
||||||
|
@ -1172,11 +1172,7 @@ func (w WebsiteService) OpWebsiteLog(req request.WebsiteLogReq) (*response.Websi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
nginx, err := getNginxFull(&website)
|
sitePath := GetSitePath(website, SiteDir)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sitePath := path.Join(nginx.SiteDir, "sites", website.Alias)
|
|
||||||
res := &response.WebsiteLog{
|
res := &response.WebsiteLog{
|
||||||
Content: "",
|
Content: "",
|
||||||
}
|
}
|
||||||
@ -1243,7 +1239,7 @@ func (w WebsiteService) OpWebsiteLog(req request.WebsiteLogReq) (*response.Websi
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case constant.DeleteLog:
|
case constant.DeleteLog:
|
||||||
logPath := path.Join(nginx.Install.GetPath(), "www", "sites", website.Alias, "log", req.LogType)
|
logPath := path.Join(sitePath, "log", req.LogType)
|
||||||
if err := files.NewFileOp().WriteFile(logPath, strings.NewReader(""), 0755); err != nil {
|
if err := files.NewFileOp().WriteFile(logPath, strings.NewReader(""), 0755); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -3145,7 +3141,7 @@ func (w WebsiteService) ListDatabases() ([]response.Database, error) {
|
|||||||
}
|
}
|
||||||
pgSqls, _ := postgresqlRepo.List()
|
pgSqls, _ := postgresqlRepo.List()
|
||||||
for _, db := range pgSqls {
|
for _, db := range pgSqls {
|
||||||
database, _ := databaseRepo.Get(commonRepo.WithByName(db.Name))
|
database, _ := databaseRepo.Get(commonRepo.WithByName(db.PostgresqlName))
|
||||||
if database.ID > 0 {
|
if database.ID > 0 {
|
||||||
res = append(res, response.Database{
|
res = append(res, response.Database{
|
||||||
ID: db.ID,
|
ID: db.ID,
|
||||||
|
@ -1205,6 +1205,7 @@ const (
|
|||||||
SiteReWritePath = "SiteReWritePath"
|
SiteReWritePath = "SiteReWritePath"
|
||||||
SiteRedirectDir = "SiteRedirectDir"
|
SiteRedirectDir = "SiteRedirectDir"
|
||||||
SiteCacheDir = "SiteCacheDir"
|
SiteCacheDir = "SiteCacheDir"
|
||||||
|
SiteConfDir = "SiteConfDir"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetSitePath(website model.Website, confType string) string {
|
func GetSitePath(website model.Website, confType string) string {
|
||||||
@ -1231,7 +1232,8 @@ func GetSitePath(website model.Website, confType string) string {
|
|||||||
return path.Join(GteSiteDir(website.Alias), "rewrite", website.Alias+".conf")
|
return path.Join(GteSiteDir(website.Alias), "rewrite", website.Alias+".conf")
|
||||||
case SiteRedirectDir:
|
case SiteRedirectDir:
|
||||||
return path.Join(GteSiteDir(website.Alias), "redirect")
|
return path.Join(GteSiteDir(website.Alias), "redirect")
|
||||||
|
case SiteConfDir:
|
||||||
|
return path.Join(GetWebSiteRootDir(), "conf.d")
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -251,7 +251,7 @@ func (t *Task) LogFailedWithErr(msg string, err error) {
|
|||||||
func (t *Task) LogSuccess(msg string) {
|
func (t *Task) LogSuccess(msg string) {
|
||||||
t.Logger.Printf(msg + i18n.GetMsgByKey("Success"))
|
t.Logger.Printf(msg + i18n.GetMsgByKey("Success"))
|
||||||
}
|
}
|
||||||
func (t *Task) LogSuccessf(format string, v ...any) {
|
func (t *Task) LogSuccessF(format string, v ...any) {
|
||||||
t.Logger.Printf(fmt.Sprintf(format, v...) + i18n.GetMsgByKey("Success"))
|
t.Logger.Printf(fmt.Sprintf(format, v...) + i18n.GetMsgByKey("Success"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"name": "ionCube",
|
"name": "ionCube",
|
||||||
"check": "ioncube_loader",
|
"check": "ionCube Loader",
|
||||||
"file": "ioncube_loader.so",
|
"file": "ioncube_loader.so",
|
||||||
"versions": ["56", "70", "71", "72", "73", "74", "81", "82"],
|
"versions": ["56", "70", "71", "72", "73", "74", "81", "82"],
|
||||||
"installed": false
|
"installed": false
|
||||||
|
@ -251,7 +251,6 @@ TaskSync: "Sync"
|
|||||||
LocalApp: "Local App"
|
LocalApp: "Local App"
|
||||||
SubTask: "Subtask"
|
SubTask: "Subtask"
|
||||||
RuntimeExtension: "Runtime Extension"
|
RuntimeExtension: "Runtime Extension"
|
||||||
TaskBuild: "Build"
|
|
||||||
|
|
||||||
# task - snapshot
|
# task - snapshot
|
||||||
Snapshot: "Snapshot"
|
Snapshot: "Snapshot"
|
||||||
@ -301,3 +300,12 @@ ImageNewTag: "New image tag {{ .name }}"
|
|||||||
ImaegPushRes: "Image push output: {{ .name }}"
|
ImaegPushRes: "Image push output: {{ .name }}"
|
||||||
ComposeCreate: "Create compose"
|
ComposeCreate: "Create compose"
|
||||||
ComposeCreateRes: "Compose creation output: {{ .name }}"
|
ComposeCreateRes: "Compose creation output: {{ .name }}"
|
||||||
|
|
||||||
|
# task - website
|
||||||
|
BackupNginxConfig: "Backup website OpenResty configuration file"
|
||||||
|
CompressFileSuccess: "Directory compression successful, compressed as {{.name}}"
|
||||||
|
CompressDir: "Compress directory"
|
||||||
|
DeCompressFile: "Decompress file {{ .name }}"
|
||||||
|
ErrCheckValid: "Failed to validate backup file, {{ .name }}"
|
||||||
|
Rollback: "Rollback"
|
||||||
|
websiteDir: "Website directory"
|
@ -253,7 +253,6 @@ TaskSync: "同步"
|
|||||||
LocalApp: "本地應用"
|
LocalApp: "本地應用"
|
||||||
SubTask: "子任務"
|
SubTask: "子任務"
|
||||||
RuntimeExtension: "運行環境擴展"
|
RuntimeExtension: "運行環境擴展"
|
||||||
TaskBuild: "構建"
|
|
||||||
|
|
||||||
|
|
||||||
# task - snapshot
|
# task - snapshot
|
||||||
@ -304,3 +303,12 @@ ImageNewTag: "新鏡像 Tag {{ .name }}"
|
|||||||
ImaegPushRes: "鏡像推送輸出:{{ .name }}"
|
ImaegPushRes: "鏡像推送輸出:{{ .name }}"
|
||||||
ComposeCreate: "創建編排"
|
ComposeCreate: "創建編排"
|
||||||
ComposeCreateRes: "編排創建輸出:{{ .name }}"
|
ComposeCreateRes: "編排創建輸出:{{ .name }}"
|
||||||
|
|
||||||
|
# task - website
|
||||||
|
BackupNginxConfig: "備份網站 OpenResty 配置檔案"
|
||||||
|
CompressFileSuccess: "壓縮目錄成功,壓縮為 {{.name}}"
|
||||||
|
CompressDir: "壓縮目錄"
|
||||||
|
DeCompressFile: "解壓檔案 {{ .name }}"
|
||||||
|
ErrCheckValid: "校驗備份檔案失敗,{{ .name }}"
|
||||||
|
Rollback: "回滾"
|
||||||
|
websiteDir: "網站目錄"
|
@ -330,3 +330,12 @@ ImageNewTag: "新镜像 Tag {{ .name }}"
|
|||||||
ImaegPushRes: "镜像推送输出:{{ .name }}"
|
ImaegPushRes: "镜像推送输出:{{ .name }}"
|
||||||
ComposeCreate: "创建编排"
|
ComposeCreate: "创建编排"
|
||||||
ComposeCreateRes: "编排创建输出:{{ .name }}"
|
ComposeCreateRes: "编排创建输出:{{ .name }}"
|
||||||
|
|
||||||
|
# task - website
|
||||||
|
BackupNginxConfig: "备份网站 OpenResty 配置文件"
|
||||||
|
CompressFileSuccess: "压缩目录成功,压缩为 {{.name}}"
|
||||||
|
CompressDir: "压缩目录"
|
||||||
|
DeCompressFile: "解压文件 {{ .name }}"
|
||||||
|
ErrCheckValid: "校验备份文件失败,{{ .name }}"
|
||||||
|
Rollback: "回滚"
|
||||||
|
websiteDir: "网站目录"
|
@ -70,6 +70,7 @@ export namespace Backup {
|
|||||||
name: string;
|
name: string;
|
||||||
detailName: string;
|
detailName: string;
|
||||||
secret: string;
|
secret: string;
|
||||||
|
taskID: string;
|
||||||
}
|
}
|
||||||
export interface Recover {
|
export interface Recover {
|
||||||
downloadAccountID: number;
|
downloadAccountID: number;
|
||||||
@ -78,5 +79,6 @@ export namespace Backup {
|
|||||||
detailName: string;
|
detailName: string;
|
||||||
file: string;
|
file: string;
|
||||||
secret: string;
|
secret: string;
|
||||||
|
taskID: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,8 @@ export namespace Website {
|
|||||||
accessLog?: boolean;
|
accessLog?: boolean;
|
||||||
errorLog?: boolean;
|
errorLog?: boolean;
|
||||||
childSites?: string[];
|
childSites?: string[];
|
||||||
|
dbID: number;
|
||||||
|
dbType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebsiteDTO extends Website {
|
export interface WebsiteDTO extends Website {
|
||||||
|
@ -85,7 +85,7 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
<el-button @click="handleClose" :disabled="loading">
|
<el-button @click="handleBackupClose" :disabled="loading">
|
||||||
{{ $t('commons.button.cancel') }}
|
{{ $t('commons.button.cancel') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="primary" @click="onSubmit" :disabled="loading">
|
<el-button type="primary" @click="onSubmit" :disabled="loading">
|
||||||
@ -96,11 +96,12 @@
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<OpDialog ref="opRef" @search="search" />
|
<OpDialog ref="opRef" @search="search" />
|
||||||
|
<TaskLog ref="taskLogRef" @close="search" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { computeSize, dateFormat, downloadFile } from '@/utils/util';
|
import { computeSize, dateFormat, downloadFile, newUUID } from '@/utils/util';
|
||||||
import {
|
import {
|
||||||
getLocalBackupDir,
|
getLocalBackupDir,
|
||||||
handleBackup,
|
handleBackup,
|
||||||
@ -113,10 +114,12 @@ import i18n from '@/lang';
|
|||||||
import { Backup } from '@/api/interface/backup';
|
import { Backup } from '@/api/interface/backup';
|
||||||
import router from '@/routers';
|
import router from '@/routers';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import TaskLog from '@/components/task-log/index.vue';
|
||||||
|
|
||||||
const selects = ref<any>([]);
|
const selects = ref<any>([]);
|
||||||
const loading = ref();
|
const loading = ref();
|
||||||
const opRef = ref();
|
const opRef = ref();
|
||||||
|
const taskLogRef = ref();
|
||||||
|
|
||||||
const data = ref();
|
const data = ref();
|
||||||
const paginationConfig = reactive({
|
const paginationConfig = reactive({
|
||||||
@ -136,6 +139,7 @@ const secret = ref();
|
|||||||
|
|
||||||
const open = ref();
|
const open = ref();
|
||||||
const isBackup = ref();
|
const isBackup = ref();
|
||||||
|
const recordInfo = ref();
|
||||||
|
|
||||||
interface DialogProps {
|
interface DialogProps {
|
||||||
type: string;
|
type: string;
|
||||||
@ -159,6 +163,7 @@ const handleClose = () => {
|
|||||||
};
|
};
|
||||||
const handleBackupClose = () => {
|
const handleBackupClose = () => {
|
||||||
open.value = false;
|
open.value = false;
|
||||||
|
search();
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadBackupDir = async () => {
|
const loadBackupDir = async () => {
|
||||||
@ -190,44 +195,60 @@ const search = async () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async (row?: any) => {
|
const openTaskLog = (taskID: string) => {
|
||||||
if (isBackup.value) {
|
taskLogRef.value.openWithTaskID(taskID);
|
||||||
let params = {
|
};
|
||||||
type: type.value,
|
|
||||||
name: name.value,
|
const backup = async (close: boolean) => {
|
||||||
detailName: detailName.value,
|
const taskID = newUUID();
|
||||||
secret: secret.value,
|
let params = {
|
||||||
};
|
type: type.value,
|
||||||
loading.value = true;
|
name: name.value,
|
||||||
await handleBackup(params)
|
detailName: detailName.value,
|
||||||
.then(() => {
|
secret: secret.value,
|
||||||
loading.value = false;
|
taskID: taskID,
|
||||||
handleClose();
|
};
|
||||||
handleBackupClose();
|
loading.value = true;
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
try {
|
||||||
search();
|
await handleBackup(params);
|
||||||
})
|
loading.value = false;
|
||||||
.catch(() => {
|
if (close) {
|
||||||
loading.value = false;
|
handleClose();
|
||||||
});
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
return;
|
search();
|
||||||
|
} else {
|
||||||
|
openTaskLog(taskID);
|
||||||
|
}
|
||||||
|
handleBackupClose();
|
||||||
|
} catch (error) {
|
||||||
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const recover = async (close: boolean, row?: any) => {
|
||||||
|
const taskID = newUUID();
|
||||||
let params = {
|
let params = {
|
||||||
downloadAccountID: row.downloadAccountID,
|
downloadAccountID: row.downloadAccountID,
|
||||||
type: type.value,
|
type: type.value,
|
||||||
name: name.value,
|
name: name.value,
|
||||||
detailName: detailName.value,
|
detailName: detailName.value,
|
||||||
file: row.file,
|
file: row.fileDir + '/' + row.fileName,
|
||||||
secret: secret.value,
|
secret: secret.value,
|
||||||
|
taskID: taskID,
|
||||||
|
backupRecordID: row.id,
|
||||||
};
|
};
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
await handleRecover(params)
|
await handleRecover(params)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
handleClose();
|
|
||||||
handleBackupClose();
|
handleBackupClose();
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
if (close) {
|
||||||
search();
|
handleClose();
|
||||||
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
search();
|
||||||
|
} else {
|
||||||
|
openTaskLog(taskID);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
@ -245,7 +266,7 @@ const onBackup = async () => {
|
|||||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
},
|
},
|
||||||
).then(async () => {
|
).then(async () => {
|
||||||
onSubmit();
|
backup(true);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -263,13 +284,22 @@ const onRecover = async (row: Backup.RecordInfo) => {
|
|||||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
},
|
},
|
||||||
).then(async () => {
|
).then(async () => {
|
||||||
onSubmit(row);
|
recover(true, row);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
recordInfo.value = row;
|
||||||
open.value = true;
|
open.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
if (isBackup.value) {
|
||||||
|
backup(false);
|
||||||
|
} else {
|
||||||
|
recover(false, recordInfo.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onDownload = async (row: Backup.RecordInfo) => {
|
const onDownload = async (row: Backup.RecordInfo) => {
|
||||||
let params = {
|
let params = {
|
||||||
downloadAccountID: row.downloadAccountID,
|
downloadAccountID: row.downloadAccountID,
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
<PHP :website="website" v-if="tabIndex == '13'"></PHP>
|
<PHP :website="website" v-if="tabIndex == '13'"></PHP>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane :label="$t('logs.resource')" name="14">
|
<el-tab-pane :label="$t('logs.resource')" name="14">
|
||||||
<Resource :id="id" :websiteType="website.type" v-if="tabIndex == '14'"></Resource>
|
<Resource :id="id" v-if="tabIndex == '14'"></Resource>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane :label="$t('website.other')" name="12">
|
<el-tab-pane :label="$t('website.other')" name="12">
|
||||||
<Other :id="id" v-if="tabIndex == '12'"></Other>
|
<Other :id="id" v-if="tabIndex == '12'"></Other>
|
||||||
|
@ -11,11 +11,16 @@
|
|||||||
label-position="left"
|
label-position="left"
|
||||||
label-width="90px"
|
label-width="90px"
|
||||||
class="mt-5"
|
class="mt-5"
|
||||||
v-if="websiteType === 'static' || websiteType === 'runtime'"
|
v-if="website.type === 'static' || website.type === 'runtime'"
|
||||||
>
|
>
|
||||||
<el-form-item :label="$t('website.changeDatabase')" prop="databaseID">
|
<el-form-item :label="$t('website.changeDatabase')" prop="db">
|
||||||
<el-select v-model="req.databaseID" class="w-full" @change="changeDatabase">
|
<el-select v-model="req.db" class="w-full" @change="changeDatabase">
|
||||||
<el-option v-for="(item, index) in databases" :key="index" :label="item.name" :value="item.id">
|
<el-option
|
||||||
|
v-for="(item, index) in databases"
|
||||||
|
:key="index"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id + item.type"
|
||||||
|
>
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<span>{{ item.name }}</span>
|
<span>{{ item.name }}</span>
|
||||||
<el-tag>{{ item.type }}</el-tag>
|
<el-tag>{{ item.type }}</el-tag>
|
||||||
@ -34,7 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ChangeDatabase, GetWebsiteDatabase, GetWebsiteResource } from '@/api/modules/website';
|
import { ChangeDatabase, GetWebsite, GetWebsiteDatabase, GetWebsiteResource } from '@/api/modules/website';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
@ -43,18 +48,20 @@ const props = defineProps({
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
websiteType: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
const data = ref([]);
|
const data = ref([]);
|
||||||
const req = reactive({
|
const req = reactive({
|
||||||
websiteID: props.id,
|
websiteID: props.id,
|
||||||
databaseID: 0,
|
databaseID: 0,
|
||||||
databaseType: '',
|
databaseType: '',
|
||||||
|
db: '',
|
||||||
});
|
});
|
||||||
const databases = ref([]);
|
const databases = ref([]);
|
||||||
|
const website = ref({
|
||||||
|
type: '',
|
||||||
|
dbID: 0,
|
||||||
|
dbType: '',
|
||||||
|
});
|
||||||
|
|
||||||
const search = async () => {
|
const search = async () => {
|
||||||
try {
|
try {
|
||||||
@ -68,13 +75,31 @@ const listDatabases = async () => {
|
|||||||
const res = await GetWebsiteDatabase();
|
const res = await GetWebsiteDatabase();
|
||||||
databases.value = res.data;
|
databases.value = res.data;
|
||||||
if (databases.value.length > 0) {
|
if (databases.value.length > 0) {
|
||||||
req.databaseID = databases.value[0].id;
|
if (website.value.dbID > 0) {
|
||||||
|
for (let i = 0; i < databases.value.length; i++) {
|
||||||
|
if (
|
||||||
|
databases.value[i].id === website.value.dbID &&
|
||||||
|
databases.value[i].type === website.value.dbType
|
||||||
|
) {
|
||||||
|
req.db = databases.value[i].id + databases.value[i].type;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
req.db = databases.value[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeDatabase = () => {
|
const changeDatabase = () => {
|
||||||
req.databaseType = databases.value.find((item) => item.id === req.databaseID)?.type;
|
for (let i = 0; i < databases.value.length; i++) {
|
||||||
|
if (databases.value[i].id + databases.value[i].type === req.db) {
|
||||||
|
req.databaseID = databases.value[i].id;
|
||||||
|
req.databaseType = databases.value[i].type;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
@ -85,9 +110,17 @@ const submit = async () => {
|
|||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getwebsite = async () => {
|
||||||
|
try {
|
||||||
|
const res = await GetWebsite(props.id);
|
||||||
|
website.value = res.data;
|
||||||
|
req.db = '';
|
||||||
|
search();
|
||||||
|
listDatabases();
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log('websiteType', props.websiteType);
|
getwebsite();
|
||||||
search();
|
|
||||||
listDatabases();
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user