Compare commits

...
13 Commits
21 changed files with 246 additions and 164 deletions
+1 -1
View File
@@ -397,7 +397,7 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
}
index++
}
if app.Limit == 0 && appInstall.Name != serviceName {
if app.Limit == 0 && appInstall.Name != serviceName && len(servicesMap) == 1 {
servicesMap[appInstall.Name] = servicesMap[serviceName]
delete(servicesMap, serviceName)
serviceName = appInstall.Name
+1 -1
View File
@@ -1066,7 +1066,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]respons
}
var versions []string
for _, detail := range details {
if detail.IgnoreUpgrade {
if detail.IgnoreUpgrade || installed.Version == "latest" {
continue
}
if common.IsCrossVersion(installed.Version, detail.Version) && !app.CrossVersionUpdate {
+10 -2
View File
@@ -109,6 +109,10 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (
if err != nil {
return nil, err
}
httpsSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
if err != nil {
return nil, err
}
lifeTime, err := strconv.Atoi(setting.Value)
if err != nil {
return nil, err
@@ -129,7 +133,7 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (
sessionUser, err := global.SESSION.Get(sID)
if err != nil {
sID = uuid.New().String()
c.SetCookie(constant.SessionName, sID, 0, "", "", false, false)
c.SetCookie(constant.SessionName, sID, 0, "", "", httpsSetting.Value == "enable", false)
err := global.SESSION.Set(sID, sessionUser, lifeTime)
if err != nil {
return nil, err
@@ -144,9 +148,13 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (
}
func (u *AuthService) LogOut(c *gin.Context) error {
httpsSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
if err != nil {
return err
}
sID, _ := c.Cookie(constant.SessionName)
if sID != "" {
c.SetCookie(constant.SessionName, sID, -1, "", "", false, false)
c.SetCookie(constant.SessionName, sID, -1, "", "", httpsSetting.Value == "enable", false)
err := global.SESSION.Delete(sID)
if err != nil {
return err
+12 -16
View File
@@ -266,22 +266,16 @@ func (u *BackupService) BatchDeleteRecord(ids []uint) error {
return err
}
for _, record := range records {
if record.Source == "LOCAL" {
if err := os.Remove(record.FileDir + "/" + record.FileName); err != nil {
global.LOG.Errorf("remove file %s failed, err: %v", record.FileDir+record.FileName, err)
}
} else {
backupAccount, err := backupRepo.Get(commonRepo.WithByType(record.Source))
if err != nil {
return err
}
client, err := u.NewClient(&backupAccount)
if err != nil {
return err
}
if _, err = client.Delete(record.FileDir + record.FileName); err != nil {
global.LOG.Errorf("remove file %s from %s failed, err: %v", record.FileDir+record.FileName, record.Source, err)
}
backupAccount, err := backupRepo.Get(commonRepo.WithByType(record.Source))
if err != nil {
return err
}
client, err := u.NewClient(&backupAccount)
if err != nil {
return err
}
if _, err = client.Delete(path.Join(record.FileDir, record.FileName)); err != nil {
global.LOG.Errorf("remove file %s from %s failed, err: %v", path.Join(record.FileDir, record.FileName), record.Source, err)
}
}
return backupRepo.DeleteRecord(context.Background(), commonRepo.WithIdsIn(ids))
@@ -481,6 +475,8 @@ func (u *BackupService) loadRecordSize(records []model.BackupRecord) ([]dto.Back
datas = append(datas, item)
wg.Done()
}(i)
} else {
datas = append(datas, item)
}
}
wg.Wait()
+6
View File
@@ -198,6 +198,9 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
}
_ = os.Remove(path.Join(secretDir, "server.crt"))
_ = os.Remove(path.Join(secretDir, "server.key"))
sID, _ := c.Cookie(constant.SessionName)
c.SetCookie(constant.SessionName, sID, 0, "", "", false, false)
go func() {
_, err := cmd.Exec("systemctl restart 1panel.service")
if err != nil {
@@ -289,6 +292,9 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
if err := settingRepo.Update("SSL", req.SSL); err != nil {
return err
}
sID, _ := c.Cookie(constant.SessionName)
c.SetCookie(constant.SessionName, sID, 0, "", "", true, false)
go func() {
time.Sleep(1 * time.Second)
_, err := cmd.Exec("systemctl restart 1panel.service")
-8
View File
@@ -77,10 +77,6 @@ func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
if !strings.HasPrefix(shortName, "1panel_v") || !strings.HasSuffix(shortName, ".tar.gz") || len(nameItems) != 3 {
return fmt.Errorf("incorrect snapshot name format of %s", shortName)
}
formatTime, err := time.Parse("20060102150405", strings.ReplaceAll(nameItems[2], ".tar.gz", ""))
if err != nil {
return fmt.Errorf("incorrect snapshot name format of %s", shortName)
}
if strings.HasSuffix(snap, ".tar.gz") {
snap = strings.ReplaceAll(snap, ".tar.gz", "")
}
@@ -91,10 +87,6 @@ func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
Version: nameItems[1],
Description: req.Description,
Status: constant.StatusSuccess,
BaseModel: model.BaseModel{
CreatedAt: formatTime,
UpdatedAt: formatTime,
},
}
if err := snapshotRepo.Create(&itemSnap); err != nil {
return err
+2
View File
@@ -70,6 +70,8 @@ func Init() {
migrations.UpdateCronjobSpec,
migrations.UpdateBackupRecordPath,
migrations.UpdateSnapshotRecords,
migrations.UpdateWebDavConf,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
+38 -3
View File
@@ -316,7 +316,7 @@ var UpdateCronjobSpec = &gormigrate.Migration{
var records []model.JobRecords
_ = tx.Where("cronjob_id = ?", job.ID).Find(&records).Error
for _, record := range records {
if job.Type == "snapshot" {
if job.Type == "snapshot" && record.Status == constant.StatusSuccess {
var snaps []model.Snapshot
_ = tx.Where("name like ?", "snapshot_"+"%").Find(&snaps).Error
for _, snap := range snaps {
@@ -329,12 +329,15 @@ var UpdateCronjobSpec = &gormigrate.Migration{
FileName: snap.Name + ".tar.gz",
Source: snap.From,
BackupType: snap.From,
BaseModel: model.BaseModel{
CreatedAt: job.CreatedAt,
},
}
_ = tx.Create(&item).Error
}
continue
}
if job.Type == "log" {
if job.Type == "log" && record.Status == constant.StatusSuccess {
item := model.BackupRecord{
From: "cronjob",
CronjobID: job.ID,
@@ -344,11 +347,14 @@ var UpdateCronjobSpec = &gormigrate.Migration{
FileName: path.Base(record.File),
Source: mapAccount[uint(job.TargetDirID)].Type,
BackupType: mapAccount[uint(job.TargetDirID)].Type,
BaseModel: model.BaseModel{
CreatedAt: job.CreatedAt,
},
}
_ = tx.Create(&item).Error
continue
}
if job.Type == "directory" {
if job.Type == "directory" && record.Status == constant.StatusSuccess {
item := model.BackupRecord{
From: "cronjob",
CronjobID: job.ID,
@@ -357,6 +363,9 @@ var UpdateCronjobSpec = &gormigrate.Migration{
FileDir: path.Dir(strings.TrimPrefix(record.File, itemPath)),
FileName: path.Base(record.File),
BackupType: mapAccount[uint(job.TargetDirID)].Type,
BaseModel: model.BaseModel{
CreatedAt: job.CreatedAt,
},
}
if record.FromLocal {
item.Source = constant.Local
@@ -442,3 +451,29 @@ var UpdateSnapshotRecords = &gormigrate.Migration{
return nil
},
}
var UpdateWebDavConf = &gormigrate.Migration{
ID: "20240205-update-webdav-conf",
Migrate: func(tx *gorm.DB) error {
var backup model.BackupAccount
_ = tx.Where("type = ?", constant.WebDAV).First(&backup).Error
if backup.ID == 0 {
return nil
}
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return err
}
delete(varMap, "addressItem")
if port, ok := varMap["port"]; ok {
varMap["address"] = fmt.Sprintf("%s:%v", varMap["address"], port)
delete(varMap, "port")
}
vars, _ := json.Marshal(varMap)
if err := tx.Model(&model.BackupAccount{}).Where("id = ?", backup.ID).Updates(map[string]interface{}{"vars": string(vars)}).Error; err != nil {
return err
}
return nil
},
}
@@ -34,6 +34,9 @@ func NewSftpClient(vars map[string]interface{}) (*sftpClient, error) {
return nil
},
}
if _, err := ssh.Dial("tcp", fmt.Sprintf("%s:%s", address, port), clientConfig); err != nil {
return nil, err
}
return &sftpClient{bucket: bucket, connInfo: fmt.Sprintf("%s:%s", address, port), config: clientConfig}, nil
}
+10 -9
View File
@@ -6,6 +6,7 @@ import (
"io"
"net/http"
"os"
"path"
"strings"
"github.com/studio-b12/gowebdav"
@@ -43,7 +44,7 @@ func NewWebDAVClient(vars map[string]interface{}) (*webDAVClient, error) {
}
func (s webDAVClient) Upload(src, target string) (bool, error) {
targetFilePath := s.Bucket + "/" + target
targetFilePath := path.Join(s.Bucket, target)
srcFile, err := os.Open(src)
if err != nil {
return false, err
@@ -62,7 +63,7 @@ func (s webDAVClient) ListBuckets() ([]interface{}, error) {
}
func (s webDAVClient) Download(src, target string) (bool, error) {
srcPath := s.Bucket + "/" + src
srcPath := path.Join(s.Bucket, src)
info, err := s.client.Stat(srcPath)
if err != nil {
return false, err
@@ -85,30 +86,30 @@ func (s webDAVClient) Download(src, target string) (bool, error) {
return true, err
}
func (s webDAVClient) Exist(path string) (bool, error) {
if _, err := s.client.Stat(s.Bucket + "/" + path); err != nil {
func (s webDAVClient) Exist(pathItem string) (bool, error) {
if _, err := s.client.Stat(path.Join(s.Bucket, pathItem)); err != nil {
return false, err
}
return true, nil
}
func (s webDAVClient) Size(path string) (int64, error) {
file, err := s.client.Stat(s.Bucket + "/" + path)
func (s webDAVClient) Size(pathItem string) (int64, error) {
file, err := s.client.Stat(path.Join(s.Bucket, pathItem))
if err != nil {
return 0, err
}
return file.Size(), nil
}
func (s webDAVClient) Delete(filePath string) (bool, error) {
if err := s.client.Remove(s.Bucket + "/" + filePath); err != nil {
func (s webDAVClient) Delete(pathItem string) (bool, error) {
if err := s.client.Remove(path.Join(s.Bucket, pathItem)); err != nil {
return false, err
}
return true, nil
}
func (s webDAVClient) ListObjects(prefix string) ([]string, error) {
files, err := s.client.ReadDir(s.Bucket + "/" + prefix)
files, err := s.client.ReadDir(path.Join(s.Bucket, prefix))
if err != nil {
return nil, err
}
+2 -1
View File
@@ -827,7 +827,7 @@ const message = {
retainCopiesHelper: 'Number of copies to retain for execution records and logs',
retainCopiesHelper1: 'Number of copies to retain for backup files',
retainCopiesUnit: ' copies (View)',
cronSpecRule: 'Please enter a correct lifecycle',
cronSpecRule: 'The execution period format in line {0} is incorrect. Please check and try again!',
perMonth: 'Every monthly',
perWeek: 'Every week',
perHour: 'Every hour',
@@ -1250,6 +1250,7 @@ const message = {
MINIO: 'MINIO',
SFTP: 'SFTP',
WebDAV: 'WebDAV',
WebDAVAlist: 'WebDAV connect Alist can refer to the official documentation',
OneDrive: 'Microsoft OneDrive',
isCN: 'Century Internet',
isNotCN: 'International Version',
+3 -2
View File
@@ -787,7 +787,7 @@ const message = {
retainCopiesHelper: '執行記錄及日誌保留份数',
retainCopiesHelper1: '備份文件保留份数',
retainCopiesUnit: ' 份 (查看)',
cronSpecRule: '請輸入正確的執行周期',
cronSpecRule: '第 {0} 行中執行週期格式錯誤,請檢查後重試!',
perMonth: '每月',
perWeek: '每周',
perHour: '每小時',
@@ -1173,6 +1173,7 @@ const message = {
MINIO: 'MINIO',
SFTP: 'SFTP',
WebDAV: 'WebDAV',
WebDAVAlist: 'WebDAV 連接 Alist 可參考官方文檔',
OneDrive: '微軟 OneDrive',
isCN: '世紀互聯',
isNotCN: '國際版',
@@ -1183,7 +1184,7 @@ const message = {
refreshTime: '令牌刷新時間',
refreshStatus: '令牌刷新狀態',
codeWarning: '當前授權碼格式錯誤,請重新確認!',
backupDir: '備份路徑',
backupDir: '備份目录',
code: '授權碼',
codeHelper:
'請點擊獲取按鈕,然後登錄 OneDrive 復製跳轉鏈接中 code 後面的內容,粘貼到該輸入框中,具體操作可參考官方文檔。',
+3 -2
View File
@@ -788,7 +788,7 @@ const message = {
retainCopiesHelper: '执行记录及日志保留份数',
retainCopiesHelper1: '备份文件保留份数',
retainCopiesUnit: ' 份 (查看)',
cronSpecRule: '请输入正确的执行周期',
cronSpecRule: '第 {0} 行中执行周期格式错误,请检查后重试!',
perMonth: '每月',
perWeek: '每周',
perHour: '每小时',
@@ -1174,6 +1174,7 @@ const message = {
MINIO: 'MINIO',
SFTP: 'SFTP',
WebDAV: 'WebDAV',
WebDAVAlist: 'WebDAV 连接 Alist 可参考官方文档',
OneDrive: '微软 OneDrive',
isCN: '世纪互联',
isNotCN: '国际版',
@@ -1184,7 +1185,7 @@ const message = {
refreshTime: '令牌刷新时间',
refreshStatus: '令牌刷新状态',
codeWarning: '当前授权码格式错误,请重新确认!',
backupDir: '备份路径',
backupDir: '备份目录',
code: '授权码',
codeHelper:
'请点击获取按钮,然后登录 OneDrive 复制跳转链接中 code 后面的内容,粘贴到该输入框中,具体操作可参考官方文档。',
+30 -30
View File
@@ -5,7 +5,7 @@
</template>
<div class="brief" v-loading="loadingApp">
<div class="detail flex">
<div class="w-12 h-12 bg-gray-100 rounded p-1 shadow-md icon">
<div class="w-12 h-12 rounded p-1 shadow-md icon">
<img :src="app.icon" alt="App Icon" class="w-full h-full rounded" />
</div>
<div class="ml-4">
@@ -20,7 +20,6 @@
<br />
<div v-if="!loadingDetail" class="mb-2">
<el-alert
style="width: 300px"
v-if="!appDetail.enable"
:title="$t('app.limitHelper')"
type="warning"
@@ -39,33 +38,31 @@
</div>
</div>
<div class="divider"></div>
<div>
<el-row>
<el-col :span="12">
<div class="descriptions">
<el-descriptions direction="vertical">
<el-descriptions-item>
<el-link @click="toLink(app.website)">
<el-icon><OfficeBuilding /></el-icon>
<span>{{ $t('app.appOfficeWebsite') }}</span>
</el-link>
</el-descriptions-item>
<el-descriptions-item>
<el-link @click="toLink(app.document)">
<el-icon><Document /></el-icon>
<span>{{ $t('app.document') }}</span>
</el-link>
</el-descriptions-item>
<el-descriptions-item>
<el-link @click="toLink(app.github)">
<el-icon><Link /></el-icon>
<span>{{ $t('app.github') }}</span>
</el-link>
</el-descriptions-item>
</el-descriptions>
</div>
</el-col>
</el-row>
<div class="descriptions">
<div>
<el-descriptions direction="vertical">
<el-descriptions-item>
<div class="icons">
<el-link @click="toLink(app.website)">
<el-icon><OfficeBuilding /></el-icon>
<span>{{ $t('app.appOfficeWebsite') }}</span>
</el-link>
</div>
</el-descriptions-item>
<el-descriptions-item>
<el-link @click="toLink(app.document)">
<el-icon><Document /></el-icon>
<span>{{ $t('app.document') }}</span>
</el-link>
</el-descriptions-item>
<el-descriptions-item>
<el-link @click="toLink(app.github)">
<el-icon><Link /></el-icon>
<span>{{ $t('app.github') }}</span>
</el-link>
</el-descriptions-item>
</el-descriptions>
</div>
</div>
</div>
<MdEditor
@@ -161,11 +158,11 @@ defineExpose({
<style lang="scss">
.brief {
padding: 10px;
.name {
span {
font-weight: 500;
font-size: 18px;
color: var(--el-text-color-regular);
}
}
@@ -188,6 +185,9 @@ defineExpose({
.descriptions {
margin-top: 5px;
.icons {
margin-left: 20px;
}
}
}
</style>
@@ -31,7 +31,7 @@
<el-checkbox v-model="operateReq.backup" :label="$t('app.backupApp')" />
<span class="input-help">{{ $t('app.backupAppHelper') }}</span>
</el-form-item>
<el-form-item pro="pullImage">
<el-form-item pro="pullImage" v-if="operateReq.operate === 'upgrade'">
<el-checkbox v-model="operateReq.pullImage" :label="$t('container.forcePull')" size="large" />
<span class="input-help">{{ $t('container.forcePullHelper') }}</span>
</el-form-item>
@@ -98,12 +98,12 @@
<el-table-column type="selection" />
<el-table-column
:label="$t('commons.table.name')"
:width="mobile ? 300 : '200'"
:width="mobile ? 300 : 200"
min-width="100"
prop="name"
sortable
fix
:fixed="mobile ? 'false' : 'left'"
:fixed="mobile ? false : 'left'"
>
<template #default="{ row }">
<Tooltip @click="onInspect(row.containerID)" :text="row.name" />
+5 -21
View File
@@ -78,6 +78,11 @@ function loadWeek(i: number) {
export function loadDefaultSpec(type: string) {
let item = {} as Cronjob.SpecObj;
item.week = 0;
item.day = 0;
item.hour = 0;
item.minute = 0;
item.second = 0;
switch (type) {
case 'shell':
item.specType = 'perWeek';
@@ -124,27 +129,6 @@ export function loadDefaultSpec(type: string) {
return item;
}
export function checkScript(specType: string, week, day, hour, minute, second) {
switch (specType) {
case 'perMonth':
return day > 0 && day < 32 && hour >= 0 && hour < 24 && minute >= 0 && minute < 60;
case 'perWeek':
return week >= 0 && week < 7 && hour >= 0 && hour < 24 && minute >= 0 && minute < 60;
case 'perDay':
return hour >= 0 && hour < 24 && minute >= 0 && minute < 60;
case 'perHour':
return minute >= 0 && minute < 60;
case 'perNDay':
return day > 0 && day < 366 && hour >= 0 && hour < 24 && minute >= 0 && minute < 60;
case 'perNHour':
return hour > 0 && hour < 8784 && minute >= 0 && minute < 60;
case 'perNMinute':
return minute > 0 && minute < 527040;
case 'perNSecond':
return second > 0 && second < 31622400;
}
}
export function transObjToSpec(specType: string, week, day, hour, minute, second): string {
switch (specType) {
case 'perMonth':
+95 -23
View File
@@ -78,7 +78,7 @@
<el-form-item :label="$t('cronjob.cronSpec')" prop="spec">
<div v-for="(specObj, index) of dialogData.rowData.specObjs" :key="index" style="width: 100%">
<el-select class="specTypeClass" v-model="specObj.specType">
<el-select class="specTypeClass" v-model="specObj.specType" @change="changeSpecType(index)">
<el-option
v-for="item in specOptions"
:key="item.label"
@@ -133,10 +133,10 @@
</el-button>
<el-divider v-if="dialogData.rowData.specObjs.length > 1" class="divider" />
</div>
<el-button class="mt-3" @click="handleSpecAdd()">
{{ $t('commons.button.add') }}
</el-button>
</el-form-item>
<el-button class="mb-3" @click="handleSpecAdd()">
{{ $t('commons.button.add') }}
</el-button>
<el-form-item v-if="hasScript()">
<el-checkbox v-model="dialogData.rowData!.inContainer">
@@ -340,7 +340,7 @@ import { useRouter } from 'vue-router';
import { listContainer } from '@/api/modules/container';
import { Database } from '@/api/interface/database';
import { ListAppInstalled } from '@/api/modules/app';
import { checkScript, loadDefaultSpec, specOptions, transObjToSpec, transSpecToObj, weekOptions } from './../helper';
import { loadDefaultSpec, specOptions, transObjToSpec, transSpecToObj, weekOptions } from './../helper';
const router = useRouter();
interface DialogProps {
@@ -413,40 +413,87 @@ const dbInfo = reactive({
const verifySpec = (rule: any, value: any, callback: any) => {
if (dialogData.value.rowData!.specObjs.length === 0) {
callback(new Error(i18n.global.t('cronjob.cronSpecRule')));
callback(new Error(i18n.global.t('commons.rule.requiredInput')));
}
for (const item of dialogData.value.rowData!.specObjs) {
for (let i = 0; i < dialogData.value.rowData!.specObjs.length; i++) {
let item = dialogData.value.rowData!.specObjs[i];
if (
!Number.isInteger(item.day) ||
!Number.isInteger(item.hour) ||
!Number.isInteger(item.minute) ||
!Number.isInteger(item.second) ||
!Number.isInteger(item.week)
) {
callback(new Error(i18n.global.t('cronjob.cronSpecRule', [i + 1])));
return;
}
switch (item.specType) {
case 'perMonth':
if (
item.day < 0 ||
item.day > 31 ||
item.hour < 0 ||
item.hour > 23 ||
item.minute < 0 ||
item.minute > 59
) {
callback(new Error(i18n.global.t('cronjob.cronSpecRule', [i + 1])));
return;
}
break;
case 'perNDay':
if (!(Number.isInteger(item.day) && Number.isInteger(item.hour) && Number.isInteger(item.minute))) {
callback(new Error(i18n.global.t('cronjob.cronSpecRule')));
if (
item.day < 0 ||
item.day > 366 ||
item.hour < 0 ||
item.hour > 23 ||
item.minute < 0 ||
item.minute > 59
) {
callback(new Error(i18n.global.t('cronjob.cronSpecRule', [i + 1])));
return;
}
break;
case 'perWeek':
if (!(Number.isInteger(item.week) && Number.isInteger(item.hour) && Number.isInteger(item.minute))) {
callback(new Error(i18n.global.t('cronjob.cronSpecRule')));
if (
item.week < 0 ||
item.week > 6 ||
item.hour < 0 ||
item.hour > 23 ||
item.minute < 0 ||
item.minute > 59
) {
callback(new Error(i18n.global.t('cronjob.cronSpecRule', [i + 1])));
return;
}
break;
case 'perDay':
if (!(Number.isInteger(item.hour) && Number.isInteger(item.minute))) {
callback(new Error(i18n.global.t('cronjob.cronSpecRule')));
if (item.hour < 0 || item.hour > 23 || item.minute < 0 || item.minute > 59) {
callback(new Error(i18n.global.t('cronjob.cronSpecRule', [i + 1])));
return;
}
break;
case 'perNHour':
if (!(Number.isInteger(item.hour) && Number.isInteger(item.minute))) {
callback(new Error(i18n.global.t('cronjob.cronSpecRule')));
if (item.hour < 0 || item.hour > 8784 || item.minute < 0 || item.minute > 59) {
callback(new Error(i18n.global.t('cronjob.cronSpecRule', [i + 1])));
return;
}
break;
case 'perHour':
if (item.minute < 0 || item.minute > 59) {
callback(new Error(i18n.global.t('cronjob.cronSpecRule', [i + 1])));
return;
}
case 'perNMinute':
if (!Number.isInteger(item.minute)) {
callback(new Error(i18n.global.t('cronjob.cronSpecRule')));
if (item.minute < 0 || item.minute > 527040) {
callback(new Error(i18n.global.t('cronjob.cronSpecRule', [i + 1])));
return;
}
break;
case 'perNSecond':
if (!Number.isInteger(item.second)) {
callback(new Error(i18n.global.t('cronjob.cronSpecRule')));
if (item.second < 0 || item.second > 31622400) {
callback(new Error(i18n.global.t('cronjob.cronSpecRule', [i + 1])));
return;
}
break;
}
@@ -495,6 +542,35 @@ const changeType = () => {
dialogData.value.rowData!.specObjs = [loadDefaultSpec(dialogData.value.rowData.type)];
};
const changeSpecType = (index: number) => {
let item = dialogData.value.rowData!.specObjs[index];
switch (item.specType) {
case 'perMonth':
case 'perNDay':
item.day = 3;
item.hour = 1;
item.minute = 30;
break;
case 'perWeek':
item.week = 1;
item.hour = 1;
item.minute = 30;
break;
case 'perDay':
case 'perNHour':
item.hour = 2;
item.minute = 30;
break;
case 'perHour':
case 'perNMinute':
item.minute = 30;
break;
case 'perNSecond':
item.second = 30;
break;
}
};
const handleSpecAdd = () => {
let item = {
specType: 'perWeek',
@@ -582,10 +658,6 @@ function hasScript() {
const onSubmit = async (formEl: FormInstance | undefined) => {
const specs = [];
for (const item of dialogData.value.rowData.specObjs) {
if (!checkScript(item.specType, item.week, item.day, item.hour, item.minute, item.second)) {
MsgError(i18n.global.t('cronjob.cronSpecHelper'));
return;
}
const itemSpec = transObjToSpec(item.specType, item.week, item.day, item.hour, item.minute, item.second);
if (itemSpec === '') {
MsgError(i18n.global.t('cronjob.cronSpecHelper'));
@@ -372,7 +372,7 @@
<el-form-item :label="$t('commons.table.port')">
{{ sftpData.varsJson['port'] }}
</el-form-item>
<el-form-item :label="$t('setting.path')">
<el-form-item :label="$t('setting.backupDir')">
{{ sftpData.bucket }}
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">
@@ -408,10 +408,7 @@
<el-form-item :label="$t('setting.address')">
{{ webDAVData.varsJson['address'] }}
</el-form-item>
<el-form-item :label="$t('commons.table.port')">
{{ webDAVData.varsJson['port'] }}
</el-form-item>
<el-form-item :label="$t('setting.path')">
<el-form-item :label="$t('setting.backupDir')">
{{ webDAVData.bucket }}
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">
@@ -39,7 +39,7 @@
v-model.trim="sftpData.rowData!.credential"
/>
</el-form-item>
<el-form-item :label="$t('setting.path')" prop="bucket" :rules="[Rules.requiredInput]">
<el-form-item :label="$t('setting.backupDir')" prop="bucket" :rules="[Rules.requiredInput]">
<el-input v-model.trim="sftpData.rowData!.bucket" />
</el-form-item>
</el-col>
@@ -12,20 +12,21 @@
</el-form-item>
<el-form-item
:label="$t('setting.address')"
prop="varsJson.addressItem"
prop="varsJson.address"
:rules="Rules.requiredInput"
>
<el-input v-model="webdavData.rowData!.varsJson['addressItem']">
<template #prepend>
<el-select v-model.trim="addressProto" style="width: 100px">
<el-option label="http" value="http" />
<el-option label="https" value="https" />
</el-select>
</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('commons.table.port')" prop="varsJson.port" :rules="[portRule]">
<el-input-number v-model.number="webdavData.rowData!.varsJson['port']" />
<el-input v-model="webdavData.rowData!.varsJson['address']" />
<span class="input-help">
{{ $t('setting.WebDAVAlist') }}
<el-link
style="font-size: 12px; margin-left: 5px"
icon="Position"
@click="toDoc()"
type="primary"
>
{{ $t('firewall.quickJump') }}
</el-link>
</span>
</el-form-item>
<el-form-item
:label="$t('commons.login.username')"
@@ -46,7 +47,7 @@
v-model.trim="webdavData.rowData!.credential"
/>
</el-form-item>
<el-form-item :label="$t('setting.path')" prop="bucket" :rules="[Rules.requiredInput]">
<el-form-item :label="$t('setting.backupDir')" prop="bucket" :rules="[Rules.requiredInput]">
<el-input v-model.trim="webdavData.rowData!.bucket" />
</el-form-item>
</el-col>
@@ -67,7 +68,7 @@
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { ref } from 'vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
@@ -75,13 +76,11 @@ import { Backup } from '@/api/interface/backup';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { addBackup, editBackup } from '@/api/modules/setting';
import { MsgSuccess } from '@/utils/message';
import { checkPort, spliceHttp, splitHttp } from '@/utils/util';
const loading = ref(false);
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
const addressProto = ref('http');
const emit = defineEmits(['search']);
interface DialogProps {
@@ -94,24 +93,8 @@ const webdavData = ref<DialogProps>({
title: '',
});
const portRule = reactive({ validator: checkportRule, trigger: 'blur', type: 'number' });
function checkportRule(rule: any, value: any, callback: any) {
if (value !== undefined && value !== null) {
if (checkPort(value)) {
return callback(new Error(i18n.global.t('commons.rule.port')));
}
}
callback();
}
const acceptParams = (params: DialogProps): void => {
webdavData.value = params;
if (webdavData.value.title === 'edit') {
let httpItem = splitHttp(webdavData.value.rowData!.varsJson['address']);
webdavData.value.rowData!.varsJson['addressItem'] = httpItem.url;
addressProto.value = httpItem.proto;
}
title.value = i18n.global.t('commons.button.' + webdavData.value.title);
drawerVisible.value = true;
};
@@ -121,15 +104,15 @@ const handleClose = () => {
drawerVisible.value = false;
};
const toDoc = () => {
window.open('https://1panel.cn/docs/user_manual/settings/', '_blank', 'noopener,noreferrer');
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
if (!webdavData.value.rowData) return;
webdavData.value.rowData!.varsJson['address'] = spliceHttp(
addressProto.value,
webdavData.value.rowData!.varsJson['addressItem'],
);
webdavData.value.rowData.vars = JSON.stringify(webdavData.value.rowData!.varsJson);
loading.value = true;
if (webdavData.value.title === 'create') {