Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions agent/app/service/cronjob_backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,82 @@ func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, jobRecord model.J
return nil
}

func (u *CronjobService) handleCompose(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error {
composeNames := loadComposesForJob(cronjob)
if len(composeNames) == 0 {
addSkipTask("Compose", taskItem)
return nil
}
accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ","))
if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk {
return buserr.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message))
}
for _, composeName := range composeNames {
retry := 0
taskItem.AddSubTaskWithOps(task.GetTaskName(composeName, task.TaskBackup, task.TaskScopeCronjob), func(t *task.Task) error {
var record model.BackupRecord
record.Status = constant.StatusSuccess
record.From = "cronjob"
record.Type = "compose"
record.CronjobID = cronjob.ID
record.Name = composeName
record.DetailName = composeName
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("tmp/compose/%s", composeName))
record.FileName = simplifiedFileName(fmt.Sprintf("compose_%s_%s.tar.gz", composeName, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5)))

req := dto.CommonBackup{
Type: "compose",
Name: composeName,
}
if err := handleComposeBackup(req, t, 0, backupDir, record.FileName); err != nil {
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
retry++
return err
} else {
t.Log(i18n.GetMsgWithDetail("IgnoreBackupErr", err.Error()))
cleanAccountMap(accountMap)
return nil
}
}
src := path.Join(backupDir, record.FileName)
dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/")
if err := uploadWithMap(*t, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes); err != nil {
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
retry++
return err
}
t.Log(i18n.GetMsgWithDetail("IgnoreUploadErr", err.Error()))
cleanAccountMap(accountMap)
return nil
}
record.FileDir = path.Dir(dst)
if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save compose backup record failed, err: %v", err)
return err
}
u.removeExpiredBackup(cronjob, accountMap, record)
cleanAccountMap(accountMap)
return nil
}, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second)
}
return nil
}

func loadComposesForJob(cronjob model.Cronjob) []string {
if cronjob.AppID == "all" {
records, _ := composeRepo.ListRecord()
var names []string
for _, record := range records {
if record.Name != "" {
names = append(names, record.Name)
}
}
return names
}
return strings.Split(cronjob.AppID, ",")
}

