feat: 计划任务支持自定义日志保留份数 (#548)

This commit is contained in:
ssongliu 2023-04-09 22:22:13 +08:00 committed by GitHub
parent 24246da71c
commit 18b4c98daa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 44 additions and 82 deletions

View file

@ -80,21 +80,15 @@ func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interfac
} }
func (u *CronjobService) CleanRecord(id uint) error { func (u *CronjobService) CleanRecord(id uint) error {
_, records, err := cronjobRepo.PageRecords(1, 7, cronjobRepo.WithByJobID(int(id)), commonRepo.WithOrderBy("created_at desc")) delRecords, err := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(id)))
if err != nil {
return err
}
if len(records) < 7 {
return nil
}
delRecords, err := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(id)), cronjobRepo.WithByRecordDropID(int(records[6].ID)))
if err != nil { if err != nil {
return err return err
} }
for _, del := range delRecords { for _, del := range delRecords {
_ = os.RemoveAll(del.File)
_ = os.RemoveAll(del.Records) _ = os.RemoveAll(del.Records)
} }
if err := cronjobRepo.DeleteRecord(cronjobRepo.WithByJobID(int(id)), cronjobRepo.WithByRecordDropID(int(records[6].ID))); err != nil { if err := cronjobRepo.DeleteRecord(cronjobRepo.WithByJobID(int(id))); err != nil {
return err return err
} }
return nil return nil

View file

@ -1,7 +1,6 @@
package service package service
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"strings" "strings"
@ -23,6 +22,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
err error err error
) )
record := cronjobRepo.StartRecords(cronjob.ID, cronjob.KeepLocal, "") record := cronjobRepo.StartRecords(cronjob.ID, cronjob.KeepLocal, "")
logDir := fmt.Sprintf("%s/1panel/task/%s/%s", global.CONF.System.BaseDir, cronjob.Type, cronjob.Name)
go func() { go func() {
switch cronjob.Type { switch cronjob.Type {
case "shell": case "shell":
@ -34,6 +34,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
err = errExec err = errExec
} }
message = []byte(stdout) message = []byte(stdout)
u.HandleRmExpired("LOCAL", logDir, cronjob, nil)
case "website": case "website":
record.File, err = u.HandleBackup(cronjob, record.StartTime) record.File, err = u.HandleBackup(cronjob, record.StartTime)
case "database": case "database":
@ -52,6 +53,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
err = errCurl err = errCurl
} }
message = []byte(stdout) message = []byte(stdout)
u.HandleRmExpired("LOCAL", logDir, cronjob, nil)
} }
if err != nil { if err != nil {
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), string(message)) cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), string(message))
@ -169,39 +171,13 @@ func (u *CronjobService) HandleRmExpired(backType, backupDir string, cronjob *mo
} }
return return
} }
files, err := os.ReadDir(backupDir)
if err != nil {
global.LOG.Errorf("read dir %s failed, err: %v", backupDir, err)
return
}
if len(files) == 0 {
return
}
prefix := "" records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), commonRepo.WithOrderBy("created_at desc"))
switch cronjob.Type {
case "database":
prefix = "db_"
case "website":
prefix = "website_"
case "directory":
prefix = "directory_"
}
dbCopies := uint64(0)
for i := len(files) - 1; i >= 0; i-- {
if strings.HasPrefix(files[i].Name(), prefix) {
dbCopies++
if dbCopies > cronjob.RetainCopies {
_ = os.Remove(backupDir + "/" + files[i].Name())
_ = backupRepo.DeleteRecord(context.Background(), backupRepo.WithByFileName(files[i].Name()))
}
}
}
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)))
if len(records) > int(cronjob.RetainCopies) { if len(records) > int(cronjob.RetainCopies) {
for i := int(cronjob.RetainCopies); i < len(records); i++ { for i := int(cronjob.RetainCopies); i < len(records); i++ {
_ = cronjobRepo.DeleteRecord(cronjobRepo.WithByJobID(int(records[i].ID))) _ = cronjobRepo.DeleteRecord(commonRepo.WithByID(uint(records[i].ID)))
_ = os.Remove(records[i].File)
_ = os.Remove(records[i].Records)
} }
} }
} }
@ -292,7 +268,8 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInf
record.Source = backup.Type record.Source = backup.Type
record.FileDir = itemFileDir record.FileDir = itemFileDir
} }
if err := saveBackupRecord(record); err != nil { if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
return err return err
} }
if backup.Type == "LOCAL" { if backup.Type == "LOCAL" {
@ -356,7 +333,8 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.Backu
return err return err
} }
record.Name = website.PrimaryDomain record.Name = website.PrimaryDomain
if err := saveBackupRecord(record); err != nil { if err := backupRepo.CreateRecord(&record); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
return err return err
} }
if backup.Type == "LOCAL" { if backup.Type == "LOCAL" {
@ -382,11 +360,3 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.Backu
} }
return nil 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
}

