mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-18 22:22:59 +08:00
feat: 备份账号增加 Onedrive (#1421)
This commit is contained in:
parent
ae38239b47
commit
3fa4a240f7
@ -8,15 +8,17 @@ type BackupOperate struct {
|
||||
Bucket string `json:"bucket"`
|
||||
AccessKey string `json:"accessKey"`
|
||||
Credential string `json:"credential"`
|
||||
BackupPath string `json:"backupPath"`
|
||||
Vars string `json:"vars" validate:"required"`
|
||||
}
|
||||
|
||||
type BackupInfo struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Type string `json:"type"`
|
||||
Bucket string `json:"bucket"`
|
||||
Vars string `json:"vars"`
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Type string `json:"type"`
|
||||
Bucket string `json:"bucket"`
|
||||
BackupPath string `json:"backupPath"`
|
||||
Vars string `json:"vars"`
|
||||
}
|
||||
|
||||
type BackupSearch struct {
|
||||
|
@ -6,6 +6,7 @@ type BackupAccount struct {
|
||||
Bucket string `gorm:"type:varchar(256)" json:"bucket"`
|
||||
AccessKey string `gorm:"type:varchar(256)" json:"accessKey"`
|
||||
Credential string `gorm:"type:varchar(256)" json:"credential"`
|
||||
BackupPath string `gorm:"type:varchar(256)" json:"backupPath"`
|
||||
Vars string `gorm:"type:longText" json:"vars"`
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,9 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
@ -62,6 +65,7 @@ func (u *BackupService) List() ([]dto.BackupInfo, error) {
|
||||
dtobas = append(dtobas, u.loadByType("MINIO", ops))
|
||||
dtobas = append(dtobas, u.loadByType("COS", ops))
|
||||
dtobas = append(dtobas, u.loadByType("KODO", ops))
|
||||
dtobas = append(dtobas, u.loadByType("OneDrive", ops))
|
||||
return dtobas, err
|
||||
}
|
||||
|
||||
@ -96,7 +100,6 @@ func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error)
|
||||
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||
return "", err
|
||||
}
|
||||
varMap["type"] = backup.Type
|
||||
varMap["bucket"] = backup.Bucket
|
||||
switch backup.Type {
|
||||
case constant.Sftp:
|
||||
@ -105,8 +108,10 @@ func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error)
|
||||
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
|
||||
varMap["accessKey"] = backup.AccessKey
|
||||
varMap["secretKey"] = backup.Credential
|
||||
case constant.OneDrive:
|
||||
varMap["accessToken"] = backup.Credential
|
||||
}
|
||||
backClient, err := cloud_storage.NewCloudStorageClient(varMap)
|
||||
backClient, err := cloud_storage.NewCloudStorageClient(backup.Type, varMap)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("new cloud storage client failed, err: %v", err)
|
||||
}
|
||||
@ -134,6 +139,12 @@ func (u *BackupService) Create(backupDto dto.BackupOperate) error {
|
||||
if err := copier.Copy(&backup, &backupDto); err != nil {
|
||||
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||
}
|
||||
|
||||
if backupDto.Type == constant.OneDrive {
|
||||
if err := u.loadAccessToken(&backup); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := backupRepo.Create(&backup); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -145,7 +156,6 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err
|
||||
if err := json.Unmarshal([]byte(backupDto.Vars), &varMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
varMap["type"] = backupDto.Type
|
||||
switch backupDto.Type {
|
||||
case constant.Sftp:
|
||||
varMap["username"] = backupDto.AccessKey
|
||||
@ -154,7 +164,7 @@ func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, err
|
||||
varMap["accessKey"] = backupDto.AccessKey
|
||||
varMap["secretKey"] = backupDto.Credential
|
||||
}
|
||||
client, err := cloud_storage.NewCloudStorageClient(varMap)
|
||||
client, err := cloud_storage.NewCloudStorageClient(backupDto.Type, varMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -215,6 +225,15 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
|
||||
upMap["bucket"] = req.Bucket
|
||||
upMap["credential"] = req.Credential
|
||||
upMap["vars"] = req.Vars
|
||||
backup.Vars = req.Vars
|
||||
|
||||
if req.Type == constant.OneDrive {
|
||||
if err := u.loadAccessToken(&backup); err != nil {
|
||||
return err
|
||||
}
|
||||
upMap["credential"] = backup.Credential
|
||||
upMap["vars"] = backup.Vars
|
||||
}
|
||||
if err := backupRepo.Update(req.ID, upMap); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -251,7 +270,6 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl
|
||||
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
varMap["type"] = backup.Type
|
||||
if backup.Type == "LOCAL" {
|
||||
return nil, errors.New("not support")
|
||||
}
|
||||
@ -263,9 +281,11 @@ func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.Cl
|
||||
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
|
||||
varMap["accessKey"] = backup.AccessKey
|
||||
varMap["secretKey"] = backup.Credential
|
||||
case constant.OneDrive:
|
||||
varMap["accessToken"] = backup.Credential
|
||||
}
|
||||
|
||||
backClient, err := cloud_storage.NewCloudStorageClient(varMap)
|
||||
backClient, err := cloud_storage.NewCloudStorageClient(backup.Type, varMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -286,6 +306,53 @@ func (u *BackupService) loadByType(accountType string, accounts []model.BackupAc
|
||||
return dto.BackupInfo{Type: accountType}
|
||||
}
|
||||
|
||||
func (u *BackupService) loadAccessToken(backup *model.BackupAccount) error {
|
||||
varMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||
return fmt.Errorf("unmarshal backup vars failed, err: %v", err)
|
||||
}
|
||||
|
||||
data := url.Values{}
|
||||
data.Set("client_id", constant.OneDriveClientID)
|
||||
data.Set("client_secret", constant.OneDriveClientSecret)
|
||||
data.Set("grant_type", "authorization_code")
|
||||
data.Set("code", varMap["code"].(string))
|
||||
data.Set("redirect_uri", constant.OneDriveRedirectURI)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("new http post client for access token failed, err: %v", err)
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("request for access token failed, err: %v", err)
|
||||
}
|
||||
delete(varMap, "code")
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read data from response body failed, err: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
token := map[string]interface{}{}
|
||||
if err := json.Unmarshal(respBody, &token); err != nil {
|
||||
return fmt.Errorf("unmarshal data from response body failed, err: %v", err)
|
||||
}
|
||||
accessToken, ok := token["refresh_token"].(string)
|
||||
if !ok {
|
||||
return errors.New("no such access token in response")
|
||||
}
|
||||
|
||||
itemVars, err := json.Marshal(varMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("json marshal var map failed, err: %v", err)
|
||||
}
|
||||
backup.Credential = accessToken
|
||||
backup.Vars = string(itemVars)
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadLocalDir() (string, error) {
|
||||
backup, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
|
||||
if err != nil {
|
||||
|
@ -100,9 +100,9 @@ func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.HandleRmExpired(backup.Type, localDir, &cronjob, client)
|
||||
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
|
||||
} else {
|
||||
u.HandleRmExpired(backup.Type, "", &cronjob, nil)
|
||||
u.HandleRmExpired(backup.Type, backup.BackupPath, "", &cronjob, nil)
|
||||
}
|
||||
}
|
||||
delRecords, err := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(req.CronjobID)))
|
||||
|
@ -33,16 +33,16 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
||||
return
|
||||
}
|
||||
message, err = u.handleShell(cronjob.Type, cronjob.Name, cronjob.Script)
|
||||
u.HandleRmExpired("LOCAL", "", cronjob, nil)
|
||||
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
|
||||
case "curl":
|
||||
if len(cronjob.URL) == 0 {
|
||||
return
|
||||
}
|
||||
message, err = u.handleShell(cronjob.Type, cronjob.Name, fmt.Sprintf("curl '%s'", cronjob.URL))
|
||||
u.HandleRmExpired("LOCAL", "", cronjob, nil)
|
||||
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
|
||||
case "ntp":
|
||||
err = u.handleNtpSync()
|
||||
u.HandleRmExpired("LOCAL", "", cronjob, nil)
|
||||
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
|
||||
case "website":
|
||||
record.File, err = u.handleBackup(cronjob, record.StartTime)
|
||||
case "database":
|
||||
@ -142,19 +142,25 @@ func (u *CronjobService) handleBackup(cronjob *model.Cronjob, startTime time.Tim
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(backup.BackupPath) != 0 {
|
||||
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
|
||||
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
|
||||
itemFileDir = itemPath + itemFileDir
|
||||
}
|
||||
if _, err = client.Upload(backupDir+"/"+fileName, itemFileDir+"/"+fileName); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
u.HandleRmExpired(backup.Type, localDir, cronjob, client)
|
||||
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, cronjob, client)
|
||||
if backup.Type == "LOCAL" || cronjob.KeepLocal {
|
||||
return fmt.Sprintf("%s/%s/%s/%s", localDir, cronjob.Type, cronjob.Name, fileName), nil
|
||||
return fmt.Sprintf("%s/%s", backupDir, fileName), nil
|
||||
} else {
|
||||
return fmt.Sprintf("%s/%s", itemFileDir, fileName), nil
|
||||
}
|
||||
return fmt.Sprintf("%s/%s/%s", cronjob.Type, cronjob.Name, fileName), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u *CronjobService) HandleRmExpired(backType, localDir string, cronjob *model.Cronjob, backClient cloud_storage.CloudStorageClient) {
|
||||
func (u *CronjobService) HandleRmExpired(backType, backupPath, localDir string, cronjob *model.Cronjob, backClient cloud_storage.CloudStorageClient) {
|
||||
global.LOG.Infof("start to handle remove expired, retain copies: %d", cronjob.RetainCopies)
|
||||
records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), commonRepo.WithOrderBy("created_at desc"))
|
||||
if len(records) > int(cronjob.RetainCopies) {
|
||||
@ -163,7 +169,7 @@ func (u *CronjobService) HandleRmExpired(backType, localDir string, cronjob *mod
|
||||
files := strings.Split(records[i].File, ",")
|
||||
for _, file := range files {
|
||||
if backType != "LOCAL" {
|
||||
_, _ = backClient.Delete(strings.ReplaceAll(file, localDir+"/", ""))
|
||||
_, _ = backClient.Delete(backupPath + "/" + strings.TrimPrefix(file, localDir+"/"))
|
||||
_ = os.Remove(file)
|
||||
} else {
|
||||
_ = os.Remove(file)
|
||||
@ -270,12 +276,11 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInf
|
||||
}
|
||||
record.DetailName = dbName
|
||||
record.FileDir = backupDir
|
||||
itemFileDir := strings.ReplaceAll(backupDir, localDir+"/", "")
|
||||
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
|
||||
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
|
||||
record.Source = backup.Type
|
||||
record.FileDir = itemFileDir
|
||||
}
|
||||
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
|
||||
|
||||
if err := backupRepo.CreateRecord(&record); err != nil {
|
||||
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||
@ -287,12 +292,22 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInf
|
||||
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
|
||||
}()
|
||||
}
|
||||
if len(backup.BackupPath) != 0 {
|
||||
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
|
||||
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
|
||||
itemFileDir = itemPath + itemFileDir
|
||||
}
|
||||
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
|
||||
return paths, err
|
||||
}
|
||||
}
|
||||
if backup.Type == "LOCAL" || cronjob.KeepLocal {
|
||||
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
|
||||
} else {
|
||||
paths = append(paths, fmt.Sprintf("%s/%s", itemFileDir, record.FileName))
|
||||
}
|
||||
}
|
||||
u.HandleRmExpired(backup.Type, localDir, &cronjob, client)
|
||||
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
@ -358,7 +373,7 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
u.HandleRmExpired("LOCAL", "", cronjob, nil)
|
||||
u.HandleRmExpired("LOCAL", "", "", cronjob, nil)
|
||||
return strings.Join(filePaths, ","), nil
|
||||
}
|
||||
|
||||
@ -399,10 +414,10 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.Backu
|
||||
}
|
||||
backupDir := fmt.Sprintf("%s/website/%s", localDir, website.PrimaryDomain)
|
||||
record.FileDir = backupDir
|
||||
itemFileDir := strings.ReplaceAll(backupDir, localDir+"/", "")
|
||||
itemFileDir := strings.TrimPrefix(backupDir, localDir+"/")
|
||||
if !cronjob.KeepLocal && backup.Type != "LOCAL" {
|
||||
record.Source = backup.Type
|
||||
record.FileDir = strings.ReplaceAll(backupDir, localDir+"/", "")
|
||||
record.FileDir = strings.TrimPrefix(backupDir, localDir+"/")
|
||||
}
|
||||
record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", website.PrimaryDomain, startTime.Format("20060102150405"))
|
||||
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
|
||||
@ -420,11 +435,21 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.Backu
|
||||
_ = os.RemoveAll(fmt.Sprintf("%s/%s", backupDir, record.FileName))
|
||||
}()
|
||||
}
|
||||
if len(backup.BackupPath) != 0 {
|
||||
itemPath := strings.TrimPrefix(backup.BackupPath, "/")
|
||||
itemPath = strings.TrimSuffix(itemPath, "/") + "/"
|
||||
itemFileDir = itemPath + itemFileDir
|
||||
}
|
||||
if _, err = client.Upload(backupDir+"/"+record.FileName, itemFileDir+"/"+record.FileName); err != nil {
|
||||
return paths, err
|
||||
}
|
||||
}
|
||||
if backup.Type == "LOCAL" || cronjob.KeepLocal {
|
||||
paths = append(paths, fmt.Sprintf("%s/%s", record.FileDir, record.FileName))
|
||||
} else {
|
||||
paths = append(paths, fmt.Sprintf("%s/%s", itemFileDir, record.FileName))
|
||||
}
|
||||
}
|
||||
u.HandleRmExpired(backup.Type, localDir, &cronjob, client)
|
||||
u.HandleRmExpired(backup.Type, backup.BackupPath, localDir, &cronjob, client)
|
||||
return paths, nil
|
||||
}
|
||||
|
@ -7,7 +7,12 @@ const (
|
||||
S3 = "S3"
|
||||
OSS = "OSS"
|
||||
Sftp = "SFTP"
|
||||
OneDrive = "OneDrive"
|
||||
MinIo = "MINIO"
|
||||
Cos = "COS"
|
||||
Kodo = "KODO"
|
||||
|
||||
OneDriveClientID = "5446cfe3-4c79-47a0-ae25-fc645478e2d9"
|
||||
OneDriveClientSecret = "ITh8Q~0UKJNXAvz6HE~pd3DTnGJOgDEEpnDOJbqY"
|
||||
OneDriveRedirectURI = "http://localhost/login/authorized"
|
||||
)
|
||||
|
@ -31,6 +31,7 @@ func Init() {
|
||||
migrations.AddBindAndAllowIPs,
|
||||
migrations.UpdateCronjobWithSecond,
|
||||
migrations.UpdateWebsite,
|
||||
migrations.AddBackupAccountDir,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
@ -390,3 +390,13 @@ var UpdateWebsite = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddBackupAccountDir = &gormigrate.Migration{
|
||||
ID: "20200620-add-backup-dir",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.BackupAccount{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
327
backend/utils/cloud_storage/client/onedrive.go
Normal file
327
backend/utils/cloud_storage/client/onedrive.go
Normal file
@ -0,0 +1,327 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
odsdk "github.com/goh-chunlin/go-onedrive/onedrive"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type oneDriveClient struct {
|
||||
Vars map[string]interface{}
|
||||
client odsdk.Client
|
||||
}
|
||||
|
||||
func NewOneDriveClient(vars map[string]interface{}) (*oneDriveClient, error) {
|
||||
token := ""
|
||||
if _, ok := vars["accessToken"]; ok {
|
||||
token = vars["accessToken"].(string)
|
||||
} else {
|
||||
return nil, constant.ErrInvalidParams
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
||||
newToken, err := refreshToken(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = global.DB.Model(&model.Group{}).Where("type = ?", "OneDrive").Updates(map[string]interface{}{"credential": newToken}).Error
|
||||
|
||||
ts := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: newToken},
|
||||
)
|
||||
tc := oauth2.NewClient(ctx, ts)
|
||||
|
||||
client := odsdk.NewClient(tc)
|
||||
return &oneDriveClient{client: *client}, nil
|
||||
}
|
||||
|
||||
func (onedrive oneDriveClient) ListBuckets() ([]interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (onedrive oneDriveClient) Exist(path string) (bool, error) {
|
||||
path = "/" + strings.TrimPrefix(path, "/")
|
||||
fileID, err := onedrive.loadIDByPath(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(fileID) != 0, nil
|
||||
}
|
||||
|
||||
func (onedrive oneDriveClient) Delete(path string) (bool, error) {
|
||||
path = "/" + strings.TrimPrefix(path, "/")
|
||||
fileID, err := onedrive.loadIDByPath(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := onedrive.client.DriveItems.Delete(context.Background(), "", fileID); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (onedrive oneDriveClient) Upload(src, target string) (bool, error) {
|
||||
target = "/" + strings.TrimPrefix(target, "/")
|
||||
if _, err := onedrive.loadIDByPath(path.Dir(target)); err != nil {
|
||||
if !strings.Contains(err.Error(), "itemNotFound") {
|
||||
return false, err
|
||||
}
|
||||
if err := onedrive.createFolder(path.Dir(target)); err != nil {
|
||||
return false, fmt.Errorf("create dir before upload failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
file, err := os.Open(src)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer file.Close()
|
||||
fileInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if fileInfo.IsDir() {
|
||||
return false, errors.New("Only file is allowed to be uploaded here.")
|
||||
}
|
||||
fileName := fileInfo.Name()
|
||||
fileSize := fileInfo.Size()
|
||||
|
||||
folderID, err := onedrive.loadIDByPath(path.Dir(target))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
apiURL := fmt.Sprintf("me/drive/items/%s:/%s:/createUploadSession", url.PathEscape(folderID), fileName)
|
||||
sessionCreationRequestInside := NewUploadSessionCreationRequest{
|
||||
ConflictBehavior: "rename",
|
||||
}
|
||||
|
||||
sessionCreationRequest := struct {
|
||||
Item NewUploadSessionCreationRequest `json:"item"`
|
||||
DeferCommit bool `json:"deferCommit"`
|
||||
}{sessionCreationRequestInside, false}
|
||||
|
||||
sessionCreationReq, err := onedrive.client.NewRequest("POST", apiURL, sessionCreationRequest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var sessionCreationResp *NewUploadSessionCreationResponse
|
||||
err = onedrive.client.Do(ctx, sessionCreationReq, false, &sessionCreationResp)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("session creation failed %w", err)
|
||||
}
|
||||
|
||||
fileSessionUploadUrl := sessionCreationResp.UploadURL
|
||||
|
||||
sizePerSplit := int64(3200 * 1024)
|
||||
buffer := make([]byte, 3200*1024)
|
||||
splitCount := fileSize / sizePerSplit
|
||||
if fileSize%sizePerSplit != 0 {
|
||||
splitCount += 1
|
||||
}
|
||||
bfReader := bufio.NewReader(file)
|
||||
var fileUploadResp *UploadSessionUploadResponse
|
||||
for splitNow := int64(0); splitNow < splitCount; splitNow++ {
|
||||
length, err := bfReader.Read(buffer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if int64(length) < sizePerSplit {
|
||||
bufferLast := buffer[:length]
|
||||
buffer = bufferLast
|
||||
}
|
||||
sessionFileUploadReq, err := onedrive.NewSessionFileUploadRequest(fileSessionUploadUrl, splitNow*sizePerSplit, fileSize, bytes.NewReader(buffer))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := onedrive.client.Do(ctx, sessionFileUploadReq, false, &fileUploadResp); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
if fileUploadResp.Id == "" {
|
||||
return false, errors.New("something went wrong. file upload incomplete. consider upload the file in a step-by-step manner")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (onedrive oneDriveClient) Download(src, target string) (bool, error) {
|
||||
src = "/" + strings.TrimPrefix(src, "/")
|
||||
req, err := onedrive.client.NewRequest("GET", fmt.Sprintf("me/drive/root:%s", src), nil)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("new request for file id failed, err: %v", err)
|
||||
}
|
||||
var driveItem *odsdk.DriveItem
|
||||
if err := onedrive.client.Do(context.Background(), req, false, &driveItem); err != nil {
|
||||
return false, fmt.Errorf("do request for file id failed, err: %v", err)
|
||||
}
|
||||
|
||||
resp, err := http.Get(driveItem.DownloadURL)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
out, err := os.Create(target)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer out.Close()
|
||||
buffer := make([]byte, 2*1024*1024)
|
||||
|
||||
_, err = io.CopyBuffer(out, resp.Body, buffer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (onedrive *oneDriveClient) ListObjects(prefix string) ([]interface{}, error) {
|
||||
prefix = "/" + strings.TrimPrefix(prefix, "/")
|
||||
folderID, err := onedrive.loadIDByPath(prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := onedrive.client.NewRequest("GET", fmt.Sprintf("me/drive/items/%s/children", folderID), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new request for delete failed, err: %v", err)
|
||||
}
|
||||
var driveItems *odsdk.OneDriveDriveItemsResponse
|
||||
if err := onedrive.client.Do(context.Background(), req, false, &driveItems); err != nil {
|
||||
return nil, fmt.Errorf("do request for delete failed, err: %v", err)
|
||||
}
|
||||
for _, item := range driveItems.DriveItems {
|
||||
return nil, fmt.Errorf("id: %v, name: %s \n", item.Id, item.Name)
|
||||
}
|
||||
|
||||
var itemList []interface{}
|
||||
for _, item := range driveItems.DriveItems {
|
||||
itemList = append(itemList, item.Name)
|
||||
}
|
||||
return itemList, nil
|
||||
}
|
||||
|
||||
func (onedrive *oneDriveClient) loadIDByPath(path string) (string, error) {
|
||||
req, err := onedrive.client.NewRequest("GET", fmt.Sprintf("me/drive/root:%s", path), nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("new request for file id failed, err: %v", err)
|
||||
}
|
||||
var driveItem *odsdk.DriveItem
|
||||
if err := onedrive.client.Do(context.Background(), req, false, &driveItem); err != nil {
|
||||
return "", fmt.Errorf("do request for file id failed, err: %v", err)
|
||||
}
|
||||
return driveItem.Id, nil
|
||||
}
|
||||
|
||||
func refreshToken(oldToken string) (string, error) {
|
||||
data := url.Values{}
|
||||
data.Set("client_id", constant.OneDriveClientID)
|
||||
data.Set("client_secret", constant.OneDriveClientSecret)
|
||||
data.Set("grant_type", "refresh_token")
|
||||
data.Set("refresh_token", oldToken)
|
||||
data.Set("redirect_uri", constant.OneDriveRedirectURI)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("new http post client for access token failed, err: %v", err)
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("request for access token failed, err: %v", err)
|
||||
}
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read data from response body failed, err: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
tokenMap := map[string]interface{}{}
|
||||
if err := json.Unmarshal(respBody, &tokenMap); err != nil {
|
||||
return "", fmt.Errorf("unmarshal data from response body failed, err: %v", err)
|
||||
}
|
||||
accessToken, ok := tokenMap["access_token"].(string)
|
||||
if !ok {
|
||||
return "", errors.New("no such access token in response")
|
||||
}
|
||||
return accessToken, nil
|
||||
}
|
||||
|
||||
func (onedrive *oneDriveClient) createFolder(parent string) error {
|
||||
if _, err := onedrive.loadIDByPath(path.Dir(parent)); err != nil {
|
||||
if !strings.Contains(err.Error(), "itemNotFound") {
|
||||
return err
|
||||
}
|
||||
_ = onedrive.createFolder(path.Dir(parent))
|
||||
}
|
||||
item2, err := onedrive.loadIDByPath(path.Dir(parent))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := onedrive.client.DriveItems.CreateNewFolder(context.Background(), "", item2, path.Base(parent)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NewUploadSessionCreationRequest struct {
|
||||
ConflictBehavior string `json:"@microsoft.graph.conflictBehavior,omitempty"`
|
||||
}
|
||||
type NewUploadSessionCreationResponse struct {
|
||||
UploadURL string `json:"uploadUrl"`
|
||||
ExpirationDateTime string `json:"expirationDateTime"`
|
||||
}
|
||||
type UploadSessionUploadResponse struct {
|
||||
ExpirationDateTime string `json:"expirationDateTime"`
|
||||
NextExpectedRanges []string `json:"nextExpectedRanges"`
|
||||
DriveItem
|
||||
}
|
||||
type DriveItem struct {
|
||||
Name string `json:"name"`
|
||||
Id string `json:"id"`
|
||||
DownloadURL string `json:"@microsoft.graph.downloadUrl"`
|
||||
Description string `json:"description"`
|
||||
Size int64 `json:"size"`
|
||||
WebURL string `json:"webUrl"`
|
||||
}
|
||||
|
||||
func (onedrive *oneDriveClient) NewSessionFileUploadRequest(absoluteUrl string, grandOffset, grandTotalSize int64, byteReader *bytes.Reader) (*http.Request, error) {
|
||||
apiUrl, err := onedrive.client.BaseURL.Parse(absoluteUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
absoluteUrl = apiUrl.String()
|
||||
contentLength := byteReader.Size()
|
||||
req, err := http.NewRequest("PUT", absoluteUrl, byteReader)
|
||||
req.Header.Set("Content-Length", strconv.FormatInt(contentLength, 10))
|
||||
preliminaryLength := grandOffset
|
||||
preliminaryRange := grandOffset + contentLength - 1
|
||||
if preliminaryRange >= grandTotalSize {
|
||||
preliminaryRange = grandTotalSize - 1
|
||||
preliminaryLength = preliminaryRange - grandOffset + 1
|
||||
}
|
||||
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", preliminaryLength, preliminaryRange, grandTotalSize))
|
||||
|
||||
return req, err
|
||||
}
|
@ -14,24 +14,23 @@ type CloudStorageClient interface {
|
||||
Download(src, target string) (bool, error)
|
||||
}
|
||||
|
||||
func NewCloudStorageClient(vars map[string]interface{}) (CloudStorageClient, error) {
|
||||
if vars["type"] == constant.S3 {
|
||||
func NewCloudStorageClient(backupType string, vars map[string]interface{}) (CloudStorageClient, error) {
|
||||
switch backupType {
|
||||
case constant.S3:
|
||||
return client.NewS3Client(vars)
|
||||
}
|
||||
if vars["type"] == constant.OSS {
|
||||
case constant.OSS:
|
||||
return client.NewOssClient(vars)
|
||||
}
|
||||
if vars["type"] == constant.Sftp {
|
||||
case constant.Sftp:
|
||||
return client.NewSftpClient(vars)
|
||||
}
|
||||
if vars["type"] == constant.MinIo {
|
||||
case constant.MinIo:
|
||||
return client.NewMinIoClient(vars)
|
||||
}
|
||||
if vars["type"] == constant.Cos {
|
||||
case constant.Cos:
|
||||
return client.NewCosClient(vars)
|
||||
}
|
||||
if vars["type"] == constant.Kodo {
|
||||
case constant.Kodo:
|
||||
return client.NewKodoClient(vars)
|
||||
case constant.OneDrive:
|
||||
return client.NewOneDriveClient(vars)
|
||||
default:
|
||||
return nil, constant.ErrNotSupportType
|
||||
}
|
||||
return nil, constant.ErrNotSupportType
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ export namespace Backup {
|
||||
accessKey: string;
|
||||
bucket: string;
|
||||
credential: string;
|
||||
backupPath: string;
|
||||
vars: string;
|
||||
varsJson: object;
|
||||
createdAt: Date;
|
||||
@ -17,6 +18,7 @@ export namespace Backup {
|
||||
accessKey: string;
|
||||
bucket: string;
|
||||
credential: string;
|
||||
backupPath: string;
|
||||
vars: string;
|
||||
}
|
||||
export interface RecordDownload {
|
||||
|
@ -1,9 +1,9 @@
|
||||
@font-face {
|
||||
font-family: "panel"; /* Project id 3575356 */
|
||||
src: url('iconfont.woff2?t=1684465849452') format('woff2'),
|
||||
url('iconfont.woff?t=1684465849452') format('woff'),
|
||||
url('iconfont.ttf?t=1684465849452') format('truetype'),
|
||||
url('iconfont.svg?t=1684465849452#panel') format('svg');
|
||||
src: url('iconfont.woff2?t=1687338712846') format('woff2'),
|
||||
url('iconfont.woff?t=1687338712846') format('woff'),
|
||||
url('iconfont.ttf?t=1687338712846') format('truetype'),
|
||||
url('iconfont.svg?t=1687338712846#panel') format('svg');
|
||||
}
|
||||
|
||||
.panel {
|
||||
@ -14,6 +14,10 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.p-onedrive:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
.p-caidan:before {
|
||||
content: "\e61d";
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -5,6 +5,13 @@
|
||||
"css_prefix_text": "p-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "13015332",
|
||||
"name": "onedrive",
|
||||
"font_class": "onedrive",
|
||||
"unicode": "e601",
|
||||
"unicode_decimal": 58881
|
||||
},
|
||||
{
|
||||
"icon_id": "7708032",
|
||||
"name": "菜单",
|
||||
|
@ -14,6 +14,8 @@
|
||||
/>
|
||||
<missing-glyph />
|
||||
|
||||
<glyph glyph-name="onedrive" unicode="" 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="caidan" unicode="" d="M896 663.272727h-744.727273a34.909091 34.909091 0 0 0 0 69.818182h744.727273a34.909091 34.909091 0 0 0 0-69.818182zM896 11.636364h-744.727273a34.909091 34.909091 0 0 0 0 69.818181h744.727273a34.909091 34.909091 0 0 0 0-69.818181zM709.818182 337.454545h-558.545455a34.909091 34.909091 0 0 0 0 69.818182h558.545455a34.909091 34.909091 0 0 0 0-69.818182z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="yanzhengma1" unicode="" d="M544 627h124c17.673 0 32-14.327 32-32 0-17.673-14.327-32-32-32H544v-116a32.5 32.5 0 0 0-0.056-1.916C670.218 429.588 768 321.96299999999997 768 191.5 768 50.39099999999996 653.609-64 512.5-64 371.391-64 257 50.39099999999996 257 191.5c0 130.12 97.27 237.523 223.064 253.46A32.488 32.488 0 0 0 480 447V800c0 17.673 14.327 32 32 32 17.673 0 32-14.327 32-32v-45h192c17.673 0 32-14.327 32-32 0-17.673-14.327-32-32-32H544v-64z m-31.5-627C618.263 0 704 85.73699999999997 704 191.5S618.263 383 512.5 383 321 297.26300000000003 321 191.5 406.737 0 512.5 0z" horiz-adv-x="1024" />
|
||||
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -989,6 +989,11 @@ const message = {
|
||||
S3: 'Amazon S3',
|
||||
MINIO: 'MINIO',
|
||||
SFTP: 'SFTP',
|
||||
OneDrive: 'Microsoft OneDrive',
|
||||
backupDir: 'Backup dir',
|
||||
isCN: 'Domestic version',
|
||||
code: 'Auth code',
|
||||
loadCode: 'Acquire',
|
||||
COS: 'Tencent COS',
|
||||
KODO: 'Qiniu Kodo',
|
||||
domainHelper: 'The accelerated domain name must contain http:// or https://',
|
||||
|
@ -967,6 +967,11 @@ const message = {
|
||||
S3: '亚马逊 S3 云存储',
|
||||
MINIO: 'MINIO',
|
||||
SFTP: 'SFTP',
|
||||
OneDrive: '微软 OneDrive',
|
||||
backupDir: '备份路径',
|
||||
isCN: '国内版',
|
||||
code: '授权码',
|
||||
loadCode: '获取',
|
||||
COS: '腾讯云 COS',
|
||||
KODO: '七牛云 Kodo',
|
||||
domainHelper: '加速域名必须包含 http:// 或者 https://',
|
||||
|
@ -66,6 +66,9 @@
|
||||
<el-form-item label="Bucket">
|
||||
{{ s3Data.bucket }}
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.backupDir')">
|
||||
{{ s3Data.backupPath }}
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.createdAt')">
|
||||
{{ dateFormat(0, 0, s3Data.createdAt) }}
|
||||
</el-form-item>
|
||||
@ -102,6 +105,9 @@
|
||||
<el-form-item label="Bucket">
|
||||
{{ ossData.bucket }}
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.backupDir')">
|
||||
{{ ossData.backupPath }}
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.createdAt')">
|
||||
{{ dateFormat(0, 0, ossData.createdAt) }}
|
||||
</el-form-item>
|
||||
@ -139,6 +145,9 @@
|
||||
<el-form-item label="Bucket">
|
||||
{{ cosData.bucket }}
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.backupDir')">
|
||||
{{ cosData.backupPath }}
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.createdAt')">
|
||||
{{ dateFormat(0, 0, cosData.createdAt) }}
|
||||
</el-form-item>
|
||||
@ -149,6 +158,47 @@
|
||||
</el-button>
|
||||
</el-alert>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<div>
|
||||
<svg-icon style="font-size: 7px" iconName="p-onedrive"></svg-icon>
|
||||
<span style="font-size: 14px; font-weight: 500"> {{ $t('setting.OneDrive') }}</span>
|
||||
<div style="float: right">
|
||||
<el-button
|
||||
round
|
||||
plain
|
||||
:disabled="oneDriveData.id === 0"
|
||||
@click="onOpenDialog('edit', 'SFTP', oneDriveData)"
|
||||
>
|
||||
{{ $t('commons.button.edit') }}
|
||||
</el-button>
|
||||
<el-button round :disabled="oneDriveData.id === 0" @click="onDelete(oneDriveData)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider class="devider" />
|
||||
<div v-if="oneDriveData.id !== 0" style="margin-left: 20px">
|
||||
<el-form-item :label="$t('setting.backupDir')">
|
||||
{{ oneDriveData.backupPath }}
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.createdAt')">
|
||||
{{ dateFormat(0, 0, oneDriveData.createdAt) }}
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-alert v-else center class="alert" style="height: 167px" :closable="false">
|
||||
<el-button
|
||||
size="large"
|
||||
round
|
||||
plain
|
||||
type="primary"
|
||||
@click="onOpenDialog('create', 'OneDrive')"
|
||||
>
|
||||
{{ $t('setting.createBackupAccount', [$t('setting.OneDrive')]) }}
|
||||
</el-button>
|
||||
</el-alert>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" style="margin-top: 20px">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<div>
|
||||
<svg-icon style="font-size: 7px" iconName="p-qiniuyun"></svg-icon>
|
||||
@ -175,6 +225,9 @@
|
||||
<el-form-item label="Bucket">
|
||||
{{ kodoData.bucket }}
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.backupDir')">
|
||||
{{ kodoData.backupPath }}
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.createdAt')">
|
||||
{{ dateFormat(0, 0, kodoData.createdAt) }}
|
||||
</el-form-item>
|
||||
@ -185,8 +238,6 @@
|
||||
</el-button>
|
||||
</el-alert>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" style="margin-top: 20px">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<div>
|
||||
<svg-icon style="font-size: 7px" iconName="p-minio"></svg-icon>
|
||||
@ -212,6 +263,9 @@
|
||||
<el-form-item label="Bucket">
|
||||
{{ minioData.bucket }}
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.backupDir')">
|
||||
{{ minioData.backupPath }}
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.createdAt')">
|
||||
{{ dateFormat(0, 0, minioData.createdAt) }}
|
||||
</el-form-item>
|
||||
@ -222,6 +276,8 @@
|
||||
</el-button>
|
||||
</el-alert>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" style="margin-top: 20px">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<div>
|
||||
<svg-icon style="font-size: 7px" iconName="p-SFTP"></svg-icon>
|
||||
@ -251,6 +307,9 @@
|
||||
<el-form-item :label="$t('setting.path')">
|
||||
{{ sftpData.bucket }}
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.backupDir')">
|
||||
{{ sftpData.backupPath }}
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.createdAt')">
|
||||
{{ dateFormat(0, 0, sftpData.createdAt) }}
|
||||
</el-form-item>
|
||||
@ -284,6 +343,7 @@ const localData = ref<Backup.BackupInfo>({
|
||||
accessKey: '',
|
||||
bucket: '',
|
||||
credential: '',
|
||||
backupPath: '',
|
||||
vars: '',
|
||||
varsJson: {
|
||||
dir: '',
|
||||
@ -296,6 +356,7 @@ const ossData = ref<Backup.BackupInfo>({
|
||||
accessKey: '',
|
||||
bucket: '',
|
||||
credential: '',
|
||||
backupPath: '',
|
||||
vars: '',
|
||||
varsJson: {
|
||||
region: '',
|
||||
@ -309,6 +370,7 @@ const minioData = ref<Backup.BackupInfo>({
|
||||
accessKey: '',
|
||||
bucket: '',
|
||||
credential: '',
|
||||
backupPath: '',
|
||||
vars: '',
|
||||
varsJson: {
|
||||
region: '',
|
||||
@ -322,6 +384,7 @@ const sftpData = ref<Backup.BackupInfo>({
|
||||
accessKey: '',
|
||||
bucket: '',
|
||||
credential: '',
|
||||
backupPath: '',
|
||||
vars: '',
|
||||
varsJson: {
|
||||
address: '',
|
||||
@ -329,12 +392,26 @@ const sftpData = ref<Backup.BackupInfo>({
|
||||
},
|
||||
createdAt: new Date(),
|
||||
});
|
||||
const oneDriveData = ref<Backup.BackupInfo>({
|
||||
id: 0,
|
||||
type: 'OneDrive',
|
||||
accessKey: '',
|
||||
bucket: '',
|
||||
credential: '',
|
||||
backupPath: '',
|
||||
vars: '',
|
||||
varsJson: {
|
||||
redirectURI: '',
|
||||
},
|
||||
createdAt: new Date(),
|
||||
});
|
||||
const s3Data = ref<Backup.BackupInfo>({
|
||||
id: 0,
|
||||
type: 'S3',
|
||||
accessKey: '',
|
||||
bucket: '',
|
||||
credential: '',
|
||||
backupPath: '',
|
||||
vars: '',
|
||||
varsJson: {
|
||||
region: '',
|
||||
@ -348,6 +425,7 @@ const cosData = ref<Backup.BackupInfo>({
|
||||
accessKey: '',
|
||||
bucket: '',
|
||||
credential: '',
|
||||
backupPath: '',
|
||||
vars: '',
|
||||
varsJson: {
|
||||
region: '',
|
||||
@ -360,6 +438,7 @@ const kodoData = ref<Backup.BackupInfo>({
|
||||
accessKey: '',
|
||||
bucket: '',
|
||||
credential: '',
|
||||
backupPath: '',
|
||||
vars: '',
|
||||
varsJson: {
|
||||
domain: '',
|
||||
@ -396,6 +475,9 @@ const search = async () => {
|
||||
case 'KODO':
|
||||
kodoData.value = bac;
|
||||
break;
|
||||
case 'OneDrive':
|
||||
oneDriveData.value = bac;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -45,6 +45,21 @@
|
||||
>
|
||||
<el-input show-password clearable v-model.trim="dialogData.rowData!.credential" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="dialogData.rowData!.type === 'OneDrive'">
|
||||
<el-checkbox v-model="dialogData.rowData!.varsJson['isCN']" :label="$t('setting.isCN')" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="dialogData.rowData!.type === 'OneDrive'"
|
||||
:label="$t('setting.code')"
|
||||
prop="varsJson.code"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-input clearable v-model.trim="dialogData.rowData!.varsJson['code']">
|
||||
<template #append>
|
||||
<el-button @click="jumpAzure">{{ $t('setting.loadCode') }}</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="dialogData.rowData!.type === 'S3' || dialogData.rowData!.type === 'COS'"
|
||||
label="Region"
|
||||
@ -136,6 +151,13 @@
|
||||
<el-input v-model="dialogData.rowData!.bucket" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item
|
||||
v-if="dialogData.rowData!.type !== 'LOCAL'"
|
||||
:label="$t('setting.backupDir')"
|
||||
prop="backupPath"
|
||||
>
|
||||
<el-input clearable v-model.trim="dialogData.rowData!.backupPath" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
@ -206,6 +228,16 @@ const handleClose = () => {
|
||||
drawerVisiable.value = false;
|
||||
};
|
||||
|
||||
const jumpAzure = () => {
|
||||
let commonUrl =
|
||||
'response_type=code&client_id=5446cfe3-4c79-47a0-ae25-fc645478e2d9&redirect_uri=http://localhost/login/authorized&scope=offline_access+Files.ReadWrite.All+User.Read';
|
||||
if (!dialogData.value.rowData!.varsJson['isCN']) {
|
||||
window.open('https://login.microsoftonline.com/common/oauth2/v2.0/authorize?' + commonUrl, '_blank');
|
||||
} else {
|
||||
window.open('https://login.chinacloudapi.cn/common/oauth2/v2.0/authorize?' + commonUrl, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
const loadDir = async (path: string) => {
|
||||
dialogData.value.rowData!.varsJson['dir'] = path;
|
||||
};
|
||||
|
12
go.mod
12
go.mod
@ -22,6 +22,7 @@ require (
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.0.2
|
||||
github.com/go-playground/validator/v10 v10.14.0
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/goh-chunlin/go-onedrive v1.1.1
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
@ -51,6 +52,7 @@ require (
|
||||
github.com/xlzd/gotp v0.0.0-20220817083547-a63b9d03d72f
|
||||
golang.org/x/crypto v0.9.0
|
||||
golang.org/x/net v0.10.0
|
||||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783
|
||||
golang.org/x/sys v0.8.0
|
||||
golang.org/x/text v0.9.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
@ -113,7 +115,7 @@ require (
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.1 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
@ -141,6 +143,7 @@ require (
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||
github.com/h2non/filetype v1.1.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
@ -208,7 +211,7 @@ require (
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/testify v1.8.3 // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 // indirect
|
||||
@ -229,7 +232,7 @@ require (
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.29.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0 // indirect
|
||||
go.opentelemetry.io/otel v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel v1.15.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1 // indirect
|
||||
@ -237,13 +240,12 @@ require (
|
||||
go.opentelemetry.io/otel/internal/metric v0.27.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v0.27.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.4.1 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.15.1 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
|
||||
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/image v0.5.0 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/term v0.8.0 // indirect
|
||||
golang.org/x/time v0.1.0 // indirect
|
||||
|
20
go.sum
20
go.sum
@ -299,8 +299,8 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7
|
||||
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
@ -349,6 +349,8 @@ github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/goh-chunlin/go-onedrive v1.1.1 h1:HGtHk5iG0MZ92zYUtaY04czfZPBIJUr12UuFc+PW8m4=
|
||||
github.com/goh-chunlin/go-onedrive v1.1.1/go.mod h1:N8qIGHD7tryO734epiBKk5oXcpGwxKET/u3LuBHciTs=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
@ -461,6 +463,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaW
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/h2non/filetype v1.1.1 h1:xvOwnXKAckvtLWsN398qS9QhlxlnVXBjXBydK2/UFB4=
|
||||
github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@ -827,8 +831,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
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/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
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/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY=
|
||||
@ -915,8 +920,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0 h1:SLme4Po
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0/go.mod h1:tLYsuf2v8fZreBVwp9gVMhefZlLFZaUiNVSq8QxXRII=
|
||||
go.opentelemetry.io/otel v1.4.0/go.mod h1:jeAqMFKy2uLIxCtKxoFj0FAL5zAPKQagc3+GtBWakzk=
|
||||
go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4=
|
||||
go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM=
|
||||
go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU=
|
||||
go.opentelemetry.io/otel v1.15.1 h1:3Iwq3lfRByPaws0f6bU3naAqOR1n5IeDWd9390kWHa8=
|
||||
go.opentelemetry.io/otel v1.15.1/go.mod h1:mHHGEHVDLal6YrKMmk9LqC4a3sF5g+fHfrttQIB1NTc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1 h1:imIM3vRDMyZK1ypQlQlO+brE22I9lRhJsBDXpDWjlz8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1 h1:WPpPsAAs8I2rA47v5u0558meKmmwm1Dj99ZbqCV8sZ8=
|
||||
@ -933,8 +938,8 @@ go.opentelemetry.io/otel/sdk v1.4.1 h1:J7EaW71E0v87qflB4cDolaqq3AcujGrtyIPGQoZOB
|
||||
go.opentelemetry.io/otel/sdk v1.4.1/go.mod h1:NBwHDgDIBYjwK2WNu1OPgsIc2IJzmBXNnvIJxJc8BpE=
|
||||
go.opentelemetry.io/otel/trace v1.4.0/go.mod h1:uc3eRsqDfWs9R7b92xbQbU42/eTNz4N+gLP8qJCi4aE=
|
||||
go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc=
|
||||
go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M=
|
||||
go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8=
|
||||
go.opentelemetry.io/otel/trace v1.15.1 h1:uXLo6iHJEzDfrNC0L0mNjItIp06SyaBQxu5t3xMlngY=
|
||||
go.opentelemetry.io/otel/trace v1.15.1/go.mod h1:IWdQG/5N1x7f6YUlmdLeJvH9yxtuJAfc4VW5Agv9r/8=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v0.12.0 h1:CMJ/3Wp7iOWES+CYLfnBv+DVmPbB+kmy9PJ92XvlR6c=
|
||||
go.opentelemetry.io/proto/otlp v0.12.0/go.mod h1:TsIjwGWIx5VFYv9KGVlOpxoBl5Dy+63SUguV7GGvlSQ=
|
||||
@ -1015,6 +1020,7 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk=
|
||||
|
Loading…
Reference in New Issue
Block a user