func loadAppsForJob(cronjob model.Cronjob) []model.AppInstall {
var apps []model.AppInstall
if cronjob.AppID == "all" {
Expand Down
4 changes: 3 additions & 1 deletion agent/app/service/cronjob_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ func (u *CronjobService) loadTask(cronjob *model.Cronjob, record *model.JobRecor
err = u.handleDirectory(*cronjob, record.StartTime, taskItem)
case "log":
err = u.handleSystemLog(*cronjob, record.StartTime, taskItem)
case "compose":
err = u.handleCompose(*cronjob, record.StartTime, taskItem)
case "syncIpGroup":
u.handleSyncIpGroup(*cronjob, taskItem)
case "cleanLog":
Expand Down Expand Up @@ -479,7 +481,7 @@ func (u *CronjobService) removeExpiredLog(cronjob model.Cronjob) {
}

func hasBackup(cronjobType string) bool {
return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log" || cronjobType == "cutWebsiteLog"
return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log" || cronjobType == "cutWebsiteLog" || cronjobType == "compose"
}

func handleCronJobAlert(cronjob *model.Cronjob) {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lang/modules/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1277,6 +1277,7 @@ const message = {
cutWebsiteLog: 'Website log rotation',
cutWebsiteLogHelper: 'The rotated log files will be backed up to the backup directory of 1Panel.',
syncIpGroup: 'Sync WAF IP groups',
compose: 'Backup compose',
requestExpirationTime: 'Upload request expiration time(Hours)',
unitHours: 'Unit: Hours',
alertTitle: 'Planned Task - {0} 「{1}」 Task Failure Alert',
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lang/modules/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1220,6 +1220,7 @@ const message = {
cutWebsiteLog: '切割网站日志',
cutWebsiteLogHelper: '切割的日志文件会备份到 1Panel 的 backup 目录下',
syncIpGroup: '同步 WAF IP 组',
compose: '备份容器编排',

requestExpirationTime: '上传请求过期时间(小时)',
unitHours: '单位:小时',
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/views/cronjob/cronjob/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export function loadDefaultSpec(type: string) {
break;
case 'app':
case 'database':
case 'compose':
item.specType = 'perDay';
item.hour = 2;
item.minute = 30;
Expand Down Expand Up @@ -84,6 +85,7 @@ export function loadDefaultSpecCustom(type: string) {
return '30 1 * * 1';
case 'app':
case 'database':
case 'compose':
return '30 2 * * *';
case 'directory':
case 'cutWebsiteLog':
Expand Down Expand Up @@ -224,4 +226,5 @@ export const cronjobTypes = [
{ value: 'ntp', label: i18n.global.t('cronjob.ntp') },
{ value: 'syncIpGroup', label: i18n.global.t('cronjob.syncIpGroup') },
{ value: 'cleanLog', label: i18n.global.t('cronjob.cleanLog') },
{ value: 'compose', label: i18n.global.t('cronjob.compose') },
];
1 change: 1 addition & 0 deletions frontend/src/views/cronjob/cronjob/import/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ const checkDataFormat = (item: any) => {
'clean',
'snapshot',
'ntp',
'compose',
];
if (!item.type || cronjobTypes.indexOf(item.type) === -1) {
return false;
Expand Down
35 changes: 33 additions & 2 deletions frontend/src/views/cronjob/cronjob/operate/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,25 @@
</el-select>
</el-form-item>
</LayoutCol>
<LayoutCol v-if="form.type === 'compose'">
<el-form-item :label="$t('cronjob.compose')" prop="appIdList">
<el-select
clearable
v-model="form.appIdList"
multiple
@change="
form.appIdList = form.appIdList.includes('all')
? ['all']
: form.appIdList
"
>
<el-option :label="$t('commons.table.all')" value="all" />
<div v-for="item in composeOptions" :key="item.name">
<el-option :value="item.name" :label="item.name" />
</div>
</el-select>
</el-form-item>
</LayoutCol>
<LayoutCol v-if="form.type === 'database'">
<el-form-item :label="$t('cronjob.database')">
<el-select v-model="form.dbType" @change="loadDatabases">
Expand Down Expand Up @@ -837,7 +856,7 @@ import { listDbItems } from '@/api/modules/database';
import { getWebsiteOptions } from '@/api/modules/website';
import { MsgError, MsgSuccess } from '@/utils/message';
import { useRouter } from 'vue-router';
import { listContainer } from '@/api/modules/container';
import { listContainer, searchCompose } from '@/api/modules/container';
import { Database } from '@/api/interface/database';
import { listAppInstalled } from '@/api/modules/app';
import {
Expand Down Expand Up @@ -1044,6 +1063,7 @@ const search = async () => {
}
loadBackups();
loadAppInstalls();
loadComposeOptions();
loadUserOptions(true);
loadWebsites();
loadContainers();
Expand All @@ -1064,6 +1084,7 @@ const websiteOptions = ref([]);
const backupOptions = ref([]);
const accountOptions = ref([]);
const appOptions = ref([]);
const composeOptions = ref<{ name: string }[]>([]);
const userOptions = ref([]);
const scriptOptions = ref([]);
const groupOptions = ref([]);
Expand Down Expand Up @@ -1470,6 +1491,15 @@ const loadAppInstalls = async () => {
appOptions.value = res.data || [];
};

const loadComposeOptions = async () => {
try {
const res = await searchCompose({ page: 1, pageSize: 1000, info: '' });
composeOptions.value = (res.data.items || []).map((item: any) => ({ name: item.name }));
} catch (e) {
composeOptions.value = [];
}
};

const loadWebsites = async () => {
const res = await getWebsiteOptions({});
websiteOptions.value = res.data || [];
Expand All @@ -1487,7 +1517,8 @@ function isBackup() {
form.type === 'database' ||
form.type === 'directory' ||
form.type === 'snapshot' ||
form.type === 'log'
form.type === 'log' ||
form.type === 'compose'
);
}

Expand Down