feat: 计划任务删除时增加删除数据提示 (#580)

This commit is contained in:
ssongliu 2023-04-11 18:48:28 +08:00 committed by GitHub
parent 38725097a6
commit a8b83cf4ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 167 additions and 66 deletions

View file

@ -96,19 +96,19 @@ func (b *BaseApi) SearchJobRecords(c *gin.Context) {
// @Summary Clean job records
// @Description 清空计划任务记录
// @Accept json
// @Param request body dto.OperateByID true "request"
// @Param request body dto.CronjobClean true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /cronjobs/records/clean [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"cronjobs","output_colume":"name","output_value":"name"}],"formatZH":"清空计划任务记录 [name]","formatEN":"clean cronjob [name] records"}
func (b *BaseApi) CleanRecord(c *gin.Context) {
var req dto.OperateByID
var req dto.CronjobClean
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := cronjobService.CleanRecord(req.ID); err != nil {
if err := cronjobService.CleanRecord(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
@ -120,13 +120,13 @@ func (b *BaseApi) CleanRecord(c *gin.Context) {
// @Summary Delete cronjob
// @Description 删除计划任务
// @Accept json
// @Param request body dto.BatchDeleteReq true "request"
// @Param request body dto.CronjobBatchDelete true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /cronjobs/del [post]
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"ids","isList":true,"db":"cronjobs","output_colume":"name","output_value":"names"}],"formatZH":"删除计划任务 [names]","formatEN":"delete cronjob [names]"}
func (b *BaseApi) DeleteCronjob(c *gin.Context) {
var req dto.BatchDeleteReq
var req dto.CronjobBatchDelete
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
@ -136,7 +136,7 @@ func (b *BaseApi) DeleteCronjob(c *gin.Context) {
return
}
if err := cronjobService.Delete(req.Ids); err != nil {
if err := cronjobService.Delete(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}

View file

@ -52,6 +52,16 @@ type CronjobDownload struct {
BackupAccountID uint `json:"backupAccountID" validate:"required"`
}
type CronjobClean struct {
CleanData bool `json:"cleanData"`
CronjobID uint `json:"cronjobID" validate:"required"`
}
type CronjobBatchDelete struct {
CleanData bool `json:"cleanData"`
IDs []uint `json:"ids"`
}
type CronjobInfo struct {
ID uint `json:"id"`
Name string `json:"name"`

View file

@ -24,10 +24,10 @@ type ICronjobService interface {
HandleOnce(id uint) error
Update(id uint, req dto.CronjobUpdate) error
UpdateStatus(id uint, status string) error
Delete(ids []uint) error
Delete(req dto.CronjobBatchDelete) error
Download(down dto.CronjobDownload) (string, error)
StartJob(cronjob *model.Cronjob) (int, error)
CleanRecord(id uint) error
CleanRecord(req dto.CronjobClean) error
}
func NewICronjobService() ICronjobService {
@ -79,16 +79,39 @@ func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interfac
return total, dtoCronjobs, err
}
func (u *CronjobService) CleanRecord(id uint) error {
delRecords, err := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(id)))
func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
if req.CleanData {
cronjob, err := cronjobRepo.Get(commonRepo.WithByID(req.CronjobID))
if err != nil {
return err
}
cronjob.RetainCopies = 0
backup, err := backupRepo.Get(commonRepo.WithByID(uint(cronjob.TargetDirID)))
if err != nil {
return err
}
if backup.Type != "LOCAL" {
localDir, err := loadLocalDir()
if err != nil {
return err
}
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
return err
}
u.HandleRmExpired(backup.Type, localDir, &cronjob, client)
} else {
u.HandleRmExpired(backup.Type, "", &cronjob, nil)
}
}
delRecords, err := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(req.CronjobID)))
if err != nil {
return err
}
for _, del := range delRecords {
_ = os.RemoveAll(del.File)
_ = os.RemoveAll(del.Records)
}
if err := cronjobRepo.DeleteRecord(cronjobRepo.WithByJobID(int(id))); err != nil {
if err := cronjobRepo.DeleteRecord(cronjobRepo.WithByJobID(int(req.CronjobID))); err != nil {
return err
}
return nil
@ -175,21 +198,23 @@ func (u *CronjobService) StartJob(cronjob *model.Cronjob) (int, error) {
return entryID, nil
}
func (u *CronjobService) Delete(ids []uint) error {
if len(ids) == 1 {
if err := u.HandleDelete(ids[0]); err != nil {
func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
for _, id := range req.IDs {
cronjob, _ := cronjobRepo.Get(commonRepo.WithByID(id))
if cronjob.ID == 0 {
return errors.New("find cronjob in db failed")
}
global.Cron.Remove(cron.EntryID(cronjob.EntryID))
global.LOG.Infof("stop cronjob entryID: %d", cronjob.EntryID)
if err := u.CleanRecord(dto.CronjobClean{CronjobID: id, CleanData: req.CleanData}); err != nil {
return err
}
if err := cronjobRepo.Delete(commonRepo.WithByID(id)); err != nil {
return err
}
return cronjobRepo.Delete(commonRepo.WithByID(ids[0]))
}
cronjobs, err := cronjobRepo.List(commonRepo.WithIdsIn(ids))
if err != nil {
return err
}
for i := range cronjobs {
_ = u.HandleDelete(ids[i])
}
return cronjobRepo.Delete(commonRepo.WithIdsIn(ids))
return nil
}
func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {

View file

@ -15,7 +15,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/pkg/errors"
"github.com/robfig/cron/v3"
)
func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
@ -123,25 +122,6 @@ func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Tim
}
}
func (u *CronjobService) HandleDelete(id uint) error {
cronjob, _ := cronjobRepo.Get(commonRepo.WithByID(id))
if cronjob.ID == 0 {
return errors.New("find cronjob in db failed")
}
commonDir := fmt.Sprintf("%s/%s/", cronjob.Type, cronjob.Name)
global.Cron.Remove(cron.EntryID(cronjob.EntryID))
global.LOG.Infof("stop cronjob entryID: %d", cronjob.EntryID)
_ = cronjobRepo.DeleteRecord(cronjobRepo.WithByJobID(int(id)))
dir := fmt.Sprintf("%s/task/%s/%s", constant.DataDir, cronjob.Type, cronjob.Name)
if _, err := os.Stat(dir); err == nil {
if err := os.RemoveAll(dir); err != nil {
global.LOG.Errorf("rm file %s/task/%s failed, err: %v", constant.DataDir, commonDir, err)
}
}
return nil
}
func (u *CronjobService) HandleRmExpired(backType, localDir string, cronjob *model.Cronjob, backClient cloud_storage.CloudStorageClient) {
global.LOG.Infof("start to handle remove expired, retain copies: %d", cronjob.RetainCopies)
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), commonRepo.WithOrderBy("created_at desc"))

View file

@ -14,16 +14,16 @@ export const editCronjob = (params: Cronjob.CronjobUpdate) => {
return http.post(`/cronjobs/update`, params);
};
export const deleteCronjob = (params: { ids: number[] }) => {
return http.post(`/cronjobs/del`, params);
export const deleteCronjob = (ids: number[], cleanData: boolean) => {
return http.post(`/cronjobs/del`, { ids: ids, cleanData: cleanData });
};
export const searchRecords = (params: Cronjob.SearchRecord) => {
return http.post<ResPage<Cronjob.Record>>(`cronjobs/search/records`, params);
};
export const cleanRecords = (id: number) => {
return http.post(`cronjobs/records/clean`, { id: id });
export const cleanRecords = (id: number, cleanData: boolean) => {
return http.post(`cronjobs/records/clean`, { cronjobID: id, cleanData: cleanData });
};
export const getRecordDetail = (params: string) => {

View file

@ -627,6 +627,9 @@ const message = {
errRecord: 'Incorrect logging',
errHandle: 'Task execution failure',
noRecord: 'The execution did not generate any logs',
cleanData: 'Clean data',
cleanDataHelper:
'Clean data will remove all data generated by this scheduled task, including backup files, execution records, and log files.',
noLogs: 'No task output yet...',
errPath: 'Backup path [{0}] error, cannot download!',
},

View file

@ -628,6 +628,8 @@ const message = {
errRecord: '错误的日志记录',
errHandle: '任务执行失败',
noRecord: '当前计划任务暂未产生记录',
cleanData: '删除数据',
cleanDataHelper: '删除数据将删除该计划任务产生的所有数据包括备份文件执行记录以及日志文件',
noLogs: '暂无任务输出...',
errPath: '备份路径 [{0}] 错误无法下载',
},

View file

@ -15,7 +15,7 @@
<el-button type="primary" @click="onOpenDialog('create')">
{{ $t('commons.button.create') }}{{ $t('cronjob.cronTask') }}
</el-button>
<el-button plain :disabled="selects.length === 0" @click="onBatchDelete(null)">
<el-button plain :disabled="selects.length === 0" @click="onDelete(null)">
{{ $t('commons.button.delete') }}
</el-button>
</el-col>
@ -114,6 +114,32 @@
</template>
</LayoutContent>
<el-dialog
v-model="deleteVisiable"
:title="$t('commons.button.clean')"
width="30%"
:close-on-click-modal="false"
>
<el-form ref="deleteForm" label-position="left" v-loading="delLoading">
<el-form-item>
<el-checkbox v-model="cleanData" :label="$t('cronjob.cleanData')" />
<span class="input-help">
{{ $t('cronjob.cleanDataHelper') }}
</span>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="deleteVisiable = false" :disabled="delLoading">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" @click="onSubmitDelete">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-dialog>
<OperatrDialog @search="search" ref="dialogRef" />
<Records @search="search()" ref="dialogRecordRef" />
</div>
@ -132,7 +158,6 @@ import RouterButton from '@/components/router-button/index.vue';
import { deleteCronjob, getCronjobPage, handleOnce, updateStatus } from '@/api/modules/cronjob';
import i18n from '@/lang';
import { Cronjob } from '@/api/interface/cronjob';
import { useDeleteData } from '@/hooks/use-delete-data';
import { ElMessageBox } from 'element-plus';
import { MsgSuccess } from '@/utils/message';
@ -148,6 +173,11 @@ const paginationConfig = reactive({
});
const searchName = ref();
const deleteVisiable = ref();
const deleteCronjobID = ref();
const delLoading = ref();
const cleanData = ref();
const weekOptions = [
{ label: i18n.global.t('cronjob.monday'), value: 1 },
{ label: i18n.global.t('cronjob.tuesday'), value: 2 },
@ -204,17 +234,35 @@ const onOpenDialog = async (
dialogRef.value!.acceptParams(params);
};
const onBatchDelete = async (row: Cronjob.CronjobInfo | null) => {
let ids: Array<number> = [];
const onDelete = async (row: Cronjob.CronjobInfo | null) => {
if (row) {
ids.push(row.id);
deleteCronjobID.value = row.id;
} else {
deleteCronjobID.value = 0;
}
deleteVisiable.value = true;
};
const onSubmitDelete = async () => {
let ids: Array<number> = [];
if (deleteCronjobID.value) {
ids.push(deleteCronjobID.value);
} else {
selects.value.forEach((item: Cronjob.CronjobInfo) => {
ids.push(item.id);
});
}
await useDeleteData(deleteCronjob, { ids: ids }, 'commons.msg.delete');
search();
delLoading.value = true;
await deleteCronjob(ids, cleanData.value)
.then(() => {
delLoading.value = false;
deleteVisiable.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();
})
.catch(() => {
delLoading.value = false;
});
};
const onChangeStatus = async (id: number, status: string) => {
@ -272,7 +320,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.delete'),
click: (row: Cronjob.CronjobInfo) => {
onBatchDelete(row);
onDelete(row);
},
},
];

View file

@ -70,7 +70,7 @@
>
{{ $t('commons.button.enable') }}
</el-button>
<el-button type="primary" @click="cleanRecord()" link>
<el-button type="primary" @click="deleteVisiable = true" link>
{{ $t('commons.button.clean') }}
</el-button>
</span>
@ -282,6 +282,32 @@
</div>
</template>
</LayoutContent>
<el-dialog
v-model="deleteVisiable"
:title="$t('commons.button.clean')"
width="30%"
:close-on-click-modal="false"
>
<el-form ref="deleteForm" label-position="left" v-loading="delLoading">
<el-form-item>
<el-checkbox v-model="cleanData" :label="$t('cronjob.cleanData')" />
<span class="input-help">
{{ $t('cronjob.cleanDataHelper') }}
</span>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="deleteVisiable = false" :disabled="delLoading">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" @click="cleanRecord">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
@ -319,6 +345,10 @@ const currentRecord = ref<Cronjob.Record>();
const currentRecordDetail = ref<string>('');
const currentRecordIndex = ref();
const deleteVisiable = ref();
const delLoading = ref();
const cleanData = ref();
const acceptParams = async (params: DialogProps): Promise<void> => {
dialogData.value = params;
recordShow.value = true;
@ -547,15 +577,18 @@ const loadRecord = async (row: Cronjob.Record) => {
}
};
const cleanRecord = async () => {
ElMessageBox.confirm(i18n.global.t('cronjob.cleanHelper'), i18n.global.t('commons.button.clean'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
}).then(async () => {
await cleanRecords(dialogData.value.rowData.id);
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search(true);
});
delLoading.value = true;
console.log(dialogData.value.rowData);
await cleanRecords(dialogData.value.rowData.id, cleanData.value)
.then(() => {
delLoading.value = false;
deleteVisiable.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search(true);
})
.catch(() => {
delLoading.value = false;
});
};
function isBackup() {