1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-19 08:19:15 +08:00

feat: 备份账号增加 WebDAV (#3120)

Refs #367
This commit is contained in:
ssongliu 2023-11-30 21:50:08 +08:00 committed by GitHub
parent 833efb0136
commit 732080b0bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 425 additions and 239 deletions

View File

@ -31,7 +31,7 @@ type CommonBackup struct {
DetailName string `json:"detailName"` DetailName string `json:"detailName"`
} }
type CommonRecover struct { type CommonRecover struct {
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO OneDrive"` Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO OneDrive WebDAV"`
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website"` Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website"`
Name string `json:"name"` Name string `json:"name"`
DetailName string `json:"detailName"` DetailName string `json:"detailName"`
@ -55,7 +55,7 @@ type BackupRecords struct {
} }
type DownloadRecord struct { type DownloadRecord struct {
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO OneDrive"` Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL COS KODO OneDrive WebDAV"`
FileDir string `json:"fileDir" validate:"required"` FileDir string `json:"fileDir" validate:"required"`
FileName string `json:"fileName" validate:"required"` FileName string `json:"fileName" validate:"required"`
} }

View File

@ -98,7 +98,7 @@ type SnapshotStatus struct {
type SnapshotCreate struct { type SnapshotCreate struct {
ID uint `json:"id"` ID uint `json:"id"`
From string `json:"from" validate:"required,oneof=OSS S3 SFTP MINIO COS KODO OneDrive"` From string `json:"from" validate:"required,oneof=OSS S3 SFTP MINIO COS KODO OneDrive WebDAV"`
Description string `json:"description" validate:"max=256"` Description string `json:"description" validate:"max=256"`
} }
type SnapshotRecover struct { type SnapshotRecover struct {

View File

@ -69,6 +69,7 @@ func (u *BackupService) List() ([]dto.BackupInfo, error) {
dtobas = append(dtobas, u.loadByType("COS", ops)) dtobas = append(dtobas, u.loadByType("COS", ops))
dtobas = append(dtobas, u.loadByType("KODO", ops)) dtobas = append(dtobas, u.loadByType("KODO", ops))
dtobas = append(dtobas, u.loadByType("OneDrive", ops)) dtobas = append(dtobas, u.loadByType("OneDrive", ops))
dtobas = append(dtobas, u.loadByType("WebDAV", ops))
return dtobas, err return dtobas, err
} }
@ -117,7 +118,7 @@ func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error)
} }
varMap["bucket"] = backup.Bucket varMap["bucket"] = backup.Bucket
switch backup.Type { switch backup.Type {
case constant.Sftp: case constant.Sftp, constant.WebDAV:
varMap["username"] = backup.AccessKey varMap["username"] = backup.AccessKey
varMap["password"] = backup.Credential varMap["password"] = backup.Credential
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo: case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
@ -166,8 +167,7 @@ func (u *BackupService) Create(req dto.BackupOperate) error {
} }
} }
if req.Type != "LOCAL" { if req.Type != "LOCAL" {
isOk, err := u.checkBackupConn(&backup) if _, err := u.checkBackupConn(&backup); err != nil {
if err != nil || !isOk {
return buserr.WithMap("ErrBackupCheck", map[string]interface{}{"err": err.Error()}, err) return buserr.WithMap("ErrBackupCheck", map[string]interface{}{"err": err.Error()}, err)
} }
} }
@ -183,7 +183,7 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err
return nil, err return nil, err
} }
switch backupDto.Type { switch backupDto.Type {
case constant.Sftp: case constant.Sftp, constant.WebDAV:
varMap["username"] = backupDto.AccessKey varMap["username"] = backupDto.AccessKey
varMap["password"] = backupDto.Credential varMap["password"] = backupDto.Credential
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo: case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
@ -328,7 +328,7 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl
} }
varMap["bucket"] = backup.Bucket varMap["bucket"] = backup.Bucket
switch backup.Type { switch backup.Type {
case constant.Sftp: case constant.Sftp, constant.WebDAV:
varMap["username"] = backup.AccessKey varMap["username"] = backup.AccessKey
varMap["password"] = backup.Credential varMap["password"] = backup.Credential
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo: case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:

View File

@ -11,6 +11,7 @@ const (
MinIo = "MINIO" MinIo = "MINIO"
Cos = "COS" Cos = "COS"
Kodo = "KODO" Kodo = "KODO"
WebDAV = "WebDAV"
OneDriveRedirectURI = "http://localhost/login/authorized" OneDriveRedirectURI = "http://localhost/login/authorized"
) )

View File

@ -1,75 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"os"
"testing"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/init/db"
"github.com/1Panel-dev/1Panel/backend/init/log"
"github.com/1Panel-dev/1Panel/backend/init/viper"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
)
func TestCron(t *testing.T) {
viper.Init()
log.Init()
db.Init()
var backup model.BackupAccount
if err := global.DB.Where("id = ?", 2).First(&backup).Error; err != nil {
fmt.Println(err)
}
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
fmt.Println(err)
}
varMap["type"] = backup.Type
varMap["bucket"] = backup.Bucket
switch backup.Type {
case constant.Sftp:
varMap["password"] = backup.Credential
case constant.OSS, constant.S3, constant.MinIo:
varMap["secretKey"] = backup.Credential
}
endpoint := varMap["endpoint"].(string)
accessKey := varMap["accessKey"].(string)
secretKey := varMap["secretKey"].(string)
client, err := oss.New(endpoint, accessKey, secretKey)
if err != nil {
fmt.Println(err)
}
bucket, err := client.Bucket(backup.Bucket)
if err != nil {
fmt.Println(err)
}
lor, err := bucket.ListObjects(oss.Prefix("directory/directory-test1/"))
if err != nil {
fmt.Println(err)
}
fmt.Println("my objects:", getObjectsFormResponse(lor))
name := "directory/directory-test1/20220928104331.tar.gz"
targetPath := constant.DataDir + "/download/directory/directory-test1/"
if _, err := os.Stat(targetPath); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(targetPath, os.ModePerm); err != nil {
fmt.Println(err)
}
}
if err := bucket.GetObjectToFile(name, targetPath+"20220928104231.tar.gz"); err != nil {
fmt.Println(err)
}
}
func getObjectsFormResponse(lor oss.ListObjectsResult) string {
var output string
for _, object := range lor.Objects {
output += object.Key + " "
}
return output
}

View File

@ -15,7 +15,9 @@ import (
) )
type sftpClient struct { type sftpClient struct {
Bucket string
Vars map[string]interface{} Vars map[string]interface{}
client *sftp.Client
} }
func NewSftpClient(vars map[string]interface{}) (*sftpClient, error) { func NewSftpClient(vars map[string]interface{}) (*sftpClient, error) {
@ -31,46 +33,45 @@ func NewSftpClient(vars map[string]interface{}) (*sftpClient, error) {
if _, ok := vars["username"]; !ok { if _, ok := vars["username"]; !ok {
return nil, constant.ErrInvalidParams return nil, constant.ErrInvalidParams
} }
return &sftpClient{ var bucket string
Vars: vars, if _, ok := vars["bucket"]; ok {
}, nil bucket = vars["bucket"].(string)
} else {
return nil, constant.ErrInvalidParams
}
port, err := strconv.Atoi(strconv.FormatFloat(vars["port"].(float64), 'G', -1, 64))
if err != nil {
return nil, err
}
sftpC, err := connect(vars["username"].(string), vars["password"].(string), vars["address"].(string), port)
if err != nil {
return nil, err
}
return &sftpClient{Bucket: bucket, client: sftpC, Vars: vars}, nil
} }
func (s sftpClient) Upload(src, target string) (bool, error) { func (s sftpClient) Upload(src, target string) (bool, error) {
bucket, err := s.getBucket() defer s.client.Close()
if err != nil {
return false, err
}
port, err := strconv.Atoi(strconv.FormatFloat(s.Vars["port"].(float64), 'G', -1, 64))
if err != nil {
return false, err
}
sftpC, err := connect(s.Vars["username"].(string), s.Vars["password"].(string), s.Vars["address"].(string), port)
if err != nil {
return false, err
}
defer sftpC.Close()
srcFile, err := os.Open(src) srcFile, err := os.Open(src)
if err != nil { if err != nil {
return false, err return false, err
} }
defer srcFile.Close() defer srcFile.Close()
targetFilePath := bucket + "/" + target targetFilePath := s.Bucket + "/" + target
remotePath, _ := path.Split(targetFilePath) targetDir, _ := path.Split(targetFilePath)
_, err = sftpC.Stat(remotePath) if _, err = s.client.Stat(targetDir); err != nil {
if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
err = sftpC.MkdirAll(remotePath) if err = s.client.MkdirAll(targetDir); err != nil {
if err != nil {
return false, err return false, err
} }
} else { } else {
return false, err return false, err
} }
} }
dstFile, err := s.client.Create(targetFilePath)
dstFile, err := sftpC.Create(targetFilePath)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -96,20 +97,8 @@ func (s sftpClient) ListBuckets() ([]interface{}, error) {
} }
func (s sftpClient) Download(src, target string) (bool, error) { func (s sftpClient) Download(src, target string) (bool, error) {
bucket, err := s.getBucket() defer s.client.Close()
if err != nil { srcFile, err := s.client.Open(s.Bucket + "/" + src)
return false, err
}
port, err := strconv.Atoi(strconv.FormatFloat(s.Vars["port"].(float64), 'G', -1, 64))
if err != nil {
return false, err
}
sftpC, err := connect(s.Vars["username"].(string), s.Vars["password"].(string), s.Vars["address"].(string), port)
if err != nil {
return false, err
}
defer sftpC.Close()
srcFile, err := sftpC.Open(bucket + "/" + src)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -128,20 +117,8 @@ func (s sftpClient) Download(src, target string) (bool, error) {
} }
func (s sftpClient) Exist(path string) (bool, error) { func (s sftpClient) Exist(path string) (bool, error) {
bucket, err := s.getBucket() defer s.client.Close()
if err != nil { srcFile, err := s.client.Open(s.Bucket + "/" + path)
return false, err
}
port, err := strconv.Atoi(strconv.FormatFloat(s.Vars["port"].(float64), 'G', -1, 64))
if err != nil {
return false, err
}
sftpC, err := connect(s.Vars["username"].(string), s.Vars["password"].(string), s.Vars["address"].(string), port)
if err != nil {
return false, err
}
defer sftpC.Close()
srcFile, err := sftpC.Open(bucket + "/" + path)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return false, nil return false, nil
@ -154,33 +131,15 @@ func (s sftpClient) Exist(path string) (bool, error) {
} }
func (s sftpClient) Delete(filePath string) (bool, error) { func (s sftpClient) Delete(filePath string) (bool, error) {
bucket, err := s.getBucket() defer s.client.Close()
if err != nil { targetFilePath := s.Bucket + "/" + filePath
if err := s.client.Remove(targetFilePath); err != nil {
return false, err return false, err
} }
port, err := strconv.Atoi(strconv.FormatFloat(s.Vars["port"].(float64), 'G', -1, 64))
if err != nil {
return false, err
}
sftpC, err := connect(s.Vars["username"].(string), s.Vars["password"].(string), s.Vars["address"].(string), port)
if err != nil {
return false, err
}
defer sftpC.Close()
targetFilePath := bucket + "/" + filePath
err = sftpC.Remove(targetFilePath)
if err != nil {
if os.IsNotExist(err) {
return true, nil
} else {
return false, err
}
}
return true, nil return true, nil
} }
func connect(user, password, host string, port int) (*sftp.Client, error) { func connect(user, password, host string, port int) (*sftp.Client, error) {
var ( var (
auth []ssh.AuthMethod auth []ssh.AuthMethod
addr string addr string
@ -210,29 +169,9 @@ func connect(user, password, host string, port int) (*sftp.Client, error) {
return sftpClient, nil return sftpClient, nil
} }
func (s sftpClient) getBucket() (string, error) {
if _, ok := s.Vars["bucket"]; ok {
return s.Vars["bucket"].(string), nil
} else {
return "", constant.ErrInvalidParams
}
}
func (s sftpClient) ListObjects(prefix string) ([]string, error) { func (s sftpClient) ListObjects(prefix string) ([]string, error) {
bucket, err := s.getBucket() defer s.client.Close()
if err != nil { files, err := s.client.ReadDir(s.Bucket + "/" + prefix)
return nil, err
}
port, err := strconv.Atoi(strconv.FormatFloat(s.Vars["port"].(float64), 'G', -1, 64))
if err != nil {
return nil, err
}
sftpC, err := connect(s.Vars["username"].(string), s.Vars["password"].(string), s.Vars["address"].(string), port)
if err != nil {
return nil, err
}
defer sftpC.Close()
files, err := sftpC.ReadDir(bucket + "/" + prefix)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,44 +0,0 @@
package client
import (
"encoding/json"
"fmt"
"testing"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/init/db"
"github.com/1Panel-dev/1Panel/backend/init/log"
"github.com/1Panel-dev/1Panel/backend/init/viper"
)
func TestCronS(t *testing.T) {
viper.Init()
log.Init()
db.Init()
var backup model.BackupAccount
if err := global.DB.Where("id = ?", 5).First(&backup).Error; err != nil {
fmt.Println(err)
}
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
fmt.Println(err)
}
varMap["type"] = backup.Type
varMap["bucket"] = backup.Bucket
switch backup.Type {
case constant.Sftp:
varMap["password"] = backup.Credential
case constant.OSS, constant.S3, constant.MinIo:
varMap["secretKey"] = backup.Credential
}
client, err := NewS3Client(varMap)
if err != nil {
fmt.Println(err)
}
_, _ = client.ListObjects("directory/directory-test-s3")
}

View File

@ -0,0 +1,135 @@
package client
import (
"fmt"
"io"
"os"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/studio-b12/gowebdav"
)
type webDAVClient struct {
Bucket string
client *gowebdav.Client
Vars map[string]interface{}
}
func NewWebDAVClient(vars map[string]interface{}) (*webDAVClient, error) {
var (
address string
username string
password string
bucket string
)
if _, ok := vars["address"]; ok {
address = vars["address"].(string)
} else {
return nil, constant.ErrInvalidParams
}
if _, ok := vars["port"].(float64); !ok {
return nil, constant.ErrInvalidParams
}
if _, ok := vars["username"]; ok {
username = vars["username"].(string)
} else {
return nil, constant.ErrInvalidParams
}
if _, ok := vars["password"]; ok {
password = vars["password"].(string)
} else {
return nil, constant.ErrInvalidParams
}
if _, ok := vars["bucket"]; ok {
bucket = vars["bucket"].(string)
} else {
return nil, constant.ErrInvalidParams
}
client := gowebdav.NewClient(fmt.Sprintf("%s:%v", address, vars["port"]), username, password)
if err := client.Connect(); err != nil {
return nil, err
}
return &webDAVClient{Vars: vars, Bucket: bucket, client: client}, nil
}
func (s webDAVClient) Upload(src, target string) (bool, error) {
targetFilePath := s.Bucket + "/" + target
fileInfo, err := os.Stat(src)
if err != nil {
return false, err
}
// 50M
if fileInfo.Size() > 52428800 {
bytes, _ := os.ReadFile(src)
if err := s.client.Write(targetFilePath, bytes, 0644); err != nil {
return false, err
}
return true, nil
}
file, _ := os.Open(src)
defer file.Close()
if err := s.client.WriteStream(targetFilePath, file, 0644); err != nil {
return false, err
}
return true, nil
}
func (s webDAVClient) ListBuckets() ([]interface{}, error) {
var result []interface{}
return result, nil
}
func (s webDAVClient) Download(src, target string) (bool, error) {
srcPath := s.Bucket + "/" + src
info, err := s.client.Stat(srcPath)
if err != nil {
return false, err
}
file, err := os.Create(target)
if err != nil {
return false, err
}
defer file.Close()
// 50M
if info.Size() > 52428800 {
reader, _ := s.client.ReadStream(srcPath)
if _, err := io.Copy(file, reader); err != nil {
return false, err
}
}
bytes, _ := s.client.Read(srcPath)
if err := os.WriteFile(target, bytes, 0644); err != nil {
return false, err
}
return true, err
}
func (s webDAVClient) Exist(path string) (bool, error) {
if _, err := s.client.Stat(s.Bucket + "/" + path); err != nil {
return false, err
}
return true, nil
}
func (s webDAVClient) Delete(filePath string) (bool, error) {
if err := s.client.Remove(s.Bucket + "/" + filePath); err != nil {
return false, err
}
return true, nil
}
func (s webDAVClient) ListObjects(prefix string) ([]string, error) {
files, err := s.client.ReadDir(s.Bucket + "/" + prefix)
if err != nil {
return nil, err
}
var result []string
for _, file := range files {
result = append(result, file.Name())
}
return result, nil
}

View File

@ -22,6 +22,8 @@ func NewCloudStorageClient(backupType string, vars map[string]interface{}) (Clou
return client.NewOssClient(vars) return client.NewOssClient(vars)
case constant.Sftp: case constant.Sftp:
return client.NewSftpClient(vars) return client.NewSftpClient(vars)
case constant.WebDAV:
return client.NewWebDAVClient(vars)
case constant.MinIo: case constant.MinIo:
return client.NewMinIoClient(vars) return client.NewMinIoClient(vars)
case constant.Cos: case constant.Cos:

View File

@ -1,5 +1,5 @@
// Package docs GENERATED BY SWAG; DO NOT EDIT // Code generated by swaggo/swag. DO NOT EDIT.
// This file was generated by swaggo/swag
package docs package docs
import "github.com/swaggo/swag" import "github.com/swaggo/swag"
@ -13769,7 +13769,8 @@ const docTemplate = `{
"LOCAL", "LOCAL",
"COS", "COS",
"KODO", "KODO",
"OneDrive" "OneDrive",
"WebDAV"
] ]
}, },
"type": { "type": {
@ -14586,6 +14587,12 @@ const docTemplate = `{
"address": { "address": {
"type": "string" "type": "string"
}, },
"clientCert": {
"type": "string"
},
"clientKey": {
"type": "string"
},
"description": { "description": {
"type": "string" "type": "string"
}, },
@ -14606,6 +14613,15 @@ const docTemplate = `{
"port": { "port": {
"type": "integer" "type": "integer"
}, },
"rootCert": {
"type": "string"
},
"skipVerify": {
"type": "boolean"
},
"ssl": {
"type": "boolean"
},
"type": { "type": {
"type": "string" "type": "string"
}, },
@ -14640,6 +14656,12 @@ const docTemplate = `{
"address": { "address": {
"type": "string" "type": "string"
}, },
"clientCert": {
"type": "string"
},
"clientKey": {
"type": "string"
},
"createdAt": { "createdAt": {
"type": "string" "type": "string"
}, },
@ -14662,6 +14684,15 @@ const docTemplate = `{
"port": { "port": {
"type": "integer" "type": "integer"
}, },
"rootCert": {
"type": "string"
},
"skipVerify": {
"type": "boolean"
},
"ssl": {
"type": "boolean"
},
"type": { "type": {
"type": "string" "type": "string"
}, },
@ -14735,6 +14766,12 @@ const docTemplate = `{
"address": { "address": {
"type": "string" "type": "string"
}, },
"clientCert": {
"type": "string"
},
"clientKey": {
"type": "string"
},
"description": { "description": {
"type": "string" "type": "string"
}, },
@ -14747,6 +14784,15 @@ const docTemplate = `{
"port": { "port": {
"type": "integer" "type": "integer"
}, },
"rootCert": {
"type": "string"
},
"skipVerify": {
"type": "boolean"
},
"ssl": {
"type": "boolean"
},
"type": { "type": {
"type": "string" "type": "string"
}, },
@ -14883,7 +14929,8 @@ const docTemplate = `{
"LOCAL", "LOCAL",
"COS", "COS",
"KODO", "KODO",
"OneDrive" "OneDrive",
"WebDAV"
] ]
} }
} }
@ -16891,7 +16938,8 @@ const docTemplate = `{
"MINIO", "MINIO",
"COS", "COS",
"KODO", "KODO",
"OneDrive" "OneDrive",
"WebDAV"
] ]
}, },
"id": { "id": {

View File

@ -13762,7 +13762,8 @@
"LOCAL", "LOCAL",
"COS", "COS",
"KODO", "KODO",
"OneDrive" "OneDrive",
"WebDAV"
] ]
}, },
"type": { "type": {
@ -14579,6 +14580,12 @@
"address": { "address": {
"type": "string" "type": "string"
}, },
"clientCert": {
"type": "string"
},
"clientKey": {
"type": "string"
},
"description": { "description": {
"type": "string" "type": "string"
}, },
@ -14599,6 +14606,15 @@
"port": { "port": {
"type": "integer" "type": "integer"
}, },
"rootCert": {
"type": "string"
},
"skipVerify": {
"type": "boolean"
},
"ssl": {
"type": "boolean"
},
"type": { "type": {
"type": "string" "type": "string"
}, },
@ -14633,6 +14649,12 @@
"address": { "address": {
"type": "string" "type": "string"
}, },
"clientCert": {
"type": "string"
},
"clientKey": {
"type": "string"
},
"createdAt": { "createdAt": {
"type": "string" "type": "string"
}, },
@ -14655,6 +14677,15 @@
"port": { "port": {
"type": "integer" "type": "integer"
}, },
"rootCert": {
"type": "string"
},
"skipVerify": {
"type": "boolean"
},
"ssl": {
"type": "boolean"
},
"type": { "type": {
"type": "string" "type": "string"
}, },
@ -14728,6 +14759,12 @@
"address": { "address": {
"type": "string" "type": "string"
}, },
"clientCert": {
"type": "string"
},
"clientKey": {
"type": "string"
},
"description": { "description": {
"type": "string" "type": "string"
}, },
@ -14740,6 +14777,15 @@
"port": { "port": {
"type": "integer" "type": "integer"
}, },
"rootCert": {
"type": "string"
},
"skipVerify": {
"type": "boolean"
},
"ssl": {
"type": "boolean"
},
"type": { "type": {
"type": "string" "type": "string"
}, },
@ -14876,7 +14922,8 @@
"LOCAL", "LOCAL",
"COS", "COS",
"KODO", "KODO",
"OneDrive" "OneDrive",
"WebDAV"
] ]
} }
} }
@ -16884,7 +16931,8 @@
"MINIO", "MINIO",
"COS", "COS",
"KODO", "KODO",
"OneDrive" "OneDrive",
"WebDAV"
] ]
}, },
"id": { "id": {

View File

@ -273,6 +273,7 @@ definitions:
- COS - COS
- KODO - KODO
- OneDrive - OneDrive
- WebDAV
type: string type: string
type: type:
enum: enum:
@ -818,6 +819,10 @@ definitions:
properties: properties:
address: address:
type: string type: string
clientCert:
type: string
clientKey:
type: string
description: description:
type: string type: string
from: from:
@ -832,6 +837,12 @@ definitions:
type: string type: string
port: port:
type: integer type: integer
rootCert:
type: string
skipVerify:
type: boolean
ssl:
type: boolean
type: type:
type: string type: string
username: username:
@ -861,6 +872,10 @@ definitions:
properties: properties:
address: address:
type: string type: string
clientCert:
type: string
clientKey:
type: string
createdAt: createdAt:
type: string type: string
description: description:
@ -876,6 +891,12 @@ definitions:
type: string type: string
port: port:
type: integer type: integer
rootCert:
type: string
skipVerify:
type: boolean
ssl:
type: boolean
type: type:
type: string type: string
username: username:
@ -920,6 +941,10 @@ definitions:
properties: properties:
address: address:
type: string type: string
clientCert:
type: string
clientKey:
type: string
description: description:
type: string type: string
id: id:
@ -928,6 +953,12 @@ definitions:
type: string type: string
port: port:
type: integer type: integer
rootCert:
type: string
skipVerify:
type: boolean
ssl:
type: boolean
type: type:
type: string type: string
username: username:
@ -1023,6 +1054,7 @@ definitions:
- COS - COS
- KODO - KODO
- OneDrive - OneDrive
- WebDAV
type: string type: string
required: required:
- fileDir - fileDir
@ -2380,6 +2412,7 @@ definitions:
- COS - COS
- KODO - KODO
- OneDrive - OneDrive
- WebDAV
type: string type: string
id: id:
type: integer type: integer

View File

@ -1,9 +1,9 @@
@font-face { @font-face {
font-family: "panel"; /* Project id 3575356 */ font-family: "panel"; /* Project id 3575356 */
src: url('iconfont.woff2?t=1695287081776') format('woff2'), src: url('iconfont.woff2?t=1701341406915') format('woff2'),
url('iconfont.woff?t=1695287081776') format('woff'), url('iconfont.woff?t=1701341406915') format('woff'),
url('iconfont.ttf?t=1695287081776') format('truetype'), url('iconfont.ttf?t=1701341406915') format('truetype'),
url('iconfont.svg?t=1695287081776#panel') format('svg'); url('iconfont.svg?t=1701341406915#panel') format('svg');
} }
.panel { .panel {
@ -14,6 +14,10 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.p-webdav:before {
content: "\e622";
}
.p-xiangqing:before { .p-xiangqing:before {
content: "\e677"; content: "\e677";
} }

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,13 @@
"css_prefix_text": "p-", "css_prefix_text": "p-",
"description": "", "description": "",
"glyphs": [ "glyphs": [
{
"icon_id": "23044673",
"name": "webdav",
"font_class": "webdav",
"unicode": "e622",
"unicode_decimal": 58914
},
{ {
"icon_id": "10293150", "icon_id": "10293150",
"name": "详情", "name": "详情",

View File

@ -14,6 +14,8 @@
/> />
<missing-glyph /> <missing-glyph />
<glyph glyph-name="webdav" unicode="&#58914;" d="M512 846.451613c255.405419 0 462.451613-207.046194 462.451613-462.451613S767.405419-78.45161299999995 512-78.45161299999995 49.548387 128.59458099999995 49.548387 384 256.594581 846.451613 512 846.451613z m0-66.064516C293.07871 780.387097 115.612903 602.92129 115.612903 384s177.465806-396.387097 396.387097-396.387097 396.387097 177.465806 396.387097 396.387097S730.92129 780.387097 512 780.387097zM332.122839 565.677419a33.032258 33.032258 0 0 0 32.090838-25.236645l54.618839-224.916645 57.575226 225.296516A33.032258 33.032258 0 0 0 508.416 565.677419h17.061161a33.032258 33.032258 0 0 0 32.008258-24.856774l57.575226-225.296516 54.618839 224.916645A33.032258 33.032258 0 0 0 701.786839 565.677419h12.684387a29.217032 29.217032 0 0 0 28.110451-37.194322l-85.80129-302.146065A33.032258 33.032258 0 0 0 625.003355 202.322581h-19.059613a33.032258 33.032258 0 0 0-32.008258 24.87329L517.169548 450.064516 459.974194 227.14632300000005A33.032258 33.032258 0 0 0 427.982452 202.322581h-19.555097a33.032258 33.032258 0 0 0-31.777032 24.047484l-85.371871 302.113032A29.233548 29.233548 0 0 0 319.438452 565.677419h12.684387z" horiz-adv-x="1024" />
<glyph glyph-name="xiangqing" unicode="&#58999;" d="M512 654.27010918m-77.34417915 0a77.34417915 77.34417915 0 1 1 154.6883583 0 77.34417915 77.34417915 0 1 1-154.6883583 0ZM512 384m-77.34417915 0a77.34417915 77.34417915 0 1 1 154.6883583 0 77.34417915 77.34417915 0 1 1-154.6883583 0ZM512 113.72989082000004m-77.34417915 0a77.34417915 77.34417915 0 1 1 154.6883583 0 77.34417915 77.34417915 0 1 1-154.6883583 0Z" horiz-adv-x="1024" /> <glyph glyph-name="xiangqing" unicode="&#58999;" d="M512 654.27010918m-77.34417915 0a77.34417915 77.34417915 0 1 1 154.6883583 0 77.34417915 77.34417915 0 1 1-154.6883583 0ZM512 384m-77.34417915 0a77.34417915 77.34417915 0 1 1 154.6883583 0 77.34417915 77.34417915 0 1 1-154.6883583 0ZM512 113.72989082000004m-77.34417915 0a77.34417915 77.34417915 0 1 1 154.6883583 0 77.34417915 77.34417915 0 1 1-154.6883583 0Z" horiz-adv-x="1024" />
<glyph glyph-name="onedrive" unicode="&#58881;" d="M597.333333 320s188.885333 129.152 192.96 128.874667A280.021333 280.021333 0 0 1 285.141333 533.333333h2.858667zM405.674667 499.968A222.869333 222.869333 0 0 1 288 533.333333h-2.858667a224 224 0 0 1-180.885333-352L405.333333 213.333333l188.437334 173.973334zM790.293333 448.874667a180.373333 180.373333 0 0 1-12.288 0.448 181.461333 181.461333 0 0 1-72.149333-14.933334l-112.085333-47.146666L725.333333 234.666667l212.906667-53.696a182.016 182.016 0 0 1-147.946667 267.904zM779.285333 276.181333l-46.464 27.733334-106.538666 63.808-32.512 19.477333-85.738667-36.096-164.266667-69.077333-73.642666-30.933334-165.866667-69.76A223.658667 223.658667 0 0 1 288 85.333333h490.005333a181.994667 181.994667 0 0 1 160.234667 95.637334z" horiz-adv-x="1024" /> <glyph glyph-name="onedrive" unicode="&#58881;" d="M597.333333 320s188.885333 129.152 192.96 128.874667A280.021333 280.021333 0 0 1 285.141333 533.333333h2.858667zM405.674667 499.968A222.869333 222.869333 0 0 1 288 533.333333h-2.858667a224 224 0 0 1-180.885333-352L405.333333 213.333333l188.437334 173.973334zM790.293333 448.874667a180.373333 180.373333 0 0 1-12.288 0.448 181.461333 181.461333 0 0 1-72.149333-14.933334l-112.085333-47.146666L725.333333 234.666667l212.906667-53.696a182.016 182.016 0 0 1-147.946667 267.904zM779.285333 276.181333l-46.464 27.733334-106.538666 63.808-32.512 19.477333-85.738667-36.096-164.266667-69.077333-73.642666-30.933334-165.866667-69.76A223.658667 223.658667 0 0 1 288 85.333333h490.005333a181.994667 181.994667 0 0 1 160.234667 95.637334z" horiz-adv-x="1024" />

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -1204,6 +1204,7 @@ const message = {
S3: 'Amazon S3', S3: 'Amazon S3',
MINIO: 'MINIO', MINIO: 'MINIO',
SFTP: 'SFTP', SFTP: 'SFTP',
WebDAV: 'WebDAV',
OneDrive: 'Microsoft OneDrive', OneDrive: 'Microsoft OneDrive',
backupDir: 'Backup dir', backupDir: 'Backup dir',
codeWarning: 'The current authorization code format is incorrect, please confirm again!', codeWarning: 'The current authorization code format is incorrect, please confirm again!',

View File

@ -1133,6 +1133,7 @@ const message = {
S3: '亞馬遜 S3 雲存儲', S3: '亞馬遜 S3 雲存儲',
MINIO: 'MINIO', MINIO: 'MINIO',
SFTP: 'SFTP', SFTP: 'SFTP',
WebDAV: 'WebDAV',
OneDrive: '微軟 OneDrive', OneDrive: '微軟 OneDrive',
codeWarning: '當前授權碼格式錯誤請重新確認', codeWarning: '當前授權碼格式錯誤請重新確認',
backupDir: '備份路徑', backupDir: '備份路徑',

View File

@ -1134,6 +1134,7 @@ const message = {
S3: '亚马逊 S3 云存储', S3: '亚马逊 S3 云存储',
MINIO: 'MINIO', MINIO: 'MINIO',
SFTP: 'SFTP', SFTP: 'SFTP',
WebDAV: 'WebDAV',
OneDrive: '微软 OneDrive', OneDrive: '微软 OneDrive',
codeWarning: '当前授权码格式错误请重新确认', codeWarning: '当前授权码格式错误请重新确认',
backupDir: '备份路径', backupDir: '备份路径',

View File

@ -365,6 +365,51 @@
</el-button> </el-button>
</el-alert> </el-alert>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<div>
<svg-icon class="card-logo" iconName="p-webdav"></svg-icon>
<span class="card-title">&nbsp;WebDAV</span>
<div style="float: right">
<el-button
round
plain
:disabled="webDAVData.id === 0"
@click="onOpenDialog('edit', 'WebDAV', webDAVData)"
>
{{ $t('commons.button.edit') }}
</el-button>
<el-button round :disabled="webDAVData.id === 0" @click="onDelete(webDAVData)">
{{ $t('commons.button.delete') }}
</el-button>
</div>
</div>
<el-divider class="divider" />
<div v-if="webDAVData.id !== 0" style="margin-left: 20px">
<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')">
{{ webDAVData.bucket }}
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">
{{ dateFormat(0, 0, webDAVData.createdAt) }}
</el-form-item>
</div>
<el-alert v-else center class="alert" style="height: 257px" :closable="false">
<el-button
size="large"
round
plain
type="primary"
@click="onOpenDialog('create', 'WebDAV')"
>
{{ $t('setting.createBackupAccount', ['WebDAV']) }}
</el-button>
</el-alert>
</el-col>
</el-row> </el-row>
</template> </template>
</LayoutContent> </LayoutContent>
@ -442,6 +487,20 @@ const sftpData = ref<Backup.BackupInfo>({
}, },
createdAt: new Date(), createdAt: new Date(),
}); });
const webDAVData = ref<Backup.BackupInfo>({
id: 0,
type: 'WebDAV',
accessKey: '',
bucket: '',
credential: '',
backupPath: '',
vars: '',
varsJson: {
address: '',
port: 10080,
},
createdAt: new Date(),
});
const oneDriveData = ref<Backup.BackupInfo>({ const oneDriveData = ref<Backup.BackupInfo>({
id: 0, id: 0,
type: 'OneDrive', type: 'OneDrive',
@ -530,6 +589,9 @@ const search = async () => {
case 'OneDrive': case 'OneDrive':
oneDriveData.value = bac; oneDriveData.value = bac;
break; break;
case 'WebDAV':
webDAVData.value = bac;
break;
} }
} }
}; };

View File

@ -195,10 +195,24 @@
:title="$t('setting.archiveHelper')" :title="$t('setting.archiveHelper')"
/> />
</el-form-item> </el-form-item>
<div v-if="dialogData.rowData!.type === 'SFTP'"> <div v-if="isSftpOrWebDAV(dialogData.rowData!.type)">
<el-form-item :label="$t('setting.address')" prop="varsJson.address" :rules="Rules.host"> <el-form-item
v-if="dialogData.rowData!.type === 'SFTP'"
:label="$t('setting.address')"
prop="varsJson.address"
:rules="Rules.host"
>
<el-input v-model.trim="dialogData.rowData!.varsJson['address']" /> <el-input v-model.trim="dialogData.rowData!.varsJson['address']" />
</el-form-item> </el-form-item>
<el-form-item
v-if="dialogData.rowData!.type === 'WebDAV'"
:label="$t('setting.address')"
prop="varsJson.address"
:rules="Rules.requiredInput"
>
<el-input v-model.trim="dialogData.rowData!.varsJson['address']" />
<span class="input-help">https://172.16.10.111</span>
</el-form-item>
<el-form-item :label="$t('commons.table.port')" prop="varsJson.port" :rules="[Rules.port]"> <el-form-item :label="$t('commons.table.port')" prop="varsJson.port" :rules="[Rules.port]">
<el-input-number <el-input-number
:min="0" :min="0"
@ -230,7 +244,7 @@
</el-form-item> </el-form-item>
</div> </div>
<el-form-item <el-form-item
v-if="dialogData.rowData!.type !== 'LOCAL' && dialogData.rowData!.type !== 'SFTP'" v-if="dialogData.rowData!.type !== 'LOCAL' && !isSftpOrWebDAV(dialogData.rowData!.type)"
:label="$t('setting.backupDir')" :label="$t('setting.backupDir')"
prop="backupPath" prop="backupPath"
> >
@ -347,6 +361,10 @@ function hasEndpoint(val: string) {
return val === 'OSS' || val === 'S3'; return val === 'OSS' || val === 'S3';
} }
function isSftpOrWebDAV(val: string) {
return val === 'SFTP' || val === 'WebDAV';
}
const toDoc = () => { const toDoc = () => {
window.open('https://1panel.cn/docs/user_manual/settings/', '_blank', 'noopener,noreferrer'); window.open('https://1panel.cn/docs/user_manual/settings/', '_blank', 'noopener,noreferrer');
}; };

1
go.mod
View File

@ -44,6 +44,7 @@ require (
github.com/spf13/afero v1.9.2 github.com/spf13/afero v1.9.2
github.com/spf13/cobra v1.7.0 github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.14.0 github.com/spf13/viper v1.14.0
github.com/studio-b12/gowebdav v0.9.0
github.com/subosito/gotenv v1.4.1 github.com/subosito/gotenv v1.4.1
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
github.com/swaggo/gin-swagger v1.5.3 github.com/swaggo/gin-swagger v1.5.3

2
go.sum
View File

@ -753,6 +753,8 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/studio-b12/gowebdav v0.9.0 h1:1j1sc9gQnNxbXXM4M/CebPOX4aXYtr7MojAVcN4dHjU=
github.com/studio-b12/gowebdav v0.9.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY= github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY=