feat: 同类备份账号支持添加多条 (#6061)

This commit is contained in:
ssongliu 2024-08-08 15:38:37 +08:00 committed by GitHub
parent 3d2d4318c7
commit 94b5a7fae4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
75 changed files with 3251 additions and 2299 deletions

View File

@ -2,6 +2,7 @@ package model
type BackupAccount struct {
BaseModel
Name string `gorm:"type:varchar(64);unique;not null" json:"name"`
Type string `gorm:"type:varchar(64);unique;not null" json:"type"`
Bucket string `gorm:"type:varchar(256)" json:"bucket"`
AccessKey string `gorm:"type:varchar(256)" json:"accessKey"`

View File

@ -13,7 +13,7 @@ import (
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost
// @BasePath /api/v1
// @BasePath /api/v2
func main() {
server.Start()
}

View File

@ -178,13 +178,6 @@ var InitSetting = &gormigrate.Migration{
return err
}
if err := tx.Create(&model.Setting{Key: "OneDriveID", Value: "MDEwOTM1YTktMWFhOS00ODU0LWExZGMtNmU0NWZlNjI4YzZi"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "OneDriveSc", Value: "akpuOFF+YkNXOU1OLWRzS1ZSRDdOcG1LT2ZRM0RLNmdvS1RkVWNGRA=="}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "FileRecycleBin", Value: "enable"}).Error; err != nil {
return err
}

View File

@ -23,7 +23,7 @@ func Routers() *gin.Engine {
c.JSON(200, "ok")
})
PublicGroup.Use(gzip.Gzip(gzip.DefaultCompression))
PublicGroup.Static("/api/v1/images", "./uploads")
PublicGroup.Static("/api/v2/images", "./uploads")
}
PrivateGroup := Router.Group("/api/v2")
if global.CurrentNode != "127.0.0.1" {

View File

@ -11,30 +11,30 @@ import (
)
var whiteUrlList = map[string]struct{}{
"/api/v1/auth/login": {},
"/api/v1/websites/config": {},
"/api/v1/websites/waf/config": {},
"/api/v1/files/loadfile": {},
"/api/v1/files/size": {},
"/api/v1/logs/operation": {},
"/api/v1/logs/login": {},
"/api/v1/auth/logout": {},
"/api/v2/auth/login": {},
"/api/v2/websites/config": {},
"/api/v2/websites/waf/config": {},
"/api/v2/files/loadfile": {},
"/api/v2/files/size": {},
"/api/v2/logs/operation": {},
"/api/v2/logs/login": {},
"/api/v2/auth/logout": {},
"/api/v1/apps/installed/loadport": {},
"/api/v1/apps/installed/check": {},
"/api/v1/apps/installed/conninfo": {},
"/api/v1/databases/load/file": {},
"/api/v1/databases/variables": {},
"/api/v1/databases/status": {},
"/api/v1/databases/baseinfo": {},
"/api/v2/apps/installed/loadport": {},
"/api/v2/apps/installed/check": {},
"/api/v2/apps/installed/conninfo": {},
"/api/v2/databases/load/file": {},
"/api/v2/databases/variables": {},
"/api/v2/databases/status": {},
"/api/v2/databases/baseinfo": {},
"/api/v1/waf/attack/stat": {},
"/api/v1/waf/config/website": {},
"/api/v2/waf/attack/stat": {},
"/api/v2/waf/config/website": {},
"/api/v1/monitor/stat": {},
"/api/v1/monitor/visitors": {},
"/api/v1/monitor/visitors/loc": {},
"/api/v1/monitor/qps": {},
"/api/v2/monitor/stat": {},
"/api/v2/monitor/visitors": {},
"/api/v2/monitor/visitors/loc": {},
"/api/v2/monitor/qps": {},
}
func DemoHandle() gin.HandlerFunc {

200
core/app/api/v2/backup.go Normal file
View File

@ -0,0 +1,200 @@
package v2
import (
"encoding/base64"
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/gin-gonic/gin"
)
// @Tags Backup Account
// @Summary Create backup account
// @Description 创建备份账号
// @Accept json
// @Param request body dto.BackupOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /core/backup [post]
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建备份账号 [type]","formatEN":"create backup account [type]"}
func (b *BaseApi) CreateBackup(c *gin.Context) {
var req dto.BackupOperate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if len(req.Credential) != 0 {
credential, err := base64.StdEncoding.DecodeString(req.Credential)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Credential = string(credential)
}
if len(req.AccessKey) != 0 {
accessKey, err := base64.StdEncoding.DecodeString(req.AccessKey)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.AccessKey = string(accessKey)
}
if err := backupService.Create(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Backup Account
// @Summary Refresh OneDrive token
// @Description 刷新 OneDrive token
// @Success 200
// @Security ApiKeyAuth
// @Router /core/backup/refresh/onedrive [post]
func (b *BaseApi) RefreshOneDriveToken(c *gin.Context) {
backupService.Run()
helper.SuccessWithData(c, nil)
}
// @Tags Backup Account
// @Summary List buckets
// @Description 获取 bucket 列表
// @Accept json
// @Param request body dto.ForBuckets true "request"
// @Success 200 {array} string
// @Security ApiKeyAuth
// @Router /core/backup/search [post]
func (b *BaseApi) ListBuckets(c *gin.Context) {
var req dto.ForBuckets
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if len(req.Credential) != 0 {
credential, err := base64.StdEncoding.DecodeString(req.Credential)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Credential = string(credential)
}
if len(req.AccessKey) != 0 {
accessKey, err := base64.StdEncoding.DecodeString(req.AccessKey)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.AccessKey = string(accessKey)
}
buckets, err := backupService.GetBuckets(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, buckets)
}
// @Tags Backup Account
// @Summary Load OneDrive info
// @Description 获取 OneDrive 信息
// @Accept json
// @Success 200 {object} dto.OneDriveInfo
// @Security ApiKeyAuth
// @Router /core/backup/onedrive [get]
func (b *BaseApi) LoadOneDriveInfo(c *gin.Context) {
data, err := backupService.LoadOneDriveInfo()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Backup Account
// @Summary Delete backup account
// @Description 删除备份账号
// @Accept json
// @Param request body dto.OperateByID true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /core/backup/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"backup_accounts","output_column":"type","output_value":"types"}],"formatZH":"删除备份账号 [types]","formatEN":"delete backup account [types]"}
func (b *BaseApi) DeleteBackup(c *gin.Context) {
var req dto.OperateByID
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := backupService.Delete(req.ID); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Backup Account
// @Summary Update backup account
// @Description 更新备份账号信息
// @Accept json
// @Param request body dto.BackupOperate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /core/backup/update [post]
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新备份账号 [types]","formatEN":"update backup account [types]"}
func (b *BaseApi) UpdateBackup(c *gin.Context) {
var req dto.BackupOperate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if len(req.Credential) != 0 {
credential, err := base64.StdEncoding.DecodeString(req.Credential)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Credential = string(credential)
}
if len(req.AccessKey) != 0 {
accessKey, err := base64.StdEncoding.DecodeString(req.AccessKey)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.AccessKey = string(accessKey)
}
if err := backupService.Update(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Backup Account
// @Summary Search backup accounts with page
// @Description 获取备份账号列表
// @Accept json
// @Param request body dto.SearchPageWithType true "request"
// @Success 200 {array} dto.BackupList
// @Security ApiKeyAuth
// @Router /core/backup/search [get]
func (b *BaseApi) SearchBackup(c *gin.Context) {
var req dto.SearchPageWithType
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, list, err := backupService.SearchWithPage(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}

View File

@ -10,6 +10,7 @@ var ApiGroupApp = new(ApiGroup)
var (
authService = service.NewIAuthService()
backupService = service.NewIBackupService()
settingService = service.NewISettingService()
logService = service.NewILogService()
upgradeService = service.NewIUpgradeService()

43
core/app/dto/backup.go Normal file
View File

@ -0,0 +1,43 @@
package dto
import "time"
type BackupOperate struct {
ID uint `json:"id"`
Name string `json:"name"`
Type string `json:"type" validate:"required"`
Bucket string `json:"bucket"`
AccessKey string `json:"accessKey"`
Credential string `json:"credential"`
BackupPath string `json:"backupPath"`
Vars string `json:"vars" validate:"required"`
RememberAuth bool `json:"rememberAuth"`
}
type BackupInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Bucket string `json:"bucket"`
AccessKey string `json:"accessKey"`
Credential string `json:"credential"`
BackupPath string `json:"backupPath"`
Vars string `json:"vars"`
CreatedAt time.Time `json:"createdAt"`
RememberAuth bool `json:"rememberAuth"`
}
type OneDriveInfo struct {
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
RedirectUri string `json:"redirect_uri"`
}
type ForBuckets struct {
Type string `json:"type" validate:"required"`
AccessKey string `json:"accessKey"`
Credential string `json:"credential" validate:"required"`
Vars string `json:"vars" validate:"required"`
}

View File

@ -5,6 +5,12 @@ type SearchWithPage struct {
Info string `json:"info"`
}
type SearchPageWithType struct {
PageInfo
Type string `json:"type"`
Info string `json:"info"`
}
type PageInfo struct {
Page int `json:"page" validate:"required,number"`
PageSize int `json:"pageSize" validate:"required,number"`
@ -24,3 +30,7 @@ type Response struct {
type Options struct {
Option string `json:"option"`
}
type OperateByID struct {
ID uint `json:"id"`
}

16
core/app/model/backup.go Normal file
View File

@ -0,0 +1,16 @@
package model
type BackupAccount struct {
BaseModel
Name string `gorm:"type:varchar(64);unique;not null" json:"name"`
Type string `gorm:"type:varchar(64);unique;not null" json:"type"`
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"`
RememberAuth bool `gorm:"type:varchar(64)" json:"rememberAuth"`
InUsed bool `gorm:"type:varchar(64)" json:"inUsed"`
EntryID uint `gorm:"type:varchar(64)" json:"entryID"`
}

69
core/app/repo/backup.go Normal file
View File

@ -0,0 +1,69 @@
package repo
import (
"github.com/1Panel-dev/1Panel/core/app/model"
"github.com/1Panel-dev/1Panel/core/global"
)
type BackupRepo struct{}
type IBackupRepo interface {
Get(opts ...DBOption) (model.BackupAccount, error)
List(opts ...DBOption) ([]model.BackupAccount, error)
Page(limit, offset int, opts ...DBOption) (int64, []model.BackupAccount, error)
Create(backup *model.BackupAccount) error
Update(id uint, vars map[string]interface{}) error
Delete(opts ...DBOption) error
}
func NewIBackupRepo() IBackupRepo {
return &BackupRepo{}
}
func (u *BackupRepo) Get(opts ...DBOption) (model.BackupAccount, error) {
var backup model.BackupAccount
db := global.DB
for _, opt := range opts {
db = opt(db)
}
err := db.First(&backup).Error
return backup, err
}
func (u *BackupRepo) Page(page, size int, opts ...DBOption) (int64, []model.BackupAccount, error) {
var ops []model.BackupAccount
db := global.DB.Model(&model.BackupAccount{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&ops).Error
return count, ops, err
}
func (u *BackupRepo) List(opts ...DBOption) ([]model.BackupAccount, error) {
var ops []model.BackupAccount
db := global.DB.Model(&model.BackupAccount{})
for _, opt := range opts {
db = opt(db)
}
err := db.Find(&ops).Error
return ops, err
}
func (u *BackupRepo) Create(backup *model.BackupAccount) error {
return global.DB.Create(backup).Error
}
func (u *BackupRepo) Update(id uint, vars map[string]interface{}) error {
return global.DB.Model(&model.BackupAccount{}).Where("id = ?", id).Updates(vars).Error
}
func (u *BackupRepo) Delete(opts ...DBOption) error {
db := global.DB
for _, opt := range opts {
db = opt(db)
}
return db.Delete(&model.BackupAccount{}).Error
}

View File

@ -8,6 +8,8 @@ type DBOption func(*gorm.DB) *gorm.DB
type ICommonRepo interface {
WithByID(id uint) DBOption
WithByName(name string) DBOption
WithByType(ty string) DBOption
WithOrderBy(orderStr string) DBOption
}
@ -22,6 +24,22 @@ func (c *CommonRepo) WithByID(id uint) DBOption {
return g.Where("id = ?", id)
}
}
func (c *CommonRepo) WithByName(name string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(name) == 0 {
return g
}
return g.Where("`name` = ?", name)
}
}
func (c *CommonRepo) WithByType(ty string) DBOption {
return func(g *gorm.DB) *gorm.DB {
if len(ty) == 0 {
return g
}
return g.Where("`type` = ?", ty)
}
}
func (c *CommonRepo) WithOrderBy(orderStr string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Order(orderStr)

405
core/app/service/backup.go Normal file
View File

@ -0,0 +1,405 @@
package service
import (
"bufio"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"path"
"strings"
"time"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/model"
"github.com/1Panel-dev/1Panel/core/buserr"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/utils/cloud_storage"
"github.com/1Panel-dev/1Panel/core/utils/cloud_storage/client"
fileUtils "github.com/1Panel-dev/1Panel/core/utils/files"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/robfig/cron/v3"
)
type BackupService struct{}
type IBackupService interface {
SearchWithPage(search dto.SearchPageWithType) (int64, interface{}, error)
LoadOneDriveInfo() (dto.OneDriveInfo, error)
Create(backupDto dto.BackupOperate) error
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
Update(req dto.BackupOperate) error
Delete(id uint) error
NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error)
Run()
}
func NewIBackupService() IBackupService {
return &BackupService{}
}
func (u *BackupService) SearchWithPage(req dto.SearchPageWithType) (int64, interface{}, error) {
count, accounts, err := backupRepo.Page(
req.Page,
req.PageSize,
commonRepo.WithByType(req.Type),
commonRepo.WithByName(req.Info),
commonRepo.WithOrderBy("created_at desc"),
)
if err != nil {
return 0, nil, err
}
var data []dto.BackupInfo
for _, account := range accounts {
var item dto.BackupInfo
if err := copier.Copy(&item, &account); err != nil {
global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err)
}
if !item.RememberAuth {
item.AccessKey = ""
item.Credential = ""
} else {
item.AccessKey = base64.StdEncoding.EncodeToString([]byte(item.AccessKey))
item.Credential = base64.StdEncoding.EncodeToString([]byte(item.Credential))
}
if account.Type == constant.OneDrive {
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(item.Vars), &varMap); err != nil {
continue
}
delete(varMap, "refresh_token")
itemVars, _ := json.Marshal(varMap)
item.Vars = string(itemVars)
}
data = append(data, item)
}
return count, data, nil
}
func (u *BackupService) LoadOneDriveInfo() (dto.OneDriveInfo, error) {
var data dto.OneDriveInfo
data.RedirectUri = constant.OneDriveRedirectURI
clientID, err := settingRepo.Get(settingRepo.WithByKey("OneDriveID"))
if err != nil {
return data, err
}
idItem, err := base64.StdEncoding.DecodeString(clientID.Value)
if err != nil {
return data, err
}
data.ClientID = string(idItem)
clientSecret, err := settingRepo.Get(settingRepo.WithByKey("OneDriveSc"))
if err != nil {
return data, err
}
secretItem, err := base64.StdEncoding.DecodeString(clientSecret.Value)
if err != nil {
return data, err
}
data.ClientSecret = string(secretItem)
return data, err
}
func (u *BackupService) Create(req dto.BackupOperate) error {
backup, _ := backupRepo.Get(commonRepo.WithByName(req.Name))
if backup.ID != 0 {
return constant.ErrRecordExist
}
if err := copier.Copy(&backup, &req); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
if req.Type == constant.OneDrive {
if err := u.loadAccessToken(&backup); err != nil {
return err
}
}
if req.Type != "LOCAL" {
if _, err := u.checkBackupConn(&backup); err != nil {
return buserr.WithMap("ErrBackupCheck", map[string]interface{}{"err": err.Error()}, err)
}
}
if backup.Type == constant.OneDrive {
if err := StartRefreshOneDriveToken(&backup); err != nil {
return err
}
}
if err := backupRepo.Create(&backup); err != nil {
return err
}
return nil
}
func (u *BackupService) GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error) {
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backupDto.Vars), &varMap); err != nil {
return nil, err
}
switch backupDto.Type {
case constant.Sftp, constant.WebDAV:
varMap["username"] = backupDto.AccessKey
varMap["password"] = backupDto.Credential
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
varMap["accessKey"] = backupDto.AccessKey
varMap["secretKey"] = backupDto.Credential
}
client, err := cloud_storage.NewCloudStorageClient(backupDto.Type, varMap)
if err != nil {
return nil, err
}
return client.ListBuckets()
}
func (u *BackupService) Delete(id uint) error {
backup, _ := backupRepo.Get(commonRepo.WithByID(id))
if backup.ID == 0 {
return constant.ErrRecordNotFound
}
if backup.Type == constant.Local {
return buserr.New(constant.ErrBackupLocalDelete)
}
if backup.InUsed {
return buserr.New(constant.ErrBackupInUsed)
}
if backup.Type == constant.OneDrive {
global.Cron.Remove(cron.EntryID(backup.EntryID))
}
return backupRepo.Delete(commonRepo.WithByID(id))
}
func (u *BackupService) Update(req dto.BackupOperate) error {
backup, err := backupRepo.Get(commonRepo.WithByID(req.ID))
if err != nil {
return constant.ErrRecordNotFound
}
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(req.Vars), &varMap); err != nil {
return err
}
oldVars := backup.Vars
oldDir, err := loadLocalDir()
if err != nil {
return err
}
upMap := make(map[string]interface{})
upMap["bucket"] = req.Bucket
upMap["access_key"] = req.AccessKey
upMap["credential"] = req.Credential
upMap["backup_path"] = req.BackupPath
upMap["vars"] = req.Vars
backup.Bucket = req.Bucket
backup.Vars = req.Vars
backup.Credential = req.Credential
backup.AccessKey = req.AccessKey
backup.BackupPath = req.BackupPath
if req.Type == constant.OneDrive {
global.Cron.Remove(cron.EntryID(backup.EntryID))
if err := u.loadAccessToken(&backup); err != nil {
return err
}
upMap["credential"] = backup.Credential
upMap["vars"] = backup.Vars
if err := StartRefreshOneDriveToken(&backup); err != nil {
return err
}
upMap["entry_id"] = backup.EntryID
}
if backup.Type != "LOCAL" {
isOk, err := u.checkBackupConn(&backup)
if err != nil || !isOk {
return buserr.WithMap("ErrBackupCheck", map[string]interface{}{"err": err.Error()}, err)
}
}
if err := backupRepo.Update(req.ID, upMap); err != nil {
return err
}
if backup.Type == "LOCAL" {
if dir, ok := varMap["dir"]; ok {
if dirStr, isStr := dir.(string); isStr {
if strings.HasSuffix(dirStr, "/") && dirStr != "/" {
dirStr = dirStr[:strings.LastIndex(dirStr, "/")]
}
if err := copyDir(oldDir, dirStr); err != nil {
_ = backupRepo.Update(req.ID, map[string]interface{}{"vars": oldVars})
return err
}
}
}
}
return nil
}
func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error) {
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return nil, err
}
varMap["bucket"] = backup.Bucket
switch backup.Type {
case constant.Sftp, constant.WebDAV:
varMap["username"] = backup.AccessKey
varMap["password"] = backup.Credential
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
varMap["accessKey"] = backup.AccessKey
varMap["secretKey"] = backup.Credential
}
backClient, err := cloud_storage.NewCloudStorageClient(backup.Type, varMap)
if err != nil {
return nil, err
}
return backClient, nil
}
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)
}
refreshToken, err := client.RefreshToken("authorization_code", "refreshToken", varMap)
if err != nil {
return err
}
delete(varMap, "code")
varMap["refresh_status"] = constant.StatusSuccess
varMap["refresh_time"] = time.Now().Format(constant.DateTimeLayout)
varMap["refresh_token"] = refreshToken
itemVars, err := json.Marshal(varMap)
if err != nil {
return fmt.Errorf("json marshal var map failed, err: %v", err)
}
backup.Vars = string(itemVars)
return nil
}
func loadLocalDir() (string, error) {
backup, err := backupRepo.Get(commonRepo.WithByType("LOCAL"))
if err != nil {
return "", err
}
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return "", err
}
if _, ok := varMap["dir"]; !ok {
return "", errors.New("load local backup dir failed")
}
baseDir, ok := varMap["dir"].(string)
if ok {
if _, err := os.Stat(baseDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(baseDir, os.ModePerm); err != nil {
return "", fmt.Errorf("mkdir %s failed, err: %v", baseDir, err)
}
}
return baseDir, nil
}
return "", fmt.Errorf("error type dir: %T", varMap["dir"])
}
func copyDir(src, dst string) error {
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
if err = os.MkdirAll(dst, srcInfo.Mode()); err != nil {
return err
}
files, err := os.ReadDir(src)
if err != nil {
return err
}
for _, file := range files {
srcPath := fmt.Sprintf("%s/%s", src, file.Name())
dstPath := fmt.Sprintf("%s/%s", dst, file.Name())
if file.IsDir() {
if err = copyDir(srcPath, dstPath); err != nil {
global.LOG.Errorf("copy dir %s to %s failed, err: %v", srcPath, dstPath, err)
}
} else {
if err := fileUtils.CopyFile(srcPath, dst); err != nil {
global.LOG.Errorf("copy file %s to %s failed, err: %v", srcPath, dstPath, err)
}
}
}
return nil
}
func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, error) {
client, err := u.NewClient(backup)
if err != nil {
return false, err
}
fileItem := path.Join(global.CONF.System.BaseDir, "1panel/tmp/test/1panel")
if _, err := os.Stat(path.Dir(fileItem)); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(fileItem), os.ModePerm); err != nil {
return false, err
}
}
file, err := os.OpenFile(fileItem, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
return false, err
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.WriteString("1Panel 备份账号测试文件。\n")
_, _ = write.WriteString("1Panel 備份賬號測試文件。\n")
_, _ = write.WriteString("1Panel Backs up account test files.\n")
_, _ = write.WriteString("1Panelアカウントのテストファイルをバックアップします。\n")
write.Flush()
targetPath := strings.TrimPrefix(path.Join(backup.BackupPath, "test/1panel"), "/")
return client.Upload(fileItem, targetPath)
}
func StartRefreshOneDriveToken(backup *model.BackupAccount) error {
service := NewIBackupService()
oneDriveCronID, err := global.Cron.AddJob("0 3 */31 * *", service)
if err != nil {
global.LOG.Errorf("can not add OneDrive corn job: %s", err.Error())
return err
}
backup.EntryID = uint(oneDriveCronID)
return nil
}
func (u *BackupService) Run() {
var backupItem model.BackupAccount
_ = global.DB.Where("`type` = ?", "OneDrive").First(&backupItem)
if backupItem.ID == 0 {
return
}
global.LOG.Info("start to refresh token of OneDrive ...")
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backupItem.Vars), &varMap); err != nil {
global.LOG.Errorf("Failed to refresh OneDrive token, please retry, err: %v", err)
return
}
refreshToken, err := client.RefreshToken("refresh_token", "refreshToken", varMap)
varMap["refresh_status"] = constant.StatusSuccess
varMap["refresh_time"] = time.Now().Format(constant.DateTimeLayout)
if err != nil {
varMap["refresh_status"] = constant.StatusFailed
varMap["refresh_msg"] = err.Error()
global.LOG.Errorf("Failed to refresh OneDrive token, please retry, err: %v", err)
return
}
varMap["refresh_token"] = refreshToken
varsItem, _ := json.Marshal(varMap)
_ = global.DB.Model(&model.BackupAccount{}).
Where("id = ?", backupItem.ID).
Updates(map[string]interface{}{
"vars": varsItem,
}).Error
global.LOG.Info("Successfully refreshed OneDrive token.")
}