View file

@ -585,7 +585,7 @@ const message = {
cronSpec: 'Lifecycle', cronSpec: 'Lifecycle',
cronSpecHelper: 'Enter the correct execution period', cronSpecHelper: 'Enter the correct execution period',
cleanHelper: cleanHelper:
'This operation will retain the latest seven task execution records and log files. Do you want to continue?', 'This operation records all job execution records, backup files, and log files. Do you want to continue?',
directory: 'Backup directory', directory: 'Backup directory',
sourceDir: 'Backup directory', sourceDir: 'Backup directory',
allOptionHelper: allOptionHelper:
@ -595,6 +595,7 @@ const message = {
url: 'URL Address', url: 'URL Address',
target: 'Target', target: 'Target',
retainCopies: 'Retain copies', retainCopies: 'Retain copies',
retainCopiesHelper: 'Number of copies of execution records, log files, and backup files',
cronSpecRule: 'Please enter a correct lifecycle', cronSpecRule: 'Please enter a correct lifecycle',
perMonth: 'Every monthly', perMonth: 'Every monthly',
perWeek: 'Every week', perWeek: 'Every week',

View file

@ -588,7 +588,7 @@ const message = {
taskName: '任务名称', taskName: '任务名称',
cronSpec: '执行周期', cronSpec: '执行周期',
cronSpecHelper: '请输入正确的执行周期', cronSpecHelper: '请输入正确的执行周期',
cleanHelper: '该操作将保留最新的 7 份任务执行记录和日志文件是否继续', cleanHelper: '该操作将所有任务执行记录备份文件和日志文件是否继续',
directory: '备份目录', directory: '备份目录',
sourceDir: '备份目录', sourceDir: '备份目录',
allOptionHelper: '当前计划任务为备份所有 {0}暂不支持直接下载可在 {0} 备份列表中查看', allOptionHelper: '当前计划任务为备份所有 {0}暂不支持直接下载可在 {0} 备份列表中查看',
@ -597,6 +597,7 @@ const message = {
url: 'URL 地址', url: 'URL 地址',
target: '备份到', target: '备份到',
retainCopies: '保留份数', retainCopies: '保留份数',
retainCopiesHelper: '执行记录日志文件备份文件保留份数',
cronSpecRule: '请输入正确的执行周期', cronSpecRule: '请输入正确的执行周期',
perMonth: '每月', perMonth: '每月',
perWeek: '每周', perWeek: '每周',

View file

@ -91,11 +91,7 @@
{{ $t('cronjob.handle') }} {{ $t('cronjob.handle') }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('cronjob.retainCopies')" :min-width="90" prop="retainCopies"> <el-table-column :label="$t('cronjob.retainCopies')" :min-width="90" prop="retainCopies" />
<template #default="{ row }">
{{ loadCopies(row) }}
</template>
</el-table-column>
<el-table-column :label="$t('cronjob.lastRecrodTime')" :min-width="120" prop="lastRecrodTime"> <el-table-column :label="$t('cronjob.lastRecrodTime')" :min-width="120" prop="lastRecrodTime">
<template #default="{ row }"> <template #default="{ row }">
@ -246,14 +242,6 @@ const onHandle = async (row: Cronjob.CronjobInfo) => {
}); });
}; };
const loadCopies = (item) => {
if (item.type === 'shell' || item.type === 'curl') {
return '-';
} else {
return item.retainCopies + '';
}
};
const loadDetail = (row: any) => { const loadDetail = (row: any) => {
isRecordShow.value = true; isRecordShow.value = true;
let params = { let params = {

View file

@ -129,16 +129,18 @@
{{ $t('cronjob.saveLocal') }} {{ $t('cronjob.saveLocal') }}
</el-checkbox> </el-checkbox>
</el-form-item> </el-form-item>
</div>
<el-form-item :label="$t('cronjob.retainCopies')" prop="retainCopies"> <el-form-item :label="$t('cronjob.retainCopies')" prop="retainCopies">
<el-input-number <el-input-number
:min="1" :min="1"
:max="30" :max="300"
step-strictly step-strictly
:step="1" :step="1"
v-model.number="dialogData.rowData!.retainCopies" v-model.number="dialogData.rowData!.retainCopies"
></el-input-number> ></el-input-number>
<span class="input-help">{{ $t('cronjob.retainCopiesHelper') }}</span>
</el-form-item> </el-form-item>
</div>
<el-form-item v-if="dialogData.rowData!.type === 'curl'" :label="$t('cronjob.url')" prop="url"> <el-form-item v-if="dialogData.rowData!.type === 'curl'" :label="$t('cronjob.url')" prop="url">
<el-input style="width: 100%" clearable v-model.trim="dialogData.rowData!.url" /> <el-input style="width: 100%" clearable v-model.trim="dialogData.rowData!.url" />

View file

@ -70,7 +70,7 @@
> >
{{ $t('commons.button.enable') }} {{ $t('commons.button.enable') }}
</el-button> </el-button>
<el-button type="primary" :disabled="records.length <= 7" @click="cleanRecord()" link> <el-button type="primary" @click="cleanRecord()" link>
{{ $t('commons.button.clean') }} {{ $t('commons.button.clean') }}
</el-button> </el-button>
</span> </span>
@ -130,7 +130,7 @@
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="16"> <el-col :span="16">
<el-form label-position="top"> <el-form label-position="top" :v-key="refresh">
<el-row type="flex" justify="center"> <el-row type="flex" justify="center">
<el-form-item class="descriptionWide" v-if="isBackup()"> <el-form-item class="descriptionWide" v-if="isBackup()">
<template #label> <template #label>
@ -301,6 +301,7 @@ import { oneDark } from '@codemirror/theme-one-dark';
import { MsgError, MsgInfo, MsgSuccess } from '@/utils/message'; import { MsgError, MsgInfo, MsgSuccess } from '@/utils/message';
const loading = ref(); const loading = ref();
const refresh = ref(false);
const hasRecords = ref(); const hasRecords = ref();
let timer: NodeJS.Timer | null = null; let timer: NodeJS.Timer | null = null;
@ -324,7 +325,7 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
search(true); search(true);
timer = setInterval(() => { timer = setInterval(() => {
onRefresh(); onRefresh();
}, 1000 * 10); }, 1000 * 5);
}; };
const shortcuts = [ const shortcuts = [
@ -402,10 +403,7 @@ const onHandle = async (row: Cronjob.CronjobInfo) => {
.then(() => { .then(() => {
loading.value = false; loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
searchInfo.pageSize = searchInfo.pageSize * searchInfo.page; onRefresh();
searchInfo.page = 1;
records.value = [];
search(false);
}) })
.catch(() => { .catch(() => {
loading.value = false; loading.value = false;
@ -483,7 +481,15 @@ const onRefresh = async () => {
status: searchInfo.status, status: searchInfo.status,
}; };
const res = await searchRecords(params); const res = await searchRecords(params);
records.value = res.data.items || []; if (res.data.items) {
records.value = res.data.items;
hasRecords.value = true;
currentRecord.value = records.value[0];
} else {
records.value = [];
hasRecords.value = false;
refresh.value = !refresh.value;
}
}; };
const onDownload = async (record: any, backupID: number) => { const onDownload = async (record: any, backupID: number) => {