mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-31 14:08:06 +08:00
feat: 计划任务支持备份所有 (#526)
This commit is contained in:
parent
a0e4c266a1
commit
0eb25d8413
@ -9,6 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||||
"github.com/1Panel-dev/1Panel/backend/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
|
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
|
||||||
@ -68,11 +69,6 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Time) (string, error) {
|
func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Time) (string, error) {
|
||||||
var (
|
|
||||||
backupDir string
|
|
||||||
fileName string
|
|
||||||
record model.BackupRecord
|
|
||||||
)
|
|
||||||
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
|
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -89,77 +85,57 @@ func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Tim
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
fileName = fmt.Sprintf("db_%s_%s.sql.gz", cronjob.DBName, startTime.Format("20060102150405"))
|
if err := u.handleDatabase(*cronjob, app, backup, startTime); err != nil {
|
||||||
backupDir = fmt.Sprintf("%s/database/mysql/%s/%s", localDir, app.Name, cronjob.DBName)
|
|
||||||
if err = handleMysqlBackup(app, backupDir, cronjob.DBName, fileName); err != nil {
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
record.Type = "mysql"
|
if cronjob.DBName != "all" {
|
||||||
record.Name = app.Name
|
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
|
||||||
record.DetailName = cronjob.DBName
|
return fmt.Sprintf("database/mysql/%s/%s/db_%s_%s.sql.gz", app.Name, cronjob.DBName, cronjob.DBName, startTime.Format("20060102150405")), nil
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s/database/mysql/%s/%s/db_%s_%s.sql.gz", localDir, app.Name, cronjob.DBName, cronjob.DBName, startTime.Format("20060102150405")), nil
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
case "website":
|
case "website":
|
||||||
fileName = fmt.Sprintf("website_%s_%s.tar.gz", cronjob.Website, startTime.Format("20060102150405"))
|
if err := u.handleWebsite(*cronjob, backup, startTime); err != nil {
|
||||||
backupDir = fmt.Sprintf("%s/website/%s", localDir, cronjob.Website)
|
|
||||||
website, err := websiteRepo.GetFirst(websiteRepo.WithDomain(cronjob.Website))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if err := handleWebsiteBackup(&website, backupDir, fileName); err != nil {
|
if cronjob.DBName != "all" {
|
||||||
return "", err
|
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
|
||||||
|
return fmt.Sprintf("website/%s/website_%s_%s.tar.gz", cronjob.Website, cronjob.Website, startTime.Format("20060102150405")), nil
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s/website/%s/website_%s_%s.tar.gz", localDir, cronjob.Website, cronjob.Website, startTime.Format("20060102150405")), nil
|
||||||
}
|
}
|
||||||
record.Type = "website"
|
return "", nil
|
||||||
record.Name = website.PrimaryDomain
|
|
||||||
default:
|
default:
|
||||||
fileName = fmt.Sprintf("directory%s_%s.tar.gz", strings.ReplaceAll(cronjob.SourceDir, "/", "_"), startTime.Format("20060102150405"))
|
fileName := fmt.Sprintf("directory%s_%s.tar.gz", strings.ReplaceAll(cronjob.SourceDir, "/", "_"), startTime.Format("20060102150405"))
|
||||||
backupDir = fmt.Sprintf("%s/%s/%s", localDir, cronjob.Type, cronjob.Name)
|
backupDir := fmt.Sprintf("%s/%s/%s", localDir, cronjob.Type, cronjob.Name)
|
||||||
|
itemFileDir := fmt.Sprintf("%s/%s", cronjob.Type, cronjob.Name)
|
||||||
global.LOG.Infof("handle tar %s to %s", backupDir, fileName)
|
global.LOG.Infof("handle tar %s to %s", backupDir, fileName)
|
||||||
if err := handleTar(cronjob.SourceDir, backupDir, fileName, cronjob.ExclusionRules); err != nil {
|
if err := handleTar(cronjob.SourceDir, backupDir, fileName, cronjob.ExclusionRules); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
if backup.Type == "LOCAL" {
|
||||||
|
u.HandleRmExpired(backup.Type, backupDir, cronjob, nil)
|
||||||
itemFileDir := strings.ReplaceAll(backupDir, localDir+"/", "")
|
return fmt.Sprintf("%s/%s", backupDir, fileName), nil
|
||||||
if len(record.Name) != 0 {
|
|
||||||
record.FileName = fileName
|
|
||||||
record.FileDir = backupDir
|
|
||||||
record.Source = "LOCAL"
|
|
||||||
record.BackupType = backup.Type
|
|
||||||
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
|
|
||||||
record.Source = backup.Type
|
|
||||||
record.FileDir = itemFileDir
|
|
||||||
}
|
}
|
||||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
if !cronjob.KeepLocal {
|
||||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
defer func() {
|
||||||
|
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, fileName))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
client, err := NewIBackupService().NewClient(&backup)
|
||||||
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
if _, err = client.Upload(backupDir+"/"+fileName, itemFileDir+"/"+fileName); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
u.HandleRmExpired(backup.Type, itemFileDir, cronjob, client)
|
||||||
|
if cronjob.KeepLocal {
|
||||||
|
u.HandleRmExpired("LOCAL", backupDir, cronjob, client)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s/%s/%s", cronjob.Type, cronjob.Name, fileName), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := fmt.Sprintf("%s/%s", backupDir, fileName)
|
|
||||||
if backup.Type != "LOCAL" {
|
|
||||||
fullPath = fmt.Sprintf("%s/%s", itemFileDir, fileName)
|
|
||||||
}
|
|
||||||
if backup.Type == "LOCAL" {
|
|
||||||
u.HandleRmExpired(backup.Type, backupDir, cronjob, nil)
|
|
||||||
return fullPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cronjob.KeepLocal {
|
|
||||||
defer func() {
|
|
||||||
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, fileName))
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
client, err := NewIBackupService().NewClient(&backup)
|
|
||||||
if err != nil {
|
|
||||||
return fullPath, err
|
|
||||||
}
|
|
||||||
if _, err = client.Upload(backupDir+"/"+fileName, itemFileDir+"/"+fileName); err != nil {
|
|
||||||
return fullPath, err
|
|
||||||
}
|
|
||||||
u.HandleRmExpired(backup.Type, itemFileDir, cronjob, client)
|
|
||||||
if cronjob.KeepLocal {
|
|
||||||
u.HandleRmExpired("LOCAL", backupDir, cronjob, client)
|
|
||||||
}
|
|
||||||
return fullPath, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *CronjobService) HandleDelete(id uint) error {
|
func (u *CronjobService) HandleDelete(id uint) error {
|
||||||
@ -281,3 +257,137 @@ func handleUnTar(sourceFile, targetDir string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInfo, backup model.BackupAccount, startTime time.Time) error {
|
||||||
|
localDir, err := loadLocalDir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var record model.BackupRecord
|
||||||
|
|
||||||
|
record.Type = "mysql"
|
||||||
|
record.Name = app.Name
|
||||||
|
record.Source = "LOCAL"
|
||||||
|
record.BackupType = backup.Type
|
||||||
|
|
||||||
|
var dblist []string
|
||||||
|
if cronjob.DBName == "all" {
|
||||||
|
mysqlService := NewIMysqlService()
|
||||||
|
dblist, err = mysqlService.ListDBName()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dblist = append(dblist, cronjob.DBName)
|
||||||
|
}
|
||||||
|
for _, dbName := range dblist {
|
||||||
|
backupDir := fmt.Sprintf("%s/database/mysql/%s/%s", localDir, app.Name, dbName)
|
||||||
|
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbName, startTime.Format("20060102150405"))
|
||||||
|
if err = handleMysqlBackup(app, backupDir, dbName, record.FileName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
record.DetailName = dbName
|
||||||
|
record.FileDir = backupDir
|
||||||
|
itemFileDir := strings.ReplaceAll(backupDir, localDir+"/", "")
|
||||||
|
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
|
||||||
|
record.Source = backup.Type
|
||||||
|
record.FileDir = itemFileDir
|
||||||
|
}
|
||||||
|
if err := saveBackupRecord(record); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if backup.Type == "LOCAL" {
|
||||||
|
u.HandleRmExpired(backup.Type, backupDir, &cronjob, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !cronjob.KeepLocal {
|
||||||
|
defer func() {
|
||||||
|
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
client, err := NewIBackupService().NewClient(&backup)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u.HandleRmExpired(backup.Type, itemFileDir, &cronjob, client)
|
||||||
|
if cronjob.KeepLocal {
|
||||||
|
u.HandleRmExpired("LOCAL", backupDir, &cronjob, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) error {
|
||||||
|
localDir, err := loadLocalDir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var record model.BackupRecord
|
||||||
|
record.Type = "website"
|
||||||
|
record.Name = cronjob.Website
|
||||||
|
record.Source = "LOCAL"
|
||||||
|
record.BackupType = backup.Type
|
||||||
|
|
||||||
|
var weblist []string
|
||||||
|
if cronjob.Website == "all" {
|
||||||
|
weblist, err = NewIWebsiteService().GetWebsiteOptions()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
weblist = append(weblist, cronjob.Website)
|
||||||
|
}
|
||||||
|
for _, websiteItem := range weblist {
|
||||||
|
website, err := websiteRepo.GetFirst(websiteRepo.WithDomain(websiteItem))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
backupDir := fmt.Sprintf("%s/website/%s", localDir, website.PrimaryDomain)
|
||||||
|
record.FileDir = backupDir
|
||||||
|
itemFileDir := strings.ReplaceAll(backupDir, localDir+"/", "")
|
||||||
|
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
|
||||||
|
record.Source = backup.Type
|
||||||
|
record.FileDir = strings.ReplaceAll(backupDir, localDir+"/", "")
|
||||||
|
}
|
||||||
|
record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", website.PrimaryDomain, startTime.Format("20060102150405"))
|
||||||
|
if err := handleWebsiteBackup(&website, backupDir, record.FileName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
record.Name = website.PrimaryDomain
|
||||||
|
if err := saveBackupRecord(record); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if backup.Type == "LOCAL" {
|
||||||
|
u.HandleRmExpired(backup.Type, backupDir, &cronjob, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !cronjob.KeepLocal {
|
||||||
|
defer func() {
|
||||||
|
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
client, err := NewIBackupService().NewClient(&backup)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u.HandleRmExpired(backup.Type, itemFileDir, &cronjob, client)
|
||||||
|
if cronjob.KeepLocal {
|
||||||
|
u.HandleRmExpired("LOCAL", backupDir, &cronjob, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveBackupRecord(record model.BackupRecord) error {
|
||||||
|
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||||
|
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -586,6 +586,8 @@ const message = {
|
|||||||
cronSpecHelper: 'Enter the correct execution period',
|
cronSpecHelper: 'Enter the correct execution period',
|
||||||
directory: 'Backup directory',
|
directory: 'Backup directory',
|
||||||
sourceDir: 'Backup directory',
|
sourceDir: 'Backup directory',
|
||||||
|
allOptionHelper:
|
||||||
|
'The current task plan is to back up all {0}. Direct download is not supported at the moment. You can check the backup list of {0} menu.',
|
||||||
exclusionRules: 'Exclusive rule',
|
exclusionRules: 'Exclusive rule',
|
||||||
saveLocal: 'Retain local backups (the same as the number of cloud storage copies)',
|
saveLocal: 'Retain local backups (the same as the number of cloud storage copies)',
|
||||||
url: 'URL Address',
|
url: 'URL Address',
|
||||||
|
@ -590,6 +590,7 @@ const message = {
|
|||||||
cronSpecHelper: '请输入正确的执行周期',
|
cronSpecHelper: '请输入正确的执行周期',
|
||||||
directory: '备份目录',
|
directory: '备份目录',
|
||||||
sourceDir: '备份目录',
|
sourceDir: '备份目录',
|
||||||
|
allOptionHelper: '当前计划任务为备份所有 {0},暂不支持直接下载,可在 {0} 备份列表中查看',
|
||||||
exclusionRules: '排除规则',
|
exclusionRules: '排除规则',
|
||||||
saveLocal: '同时保留本地备份(和云存储保留份数一致)',
|
saveLocal: '同时保留本地备份(和云存储保留份数一致)',
|
||||||
url: 'URL 地址',
|
url: 'URL 地址',
|
||||||
|
@ -23,7 +23,12 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item :label="$t('cronjob.taskName')" prop="name">
|
<el-form-item :label="$t('cronjob.taskName')" prop="name">
|
||||||
<el-input style="width: 100%" clearable v-model.trim="dialogData.rowData!.name" />
|
<el-input
|
||||||
|
:disabled="dialogData.title === 'edit'"
|
||||||
|
style="width: 100%"
|
||||||
|
clearable
|
||||||
|
v-model.trim="dialogData.rowData!.name"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item :label="$t('cronjob.cronSpec')" prop="spec">
|
<el-form-item :label="$t('cronjob.cronSpec')" prop="spec">
|
||||||
@ -82,6 +87,7 @@
|
|||||||
prop="website"
|
prop="website"
|
||||||
>
|
>
|
||||||
<el-select style="width: 100%" v-model="dialogData.rowData!.website">
|
<el-select style="width: 100%" v-model="dialogData.rowData!.website">
|
||||||
|
<el-option :label="$t('commons.table.all')" value="all" />
|
||||||
<el-option v-for="item in websiteOptions" :key="item" :value="item" :label="item" />
|
<el-option v-for="item in websiteOptions" :key="item" :value="item" :label="item" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -89,6 +95,7 @@
|
|||||||
<div v-if="dialogData.rowData!.type === 'database'">
|
<div v-if="dialogData.rowData!.type === 'database'">
|
||||||
<el-form-item :label="$t('cronjob.database')" prop="dbName">
|
<el-form-item :label="$t('cronjob.database')" prop="dbName">
|
||||||
<el-select style="width: 100%" clearable v-model="dialogData.rowData!.dbName">
|
<el-select style="width: 100%" clearable v-model="dialogData.rowData!.dbName">
|
||||||
|
<el-option :label="$t('commons.table.all')" value="all" />
|
||||||
<el-option v-for="item in mysqlInfo.dbNames" :key="item" :label="item" :value="item" />
|
<el-option v-for="item in mysqlInfo.dbNames" :key="item" :label="item" :value="item" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
@ -152,13 +152,23 @@
|
|||||||
<template #label>
|
<template #label>
|
||||||
<span class="status-label">{{ $t('cronjob.website') }}</span>
|
<span class="status-label">{{ $t('cronjob.website') }}</span>
|
||||||
</template>
|
</template>
|
||||||
<span class="status-count">{{ dialogData.rowData!.website }}</span>
|
<span v-if="dialogData.rowData!.website !== 'all'" class="status-count">
|
||||||
|
{{ dialogData.rowData!.website }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="status-count">
|
||||||
|
{{ $t('commons.table.all') }}
|
||||||
|
</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item class="description" v-if="dialogData.rowData!.type === 'database'">
|
<el-form-item class="description" v-if="dialogData.rowData!.type === 'database'">
|
||||||
<template #label>
|
<template #label>
|
||||||
<span class="status-label">{{ $t('cronjob.database') }}</span>
|
<span class="status-label">{{ $t('cronjob.database') }}</span>
|
||||||
</template>
|
</template>
|
||||||
<span class="status-count">{{ dialogData.rowData!.dbName }}</span>
|
<span v-if="dialogData.rowData!.website !== 'all'" class="status-count">
|
||||||
|
{{ dialogData.rowData!.dbName }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="status-count">
|
||||||
|
{{ $t('commons.table.all') }}
|
||||||
|
</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item class="description" v-if="dialogData.rowData!.type === 'directory'">
|
<el-form-item class="description" v-if="dialogData.rowData!.type === 'directory'">
|
||||||
<template #label>
|
<template #label>
|
||||||
@ -288,7 +298,7 @@ import LayoutContent from '@/layout/layout-content.vue';
|
|||||||
import { Codemirror } from 'vue-codemirror';
|
import { Codemirror } from 'vue-codemirror';
|
||||||
import { javascript } from '@codemirror/lang-javascript';
|
import { javascript } from '@codemirror/lang-javascript';
|
||||||
import { oneDark } from '@codemirror/theme-one-dark';
|
import { oneDark } from '@codemirror/theme-one-dark';
|
||||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgInfo, MsgSuccess } from '@/utils/message';
|
||||||
|
|
||||||
const loading = ref();
|
const loading = ref();
|
||||||
const hasRecords = ref();
|
const hasRecords = ref();
|
||||||
@ -456,6 +466,14 @@ const onRefresh = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onDownload = async (record: any, backupID: number) => {
|
const onDownload = async (record: any, backupID: number) => {
|
||||||
|
if (dialogData.value.rowData.dbName === 'all') {
|
||||||
|
MsgInfo(i18n.global.t('cronjob.allOptionHelper', [i18n.global.t('database.database')]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (dialogData.value.rowData.website === 'all') {
|
||||||
|
MsgInfo(i18n.global.t('cronjob.allOptionHelper', [i18n.global.t('website.website')]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!record.file || record.file.indexOf('/') === -1) {
|
if (!record.file || record.file.indexOf('/') === -1) {
|
||||||
MsgError(i18n.global.t('cronjob.errPath', [record.file]));
|
MsgError(i18n.global.t('cronjob.errPath', [record.file]));
|
||||||
return;
|
return;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user