mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
13
Commits
v1.9.5
...
release-1.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fc26a3061 | ||
|
|
d8669b90bf | ||
|
|
9e980e8e0c | ||
|
|
140cd564da | ||
|
|
b40087c88c | ||
|
|
8e620172e6 | ||
|
|
9b8b9f505b | ||
|
|
1169648162 | ||
|
|
c169f211a0 | ||
|
|
eef445c1eb | ||
|
|
bedb8ce286 | ||
|
|
3652c46ac9 | ||
|
|
0449868d3b |
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -70,6 +70,8 @@ func Init() {
|
||||
migrations.UpdateCronjobSpec,
|
||||
migrations.UpdateBackupRecordPath,
|
||||
migrations.UpdateSnapshotRecords,
|
||||
|
||||
migrations.UpdateWebDavConf,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 後面的內容,粘貼到該輸入框中,具體操作可參考官方文檔。',
|
||||
|
||||
@@ -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 后面的内容,粘贴到该输入框中,具体操作可参考官方文档。',
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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') {
|
||||
|
||||
Reference in New Issue
Block a user