mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 08:19:15 +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)
|
# Dependency directories (remove the comment below to include it)
|
||||||
# vendor/
|
# vendor/
|
||||||
/build
|
|
||||||
/pkg/
|
/pkg/
|
||||||
backend/__debug_bin
|
backend/__debug_bin
|
||||||
cmd/server/__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) {
|
func (u *CronjobRepo) RecordFirst(id uint) (model.JobRecords, error) {
|
||||||
var record model.JobRecords
|
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
|
return record, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
"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/constant"
|
||||||
"github.com/1Panel-dev/1Panel/backend/global"
|
"github.com/1Panel-dev/1Panel/backend/global"
|
||||||
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
|
"github.com/1Panel-dev/1Panel/backend/utils/cloud_storage"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
@ -232,6 +232,9 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
|
|||||||
if backup.Type == "LOCAL" {
|
if backup.Type == "LOCAL" {
|
||||||
if dir, ok := varMap["dir"]; ok {
|
if dir, ok := varMap["dir"]; ok {
|
||||||
if dirStr, isStr := dir.(string); isStr {
|
if dirStr, isStr := dir.(string); isStr {
|
||||||
|
if strings.HasSuffix(dirStr, "/") {
|
||||||
|
dirStr = dirStr[:strings.LastIndex(dirStr, "/")]
|
||||||
|
}
|
||||||
if err := updateBackupDir(dirStr); err != nil {
|
if err := updateBackupDir(dirStr); err != nil {
|
||||||
upMap["vars"] = backup.Vars
|
upMap["vars"] = backup.Vars
|
||||||
_ = backupRepo.Update(req.ID, upMap)
|
_ = backupRepo.Update(req.ID, upMap)
|
||||||
@ -316,8 +319,7 @@ func updateBackupDir(dir string) error {
|
|||||||
if strings.HasSuffix(oldDir, "/") {
|
if strings.HasSuffix(oldDir, "/") {
|
||||||
oldDir = oldDir[:strings.LastIndex(oldDir, "/")]
|
oldDir = oldDir[:strings.LastIndex(oldDir, "/")]
|
||||||
}
|
}
|
||||||
cmd := exec.Command("cp", "-r", oldDir+"/*", dir)
|
stdout, err := cmd.Execf("cp -r %s/* %s", oldDir, dir)
|
||||||
stdout, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(string(stdout))
|
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
|
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":
|
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:
|
default:
|
||||||
return "", fmt.Errorf("not support type %s", cronjob.Type)
|
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 {
|
if err := copier.Copy(&cronjob, &req); err != nil {
|
||||||
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
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)
|
cronjob.Spec = loadSpec(cronjob)
|
||||||
if err := u.StartJob(&cronjob); err != nil {
|
if err := u.StartJob(&cronjob); err != nil {
|
||||||
return err
|
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"))
|
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)
|
backupDir = fmt.Sprintf("%s/%s/%s", localDir, cronjob.Type, cronjob.Name)
|
||||||
global.LOG.Infof("handle tar %s to %s", backupDir, fileName)
|
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
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,10 +197,20 @@ func (u *CronjobService) HandleRmExpired(backType, backupDir string, cronjob *mo
|
|||||||
if len(files) == 0 {
|
if len(files) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if cronjob.Type == "database" {
|
|
||||||
|
prefix := ""
|
||||||
|
switch cronjob.Type {
|
||||||
|
case "database":
|
||||||
|
prefix = "db_"
|
||||||
|
case "website":
|
||||||
|
prefix = "website_"
|
||||||
|
case "directory":
|
||||||
|
prefix = "directory_"
|
||||||
|
}
|
||||||
|
|
||||||
dbCopies := uint64(0)
|
dbCopies := uint64(0)
|
||||||
for i := len(files) - 1; i >= 0; i-- {
|
for i := len(files) - 1; i >= 0; i-- {
|
||||||
if strings.HasPrefix(files[i].Name(), "db_") {
|
if strings.HasPrefix(files[i].Name(), prefix) {
|
||||||
dbCopies++
|
dbCopies++
|
||||||
if dbCopies > cronjob.RetainCopies {
|
if dbCopies > cronjob.RetainCopies {
|
||||||
_ = os.Remove(backupDir + "/" + files[i].Name())
|
_ = os.Remove(backupDir + "/" + files[i].Name())
|
||||||
@ -208,11 +218,6 @@ func (u *CronjobService) HandleRmExpired(backType, backupDir string, cronjob *mo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} 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)))
|
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++ {
|
||||||
|
@ -84,7 +84,7 @@ const checkImageName = (rule: any, value: any, callback: any) => {
|
|||||||
if (value === '' || typeof value === 'undefined' || value == null) {
|
if (value === '' || typeof value === 'undefined' || value == null) {
|
||||||
callback(new Error(i18n.global.t('commons.rule.imageName')));
|
callback(new Error(i18n.global.t('commons.rule.imageName')));
|
||||||
} else {
|
} 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 !== '') {
|
if (!reg.test(value) && value !== '') {
|
||||||
callback(new Error(i18n.global.t('commons.rule.imageName')));
|
callback(new Error(i18n.global.t('commons.rule.imageName')));
|
||||||
} else {
|
} else {
|
||||||
|
@ -129,7 +129,7 @@ export default {
|
|||||||
userName: 'Support English, Chinese, numbers and _ length 3-30',
|
userName: 'Support English, Chinese, numbers and _ length 3-30',
|
||||||
simpleName: 'Support English, numbers and _ length 1-30',
|
simpleName: 'Support English, numbers and _ length 1-30',
|
||||||
dbName: 'Support English, Chinese, numbers, .-, and _ length 1-16',
|
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',
|
volumeName: 'Support English, numbers, .-_, length 1-30',
|
||||||
complexityPassword:
|
complexityPassword:
|
||||||
'Enter a password that is longer than eight characters and contains at least two letters, digits, and special characters',
|
'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',
|
userName: '支持英文、中文、数字和_,长度3-30',
|
||||||
simpleName: '支持英文、数字、_,长度1-30',
|
simpleName: '支持英文、数字、_,长度1-30',
|
||||||
dbName: '支持英文、中文、数字、.-_,长度1-16',
|
dbName: '支持英文、中文、数字、.-_,长度1-16',
|
||||||
imageName: '支持英文、中文、数字、:.-_,长度1-30',
|
imageName: '支持英文、数字、:/.-_,长度1-150',
|
||||||
volumeName: '支持英文、数字、.-和_,长度1-30',
|
volumeName: '支持英文、数字、.-和_,长度1-30',
|
||||||
complexityPassword: '请输入长度大于 8 位且包含字母、数字、特殊字符至少两项的密码组合',
|
complexityPassword: '请输入长度大于 8 位且包含字母、数字、特殊字符至少两项的密码组合',
|
||||||
commonPassword: '请输入 6 位以上长度密码',
|
commonPassword: '请输入 6 位以上长度密码',
|
||||||
|
@ -279,3 +279,10 @@
|
|||||||
.middle-center {
|
.middle-center {
|
||||||
vertical-align: middle;
|
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-option v-for="item in repos" :key="item.id" :value="item.id" :label="item.name" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</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">
|
<el-input v-model.trim="form.imageName">
|
||||||
<template v-if="form.fromRepo" #prepend>{{ loadDetailInfo(form.repoID) }}/</template>
|
<template v-if="form.fromRepo" #prepend>{{ loadDetailInfo(form.repoID) }}/</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</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">
|
<el-input v-model.trim="form.name">
|
||||||
<template #prepend>{{ loadDetailInfo(form.repoID) }}/</template>
|
<template #prepend>{{ loadDetailInfo(form.repoID) }}/</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
|
@ -45,7 +45,14 @@
|
|||||||
<el-table-column type="selection" fix />
|
<el-table-column type="selection" fix />
|
||||||
<el-table-column :label="$t('cronjob.taskName')" :min-width="120" prop="name">
|
<el-table-column :label="$t('cronjob.taskName')" :min-width="120" prop="name">
|
||||||
<template #default="{ row }">
|
<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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('commons.table.status')" :min-width="80" prop="status">
|
<el-table-column :label="$t('commons.table.status')" :min-width="80" prop="status">
|
||||||
|
@ -7,13 +7,19 @@
|
|||||||
<el-row type="flex" justify="center">
|
<el-row type="flex" justify="center">
|
||||||
<el-col :span="22">
|
<el-col :span="22">
|
||||||
<el-form-item :label="$t('cronjob.taskType')" prop="type">
|
<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="shell" :label="$t('cronjob.shell')" />
|
||||||
<el-option value="website" :label="$t('cronjob.website')" />
|
<el-option value="website" :label="$t('cronjob.website')" />
|
||||||
<el-option value="database" :label="$t('cronjob.database')" />
|
<el-option value="database" :label="$t('cronjob.database')" />
|
||||||
<el-option value="directory" :label="$t('cronjob.directory')" />
|
<el-option value="directory" :label="$t('cronjob.directory')" />
|
||||||
<el-option value="curl" :label="$t('cronjob.curl')" />
|
<el-option value="curl" :label="$t('cronjob.curl')" />
|
||||||
</el-select>
|
</el-select>
|
||||||
|
<el-tag v-else>{{ dialogData.rowData!.type }}</el-tag>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item :label="$t('cronjob.taskName')" prop="name">
|
<el-form-item :label="$t('cronjob.taskName')" prop="name">
|
||||||
@ -120,6 +126,8 @@
|
|||||||
<el-input-number
|
<el-input-number
|
||||||
:min="1"
|
:min="1"
|
||||||
:max="30"
|
:max="30"
|
||||||
|
step-strictly
|
||||||
|
:step="1"
|
||||||
v-model.number="dialogData.rowData!.retainCopies"
|
v-model.number="dialogData.rowData!.retainCopies"
|
||||||
></el-input-number>
|
></el-input-number>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -184,7 +192,9 @@ const dialogData = ref<DialogProps>({
|
|||||||
});
|
});
|
||||||
const acceptParams = (params: DialogProps): void => {
|
const acceptParams = (params: DialogProps): void => {
|
||||||
dialogData.value = params;
|
dialogData.value = params;
|
||||||
|
if (dialogData.value.title === 'create') {
|
||||||
changeType();
|
changeType();
|
||||||
|
}
|
||||||
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
|
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
|
||||||
drawerVisiable.value = true;
|
drawerVisiable.value = true;
|
||||||
checkMysqlInstalled();
|
checkMysqlInstalled();
|
||||||
|
@ -126,59 +126,13 @@
|
|||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="16">
|
<el-col :span="16">
|
||||||
<el-card style="height: 362px">
|
<el-form label-position="top">
|
||||||
<el-form>
|
<el-row type="flex" justify="center">
|
||||||
<el-row v-if="hasScript()">
|
<el-form-item class="descriptionWide" v-if="isBackup()">
|
||||||
<span>{{ $t('cronjob.shellContent') }}</span>
|
<template #label>
|
||||||
<codemirror
|
<span class="status-label">{{ $t('cronjob.target') }}</span>
|
||||||
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>
|
</template>
|
||||||
</el-popover>
|
<span class="status-count">{{ dialogData.rowData!.targetDir }}</span>
|
||||||
</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
|
<el-button
|
||||||
v-if="currentRecord?.status! !== 'Failed'"
|
v-if="currentRecord?.status! !== 'Failed'"
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -190,52 +144,83 @@
|
|||||||
{{ $t('file.download') }}
|
{{ $t('file.download') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
<el-form-item class="description" v-if="dialogData.rowData!.type === 'website'">
|
||||||
<el-col :span="8" v-if="isBackup()">
|
<template #label>
|
||||||
<el-form-item :label="$t('cronjob.retainCopies')">
|
<span class="status-label">{{ $t('cronjob.website') }}</span>
|
||||||
{{ dialogData.rowData!.retainCopies }}
|
</template>
|
||||||
|
<span class="status-count">{{ dialogData.rowData!.website }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
<el-form-item class="description" v-if="dialogData.rowData!.type === 'database'">
|
||||||
<el-col :span="8" v-if="dialogData.rowData!.type === 'curl'">
|
<template #label>
|
||||||
<el-form-item :label="$t('cronjob.url')">
|
<span class="status-label">{{ $t('cronjob.database') }}</span>
|
||||||
{{ dialogData.rowData!.url }}
|
</template>
|
||||||
|
<span class="status-count">{{ dialogData.rowData!.dbName }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
<el-form-item class="description" v-if="dialogData.rowData!.type === 'directory'">
|
||||||
<el-col
|
<template #label>
|
||||||
:span="8"
|
<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'"
|
v-if="dialogData.rowData!.type === 'website' || dialogData.rowData!.type === 'directory'"
|
||||||
>
|
>
|
||||||
<el-form-item :label="$t('cronjob.exclusionRules')">
|
<template #label>
|
||||||
|
<span class="status-label">{{ $t('cronjob.exclusionRules') }}</span>
|
||||||
|
</template>
|
||||||
<div v-if="dialogData.rowData!.exclusionRules">
|
<div v-if="dialogData.rowData!.exclusionRules">
|
||||||
<div
|
<div v-for="item in dialogData.rowData!.exclusionRules.split(';')" :key="item">
|
||||||
v-for="item in dialogData.rowData!.exclusionRules.split(';')"
|
|
||||||
:key="item"
|
|
||||||
>
|
|
||||||
<el-tag>{{ item }}</el-tag>
|
<el-tag>{{ item }}</el-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span v-else>-</span>
|
<span class="status-count" v-else>-</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
<el-row type="flex" justify="center">
|
||||||
</el-row>
|
<el-form-item class="descriptionWide">
|
||||||
<el-row>
|
<template #label>
|
||||||
<el-col :span="8">
|
<span class="status-label">{{ $t('commons.search.timeStart') }}</span>
|
||||||
<el-form-item :label="$t('commons.search.timeStart')">
|
</template>
|
||||||
{{ dateFormat(0, 0, currentRecord?.startTime) }}
|
<span class="status-count">{{ dateFormat(0, 0, currentRecord?.startTime) }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
<el-form-item class="description">
|
||||||
<el-col :span="8">
|
<template #label>
|
||||||
<el-form-item :label="$t('commons.table.interval')">
|
<span class="status-label">{{ $t('commons.table.interval') }}</span>
|
||||||
<span v-if="currentRecord?.interval! <= 1000">
|
</template>
|
||||||
|
<span class="status-count" v-if="currentRecord?.interval! <= 1000">
|
||||||
{{ currentRecord?.interval }} ms
|
{{ currentRecord?.interval }} ms
|
||||||
</span>
|
</span>
|
||||||
<span v-if="currentRecord?.interval! > 1000">
|
<span class="status-count" v-if="currentRecord?.interval! > 1000">
|
||||||
{{ currentRecord?.interval! / 1000 }} s
|
{{ currentRecord?.interval! / 1000 }} s
|
||||||
</span>
|
</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
<el-form-item class="description">
|
||||||
<el-col :span="8">
|
<template #label>
|
||||||
<el-form-item :label="$t('commons.table.status')">
|
<span class="status-label">{{ $t('commons.table.status') }}</span>
|
||||||
|
</template>
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
v-if="currentRecord?.status === 'Failed'"
|
v-if="currentRecord?.status === 'Failed'"
|
||||||
class="box-item"
|
class="box-item"
|
||||||
@ -251,7 +236,6 @@
|
|||||||
{{ $t('commons.table.statusWaiting') }}
|
{{ $t('commons.table.statusWaiting') }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row v-if="currentRecord?.records">
|
<el-row v-if="currentRecord?.records">
|
||||||
<span>{{ $t('commons.table.records') }}</span>
|
<span>{{ $t('commons.table.records') }}</span>
|
||||||
@ -261,7 +245,7 @@
|
|||||||
:placeholder="$t('cronjob.noLogs')"
|
:placeholder="$t('cronjob.noLogs')"
|
||||||
:indent-with-tab="true"
|
:indent-with-tab="true"
|
||||||
:tabSize="4"
|
:tabSize="4"
|
||||||
style="height: 130px; width: 100%; margin-top: 5px"
|
style="height: calc(100vh - 484px); width: 100%; margin-top: 5px"
|
||||||
:lineWrapping="true"
|
:lineWrapping="true"
|
||||||
:matchBrackets="true"
|
:matchBrackets="true"
|
||||||
theme="cobalt"
|
theme="cobalt"
|
||||||
@ -272,7 +256,6 @@
|
|||||||
/>
|
/>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-card>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<div class="app-warn" v-if="!hasRecords">
|
<div class="app-warn" v-if="!hasRecords">
|
||||||
@ -416,6 +399,8 @@ 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.page = 1;
|
||||||
|
records.value = [];
|
||||||
search();
|
search();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@ -451,13 +436,14 @@ const search = async () => {
|
|||||||
endTime: searchInfo.endTime,
|
endTime: searchInfo.endTime,
|
||||||
status: searchInfo.status,
|
status: searchInfo.status,
|
||||||
};
|
};
|
||||||
records.value = [];
|
|
||||||
const res = await searchRecords(params);
|
const res = await searchRecords(params);
|
||||||
if (!res.data.items) {
|
if (searchInfo.page === 1 && !res.data.items) {
|
||||||
hasRecords.value = false;
|
hasRecords.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
records.value = res.data.items;
|
for (const item of res.data.items) {
|
||||||
|
records.value.push(item);
|
||||||
|
}
|
||||||
hasRecords.value = true;
|
hasRecords.value = true;
|
||||||
currentRecord.value = records.value[0];
|
currentRecord.value = records.value[0];
|
||||||
currentRecordIndex.value = 0;
|
currentRecordIndex.value = 0;
|
||||||
@ -490,7 +476,7 @@ const nextPage = async () => {
|
|||||||
if (searchInfo.pageSize >= searchInfo.recordTotal) {
|
if (searchInfo.pageSize >= searchInfo.recordTotal) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
searchInfo.pageSize = searchInfo.pageSize + 5;
|
searchInfo.page = searchInfo.page + 1;
|
||||||
search();
|
search();
|
||||||
};
|
};
|
||||||
const forDetail = async (row: Cronjob.Record, index: number) => {
|
const forDetail = async (row: Cronjob.Record, index: number) => {
|
||||||
@ -515,9 +501,6 @@ function isBackup() {
|
|||||||
dialogData.value.rowData!.type === 'directory'
|
dialogData.value.rowData!.type === 'directory'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function hasScript() {
|
|
||||||
return dialogData.value.rowData!.type === 'shell' || dialogData.value.rowData!.type === 'sync';
|
|
||||||
}
|
|
||||||
function loadWeek(i: number) {
|
function loadWeek(i: number) {
|
||||||
for (const week of weekOptions) {
|
for (const week of weekOptions) {
|
||||||
if (week.value === i) {
|
if (week.value === i) {
|
||||||
@ -534,7 +517,7 @@ defineExpose({
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.infinite-list {
|
.infinite-list {
|
||||||
height: 310px;
|
height: calc(100vh - 435px);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
@ -593,4 +576,10 @@ defineExpose({
|
|||||||
height: 300px;
|
height: 300px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.descriptionWide {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -213,13 +213,6 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.status-count {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
.status-label {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #646a73;
|
|
||||||
}
|
|
||||||
.devider {
|
.devider {
|
||||||
display: block;
|
display: block;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
|
@ -169,13 +169,6 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.status-count {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
.status-label {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #646a73;
|
|
||||||
}
|
|
||||||
.devider {
|
.devider {
|
||||||
display: block;
|
display: block;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div v-loading="loading">
|
||||||
<el-drawer v-model="drawerVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
<el-drawer v-model="drawerVisiable" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
|
||||||
<template #header>
|
<template #header>
|
||||||
<DrawerHeader :header="title + $t('setting.backupAccount')" :back="handleClose" />
|
<DrawerHeader :header="title + $t('setting.backupAccount')" :back="handleClose" />
|
||||||
</template>
|
</template>
|
||||||
<el-form ref="formRef" v-loading="loading" label-position="top" :model="dialogData.rowData" label-width="120px">
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
v-loading="loading"
|
||||||
|
label-position="top"
|
||||||
|
:model="dialogData.rowData"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
<el-row type="flex" justify="center">
|
<el-row type="flex" justify="center">
|
||||||
<el-col :span="22">
|
<el-col :span="22">
|
||||||
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
|
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
|
||||||
@ -73,7 +80,11 @@
|
|||||||
label="Bucket"
|
label="Bucket"
|
||||||
prop="bucket"
|
prop="bucket"
|
||||||
>
|
>
|
||||||
<el-select style="width: 80%" @change="errBuckets = false" v-model="dialogData.rowData!.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-option v-for="item in buckets" :key="item" :value="item" />
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-button style="width: 20%" plain @click="getBuckets(formRef)">
|
<el-button style="width: 20%" plain @click="getBuckets(formRef)">
|
||||||
@ -92,10 +103,18 @@
|
|||||||
v-model.number="dialogData.rowData!.varsJson['port']"
|
v-model.number="dialogData.rowData!.varsJson['port']"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('setting.username')" prop="accessKey" :rules="[Rules.requiredInput]">
|
<el-form-item
|
||||||
|
:label="$t('setting.username')"
|
||||||
|
prop="accessKey"
|
||||||
|
:rules="[Rules.requiredInput]"
|
||||||
|
>
|
||||||
<el-input v-model="dialogData.rowData!.accessKey" />
|
<el-input v-model="dialogData.rowData!.accessKey" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('setting.password')" prop="credential" :rules="[Rules.requiredInput]">
|
<el-form-item
|
||||||
|
:label="$t('setting.password')"
|
||||||
|
prop="credential"
|
||||||
|
:rules="[Rules.requiredInput]"
|
||||||
|
>
|
||||||
<el-input
|
<el-input
|
||||||
type="password"
|
type="password"
|
||||||
clearable
|
clearable
|
||||||
@ -121,6 +140,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -213,7 +233,7 @@ const getBuckets = async (formEl: FormInstance | undefined) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = 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;
|
errBuckets.value = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -228,16 +248,29 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
|
|||||||
dialogData.value.rowData!.varsJson['endpointItem'] = undefined;
|
dialogData.value.rowData!.varsJson['endpointItem'] = undefined;
|
||||||
}
|
}
|
||||||
dialogData.value.rowData.vars = JSON.stringify(dialogData.value.rowData!.varsJson);
|
dialogData.value.rowData.vars = JSON.stringify(dialogData.value.rowData!.varsJson);
|
||||||
|
loading.value = true;
|
||||||
if (dialogData.value.title === 'create') {
|
if (dialogData.value.title === 'create') {
|
||||||
await addBackup(dialogData.value.rowData);
|
await addBackup(dialogData.value.rowData)
|
||||||
}
|
.then(() => {
|
||||||
if (dialogData.value.title === 'edit') {
|
loading.value = false;
|
||||||
await editBackup(dialogData.value.rowData);
|
|
||||||
}
|
|
||||||
|
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
emit('search');
|
emit('search');
|
||||||
drawerVisiable.value = false;
|
drawerVisiable.value = false;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.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();
|
get();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.status-count {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-label {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #646a73;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user