mirror of https://github.com/1Panel-dev/1Panel
fix: 计划任务部分 bug 修改
parent
7fdffa6848
commit
4849fde8c9
|
@ -93,4 +93,5 @@ type Record struct {
|
|||
Message string `json:"message"`
|
||||
TargetPath string `json:"targetPath"`
|
||||
Interval int `json:"interval"`
|
||||
File string `json:"file"`
|
||||
}
|
||||
|
|
|
@ -126,6 +126,7 @@ func (u *CronjobRepo) EndRecords(record model.JobRecords, status, message, recor
|
|||
errMap := make(map[string]interface{})
|
||||
errMap["records"] = records
|
||||
errMap["status"] = status
|
||||
errMap["file"] = record.File
|
||||
errMap["message"] = message
|
||||
errMap["interval"] = time.Since(record.StartTime).Milliseconds()
|
||||
if err := global.DB.Model(&model.JobRecords{}).Where("id = ?", record.ID).Updates(errMap).Error; err != nil {
|
||||
|
|
|
@ -107,7 +107,7 @@ func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Tim
|
|||
}
|
||||
fileName = fileName + ".tar.gz"
|
||||
default:
|
||||
fileName = fmt.Sprintf("%s.tar.gz", startTime.Format("20060102150405"))
|
||||
fileName = fmt.Sprintf("directory%s_%s.tar.gz", strings.ReplaceAll(cronjob.SourceDir, "/", "_"), startTime.Format("20060102150405"))
|
||||
backupDir = fmt.Sprintf("%s/%s", cronjob.Type, cronjob.Name)
|
||||
global.LOG.Infof("handle tar %s to %s", backupDir, fileName)
|
||||
if err := handleTar(cronjob.SourceDir, baseDir+"/"+backupDir, fileName, cronjob.ExclusionRules); err != nil {
|
||||
|
@ -214,10 +214,10 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string) error {
|
|||
if len(exclude) == 0 {
|
||||
continue
|
||||
}
|
||||
excludeRules += (" --exclude" + exclude)
|
||||
excludeRules += (" --exclude " + exclude)
|
||||
}
|
||||
path := ""
|
||||
if len(strings.Split(sourceDir, "/")) > 3 {
|
||||
if strings.Contains(sourceDir, "/") {
|
||||
itemDir := strings.ReplaceAll(sourceDir[strings.LastIndex(sourceDir, "/"):], "/", "")
|
||||
aheadDir := strings.ReplaceAll(sourceDir, itemDir, "")
|
||||
path += fmt.Sprintf("-C %s %s", aheadDir, itemDir)
|
||||
|
@ -225,6 +225,7 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string) error {
|
|||
path = sourceDir
|
||||
}
|
||||
|
||||
global.LOG.Debugf("tar zcvf %s %s %s \n", targetDir+"/"+name, excludeRules, path)
|
||||
stdout, err := cmd.Execf("tar zcvf %s %s %s", targetDir+"/"+name, excludeRules, path)
|
||||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
|
|
|
@ -80,6 +80,19 @@ const checkImageName = (rule: any, value: any, callback: any) => {
|
|||
}
|
||||
};
|
||||
|
||||
const checkVolumeName = (rule: any, value: any, callback: any) => {
|
||||
if (value === '' || typeof value === 'undefined' || value == null) {
|
||||
callback(new Error(i18n.global.t('commons.rule.volumeName')));
|
||||
} else {
|
||||
const reg = /^[a-zA-Z0-9]{1}[a-z:A-Z0-9_.-]{0,30}$/;
|
||||
if (!reg.test(value) && value !== '') {
|
||||
callback(new Error(i18n.global.t('commons.rule.volumeName')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const checkLinuxName = (rule: any, value: any, callback: any) => {
|
||||
if (value === '' || typeof value === 'undefined' || value == null) {
|
||||
callback(new Error(i18n.global.t('commons.rule.linuxName', ['/\\:*?"<>|'])));
|
||||
|
@ -128,6 +141,7 @@ interface CommonRule {
|
|||
simpleName: FormItemRule;
|
||||
dbName: FormItemRule;
|
||||
imageName: FormItemRule;
|
||||
volumeName: FormItemRule;
|
||||
linuxName: FormItemRule;
|
||||
password: FormItemRule;
|
||||
email: FormItemRule;
|
||||
|
@ -172,6 +186,11 @@ export const Rules: CommonRule = {
|
|||
validator: checkImageName,
|
||||
trigger: 'blur',
|
||||
},
|
||||
volumeName: {
|
||||
required: true,
|
||||
validator: checkVolumeName,
|
||||
trigger: 'blur',
|
||||
},
|
||||
name: {
|
||||
required: true,
|
||||
validator: checkName,
|
||||
|
|
|
@ -123,6 +123,7 @@ export default {
|
|||
simpleName: 'Support English, numbers and _ length 1-30',
|
||||
dbName: 'Support English, Chinese, numbers, .-, and _ length 1-16',
|
||||
imageName: 'Support English, Chinese, numbers, :.-_, length 1-30',
|
||||
volumeName: 'Support English, numbers, .-_, length 1-30',
|
||||
complexityPassword:
|
||||
'Please enter a password with more than 8 characters and must contain letters, digits, and special symbols',
|
||||
commonPassword: 'Please enter a password with more than 6 characters',
|
||||
|
@ -526,18 +527,18 @@ export default {
|
|||
enableMsg: 'The cronjob has been stopped. Enable now?',
|
||||
taskType: 'Task type',
|
||||
record: 'Records',
|
||||
shell: 'shell',
|
||||
website: 'website',
|
||||
shell: 'Shell script',
|
||||
website: 'Backup website',
|
||||
rulesHelper: 'Compression exclusion rules (with; Is a delimiter), for example: \n*.log; *.sql',
|
||||
lastRecrodTime: 'Last execution time',
|
||||
all: 'All',
|
||||
failedRecord: 'Failed records',
|
||||
successRecord: 'Successful records',
|
||||
database: 'database',
|
||||
database: 'Backup database',
|
||||
missBackupAccount: 'The backup account could not be found',
|
||||
syncDate: 'Synchronization time ',
|
||||
releaseMemory: 'Free memory',
|
||||
curl: 'Crul',
|
||||
curl: 'Access URL',
|
||||
taskName: 'Task name',
|
||||
cronSpec: 'Lifecycle',
|
||||
directory: 'Backup directory',
|
||||
|
@ -548,9 +549,9 @@ export default {
|
|||
target: 'Target',
|
||||
retainCopies: 'Retain copies',
|
||||
cronSpecRule: 'Please enter a correct lifecycle',
|
||||
perMonth: 'Per monthly',
|
||||
perWeek: 'Per week',
|
||||
perHour: 'Per hour',
|
||||
perMonth: 'Every monthly',
|
||||
perWeek: 'Every week',
|
||||
perHour: 'Every hour',
|
||||
perNDay: 'Every N days',
|
||||
perDay: 'Every days',
|
||||
perNHour: 'Every N hours',
|
||||
|
@ -572,6 +573,8 @@ export default {
|
|||
errRecord: 'Incorrect logging',
|
||||
errHandle: 'Task execution failure',
|
||||
noRecord: 'The execution did not generate any logs',
|
||||
noLogs: 'No task output yet...',
|
||||
errPath: 'Backup path [{0}] error, cannot download!',
|
||||
},
|
||||
monitor: {
|
||||
avgLoad: 'Average load',
|
||||
|
|
|
@ -128,6 +128,7 @@ export default {
|
|||
simpleName: '支持英文、数字、_,长度1-30',
|
||||
dbName: '支持英文、中文、数字、.-_,长度1-16',
|
||||
imageName: '支持英文、中文、数字、:.-_,长度1-30',
|
||||
volumeName: '支持英文、数字、.-和_,长度1-30',
|
||||
complexityPassword: '请输入 8 位以上、必须含有字母、数字、特殊符号的密码',
|
||||
commonPassword: '请输入 6 位以上长度密码',
|
||||
linuxName: '长度1-30,名称不能含有{0}等符号',
|
||||
|
@ -581,6 +582,8 @@ export default {
|
|||
errRecord: '错误的日志记录',
|
||||
errHandle: '任务执行失败',
|
||||
noRecord: '当前计划任务暂未产生记录',
|
||||
noLogs: '暂无任务输出...',
|
||||
errPath: '备份路径 [{0}] 错误,无法下载!',
|
||||
},
|
||||
monitor: {
|
||||
avgLoad: '平均负载',
|
||||
|
|
|
@ -173,7 +173,7 @@ const search = async () => {
|
|||
loading.value = false;
|
||||
data.value = res.data.items || [];
|
||||
for (const item of data.value) {
|
||||
if (item.targetDir !== '-' || item.targetDir !== '') {
|
||||
if (item.targetDir !== '-' && item.targetDir !== '') {
|
||||
item.targetDir = i18n.global.t('setting.' + item.targetDir);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@
|
|||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
v-if="dialogData.rowData!.type === 'website' || dialogData.rowData!.type === 'directory'"
|
||||
v-if="dialogData.rowData!.type === 'directory'"
|
||||
:label="$t('cronjob.exclusionRules')"
|
||||
prop="exclusionRules"
|
||||
>
|
||||
|
@ -282,7 +282,7 @@ const weekOptions = [
|
|||
{ label: i18n.global.t('cronjob.sunday'), value: 7 },
|
||||
];
|
||||
const rules = reactive({
|
||||
name: [Rules.requiredInput, Rules.name],
|
||||
name: [Rules.requiredInput],
|
||||
type: [Rules.requiredSelect],
|
||||
specType: [Rules.requiredSelect],
|
||||
spec: [
|
||||
|
|
|
@ -183,7 +183,7 @@
|
|||
style="margin-left: 10px"
|
||||
link
|
||||
icon="Download"
|
||||
@click="onDownload(currentRecord!.id, dialogData.rowData!.targetDirID)"
|
||||
@click="onDownload(currentRecord, dialogData.rowData!.targetDirID)"
|
||||
>
|
||||
{{ $t('file.download') }}
|
||||
</el-button>
|
||||
|
@ -253,7 +253,7 @@
|
|||
<codemirror
|
||||
ref="mymirror"
|
||||
:autofocus="true"
|
||||
placeholder="None data"
|
||||
:placeholder="$t('cronjob.noLogs')"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="height: 130px; width: 100%; margin-top: 5px"
|
||||
|
@ -288,7 +288,7 @@ import { reactive, ref } from 'vue';
|
|||
import { Cronjob } from '@/api/interface/cronjob';
|
||||
import { loadZero } from '@/utils/util';
|
||||
import { searchRecords, download, handleOnce, updateStatus } from '@/api/modules/cronjob';
|
||||
import { dateFormat, dateFormatForName } from '@/utils/util';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import i18n from '@/lang';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { LoadFile } from '@/api/modules/files';
|
||||
|
@ -296,7 +296,7 @@ import LayoutContent from '@/layout/layout-content.vue';
|
|||
import { Codemirror } from 'vue-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
|
||||
const loading = ref();
|
||||
const hasRecords = ref();
|
||||
|
@ -334,7 +334,7 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
|
|||
hasRecords.value = true;
|
||||
currentRecord.value = records.value[0];
|
||||
currentRecordIndex.value = 0;
|
||||
loadRecord(currentRecord.value.records);
|
||||
loadRecord(currentRecord.value);
|
||||
searchInfo.recordTotal = res.data.total;
|
||||
recordShow.value = true;
|
||||
};
|
||||
|
@ -446,13 +446,26 @@ const search = async () => {
|
|||
endTime: searchInfo.endTime,
|
||||
status: searchInfo.status,
|
||||
};
|
||||
records.value = [];
|
||||
const res = await searchRecords(params);
|
||||
records.value = res.data.items || [];
|
||||
if (!res.data.items) {
|
||||
hasRecords.value = false;
|
||||
return;
|
||||
}
|
||||
records.value = res.data.items;
|
||||
hasRecords.value = true;
|
||||
currentRecord.value = records.value[0];
|
||||
currentRecordIndex.value = 0;
|
||||
loadRecord(currentRecord.value);
|
||||
searchInfo.recordTotal = res.data.total;
|
||||
};
|
||||
const onDownload = async (recordID: number, backupID: number) => {
|
||||
const onDownload = async (record: any, backupID: number) => {
|
||||
if (!record.file || record.file.indexOf('/') === -1) {
|
||||
MsgError(i18n.global.t('cronjob.errPath', [record.file]));
|
||||
return;
|
||||
}
|
||||
let params = {
|
||||
recordID: recordID,
|
||||
recordID: record.id,
|
||||
backupAccountID: backupID,
|
||||
};
|
||||
const res = await download(params);
|
||||
|
@ -460,15 +473,9 @@ const onDownload = async (recordID: number, backupID: number) => {
|
|||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = downloadUrl;
|
||||
if (dialogData.value.rowData!.type === 'database') {
|
||||
a.download =
|
||||
dialogData.value.rowData!.dbName + '_' + dateFormatForName(currentRecord.value?.startTime) + '.sql.gz';
|
||||
} else if (dialogData.value.rowData!.type === 'website') {
|
||||
a.download =
|
||||
dialogData.value.rowData!.website + '_' + dateFormatForName(currentRecord.value?.startTime) + '.tar.gz';
|
||||
} else {
|
||||
let name = dialogData.value.rowData!.sourceDir.replaceAll('/', '_');
|
||||
a.download = name + '_' + dateFormatForName(currentRecord.value?.startTime) + '.tar.gz';
|
||||
if (record.file && record.file.indexOf('/') !== -1) {
|
||||
let pathItem = record.file.split('/');
|
||||
a.download = pathItem[pathItem.length - 1];
|
||||
}
|
||||
const event = new MouseEvent('click');
|
||||
a.dispatchEvent(event);
|
||||
|
@ -484,11 +491,15 @@ const nextPage = async () => {
|
|||
const forDetail = async (row: Cronjob.Record, index: number) => {
|
||||
currentRecord.value = row;
|
||||
currentRecordIndex.value = index;
|
||||
loadRecord(row.records);
|
||||
loadRecord(row);
|
||||
};
|
||||
const loadRecord = async (path: string) => {
|
||||
if (path) {
|
||||
const res = await LoadFile({ path: path });
|
||||
const loadRecord = async (row: Cronjob.Record) => {
|
||||
if (row.status === 'Failed') {
|
||||
currentRecordDetail.value = row.records;
|
||||
return;
|
||||
}
|
||||
if (row.records) {
|
||||
const res = await LoadFile({ path: row.records });
|
||||
currentRecordDetail.value = res.data;
|
||||
}
|
||||
};
|
||||
|
@ -521,7 +532,6 @@ defineExpose({
|
|||
height: 310px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.infinite-list .infinite-list-item {
|
||||
display: flex;
|
||||
|
|
|
@ -1,52 +1,54 @@
|
|||
<template>
|
||||
<el-row>
|
||||
<el-col :span="22" :offset="1">
|
||||
<el-descriptions :column="4" direction="vertical">
|
||||
<el-descriptions-item>
|
||||
<div>
|
||||
<el-form label-position="top">
|
||||
<el-row type="flex" style="margin-left: 50px" justify="center">
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('nginx.connections') }}</span>
|
||||
<span class="status-label">{{ $t('database.connections') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ data.active }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('nginx.accepts') }}</span>
|
||||
<span class="status-label">{{ $t('database.accepts') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ data.accepts }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('nginx.handled') }}</span>
|
||||
<span class="status-label">{{ $t('database.handled') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ data.handled }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('nginx.requests') }}</span>
|
||||
<span class="status-label">{{ $t('database.requests') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ data.requests }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('nginx.reading') }}</span>
|
||||
<span class="status-label">{{ $t('database.reading') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ data.reading }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('nginx.writing') }}</span>
|
||||
<span class="status-label">{{ $t('database.writing') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ data.writing }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('nginx.waiting') }}</span>
|
||||
<span class="status-label">{{ $t('database.waiting') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ data.waiting }}</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%" />
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
Loading…
Reference in New Issue