mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-27 12:39:01 +08:00
fix: 解决计划任务部分 bug
This commit is contained in:
parent
6da43d7eb0
commit
015baec864
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,7 +16,6 @@ build/1panel
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
/build
|
||||
/pkg/
|
||||
backend/__debug_bin
|
||||
cmd/server/__debug_bin
|
||||
|
@ -86,7 +86,7 @@ func (u *CronjobRepo) Page(page, size int, opts ...DBOption) (int64, []model.Cro
|
||||
|
||||
func (u *CronjobRepo) RecordFirst(id uint) (model.JobRecords, error) {
|
||||
var record model.JobRecords
|
||||
err := global.DB.Order("created_at desc").First(&record).Error
|
||||
err := global.DB.Where("cronjob_id = ?", id).Order("created_at desc").First(&record).Error
|
||||
return record, err
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
@ -13,6 +12,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -232,6 +232,9 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
|
||||
if backup.Type == "LOCAL" {
|
||||
if dir, ok := varMap["dir"]; ok {
|
||||
if dirStr, isStr := dir.(string); isStr {
|
||||
if strings.HasSuffix(dirStr, "/") {
|
||||
dirStr = dirStr[:strings.LastIndex(dirStr, "/")]
|
||||
}
|
||||
if err := updateBackupDir(dirStr); err != nil {
|
||||
upMap["vars"] = backup.Vars
|
||||
_ = backupRepo.Update(req.ID, upMap)
|
||||
@ -316,8 +319,7 @@ func updateBackupDir(dir string) error {
|
||||
if strings.HasSuffix(oldDir, "/") {
|
||||
oldDir = oldDir[:strings.LastIndex(oldDir, "/")]
|
||||
}
|
||||
cmd := exec.Command("cp", "-r", oldDir+"/*", dir)
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
stdout, err := cmd.Execf("cp -r %s/* %s", oldDir, dir)
|
||||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ func (u *CronjobService) Download(down dto.CronjobDownload) (string, error) {
|
||||
}
|
||||
return fmt.Sprintf("%v/database/mysql/%s/%s/db_%s_%s.sql.gz", varMap["dir"], mysqlInfo.Name, cronjob.DBName, cronjob.DBName, record.StartTime.Format("20060102150405")), nil
|
||||
case "directory":
|
||||
return fmt.Sprintf("%v/%s/%s/%s.tar.gz", varMap["dir"], cronjob.Type, cronjob.Name, record.StartTime.Format("20060102150405")), nil
|
||||
return fmt.Sprintf("%v/%s/%s/directory%s_%s.tar.gz", varMap["dir"], cronjob.Type, cronjob.Name, strings.ReplaceAll(cronjob.SourceDir, "/", "_"), record.StartTime.Format("20060102150405")), nil
|
||||
default:
|
||||
return "", fmt.Errorf("not support type %s", cronjob.Type)
|
||||
}
|
||||
@ -220,6 +220,11 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
|
||||
if err := copier.Copy(&cronjob, &req); err != nil {
|
||||
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
cronModel, err := cronjobRepo.Get(commonRepo.WithByID(id))
|
||||
if err != nil {
|
||||
return constant.ErrRecordNotFound
|
||||
}
|
||||
cronjob.EntryID = cronModel.EntryID
|
||||
cronjob.Spec = loadSpec(cronjob)
|
||||
if err := u.StartJob(&cronjob); err != nil {
|
||||
return err
|
||||
|
@ -114,7 +114,7 @@ func (u *CronjobService) HandleBackup(cronjob *model.Cronjob, startTime time.Tim
|
||||
fileName = fmt.Sprintf("directory%s_%s.tar.gz", strings.ReplaceAll(cronjob.SourceDir, "/", "_"), startTime.Format("20060102150405"))
|
||||
backupDir = fmt.Sprintf("%s/%s/%s", localDir, cronjob.Type, cronjob.Name)
|
||||
global.LOG.Infof("handle tar %s to %s", backupDir, fileName)
|
||||
if err := handleTar(cronjob.SourceDir, localDir+"/"+backupDir, fileName, cronjob.ExclusionRules); err != nil {
|
||||
if err := handleTar(cronjob.SourceDir, backupDir, fileName, cronjob.ExclusionRules); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
@ -197,21 +197,26 @@ func (u *CronjobService) HandleRmExpired(backType, backupDir string, cronjob *mo
|
||||
if len(files) == 0 {
|
||||
return
|
||||
}
|
||||
if cronjob.Type == "database" {
|
||||
dbCopies := uint64(0)
|
||||
for i := len(files) - 1; i >= 0; i-- {
|
||||
if strings.HasPrefix(files[i].Name(), "db_") {
|
||||
dbCopies++
|
||||
if dbCopies > cronjob.RetainCopies {
|
||||
_ = os.Remove(backupDir + "/" + files[i].Name())
|
||||
_ = backupRepo.DeleteRecord(context.Background(), backupRepo.WithByFileName(files[i].Name()))
|
||||
}
|
||||
|
||||
prefix := ""
|
||||
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()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < len(files)-int(cronjob.RetainCopies); i++ {
|
||||
_ = os.Remove(backupDir + "/" + files[i].Name())
|
||||
}
|
||||
}
|
||||
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)))
|
||||
if len(records) > int(cronjob.RetainCopies) {
|
||||
|
@ -84,7 +84,7 @@ const checkImageName = (rule: any, value: any, callback: any) => {
|
||||
if (value === '' || typeof value === 'undefined' || value == null) {
|
||||
callback(new Error(i18n.global.t('commons.rule.imageName')));
|
||||
} else {
|
||||
const reg = /^[a-zA-Z0-9\u4e00-\u9fa5]{1}[a-z:A-Z0-9_.\u4e00-\u9fa5-]{0,30}$/;
|
||||
const reg = /^[a-zA-Z0-9]{1}[a-z:A-Z0-9_/.-]{0,150}$/;
|
||||
if (!reg.test(value) && value !== '') {
|
||||
callback(new Error(i18n.global.t('commons.rule.imageName')));
|
||||
} else {
|
||||
|
@ -129,7 +129,7 @@ export default {
|
||||
userName: 'Support English, Chinese, numbers and _ length 3-30',
|
||||
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',
|
||||
imageName: 'Support English, numbers, :/.-_, length 1-150',
|
||||
volumeName: 'Support English, numbers, .-_, length 1-30',
|
||||
complexityPassword:
|
||||
'Enter a password that is longer than eight characters and contains at least two letters, digits, and special characters',
|
||||
|
@ -133,7 +133,7 @@ export default {
|
||||
userName: '支持英文、中文、数字和_,长度3-30',
|
||||
simpleName: '支持英文、数字、_,长度1-30',
|
||||
dbName: '支持英文、中文、数字、.-_,长度1-16',
|
||||
imageName: '支持英文、中文、数字、:.-_,长度1-30',
|
||||
imageName: '支持英文、数字、:/.-_,长度1-150',
|
||||
volumeName: '支持英文、数字、.-和_,长度1-30',
|
||||
complexityPassword: '请输入长度大于 8 位且包含字母、数字、特殊字符至少两项的密码组合',
|
||||
commonPassword: '请输入 6 位以上长度密码',
|
||||
|
@ -279,3 +279,10 @@
|
||||
.middle-center {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.status-count {
|
||||
font-size: 24px;
|
||||
}
|
||||
.status-label {
|
||||
font-size: 14px;
|
||||
color: #646a73;
|
||||
}
|
||||
|
@ -25,7 +25,7 @@
|
||||
<el-option v-for="item in repos" :key="item.id" :value="item.id" :label="item.name" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.imageName')" :rules="Rules.requiredInput" prop="imageName">
|
||||
<el-form-item :label="$t('container.imageName')" :rules="Rules.imageName" prop="imageName">
|
||||
<el-input v-model.trim="form.imageName">
|
||||
<template v-if="form.fromRepo" #prepend>{{ loadDetailInfo(form.repoID) }}/</template>
|
||||
</el-input>
|
||||
|
@ -27,7 +27,7 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.image')" :rules="Rules.requiredInput" prop="name">
|
||||
<el-form-item :label="$t('container.image')" :rules="Rules.imageName" prop="name">
|
||||
<el-input v-model.trim="form.name">
|
||||
<template #prepend>{{ loadDetailInfo(form.repoID) }}/</template>
|
||||
</el-input>
|
||||
|
@ -45,7 +45,14 @@
|
||||
<el-table-column type="selection" fix />
|
||||
<el-table-column :label="$t('cronjob.taskName')" :min-width="120" prop="name">
|
||||
<template #default="{ row }">
|
||||
<el-link @click="loadDetail(row)" type="primary">{{ row.name }}</el-link>
|
||||
<el-tooltip effect="dark" :content="row.name" v-if="row.name.length > 12" placement="top">
|
||||
<el-link @click="loadDetail(row)" type="primary">
|
||||
{{ row.name.substring(0, 15) }}...
|
||||
</el-link>
|
||||
</el-tooltip>
|
||||
<el-link v-else @click="loadDetail(row)" type="primary">
|
||||
{{ row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.status')" :min-width="80" prop="status">
|
||||
|
@ -7,13 +7,19 @@
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-form-item :label="$t('cronjob.taskType')" prop="type">
|
||||
<el-select style="width: 100%" @change="changeType" v-model="dialogData.rowData!.type">
|
||||
<el-select
|
||||
v-if="dialogData.title === 'create'"
|
||||
style="width: 100%"
|
||||
@change="changeType"
|
||||
v-model="dialogData.rowData!.type"
|
||||
>
|
||||
<el-option value="shell" :label="$t('cronjob.shell')" />
|
||||
<el-option value="website" :label="$t('cronjob.website')" />
|
||||
<el-option value="database" :label="$t('cronjob.database')" />
|
||||
<el-option value="directory" :label="$t('cronjob.directory')" />
|
||||
<el-option value="curl" :label="$t('cronjob.curl')" />
|
||||
</el-select>
|
||||
<el-tag v-else>{{ dialogData.rowData!.type }}</el-tag>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('cronjob.taskName')" prop="name">
|
||||
@ -120,6 +126,8 @@
|
||||
<el-input-number
|
||||
:min="1"
|
||||
:max="30"
|
||||
step-strictly
|
||||
:step="1"
|
||||
v-model.number="dialogData.rowData!.retainCopies"
|
||||
></el-input-number>
|
||||
</el-form-item>
|
||||
@ -184,7 +192,9 @@ const dialogData = ref<DialogProps>({
|
||||
});
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
dialogData.value = params;
|
||||
changeType();
|
||||
if (dialogData.value.title === 'create') {
|
||||
changeType();
|
||||
}
|
||||
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
|
||||
drawerVisiable.value = true;
|
||||
checkMysqlInstalled();
|
||||
|
@ -126,153 +126,136 @@
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<el-card style="height: 362px">
|
||||
<el-form>
|
||||
<el-row v-if="hasScript()">
|
||||
<span>{{ $t('cronjob.shellContent') }}</span>
|
||||
<codemirror
|
||||
ref="mymirror"
|
||||
:autofocus="true"
|
||||
placeholder="None data"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="height: 100px; width: 100%; margin-top: 5px"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
:styleActiveLine="true"
|
||||
:extensions="extensions"
|
||||
v-model="dialogData.rowData!.script"
|
||||
:disabled="true"
|
||||
/>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="8" v-if="dialogData.rowData!.type === 'website'">
|
||||
<el-form-item :label="$t('cronjob.website')">
|
||||
{{ dialogData.rowData!.website }}
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" v-if="dialogData.rowData!.type === 'database'">
|
||||
<el-form-item :label="$t('cronjob.database')">
|
||||
{{ dialogData.rowData!.dbName }}
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" v-if="dialogData.rowData!.type === 'directory'">
|
||||
<el-form-item :label="$t('cronjob.directory')">
|
||||
<span v-if="dialogData.rowData!.sourceDir.length <= 20">
|
||||
{{ dialogData.rowData!.sourceDir }}
|
||||
</span>
|
||||
<div v-else>
|
||||
<el-popover
|
||||
placement="top-start"
|
||||
trigger="hover"
|
||||
width="250"
|
||||
:content="dialogData.rowData!.sourceDir"
|
||||
>
|
||||
<template #reference>
|
||||
{{ dialogData.rowData!.sourceDir.substring(0, 20) }}...
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" v-if="isBackup()">
|
||||
<el-form-item :label="$t('cronjob.target')">
|
||||
{{ dialogData.rowData!.targetDir }}
|
||||
<el-button
|
||||
v-if="currentRecord?.status! !== 'Failed'"
|
||||
type="primary"
|
||||
style="margin-left: 10px"
|
||||
link
|
||||
icon="Download"
|
||||
@click="onDownload(currentRecord, dialogData.rowData!.targetDirID)"
|
||||
>
|
||||
{{ $t('file.download') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" v-if="isBackup()">
|
||||
<el-form-item :label="$t('cronjob.retainCopies')">
|
||||
{{ dialogData.rowData!.retainCopies }}
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" v-if="dialogData.rowData!.type === 'curl'">
|
||||
<el-form-item :label="$t('cronjob.url')">
|
||||
{{ dialogData.rowData!.url }}
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col
|
||||
:span="8"
|
||||
v-if="dialogData.rowData!.type === 'website' || dialogData.rowData!.type === 'directory'"
|
||||
<el-form label-position="top">
|
||||
<el-row type="flex" justify="center">
|
||||
<el-form-item class="descriptionWide" v-if="isBackup()">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('cronjob.target') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ dialogData.rowData!.targetDir }}</span>
|
||||
<el-button
|
||||
v-if="currentRecord?.status! !== 'Failed'"
|
||||
type="primary"
|
||||
style="margin-left: 10px"
|
||||
link
|
||||
icon="Download"
|
||||
@click="onDownload(currentRecord, dialogData.rowData!.targetDirID)"
|
||||
>
|
||||
<el-form-item :label="$t('cronjob.exclusionRules')">
|
||||
<div v-if="dialogData.rowData!.exclusionRules">
|
||||
<div
|
||||
v-for="item in dialogData.rowData!.exclusionRules.split(';')"
|
||||
:key="item"
|
||||
>
|
||||
<el-tag>{{ item }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else>-</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item :label="$t('commons.search.timeStart')">
|
||||
{{ dateFormat(0, 0, currentRecord?.startTime) }}
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item :label="$t('commons.table.interval')">
|
||||
<span v-if="currentRecord?.interval! <= 1000">
|
||||
{{ currentRecord?.interval }} ms
|
||||
</span>
|
||||
<span v-if="currentRecord?.interval! > 1000">
|
||||
{{ currentRecord?.interval! / 1000 }} s
|
||||
</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item :label="$t('commons.table.status')">
|
||||
<el-tooltip
|
||||
v-if="currentRecord?.status === 'Failed'"
|
||||
class="box-item"
|
||||
:content="currentRecord?.message"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag type="danger">{{ $t('commons.table.statusFailed') }}</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tag type="success" v-if="currentRecord?.status === 'Success'">
|
||||
{{ $t('commons.table.statusSuccess') }}
|
||||
</el-tag>
|
||||
<el-tag type="info" v-if="currentRecord?.status === 'Waiting'">
|
||||
{{ $t('commons.table.statusWaiting') }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-if="currentRecord?.records">
|
||||
<span>{{ $t('commons.table.records') }}</span>
|
||||
<codemirror
|
||||
ref="mymirror"
|
||||
:autofocus="true"
|
||||
:placeholder="$t('cronjob.noLogs')"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="height: 130px; width: 100%; margin-top: 5px"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
:styleActiveLine="true"
|
||||
:extensions="extensions"
|
||||
v-model="currentRecordDetail"
|
||||
:disabled="true"
|
||||
/>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
{{ $t('file.download') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item class="description" v-if="dialogData.rowData!.type === 'website'">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('cronjob.website') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ dialogData.rowData!.website }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item class="description" v-if="dialogData.rowData!.type === 'database'">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('cronjob.database') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ dialogData.rowData!.dbName }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item class="description" v-if="dialogData.rowData!.type === 'directory'">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('cronjob.directory') }}</span>
|
||||
</template>
|
||||
<span v-if="dialogData.rowData!.sourceDir.length <= 12" class="status-count">
|
||||
{{ dialogData.rowData!.sourceDir }}
|
||||
</span>
|
||||
<div v-else>
|
||||
<el-popover
|
||||
placement="top-start"
|
||||
trigger="hover"
|
||||
width="250"
|
||||
:content="dialogData.rowData!.sourceDir"
|
||||
>
|
||||
<template #reference>
|
||||
<span class="status-count">
|
||||
{{ dialogData.rowData!.sourceDir.substring(0, 12) }}...
|
||||
</span>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item class="description" v-if="isBackup()">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('cronjob.retainCopies') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ dialogData.rowData!.retainCopies }}</span>
|
||||
</el-form-item>
|
||||
</el-row>
|
||||
<el-form-item
|
||||
class="description"
|
||||
v-if="dialogData.rowData!.type === 'website' || dialogData.rowData!.type === 'directory'"
|
||||
>
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('cronjob.exclusionRules') }}</span>
|
||||
</template>
|
||||
<div v-if="dialogData.rowData!.exclusionRules">
|
||||
<div v-for="item in dialogData.rowData!.exclusionRules.split(';')" :key="item">
|
||||
<el-tag>{{ item }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<span class="status-count" v-else>-</span>
|
||||
</el-form-item>
|
||||
<el-row type="flex" justify="center">
|
||||
<el-form-item class="descriptionWide">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('commons.search.timeStart') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ dateFormat(0, 0, currentRecord?.startTime) }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item class="description">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('commons.table.interval') }}</span>
|
||||
</template>
|
||||
<span class="status-count" v-if="currentRecord?.interval! <= 1000">
|
||||
{{ currentRecord?.interval }} ms
|
||||
</span>
|
||||
<span class="status-count" v-if="currentRecord?.interval! > 1000">
|
||||
{{ currentRecord?.interval! / 1000 }} s
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item class="description">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('commons.table.status') }}</span>
|
||||
</template>
|
||||
<el-tooltip
|
||||
v-if="currentRecord?.status === 'Failed'"
|
||||
class="box-item"
|
||||
:content="currentRecord?.message"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag type="danger">{{ $t('commons.table.statusFailed') }}</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tag type="success" v-if="currentRecord?.status === 'Success'">
|
||||
{{ $t('commons.table.statusSuccess') }}
|
||||
</el-tag>
|
||||
<el-tag type="info" v-if="currentRecord?.status === 'Waiting'">
|
||||
{{ $t('commons.table.statusWaiting') }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
</el-row>
|
||||
<el-row v-if="currentRecord?.records">
|
||||
<span>{{ $t('commons.table.records') }}</span>
|
||||
<codemirror
|
||||
ref="mymirror"
|
||||
:autofocus="true"
|
||||
:placeholder="$t('cronjob.noLogs')"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="height: calc(100vh - 484px); width: 100%; margin-top: 5px"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
:styleActiveLine="true"
|
||||
:extensions="extensions"
|
||||
v-model="currentRecordDetail"
|
||||
:disabled="true"
|
||||
/>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="app-warn" v-if="!hasRecords">
|
||||
@ -416,6 +399,8 @@ const onHandle = async (row: Cronjob.CronjobInfo) => {
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
searchInfo.page = 1;
|
||||
records.value = [];
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
@ -451,13 +436,14 @@ const search = async () => {
|
||||
endTime: searchInfo.endTime,
|
||||
status: searchInfo.status,
|
||||
};
|
||||
records.value = [];
|
||||
const res = await searchRecords(params);
|
||||
if (!res.data.items) {
|
||||
if (searchInfo.page === 1 && !res.data.items) {
|
||||
hasRecords.value = false;
|
||||
return;
|
||||
}
|
||||
records.value = res.data.items;
|
||||
for (const item of res.data.items) {
|
||||
records.value.push(item);
|
||||
}
|
||||
hasRecords.value = true;
|
||||
currentRecord.value = records.value[0];
|
||||
currentRecordIndex.value = 0;
|
||||
@ -490,7 +476,7 @@ const nextPage = async () => {
|
||||
if (searchInfo.pageSize >= searchInfo.recordTotal) {
|
||||
return;
|
||||
}
|
||||
searchInfo.pageSize = searchInfo.pageSize + 5;
|
||||
searchInfo.page = searchInfo.page + 1;
|
||||
search();
|
||||
};
|
||||
const forDetail = async (row: Cronjob.Record, index: number) => {
|
||||
@ -515,9 +501,6 @@ function isBackup() {
|
||||
dialogData.value.rowData!.type === 'directory'
|
||||
);
|
||||
}
|
||||
function hasScript() {
|
||||
return dialogData.value.rowData!.type === 'shell' || dialogData.value.rowData!.type === 'sync';
|
||||
}
|
||||
function loadWeek(i: number) {
|
||||
for (const week of weekOptions) {
|
||||
if (week.value === i) {
|
||||
@ -534,7 +517,7 @@ defineExpose({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.infinite-list {
|
||||
height: 310px;
|
||||
height: calc(100vh - 435px);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
@ -593,4 +576,10 @@ defineExpose({
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
.descriptionWide {
|
||||
width: 40%;
|
||||
}
|
||||
.description {
|
||||
width: 30%;
|
||||
}
|
||||
</style>
|
||||
|
@ -213,13 +213,6 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.status-count {
|
||||
font-size: 24px;
|
||||
}
|
||||
.status-label {
|
||||
font-size: 14px;
|
||||
color: #646a73;
|
||||
}
|
||||
.devider {
|
||||
display: block;
|
||||
height: 1px;
|
||||
|
@ -169,13 +169,6 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.status-count {
|
||||
font-size: 24px;
|
||||
}
|
||||
.status-label {
|
||||
font-size: 14px;
|
||||
color: #646a73;
|
||||
}
|
||||
.devider {
|
||||
display: block;
|
||||
height: 1px;
|
||||
|
@ -1,126 +1,146 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawerVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="title + $t('setting.backupAccount')" :back="handleClose" />
|
||||
</template>
|
||||
<el-form ref="formRef" v-loading="loading" label-position="top" :model="dialogData.rowData" label-width="120px">
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
|
||||
<el-tag>{{ $t('setting.' + dialogData.rowData!.type) }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="dialogData.rowData!.type === 'LOCAL'"
|
||||
:label="$t('setting.currentPath')"
|
||||
prop="varsJson['dir']"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-input v-model="dialogData.rowData!.varsJson['dir']">
|
||||
<template #prepend>
|
||||
<FileList @choose="loadDir" :dir="true"></FileList>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="hasBucket(dialogData.rowData!.type)"
|
||||
label="Access Key ID"
|
||||
prop="accessKey"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-input v-model.trim="dialogData.rowData!.accessKey" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="hasBucket(dialogData.rowData!.type)"
|
||||
label="Secret Key"
|
||||
prop="credential"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-input show-password clearable v-model.trim="dialogData.rowData!.credential" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="dialogData.rowData!.type === 'S3'"
|
||||
label="Region"
|
||||
prop="varsJson.region"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-input v-model.trim="dialogData.rowData!.varsJson['region']" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="hasBucket(dialogData.rowData!.type) && dialogData.rowData!.type !== 'MINIO'"
|
||||
label="Endpoint"
|
||||
prop="varsJson.endpoint"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-input v-model.trim="dialogData.rowData!.varsJson['endpoint']" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="dialogData.rowData!.type === 'MINIO'"
|
||||
label="Endpoint"
|
||||
prop="varsJson.endpointItem"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-input v-model="dialogData.rowData!.varsJson['endpointItem']">
|
||||
<template #prepend>
|
||||
<el-select v-model.trim="endpoints" style="width: 80px">
|
||||
<el-option label="http" value="http" />
|
||||
<el-option label="https" value="https" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="dialogData.rowData!.type !== '' && hasBucket(dialogData.rowData!.type)"
|
||||
label="Bucket"
|
||||
prop="bucket"
|
||||
>
|
||||
<el-select style="width: 80%" @change="errBuckets = false" v-model="dialogData.rowData!.bucket">
|
||||
<el-option v-for="item in buckets" :key="item" :value="item" />
|
||||
</el-select>
|
||||
<el-button style="width: 20%" plain @click="getBuckets(formRef)">
|
||||
{{ $t('setting.loadBucket') }}
|
||||
</el-button>
|
||||
<span v-if="errBuckets" class="input-error">{{ $t('commons.rule.requiredSelect') }}</span>
|
||||
</el-form-item>
|
||||
<div v-if="dialogData.rowData!.type === 'SFTP'">
|
||||
<el-form-item :label="$t('setting.address')" prop="varsJson.address" :rules="Rules.ip">
|
||||
<el-input v-model.trim="dialogData.rowData!.varsJson['address']" />
|
||||
<div v-loading="loading">
|
||||
<el-drawer v-model="drawerVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="title + $t('setting.backupAccount')" :back="handleClose" />
|
||||
</template>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="loading"
|
||||
label-position="top"
|
||||
:model="dialogData.rowData"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
|
||||
<el-tag>{{ $t('setting.' + dialogData.rowData!.type) }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.port')" prop="varsJson.port" :rules="[Rules.port]">
|
||||
<el-input-number
|
||||
:min="0"
|
||||
:max="65535"
|
||||
v-model.number="dialogData.rowData!.varsJson['port']"
|
||||
/>
|
||||
<el-form-item
|
||||
v-if="dialogData.rowData!.type === 'LOCAL'"
|
||||
:label="$t('setting.currentPath')"
|
||||
prop="varsJson['dir']"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-input v-model="dialogData.rowData!.varsJson['dir']">
|
||||
<template #prepend>
|
||||
<FileList @choose="loadDir" :dir="true"></FileList>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.username')" prop="accessKey" :rules="[Rules.requiredInput]">
|
||||
<el-input v-model="dialogData.rowData!.accessKey" />
|
||||
<el-form-item
|
||||
v-if="hasBucket(dialogData.rowData!.type)"
|
||||
label="Access Key ID"
|
||||
prop="accessKey"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-input v-model.trim="dialogData.rowData!.accessKey" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.password')" prop="credential" :rules="[Rules.requiredInput]">
|
||||
<el-input
|
||||
type="password"
|
||||
clearable
|
||||
show-password
|
||||
v-model="dialogData.rowData!.credential"
|
||||
/>
|
||||
<el-form-item
|
||||
v-if="hasBucket(dialogData.rowData!.type)"
|
||||
label="Secret Key"
|
||||
prop="credential"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-input show-password clearable v-model.trim="dialogData.rowData!.credential" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.path')" prop="bucket" :rules="[Rules.requiredInput]">
|
||||
<el-input v-model="dialogData.rowData!.bucket" />
|
||||
<el-form-item
|
||||
v-if="dialogData.rowData!.type === 'S3'"
|
||||
label="Region"
|
||||
prop="varsJson.region"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-input v-model.trim="dialogData.rowData!.varsJson['region']" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button :disabled="loading" @click="drawerVisiable = false">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
<el-form-item
|
||||
v-if="hasBucket(dialogData.rowData!.type) && dialogData.rowData!.type !== 'MINIO'"
|
||||
label="Endpoint"
|
||||
prop="varsJson.endpoint"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-input v-model.trim="dialogData.rowData!.varsJson['endpoint']" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="dialogData.rowData!.type === 'MINIO'"
|
||||
label="Endpoint"
|
||||
prop="varsJson.endpointItem"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-input v-model="dialogData.rowData!.varsJson['endpointItem']">
|
||||
<template #prepend>
|
||||
<el-select v-model.trim="endpoints" style="width: 80px">
|
||||
<el-option label="http" value="http" />
|
||||
<el-option label="https" value="https" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="dialogData.rowData!.type !== '' && hasBucket(dialogData.rowData!.type)"
|
||||
label="Bucket"
|
||||
prop="bucket"
|
||||
>
|
||||
<el-select
|
||||
style="width: 80%"
|
||||
@change="errBuckets = false"
|
||||
v-model="dialogData.rowData!.bucket"
|
||||
>
|
||||
<el-option v-for="item in buckets" :key="item" :value="item" />
|
||||
</el-select>
|
||||
<el-button style="width: 20%" plain @click="getBuckets(formRef)">
|
||||
{{ $t('setting.loadBucket') }}
|
||||
</el-button>
|
||||
<span v-if="errBuckets" class="input-error">{{ $t('commons.rule.requiredSelect') }}</span>
|
||||
</el-form-item>
|
||||
<div v-if="dialogData.rowData!.type === 'SFTP'">
|
||||
<el-form-item :label="$t('setting.address')" prop="varsJson.address" :rules="Rules.ip">
|
||||
<el-input v-model.trim="dialogData.rowData!.varsJson['address']" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.port')" prop="varsJson.port" :rules="[Rules.port]">
|
||||
<el-input-number
|
||||
:min="0"
|
||||
:max="65535"
|
||||
v-model.number="dialogData.rowData!.varsJson['port']"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('setting.username')"
|
||||
prop="accessKey"
|
||||
:rules="[Rules.requiredInput]"
|
||||
>
|
||||
<el-input v-model="dialogData.rowData!.accessKey" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('setting.password')"
|
||||
prop="credential"
|
||||
:rules="[Rules.requiredInput]"
|
||||
>
|
||||
<el-input
|
||||
type="password"
|
||||
clearable
|
||||
show-password
|
||||
v-model="dialogData.rowData!.credential"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.path')" prop="bucket" :rules="[Rules.requiredInput]">
|
||||
<el-input v-model="dialogData.rowData!.bucket" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button :disabled="loading" @click="drawerVisiable = false">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@ -213,7 +233,7 @@ const getBuckets = async (formEl: FormInstance | undefined) => {
|
||||
};
|
||||
|
||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!dialogData.value.rowData.bucket) {
|
||||
if (hasBucket(dialogData.value.rowData.type) && !dialogData.value.rowData.bucket) {
|
||||
errBuckets.value = true;
|
||||
return;
|
||||
}
|
||||
@ -228,16 +248,29 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
dialogData.value.rowData!.varsJson['endpointItem'] = undefined;
|
||||
}
|
||||
dialogData.value.rowData.vars = JSON.stringify(dialogData.value.rowData!.varsJson);
|
||||
loading.value = true;
|
||||
if (dialogData.value.title === 'create') {
|
||||
await addBackup(dialogData.value.rowData);
|
||||
await addBackup(dialogData.value.rowData)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
drawerVisiable.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
if (dialogData.value.title === 'edit') {
|
||||
await editBackup(dialogData.value.rowData);
|
||||
}
|
||||
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
drawerVisiable.value = false;
|
||||
await editBackup(dialogData.value.rowData)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
drawerVisiable.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -85,14 +85,3 @@ onMounted(() => {
|
||||
get();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.status-count {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-size: 14px;
|
||||
color: #646a73;
|
||||
}
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user