diff --git a/backend/app/api/v1/backup.go b/backend/app/api/v1/backup.go new file mode 100644 index 000000000..3f21a25df --- /dev/null +++ b/backend/app/api/v1/backup.go @@ -0,0 +1,112 @@ +package v1 + +import ( + "github.com/1Panel-dev/1Panel/app/api/v1/helper" + "github.com/1Panel-dev/1Panel/app/dto" + "github.com/1Panel-dev/1Panel/constant" + "github.com/1Panel-dev/1Panel/global" + "github.com/gin-gonic/gin" +) + +func (b *BaseApi) CreateBackup(c *gin.Context) { + var req dto.BackupOperate + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := global.VALID.Struct(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := backupService.Create(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} + +func (b *BaseApi) ListBuckets(c *gin.Context) { + var req dto.ForBuckets + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := global.VALID.Struct(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + buckets, err := backupService.GetBuckets(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, buckets) +} + +func (b *BaseApi) DeleteBackup(c *gin.Context) { + var req dto.BatchDeleteReq + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := global.VALID.Struct(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + + if err := backupService.BatchDelete(req.Ids); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} + +func (b *BaseApi) UpdateBackup(c *gin.Context) { + var req dto.BackupOperate + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := global.VALID.Struct(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + id, err := helper.GetParamID(c) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + + upMap := make(map[string]interface{}) + upMap["bucket"] = req.Bucket + upMap["credential"] = req.Credential + upMap["vars"] = req.Vars + if err := backupService.Update(id, upMap); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} + +func (b *BaseApi) PageBackup(c *gin.Context) { + var req dto.PageInfo + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := global.VALID.Struct(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + + total, list, err := backupService.Page(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} diff --git a/backend/app/api/v1/entry.go b/backend/app/api/v1/entry.go index 9677aa026..62ece23e0 100644 --- a/backend/app/api/v1/entry.go +++ b/backend/app/api/v1/entry.go @@ -11,6 +11,7 @@ var ApiGroupApp = new(ApiGroup) var ( authService = service.ServiceGroupApp.AuthService hostService = service.ServiceGroupApp.HostService + backupService = service.ServiceGroupApp.BackupService groupService = service.ServiceGroupApp.GroupService commandService = service.ServiceGroupApp.CommandService operationService = service.ServiceGroupApp.OperationService diff --git a/backend/app/dto/backup.go b/backend/app/dto/backup.go new file mode 100644 index 000000000..680bf5519 --- /dev/null +++ b/backend/app/dto/backup.go @@ -0,0 +1,26 @@ +package dto + +import "time" + +type BackupOperate struct { + Name string `json:"name" validate:"required"` + Type string `json:"type" validate:"required"` + Bucket string `json:"bucket"` + Credential string `json:"credential"` + Vars string `json:"vars" validate:"required"` +} + +type BackupInfo struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"createdAt"` + Name string `json:"name"` + Type string `json:"type"` + Bucket string `json:"bucket"` + Vars string `json:"vars"` +} + +type ForBuckets struct { + Type string `json:"type" validate:"required"` + Credential string `json:"credential" validate:"required"` + Vars string `json:"vars" validate:"required"` +} diff --git a/backend/app/model/backup.go b/backend/app/model/backup.go new file mode 100644 index 000000000..57b896a08 --- /dev/null +++ b/backend/app/model/backup.go @@ -0,0 +1,11 @@ +package model + +type BackupAccount struct { + BaseModel + Name string `gorm:"type:varchar(64);not null" json:"name"` + Type string `gorm:"type:varchar(64)" json:"type"` + Bucket string `gorm:"type:varchar(256)" json:"bucket"` + Credential string `gorm:"type:varchar(256)" json:"credential"` + Vars string `gorm:"type:longText" json:"vars"` + Status string `gorm:"type:varchar(64)" json:"status"` +} diff --git a/backend/app/repo/backup.go b/backend/app/repo/backup.go new file mode 100644 index 000000000..68999678e --- /dev/null +++ b/backend/app/repo/backup.go @@ -0,0 +1,57 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/app/model" + "github.com/1Panel-dev/1Panel/global" +) + +type BackupRepo struct{} + +type IBackupRepo interface { + Page(page, size 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 NewIBackupService() 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) 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 +} diff --git a/backend/app/repo/entry.go b/backend/app/repo/entry.go index 58f4d57e5..0b662becc 100644 --- a/backend/app/repo/entry.go +++ b/backend/app/repo/entry.go @@ -2,6 +2,7 @@ package repo type RepoGroup struct { HostRepo + BackupRepo GroupRepo CommandRepo OperationRepo diff --git a/backend/app/service/backup.go b/backend/app/service/backup.go new file mode 100644 index 000000000..db05b3701 --- /dev/null +++ b/backend/app/service/backup.go @@ -0,0 +1,83 @@ +package service + +import ( + "encoding/json" + + "github.com/1Panel-dev/1Panel/app/dto" + "github.com/1Panel-dev/1Panel/constant" + "github.com/1Panel-dev/1Panel/utils/cloud_storage" + "github.com/jinzhu/copier" + "github.com/pkg/errors" +) + +type BackupService struct{} + +type IBackupService interface { + Page(search dto.PageInfo) (int64, interface{}, error) + Create(backupDto dto.BackupOperate) error + GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error) + Update(id uint, upMap map[string]interface{}) error + BatchDelete(ids []uint) error +} + +func NewIBackupService() IBackupService { + return &BackupService{} +} + +func (u *BackupService) Page(search dto.PageInfo) (int64, interface{}, error) { + total, ops, err := backupRepo.Page(search.Page, search.PageSize, commonRepo.WithOrderBy("created_at desc")) + var dtobas []dto.BackupInfo + for _, group := range ops { + var item dto.BackupInfo + if err := copier.Copy(&item, &group); err != nil { + return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) + } + dtobas = append(dtobas, item) + } + return total, dtobas, err +} + +func (u *BackupService) Create(backupDto dto.BackupOperate) error { + backup, _ := backupRepo.Get(commonRepo.WithByName(backupDto.Name)) + if backup.ID != 0 { + return constant.ErrRecordExist + } + if err := copier.Copy(&backup, &backupDto); err != nil { + return errors.WithMessage(constant.ErrStructTransform, err.Error()) + } + if err := backupRepo.Create(&backup); err != nil { + return err + } + var backupinfo dto.BackupInfo + if err := copier.Copy(&backupinfo, &backup); err != nil { + return errors.WithMessage(constant.ErrStructTransform, err.Error()) + } + 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 + } + varMap["type"] = backupDto.Type + switch backupDto.Type { + case constant.Sftp: + varMap["password"] = backupDto.Credential + case constant.OSS, constant.S3, constant.MinIo: + varMap["secretKey"] = backupDto.Credential + } + client, err := cloud_storage.NewCloudStorageClient(varMap) + if err != nil { + return nil, err + } + return client.ListBuckets() +} + +func (u *BackupService) BatchDelete(ids []uint) error { + return backupRepo.Delete(commonRepo.WithIdsIn(ids)) +} + +func (u *BackupService) Update(id uint, upMap map[string]interface{}) error { + return backupRepo.Update(id, upMap) +} diff --git a/backend/app/service/entry.go b/backend/app/service/entry.go index 10f907f81..09ff6626f 100644 --- a/backend/app/service/entry.go +++ b/backend/app/service/entry.go @@ -5,6 +5,7 @@ import "github.com/1Panel-dev/1Panel/app/repo" type ServiceGroup struct { AuthService HostService + BackupService GroupService CommandService OperationService @@ -16,6 +17,7 @@ var ServiceGroupApp = new(ServiceGroup) var ( hostRepo = repo.RepoGroupApp.HostRepo + backupRepo = repo.RepoGroupApp.BackupRepo groupRepo = repo.RepoGroupApp.GroupRepo commandRepo = repo.RepoGroupApp.CommandRepo operationRepo = repo.RepoGroupApp.OperationRepo diff --git a/backend/app/service/operation_log.go b/backend/app/service/operation_log.go index 35c750966..21a8de1b1 100644 --- a/backend/app/service/operation_log.go +++ b/backend/app/service/operation_log.go @@ -56,7 +56,7 @@ func (u *OperationService) BatchDelete(ids []uint) error { } func filterSensitive(vars string) string { - var Sensitives = []string{"password", "Password", "privateKey"} + var Sensitives = []string{"password", "Password", "credential", "privateKey"} ops := make(map[string]interface{}) if err := json.Unmarshal([]byte(vars), &ops); err != nil { return vars diff --git a/backend/constant/backup_account.go b/backend/constant/backup_account.go new file mode 100644 index 000000000..d2a688620 --- /dev/null +++ b/backend/constant/backup_account.go @@ -0,0 +1,11 @@ +package constant + +const ( + Valid = "VALID" + DisConnect = "DISCONNECT" + VerifyFailed = "VERIFYFAILED" + S3 = "S3" + OSS = "OSS" + Sftp = "SFTP" + MinIo = "MINIO" +) diff --git a/backend/constant/errs.go b/backend/constant/errs.go index 67e57dd41..22b500148 100644 --- a/backend/constant/errs.go +++ b/backend/constant/errs.go @@ -22,6 +22,8 @@ var ( ErrRecordNotFound = errors.New("ErrRecordNotFound") ErrStructTransform = errors.New("ErrStructTransform") ErrInitialPassword = errors.New("ErrInitialPassword") + ErrNotSupportType = errors.New("ErrNotSupportType") + ErrInvalidParams = errors.New("ErrInvalidParams") ErrTokenParse = errors.New("ErrTokenParse") diff --git a/backend/go.mod b/backend/go.mod index daa2bebce..1b961c284 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -3,6 +3,8 @@ module github.com/1Panel-dev/1Panel go 1.18 require ( + github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible + github.com/aws/aws-sdk-go v1.44.99 github.com/dgraph-io/badger/v3 v3.2103.2 github.com/fsnotify/fsnotify v1.5.4 github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 @@ -19,10 +21,12 @@ require ( github.com/jinzhu/copier v0.3.5 github.com/kr/pty v1.1.1 github.com/mholt/archiver/v4 v4.0.0-alpha.7 + github.com/minio/minio-go/v7 v7.0.36 github.com/mojocn/base64Captcha v1.3.5 github.com/natefinch/lumberjack v2.0.0+incompatible github.com/nicksnyder/go-i18n/v2 v2.1.2 github.com/pkg/errors v0.9.1 + github.com/pkg/sftp v1.13.1 github.com/robfig/cron/v3 v3.0.1 github.com/satori/go.uuid v1.2.0 github.com/shirou/gopsutil v3.21.11+incompatible @@ -34,7 +38,7 @@ require ( github.com/swaggo/gin-swagger v1.5.1 github.com/swaggo/swag v1.8.4 github.com/xlzd/gotp v0.0.0-20220817083547-a63b9d03d72f - golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 + golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa golang.org/x/text v0.3.7 gopkg.in/yaml.v2 v2.4.0 gorm.io/driver/mysql v1.3.5 @@ -70,20 +74,26 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/flatbuffers v1.12.1 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/gorilla/securecookie v1.1.1 // indirect github.com/hashicorp/hcl v1.0.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.15.9 // indirect + github.com/klauspost/cpuid/v2 v2.1.0 // indirect github.com/klauspost/pgzip v1.2.5 // indirect + github.com/kr/fs v0.1.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect + github.com/minio/md5-simd v1.1.2 // indirect + github.com/minio/sha256-simd v1.0.0 // 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 @@ -91,6 +101,7 @@ require ( github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.2 // indirect github.com/pierrec/lz4/v4 v4.1.15 // indirect + github.com/rs/xid v1.4.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -105,11 +116,12 @@ require ( go.opentelemetry.io/otel v1.0.0 // indirect go.opentelemetry.io/otel/trace v1.0.0 // indirect golang.org/x/image v0.0.0-20190802002840-cff245a6509b // indirect - golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect - golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect golang.org/x/tools v0.1.10 // indirect google.golang.org/protobuf v1.28.0 // indirect - gopkg.in/ini.v1 v1.66.4 // indirect + gopkg.in/ini.v1 v1.66.6 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index ac08fd708..d3c5664f6 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -48,9 +48,13 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt 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/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= +github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible h1:QoRMR0TCctLDqBCMyOu1eXdZyMw3F7uGA9qPn2J4+R8= +github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aws/aws-sdk-go v1.44.99 h1:ITZ9q/fmH+Ksaz2TbyMU2d19vOOWs/hAlt8NbXAieHw= +github.com/aws/aws-sdk-go v1.44.99/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -226,6 +230,8 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.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/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= @@ -263,6 +269,10 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 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/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= 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= @@ -279,8 +289,13 @@ github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8 github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0= +github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +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= @@ -315,6 +330,12 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJK github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mholt/archiver/v4 v4.0.0-alpha.7 h1:xzByj8G8tj0Oq7ZYYU4+ixL/CVb5ruWCm0EZQ1PjOkE= github.com/mholt/archiver/v4 v4.0.0-alpha.7/go.mod h1:Fs8qUkO74HHaidabihzYephJH8qmGD/nCP6tE5xC9BM= +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.36 h1:KPzAl8C6jcRFEUsGUHR6deRivvKATPNZThzi7D9y/sc= +github.com/minio/minio-go/v7 v7.0.36/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -352,6 +373,7 @@ github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu 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.1 h1:I2qBYMChEhIjOgazfJmV3/mZM256btk6wkCDRmW7JYs= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -362,6 +384,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= +github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= @@ -464,8 +488,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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= @@ -539,9 +563,11 @@ golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1 golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 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= @@ -611,8 +637,10 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/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 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/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 h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -627,6 +655,7 @@ golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 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 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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= @@ -785,8 +814,8 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 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.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= -gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI= +gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index 24f650651..ee53ce0e6 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -9,4 +9,5 @@ ErrRecordExist: "Record already exists: {{ .detail }}" ErrRecordNotFound: "Records not found: {{ .detail }}" ErrStructTransform: "Type conversion failure: {{ .detail }}" ErrNotLogin: "User is not Login: {{ .detail }}" -ErrNotSafety: "The login status of the current user is unsafe: {{ .detail }}" \ No newline at end of file +ErrNotSafety: "The login status of the current user is unsafe: {{ .detail }}" +ErrNotSupportType: "The system does not support the current type: {{ .detail }}" \ No newline at end of file diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index 0023c170d..0db478400 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -9,4 +9,5 @@ ErrRecordExist: "记录已存在: {{ .detail }}" ErrRecordNotFound: "记录未能找到: {{ .detail }}" ErrStructTransform: "类型转换失败: {{ .detail }}" ErrNotLogin: "用户未登录: {{ .detail }}" -ErrNotSafety: "当前用户登录状态不安全: {{ .detail }}" \ No newline at end of file +ErrNotSafety: "当前用户登录状态不安全: {{ .detail }}" +ErrNotSupportType: "系统暂不支持当前类型: {{ .detail }}" \ No newline at end of file diff --git a/backend/init/migration/migrate.go b/backend/init/migration/migrate.go index c6a60eaac..5915d9f0a 100644 --- a/backend/init/migration/migrate.go +++ b/backend/init/migration/migrate.go @@ -13,6 +13,7 @@ func Init() { migrations.AddTableHost, migrations.AddTableMonitor, migrations.AddTableSetting, + migrations.AddTableBackupAccount, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/backend/init/migration/migrations/init.go b/backend/init/migration/migrations/init.go index ca5b46ce9..dfaf8e069 100644 --- a/backend/init/migration/migrations/init.go +++ b/backend/init/migration/migrations/init.go @@ -119,3 +119,22 @@ var AddTableSetting = &gormigrate.Migration{ return nil }, } + +var AddTableBackupAccount = &gormigrate.Migration{ + ID: "20200916-add-table-backup", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.BackupAccount{}); err != nil { + return err + } + item := &model.BackupAccount{ + Name: "Default Local", + Type: "LOCAL", + Status: "VALID", + Vars: "{\"dir\":\"/opt/1Panel/backup\"}", + } + if err := tx.Create(item).Error; err != nil { + return err + } + return nil + }, +} diff --git a/backend/init/router/router.go b/backend/init/router/router.go index a12cf8de5..bfdec1ba0 100644 --- a/backend/init/router/router.go +++ b/backend/init/router/router.go @@ -45,6 +45,7 @@ func Routers() *gin.Engine { { systemRouter.InitBaseRouter(PrivateGroup) systemRouter.InitHostRouter(PrivateGroup) + systemRouter.InitBackupRouter(PrivateGroup) systemRouter.InitGroupRouter(PrivateGroup) systemRouter.InitCommandRouter(PrivateGroup) systemRouter.InitTerminalRouter(PrivateGroup) diff --git a/backend/router/entry.go b/backend/router/entry.go index 76109230c..5bde3c8ee 100644 --- a/backend/router/entry.go +++ b/backend/router/entry.go @@ -3,6 +3,7 @@ package router type RouterGroup struct { BaseRouter HostRouter + BackupRouter GroupRouter CommandRouter MonitorRouter diff --git a/backend/router/ro_backup.go b/backend/router/ro_backup.go new file mode 100644 index 000000000..674642890 --- /dev/null +++ b/backend/router/ro_backup.go @@ -0,0 +1,23 @@ +package router + +import ( + v1 "github.com/1Panel-dev/1Panel/app/api/v1" + "github.com/1Panel-dev/1Panel/middleware" + + "github.com/gin-gonic/gin" +) + +type BackupRouter struct{} + +func (s *BackupRouter) InitBackupRouter(Router *gin.RouterGroup) { + baRouter := Router.Group("backups").Use(middleware.JwtAuth()).Use(middleware.SessionAuth()) + withRecordRouter := Router.Group("backups").Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.OperationRecord()) + baseApi := v1.ApiGroupApp.BaseApi + { + baRouter.POST("/search", baseApi.PageBackup) + baRouter.POST("/buckets", baseApi.ListBuckets) + withRecordRouter.POST("", baseApi.CreateBackup) + withRecordRouter.POST("/del", baseApi.DeleteBackup) + withRecordRouter.PUT(":id", baseApi.UpdateBackup) + } +} diff --git a/backend/utils/cloud_storage/client/minio.go b/backend/utils/cloud_storage/client/minio.go new file mode 100644 index 000000000..1a9aefdf6 --- /dev/null +++ b/backend/utils/cloud_storage/client/minio.go @@ -0,0 +1,158 @@ +package client + +import ( + "context" + "crypto/tls" + "io" + "net/http" + "os" + "strings" + + "github.com/1Panel-dev/1Panel/constant" + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" +) + +type minIoClient struct { + Vars map[string]interface{} + client *minio.Client +} + +func NewMinIoClient(vars map[string]interface{}) (*minIoClient, error) { + var endpoint string + var accessKeyID string + var secretAccessKey string + if _, ok := vars["endpoint"]; ok { + endpoint = vars["endpoint"].(string) + } else { + return nil, constant.ErrInvalidParams + } + if _, ok := vars["accessKey"]; ok { + accessKeyID = vars["accessKey"].(string) + } else { + return nil, constant.ErrInvalidParams + } + if _, ok := vars["secretKey"]; ok { + secretAccessKey = vars["secretKey"].(string) + } else { + return nil, constant.ErrInvalidParams + } + 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(endpoint, &minio.Options{ + Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), + Secure: secure, + Transport: transport, + }) + if err != nil { + return nil, err + } + return &minIoClient{ + client: client, + Vars: vars, + }, nil +} + +func (minIo minIoClient) ListBuckets() ([]interface{}, error) { + buckets, err := minIo.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 (minIo minIoClient) Exist(path string) (bool, error) { + if _, ok := minIo.Vars["bucket"]; ok { + _, err := minIo.client.GetObject(context.Background(), minIo.Vars["bucket"].(string), path, minio.GetObjectOptions{}) + if err != nil { + return true, err + } + return false, nil + } else { + return false, constant.ErrInvalidParams + } +} + +func (minIo minIoClient) Delete(path string) (bool, error) { + if _, ok := minIo.Vars["bucket"]; ok { + object, err := minIo.client.GetObject(context.Background(), minIo.Vars["bucket"].(string), path, minio.GetObjectOptions{}) + if err != nil { + return false, err + } + info, err := object.Stat() + if err != nil { + return false, err + } + err = minIo.client.RemoveObject(context.Background(), minIo.Vars["bucket"].(string), path, minio.RemoveObjectOptions{ + GovernanceBypass: true, + VersionID: info.VersionID, + }) + if err != nil { + return false, err + } + return true, nil + } else { + return false, constant.ErrInvalidParams + } +} + +func (minIo minIoClient) Upload(src, target string) (bool, error) { + + var bucket string + if _, ok := minIo.Vars["bucket"]; ok { + bucket = minIo.Vars["bucket"].(string) + } else { + return false, constant.ErrInvalidParams + } + + 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 = minIo.client.PutObject(context.Background(), bucket, target, file, fileStat.Size(), minio.PutObjectOptions{ContentType: "application/octet-stream"}) + if err != nil { + return false, err + } + return true, nil +} + +func (minIo minIoClient) Download(src, target string) (bool, error) { + if _, ok := minIo.Vars["bucket"]; ok { + object, err := minIo.client.GetObject(context.Background(), minIo.Vars["bucket"].(string), src, minio.GetObjectOptions{}) + if err != nil { + return false, err + } + localFile, err := os.Create(target) + if err != nil { + return false, err + } + if _, err = io.Copy(localFile, object); err != nil { + return false, err + } + return true, nil + } else { + return false, constant.ErrInvalidParams + } +} diff --git a/backend/utils/cloud_storage/client/oss.go b/backend/utils/cloud_storage/client/oss.go new file mode 100644 index 000000000..df456cbed --- /dev/null +++ b/backend/utils/cloud_storage/client/oss.go @@ -0,0 +1,109 @@ +package client + +import ( + "github.com/1Panel-dev/1Panel/constant" + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +type ossClient struct { + Vars map[string]interface{} + client oss.Client +} + +func NewOssClient(vars map[string]interface{}) (*ossClient, error) { + var endpoint string + var accessKey string + var secretKey string + if _, ok := vars["endpoint"]; ok { + endpoint = vars["endpoint"].(string) + } else { + return nil, constant.ErrInvalidParams + } + if _, ok := vars["accessKey"]; ok { + accessKey = vars["accessKey"].(string) + } else { + return nil, constant.ErrInvalidParams + } + if _, ok := vars["secretKey"]; ok { + secretKey = vars["secretKey"].(string) + } else { + return nil, constant.ErrInvalidParams + } + client, err := oss.New(endpoint, accessKey, secretKey) + if err != nil { + return nil, err + } + return &ossClient{ + Vars: vars, + client: *client, + }, nil +} + +func (oss ossClient) ListBuckets() ([]interface{}, error) { + response, err := oss.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 (oss ossClient) Exist(path string) (bool, error) { + bucket, err := oss.GetBucket() + if err != nil { + return false, err + } + return bucket.IsObjectExist(path) + +} + +func (oss ossClient) Delete(path string) (bool, error) { + bucket, err := oss.GetBucket() + if err != nil { + return false, err + } + err = bucket.DeleteObject(path) + if err != nil { + return false, err + } + return true, nil +} + +func (oss ossClient) Upload(src, target string) (bool, error) { + bucket, err := oss.GetBucket() + if err != nil { + return false, err + } + err = bucket.PutObjectFromFile(target, src) + if err != nil { + return false, err + } + return true, nil +} + +func (oss ossClient) Download(src, target string) (bool, error) { + bucket, err := oss.GetBucket() + if err != nil { + return false, err + } + err = bucket.GetObjectToFile(src, target) + if err != nil { + return false, err + } + return true, nil +} + +func (oss *ossClient) GetBucket() (*oss.Bucket, error) { + if _, ok := oss.Vars["bucket"]; ok { + bucket, err := oss.client.Bucket(oss.Vars["bucket"].(string)) + if err != nil { + return nil, err + } + return bucket, nil + } else { + return nil, constant.ErrInvalidParams + } +} diff --git a/backend/utils/cloud_storage/client/s3.go b/backend/utils/cloud_storage/client/s3.go new file mode 100644 index 000000000..4592ea679 --- /dev/null +++ b/backend/utils/cloud_storage/client/s3.go @@ -0,0 +1,177 @@ +package client + +import ( + "os" + + "github.com/1Panel-dev/1Panel/constant" + "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 { + Vars map[string]interface{} + Sess session.Session +} + +func NewS3Client(vars map[string]interface{}) (*s3Client, error) { + + var accessKey string + var secretKey string + var endpoint string + var region string + if _, ok := vars["accessKey"]; ok { + accessKey = vars["accessKey"].(string) + } else { + return nil, constant.ErrInvalidParams + } + if _, ok := vars["secretKey"]; ok { + secretKey = vars["secretKey"].(string) + } else { + return nil, constant.ErrInvalidParams + } + if _, ok := vars["endpoint"]; ok { + endpoint = vars["endpoint"].(string) + } else { + return nil, constant.ErrInvalidParams + } + if _, ok := vars["region"]; ok { + region = vars["region"].(string) + } else { + return nil, constant.ErrInvalidParams + } + 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{ + Vars: vars, + Sess: *sess, + }, nil +} + +func (s3C s3Client) ListBuckets() ([]interface{}, error) { + var result []interface{} + svc := s3.New(&s3C.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 (s3C s3Client) Exist(path string) (bool, error) { + bucket, err := s3C.getBucket() + if err != nil { + return false, err + } + svc := s3.New(&s3C.Sess) + _, err = svc.HeadObject(&s3.HeadObjectInput{ + Bucket: &bucket, + Key: &path, + }) + if err != nil { + if aerr, ok := err.(awserr.RequestFailure); ok { + if aerr.StatusCode() == 404 { + return false, nil + } + } else { + return false, aerr + } + } + return true, nil +} + +func (s3C s3Client) Delete(path string) (bool, error) { + bucket, err := s3C.getBucket() + if err != nil { + return false, err + } + svc := s3.New(&s3C.Sess) + _, err = svc.DeleteObject(&s3.DeleteObjectInput{Bucket: aws.String(bucket), Key: aws.String(path)}) + if err != nil { + return false, err + } + err = svc.WaitUntilObjectNotExists(&s3.HeadObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(path), + }) + if err != nil { + return false, err + } + return true, nil +} + +func (s3C s3Client) Upload(src, target string) (bool, error) { + bucket, err := s3C.getBucket() + if err != nil { + return false, err + } + file, err := os.Open(src) + if err != nil { + return false, err + } + defer file.Close() + + uploader := s3manager.NewUploader(&s3C.Sess) + _, err = uploader.Upload(&s3manager.UploadInput{ + Bucket: aws.String(bucket), + Key: aws.String(target), + Body: file, + }) + if err != nil { + return false, err + } + return true, nil +} + +func (s3C s3Client) Download(src, target string) (bool, error) { + bucket, err := s3C.getBucket() + if err != nil { + return false, err + } + _, err = os.Stat(target) + if 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(&s3C.Sess) + _, err = downloader.Download(file, + &s3.GetObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(src), + }) + if err != nil { + os.Remove(target) + return false, err + } + return true, nil +} + +func (s3C *s3Client) getBucket() (string, error) { + if _, ok := s3C.Vars["bucket"]; ok { + return s3C.Vars["bucket"].(string), nil + } else { + return "", constant.ErrInvalidParams + } +} diff --git a/backend/utils/cloud_storage/client/sftp.go b/backend/utils/cloud_storage/client/sftp.go new file mode 100644 index 000000000..ea94acce3 --- /dev/null +++ b/backend/utils/cloud_storage/client/sftp.go @@ -0,0 +1,212 @@ +package client + +import ( + "fmt" + "io/ioutil" + "net" + "os" + "path" + "strconv" + "time" + + "github.com/1Panel-dev/1Panel/constant" + "github.com/pkg/sftp" + "golang.org/x/crypto/ssh" +) + +type sftpClient struct { + Vars map[string]interface{} +} + +func NewSftpClient(vars map[string]interface{}) (*sftpClient, error) { + if _, ok := vars["address"]; !ok { + return nil, constant.ErrInvalidParams + } + if _, ok := vars["port"].(float64); !ok { + return nil, constant.ErrInvalidParams + } + if _, ok := vars["password"]; !ok { + return nil, constant.ErrInvalidParams + } + if _, ok := vars["username"]; !ok { + return nil, constant.ErrInvalidParams + } + return &sftpClient{ + Vars: vars, + }, nil +} + +func (s sftpClient) Upload(src, target string) (bool, error) { + bucket, err := s.getBucket() + if err != nil { + return false, err + } + port, err := strconv.Atoi(strconv.FormatFloat(s.Vars["port"].(float64), 'G', -1, 64)) + if err != nil { + return false, err + } + sftpC, err := connect(s.Vars["username"].(string), s.Vars["password"].(string), s.Vars["address"].(string), port) + if err != nil { + return false, err + } + defer sftpC.Close() + srcFile, err := os.Open(src) + if err != nil { + return false, err + } + defer srcFile.Close() + + targetFilePath := bucket + "/" + target + remotePath, _ := path.Split(targetFilePath) + _, err = sftpC.Stat(remotePath) + if err != nil { + if os.IsNotExist(err) { + err = sftpC.MkdirAll(remotePath) + if err != nil { + return false, err + } + } else { + return false, err + } + } + + dstFile, err := sftpC.Create(targetFilePath) + if err != nil { + return false, err + } + defer dstFile.Close() + ff, err := ioutil.ReadAll(srcFile) + if err != nil { + return false, err + } + _, _ = dstFile.Write(ff) + return true, nil +} + +func (s sftpClient) ListBuckets() ([]interface{}, error) { + var result []interface{} + return result, nil +} + +func (s sftpClient) Download(src, target string) (bool, error) { + bucket, err := s.getBucket() + if err != nil { + return false, err + } + port, err := strconv.Atoi(strconv.FormatFloat(s.Vars["port"].(float64), 'G', -1, 64)) + if err != nil { + return false, err + } + sftpC, err := connect(s.Vars["username"].(string), s.Vars["password"].(string), s.Vars["address"].(string), port) + if err != nil { + return false, err + } + defer sftpC.Close() + srcFile, err := sftpC.Open(bucket + "/" + src) + if err != nil { + return false, err + } + defer srcFile.Close() + + dstFile, err := os.Create(target) + if err != nil { + return false, err + } + defer dstFile.Close() + + if _, err = srcFile.WriteTo(dstFile); err != nil { + return false, err + } + return true, err +} + +func (s sftpClient) Exist(path string) (bool, error) { + bucket, err := s.getBucket() + if err != nil { + return false, err + } + port, err := strconv.Atoi(strconv.FormatFloat(s.Vars["port"].(float64), 'G', -1, 64)) + if err != nil { + return false, err + } + sftpC, err := connect(s.Vars["username"].(string), s.Vars["password"].(string), s.Vars["address"].(string), port) + if err != nil { + return false, err + } + defer sftpC.Close() + srcFile, err := sftpC.Open(bucket + "/" + path) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } else { + return false, err + } + } + defer srcFile.Close() + return true, err +} + +func (s sftpClient) Delete(filePath string) (bool, error) { + bucket, err := s.getBucket() + if err != nil { + return false, err + } + port, err := strconv.Atoi(strconv.FormatFloat(s.Vars["port"].(float64), 'G', -1, 64)) + if err != nil { + return false, err + } + sftpC, err := connect(s.Vars["username"].(string), s.Vars["password"].(string), s.Vars["address"].(string), port) + if err != nil { + return false, err + } + defer sftpC.Close() + targetFilePath := bucket + "/" + filePath + err = sftpC.Remove(targetFilePath) + if err != nil { + if os.IsNotExist(err) { + return true, nil + } else { + return false, err + } + } + return true, nil +} + +func connect(user, password, host string, port int) (*sftp.Client, error) { + + var ( + auth []ssh.AuthMethod + addr string + clientConfig *ssh.ClientConfig + sshClient *ssh.Client + sftpClient *sftp.Client + err error + ) + auth = make([]ssh.AuthMethod, 0) + auth = append(auth, ssh.Password(password)) + clientConfig = &ssh.ClientConfig{ + User: user, + Auth: auth, + Timeout: 30 * time.Second, + HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return nil + }, + } + addr = fmt.Sprintf("%s:%d", host, port) + + if sshClient, err = ssh.Dial("tcp", addr, clientConfig); err != nil { + return nil, err + } + if sftpClient, err = sftp.NewClient(sshClient); err != nil { + return nil, err + } + return sftpClient, nil +} + +func (s sftpClient) getBucket() (string, error) { + if _, ok := s.Vars["bucket"]; ok { + return s.Vars["bucket"].(string), nil + } else { + return "", constant.ErrInvalidParams + } +} diff --git a/backend/utils/cloud_storage/cloud_storage_client.go b/backend/utils/cloud_storage/cloud_storage_client.go new file mode 100644 index 000000000..4c1cbefbf --- /dev/null +++ b/backend/utils/cloud_storage/cloud_storage_client.go @@ -0,0 +1,30 @@ +package cloud_storage + +import ( + "github.com/1Panel-dev/1Panel/constant" + "github.com/1Panel-dev/1Panel/utils/cloud_storage/client" +) + +type CloudStorageClient interface { + ListBuckets() ([]interface{}, error) + Exist(path string) (bool, error) + Delete(path string) (bool, error) + Upload(src, target string) (bool, error) + Download(src, target string) (bool, error) +} + +func NewCloudStorageClient(vars map[string]interface{}) (CloudStorageClient, error) { + if vars["type"] == constant.S3 { + return client.NewS3Client(vars) + } + if vars["type"] == constant.OSS { + return client.NewOssClient(vars) + } + if vars["type"] == constant.Sftp { + return client.NewSftpClient(vars) + } + if vars["type"] == constant.MinIo { + return client.NewMinIoClient(vars) + } + return nil, constant.ErrNotSupportType +} diff --git a/frontend/src/api/interface/backup.ts b/frontend/src/api/interface/backup.ts new file mode 100644 index 000000000..d6ffeb61e --- /dev/null +++ b/frontend/src/api/interface/backup.ts @@ -0,0 +1,23 @@ +export namespace Backup { + export interface BackupInfo { + id: number; + name: string; + type: string; + bucket: string; + vars: string; + varsJson: object; + } + export interface BackupOperate { + id: number; + name: string; + type: string; + bucket: string; + credential: string; + vars: string; + } + export interface ForBucket { + type: string; + credential: string; + vars: string; + } +} diff --git a/frontend/src/api/modules/backup.ts b/frontend/src/api/modules/backup.ts new file mode 100644 index 000000000..19d3d3476 --- /dev/null +++ b/frontend/src/api/modules/backup.ts @@ -0,0 +1,23 @@ +import http from '@/api'; +import { Backup } from '../interface/backup'; +import { ResPage, ReqPage } from '../interface'; + +export const getBackupList = (params: ReqPage) => { + return http.post>(`/backups/search`, params); +}; + +export const addBackup = (params: Backup.BackupOperate) => { + return http.post(`/backups`, params); +}; + +export const editBackup = (params: Backup.BackupOperate) => { + return http.put(`/backups/` + params.id, params); +}; + +export const deleteBackup = (params: { ids: number[] }) => { + return http.post(`/backups/del`, params); +}; + +export const listBucket = (params: Backup.ForBucket) => { + return http.post(`/backups/buckets`, params); +}; diff --git a/frontend/src/components/file-list/index.vue b/frontend/src/components/file-list/index.vue index fd3696f8e..2dca2d591 100644 --- a/frontend/src/components/file-list/index.vue +++ b/frontend/src/components/file-list/index.vue @@ -48,7 +48,7 @@ let rowName = ref(''); let data = ref(); let loading = ref(false); let paths = ref([]); -let req = reactive({ path: '/', expand: true }); +let req = reactive({ path: '/', expand: true, page: 1, pageSize: 20 }); const props = defineProps({ path: { diff --git a/frontend/src/global/form-rules.ts b/frontend/src/global/form-rules.ts index 9bc07cc3d..c0e98dafa 100644 --- a/frontend/src/global/form-rules.ts +++ b/frontend/src/global/form-rules.ts @@ -64,6 +64,7 @@ export const Rules: CommonRule = { trigger: 'change', }, name: { + required: true, validator: checkName, trigger: 'blur', }, diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 4ff612025..16c684d13 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -28,6 +28,8 @@ export default { }, table: { name: 'Name', + type: 'Type', + status: 'Status', group: 'Group', createdAt: 'Creation Time', date: 'Date', @@ -43,6 +45,7 @@ export default { loginSuccess: 'Login Success', requestTimeout: 'The request timed out, please try again later', operationSuccess: 'Successful operation', + notSupportOperation: 'This operation is not supported', infoTitle: 'Hint', sureLogOut: 'Are you sure you want to log out?', createSuccess: 'Create Success', @@ -65,6 +68,7 @@ export default { rule: { username: 'Please enter a username', password: 'Please enter a password', + rePassword: 'The passwords are inconsistent. Please check and re-enter the password', requiredInput: 'Please enter the required fields', requiredSelect: 'Please select the required fields', commonName: 'Support English, Chinese, numbers, .-_, length 1-30', @@ -72,6 +76,7 @@ export default { 'Please enter a password with more than 8 characters and must contain letters, digits, and special symbols', commonPassword: 'Please enter a password with more than 6 characters', email: 'Email format error', + number: 'Please enter the correct number', ip: 'Please enter the correct IP address', port: 'Please enter the correct port', }, @@ -102,7 +107,6 @@ export default { }, menu: { home: 'Overview', - terminal: 'Terminal', apps: 'App Store', website: 'Website', project: 'Project', @@ -113,12 +117,12 @@ export default { plan: 'Planned Task', host: 'Host', security: 'Security', - systemConfig: 'Panel Settings', - toolbox: 'Toolbox', - monitor: 'Monitor', - operations: 'Operation Records', files: 'File Management', + monitor: 'Monitor', + terminal: 'Terminal', settings: 'Setting', + toolbox: 'Toolbox', + operations: 'Operation Records', }, home: { welcome: 'Welcome', @@ -130,18 +134,6 @@ export default { closeAll: 'Close All', }, header: { - componentSize: 'Component size', - language: 'Language', - theme: 'theme', - layoutConfig: 'Layout config', - primary: 'primary', - darkMode: 'Dark Mode', - greyMode: 'Grey mode', - weakMode: 'Weak mode', - fullScreen: 'Full Screen', - exitFullScreen: 'Exit Full Screen', - personalData: 'Personal Data', - changePassword: 'Change Password', logout: 'Logout', }, monitor: { @@ -196,6 +188,8 @@ export default { hosts: 'Host', groups: 'Group', commands: 'Command', + backups: 'Backup Account', + settings: 'Panel Setting', auth: 'User', login: ' login', logout: ' logout', @@ -285,6 +279,19 @@ export default { oldPassword: 'Original password', newPassword: 'New password', retryPassword: 'Confirm password', + + backup: 'Backup', + serverDisk: 'Server disks', + backupAccount: 'Backup account', + loadBucket: 'Get bucket', + accountName: 'Account name', + accountKey: 'Account key', + address: 'Address', + port: 'Port', + username: 'Username', + password: 'Password', + path: 'Path', + safe: 'Safe', panelPort: 'Panel port', portHelper: @@ -302,9 +309,11 @@ export default { mfaHelper1: 'Download a MFA verification mobile app such as:', mfaHelper2: 'Scan the following QR code using the mobile app to obtain the 6-digit verification code', mfaHelper3: 'Enter six digits from the app', + enableMonitor: 'Enable', storeDays: 'Expiration time (day)', cleanMonitor: 'Clearing monitoring records', + message: 'Message', messageType: 'Message type', email: 'Email', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index a7e622b8d..59fe54464 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -26,18 +26,10 @@ export default { dateStart: '开始日期', dateEnd: '结束日期', }, - personal: { - about: '关于', - project_url: '项目地址', - issue: '问题反馈', - talk: '参与讨论', - star: '点亮 Star', - version: '版本', - ko_introduction: - '是一个开源的轻量级 Kubernetes 发行版,专注于帮助企业规划、部署和运营生产级别的 Kubernetes 集群。', - }, table: { name: '名称', + type: '类型', + status: '状态', group: '组', createdAt: '创建时间', date: '时间', @@ -52,6 +44,7 @@ export default { deleteSuccess: '删除成功', loginSuccess: '登录成功', operationSuccess: '操作成功', + notSupportOperation: '不支持的当前操作', requestTimeout: '请求超时,请稍后重试', infoTitle: '提示', sureLogOut: '您是否确认退出登录?', @@ -111,10 +104,6 @@ export default { }, menu: { home: '概览', - monitor: '监控', - terminal: '终端', - operations: '操作日志', - files: '文件管理', apps: '应用商店', website: '网站', project: '项目', @@ -125,8 +114,12 @@ export default { plan: '计划任务', host: '主机', security: '安全', + files: '文件管理', + monitor: '监控', + terminal: '终端', settings: '面板设置', toolbox: '工具箱', + operations: '操作日志', }, home: { welcome: '欢迎使用', @@ -138,18 +131,6 @@ export default { closeAll: '关闭所有', }, header: { - componentSize: '组件大小', - language: '国际化', - theme: '全局主题', - layoutConfig: '布局设置', - primary: 'primary', - darkMode: '暗黑模式', - greyMode: '灰色模式', - weakMode: '色弱模式', - fullScreen: '全屏', - exitFullScreen: '退出全屏', - personalData: '个人资料', - changePassword: '修改密码', logout: '退出登录', }, monitor: { @@ -204,6 +185,8 @@ export default { hosts: '主机', groups: '组', commands: '快捷命令', + backups: '备份账号', + settings: '面板设置', auth: '用户', post: '创建', put: '更新', @@ -292,6 +275,19 @@ export default { oldPassword: '原密码', newPassword: '新密码', retryPassword: '确认密码', + + backup: '备份', + serverDisk: '服务器磁盘', + backupAccount: '备份账号', + loadBucket: '获取桶', + accountName: '账户名称', + accountKey: '账户密钥', + address: '地址', + port: '端口', + username: '用户名', + password: '密码', + path: '路径', + safe: '安全', panelPort: '面板端口', portHelper: '建议端口范围8888 - 65535,注意:有安全组的服务器请提前在安全组放行新端口', @@ -305,9 +301,11 @@ export default { mfaHelper1: '下载两步验证手机应用 如:', mfaHelper2: '使用手机应用扫描以下二维码,获取 6 位验证码', mfaHelper3: '输入手机应用上的 6 位数字', + enableMonitor: '监控状态', storeDays: '过期时间 (天)', cleanMonitor: '清空监控记录', + message: '通知', messageType: '通知方式', email: '邮箱', @@ -318,5 +316,7 @@ export default { emailAddr: '邮箱地址', emailSMTP: '邮箱 SMTP 授权码', secret: '密钥', + + about: '关于', }, }; diff --git a/frontend/src/views/host/file-management/move/index.vue b/frontend/src/views/host/file-management/move/index.vue index 01cf0035b..c1a271c1b 100644 --- a/frontend/src/views/host/file-management/move/index.vue +++ b/frontend/src/views/host/file-management/move/index.vue @@ -82,6 +82,7 @@ const handleClose = () => { }; const getPath = (path: string) => { + console.log(path); addForm.newPath = path; }; diff --git a/frontend/src/views/host/terminal/command/index.vue b/frontend/src/views/host/terminal/command/index.vue index 377db8e28..3e9a8d208 100644 --- a/frontend/src/views/host/terminal/command/index.vue +++ b/frontend/src/views/host/terminal/command/index.vue @@ -1,5 +1,5 @@