View File

@ -5,5 +5,6 @@ import "github.com/1Panel-dev/1Panel/core/app/repo"
var (
commonRepo = repo.NewICommonRepo()
settingRepo = repo.NewISettingRepo()
backupRepo = repo.NewIBackupRepo()
logRepo = repo.NewILogRepo()
)

View File

@ -83,8 +83,8 @@ func (u *UpgradeService) LoadNotes(req dto.Upgrade) (string, error) {
func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
global.LOG.Info("start to upgrade now...")
timeStr := time.Now().Format(constant.DateTimeSlimLayout)
rootDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("upgrade/upgrade_%s/downloads", timeStr))
originalDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("upgrade/upgrade_%s/original", timeStr))
rootDir := path.Join(global.CONF.System.BaseDir, fmt.Sprintf("1panel/tmp/upgrade/upgrade_%s/downloads", timeStr))
originalDir := path.Join(global.CONF.System.BaseDir, fmt.Sprintf("1panel/tmp/upgrade/upgrade_%s/original", timeStr))
if err := os.MkdirAll(rootDir, os.ModePerm); err != nil {
return err
}
@ -181,13 +181,14 @@ func (u *UpgradeService) handleRollback(originalDir string, errStep int) {
_ = settingRepo.Update("SystemStatus", "Free")
checkPointOfWal()
dbPath := path.Join(global.CONF.System.BaseDir, "1panel/db")
if _, err := os.Stat(path.Join(originalDir, "1Panel.db")); err == nil {
if err := files.CopyFile(path.Join(originalDir, "1Panel.db"), global.CONF.System.DbPath); err != nil {
if err := files.CopyFile(path.Join(originalDir, "1Panel.db"), dbPath); err != nil {
global.LOG.Errorf("rollback 1panel db failed, err: %v", err)
}
}
if _, err := os.Stat(path.Join(originalDir, "db.tar.gz")); err == nil {
if err := files.HandleUnTar(path.Join(originalDir, "db.tar.gz"), global.CONF.System.DbPath, ""); err != nil {
if err := files.HandleUnTar(path.Join(originalDir, "db.tar.gz"), dbPath, ""); err != nil {
global.LOG.Errorf("rollback 1panel db failed, err: %v", err)
}
}

View File

@ -6,12 +6,6 @@ type System struct {
BindAddress string `mapstructure:"bindAddress"`
SSL string `mapstructure:"ssl"`
DbCoreFile string `mapstructure:"db_core_file"`
DbPath string `mapstructure:"db_path"`
LogPath string `mapstructure:"log_path"`
DataDir string `mapstructure:"data_dir"`
TmpDir string `mapstructure:"tmp_dir"`
Cache string `mapstructure:"cache"`
Backup string `mapstructure:"backup"`
EncryptKey string `mapstructure:"encrypt_key"`
BaseDir string `mapstructure:"base_dir"`
Mode string `mapstructure:"mode"`

View File

@ -17,4 +17,16 @@ const (
StatusEnable = "Enable"
StatusDisable = "Disable"
// backup
S3 = "S3"
OSS = "OSS"
Sftp = "SFTP"
OneDrive = "OneDrive"
MinIo = "MINIO"
Cos = "COS"
Kodo = "KODO"
WebDAV = "WebDAV"
Local = "LOCAL"
OneDriveRedirectURI = "http://localhost/login/authorized"
)

View File

@ -1,9 +0,0 @@
package constant
import (
"github.com/1Panel-dev/1Panel/core/global"
)
var (
DataDir = global.CONF.System.DataDir
)

View File

@ -30,6 +30,7 @@ var (
ErrTransform = errors.New("ErrTransform")
ErrInitialPassword = errors.New("ErrInitialPassword")
ErrInvalidParams = errors.New("ErrInvalidParams")
ErrNotSupportType = errors.New("ErrNotSupportType")
ErrTokenParse = errors.New("ErrTokenParse")
ErrStructTransform = errors.New("ErrStructTransform")
@ -49,3 +50,9 @@ var (
ErrProxy = "ErrProxy"
ErrLocalDelete = "ErrLocalDelete"
)
// backup
var (
ErrBackupInUsed = "ErrBackupInUsed"
ErrBackupLocalDelete = "ErrBackupLocalDelete"
)

View File

@ -7,6 +7,7 @@ import (
"github.com/dgraph-io/badger/v4"
"github.com/go-playground/validator/v10"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/robfig/cron/v3"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
"gorm.io/gorm"
@ -23,4 +24,6 @@ var (
Viper *viper.Viper
I18n *i18n.Localizer
Cron *cron.Cron
)

View File

@ -23,4 +23,9 @@ ErrCreateHttpClient: "Failed to create HTTP request {{.err}}"
ErrHttpReqTimeOut: "Request timed out {{.err}}"
ErrHttpReqFailed: "Request failed {{.err}}"
ErrHttpReqNotFound: "The file does not exist"
ErrNoSuchHost: "Network connection failed"
ErrNoSuchHost: "Network connection failed"
#backup
ErrBackupInUsed: "The backup account is currently in use in a scheduled task and cannot be deleted."
ErrBackupCheck: "Backup account test connection failed {{.err}}"
ErrBackupLocalDelete: "Deleting local server backup accounts is not currently supported."

View File

@ -23,4 +23,9 @@ ErrCreateHttpClient: "創建HTTP請求失敗 {{.err}}"
ErrHttpReqTimeOut: "請求超時 {{.err}}"
ErrHttpReqFailed: "請求失敗 {{.err}}"
ErrHttpReqNotFound: "文件不存在"
ErrNoSuchHost: "網路連接失敗"
ErrNoSuchHost: "網路連接失敗"
#backup
ErrBackupInUsed: "該備份帳號已在計劃任務中使用,無法刪除"
ErrBackupCheck: "備份帳號測試連接失敗 {{.err}}"
ErrBackupLocalDelete: "暫不支持刪除本地伺服器備份帳號"

View File

@ -23,4 +23,9 @@ ErrCreateHttpClient: "创建HTTP请求失败 {{.err}}"
ErrHttpReqTimeOut: "请求超时 {{.err}}"
ErrHttpReqFailed: "请求失败 {{.err}}"
ErrHttpReqNotFound: "文件不存在"
ErrNoSuchHost: "网络连接失败"
ErrNoSuchHost: "网络连接失败"
#backup
ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除"
ErrBackupCheck: "备份账号测试连接失败 {{ .err}}"
ErrBackupLocalDelete: "暂不支持删除本地服务器备份账号"

View File

@ -1,6 +1,7 @@
package cache
import (
"path"
"time"
"github.com/1Panel-dev/1Panel/core/global"
@ -9,7 +10,7 @@ import (
)
func Init() {
c := global.CONF.System.Cache
c := path.Join(global.CONF.System.BaseDir, "1panel/cache")
options := badger.Options{
Dir: c,

23
core/init/cron/cron.go Normal file
View File

@ -0,0 +1,23 @@
package cron
import (
"time"
"github.com/1Panel-dev/1Panel/core/app/model"
"github.com/1Panel-dev/1Panel/core/app/service"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/utils/common"
"github.com/robfig/cron/v3"
)
func Init() {
nyc, _ := time.LoadLocation(common.LoadTimeZone())
global.Cron = cron.New(cron.WithLocation(nyc), cron.WithChain(cron.Recover(cron.DefaultLogger)), cron.WithChain(cron.DelayIfStillRunning(cron.DefaultLogger)))
var accounts []model.BackupAccount
_ = global.DB.Where("type = ?", "OneDrive").Find(&accounts).Error
for i := 0; i < len(accounts); i++ {
_ = service.StartRefreshOneDriveToken(&accounts[i])
}
global.Cron.Start()
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
"log"
"os"
"path"
"time"
"github.com/1Panel-dev/1Panel/core/global"
@ -13,12 +14,13 @@ import (
)
func Init() {
if _, err := os.Stat(global.CONF.System.DbPath); err != nil {
if err := os.MkdirAll(global.CONF.System.DbPath, os.ModePerm); err != nil {
dbPath := path.Join(global.CONF.System.BaseDir, "1panel/db")
if _, err := os.Stat(dbPath); err != nil {
if err := os.MkdirAll(dbPath, os.ModePerm); err != nil {
panic(fmt.Errorf("init db dir failed, err: %v", err))
}
}
fullPath := global.CONF.System.DbPath + "/" + global.CONF.System.DbCoreFile
fullPath := path.Join(dbPath, global.CONF.System.DbCoreFile)
if _, err := os.Stat(fullPath); err != nil {
f, err := os.Create(fullPath)
if err != nil {

View File

@ -4,6 +4,7 @@ import (
"fmt"
"io"
"os"
"path"
"strings"
"time"
@ -29,7 +30,7 @@ func Init() {
func setOutput(logger *logrus.Logger, config configs.LogConfig) {
writer, err := log.NewWriterFromConfig(&log.Config{
LogPath: global.CONF.System.LogPath,
LogPath: path.Join(global.CONF.System.BaseDir, "1panel/log"),
FileName: config.LogName,
TimeTagFormat: FileTImeFormat,
MaxRemain: config.MaxBackup,

View File

@ -11,6 +11,7 @@ func Init() {
m := gormigrate.New(global.DB, gormigrate.DefaultOptions, []*gormigrate.Migration{
migrations.AddTable,
migrations.InitSetting,
migrations.InitOneDrive,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)

View File

@ -1,6 +1,8 @@
package migrations
import (
"fmt"
"path"
"time"
"github.com/1Panel-dev/1Panel/core/app/model"
@ -13,12 +15,13 @@ import (
)
var AddTable = &gormigrate.Migration{
ID: "20240722-add-table",
ID: "20240808-add-table",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&model.OperationLog{},
&model.LoginLog{},
&model.Setting{},
&model.BackupAccount{},
)
},
}
@ -137,3 +140,23 @@ var InitSetting = &gormigrate.Migration{
return nil
},
}
var InitOneDrive = &gormigrate.Migration{
ID: "20240808-init-one-drive",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Setting{Key: "OneDriveID", Value: "MDEwOTM1YTktMWFhOS00ODU0LWExZGMtNmU0NWZlNjI4YzZi"}).Error; err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "OneDriveSc", Value: "akpuOFF+YkNXOU1OLWRzS1ZSRDdOcG1LT2ZRM0RLNmdvS1RkVWNGRA=="}).Error; err != nil {
return err
}
if err := tx.Create(&model.BackupAccount{
Name: "localhost",
Type: "LOCAL",
Vars: fmt.Sprintf("{\"dir\":\"%s\"}", path.Join(global.CONF.System.BaseDir, "1panel/backup")),
}).Error; err != nil {
return err
}
return nil
},
}

View File

@ -22,7 +22,7 @@ var (
func setWebStatic(rootRouter *gin.RouterGroup) {
rootRouter.StaticFS("/public", http.FS(web.Favicon))
rootRouter.Static("/api/v1/images", "./uploads")
rootRouter.Static("/api/v2/images", "./uploads")
rootRouter.Use(func(c *gin.Context) {
c.Next()
})

View File

@ -86,12 +86,6 @@ func Init() {
global.CONF = serverConfig
global.CONF.System.BaseDir = baseDir
global.CONF.System.IsDemo = v.GetBool("system.is_demo")
global.CONF.System.DataDir = path.Join(global.CONF.System.BaseDir, "1panel")
global.CONF.System.Cache = path.Join(global.CONF.System.DataDir, "cache")
global.CONF.System.Backup = path.Join(global.CONF.System.DataDir, "backup")
global.CONF.System.DbPath = path.Join(global.CONF.System.DataDir, "db")
global.CONF.System.LogPath = path.Join(global.CONF.System.DataDir, "log")
global.CONF.System.TmpDir = path.Join(global.CONF.System.DataDir, "tmp")
global.CONF.System.Port = port
global.CONF.System.Version = version
global.CONF.System.Username = username

View File

@ -3,6 +3,7 @@ package router
func commonGroups() []CommonRouter {
return []CommonRouter{
&BaseRouter{},
&BackupRouter{},
&LogRouter{},
&SettingRouter{},
}

22
core/router/ro_backup.go Normal file
View File

@ -0,0 +1,22 @@
package router
import (
v2 "github.com/1Panel-dev/1Panel/core/app/api/v2"
"github.com/gin-gonic/gin"
)
type BackupRouter struct{}
func (s *BackupRouter) InitRouter(Router *gin.RouterGroup) {
backupRouter := Router.Group("backup")
baseApi := v2.ApiGroupApp.BaseApi
{
backupRouter.GET("/onedrive", baseApi.LoadOneDriveInfo)
backupRouter.POST("/search", baseApi.SearchBackup)
backupRouter.POST("/refresh/onedrive", baseApi.RefreshOneDriveToken)
backupRouter.POST("/buckets", baseApi.ListBuckets)
backupRouter.POST("", baseApi.CreateBackup)
backupRouter.POST("/del", baseApi.DeleteBackup)
backupRouter.POST("/update", baseApi.UpdateBackup)
}
}

View File

@ -12,6 +12,7 @@ import (
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/i18n"
"github.com/1Panel-dev/1Panel/core/init/cache"
"github.com/1Panel-dev/1Panel/core/init/cron"
"github.com/1Panel-dev/1Panel/core/init/db"
"github.com/1Panel-dev/1Panel/core/init/hook"
"github.com/1Panel-dev/1Panel/core/init/log"
@ -34,6 +35,7 @@ func Start() {
validator.Init()
gob.Register(psession.SessionUser{})
cache.Init()
cron.Init()
session.Init()
gin.SetMode("debug")
InitOthers()

View File

@ -0,0 +1,96 @@
package client
import (
"context"
"fmt"
"net/http"
"net/url"
"os"
cosSDK "github.com/tencentyun/cos-go-sdk-v5"
)
type cosClient struct {
scType string
client *cosSDK.Client
clientWithBucket *cosSDK.Client
}
func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
region := loadParamFromVars("region", vars)
accessKey := loadParamFromVars("accessKey", vars)
secretKey := loadParamFromVars("secretKey", vars)
bucket := loadParamFromVars("bucket", vars)
scType := loadParamFromVars("scType", vars)
if len(scType) == 0 {
scType = "Standard"
}
u, _ := url.Parse(fmt.Sprintf("https://cos.%s.myqcloud.com", region))
b := &cosSDK.BaseURL{BucketURL: u}
client := cosSDK.NewClient(b, &http.Client{
Transport: &cosSDK.AuthorizationTransport{
SecretID: accessKey,
SecretKey: secretKey,
},
})
if len(bucket) != 0 {
u2, _ := url.Parse(fmt.Sprintf("https://%s.cos.%s.myqcloud.com", bucket, region))
b2 := &cosSDK.BaseURL{BucketURL: u2}
clientWithBucket := cosSDK.NewClient(b2, &http.Client{
Transport: &cosSDK.AuthorizationTransport{
SecretID: accessKey,
SecretKey: secretKey,
},
})
return &cosClient{client: client, clientWithBucket: clientWithBucket, scType: scType}, nil
}
return &cosClient{client: client, clientWithBucket: nil, scType: scType}, nil
}
func (c cosClient) ListBuckets() ([]interface{}, error) {
buckets, _, err := c.client.Service.Get(context.Background())
if err != nil {
return nil, err
}
var datas []interface{}
for _, bucket := range buckets.Buckets {
datas = append(datas, bucket.Name)
}
return datas, nil
}
func (c cosClient) Upload(src, target string) (bool, error) {
fileInfo, err := os.Stat(src)
if err != nil {
return false, err
}
if fileInfo.Size() > 5368709120 {
opt := &cosSDK.MultiUploadOptions{
OptIni: &cosSDK.InitiateMultipartUploadOptions{
ACLHeaderOptions: nil,
ObjectPutHeaderOptions: &cosSDK.ObjectPutHeaderOptions{
XCosStorageClass: c.scType,
},
},
PartSize: 200,
}
if _, _, err := c.clientWithBucket.Object.MultiUpload(
context.Background(), target, src, opt,
); err != nil {
return false, err
}
return true, nil
}
if _, err := c.clientWithBucket.Object.PutFromFile(context.Background(), target, src, &cosSDK.ObjectPutOptions{
ACLHeaderOptions: nil,
ObjectPutHeaderOptions: &cosSDK.ObjectPutHeaderOptions{
XCosStorageClass: c.scType,
},
}); err != nil {
return false, err
}
return true, nil
}

View File

@ -0,0 +1,18 @@
package client
import (
"fmt"
"github.com/1Panel-dev/1Panel/core/global"
)
func loadParamFromVars(key string, vars map[string]interface{}) string {
if _, ok := vars[key]; !ok {
if key != "bucket" && key != "port" {
global.LOG.Errorf("load param %s from vars failed, err: not exist!", key)
}
return ""
}
return fmt.Sprintf("%v", vars[key])
}

View File

@ -0,0 +1,66 @@
package client
import (
"context"
"strconv"
"github.com/qiniu/go-sdk/v7/auth"
"github.com/qiniu/go-sdk/v7/storage"
)
type kodoClient struct {
bucket string
domain string
timeout string
auth *auth.Credentials
client *storage.BucketManager
}
func NewKodoClient(vars map[string]interface{}) (*kodoClient, error) {
accessKey := loadParamFromVars("accessKey", vars)
secretKey := loadParamFromVars("secretKey", vars)
bucket := loadParamFromVars("bucket", vars)
domain := loadParamFromVars("domain", vars)
timeout := loadParamFromVars("timeout", vars)
if timeout == "" {
timeout = "1"
}
conn := auth.New(accessKey, secretKey)
cfg := storage.Config{
UseHTTPS: false,
}
bucketManager := storage.NewBucketManager(conn, &cfg)
return &kodoClient{client: bucketManager, auth: conn, bucket: bucket, domain: domain, timeout: timeout}, nil
}
func (k kodoClient) ListBuckets() ([]interface{}, error) {
buckets, err := k.client.Buckets(true)
if err != nil {
return nil, err
}
var datas []interface{}
for _, bucket := range buckets {
datas = append(datas, bucket)
}
return datas, nil
}
func (k kodoClient) Upload(src, target string) (bool, error) {
int64Value, _ := strconv.ParseInt(k.timeout, 10, 64)
unixTimestamp := int64Value * 3600
putPolicy := storage.PutPolicy{
Scope: k.bucket,
Expires: uint64(unixTimestamp),
}
upToken := putPolicy.UploadToken(k.auth)
cfg := storage.Config{UseHTTPS: true, UseCdnDomains: false}
resumeUploader := storage.NewResumeUploaderV2(&cfg)
ret := storage.PutRet{}
putExtra := storage.RputV2Extra{}
if err := resumeUploader.PutFile(context.Background(), &ret, upToken, target, src, &putExtra); err != nil {
return false, err
}
return true, nil
}

View File

@ -0,0 +1,40 @@
package client
import (
"fmt"
"os"
"path"
"github.com/1Panel-dev/1Panel/core/utils/files"
)
type localClient struct {
dir string
}
func NewLocalClient(vars map[string]interface{}) (*localClient, error) {
dir := loadParamFromVars("dir", vars)
return &localClient{dir: dir}, nil
}
func (c localClient) ListBuckets() ([]interface{}, error) {
return nil, nil
}
func (c localClient) Upload(src, target string) (bool, error) {
targetFilePath := path.Join(c.dir, target)
if _, err := os.Stat(path.Dir(targetFilePath)); err != nil {
if os.IsNotExist(err) {
if err = os.MkdirAll(path.Dir(targetFilePath), os.ModePerm); err != nil {
return false, err
}
} else {
return false, err
}
}
if err := files.CopyFile(src, targetFilePath); err != nil {
return false, fmt.Errorf("cp file failed, err: %v", err)
}
return true, nil
}

View File

@ -0,0 +1,78 @@
package client
import (
"context"
"crypto/tls"
"net/http"
"os"
"strings"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
type minIoClient struct {
bucket string
client *minio.Client
}
func NewMinIoClient(vars map[string]interface{}) (*minIoClient, error) {
endpoint := loadParamFromVars("endpoint", vars)
accessKeyID := loadParamFromVars("accessKey", vars)
secretAccessKey := loadParamFromVars("secretKey", vars)
bucket := loadParamFromVars("bucket", vars)
ssl := strings.Split(endpoint, ":")[0]
if len(ssl) == 0 || (ssl != "https" && ssl != "http") {
return nil, constant.ErrInvalidParams
}
secure := false
tlsConfig := &tls.Config{}
if ssl == "https" {
secure = true
tlsConfig.InsecureSkipVerify = true
}
var transport http.RoundTripper = &http.Transport{
TLSClientConfig: tlsConfig,
}
client, err := minio.New(strings.ReplaceAll(endpoint, ssl+"://", ""), &minio.Options{
Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
Secure: secure,
Transport: transport,
})
if err != nil {
return nil, err
}
return &minIoClient{bucket: bucket, client: client}, nil
}
func (m minIoClient) ListBuckets() ([]interface{}, error) {
buckets, err := m.client.ListBuckets(context.Background())
if err != nil {
return nil, err
}
var result []interface{}
for _, bucket := range buckets {
result = append(result, bucket.Name)
}
return result, err
}
func (m minIoClient) Upload(src, target string) (bool, error) {
file, err := os.Open(src)
if err != nil {
return false, err
}
defer file.Close()
fileStat, err := file.Stat()
if err != nil {
return false, err
}
_, err = m.client.PutObject(context.Background(), m.bucket, target, file, fileStat.Size(), minio.PutObjectOptions{ContentType: "application/octet-stream"})
if err != nil {
return false, err
}
return true, nil
}

View File

@ -0,0 +1,350 @@
package client
import (
"bufio"
"bytes"
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path"
"strconv"
"strings"
"time"
odsdk "github.com/goh-chunlin/go-onedrive/onedrive"
"golang.org/x/oauth2"
)
type oneDriveClient struct {
client odsdk.Client
}
func NewOneDriveClient(vars map[string]interface{}) (*oneDriveClient, error) {
token, err := RefreshToken("refresh_token", "accessToken", vars)
if err != nil {
return nil, err
}
isCN := loadParamFromVars("isCN", vars)
ctx := context.Background()
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)
tc := oauth2.NewClient(ctx, ts)
client := odsdk.NewClient(tc)
if isCN == "true" {
client.BaseURL, _ = url.Parse("https://microsoftgraph.chinacloudapi.cn/v1.0/")
}
return &oneDriveClient{client: *client}, nil
}
func (o oneDriveClient) ListBuckets() ([]interface{}, error) {
return nil, nil
}
func (o oneDriveClient) Upload(src, target string) (bool, error) {
target = "/" + strings.TrimPrefix(target, "/")
if _, err := o.loadIDByPath(path.Dir(target)); err != nil {
if !strings.Contains(err.Error(), "itemNotFound") {
return false, err
}
if err := o.createFolder(path.Dir(target)); err != nil {
return false, fmt.Errorf("create dir before upload failed, err: %v", err)
}
}
ctx := context.Background()
folderID, err := o.loadIDByPath(path.Dir(target))
if err != nil {
return false, err
}
fileInfo, err := os.Stat(src)
if err != nil {
return false, err
}
if fileInfo.IsDir() {
return false, errors.New("only file is allowed to be uploaded here")
}
var isOk bool
if fileInfo.Size() < 4*1024*1024 {
isOk, err = o.upSmall(src, folderID, fileInfo.Size())
} else {
isOk, err = o.upBig(ctx, src, folderID, fileInfo.Size())
}
return isOk, err
}
func (o oneDriveClient) Download(src, target string) (bool, error) {
src = "/" + strings.TrimPrefix(src, "/")
req, err := o.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 := o.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 (o *oneDriveClient) loadIDByPath(path string) (string, error) {
pathItem := "root:" + path
if path == "/" {
pathItem = "root"
}
req, err := o.client.NewRequest("GET", fmt.Sprintf("me/drive/%s", pathItem), nil)
if err != nil {
return "", fmt.Errorf("new request for file id failed, err: %v", err)
}
var driveItem *odsdk.DriveItem
if err := o.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(grantType string, tokenType string, varMap map[string]interface{}) (string, error) {
data := url.Values{}
isCN := loadParamFromVars("isCN", varMap)
data.Set("client_id", loadParamFromVars("client_id", varMap))
data.Set("client_secret", loadParamFromVars("client_secret", varMap))
if grantType == "refresh_token" {
data.Set("grant_type", "refresh_token")
data.Set("refresh_token", loadParamFromVars("refresh_token", varMap))
} else {
data.Set("grant_type", "authorization_code")
data.Set("code", loadParamFromVars("code", varMap))
}
data.Set("redirect_uri", loadParamFromVars("redirect_uri", varMap))
client := &http.Client{}
url := "https://login.microsoftonline.com/common/oauth2/v2.0/token"
if isCN == "true" {
url = "https://login.chinacloudapi.cn/common/oauth2/v2.0/token"
}
req, err := http.NewRequest("POST", url, 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)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("read data from response body failed, err: %v", err)
}
tokenMap := map[string]interface{}{}
if err := json.Unmarshal(respBody, &tokenMap); err != nil {
return "", fmt.Errorf("unmarshal data from response body failed, err: %v", err)
}
if tokenType == "accessToken" {
accessToken, ok := tokenMap["access_token"].(string)
if !ok {
return "", errors.New("no such access token in response")
}
tokenMap = nil
return accessToken, nil
}
refreshToken, ok := tokenMap["refresh_token"].(string)
if !ok {
return "", errors.New("no such access token in response")
}
tokenMap = nil
return refreshToken, nil
}
func (o *oneDriveClient) createFolder(parent string) error {
if _, err := o.loadIDByPath(path.Dir(parent)); err != nil {
if !strings.Contains(err.Error(), "itemNotFound") {
return err
}
_ = o.createFolder(path.Dir(parent))
}
item2, err := o.loadIDByPath(path.Dir(parent))
if err != nil {
return err
}
if _, err := o.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 (o *oneDriveClient) NewSessionFileUploadRequest(absoluteUrl string, grandOffset, grandTotalSize int64, byteReader *bytes.Reader) (*http.Request, error) {
apiUrl, err := o.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
}
func (o *oneDriveClient) upSmall(srcPath, folderID string, fileSize int64) (bool, error) {
file, err := os.Open(srcPath)
if err != nil {
return false, err
}
defer file.Close()
buffer := make([]byte, fileSize)
_, _ = file.Read(buffer)
fileReader := bytes.NewReader(buffer)
apiURL := fmt.Sprintf("me/drive/items/%s:/%s:/content?@microsoft.graph.conflictBehavior=rename", url.PathEscape(folderID), path.Base(srcPath))
mimeType := getMimeType(srcPath)
req, err := o.client.NewFileUploadRequest(apiURL, mimeType, fileReader)
if err != nil {
return false, err
}
var response *DriveItem
if err := o.client.Do(context.Background(), req, false, &response); err != nil {
return false, fmt.Errorf("do request for list failed, err: %v", err)
}
return true, nil
}
func (o *oneDriveClient) upBig(ctx context.Context, srcPath, folderID string, fileSize int64) (bool, error) {
file, err := os.Open(srcPath)
if err != nil {
return false, err
}
defer file.Close()
apiURL := fmt.Sprintf("me/drive/items/%s:/%s:/createUploadSession", url.PathEscape(folderID), path.Base(srcPath))
sessionCreationRequestInside := NewUploadSessionCreationRequest{
ConflictBehavior: "rename",
}
sessionCreationRequest := struct {
Item NewUploadSessionCreationRequest `json:"item"`
DeferCommit bool `json:"deferCommit"`
}{sessionCreationRequestInside, false}
sessionCreationReq, err := o.client.NewRequest("POST", apiURL, sessionCreationRequest)
if err != nil {
return false, err
}
var sessionCreationResp *NewUploadSessionCreationResponse
err = o.client.Do(ctx, sessionCreationReq, false, &sessionCreationResp)
if err != nil {
return false, fmt.Errorf("session creation failed %w", err)
}
fileSessionUploadUrl := sessionCreationResp.UploadURL
sizePerSplit := int64(5 * 1024 * 1024)
buffer := make([]byte, 5*1024*1024)
splitCount := fileSize / sizePerSplit
if fileSize%sizePerSplit != 0 {
splitCount += 1
}
bfReader := bufio.NewReader(file)
httpClient := http.Client{
Timeout: time.Minute * 10,
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
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 := o.NewSessionFileUploadRequest(fileSessionUploadUrl, splitNow*sizePerSplit, fileSize, bytes.NewReader(buffer))
if err != nil {
return false, err
}
res, err := httpClient.Do(sessionFileUploadReq)
if err != nil {
return false, err
}
res.Body.Close()
if res.StatusCode != 201 && res.StatusCode != 202 && res.StatusCode != 200 {
data, _ := io.ReadAll(res.Body)
return false, errors.New(string(data))
}
}
return true, nil
}
func getMimeType(path string) string {
file, err := os.Open(path)
if err != nil {
return ""
}
defer file.Close()
buffer := make([]byte, 512)
_, err = file.Read(buffer)
if err != nil {
return ""
}
mimeType := http.DetectContentType(buffer)
return mimeType
}

View File

@ -0,0 +1,118 @@
package client
import (
"fmt"
osssdk "github.com/aliyun/aliyun-oss-go-sdk/oss"
)
type ossClient struct {
scType string
bucketStr string
client osssdk.Client
}
func NewOssClient(vars map[string]interface{}) (*ossClient, error) {
endpoint := loadParamFromVars("endpoint", vars)
accessKey := loadParamFromVars("accessKey", vars)
secretKey := loadParamFromVars("secretKey", vars)
bucketStr := loadParamFromVars("bucket", vars)
scType := loadParamFromVars("scType", vars)
if len(scType) == 0 {
scType = "Standard"
}
client, err := osssdk.New(endpoint, accessKey, secretKey)
if err != nil {
return nil, err
}
return &ossClient{scType: scType, bucketStr: bucketStr, client: *client}, nil
}
func (o ossClient) ListBuckets() ([]interface{}, error) {
response, err := o.client.ListBuckets()
if err != nil {
return nil, err
}
var result []interface{}
for _, bucket := range response.Buckets {
result = append(result, bucket.Name)
}
return result, err
}
func (o ossClient) Exist(path string) (bool, error) {
bucket, err := o.client.Bucket(o.bucketStr)
if err != nil {
return false, err
}
return bucket.IsObjectExist(path)
}
func (o ossClient) Size(path string) (int64, error) {
bucket, err := o.client.Bucket(o.bucketStr)
if err != nil {
return 0, err
}
lor, err := bucket.ListObjectsV2(osssdk.Prefix(path))
if err != nil {
return 0, err
}
if len(lor.Objects) == 0 {
return 0, fmt.Errorf("no such file %s", path)
}
return lor.Objects[0].Size, nil
}
func (o ossClient) Delete(path string) (bool, error) {
bucket, err := o.client.Bucket(o.bucketStr)
if err != nil {
return false, err
}
if err := bucket.DeleteObject(path); err != nil {
return false, err
}
return true, nil
}
func (o ossClient) Upload(src, target string) (bool, error) {
bucket, err := o.client.Bucket(o.bucketStr)
if err != nil {
return false, err
}
if err := bucket.UploadFile(target, src,
200*1024*1024,
osssdk.Routines(5),
osssdk.Checkpoint(true, ""),
osssdk.ObjectStorageClass(osssdk.StorageClassType(o.scType))); err != nil {
return false, err
}
return true, nil
}
func (o ossClient) Download(src, target string) (bool, error) {
bucket, err := o.client.Bucket(o.bucketStr)
if err != nil {
return false, err
}
if err := bucket.DownloadFile(src, target, 200*1024*1024, osssdk.Routines(5), osssdk.Checkpoint(true, "")); err != nil {
return false, err
}
return true, nil
}
func (o *ossClient) ListObjects(prefix string) ([]string, error) {
bucket, err := o.client.Bucket(o.bucketStr)
if err != nil {
return nil, err
}
lor, err := bucket.ListObjectsV2(osssdk.Prefix(prefix))
if err != nil {
return nil, err
}
var result []string
for _, obj := range lor.Objects {
result = append(result, obj.Key)
}
return result, nil
}

View File

@ -0,0 +1,163 @@
package client
import (
"os"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
)
type s3Client struct {
scType string
bucket string
Sess session.Session
}
func NewS3Client(vars map[string]interface{}) (*s3Client, error) {
accessKey := loadParamFromVars("accessKey", vars)
secretKey := loadParamFromVars("secretKey", vars)
endpoint := loadParamFromVars("endpoint", vars)
region := loadParamFromVars("region", vars)
bucket := loadParamFromVars("bucket", vars)
scType := loadParamFromVars("scType", vars)
if len(scType) == 0 {
scType = "Standard"
}
sess, err := session.NewSession(&aws.Config{
Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
Endpoint: aws.String(endpoint),
Region: aws.String(region),
DisableSSL: aws.Bool(true),
S3ForcePathStyle: aws.Bool(false),
})
if err != nil {
return nil, err
}
return &s3Client{scType: scType, bucket: bucket, Sess: *sess}, nil
}
func (s s3Client) ListBuckets() ([]interface{}, error) {
var result []interface{}
svc := s3.New(&s.Sess)
res, err := svc.ListBuckets(nil)
if err != nil {
return nil, err
}
for _, b := range res.Buckets {
result = append(result, b.Name)
}
return result, nil
}
func (s s3Client) Exist(path string) (bool, error) {
svc := s3.New(&s.Sess)
if _, err := svc.HeadObject(&s3.HeadObjectInput{
Bucket: &s.bucket,
Key: &path,
}); err != nil {
if aerr, ok := err.(awserr.RequestFailure); ok {
if aerr.StatusCode() == 404 {
return false, nil
}
} else {
return false, aerr
}
}
return true, nil
}
func (s *s3Client) Size(path string) (int64, error) {
svc := s3.New(&s.Sess)
file, err := svc.GetObject(&s3.GetObjectInput{
Bucket: &s.bucket,
Key: &path,
})
if err != nil {
return 0, err
}
return *file.ContentLength, nil
}
func (s s3Client) Delete(path string) (bool, error) {
svc := s3.New(&s.Sess)
if _, err := svc.DeleteObject(&s3.DeleteObjectInput{Bucket: aws.String(s.bucket), Key: aws.String(path)}); err != nil {
return false, err
}
if err := svc.WaitUntilObjectNotExists(&s3.HeadObjectInput{
Bucket: aws.String(s.bucket),
Key: aws.String(path),
}); err != nil {
return false, err
}
return true, nil
}
func (s s3Client) Upload(src, target string) (bool, error) {
fileInfo, err := os.Stat(src)
if err != nil {
return false, err
}
file, err := os.Open(src)
if err != nil {
return false, err
}
defer file.Close()
uploader := s3manager.NewUploader(&s.Sess)
if fileInfo.Size() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
uploader.PartSize = fileInfo.Size() / (s3manager.MaxUploadParts - 1)
}
if _, err := uploader.Upload(&s3manager.UploadInput{
Bucket: aws.String(s.bucket),
Key: aws.String(target),
Body: file,
StorageClass: &s.scType,
}); err != nil {
return false, err
}
return true, nil
}
func (s s3Client) Download(src, target string) (bool, error) {
if _, err := os.Stat(target); err != nil {
if os.IsNotExist(err) {
os.Remove(target)
} else {
return false, err
}
}
file, err := os.Create(target)
if err != nil {
return false, err
}
defer file.Close()
downloader := s3manager.NewDownloader(&s.Sess)
if _, err = downloader.Download(file, &s3.GetObjectInput{
Bucket: aws.String(s.bucket),
Key: aws.String(src),
}); err != nil {
os.Remove(target)
return false, err
}
return true, nil
}
func (s *s3Client) ListObjects(prefix string) ([]string, error) {
svc := s3.New(&s.Sess)
var result []string
outputs, err := svc.ListObjects(&s3.ListObjectsInput{
Bucket: &s.bucket,
Prefix: &prefix,
})
if err != nil {
return result, err
}
for _, item := range outputs.Contents {
result = append(result, *item.Key)
}
return result, nil
}

View File

@ -0,0 +1,122 @@
package client
import (
"fmt"
"io"
"net"
"os"
"path"
"time"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
type sftpClient struct {
bucket string
connInfo string
config *ssh.ClientConfig
}
func NewSftpClient(vars map[string]interface{}) (*sftpClient, error) {
address := loadParamFromVars("address", vars)
port := loadParamFromVars("port", vars)
if len(port) == 0 {
global.LOG.Errorf("load param port from vars failed, err: not exist!")
}
authMode := loadParamFromVars("authMode", vars)
privateKey := loadParamFromVars("privateKey", vars)
password := loadParamFromVars("password", vars)
bucket := loadParamFromVars("bucket", vars)
var auth []ssh.AuthMethod
if authMode == "key" {
itemPrivateKey, err := ssh.ParsePrivateKey([]byte(privateKey))
if err != nil {
return nil, err
}
auth = []ssh.AuthMethod{ssh.PublicKeys(itemPrivateKey)}
} else {
auth = []ssh.AuthMethod{ssh.Password(password)}
}
username := loadParamFromVars("username", vars)
clientConfig := &ssh.ClientConfig{
User: username,
Auth: auth,
Timeout: 30 * time.Second,
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
if _, err := ssh.Dial("tcp", fmt.Sprintf("%s:%s", address, port), clientConfig); err != nil {
return nil, err
}
return &sftpClient{connInfo: fmt.Sprintf("%s:%s", address, port), config: clientConfig, bucket: bucket}, nil
}
func (s sftpClient) Upload(src, target string) (bool, error) {
sshClient, err := ssh.Dial("tcp", s.connInfo, s.config)
if err != nil {
return false, err
}
defer sshClient.Close()
client, err := sftp.NewClient(sshClient)
if err != nil {
return false, err
}
defer client.Close()
srcFile, err := os.Open(src)
if err != nil {
return false, err
}
defer srcFile.Close()
targetFilePath := path.Join(s.bucket, target)
targetDir, _ := path.Split(targetFilePath)
if _, err = client.Stat(targetDir); err != nil {
if os.IsNotExist(err) {
if err = client.MkdirAll(targetDir); err != nil {
return false, err
}
} else {
return false, err
}
}
dstFile, err := client.Create(path.Join(s.bucket, target))
if err != nil {
return false, err
}
defer dstFile.Close()
if _, err := io.Copy(dstFile, srcFile); err != nil {
return false, err
}
return true, nil
}
func (s sftpClient) ListBuckets() ([]interface{}, error) {
var result []interface{}
return result, nil
}
func (s sftpClient) Delete(filePath string) error {
sshClient, err := ssh.Dial("tcp", s.connInfo, s.config)
if err != nil {
return err
}
client, err := sftp.NewClient(sshClient)
if err != nil {
return err
}
defer client.Close()
defer sshClient.Close()
if err := client.Remove(filePath); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,121 @@
package client
import (
"crypto/tls"
"fmt"
"io"
"net/http"
"os"
"path"
"strings"
"github.com/studio-b12/gowebdav"
)
type webDAVClient struct {
Bucket string
client *gowebdav.Client
}
func NewWebDAVClient(vars map[string]interface{}) (*webDAVClient, error) {
address := loadParamFromVars("address", vars)
port := loadParamFromVars("port", vars)
password := loadParamFromVars("password", vars)
username := loadParamFromVars("username", vars)
bucket := loadParamFromVars("bucket", vars)
url := fmt.Sprintf("%s:%s", address, port)
if len(port) == 0 {
url = address
}
client := gowebdav.NewClient(url, username, password)
tlsConfig := &tls.Config{}
if strings.HasPrefix(address, "https") {
tlsConfig.InsecureSkipVerify = true
}
var transport http.RoundTripper = &http.Transport{
TLSClientConfig: tlsConfig,
}
client.SetTransport(transport)
if err := client.Connect(); err != nil {
return nil, err
}
return &webDAVClient{Bucket: bucket, client: client}, nil
}
func (s webDAVClient) Upload(src, target string) (bool, error) {
targetFilePath := path.Join(s.Bucket, target)
srcFile, err := os.Open(src)
if err != nil {
return false, err
}
defer srcFile.Close()
if err := s.client.WriteStream(targetFilePath, srcFile, 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 := path.Join(s.Bucket, src)
info, err := s.client.Stat(srcPath)
if err != nil {
return false, err
}
targetStat, err := os.Stat(target)
if err == nil {
if info.Size() == targetStat.Size() {
return true, nil
}
}
file, err := os.Create(target)
if err != nil {
return false, err
}
defer file.Close()
reader, _ := s.client.ReadStream(srcPath)
if _, err := io.Copy(file, reader); err != nil {
return false, err
}
return true, err
}
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(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(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(path.Join(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

@ -0,0 +1,36 @@
package cloud_storage
import (
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/utils/cloud_storage/client"
)
type CloudStorageClient interface {
ListBuckets() ([]interface{}, error)
Upload(src, target string) (bool, error)
}
func NewCloudStorageClient(backupType string, vars map[string]interface{}) (CloudStorageClient, error) {
switch backupType {
case constant.Local:
return client.NewLocalClient(vars)
case constant.S3:
return client.NewS3Client(vars)
case constant.OSS:
return client.NewOssClient(vars)
case constant.Sftp:
return client.NewSftpClient(vars)
case constant.WebDAV:
return client.NewWebDAVClient(vars)
case constant.MinIo:
return client.NewMinIoClient(vars)
case constant.Cos:
return client.NewCosClient(vars)
case constant.Kodo:
return client.NewKodoClient(vars)
case constant.OneDrive:
return client.NewOneDriveClient(vars)
default:
return nil, constant.ErrNotSupportType
}
}

View File

@ -1,12 +1,18 @@
import { ReqPage } from '.';
export namespace Backup {
export interface SearchWithType extends ReqPage {
type: string;
name: string;
}
export interface BackupInfo {
id: number;
name: string;
type: string;
accessKey: string;
bucket: string;
credential: string;
rememberAuth: boolean;
backupPath: string;
vars: string;
varsJson: object;

View File

@ -27,6 +27,9 @@ export const unbindLicense = () => {
export const loadBaseDir = () => {
return http.get<string>(`/settings/basedir`);
};
export const loadDaemonJsonPath = () => {
return http.get<string>(`/settings/daemonjson`, {});
};
// core
export const getSettingInfo = () => {
@ -35,15 +38,12 @@ export const getSettingInfo = () => {
export const getSystemAvailable = () => {
return http.get(`/settings/search/available`);
};
export const updateSetting = (param: Setting.SettingUpdate) => {
return http.post(`/core/settings/update`, param);
};
export const updateMenu = (param: Setting.SettingUpdate) => {
return http.post(`/core/settings/menu/update`, param);
};
export const updateProxy = (params: Setting.ProxyUpdate) => {
let request = deepCopy(params) as Setting.ProxyUpdate;
if (request.proxyPasswd) {
@ -52,23 +52,18 @@ export const updateProxy = (params: Setting.ProxyUpdate) => {
request.proxyType = request.proxyType === 'close' ? '' : request.proxyType;
return http.post(`/core/settings/proxy/update`, request);
};
export const updatePassword = (param: Setting.PasswordUpdate) => {
return http.post(`/core/settings/password/update`, param);
};
export const loadInterfaceAddr = () => {
return http.get(`/core/settings/interface`);
};
export const updateBindInfo = (ipv6: string, bindAddress: string) => {
return http.post(`/core/settings/bind/update`, { ipv6: ipv6, bindAddress: bindAddress });
};
export const updatePort = (param: Setting.PortUpdate) => {
return http.post(`/core/settings/port/update`, param);
};
export const updateSSL = (param: Setting.SSLUpdate) => {
return http.post(`/core/settings/ssl/update`, param);
};
@ -78,24 +73,17 @@ export const loadSSLInfo = () => {
export const downloadSSL = () => {
return http.download<any>(`/core/settings/ssl/download`);
};
export const handleExpired = (param: Setting.PasswordUpdate) => {
return http.post(`/core/settings/expired/handle`, param);
};
export const loadMFA = (param: Setting.MFARequest) => {
return http.post<Setting.MFAInfo>(`/core/settings/mfa`, param);
};
export const loadDaemonJsonPath = () => {
return http.get<string>(`/core/settings/daemonjson`, {});
};
export const bindMFA = (param: Setting.MFABind) => {
return http.post(`/core/settings/mfa/bind`, param);
};
// backup
// backup-agent
export const handleBackup = (params: Backup.Backup) => {
return http.post(`/settings/backup/backup`, params, TimeoutEnum.T_1H);
};
@ -105,9 +93,6 @@ export const handleRecover = (params: Backup.Recover) => {
export const handleRecoverByUpload = (params: Backup.Recover) => {
return http.post(`/settings/backup/recover/byupload`, params, TimeoutEnum.T_1D);
};
export const refreshOneDrive = () => {
return http.post(`/settings/backup/refresh/onedrive`, {});
};
export const downloadBackupRecord = (params: Backup.RecordDownload) => {
return http.post<string>(`/settings/backup/record/download`, params, TimeoutEnum.T_10M);
};
@ -120,16 +105,23 @@ export const searchBackupRecords = (params: Backup.SearchBackupRecord) => {
export const searchBackupRecordsByCronjob = (params: Backup.SearchBackupRecordByCronjob) => {
return http.post<ResPage<Backup.RecordInfo>>(`/settings/backup/record/search/bycronjob`, params, TimeoutEnum.T_5M);
};
export const getBackupList = () => {
return http.get<Array<Backup.BackupInfo>>(`/settings/backup/search`);
};
export const getOneDriveInfo = () => {
return http.get<Backup.OneDriveInfo>(`/settings/backup/onedrive`);
};
export const getFilesFromBackup = (type: string) => {
return http.post<Array<any>>(`/settings/backup/search/files`, { type: type });
};
// backup-core
export const refreshOneDrive = () => {
return http.post(`/core/backup/refresh/onedrive`, {});
};
export const getBackupList = () => {
return http.post<Array<Backup.BackupInfo>>(`/core/backup/list`);
};
export const searchBackup = (params: Backup.SearchWithType) => {
return http.post<ResPage<Backup.BackupInfo>>(`/core/backup/search`, params);
};
export const getOneDriveInfo = () => {
return http.get<Backup.OneDriveInfo>(`/core/backup/onedrive`);
};
export const addBackup = (params: Backup.BackupOperate) => {
let request = deepCopy(params) as Backup.BackupOperate;
if (request.accessKey) {
@ -138,7 +130,7 @@ export const addBackup = (params: Backup.BackupOperate) => {
if (request.credential) {
request.credential = Base64.encode(request.credential);
}
return http.post<Backup.BackupOperate>(`/settings/backup`, request);
return http.post<Backup.BackupOperate>(`/core/backup`, request, TimeoutEnum.T_60S);
};
export const editBackup = (params: Backup.BackupOperate) => {
let request = deepCopy(params) as Backup.BackupOperate;
@ -148,10 +140,10 @@ export const editBackup = (params: Backup.BackupOperate) => {
if (request.credential) {
request.credential = Base64.encode(request.credential);
}
return http.post(`/settings/backup/update`, request);
return http.post(`/core/backup/update`, request);
};
export const deleteBackup = (params: { id: number }) => {
return http.post(`/settings/backup/del`, params);
return http.post(`/core/backup/del`, params);
};
export const listBucket = (params: Backup.ForBucket) => {
let request = deepCopy(params) as Backup.BackupOperate;
@ -161,7 +153,7 @@ export const listBucket = (params: Backup.ForBucket) => {
if (request.credential) {
request.credential = Base64.encode(request.credential);
}
return http.post(`/settings/backup/buckets`, request);
return http.post(`/core/backup/buckets`, request);
};
// snapshot

View File

@ -1423,6 +1423,7 @@ const message = {
client_secret: 'Client Secret',
redirect_uri: 'Redirect URL',
onedrive_helper: 'Custom configuration can be referred to in the official documentation',
clickToRefresh: 'Click to refresh',
refreshTime: 'Token Refresh Time',
refreshStatus: 'Token Refresh Status',
backupDir: 'Backup Dir',

View File

@ -1336,6 +1336,7 @@ const message = {
client_secret: '客戶端密鑰',
redirect_uri: '重定向 URL',
onedrive_helper: '自訂配置可參考官方文件',
clickToRefresh: '單擊可手動刷新',
refreshTime: '令牌刷新時間',
refreshStatus: '令牌刷新狀態',
codeWarning: '當前授權碼格式錯誤請重新確認',

View File

@ -1338,6 +1338,7 @@ const message = {
client_secret: '客户端密钥',
redirect_uri: '重定向 Url',
onedrive_helper: '自定义配置可参考官方文档',
clickToRefresh: '单击可手动刷新',
refreshTime: '令牌刷新时间',
refreshStatus: '令牌刷新状态',
codeWarning: '当前授权码格式错误请重新确认',

View File

@ -1,13 +1,13 @@
<template>
<div class="logo" style="cursor: pointer" @click="goHome">
<template v-if="isCollapse">
<img v-if="globalStore.themeConfig.logo" :src="'/api/v1/images/logo'" style="cursor: pointer" alt="logo" />
<img v-if="globalStore.themeConfig.logo" :src="'/api/v2/images/logo'" style="cursor: pointer" alt="logo" />
<MenuLogo v-else />
</template>
<template v-else>
<img
v-if="globalStore.themeConfig.logoWithText"
:src="'/api/v1/images/logoWithText'"
:src="'/api/v2/images/logoWithText'"
style="cursor: pointer"
alt="logo"
/>

View File

@ -20,9 +20,9 @@ export function initFavicon() {
link.rel = 'shortcut icon';
if (globalStore.isDarkGoldTheme) {
let goldLink = new URL(`../assets/images/favicon-gold.png`, import.meta.url).href;
link.href = favicon ? '/api/v1/images/favicon' : goldLink;
link.href = favicon ? '/api/v2/images/favicon' : goldLink;
} else {
link.href = favicon ? '/api/v1/images/favicon' : '/public/favicon.png';
link.href = favicon ? '/api/v2/images/favicon' : '/public/favicon.png';
}
document.getElementsByTagName('head')[0].appendChild(link);
}

View File

@ -130,7 +130,7 @@ const searchLogs = async () => {
const protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss';
const host = href.split('//')[1].split('/')[0];
terminalSocket.value = new WebSocket(
`${protocol}://${host}/api/v1/containers/search/log?container=${logSearch.containerID}&since=${logSearch.mode}&tail=${logSearch.tail}&follow=${logSearch.isWatch}`,
`${protocol}://${host}/api/v2/containers/search/log?container=${logSearch.containerID}&since=${logSearch.mode}&tail=${logSearch.tail}&follow=${logSearch.isWatch}`,
);
terminalSocket.value.onmessage = (event) => {
logInfo.value += event.data.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '');

View File

@ -95,7 +95,7 @@ const initTerm = (formEl: FormInstance | undefined) => {
terminalOpen.value = true;
await nextTick();
terminalRef.value!.acceptParams({
endpoint: '/api/v1/containers/exec',
endpoint: '/api/v2/containers/exec',
args: `containerid=${form.containerID}&user=${form.user}&command=${form.command}`,
error: '',
initCmd: '',

View File

@ -301,7 +301,7 @@ const initTerminal = async () => {
terminalShow.value = true;
redisStatus.value = 'Running';
terminalRef.value.acceptParams({
endpoint: '/api/v1/databases/redis/exec',
endpoint: '/api/v2/databases/redis/exec',
args: `name=${currentDBName.value}&from=${currentDB.value.from}`,
error: '',
initCmd: '',
@ -319,7 +319,7 @@ const initTerminal = async () => {
if (res.data.status === 'Running') {
terminalShow.value = true;
terminalRef.value.acceptParams({
endpoint: '/api/v1/databases/redis/exec',
endpoint: '/api/v2/databases/redis/exec',
args: `name=${currentDBName.value}&from=${currentDB.value.from}`,
error: '',
initCmd: '',

View File

@ -64,7 +64,7 @@ const initProcess = () => {
let href = window.location.href;
let protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss';
let ipLocal = href.split('//')[1].split('/')[0];
processSocket = new WebSocket(`${protocol}://${ipLocal}/api/v1/files/ws`);
processSocket = new WebSocket(`${protocol}://${ipLocal}/api/v2/files/ws`);
processSocket.onopen = onOpenProcess;
processSocket.onmessage = onMessage;
processSocket.onerror = onerror;

View File

@ -173,7 +173,7 @@ const initProcess = () => {
let href = window.location.href;
let protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss';
let ipLocal = href.split('//')[1].split('/')[0];
processSocket = new WebSocket(`${protocol}://${ipLocal}/api/v1/process/ws`);
processSocket = new WebSocket(`${protocol}://${ipLocal}/api/v2/process/ws`);
processSocket.onopen = onOpenProcess;
processSocket.onmessage = onMessage;
processSocket.onerror = onerror;

View File

@ -238,7 +238,7 @@ const initProcess = () => {
let href = window.location.href;
let protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss';
let ipLocal = href.split('//')[1].split('/')[0];
processSocket = new WebSocket(`${protocol}://${ipLocal}/api/v1/process/ws`);
processSocket = new WebSocket(`${protocol}://${ipLocal}/api/v2/process/ws`);
processSocket.onopen = onOpenProcess;
processSocket.onmessage = onMessage;
processSocket.onerror = onerror;

View File

@ -75,7 +75,7 @@ const initProcess = () => {
let href = window.location.href;
let protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss';
let ipLocal = href.split('//')[1].split('/')[0];
processSocket = new WebSocket(`${protocol}://${ipLocal}/api/v1/process/ws`);
processSocket = new WebSocket(`${protocol}://${ipLocal}/api/v2/process/ws`);
processSocket.onopen = onOpenProcess;
processSocket.onmessage = onMessage;
processSocket.onerror = onerror;

View File

@ -348,7 +348,7 @@ const onReconnect = async (item: any) => {
nextTick(() => {
ctx.refs[`t-${item.index}`] &&
ctx.refs[`t-${item.index}`][0].acceptParams({
endpoint: '/api/v1/terminals',
endpoint: '/api/v2/terminals',
args: `id=${item.wsID}`,
error: res.data ? '' : 'Failed to set up the connection. Please check the host information',
});
@ -380,7 +380,7 @@ const onConnTerminal = async (title: string, wsID: number, isLocal?: boolean) =>
nextTick(() => {
ctx.refs[`t-${terminalValue.value}`] &&
ctx.refs[`t-${terminalValue.value}`][0].acceptParams({
endpoint: '/api/v1/terminals',
endpoint: '/api/v2/terminals',
args: `id=${wsID}`,
initCmd: initCmd.value,
error: res.data ? '' : 'Authentication failed. Please check the host information !',

View File

@ -4,7 +4,7 @@
<template #main>
<div style="text-align: center; margin-top: 20px">
<div style="justify-self: center" class="logo">
<img v-if="globalStore.themeConfig.logo" style="width: 80px" :src="'/api/v1/images/logo'" />
<img v-if="globalStore.themeConfig.logo" style="width: 80px" :src="'/api/v2/images/logo'" />
<PrimaryLogo v-else />
</div>
<h3 class="description">{{ globalStore.themeConfig.title || $t('setting.description') }}</h3>

View File

@ -1,225 +0,0 @@
<template>
<DrawerPro v-model="drawerVisible" :header="title + $t('setting.backupAccount')" :back="handleClose" size="large">
<el-form @submit.prevent ref="formRef" v-loading="loading" label-position="top" :model="cosData.rowData">
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
<el-tag>{{ $t('setting.' + cosData.rowData!.type) }}</el-tag>
</el-form-item>
<el-form-item label="Access Key ID" prop="accessKey" :rules="Rules.requiredInput">
<el-input v-model.trim="cosData.rowData!.accessKey" />
</el-form-item>
<el-form-item label="Secret Key" prop="credential" :rules="Rules.requiredInput">
<el-input show-password clearable v-model.trim="cosData.rowData!.credential" />
</el-form-item>
<el-form-item label="Region" prop="varsJson.region" :rules="Rules.requiredInput">
<el-checkbox v-model="regionInput" :label="$t('container.input')" />
<el-select v-if="!regionInput" v-model="cosData.rowData!.varsJson['region']" filterable clearable>
<el-option v-for="item in cities" :key="item.value" :label="item.label" :value="item.value">
<span class="float-left">{{ item.label }}</span>
<span class="option-help">
{{ item.value }}
</span>
</el-option>
</el-select>
<el-input v-else v-model.trim="cosData.rowData!.varsJson['region']" />
</el-form-item>
<el-form-item label="Endpoint" prop="varsJson.endpointItem" :rules="Rules.requiredInput">
<el-input v-model.trim="cosData.rowData!.varsJson['endpointItem']">
<template #prepend>
<el-select v-model.trim="endpointProto" class="p-w-100">
<el-option label="http" value="http" />
<el-option label="https" value="https" />
</el-select>
</template>
</el-input>
</el-form-item>
<el-form-item label="Bucket" prop="bucket">
<el-select @change="errBuckets = false" class="!w-4/5" v-model="cosData.rowData!.bucket">
<el-option v-for="item in buckets" :key="item" :value="item" />
</el-select>
<el-button class="!w-1/5" plain @click="getBuckets(formRef)">
{{ $t('setting.loadBucket') }}
</el-button>
<span v-if="errBuckets" class="input-error">{{ $t('commons.rule.requiredSelect') }}</span>
</el-form-item>
<el-form-item :label="$t('setting.scType')" prop="varsJson.scType" :rules="[Rules.requiredSelect]">
<el-select v-model="cosData.rowData!.varsJson['scType']">
<el-option value="Standard" :label="$t('setting.scStandard')" />
<el-option value="Standard_IA" :label="$t('setting.scStandard_IA')" />
<el-option value="Archive" :label="$t('setting.scArchive')" />
<el-option value="Deep_Archive" :label="$t('setting.scDeep_Archive')" />
</el-select>
<el-alert
v-if="cosData.rowData!.varsJson['scType'] === 'Archive' || cosData.rowData!.varsJson['scType'] === 'Deep_Archive'"
class="mt-2.5"
:closable="false"
type="warning"
:title="$t('setting.archiveHelper')"
/>
</el-form-item>
<el-form-item :label="$t('setting.backupDir')" prop="backupPath">
<el-input clearable v-model.trim="cosData.rowData!.backupPath" placeholder="/1panel" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="loading" @click="handleClose">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</DrawerPro>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { Backup } from '@/api/interface/backup';
import { addBackup, editBackup, listBucket } from '@/api/modules/setting';
import { deepCopy, spliceHttp, splitHttp } from '@/utils/util';
import { MsgSuccess } from '@/utils/message';
const loading = ref(false);
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
const buckets = ref();
const regionInput = ref();
const errBuckets = ref();
const endpointProto = ref('http');
const emit = defineEmits(['search']);
const cities = [
{ value: 'ap-beijing-1', label: i18n.global.t('setting.ap_beijing_1') },
{ value: 'ap-beijing', label: i18n.global.t('setting.ap_beijing') },
{ value: 'ap-nanjing', label: i18n.global.t('setting.ap_nanjing') },
{ value: 'ap-shanghai', label: i18n.global.t('setting.ap_shanghai') },
{ value: 'ap-guangzhou', label: i18n.global.t('setting.ap_guangzhou') },
{ value: 'ap-chengdu', label: i18n.global.t('setting.ap_chengdu') },
{ value: 'ap-chongqing', label: i18n.global.t('setting.ap_chongqing') },
{ value: 'ap-shenzhen_fsi', label: i18n.global.t('setting.ap_shenzhen_fsi') },
{ value: 'ap-shanghai_fsi', label: i18n.global.t('setting.ap_shanghai_fsi') },
{ value: 'ap-beijing_fsi', label: i18n.global.t('setting.ap_beijing_fsi') },
{ value: 'ap-hongkong', label: i18n.global.t('setting.ap_hongkong') },
{ value: 'ap-singapore', label: i18n.global.t('setting.ap_singapore') },
{ value: 'ap-mumbai', label: i18n.global.t('setting.ap_mumbai') },
{ value: 'ap-jakarta', label: i18n.global.t('setting.ap_jakarta') },
{ value: 'ap-seoul', label: i18n.global.t('setting.ap_seoul') },
{ value: 'ap-bangkok', label: i18n.global.t('setting.ap_bangkok') },
{ value: 'ap-tokyo', label: i18n.global.t('setting.ap_tokyo') },
{ value: 'na-siliconvalley', label: i18n.global.t('setting.na_siliconvalley') },
{ value: 'na-ashburn', label: i18n.global.t('setting.na_ashburn') },
{ value: 'na-toronto', label: i18n.global.t('setting.na_toronto') },
{ value: 'sa-saopaulo', label: i18n.global.t('setting.sa_saopaulo') },
{ value: 'eu-frankfurt', label: i18n.global.t('setting.eu_frankfurt') },
];
interface DialogProps {
title: string;
rowData?: Backup.BackupInfo;
}
const title = ref<string>('');
const drawerVisible = ref(false);
const cosData = ref<DialogProps>({
title: '',
});
const acceptParams = (params: DialogProps): void => {
buckets.value = [];
cosData.value = params;
if (params.title === 'create' || (params.title === 'edit' && !cosData.value.rowData.varsJson['scType'])) {
cosData.value.rowData.varsJson['scType'] = 'Standard';
}
if (cosData.value.title === 'edit') {
let httpItem = splitHttp(cosData.value.rowData!.varsJson['endpoint']);
cosData.value.rowData!.varsJson['endpointItem'] = httpItem.url;
endpointProto.value = httpItem.proto;
}
title.value = i18n.global.t('commons.button.' + cosData.value.title);
drawerVisible.value = true;
};
const handleClose = () => {
emit('search');
drawerVisible.value = false;
};
const getBuckets = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
loading.value = true;
let item = deepCopy(cosData.value.rowData!.varsJson);
item['endpoint'] = spliceHttp(endpointProto.value, cosData.value.rowData!.varsJson['endpointItem']);
listBucket({
type: cosData.value.rowData!.type,
vars: JSON.stringify(item),
accessKey: cosData.value.rowData!.accessKey,
credential: cosData.value.rowData!.credential,
})
.then((res) => {
loading.value = false;
buckets.value = res.data;
})
.catch(() => {
buckets.value = [];
loading.value = false;
});
});
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!cosData.value.rowData.bucket) {
errBuckets.value = true;
return;
}
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
if (!cosData.value.rowData) return;
cosData.value.rowData!.varsJson['endpoint'] = spliceHttp(
endpointProto.value,
cosData.value.rowData!.varsJson['endpointItem'],
);
cosData.value.rowData.vars = JSON.stringify(cosData.value.rowData!.varsJson);
loading.value = true;
if (cosData.value.title === 'create') {
await addBackup(cosData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
return;
}
await editBackup(cosData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
});
};
defineExpose({
acceptParams,
});
</script>
<style scoped lang="scss">
.option-help {
float: right;
font-size: 12px;
word-break: break-all;
color: #8f959e;
}
</style>

View File

@ -0,0 +1,26 @@
import i18n from '@/lang';
export const cities = [
{ value: 'ap-beijing-1', label: i18n.global.t('setting.ap_beijing_1') },
{ value: 'ap-beijing', label: i18n.global.t('setting.ap_beijing') },
{ value: 'ap-nanjing', label: i18n.global.t('setting.ap_nanjing') },
{ value: 'ap-shanghai', label: i18n.global.t('setting.ap_shanghai') },
{ value: 'ap-guangzhou', label: i18n.global.t('setting.ap_guangzhou') },
{ value: 'ap-chengdu', label: i18n.global.t('setting.ap_chengdu') },
{ value: 'ap-chongqing', label: i18n.global.t('setting.ap_chongqing') },
{ value: 'ap-shenzhen_fsi', label: i18n.global.t('setting.ap_shenzhen_fsi') },
{ value: 'ap-shanghai_fsi', label: i18n.global.t('setting.ap_shanghai_fsi') },
{ value: 'ap-beijing_fsi', label: i18n.global.t('setting.ap_beijing_fsi') },
{ value: 'ap-hongkong', label: i18n.global.t('setting.ap_hongkong') },
{ value: 'ap-singapore', label: i18n.global.t('setting.ap_singapore') },
{ value: 'ap-mumbai', label: i18n.global.t('setting.ap_mumbai') },
{ value: 'ap-jakarta', label: i18n.global.t('setting.ap_jakarta') },
{ value: 'ap-seoul', label: i18n.global.t('setting.ap_seoul') },
{ value: 'ap-bangkok', label: i18n.global.t('setting.ap_bangkok') },
{ value: 'ap-tokyo', label: i18n.global.t('setting.ap_tokyo') },
{ value: 'na-siliconvalley', label: i18n.global.t('setting.na_siliconvalley') },
{ value: 'na-ashburn', label: i18n.global.t('setting.na_ashburn') },
{ value: 'na-toronto', label: i18n.global.t('setting.na_toronto') },
{ value: 'sa-saopaulo', label: i18n.global.t('setting.sa_saopaulo') },
{ value: 'eu-frankfurt', label: i18n.global.t('setting.eu_frankfurt') },
];

View File

@ -1,38 +1,16 @@
<template>
<div>
<LayoutContent :title="$t('commons.button.backup')">
<LayoutContent :title="$t('setting.backupAccount')">
<template #leftToolBar>
<el-button type="primary" @click="onOpenDialog('create')">
{{ $t('commons.button.add') }}
</el-button>
</template>
<template #rightToolBar>
<TableSearch @search="search()" v-model:searchName="paginationConfig.name" class="mr-2.5" />
<TableSetting @search="search()" />
</template>
<template #main>
<el-form label-width="130px" :v-key="refresh">
<el-row :gutter="20">
<el-col :span="24">
<div class="flx-justify-between">
<span class="flx-align-center">
<svg-icon class="card-logo" iconName="p-file-folder"></svg-icon>
<span class="card-title">&nbsp;{{ $t('setting.LOCAL') }}</span>
</span>
<div style="float: right">
<el-button round @click="onOpenDialog('edit', 'LOCAL', localData)">
{{ $t('commons.button.edit') }}
</el-button>
</div>
</div>
<el-divider class="divider" />
<div style="margin-left: 20px">
<el-form-item :label="$t('setting.backupDir')">
{{ localData.varsJson['dir'] }}
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">
{{ dateFormat(0, 0, localData.createdAt) }}
</el-form-item>
</div>
</el-col>
</el-row>
</el-form>
<div class="common-div">
<span class="card-title">{{ $t('setting.thirdParty') }}</span>
</div>
<el-alert type="info" :closable="false" class="common-div">
<template #title>
<span>
@ -48,626 +26,127 @@
</span>
</template>
</el-alert>
<el-row :gutter="20" class="common-div">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<div class="flx-justify-between">
<span class="flx-align-center">
<svg-icon class="card-logo" iconName="p-aws"></svg-icon>
<span class="card-title">&nbsp;{{ $t('setting.S3') }}</span>
</span>
<div>
<el-button
round
:disabled="s3Data.id === 0"
@click="onOpenDialog('edit', 'S3', s3Data)"
>
{{ $t('commons.button.edit') }}
</el-button>
<el-button round :disabled="s3Data.id === 0" @click="onDelete(s3Data)">
{{ $t('commons.button.delete') }}
</el-button>
</div>
</div>
<el-divider class="divider" />
<div v-if="s3Data.id !== 0" style="margin-left: 20px">
<el-form-item label="Region">
{{ s3Data.varsJson['region'] }}
</el-form-item>
<el-form-item label="Endpoint">
{{ s3Data.varsJson['endpoint'] }}
</el-form-item>
<el-form-item label="Bucket">
{{ s3Data.bucket }}
</el-form-item>
<el-form-item :label="$t('setting.scType')">
<span v-if="!s3Data.varsJson['scType'] || s3Data.varsJson['scType'] === 'STANDARD'">
{{ $t('setting.typeStandard') }}
</span>
<span v-if="s3Data.varsJson['scType'] === 'STANDARD_IA'">
{{ $t('setting.typeStandard_IA') }}
</span>
<span v-if="s3Data.varsJson['scType'] === 'GLACIER'">
{{ $t('setting.typeArchive') }}
</span>
<span v-if="s3Data.varsJson['scType'] === 'DEEP_ARCHIVE'">
{{ $t('setting.typeDeep_Archive') }}
</span>
</el-form-item>
<el-form-item :label="$t('setting.backupDir')">
<span v-if="s3Data.backupPath">{{ s3Data.backupPath }}</span>
<span v-else>{{ $t('setting.unSetting') }}</span>
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">
{{ dateFormat(0, 0, s3Data.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', 'S3')">
{{ $t('setting.createBackupAccount', [$t('setting.S3')]) }}
</el-button>
</el-alert>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<div class="flx-justify-between">
<span class="flx-align-center">
<svg-icon class="card-logo" iconName="p-oss"></svg-icon>
<span class="card-title">&nbsp;{{ $t('setting.OSS') }}</span>
</span>
<div>
<el-button
round
:disabled="ossData.id === 0"
@click="onOpenDialog('edit', 'OSS', ossData)"
>
{{ $t('commons.button.edit') }}
</el-button>
<el-button round :disabled="ossData.id === 0" @click="onDelete(ossData)">
{{ $t('commons.button.delete') }}
</el-button>
</div>
</div>
<el-divider class="divider" />
<div v-if="ossData.id !== 0" style="margin-left: 20px">
<el-form-item label="Endpoint">
{{ ossData.varsJson['endpoint'] }}
</el-form-item>
<el-form-item label="Bucket">
{{ ossData.bucket }}
</el-form-item>
<el-form-item :label="$t('setting.scType')">
<span v-if="!ossData.varsJson['scType'] || ossData.varsJson['scType'] === 'Standard'">
{{ $t('setting.typeStandard') }}
</span>
<span v-if="ossData.varsJson['scType'] === 'IA'">
{{ $t('setting.typeStandard_IA') }}
</span>
<span v-if="ossData.varsJson['scType'] === 'Archive'">
{{ $t('setting.typeArchive') }}
</span>
<span v-if="ossData.varsJson['scType'] === 'ColdArchive'">
{{ $t('setting.typeDeep_Archive') }}
</span>
</el-form-item>
<el-form-item :label="$t('setting.backupDir')">
<span v-if="ossData.backupPath">{{ ossData.backupPath }}</span>
<span v-else>{{ $t('setting.unSetting') }}</span>
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">
{{ dateFormat(0, 0, ossData.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', 'OSS')">
{{ $t('setting.createBackupAccount', [$t('setting.OSS')]) }}
</el-button>
</el-alert>
</el-col>
</el-row>
<el-row :gutter="20" class="common-div">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<div class="flx-justify-between">
<span class="flx-align-center">
<svg-icon class="card-logo" iconName="p-tengxunyun1"></svg-icon>
<span class="card-title">&nbsp;{{ $t('setting.COS') }}</span>
</span>
<div>
<el-button
round
:disabled="cosData.id === 0"
@click="onOpenDialog('edit', 'COS', cosData)"
>
{{ $t('commons.button.edit') }}
</el-button>
<el-button round :disabled="cosData.id === 0" @click="onDelete(cosData)">
{{ $t('commons.button.delete') }}
</el-button>
</div>
</div>
<el-divider class="divider" />
<div v-if="cosData.id !== 0" style="margin-left: 20px">
<el-form-item label="Region">
{{ cosData.varsJson['region'] }}
</el-form-item>
<el-form-item label="Bucket">
{{ cosData.bucket }}
</el-form-item>
<el-form-item :label="$t('setting.scType')">
<span v-if="!cosData.varsJson['scType'] || cosData.varsJson['scType'] === 'Standard'">
{{ $t('setting.typeStandard') }}
</span>
<span v-if="cosData.varsJson['scType'] === 'Standard_IA'">
{{ $t('setting.typeStandard_IA') }}
</span>
<span v-if="cosData.varsJson['scType'] === 'Archive'">
{{ $t('setting.typeArchive') }}
</span>
<span v-if="cosData.varsJson['scType'] === 'Deep_Archive'">
{{ $t('setting.typeDeep_Archive') }}
</span>
</el-form-item>
<el-form-item :label="$t('setting.backupDir')">
<span v-if="cosData.backupPath">{{ cosData.backupPath }}</span>
<span v-else>{{ $t('setting.unSetting') }}</span>
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">
{{ dateFormat(0, 0, cosData.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', 'COS')">
{{ $t('setting.createBackupAccount', [$t('setting.COS')]) }}
</el-button>
</el-alert>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<div class="flx-justify-between">
<span class="flx-align-center">
<svg-icon class="card-logo" iconName="p-onedrive"></svg-icon>
<span class="card-title">&nbsp;{{ $t('setting.OneDrive') }}</span>
</span>
<div>
<el-button
round
plain
:disabled="oneDriveData.id === 0"
@click="onOpenDialog('edit', 'OneDrive', 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="divider" />
<div v-if="oneDriveData.id !== 0" style="margin-left: 20px">
<el-form-item :label="$t('setting.backupDir')">
<span v-if="oneDriveData.backupPath">{{ oneDriveData.backupPath }}</span>
<span v-else>{{ $t('setting.unSetting') }}</span>
</el-form-item>
<el-form-item :label="$t('setting.refreshTime')">
<span>{{ oneDriveData.varsJson['refresh_time'] }}</span>
<el-button @click="refreshToken" link type="primary" class="ml-2">
{{ $t('commons.button.refresh') }}
</el-button>
</el-form-item>
<el-form-item :label="$t('setting.refreshStatus')">
<el-tag v-if="oneDriveData.varsJson['refresh_status'] === 'Success'" type="success">
{{ $t('commons.status.success') }}
<ComplexTable :pagination-config="paginationConfig" @sort-change="search" @search="search" :data="data">
<el-table-column
:label="$t('commons.table.name')"
:min-width="80"
prop="name"
show-overflow-tooltip
/>
<el-table-column :label="$t('commons.table.type')" :min-width="80" prop="type">
<template #default="{ row }">
<el-tag>{{ $t('setting.' + row.type) }}</el-tag>
<el-tooltip>
<template #content>
{{ $t('setting.clickToRefresh') }}
<br />
<span v-if="row.varsJson['refresh_status'] === 'Success'">
{{ $t('setting.refreshStatus') + ':' + $t('commons.status.success') }}
</span>
<div v-else>
<span>
{{ $t('setting.refreshStatus') + ':' + $t('commons.status.failed') }}
</span>
<br />
<span>
{{ $t('commons.table.message') + ':' + row.varsJson['refresh_msg'] }}
</span>
</div>
<br />
{{ $t('setting.refreshTime') + ':' + row.varsJson['refresh_time'] }}
</template>
<el-tag @click="refreshToken" v-if="row.type === 'OneDrive'" class="ml-1">
{{ 'Token ' + $t('commons.button.refresh') }}
</el-tag>
<el-tooltip
v-if="oneDriveData.varsJson['refresh_status'] === 'Failed'"
:content="oneDriveData.varsJson['refresh_msg']"
placement="top"
>
<el-tag type="danger">
{{ $t('commons.status.failed') }}
</el-tag>
</el-tooltip>
</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: 257px" :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 class="flx-justify-between">
<span class="flx-align-center">
<svg-icon class="card-logo" iconName="p-qiniuyun"></svg-icon>
<span class="card-title">&nbsp;{{ $t('setting.KODO') }}</span>
</span>
<div>
<el-button
round
:disabled="kodoData.id === 0"
@click="onOpenDialog('edit', 'KODO', kodoData)"
>
{{ $t('commons.button.edit') }}
</el-button>
<el-button round :disabled="kodoData.id === 0" @click="onDelete(kodoData)">
{{ $t('commons.button.delete') }}
</el-button>
</div>
</div>
<el-divider class="divider" />
<div v-if="kodoData.id !== 0" style="margin-left: 20px">
<el-form-item :label="$t('setting.domain')">
{{ kodoData.varsJson['domain'] }}
</el-form-item>
<el-form-item label="Bucket">
{{ kodoData.bucket }}
</el-form-item>
<el-form-item :label="$t('setting.backupDir')">
<span v-if="kodoData.backupPath">{{ kodoData.backupPath }}</span>
<span v-else>{{ $t('setting.unSetting') }}</span>
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">
{{ dateFormat(0, 0, kodoData.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', 'KODO')">
{{ $t('setting.createBackupAccount', [$t('setting.KODO')]) }}
</el-button>
</el-alert>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<div class="flx-justify-between">
<span class="flx-align-center">
<svg-icon class="card-logo" iconName="p-minio"></svg-icon>
<span class="card-title">&nbsp;MINIO</span>
</span>
<div>
<el-button
round
:disabled="minioData.id === 0"
@click="onOpenDialog('edit', 'MINIO', minioData)"
>
{{ $t('commons.button.edit') }}
</el-button>
<el-button :disabled="minioData.id === 0" round @click="onDelete(minioData)">
{{ $t('commons.button.delete') }}
</el-button>
</div>
</div>
<el-divider class="divider" />
<div v-if="minioData.id !== 0" style="margin-left: 20px">
<el-form-item label="Endpoint">
{{ minioData.varsJson['endpoint'] }}
</el-form-item>
<el-form-item label="Bucket">
{{ minioData.bucket }}
</el-form-item>
<el-form-item :label="$t('setting.backupDir')">
<span v-if="minioData.backupPath">{{ minioData.backupPath }}</span>
<span v-else>{{ $t('setting.unSetting') }}</span>
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">
{{ dateFormat(0, 0, minioData.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', 'MINIO')">
{{ $t('setting.createBackupAccount', [$t('setting.MINIO')]) }}
</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 class="flx-justify-between">
<span class="flx-align-center">
<svg-icon class="card-logo" iconName="p-SFTP"></svg-icon>
<span class="card-title">&nbsp;SFTP</span>
</span>
<div>
<el-button
round
plain
:disabled="sftpData.id === 0"
@click="onOpenDialog('edit', 'SFTP', sftpData)"
>
{{ $t('commons.button.edit') }}
</el-button>
<el-button round :disabled="sftpData.id === 0" @click="onDelete(sftpData)">
{{ $t('commons.button.delete') }}
</el-button>
</div>
</div>
<el-divider class="divider" />
<div v-if="sftpData.id !== 0" style="margin-left: 20px">
<el-form-item :label="$t('setting.address')">
{{ sftpData.varsJson['address'] }}
</el-form-item>
<el-form-item :label="$t('commons.table.port')">
{{ sftpData.varsJson['port'] }}
</el-form-item>
<el-form-item :label="$t('setting.backupDir')">
{{ sftpData.bucket }}
</el-form-item>
<el-form-item :label="$t('commons.table.createdAt')">
{{ dateFormat(0, 0, sftpData.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', 'SFTP')">
{{ $t('setting.createBackupAccount', [$t('setting.SFTP')]) }}
</el-button>
</el-alert>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<div class="flx-justify-between">
<span class="flx-align-center">
<svg-icon class="card-logo" iconName="p-webdav"></svg-icon>
<span class="card-title">&nbsp;WebDAV</span>
</span>
<div>
<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('setting.backupDir')">
{{ 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-tooltip>
</template>
</el-table-column>
<el-table-column prop="bucket" label="Bucket" show-overflow-tooltip>
<template #default="{ row }">
{{ row.bucket || '-' }}
</template>
</el-table-column>
<el-table-column prop="endpoint" label="Endpoint" show-overflow-tooltip>
<template #default="{ row }">
{{ loadEndpoint(row) }}
</template>
</el-table-column>
<el-table-column prop="backupPath" :label="$t('setting.backupDir')" show-overflow-tooltip />
<el-table-column
prop="createdAt"
:label="$t('commons.table.date')"
:formatter="dateFormat"
show-overflow-tooltip
/>
<fu-table-operations
width="300px"
:buttons="buttons"
:ellipsis="10"
:label="$t('commons.table.operate')"
fix
/>
</ComplexTable>
</template>
</LayoutContent>
<localDialog ref="localRef" @search="search" />
<s3Dialog ref="s3Ref" @search="search" />
<ossDialog ref="ossRef" @search="search" />
<cosDialog ref="cosRef" @search="search" />
<oneDriveDialog ref="oneDriveRef" @search="search" />
<kodoDialog ref="kodoRef" @search="search" />
<minioDialog ref="minioRef" @search="search" />
<sftpDialog ref="sftpRef" @search="search" />
<webDavDialog ref="webDavRef" @search="search" />
<Operate ref="dialogRef" @search="search" />
<OpDialog ref="opRef" @search="search" />
</div>
</template>
<script setup lang="ts">
import { dateFormat } from '@/utils/util';
import { onMounted, ref } from 'vue';
import { getBackupList, deleteBackup, refreshOneDrive } from '@/api/modules/setting';
import localDialog from '@/views/setting/backup-account/local/index.vue';
import s3Dialog from '@/views/setting/backup-account/s3/index.vue';
import ossDialog from '@/views/setting/backup-account/oss/index.vue';
import cosDialog from '@/views/setting/backup-account/cos/index.vue';
import oneDriveDialog from '@/views/setting/backup-account/onedrive/index.vue';
import kodoDialog from '@/views/setting/backup-account/kodo/index.vue';
import minioDialog from '@/views/setting/backup-account/minio/index.vue';
import sftpDialog from '@/views/setting/backup-account/sftp/index.vue';
import webDavDialog from '@/views/setting/backup-account/webdav/index.vue';
import { searchBackup, deleteBackup, refreshOneDrive } from '@/api/modules/setting';
import Operate from '@/views/setting/backup-account/operate/index.vue';
import { Backup } from '@/api/interface/backup';
import { ElForm } from 'element-plus';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
const loading = ref();
const data = ref();
const paginationConfig = reactive({
cacheSizeKey: 'backup-page-size',
currentPage: 1,
pageSize: 10,
total: 0,
type: '',
name: '',
});
const opRef = ref();
const refresh = ref(false);
const localRef = ref();
const s3Ref = ref();
const ossRef = ref();
const cosRef = ref();
const oneDriveRef = ref();
const kodoRef = ref();
const minioRef = ref();
const sftpRef = ref();
const webDavRef = ref();
const localData = ref<Backup.BackupInfo>({
id: 0,
type: 'LOCAL',
accessKey: '',
bucket: '',
credential: '',
backupPath: '',
vars: '',
varsJson: {
dir: '',
},
createdAt: new Date(),
});
const ossData = ref<Backup.BackupInfo>({
id: 0,
type: 'OSS',
accessKey: '',
bucket: '',
credential: '',
backupPath: '',
vars: '',
varsJson: {
endpoint: '',
scType: 'Standard',
},
createdAt: new Date(),
});
const minioData = ref<Backup.BackupInfo>({
id: 0,
type: 'MINIO',
accessKey: '',
bucket: '',
credential: '',
backupPath: '',
vars: '',
varsJson: {
region: '',
endpoint: '',
},
createdAt: new Date(),
});
const sftpData = ref<Backup.BackupInfo>({
id: 0,
type: 'SFTP',
accessKey: '',
bucket: '',
credential: '',
backupPath: '',
vars: '',
varsJson: {
address: '',
port: 22,
},
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>({
id: 0,
type: 'OneDrive',
accessKey: '',
bucket: '',
credential: '',
backupPath: '',
vars: '',
varsJson: {
refresh_msg: '',
refresh_time: '',
refresh_status: '',
},
createdAt: new Date(),
});
const s3Data = ref<Backup.BackupInfo>({
id: 0,
type: 'S3',
accessKey: '',
bucket: '',
credential: '',
backupPath: '',
vars: '',
varsJson: {
region: '',
scType: 'Standard',
endpoint: '',
},
createdAt: new Date(),
});
const cosData = ref<Backup.BackupInfo>({
id: 0,
type: 'COS',
accessKey: '',
bucket: '',
credential: '',
backupPath: '',
vars: '',
varsJson: {
region: '',
scType: 'Standard',
endpoint: '',
},
createdAt: new Date(),
});
const kodoData = ref<Backup.BackupInfo>({
id: 0,
type: 'KODO',
accessKey: '',
bucket: '',
credential: '',
backupPath: '',
vars: '',
varsJson: {
domain: '',
},
createdAt: new Date(),
});
const dialogRef = ref();
const search = async () => {
const res = await getBackupList();
data.value = res.data || [];
for (const bac of data.value) {
if (bac.id !== 0) {
bac.varsJson = JSON.parse(bac.vars);
}
switch (bac.type) {
case 'LOCAL':
localData.value = bac;
break;
case 'OSS':
ossData.value = bac;
break;
case 'S3':
s3Data.value = bac;
break;
case 'MINIO':
minioData.value = bac;
break;
case 'SFTP':
sftpData.value = bac;
break;
case 'COS':
cosData.value = bac;
break;
case 'KODO':
kodoData.value = bac;
break;
case 'OneDrive':
oneDriveData.value = bac;
break;
case 'WebDAV':
webDAVData.value = bac;
break;
}
let params = {
page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize,
type: paginationConfig.type,
name: paginationConfig.name,
};
loading.value = true;
await searchBackup(params)
.then((res) => {
loading.value = false;
data.value = res.data.items || [];
for (const bac of data.value) {
if (bac.id !== 0) {
bac.varsJson = JSON.parse(bac.vars);
}
}
paginationConfig.total = res.data.total;
})
.catch(() => {
loading.value = false;
});
};
const loadEndpoint = (row: any) => {
if (row.type === 'COS' || row.type === 'MINIO' || row.type === 'OSS' || row.type === 'S3') {
return row.varsJson['endpoint'];
}
if (row.type === 'KODO') {
return row.varsJson['domain'];
}
return '';
};
const onDelete = async (row: Backup.BackupInfo) => {
@ -685,10 +164,8 @@ const onDelete = async (row: Backup.BackupInfo) => {
const onOpenDialog = async (
title: string,
accountType: string,
rowData: Partial<Backup.BackupInfo> = {
id: 0,
type: accountType,
varsJson: {},
},
) => {
@ -696,35 +173,7 @@ const onOpenDialog = async (
title,
rowData: { ...rowData },
};
switch (accountType) {
case 'LOCAL':
localRef.value.acceptParams(params);
return;
case 'S3':
s3Ref.value.acceptParams(params);
return;
case 'OSS':
ossRef.value.acceptParams(params);
return;
case 'COS':
cosRef.value.acceptParams(params);
return;
case 'OneDrive':
oneDriveRef.value.acceptParams(params);
return;
case 'KODO':
kodoRef.value.acceptParams(params);
return;
case 'MINIO':
minioRef.value.acceptParams(params);
return;
case 'SFTP':
sftpRef.value.acceptParams(params);
return;
case 'WebDAV':
webDavRef.value.acceptParams(params);
return;
}
dialogRef.value!.acceptParams(params);
};
const refreshToken = async () => {
@ -733,33 +182,22 @@ const refreshToken = async () => {
search();
};
const buttons = [
{
label: i18n.global.t('commons.button.edit'),
click: (row: Backup.BackupInfo) => {
onOpenDialog('edit', row);
},
},
{
label: i18n.global.t('commons.button.delete'),
click: (row: Backup.BackupInfo) => {
onDelete(row);
},
},
];
onMounted(() => {
search();
});
</script>
<style scoped lang="scss">
.divider {
display: block;
height: 1px;
width: 100%;
margin: 12px 0;
border-top: 1px var(--el-border-color) var(--el-border-style);
}
.alert-span {
color: var(--el-color-primary);
}
.common-div {
margin-top: 20px;
}
.card-title {
font-size: 14px;
font-weight: 500;
line-height: 25px;
}
.card-logo {
font-size: 7px;
}
</style>

View File

@ -1,172 +0,0 @@
<template>
<DrawerPro v-model="drawerVisible" :header="title + $t('setting.backupAccount')" :back="handleClose" size="large">
<el-form @submit.prevent ref="formRef" v-loading="loading" label-position="top" :model="kodoData.rowData">
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
<el-tag>{{ $t('setting.' + kodoData.rowData!.type) }}</el-tag>
</el-form-item>
<el-form-item label="Access Key ID" prop="accessKey" :rules="Rules.requiredInput">
<el-input v-model.trim="kodoData.rowData!.accessKey" />
</el-form-item>
<el-form-item label="Secret Key" prop="credential" :rules="Rules.requiredInput">
<el-input show-password clearable v-model.trim="kodoData.rowData!.credential" />
</el-form-item>
<el-form-item :label="$t('setting.domain')" prop="varsJson.domainItem" :rules="Rules.requiredInput">
<el-input v-model="kodoData.rowData!.varsJson['domainItem']">
<template #prepend>
<el-select v-model.trim="domainProto" class="p-w-100">
<el-option label="http" value="http" />
<el-option label="https" value="https" />
</el-select>
</template>
</el-input>
</el-form-item>
<el-form-item label="Bucket" prop="bucket">
<el-select @change="errBuckets = false" class="!w-4/5" v-model="kodoData.rowData!.bucket">
<el-option v-for="item in buckets" :key="item" :value="item" />
</el-select>
<el-button class="!w-1/5" plain @click="getBuckets(formRef)">
{{ $t('setting.loadBucket') }}
</el-button>
<span v-if="errBuckets" class="input-error">{{ $t('commons.rule.requiredSelect') }}</span>
</el-form-item>
<el-form-item :label="$t('cronjob.requestExpirationTime')" prop="varsJson.timeout">
<el-input-number
style="width: 200px"
:min="1"
step-strictly
:step="1"
v-model.number="kodoData.rowData!.varsJson['timeout']"
></el-input-number>
</el-form-item>
<el-form-item :label="$t('setting.backupDir')" prop="backupPath">
<el-input clearable v-model.trim="kodoData.rowData!.backupPath" placeholder="/1panel" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="loading" @click="handleClose">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</DrawerPro>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { Backup } from '@/api/interface/backup';
import { addBackup, editBackup, listBucket } from '@/api/modules/setting';
import { deepCopy, spliceHttp, splitHttp } from '@/utils/util';
import { MsgSuccess } from '@/utils/message';
const loading = ref(false);
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
const buckets = ref();
const errBuckets = ref();
const domainProto = ref('http');
const emit = defineEmits(['search']);
interface DialogProps {
title: string;
rowData?: Backup.BackupInfo;
}
const title = ref<string>('');
const drawerVisible = ref(false);
const kodoData = ref<DialogProps>({
title: '',
});
const acceptParams = (params: DialogProps): void => {
buckets.value = [];
kodoData.value = params;
if (kodoData.value.title === 'edit') {
let httpItem = splitHttp(kodoData.value.rowData!.varsJson['domain']);
kodoData.value.rowData!.varsJson['domainItem'] = httpItem.url;
domainProto.value = httpItem.proto;
}
title.value = i18n.global.t('commons.button.' + kodoData.value.title);
if (kodoData.value.rowData!.varsJson['timeout'] === undefined) {
kodoData.value.rowData!.varsJson['timeout'] = 1;
}
drawerVisible.value = true;
};
const handleClose = () => {
emit('search');
drawerVisible.value = false;
};
const getBuckets = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
loading.value = true;
let item = deepCopy(kodoData.value.rowData!.varsJson);
item['domain'] = spliceHttp(domainProto.value, kodoData.value.rowData!.varsJson['domainItem']);
listBucket({
type: kodoData.value.rowData!.type,
vars: JSON.stringify(item),
accessKey: kodoData.value.rowData!.accessKey,
credential: kodoData.value.rowData!.credential,
})
.then((res) => {
loading.value = false;
buckets.value = res.data;
})
.catch(() => {
buckets.value = [];
loading.value = false;
});
});
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!kodoData.value.rowData.bucket) {
errBuckets.value = true;
return;
}
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
if (!kodoData.value.rowData) return;
kodoData.value.rowData!.varsJson['domain'] = spliceHttp(
domainProto.value,
kodoData.value.rowData!.varsJson['domainItem'],
);
kodoData.value.rowData.vars = JSON.stringify(kodoData.value.rowData!.varsJson);
loading.value = true;
if (kodoData.value.title === 'create') {
await addBackup(kodoData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
return;
}
await editBackup(kodoData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
});
};
defineExpose({
acceptParams,
});
</script>

View File

@ -1,110 +0,0 @@
<template>
<DrawerPro v-model="drawerVisible" :header="title + $t('setting.backupAccount')" :back="handleClose" size="large">
<el-form @submit.prevent ref="formRef" v-loading="loading" label-position="top" :model="dialogData.rowData">
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
<el-tag>{{ $t('setting.' + dialogData.rowData!.type) }}</el-tag>
</el-form-item>
<el-form-item :label="$t('setting.backupDir')" prop="varsJson['dir']" :rules="Rules.requiredInput">
<el-input v-model="dialogData.rowData!.varsJson['dir']">
<template #prepend>
<FileList @choose="loadDir" :dir="true"></FileList>
</template>
</el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="loading" @click="handleClose">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</DrawerPro>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Rules } from '@/global/form-rules';
import FileList from '@/components/file-list/index.vue';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { Backup } from '@/api/interface/backup';
import { addBackup, editBackup } from '@/api/modules/setting';
import { MsgSuccess } from '@/utils/message';
const loading = ref(false);
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
const emit = defineEmits<{ (e: 'search'): void }>();
interface DialogProps {
title: string;
rowData?: Backup.BackupInfo;
}
const title = ref<string>('');
const drawerVisible = ref(false);
const dialogData = ref<DialogProps>({
title: '',
});
const acceptParams = (params: DialogProps): void => {
dialogData.value = params;
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
drawerVisible.value = true;
};
const handleClose = () => {
emit('search');
drawerVisible.value = false;
};
const loadDir = async (path: string) => {
dialogData.value.rowData!.varsJson['dir'] = path;
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
if (!dialogData.value.rowData) return;
dialogData.value.rowData.vars = JSON.stringify(dialogData.value.rowData!.varsJson);
loading.value = true;
if (dialogData.value.title === 'create') {
await addBackup(dialogData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
return;
}
await editBackup(dialogData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
});
};
defineExpose({
acceptParams,
});
</script>
<style lang="scss" scoped>
.append-button {
width: 80px;
background-color: var(--el-fill-color-light);
color: var(--el-color-info);
}
</style>

View File

@ -1,162 +0,0 @@
<template>
<DrawerPro v-model="drawerVisible" :header="title + $t('setting.backupAccount')" :back="handleClose" size="large">
<el-form @submit.prevent ref="formRef" v-loading="loading" label-position="top" :model="minioData.rowData">
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
<el-tag>{{ $t('setting.' + minioData.rowData!.type) }}</el-tag>
</el-form-item>
<el-form-item label="Access Key ID" prop="accessKey" :rules="Rules.requiredInput">
<el-input v-model.trim="minioData.rowData!.accessKey" />
</el-form-item>
<el-form-item label="Secret Key" prop="credential" :rules="Rules.requiredInput">
<el-input show-password clearable v-model.trim="minioData.rowData!.credential" />
</el-form-item>
<el-form-item label="Endpoint" prop="varsJson.endpointItem" :rules="Rules.requiredInput">
<el-input v-model="minioData.rowData!.varsJson['endpointItem']">
<template #prepend>
<el-select v-model.trim="endpointProto" 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="Bucket" prop="bucket">
<el-select class="!w-4/5" @change="errBuckets = false" v-model="minioData.rowData!.bucket">
<el-option v-for="item in buckets" :key="item" :value="item" />
</el-select>
<el-button class="!w-1/5" plain @click="getBuckets(formRef)">
{{ $t('setting.loadBucket') }}
</el-button>
<span v-if="errBuckets" class="input-error">{{ $t('commons.rule.requiredSelect') }}</span>
</el-form-item>
<el-form-item :label="$t('setting.backupDir')" prop="backupPath">
<el-input clearable v-model.trim="minioData.rowData!.backupPath" placeholder="/1panel" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="loading" @click="handleClose">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</DrawerPro>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { Backup } from '@/api/interface/backup';
import { addBackup, editBackup, listBucket } from '@/api/modules/setting';
import { deepCopy, splitHttp, spliceHttp } from '@/utils/util';
import { MsgSuccess } from '@/utils/message';
const loading = ref(false);
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
const buckets = ref();
const errBuckets = ref();
const endpointProto = ref('http');
const emit = defineEmits(['search']);
interface DialogProps {
title: string;
rowData?: Backup.BackupInfo;
}
const title = ref<string>('');
const drawerVisible = ref(false);
const minioData = ref<DialogProps>({
title: '',
});
const acceptParams = (params: DialogProps): void => {
buckets.value = [];
minioData.value = params;
if (minioData.value.title === 'edit') {
let httpItem = splitHttp(minioData.value.rowData!.varsJson['endpoint']);
minioData.value.rowData!.varsJson['endpointItem'] = httpItem.url;
endpointProto.value = httpItem.proto;
}
title.value = i18n.global.t('commons.button.' + minioData.value.title);
drawerVisible.value = true;
};
const handleClose = () => {
emit('search');
drawerVisible.value = false;
};
const getBuckets = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
loading.value = true;
let item = deepCopy(minioData.value.rowData!.varsJson);
item['endpoint'] = spliceHttp(endpointProto.value, minioData.value.rowData!.varsJson['endpointItem']);
item['endpointItem'] = undefined;
listBucket({
type: minioData.value.rowData!.type,
vars: JSON.stringify(item),
accessKey: minioData.value.rowData!.accessKey,
credential: minioData.value.rowData!.credential,
})
.then((res) => {
loading.value = false;
buckets.value = res.data;
})
.catch(() => {
buckets.value = [];
loading.value = false;
});
});
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!minioData.value.rowData.bucket) {
errBuckets.value = true;
return;
}
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
if (!minioData.value.rowData) return;
minioData.value.rowData!.varsJson['endpoint'] = spliceHttp(
endpointProto.value,
minioData.value.rowData!.varsJson['endpointItem'],
);
minioData.value.rowData!.varsJson['endpointItem'] = undefined;
minioData.value.rowData.vars = JSON.stringify(minioData.value.rowData!.varsJson);
loading.value = true;
if (minioData.value.title === 'create') {
await addBackup(minioData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
return;
}
await editBackup(minioData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
});
};
defineExpose({
acceptParams,
});
</script>

View File

@ -1,232 +0,0 @@
<template>
<DrawerPro v-model="drawerVisible" :header="title + $t('setting.backupAccount')" :back="handleClose" size="large">
<el-form @submit.prevent ref="formRef" v-loading="loading" label-position="top" :model="oneDriveData.rowData">
<el-form-item :label="$t('commons.table.type')" prop="type">
<el-tag>{{ $t('setting.' + oneDriveData.rowData!.type) }}</el-tag>
</el-form-item>
<el-form-item>
<el-radio-group v-model="oneDriveData.rowData!.varsJson['isCN']" @change="changeFrom">
<el-radio-button :value="false">{{ $t('setting.isNotCN') }}</el-radio-button>
<el-radio-button :value="true">{{ $t('setting.isCN') }}</el-radio-button>
</el-radio-group>
<span class="input-help">
{{ $t('setting.onedrive_helper') }}
<el-link
style="font-size: 12px; margin-left: 5px"
icon="Position"
@click="toDoc(true)"
type="primary"
>
{{ $t('firewall.quickJump') }}
</el-link>
</span>
</el-form-item>
<el-form-item :label="$t('setting.client_id')" prop="varsJson.client_id" :rules="Rules.requiredInput">
<el-input v-model.trim="oneDriveData.rowData!.varsJson['client_id']" />
</el-form-item>
<el-form-item
:label="$t('setting.client_secret')"
prop="varsJson.client_secret"
:rules="Rules.requiredInput"
>
<el-input v-model.trim="oneDriveData.rowData!.varsJson['client_secret']" />
</el-form-item>
<el-form-item :label="$t('setting.redirect_uri')" prop="varsJson.redirect_uri" :rules="Rules.requiredInput">
<el-input v-model.trim="oneDriveData.rowData!.varsJson['redirect_uri']" />
</el-form-item>
<el-form-item :label="$t('setting.code')" prop="varsJson.code" :rules="rules.driveCode">
<div class="!w-full">
<el-input
style="width: calc(100% - 80px)"
:rows="3"
type="textarea"
clearable
v-model.trim="oneDriveData.rowData!.varsJson['code']"
/>
<el-button class="append-button" @click="jumpAzure(formRef)">
{{ $t('setting.loadCode') }}
</el-button>
</div>
<span class="input-help">
{{ $t('setting.codeHelper') }}
<el-link
style="font-size: 12px; margin-left: 5px"
icon="Position"
@click="toDoc(false)"
type="primary"
>
{{ $t('firewall.quickJump') }}
</el-link>
</span>
</el-form-item>
<el-form-item :label="$t('setting.backupDir')" prop="backupPath">
<el-input clearable v-model.trim="oneDriveData.rowData!.backupPath" placeholder="/1panel" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="loading" @click="handleClose">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</DrawerPro>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { Backup } from '@/api/interface/backup';
import { addBackup, editBackup, getOneDriveInfo } from '@/api/modules/setting';
import { MsgSuccess } from '@/utils/message';
const loading = ref(false);
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
const rules = reactive({
driveCode: [{ validator: checkDriveCode, required: true, trigger: 'blur' }],
});
function checkDriveCode(rule: any, value: any, callback: any) {
if (!value) {
return callback(new Error(i18n.global.t('setting.codeWarning')));
}
const reg = /^[A-Za-z0-9_.-]+$/;
if (!reg.test(value)) {
return callback(new Error(i18n.global.t('setting.codeWarning')));
}
callback();
}
const emit = defineEmits(['search']);
const oneDriveInfo = ref();
interface DialogProps {
title: string;
rowData?: Backup.BackupInfo;
}
const title = ref<string>('');
const drawerVisible = ref(false);
const oneDriveData = ref<DialogProps>({
title: '',
});
const acceptParams = async (params: DialogProps): Promise<void> => {
oneDriveData.value = params;
oneDriveData.value.rowData.varsJson['isCN'] = oneDriveData.value.rowData.varsJson['isCN'] || false;
title.value = i18n.global.t('commons.button.' + oneDriveData.value.title);
drawerVisible.value = true;
const res = await getOneDriveInfo();
oneDriveInfo.value = res.data;
if (!oneDriveData.value.rowData.id) {
oneDriveData.value.rowData.varsJson = {
isCN: false,
client_id: res.data.client_id,
client_secret: res.data.client_secret,
redirect_uri: res.data.redirect_uri,
};
}
};
const changeFrom = () => {
if (oneDriveData.value.rowData.varsJson['isCN']) {
oneDriveData.value.rowData.varsJson = {
isCN: true,
client_id: '',
client_secret: '',
redirect_uri: '',
};
} else {
oneDriveData.value.rowData.varsJson = {
isCN: false,
client_id: oneDriveInfo.value.client_id,
client_secret: oneDriveInfo.value.client_secret,
redirect_uri: oneDriveInfo.value.redirect_uri,
};
}
};
const handleClose = () => {
emit('search');
drawerVisible.value = false;
};
const jumpAzure = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
const result = await formEl.validateField('varsJson.client_id', callback);
if (!result) {
return;
}
const result1 = await formEl.validateField('varsJson.redirect_uri', callback);
if (!result1) {
return;
}
let client_id = oneDriveData.value.rowData.varsJson['client_id'];
let redirect_uri = oneDriveData.value.rowData.varsJson['redirect_uri'];
let commonUrl = `response_type=code&client_id=${client_id}&redirect_uri=${redirect_uri}&scope=offline_access+Files.ReadWrite.All+User.Read`;
if (!oneDriveData.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');
}
};
function callback(error: any) {
if (error) {
return error.message;
} else {
return;
}
}
const toDoc = (isConf: boolean) => {
let item = isConf ? '#onedrive' : '#onedrive_1';
window.open('https://1panel.cn/docs/user_manual/settings/' + item, '_blank', 'noopener,noreferrer');
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
if (!oneDriveData.value.rowData) return;
oneDriveData.value.rowData.vars = JSON.stringify(oneDriveData.value.rowData!.varsJson);
loading.value = true;
if (oneDriveData.value.title === 'create') {
await addBackup(oneDriveData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
return;
}
await editBackup(oneDriveData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
});
};
defineExpose({
acceptParams,
});
</script>
<style lang="scss" scoped>
.append-button {
width: 80px;
background-color: var(--el-fill-color-light);
color: var(--el-color-info);
}
</style>

View File

@ -0,0 +1,552 @@
<template>
<DrawerPro v-model="drawerVisible" :header="title + $t('setting.backupAccount')" :back="handleClose" size="large">
<el-form @submit.prevent ref="formRef" v-loading="loading" label-position="top" :model="dialogData.rowData">
<el-form-item :label="$t('commons.table.name')" prop="name" :rules="Rules.requiredInput">
<el-tag v-if="dialogData.title === 'edit'">{{ dialogData.rowData!.name }}</el-tag>
<el-input v-else v-model="dialogData.rowData!.name" />
</el-form-item>
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
<el-tag v-if="dialogData.title === 'edit'">{{ $t('setting.' + dialogData.rowData!.type) }}</el-tag>
<el-select v-else v-model="dialogData.rowData!.type" @change="changeType">
<el-option :label="$t('setting.COS')" value="COS"></el-option>
<el-option :label="$t('setting.KODO')" value="KODO"></el-option>
<el-option :label="$t('setting.MINIO')" value="MINIO"></el-option>
<el-option :label="$t('setting.OneDrive')" value="OneDrive"></el-option>
<el-option :label="$t('setting.OSS')" value="OSS"></el-option>
<el-option :label="$t('setting.S3')" value="S3"></el-option>
<el-option :label="$t('setting.SFTP')" value="SFTP"></el-option>
<el-option :label="$t('setting.WebDAV')" value="WebDAV"></el-option>
</el-select>
</el-form-item>
<el-form-item v-if="hasAccessKey()" label="Access Key ID" prop="accessKey" :rules="Rules.requiredInput">
<el-input v-model.trim="dialogData.rowData!.accessKey" />
</el-form-item>
<el-form-item v-if="hasAccessKey()" label="Secret Key" prop="credential" :rules="Rules.requiredInput">
<el-input show-password clearable v-model.trim="dialogData.rowData!.credential" />
</el-form-item>
<el-form-item
v-if="dialogData.rowData!.type === 'WebDAV'"
:label="$t('setting.address')"
prop="varsJson.address"
:rules="Rules.requiredInput"
>
<el-input v-model="dialogData.rowData!.varsJson['address']" />
<span class="input-help">
{{ $t('setting.WebDAVAlist') }}
<el-link
style="font-size: 12px; margin-left: 5px"
icon="Position"
@click="toWebDAVDoc()"
type="primary"
>
{{ $t('firewall.quickJump') }}
</el-link>
</span>
</el-form-item>
<div v-if="dialogData.rowData!.type === 'SFTP'">
<el-form-item :label="$t('setting.address')" prop="varsJson.address" :rules="Rules.host">
<el-input v-model.trim="dialogData.rowData!.varsJson['address']" />
</el-form-item>
<el-form-item :label="$t('commons.table.port')" prop="varsJson.port" :rules="[Rules.port]">
<el-input-number :min="0" :max="65535" v-model.number="dialogData.rowData!.varsJson['port']" />
</el-form-item>
</div>
<div v-if="hasPassword()">
<el-form-item :label="$t('commons.login.username')" prop="accessKey" :rules="[Rules.requiredInput]">
<el-input v-model.trim="dialogData.rowData!.accessKey" />
</el-form-item>
<el-form-item :label="$t('commons.login.password')" prop="credential" :rules="[Rules.requiredInput]">
<el-input type="password" clearable show-password v-model.trim="dialogData.rowData!.credential" />
</el-form-item>
</div>
<el-form-item
v-if="dialogData.rowData!.type !== 'LOCAL' && dialogData.rowData!.type !== 'OneDrive'"
prop="rememberAuth"
>
<el-checkbox v-model="dialogData.rowData!.rememberAuth">
{{ $t('terminal.rememberPassword') }}
</el-checkbox>
</el-form-item>
<el-form-item
v-if="dialogData.rowData!.type === 'COS'"
label="Region"
prop="varsJson.region"
:rules="Rules.requiredInput"
>
<el-checkbox v-model="regionInput" :label="$t('container.input')" />
<el-select v-if="!regionInput" v-model="dialogData.rowData!.varsJson['region']" filterable clearable>
<el-option v-for="item in cities" :key="item.value" :label="item.label" :value="item.value">
<span class="float-left">{{ item.label }}</span>
<span class="option-help">
{{ item.value }}
</span>
</el-option>
</el-select>
<el-input v-else v-model.trim="dialogData.rowData!.varsJson['region']" />
</el-form-item>
<el-form-item
v-if="dialogData.rowData!.type === 'S3'"
label="Region"
prop="varsJson.region"
:rules="Rules.requiredInput"
>
<el-input v-model.trim="dialogData.rowData!.varsJson['region']" />
</el-form-item>
<el-form-item
v-if="hasAccessKey()"
:label="dialogData.rowData!.type === 'KODO' ? $t('setting.domain') : 'Endpoint'"
prop="varsJson.endpointItem"
:rules="Rules.requiredInput"
>
<el-input v-model.trim="dialogData.rowData!.varsJson['endpointItem']">
<template #prepend>
<el-select v-model.trim="domainProto" class="p-w-100">
<el-option label="http" value="http" />
<el-option label="https" value="https" />
</el-select>
</template>
</el-input>
</el-form-item>
<el-form-item v-if="hasAccessKey()" label="Bucket" prop="bucket">
<el-select @change="errBuckets = false" class="!w-4/5" v-model="dialogData.rowData!.bucket">
<el-option v-for="item in buckets" :key="item" :value="item" />
</el-select>
<el-button class="!w-1/5" plain @click="getBuckets(formRef)">
{{ $t('setting.loadBucket') }}
</el-button>
<span v-if="errBuckets" class="input-error">{{ $t('commons.rule.requiredSelect') }}</span>
</el-form-item>
<el-form-item
v-if="dialogData.rowData!.type === 'COS'"
:label="$t('setting.scType')"
prop="varsJson.scType"
:rules="[Rules.requiredSelect]"
>
<el-select v-model="dialogData.rowData!.varsJson['scType']">
<el-option value="Standard" :label="$t('setting.scStandard')" />
<el-option value="Standard_IA" :label="$t('setting.scStandard_IA')" />
<el-option value="Archive" :label="$t('setting.scArchive')" />
<el-option value="Deep_Archive" :label="$t('setting.scDeep_Archive')" />
</el-select>
<el-alert
v-if="dialogData.rowData!.varsJson['scType'] === 'Archive' || dialogData.rowData!.varsJson['scType'] === 'Deep_Archive'"
class="mt-2.5"
:closable="false"
type="warning"
:title="$t('setting.archiveHelper')"
/>
</el-form-item>
<el-form-item
v-if="dialogData.rowData!.type === 'OSS'"
:label="$t('setting.scType')"
prop="varsJson.scType"
:rules="[Rules.requiredSelect]"
>
<el-select v-model="dialogData.rowData!.varsJson['scType']">
<el-option value="Standard" :label="$t('setting.scStandard')" />
<el-option value="IA" :label="$t('setting.scStandard_IA')" />
<el-option value="Archive" :label="$t('setting.scArchive')" />
<el-option value="ColdArchive" :label="$t('setting.scDeep_Archive')" />
</el-select>
<el-alert
v-if="dialogData.rowData!.varsJson['scType'] === 'Archive' || dialogData.rowData!.varsJson['scType'] === 'ColdArchive'"
class="mt-2.5"
:closable="false"
type="warning"
:title="$t('setting.archiveHelper')"
/>
</el-form-item>
<el-form-item
v-if="dialogData.rowData!.type === 'S3'"
:label="$t('setting.scType')"
prop="varsJson.scType"
:rules="[Rules.requiredSelect]"
>
<el-select v-model="dialogData.rowData!.varsJson['scType']">
<el-option value="STANDARD" :label="$t('setting.scStandard')" />
<el-option value="STANDARD_IA" :label="$t('setting.scStandard_IA')" />
<el-option value="GLACIER" :label="$t('setting.scArchive')" />
<el-option value="DEEP_ARCHIVE" :label="$t('setting.scDeep_Archive')" />
</el-select>
<el-alert
v-if="dialogData.rowData!.varsJson['scType'] === 'GLACIER' || dialogData.rowData!.varsJson['scType'] === 'DEEP_ARCHIVE'"
class="mt-2.5"
:closable="false"
type="warning"
:title="$t('setting.archiveHelper')"
/>
</el-form-item>
<el-form-item
v-if="dialogData.rowData!.type === 'KODO'"
:label="$t('cronjob.requestExpirationTime')"
prop="varsJson.timeout"
>
<el-input-number
style="width: 200px"
:min="1"
step-strictly
:step="1"
v-model.number="dialogData.rowData!.varsJson['timeout']"
></el-input-number>
</el-form-item>
<div v-if="dialogData.rowData!.type === 'OneDrive'">
<el-form-item>
<el-radio-group v-model="dialogData.rowData!.varsJson['isCN']" @change="changeFrom">
<el-radio-button :value="false">{{ $t('setting.isNotCN') }}</el-radio-button>
<el-radio-button :value="true">{{ $t('setting.isCN') }}</el-radio-button>
</el-radio-group>
<span class="input-help">
{{ $t('setting.onedrive_helper') }}
<el-link
style="font-size: 12px; margin-left: 5px"
icon="Position"
@click="toDoc(true)"
type="primary"
>
{{ $t('firewall.quickJump') }}
</el-link>
</span>
</el-form-item>
<el-form-item :label="$t('setting.client_id')" prop="varsJson.client_id" :rules="Rules.requiredInput">
<el-input v-model.trim="dialogData.rowData!.varsJson['client_id']" />
</el-form-item>
<el-form-item
:label="$t('setting.client_secret')"
prop="varsJson.client_secret"
:rules="Rules.requiredInput"
>
<el-input v-model.trim="dialogData.rowData!.varsJson['client_secret']" />
</el-form-item>
<el-form-item
:label="$t('setting.redirect_uri')"
prop="varsJson.redirect_uri"
:rules="Rules.requiredInput"
>
<el-input v-model.trim="dialogData.rowData!.varsJson['redirect_uri']" />
</el-form-item>
<el-form-item :label="$t('setting.code')" prop="varsJson.code" :rules="rules.driveCode">
<div class="!w-full">
<el-input
style="width: calc(100% - 80px)"
:rows="3"
type="textarea"
clearable
v-model.trim="dialogData.rowData!.varsJson['code']"
/>
<el-button class="append-button" @click="jumpAzure(formRef)">
{{ $t('setting.loadCode') }}
</el-button>
</div>
<span class="input-help">
{{ $t('setting.codeHelper') }}
<el-link
style="font-size: 12px; margin-left: 5px"
icon="Position"
@click="toDoc(false)"
type="primary"
>
{{ $t('firewall.quickJump') }}
</el-link>
</span>
</el-form-item>
</div>
<el-form-item v-if="hasBackDir()" :label="$t('setting.backupDir')" prop="backupPath">
<el-input clearable v-model.trim="dialogData.rowData!.backupPath" placeholder="/1panel" />
</el-form-item>
<el-form-item
v-if="dialogData.rowData!.type === 'SFTP'"
:label="$t('setting.backupDir')"
prop="bucket"
:rules="[Rules.requiredInput]"
>
<el-input v-model.trim="dialogData.rowData!.bucket" />
</el-form-item>
<el-form-item
v-if="dialogData.rowData!.type === 'LOCAL'"
:label="$t('setting.backupDir')"
prop="varsJson['dir']"
:rules="Rules.requiredInput"
>
<el-input v-model="dialogData.rowData!.varsJson['dir']">
<template #prepend>
<FileList @choose="loadDir" :dir="true"></FileList>
</template>
</el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="loading" @click="handleClose">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</DrawerPro>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { Backup } from '@/api/interface/backup';
import { addBackup, editBackup, getOneDriveInfo, listBucket } from '@/api/modules/setting';
import { cities } from './../helper';
import { deepCopy, spliceHttp, splitHttp } from '@/utils/util';
import { MsgSuccess } from '@/utils/message';
import { Base64 } from 'js-base64';
const loading = ref(false);
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
const buckets = ref();
const errBuckets = ref();
const oneDriveInfo = ref();
const regionInput = ref();
const domainProto = ref('http');
const emit = defineEmits(['search']);
const rules = reactive({
driveCode: [{ validator: checkDriveCode, required: true, trigger: 'blur' }],
});
function checkDriveCode(rule: any, value: any, callback: any) {
if (!value) {
return callback(new Error(i18n.global.t('setting.codeWarning')));
}
const reg = /^[A-Za-z0-9_.-]+$/;
if (!reg.test(value)) {
return callback(new Error(i18n.global.t('setting.codeWarning')));
}
callback();
}
interface DialogProps {
title: string;
rowData?: Backup.BackupInfo;
}
const title = ref<string>('');
const drawerVisible = ref(false);
const dialogData = ref<DialogProps>({
title: '',
});
const acceptParams = (params: DialogProps): void => {
dialogData.value = params;
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
if (dialogData.value.title === 'create') {
dialogData.value.rowData!.type = 'OSS';
changeType();
drawerVisible.value = true;
return;
}
buckets.value = [];
if (hasAccessKey()) {
let itemJson = dialogData.value.rowData!.varsJson['endpoint'];
if (dialogData.value.rowData!.type === 'KODO') {
itemJson = dialogData.value.rowData!.varsJson['domain'];
}
let httpItem = splitHttp(itemJson);
dialogData.value.rowData!.varsJson['endpointItem'] = httpItem.url;
domainProto.value = httpItem.proto;
}
if (dialogData.value.rowData!.rememberAuth) {
dialogData.value.rowData!.accessKey = Base64.decode(dialogData.value.rowData!.accessKey);
dialogData.value.rowData!.credential = Base64.decode(dialogData.value.rowData!.credential);
}
if (dialogData.value.rowData!.varsJson['timeout'] === undefined) {
dialogData.value.rowData!.varsJson['timeout'] = 1;
}
drawerVisible.value = true;
};
const toDoc = (isConf: boolean) => {
let item = isConf ? '#onedrive' : '#onedrive_1';
window.open('https://1panel.cn/docs/user_manual/settings/' + item, '_blank', 'noopener,noreferrer');
};
const toWebDAVDoc = () => {
window.open('https://1panel.cn/docs/user_manual/settings/#webdav-alist', '_blank', 'noopener,noreferrer');
};
const jumpAzure = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
const result = await formEl.validateField('varsJson.client_id', callback);
if (!result) {
return;
}
const result1 = await formEl.validateField('varsJson.redirect_uri', callback);
if (!result1) {
return;
}
let client_id = dialogData.value.rowData.varsJson['client_id'];
let redirect_uri = dialogData.value.rowData.varsJson['redirect_uri'];
let commonUrl = `response_type=code&client_id=${client_id}&redirect_uri=${redirect_uri}&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');
}
};
function callback(error: any) {
if (error) {
return error.message;
} else {
return;
}
}
const hasAccessKey = () => {
let itemType = dialogData.value.rowData!.type;
return itemType === 'COS' || itemType === 'KODO' || itemType === 'MINIO' || itemType === 'OSS' || itemType === 'S3';
};
const hasPassword = () => {
let itemType = dialogData.value.rowData!.type;
return itemType === 'SFTP' || itemType === 'WebDAV';
};
const hasBackDir = () => {
let itemType = dialogData.value.rowData!.type;
return itemType !== 'LOCAL' && itemType !== 'SFTP';
};
const loadDir = async (path: string) => {
dialogData.value.rowData!.varsJson['dir'] = path;
};
const changeType = async () => {
buckets.value = [];
dialogData.value.rowData!.varsJson = {};
dialogData.value.rowData!.rememberAuth = false;
switch (dialogData.value.rowData!.type) {
case 'COS':
case 'OSS':
case 'S3':
dialogData.value.rowData.varsJson['scType'] = 'Standard';
break;
case 'KODO':
dialogData.value.rowData!.varsJson['timeout'] = 1;
break;
case 'OneDrive':
dialogData.value.rowData.varsJson['isCN'] = false;
const res = await getOneDriveInfo();
oneDriveInfo.value = res.data;
if (!dialogData.value.rowData.id) {
dialogData.value.rowData.varsJson = {
isCN: false,
client_id: res.data.client_id,
client_secret: res.data.client_secret,
redirect_uri: res.data.redirect_uri,
};
}
case 'SFTP':
dialogData.value.rowData.varsJson['port'] = 22;
}
};
const changeFrom = () => {
if (dialogData.value.rowData.varsJson['isCN']) {
dialogData.value.rowData.varsJson = {
isCN: true,
client_id: '',
client_secret: '',
redirect_uri: '',
};
} else {
dialogData.value.rowData.varsJson = {
isCN: false,
client_id: oneDriveInfo.value.client_id,
client_secret: oneDriveInfo.value.client_secret,
redirect_uri: oneDriveInfo.value.redirect_uri,
};
}
};
const handleClose = () => {
emit('search');
drawerVisible.value = false;
};
const getBuckets = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
loading.value = true;
let item = deepCopy(dialogData.value.rowData!.varsJson);
if (dialogData.value.rowData!.type === 'KODO') {
item['domain'] = spliceHttp(domainProto.value, dialogData.value.rowData!.varsJson['endpointItem']);
} else {
item['endpoint'] = spliceHttp(domainProto.value, dialogData.value.rowData!.varsJson['endpointItem']);
}
item['endpointItem'] = undefined;
listBucket({
type: dialogData.value.rowData!.type,
vars: JSON.stringify(item),
accessKey: dialogData.value.rowData!.accessKey,
credential: dialogData.value.rowData!.credential,
})
.then((res) => {
loading.value = false;
buckets.value = res.data;
})
.catch(() => {
buckets.value = [];
loading.value = false;
});
});
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (hasAccessKey() && !dialogData.value.rowData.bucket) {
errBuckets.value = true;
return;
}
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
if (!dialogData.value.rowData) return;
if (hasAccessKey()) {
let itemEndpoint = spliceHttp(domainProto.value, dialogData.value.rowData!.varsJson['endpointItem']);
if (dialogData.value.rowData!.type === 'KODO') {
dialogData.value.rowData!.varsJson['domain'] = itemEndpoint;
} else {
dialogData.value.rowData!.varsJson['endpoint'] = itemEndpoint;
}
dialogData.value.rowData!.varsJson['endpointItem'] = undefined;
}
dialogData.value.rowData.vars = JSON.stringify(dialogData.value.rowData!.varsJson);
loading.value = true;
if (dialogData.value.title === 'create') {
await addBackup(dialogData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
return;
}
await editBackup(dialogData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
});
};
defineExpose({
acceptParams,
});
</script>
<style scoped lang="scss">
.option-help {
float: right;
font-size: 12px;
word-break: break-all;
color: #8f959e;
}
</style>

View File

@ -1,178 +0,0 @@
<template>
<DrawerPro v-model="drawerVisible" :header="title + $t('setting.backupAccount')" :back="handleClose" size="large">
<el-form @submit.prevent ref="formRef" v-loading="loading" label-position="top" :model="ossData.rowData">
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
<el-tag>{{ $t('setting.' + ossData.rowData!.type) }}</el-tag>
</el-form-item>
<el-form-item label="Access Key ID" prop="accessKey" :rules="Rules.requiredInput">
<el-input v-model.trim="ossData.rowData!.accessKey" />
</el-form-item>
<el-form-item label="Secret Key" prop="credential" :rules="Rules.requiredInput">
<el-input show-password clearable v-model.trim="ossData.rowData!.credential" />
</el-form-item>
<el-form-item label="Endpoint" prop="varsJson.endpointItem" :rules="Rules.requiredInput">
<el-input v-model="ossData.rowData!.varsJson['endpointItem']">
<template #prepend>
<el-select v-model.trim="endpointProto" class="!w-full">
<el-option label="http" value="http" />
<el-option label="https" value="https" />
</el-select>
</template>
</el-input>
</el-form-item>
<el-form-item label="Bucket" prop="bucket">
<el-select @change="errBuckets = false" class="!w-4/5" v-model="ossData.rowData!.bucket">
<el-option v-for="item in buckets" :key="item" :value="item" />
</el-select>
<el-button class="!w-1/5" plain @click="getBuckets(formRef)">
{{ $t('setting.loadBucket') }}
</el-button>
<span v-if="errBuckets" class="input-error">{{ $t('commons.rule.requiredSelect') }}</span>
</el-form-item>
<el-form-item :label="$t('setting.scType')" prop="varsJson.scType" :rules="[Rules.requiredSelect]">
<el-select v-model="ossData.rowData!.varsJson['scType']">
<el-option value="Standard" :label="$t('setting.scStandard')" />
<el-option value="IA" :label="$t('setting.scStandard_IA')" />
<el-option value="Archive" :label="$t('setting.scArchive')" />
<el-option value="ColdArchive" :label="$t('setting.scDeep_Archive')" />
</el-select>
<el-alert
v-if="ossData.rowData!.varsJson['scType'] === 'Archive' || ossData.rowData!.varsJson['scType'] === 'ColdArchive'"
class="mt-2.5"
:closable="false"
type="warning"
:title="$t('setting.archiveHelper')"
/>
</el-form-item>
<el-form-item :label="$t('setting.backupDir')" prop="backupPath">
<el-input clearable v-model.trim="ossData.rowData!.backupPath" placeholder="/1panel" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="loading" @click="handleClose">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</DrawerPro>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { Backup } from '@/api/interface/backup';
import { addBackup, editBackup, listBucket } from '@/api/modules/setting';
import { deepCopy, spliceHttp, splitHttp } from '@/utils/util';
import { MsgSuccess } from '@/utils/message';
const loading = ref(false);
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
const buckets = ref();
const errBuckets = ref();
const endpointProto = ref('http');
const emit = defineEmits(['search']);
interface DialogProps {
title: string;
rowData?: Backup.BackupInfo;
}
const title = ref<string>('');
const drawerVisible = ref(false);
const ossData = ref<DialogProps>({
title: '',
});
const acceptParams = (params: DialogProps): void => {
buckets.value = [];
ossData.value = params;
if (params.title === 'create' || (params.title === 'edit' && !ossData.value.rowData.varsJson['scType'])) {
ossData.value.rowData.varsJson['scType'] = 'Standard';
}
if (ossData.value.title === 'edit') {
let httpItem = splitHttp(ossData.value.rowData!.varsJson['endpoint']);
ossData.value.rowData!.varsJson['endpointItem'] = httpItem.url;
endpointProto.value = httpItem.proto;
}
title.value = i18n.global.t('commons.button.' + ossData.value.title);
drawerVisible.value = true;
};
const handleClose = () => {
emit('search');
drawerVisible.value = false;
};
const getBuckets = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
loading.value = true;
let item = deepCopy(ossData.value.rowData!.varsJson);
item['endpoint'] = spliceHttp(endpointProto.value, ossData.value.rowData!.varsJson['endpointItem']);
listBucket({
type: ossData.value.rowData!.type,
vars: JSON.stringify(item),
accessKey: ossData.value.rowData!.accessKey,
credential: ossData.value.rowData!.credential,
})
.then((res) => {
loading.value = false;
buckets.value = res.data;
})
.catch(() => {
buckets.value = [];
loading.value = false;
});
});
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!ossData.value.rowData.bucket) {
errBuckets.value = true;
return;
}
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
if (!ossData.value.rowData) return;
ossData.value.rowData!.varsJson['endpoint'] = spliceHttp(
endpointProto.value,
ossData.value.rowData!.varsJson['endpointItem'],
);
ossData.value.rowData.vars = JSON.stringify(ossData.value.rowData!.varsJson);
loading.value = true;
if (ossData.value.title === 'create') {
await addBackup(ossData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
return;
}
await editBackup(ossData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
});
};
defineExpose({
acceptParams,
});
</script>

View File

@ -1,181 +0,0 @@
<template>
<DrawerPro v-model="drawerVisible" :header="title + $t('setting.backupAccount')" :back="handleClose" size="large">
<el-form @submit.prevent ref="formRef" v-loading="loading" label-position="top" :model="s3Data.rowData">
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
<el-tag>{{ $t('setting.' + s3Data.rowData!.type) }}</el-tag>
</el-form-item>
<el-form-item label="Access Key ID" prop="accessKey" :rules="Rules.requiredInput">
<el-input v-model.trim="s3Data.rowData!.accessKey" />
</el-form-item>
<el-form-item label="Secret Key" prop="credential" :rules="Rules.requiredInput">
<el-input show-password clearable v-model.trim="s3Data.rowData!.credential" />
</el-form-item>
<el-form-item label="Region" prop="varsJson.region" :rules="Rules.requiredInput">
<el-input pro v-model.trim="s3Data.rowData!.varsJson['region']" />
</el-form-item>
<el-form-item label="Endpoint" prop="varsJson.endpointItem" :rules="Rules.requiredInput">
<el-input v-model="s3Data.rowData!.varsJson['endpointItem']">
<template #prepend>
<el-select v-model.trim="endpointProto" class="p-w-100">
<el-option label="http" value="http" />
<el-option label="https" value="https" />
</el-select>
</template>
</el-input>
</el-form-item>
<el-form-item label="Bucket" prop="bucket">
<el-select @change="errBuckets = false" class="!w-4/5" v-model="s3Data.rowData!.bucket">
<el-option v-for="item in buckets" :key="item" :value="item" />
</el-select>
<el-button class="!w-1/5" plain @click="getBuckets(formRef)">
{{ $t('setting.loadBucket') }}
</el-button>
<span v-if="errBuckets" class="input-error">{{ $t('commons.rule.requiredSelect') }}</span>
</el-form-item>
<el-form-item :label="$t('setting.scType')" prop="varsJson.scType" :rules="[Rules.requiredSelect]">
<el-select v-model="s3Data.rowData!.varsJson['scType']">
<el-option value="STANDARD" :label="$t('setting.scStandard')" />
<el-option value="STANDARD_IA" :label="$t('setting.scStandard_IA')" />
<el-option value="GLACIER" :label="$t('setting.scArchive')" />
<el-option value="DEEP_ARCHIVE" :label="$t('setting.scDeep_Archive')" />
</el-select>
<el-alert
v-if="s3Data.rowData!.varsJson['scType'] === 'Archive' || s3Data.rowData!.varsJson['scType'] === 'ColdArchive'"
class="mt-2.5"
:closable="false"
type="warning"
:title="$t('setting.archiveHelper')"
/>
</el-form-item>
<el-form-item :label="$t('setting.backupDir')" prop="backupPath">
<el-input clearable v-model.trim="s3Data.rowData!.backupPath" placeholder="/1panel" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="loading" @click="handleClose">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</DrawerPro>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { Backup } from '@/api/interface/backup';
import { addBackup, editBackup, listBucket } from '@/api/modules/setting';
import { deepCopy, spliceHttp, splitHttp } from '@/utils/util';
import { MsgSuccess } from '@/utils/message';
const loading = ref(false);
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
const buckets = ref();
const errBuckets = ref();
const endpointProto = ref('http');
const emit = defineEmits(['search']);
interface DialogProps {
title: string;
rowData?: Backup.BackupInfo;
}
const title = ref<string>('');
const drawerVisible = ref(false);
const s3Data = ref<DialogProps>({
title: '',
});
const acceptParams = (params: DialogProps): void => {
buckets.value = [];
s3Data.value = params;
if (params.title === 'create' || (params.title === 'edit' && !s3Data.value.rowData.varsJson['scType'])) {
s3Data.value.rowData.varsJson['scType'] = 'STANDARD';
}
if (s3Data.value.title === 'edit') {
let httpItem = splitHttp(s3Data.value.rowData!.varsJson['endpoint']);
s3Data.value.rowData!.varsJson['endpointItem'] = httpItem.url;
endpointProto.value = httpItem.proto;
}
title.value = i18n.global.t('commons.button.' + s3Data.value.title);
drawerVisible.value = true;
};
const handleClose = () => {
emit('search');
drawerVisible.value = false;
};
const getBuckets = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
loading.value = true;
let item = deepCopy(s3Data.value.rowData!.varsJson);
item['endpoint'] = spliceHttp(endpointProto.value, s3Data.value.rowData!.varsJson['endpointItem']);
listBucket({
type: s3Data.value.rowData!.type,
vars: JSON.stringify(item),
accessKey: s3Data.value.rowData!.accessKey,
credential: s3Data.value.rowData!.credential,
})
.then((res) => {
loading.value = false;
buckets.value = res.data;
})
.catch(() => {
buckets.value = [];
loading.value = false;
});
});
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!s3Data.value.rowData.bucket) {
errBuckets.value = true;
return;
}
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
if (!s3Data.value.rowData) return;
s3Data.value.rowData!.varsJson['endpoint'] = spliceHttp(
endpointProto.value,
s3Data.value.rowData!.varsJson['endpointItem'],
);
s3Data.value.rowData.vars = JSON.stringify(s3Data.value.rowData!.varsJson);
loading.value = true;
if (s3Data.value.title === 'create') {
await addBackup(s3Data.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
return;
}
await editBackup(s3Data.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
});
};
defineExpose({
acceptParams,
});
</script>

View File

@ -1,109 +0,0 @@
<template>
<DrawerPro v-model="drawerVisible" :header="title + $t('setting.backupAccount')" :back="handleClose" size="large">
<el-form @submit.prevent ref="formRef" v-loading="loading" label-position="top" :model="sftpData.rowData">
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
<el-tag>{{ $t('setting.' + sftpData.rowData!.type) }}</el-tag>
</el-form-item>
<el-form-item :label="$t('setting.address')" prop="varsJson.address" :rules="Rules.host">
<el-input v-model.trim="sftpData.rowData!.varsJson['address']" />
</el-form-item>
<el-form-item :label="$t('commons.table.port')" prop="varsJson.port" :rules="[Rules.port]">
<el-input-number :min="0" :max="65535" v-model.number="sftpData.rowData!.varsJson['port']" />
</el-form-item>
<el-form-item :label="$t('commons.login.username')" prop="accessKey" :rules="[Rules.requiredInput]">
<el-input v-model.trim="sftpData.rowData!.accessKey" />
</el-form-item>
<el-form-item :label="$t('commons.login.password')" prop="credential" :rules="[Rules.requiredInput]">
<el-input type="password" clearable show-password v-model.trim="sftpData.rowData!.credential" />
</el-form-item>
<el-form-item :label="$t('setting.backupDir')" prop="bucket" :rules="[Rules.requiredInput]">
<el-input v-model.trim="sftpData.rowData!.bucket" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="loading" @click="handleClose">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</DrawerPro>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { Backup } from '@/api/interface/backup';
import { addBackup, editBackup } from '@/api/modules/setting';
import { MsgSuccess } from '@/utils/message';
const loading = ref(false);
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
const emit = defineEmits(['search']);
interface DialogProps {
title: string;
rowData?: Backup.BackupInfo;
getTableList?: () => Promise<any>;
}
const title = ref<string>('');
const drawerVisible = ref(false);
const sftpData = ref<DialogProps>({
title: '',
});
const acceptParams = (params: DialogProps): void => {
sftpData.value = params;
if (sftpData.value.title === 'create') {
sftpData.value.rowData.varsJson['port'] = 22;
}
title.value = i18n.global.t('commons.button.' + sftpData.value.title);
drawerVisible.value = true;
};
const handleClose = () => {
emit('search');
drawerVisible.value = false;
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
if (!sftpData.value.rowData) return;
sftpData.value.rowData.vars = JSON.stringify(sftpData.value.rowData!.varsJson);
loading.value = true;
if (sftpData.value.title === 'create') {
await addBackup(sftpData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
return;
}
await editBackup(sftpData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
});
};
defineExpose({
acceptParams,
});
</script>

View File

@ -1,113 +0,0 @@
<template>
<DrawerPro v-model="drawerVisible" :header="title + $t('setting.backupAccount')" :back="handleClose" size="large">
<el-form @submit.prevent ref="formRef" v-loading="loading" label-position="top" :model="webdavData.rowData">
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
<el-tag>{{ $t('setting.' + webdavData.rowData!.type) }}</el-tag>
</el-form-item>
<el-form-item :label="$t('setting.address')" prop="varsJson.address" :rules="Rules.requiredInput">
<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')" prop="accessKey" :rules="[Rules.requiredInput]">
<el-input v-model.trim="webdavData.rowData!.accessKey" />
</el-form-item>
<el-form-item :label="$t('commons.login.password')" prop="credential" :rules="[Rules.requiredInput]">
<el-input type="password" clearable show-password v-model.trim="webdavData.rowData!.credential" />
</el-form-item>
<el-form-item :label="$t('setting.backupDir')" prop="bucket" :rules="[Rules.requiredInput]">
<el-input v-model.trim="webdavData.rowData!.bucket" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="loading" @click="handleClose">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="loading" type="primary" @click="onSubmit(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</DrawerPro>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { Backup } from '@/api/interface/backup';
import { addBackup, editBackup } from '@/api/modules/setting';
import { MsgSuccess } from '@/utils/message';
const loading = ref(false);
type FormInstance = InstanceType<typeof ElForm>;
const formRef = ref<FormInstance>();
const emit = defineEmits(['search']);
interface DialogProps {
title: string;
rowData?: Backup.BackupInfo;
}
const title = ref<string>('');
const drawerVisible = ref(false);
const webdavData = ref<DialogProps>({
title: '',
});
const acceptParams = (params: DialogProps): void => {
webdavData.value = params;
title.value = i18n.global.t('commons.button.' + webdavData.value.title);
drawerVisible.value = true;
};
const handleClose = () => {
emit('search');
drawerVisible.value = false;
};
const toDoc = () => {
window.open('https://1panel.cn/docs/user_manual/settings/#webdav-alist', '_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.vars = JSON.stringify(webdavData.value.rowData!.varsJson);
loading.value = true;
if (webdavData.value.title === 'create') {
await addBackup(webdavData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
return;
}
await editBackup(webdavData.value.rowData)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
});
};
defineExpose({
acceptParams,
});
</script>

33
go.mod
View File

@ -3,6 +3,8 @@ module github.com/1Panel-dev/1Panel
go 1.22.4
require (
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/aws/aws-sdk-go v1.55.5
github.com/dgraph-io/badger/v4 v4.2.0
github.com/fsnotify/fsnotify v1.7.0
github.com/gin-contrib/gzip v1.0.1
@ -10,23 +12,29 @@ require (
github.com/glebarez/sqlite v1.11.0
github.com/go-gormigrate/gormigrate/v2 v2.1.2
github.com/go-playground/validator/v10 v10.22.0
github.com/goh-chunlin/go-onedrive v1.1.1
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/google/uuid v1.6.0
github.com/jinzhu/copier v0.4.0
github.com/minio/minio-go/v7 v7.0.74
github.com/mojocn/base64Captcha v1.3.6
github.com/nicksnyder/go-i18n/v2 v2.4.0
github.com/pkg/errors v0.9.1
github.com/pkg/sftp v1.13.6
github.com/qiniu/go-sdk/v7 v7.21.1
github.com/robfig/cron/v3 v3.0.1
github.com/sirupsen/logrus v1.9.3
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
github.com/studio-b12/gowebdav v0.9.0
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.3
github.com/tencentyun/cos-go-sdk-v5 v0.7.54
github.com/xlzd/gotp v0.1.0
golang.org/x/crypto v0.23.0
golang.org/x/crypto v0.24.0
golang.org/x/oauth2 v0.18.0
golang.org/x/sys v0.22.0
golang.org/x/term v0.22.0
golang.org/x/text v0.16.0
@ -35,12 +43,15 @@ require (
)
require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/clbanning/mxj v1.8.4 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
@ -48,13 +59,15 @@ require (
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/glog v1.0.0 // indirect
@ -62,24 +75,31 @@ require (
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.3 // indirect
github.com/google/flatbuffers v1.12.1 // indirect
github.com/google/go-querystring v1.0.0 // indirect
github.com/h2non/filetype v1.1.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.2 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/matishsiao/goInfo v0.0.0-20210923090445-da2e3fa8d45f // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mozillazg/go-httpheader v0.2.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
@ -95,8 +115,11 @@ require (
golang.org/x/arch v0.8.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/image v0.13.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

360
go.sum
View File

@ -1,13 +1,53 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 h1:7dONQ3WNZ1zy960TmkxJPuwoolZwL7xKtpcM04MBnt4=
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI=
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
@ -16,6 +56,11 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
@ -24,6 +69,7 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@ -32,6 +78,7 @@ github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8Bzu
github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@ -57,8 +104,13 @@ github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9g
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gormigrate/gormigrate/v2 v2.1.2 h1:F/d1hpHbRAvKezziV2CC5KUE82cVe9zTgHSBoOOZ4CY=
github.com/go-gormigrate/gormigrate/v2 v2.1.2/go.mod h1:9nHVX6z3FCMCQPA7PThGcA55t22yKQfK/Dnsf5i7hUo=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
@ -69,18 +121,28 @@ github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
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/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
@ -88,43 +150,83 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/grafov/m3u8 v0.12.0/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080=
github.com/h2non/filetype v1.1.1 h1:xvOwnXKAckvtLWsN398qS9QhlxlnVXBjXBydK2/UFB4=
github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
@ -133,27 +235,37 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
@ -162,8 +274,15 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matishsiao/goInfo v0.0.0-20210923090445-da2e3fa8d45f h1:B0OD7nYl2FPQEVrw8g2uyc1lGEzNbvrKh7fspGZcbvY=
github.com/matishsiao/goInfo v0.0.0-20210923090445-da2e3fa8d45f/go.mod h1:aEt7p9Rvh67BYApmZwNDPpgircTO2kgdmDUoF/1QmwA=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.74 h1:fTo/XlPBTSpo3BAMshlwKL5RspXRv9us5UeHEGYCFe0=
github.com/minio/minio-go/v7 v7.0.74/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -173,12 +292,14 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mojocn/base64Captcha v1.3.6 h1:gZEKu1nsKpttuIAQgWHO+4Mhhls8cAKyiV2Ew03H+Tw=
github.com/mojocn/base64Captcha v1.3.6/go.mod h1:i5CtHvm+oMbj1UzEPXaA8IH/xHFZ3DGY3Wh3dBpZ28E=
github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ=
github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
@ -187,13 +308,22 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
github.com/qiniu/go-sdk/v7 v7.21.1 h1:D/IjVOlg5pTw0jeDjqTo6H5QM73Obb1AYfPOHmIFN+Q=
github.com/qiniu/go-sdk/v7 v7.21.1/go.mod h1:8EM2awITynlem2VML2dXGHkMYP2UyECsGLOdp6yMpco=
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
@ -229,6 +359,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
@ -237,15 +369,26 @@ github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+z
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=
github.com/tencentyun/cos-go-sdk-v5 v0.7.54 h1:FRamEhNBbSeggyYfWfzFejTLftgbICocSYFk4PKTSV4=
github.com/tencentyun/cos-go-sdk-v5 v0.7.54/go.mod h1:UN+VdbCl1hg+kKi5RXqZgaP+Boqfmk+D04GRc4XFk70=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
@ -256,20 +399,47 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUu
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@ -278,11 +448,30 @@ golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@ -291,13 +480,24 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -305,11 +505,34 @@ golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -317,6 +540,7 @@ golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -325,22 +549,65 @@ golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
@ -350,15 +617,71 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@ -368,6 +691,7 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
@ -375,21 +699,30 @@ google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFW
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
@ -399,4 +732,7 @@ modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=