fix: 解决计划任务部分 bug

This commit is contained in:
ssongliu 2023-03-10 00:27:41 +08:00 committed by ssongliu
parent 6da43d7eb0
commit 015baec864
18 changed files with 365 additions and 333 deletions

1
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 位以上长度密码',

View File

@ -279,3 +279,10 @@
.middle-center {
vertical-align: middle;
}
.status-count {
font-size: 24px;
}
.status-label {
font-size: 14px;
color: #646a73;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -85,14 +85,3 @@ onMounted(() => {
get();
});
</script>
<style lang="scss" scoped>
.status-count {
font-size: 24px;
}
.status-label {
font-size: 14px;
color: #646a73;
}
</style>