Browse Source

fix: 计划任务部分 bug 修改

pull/163/head
ssongliu 2 years ago committed by ssongliu
parent
commit
4849fde8c9
  1. 1
      backend/app/dto/cronjob.go
  2. 1
      backend/app/repo/cronjob.go
  3. 7
      backend/app/service/cronjob_helper.go
  4. 19
      frontend/src/global/form-rules.ts
  5. 17
      frontend/src/lang/modules/en.ts
  6. 3
      frontend/src/lang/modules/zh.ts
  7. 2
      frontend/src/views/cronjob/index.vue
  8. 4
      frontend/src/views/cronjob/operate/index.vue
  9. 54
      frontend/src/views/cronjob/record/index.vue
  10. 56
      frontend/src/views/website/website/nginx/status/index.vue

1
backend/app/dto/cronjob.go

@ -93,4 +93,5 @@ type Record struct {
Message string `json:"message"`
TargetPath string `json:"targetPath"`
Interval int `json:"interval"`
File string `json:"file"`
}

1
backend/app/repo/cronjob.go

@ -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 {

7
backend/app/service/cronjob_helper.go

@ -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))

19
frontend/src/global/form-rules.ts

@ -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,

17
frontend/src/lang/modules/en.ts

@ -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',

3
frontend/src/lang/modules/zh.ts

@ -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: '平均负载',

2
frontend/src/views/cronjob/index.vue

@ -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);
}
}

4
frontend/src/views/cronjob/operate/index.vue

@ -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: [

54
frontend/src/views/cronjob/record/index.vue

@ -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;

56
frontend/src/views/website/website/nginx/status/index.vue

@ -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…
Cancel
Save