mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-23 18:49:21 +08:00
feat: 面板设置增加备份账号管理
This commit is contained in:
parent
fc3318c8e9
commit
f99a2ae656
112
backend/app/api/v1/backup.go
Normal file
112
backend/app/api/v1/backup.go
Normal file
@ -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,
|
||||
})
|
||||
}
|
@ -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
|
||||
|
26
backend/app/dto/backup.go
Normal file
26
backend/app/dto/backup.go
Normal file
@ -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"`
|
||||
}
|
11
backend/app/model/backup.go
Normal file
11
backend/app/model/backup.go
Normal file
@ -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"`
|
||||
}
|
57
backend/app/repo/backup.go
Normal file
57
backend/app/repo/backup.go
Normal file
@ -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
|
||||
}
|
@ -2,6 +2,7 @@ package repo
|
||||
|
||||
type RepoGroup struct {
|
||||
HostRepo
|
||||
BackupRepo
|
||||
GroupRepo
|
||||
CommandRepo
|
||||
OperationRepo
|
||||
|
83
backend/app/service/backup.go
Normal file
83
backend/app/service/backup.go
Normal file
@ -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)
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
11
backend/constant/backup_account.go
Normal file
11
backend/constant/backup_account.go
Normal file
@ -0,0 +1,11 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
Valid = "VALID"
|
||||
DisConnect = "DISCONNECT"
|
||||
VerifyFailed = "VERIFYFAILED"
|
||||
S3 = "S3"
|
||||
OSS = "OSS"
|
||||
Sftp = "SFTP"
|
||||
MinIo = "MINIO"
|
||||
)
|
@ -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")
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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=
|
||||
|
@ -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 }}"
|
||||
ErrNotSafety: "The login status of the current user is unsafe: {{ .detail }}"
|
||||
ErrNotSupportType: "The system does not support the current type: {{ .detail }}"
|
@ -9,4 +9,5 @@ ErrRecordExist: "记录已存在: {{ .detail }}"
|
||||
ErrRecordNotFound: "记录未能找到: {{ .detail }}"
|
||||
ErrStructTransform: "类型转换失败: {{ .detail }}"
|
||||
ErrNotLogin: "用户未登录: {{ .detail }}"
|
||||
ErrNotSafety: "当前用户登录状态不安全: {{ .detail }}"
|
||||
ErrNotSafety: "当前用户登录状态不安全: {{ .detail }}"
|
||||
ErrNotSupportType: "系统暂不支持当前类型: {{ .detail }}"
|
@ -13,6 +13,7 @@ func Init() {
|
||||
migrations.AddTableHost,
|
||||
migrations.AddTableMonitor,
|
||||
migrations.AddTableSetting,
|
||||
migrations.AddTableBackupAccount,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
@ -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
|
||||
},
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -3,6 +3,7 @@ package router
|
||||
type RouterGroup struct {
|
||||
BaseRouter
|
||||
HostRouter
|
||||
BackupRouter
|
||||
GroupRouter
|
||||
CommandRouter
|
||||
MonitorRouter
|
||||
|
23
backend/router/ro_backup.go
Normal file
23
backend/router/ro_backup.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
158
backend/utils/cloud_storage/client/minio.go
Normal file
158
backend/utils/cloud_storage/client/minio.go
Normal file
@ -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
|
||||
}
|
||||
}
|
109
backend/utils/cloud_storage/client/oss.go
Normal file
109
backend/utils/cloud_storage/client/oss.go
Normal file
@ -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
|
||||
}
|
||||
}
|
177
backend/utils/cloud_storage/client/s3.go
Normal file
177
backend/utils/cloud_storage/client/s3.go
Normal file
@ -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
|
||||
}
|
||||
}
|
212
backend/utils/cloud_storage/client/sftp.go
Normal file
212
backend/utils/cloud_storage/client/sftp.go
Normal file
@ -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
|
||||
}
|
||||
}
|
30
backend/utils/cloud_storage/cloud_storage_client.go
Normal file
30
backend/utils/cloud_storage/cloud_storage_client.go
Normal file
@ -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
|
||||
}
|
23
frontend/src/api/interface/backup.ts
Normal file
23
frontend/src/api/interface/backup.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
23
frontend/src/api/modules/backup.ts
Normal file
23
frontend/src/api/modules/backup.ts
Normal file
@ -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<ResPage<Backup.BackupInfo>>(`/backups/search`, params);
|
||||
};
|
||||
|
||||
export const addBackup = (params: Backup.BackupOperate) => {
|
||||
return http.post<Backup.BackupOperate>(`/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);
|
||||
};
|
@ -48,7 +48,7 @@ let rowName = ref('');
|
||||
let data = ref();
|
||||
let loading = ref(false);
|
||||
let paths = ref<string[]>([]);
|
||||
let req = reactive({ path: '/', expand: true });
|
||||
let req = reactive({ path: '/', expand: true, page: 1, pageSize: 20 });
|
||||
|
||||
const props = defineProps({
|
||||
path: {
|
||||
|
@ -64,6 +64,7 @@ export const Rules: CommonRule = {
|
||||
trigger: 'change',
|
||||
},
|
||||
name: {
|
||||
required: true,
|
||||
validator: checkName,
|
||||
trigger: 'blur',
|
||||
},
|
||||
|
@ -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',
|
||||
|
@ -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: '关于',
|
||||
},
|
||||
};
|
||||
|
@ -82,6 +82,7 @@ const handleClose = () => {
|
||||
};
|
||||
|
||||
const getPath = (path: string) => {
|
||||
console.log(path);
|
||||
addForm.newPath = path;
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div style="margin: 20px; margin-left: 20px">
|
||||
<div style="margin: 20px">
|
||||
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data" @search="search">
|
||||
<template #toolbar>
|
||||
<el-button @click="onCreate()">{{ $t('commons.button.create') }}</el-button>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-row style="margin-top: 10px; margin-left: 10px" class="row-box" :gutter="20">
|
||||
<el-row style="margin-top: 20px" class="row-box" :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-card class="el-card">
|
||||
<el-tooltip class="box-item" effect="dark" :content="$t('terminal.createConn')" placement="top-start">
|
||||
|
@ -1,133 +1,127 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-tabs tab-position="left" @tab-click="handleClick" v-model="routeTab" class="demo-tabs">
|
||||
<el-tab-pane name="terminal">
|
||||
<template #label>
|
||||
<el-tooltip class="box-item" effect="dark" :content="$t('terminal.conn')" placement="right">
|
||||
<el-icon><Connection /></el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-tabs
|
||||
type="card"
|
||||
class="terminal-tabs"
|
||||
style="background-color: #efefef"
|
||||
v-model="terminalValue"
|
||||
:before-leave="beforeLeave"
|
||||
@edit="handleTabsRemove"
|
||||
<div>
|
||||
<el-card class="topCard">
|
||||
<el-radio-group @change="handleChange" v-model="activeNames">
|
||||
<el-radio-button class="topButton" size="large" label="terminal">
|
||||
{{ $t('menu.terminal') }}
|
||||
</el-radio-button>
|
||||
<el-radio-button class="topButton" size="large" label="host">
|
||||
{{ $t('menu.host') }}
|
||||
</el-radio-button>
|
||||
<el-radio-button class="topButton" size="large" label="command">
|
||||
{{ $t('terminal.quickCommand') }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-card>
|
||||
</div>
|
||||
<div v-if="activeNames === 'terminal'">
|
||||
<el-tabs
|
||||
type="card"
|
||||
class="terminal-tabs"
|
||||
style="background-color: #efefef; margin-top: 20px"
|
||||
v-model="terminalValue"
|
||||
:before-leave="beforeLeave"
|
||||
@edit="handleTabsRemove"
|
||||
>
|
||||
<el-tab-pane
|
||||
:key="item.key"
|
||||
v-for="item in terminalTabs"
|
||||
:closable="true"
|
||||
:label="item.title"
|
||||
:name="item.key"
|
||||
>
|
||||
<el-tab-pane
|
||||
:key="item.key"
|
||||
v-for="item in terminalTabs"
|
||||
:closable="true"
|
||||
:label="item.title"
|
||||
:name="item.key"
|
||||
>
|
||||
<template #label>
|
||||
<span class="custom-tabs-label">
|
||||
<el-icon style="margin-top: 1px" color="#67C23A" v-if="item.status === 'online'">
|
||||
<circleCheck />
|
||||
</el-icon>
|
||||
<el-icon style="margin-top: 1px" color="#F56C6C" v-if="item.status === 'closed'">
|
||||
<circleClose />
|
||||
</el-icon>
|
||||
<span> {{ item.title }} </span>
|
||||
</span>
|
||||
</template>
|
||||
<Terminal
|
||||
style="height: calc(100vh - 150px); background-color: #000"
|
||||
:ref="'Ref' + item.key"
|
||||
:wsID="item.wsID"
|
||||
:terminalID="item.key"
|
||||
></Terminal>
|
||||
<div>
|
||||
<el-select
|
||||
v-model="quickCmd"
|
||||
clearable
|
||||
filterable
|
||||
@change="quickInput"
|
||||
style="width: 25%"
|
||||
:placeholder="$t('terminal.quickCommand')"
|
||||
>
|
||||
<el-option
|
||||
v-for="cmd in commandList"
|
||||
:key="cmd.id"
|
||||
:label="cmd.name + ' [ ' + cmd.command + ' ] '"
|
||||
:value="cmd.command"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input
|
||||
:placeholder="$t('terminal.batchInput')"
|
||||
v-model="batchVal"
|
||||
@keyup.enter="batchInput"
|
||||
style="width: 75%"
|
||||
>
|
||||
<template #append>
|
||||
<el-switch v-model="isBatch" class="ml-2" />
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :closable="false" name="newTabs">
|
||||
<template #label>
|
||||
<el-button
|
||||
v-popover="popoverRef"
|
||||
style="background-color: #ededed; border: 0"
|
||||
icon="Plus"
|
||||
></el-button>
|
||||
<el-popover ref="popoverRef" width="250px" trigger="hover" virtual-triggering persistent>
|
||||
<el-button-group style="width: 100%">
|
||||
<el-button @click="onNewSsh">New ssh</el-button>
|
||||
<el-button @click="onNewTab">New tab</el-button>
|
||||
</el-button-group>
|
||||
<el-input clearable style="margin-top: 5px" v-model="hostfilterInfo">
|
||||
<template #append><el-button icon="search" /></template>
|
||||
</el-input>
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
:expand-on-click-node="false"
|
||||
node-key="id"
|
||||
:default-expand-all="true"
|
||||
:data="hostTree"
|
||||
:props="defaultProps"
|
||||
:filter-node-method="filterHost"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<span class="custom-tree-node">
|
||||
<span>
|
||||
<a @click="onConn(node, data)">{{ node.label }}</a>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
<div v-if="terminalTabs.length === 0">
|
||||
<el-empty
|
||||
style="background-color: #000; height: calc(100vh - 150px)"
|
||||
:description="$t('terminal.emptyTerminal')"
|
||||
></el-empty>
|
||||
<template #label>
|
||||
<span class="custom-tabs-label">
|
||||
<el-icon style="margin-top: 1px" color="#67C23A" v-if="item.status === 'online'">
|
||||
<circleCheck />
|
||||
</el-icon>
|
||||
<el-icon style="margin-top: 1px" color="#F56C6C" v-if="item.status === 'closed'">
|
||||
<circleClose />
|
||||
</el-icon>
|
||||
<span> {{ item.title }} </span>
|
||||
</span>
|
||||
</template>
|
||||
<Terminal
|
||||
style="height: calc(100vh - 178px); background-color: #000"
|
||||
:ref="'Ref' + item.key"
|
||||
:wsID="item.wsID"
|
||||
:terminalID="item.key"
|
||||
></Terminal>
|
||||
<div>
|
||||
<el-select
|
||||
v-model="quickCmd"
|
||||
clearable
|
||||
filterable
|
||||
@change="quickInput"
|
||||
style="width: 25%"
|
||||
:placeholder="$t('terminal.quickCommand')"
|
||||
>
|
||||
<el-option
|
||||
v-for="cmd in commandList"
|
||||
:key="cmd.id"
|
||||
:label="cmd.name + ' [ ' + cmd.command + ' ] '"
|
||||
:value="cmd.command"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input
|
||||
:placeholder="$t('terminal.batchInput')"
|
||||
v-model="batchVal"
|
||||
@keyup.enter="batchInput"
|
||||
style="width: 75%"
|
||||
>
|
||||
<template #append>
|
||||
<el-switch v-model="isBatch" class="ml-2" />
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-tabs>
|
||||
<el-button @click="toggleFullscreen" class="fullScreen" icon="FullScreen"></el-button>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="host">
|
||||
<template #label>
|
||||
<el-tooltip class="box-item" effect="dark" :content="$t('terminal.hostList')" placement="right">
|
||||
<el-icon><Platform /></el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<HostTab ref="hostTabRef" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="command">
|
||||
<template #label>
|
||||
<el-tooltip class="box-item" effect="dark" :content="$t('terminal.quickCmd')" placement="right">
|
||||
<el-icon><Reading /></el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<CommandTab ref="commandTabRef" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :closable="false" name="newTabs">
|
||||
<template #label>
|
||||
<el-button
|
||||
v-popover="popoverRef"
|
||||
style="background-color: #ededed; border: 0"
|
||||
icon="Plus"
|
||||
></el-button>
|
||||
<el-popover ref="popoverRef" width="250px" trigger="hover" virtual-triggering persistent>
|
||||
<el-button-group style="width: 100%">
|
||||
<el-button @click="onNewSsh">New ssh</el-button>
|
||||
<el-button @click="onNewTab">New tab</el-button>
|
||||
</el-button-group>
|
||||
<el-input clearable style="margin-top: 5px" v-model="hostfilterInfo">
|
||||
<template #append><el-button icon="search" /></template>
|
||||
</el-input>
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
:expand-on-click-node="false"
|
||||
node-key="id"
|
||||
:default-expand-all="true"
|
||||
:data="hostTree"
|
||||
:props="defaultProps"
|
||||
:filter-node-method="filterHost"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<span class="custom-tree-node">
|
||||
<span>
|
||||
<a @click="onConn(node, data)">{{ node.label }}</a>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
<div v-if="terminalTabs.length === 0">
|
||||
<el-empty
|
||||
style="background-color: #000; height: calc(100vh - 150px)"
|
||||
:description="$t('terminal.emptyTerminal')"
|
||||
></el-empty>
|
||||
</div>
|
||||
</el-tabs>
|
||||
<el-button @click="toggleFullscreen" class="fullScreen" icon="FullScreen"></el-button>
|
||||
</div>
|
||||
<div v-if="activeNames === 'host'"><HostTab ref="hostTabRef" /></div>
|
||||
<div v-if="activeNames === 'command'"><CommandTab ref="commandTabRef" /></div>
|
||||
|
||||
<el-dialog v-model="connVisiable" :title="$t('terminal.addHost')" width="30%">
|
||||
<el-form ref="hostInfoRef" label-width="100px" label-position="left" :model="hostInfo" :rules="rules">
|
||||
@ -189,7 +183,7 @@ import screenfull from 'screenfull';
|
||||
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
|
||||
const routeTab = ref<string>('terminal');
|
||||
const activeNames = ref<string>('terminal');
|
||||
const hostTabRef = ref();
|
||||
const commandTabRef = ref();
|
||||
|
||||
@ -248,13 +242,13 @@ function toggleFullscreen() {
|
||||
screenfull.toggle();
|
||||
}
|
||||
}
|
||||
const handleClick = (tab: any) => {
|
||||
if (tab.paneName === 'host') {
|
||||
const handleChange = (tab: any) => {
|
||||
if (tab === 'host') {
|
||||
if (ctx) {
|
||||
ctx.refs[`hostTabRef`] && ctx.refs[`hostTabRef`].onInit();
|
||||
}
|
||||
}
|
||||
if (tab.paneName === 'command') {
|
||||
if (tab === 'command') {
|
||||
if (ctx) {
|
||||
ctx.refs[`commandTabRef`] && ctx.refs[`commandTabRef`].onInit();
|
||||
}
|
||||
@ -416,23 +410,23 @@ onBeforeMount(() => {
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.terminal-tabs {
|
||||
:deep .el-tabs__header {
|
||||
:deep(.el-tabs__header) {
|
||||
padding: 0;
|
||||
position: relative;
|
||||
margin: 0 0 3px 0;
|
||||
}
|
||||
:deep .el-tabs__nav {
|
||||
:deep(.el-tabs__nav) {
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
transition: transform var(--el-transition-duration);
|
||||
float: left;
|
||||
z-index: calc(var(--el-index-normal) + 1);
|
||||
}
|
||||
:deep .el-tabs__item {
|
||||
:deep(.el-tabs__item) {
|
||||
color: #575758;
|
||||
padding: 0 0px;
|
||||
}
|
||||
:deep .el-tabs__item.is-active {
|
||||
:deep(.el-tabs__item.is-active) {
|
||||
color: #ebeef5;
|
||||
background-color: #575758;
|
||||
}
|
||||
@ -454,4 +448,33 @@ onBeforeMount(() => {
|
||||
.el-tabs--top.el-tabs--card > .el-tabs__header .el-tabs__item:last-child {
|
||||
padding-right: 0px;
|
||||
}
|
||||
.topCard {
|
||||
--el-card-border-color: var(--el-border-color-light);
|
||||
--el-card-border-radius: 4px;
|
||||
--el-card-padding: 0px;
|
||||
--el-card-bg-color: var(--el-fill-color-blank);
|
||||
}
|
||||
.topButton .el-radio-button__inner {
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
background: var(--el-button-bg-color, var(--el-fill-color-blank));
|
||||
border: 0;
|
||||
font-weight: 350;
|
||||
border-left: 0;
|
||||
color: var(--el-button-text-color, var(--el-text-color-regular));
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
outline: 0;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: var(--el-transition-all);
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
padding: 8px 15px;
|
||||
font-size: var(--el-font-size-base);
|
||||
border-radius: 0;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="demo-collapse">
|
||||
<div>
|
||||
<el-card class="topCard">
|
||||
<el-radio-group v-model="activeNames">
|
||||
<el-radio-button class="topButton" size="large" label="all">全部</el-radio-button>
|
||||
|
@ -1,17 +1,303 @@
|
||||
<template>
|
||||
<el-card style="margin-top: 10px">
|
||||
<el-card style="margin-top: 20px">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>备份</span>
|
||||
<span>{{ $t('setting.backup') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-col :span="1"><br /></el-col>
|
||||
<el-col :span="8"></el-col>
|
||||
<el-button type="primary" @click="onCreate">
|
||||
{{ $t('commons.button.create') }}
|
||||
</el-button>
|
||||
<el-row :gutter="20" class="row-box" style="margin-top: 10px; margin-bottom: 20px">
|
||||
<el-col v-for="item in data" :key="item.id" :span="8">
|
||||
<el-card class="el-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span style="font-size: 16px; font-weight: 500">
|
||||
[{{ item.type === 'LOCAL' ? $t('setting.serverDisk') : item.type }}] {{ item.name }}
|
||||
</span>
|
||||
<div style="float: right">
|
||||
<el-button @click="onEdit(item)">{{ $t('commons.button.edit') }}</el-button>
|
||||
<el-button @click="onBatchDelete(item)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-form label-position="left" label-width="130px">
|
||||
<el-form-item v-if="item.type === 'LOCAL'" label="Dir">
|
||||
{{ item.varsJson['dir'] }}
|
||||
</el-form-item>
|
||||
<el-form-item v-if="hasBucket(item.type)" label="Access Key ID">
|
||||
{{ item.varsJson['accessKey'] }}
|
||||
</el-form-item>
|
||||
<el-form-item v-if="item.type === 'S3'" label="Region">
|
||||
{{ item.varsJson['region'] }}
|
||||
</el-form-item>
|
||||
<el-form-item v-if="hasBucket(item.type)" label="Endpoint">
|
||||
{{ item.varsJson['endpoint'] }}
|
||||
</el-form-item>
|
||||
<el-form-item v-if="hasBucket(item.type)" label="Bucket">
|
||||
{{ item.bucket }}
|
||||
</el-form-item>
|
||||
<el-form-item v-if="item.type === 'SFTP'" :label="$t('setting.address')">
|
||||
{{ item.varsJson['address'] }}
|
||||
</el-form-item>
|
||||
<el-form-item v-if="item.type === 'SFTP'" :label="$t('setting.port')">
|
||||
{{ item.varsJson['port'] }}
|
||||
</el-form-item>
|
||||
<el-form-item v-if="item.type === 'SFTP'" :label="$t('setting.username')">
|
||||
{{ item.varsJson['username'] }}
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.createdAt')">
|
||||
{{ dateFromat(0, 0, item.createdAt) }}
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-dialog @close="search" v-model="backupVisiable" :title="$t('setting.backupAccount')" width="30%">
|
||||
<el-form ref="formRef" label-position="left" :model="form" label-width="160px">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name" :rules="Rules.name">
|
||||
<el-input v-model="form.name" :disabled="operation === 'edit'" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
|
||||
<el-select style="width: 100%" v-model="form.type" :disabled="operation === 'edit'">
|
||||
<el-option
|
||||
v-for="item in typeOptions"
|
||||
:key="item.label"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.type === 'LOCAL'"
|
||||
label="Dir"
|
||||
prop="varsJson['dir']"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-input v-model="form.varsJson['dir']">
|
||||
<template #append>
|
||||
<FileList @choose="loadDir" :dir="true"></FileList>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="hasBucket(form.type)"
|
||||
label="Access Key ID"
|
||||
prop="varsJson.accessKey"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-input v-model="form.varsJson['accessKey']" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="hasBucket(form.type)"
|
||||
label="Secret Access Key"
|
||||
prop="credential"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-input show-password v-model="form.credential" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.type === 'S3'"
|
||||
label="Region"
|
||||
prop="varsJson.region"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-input v-model="form.varsJson['region']" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="hasBucket(form.type)"
|
||||
label="Endpoint"
|
||||
prop="varsJson.endpoint"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-input v-model="form.varsJson['endpoint']" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.type !== '' && hasBucket(form.type)"
|
||||
label="Bucket"
|
||||
prop="bucket"
|
||||
:rules="Rules.requiredSelect"
|
||||
>
|
||||
<el-select style="width: 80%" v-model="form.bucket">
|
||||
<el-option v-for="item in buckets" :key="item" :value="item" />
|
||||
</el-select>
|
||||
<el-button style="width: 20%" plain @click="getBuckets">
|
||||
{{ $t('setting.loadBucket') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<div v-if="form.type === 'SFTP'">
|
||||
<el-form-item :label="$t('setting.address')" prop="varsJson.address" :rules="Rules.requiredInput">
|
||||
<el-input v-model="form.varsJson['address']" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('setting.port')"
|
||||
prop="varsJson.port"
|
||||
:rules="[Rules.number, { max: 65535 }]"
|
||||
>
|
||||
<el-input v-model.number="form.varsJson['port']" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('setting.username')"
|
||||
prop="varsJson.username"
|
||||
:rules="[Rules.requiredInput]"
|
||||
>
|
||||
<el-input v-model="form.varsJson['username']" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.password')" prop="credential" :rules="[Rules.requiredInput]">
|
||||
<el-input type="password" show-password v-model="form.credential" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('setting.path')" prop="bucket">
|
||||
<el-input v-model="form.bucket" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="backupVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="onSubmit(formRef)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</el-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted } from 'vue';
|
||||
onMounted(() => {});
|
||||
<script setup lang="ts">
|
||||
import { dateFromat } from '@/utils/util';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { getBackupList, addBackup, editBackup, listBucket, deleteBackup } from '@/api/modules/backup';
|
||||
import { Backup } from '@/api/interface/backup';
|
||||
import i18n from '@/lang';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { ElForm, ElMessage } from 'element-plus';
|
||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
|
||||
const data = ref();
|
||||
const selects = ref<any>([]);
|
||||
const backupVisiable = ref<boolean>(false);
|
||||
const operation = ref<string>('create');
|
||||
|
||||
const paginationConfig = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 5,
|
||||
total: 0,
|
||||
});
|
||||
const backSearch = reactive({
|
||||
page: 1,
|
||||
pageSize: 5,
|
||||
});
|
||||
|
||||
const form = reactive({
|
||||
id: 0,
|
||||
name: '',
|
||||
type: 'LOCAL',
|
||||
bucket: '',
|
||||
credential: '',
|
||||
vars: '',
|
||||
varsJson: {},
|
||||
});
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const formRef = ref<FormInstance>();
|
||||
const typeOptions = ref([
|
||||
{ label: i18n.global.t('setting.serverDisk'), value: 'LOCAL' },
|
||||
{ label: 'OSS', value: 'OSS' },
|
||||
{ label: 'S3', value: 'S3' },
|
||||
{ label: 'SFTP', value: 'SFTP' },
|
||||
{ label: 'MINIO', value: 'MINIO' },
|
||||
]);
|
||||
const buckets = ref();
|
||||
|
||||
const search = async () => {
|
||||
backSearch.page = paginationConfig.currentPage;
|
||||
backSearch.pageSize = paginationConfig.pageSize;
|
||||
const res = await getBackupList(backSearch);
|
||||
data.value = res.data.items;
|
||||
for (const bac of data.value) {
|
||||
bac.varsJson = JSON.parse(bac.vars);
|
||||
}
|
||||
paginationConfig.total = res.data.total;
|
||||
};
|
||||
|
||||
const onCreate = () => {
|
||||
operation.value = 'create';
|
||||
form.id = 0;
|
||||
form.name = '';
|
||||
form.type = 'LOCAL';
|
||||
form.bucket = '';
|
||||
form.credential = '';
|
||||
form.vars = '';
|
||||
form.varsJson = {};
|
||||
backupVisiable.value = true;
|
||||
};
|
||||
|
||||
const onBatchDelete = async (row: Backup.BackupInfo | null) => {
|
||||
let ids: Array<number> = [];
|
||||
if (row === null) {
|
||||
selects.value.forEach((item: Backup.BackupInfo) => {
|
||||
ids.push(item.id);
|
||||
});
|
||||
} else {
|
||||
ids.push(row.id);
|
||||
}
|
||||
await useDeleteData(deleteBackup, { ids: ids }, 'commons.msg.delete', true);
|
||||
search();
|
||||
restForm();
|
||||
};
|
||||
|
||||
const onEdit = (row: Backup.BackupInfo) => {
|
||||
restForm();
|
||||
form.id = row.id;
|
||||
form.name = row.name;
|
||||
form.type = row.type;
|
||||
form.bucket = row.bucket;
|
||||
form.varsJson = JSON.parse(row.vars);
|
||||
operation.value = 'edit';
|
||||
backupVisiable.value = true;
|
||||
};
|
||||
|
||||
const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
form.vars = JSON.stringify(form.varsJson);
|
||||
if (form.id !== 0 && operation.value === 'edit') {
|
||||
await editBackup(form);
|
||||
} else if (form.id === 0 && operation.value === 'create') {
|
||||
await addBackup(form);
|
||||
} else {
|
||||
ElMessage.success(i18n.global.t('commons.msg.notSupportOperation'));
|
||||
return;
|
||||
}
|
||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||
restForm();
|
||||
search();
|
||||
backupVisiable.value = false;
|
||||
});
|
||||
};
|
||||
function hasBucket(val: string) {
|
||||
return val === 'OSS' || val === 'S3' || val === 'MINIO';
|
||||
}
|
||||
function restForm() {
|
||||
if (formRef.value) {
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
}
|
||||
|
||||
const getBuckets = async () => {
|
||||
const res = await listBucket({ type: form.type, vars: JSON.stringify(form.varsJson), credential: form.credential });
|
||||
buckets.value = res.data;
|
||||
};
|
||||
const loadDir = async (path: string) => {
|
||||
console.log(path);
|
||||
form.varsJson['dir'] = path;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
</script>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-form :model="mesForm" label-position="left" label-width="160px">
|
||||
<el-card style="margin-top: 10px">
|
||||
<el-card style="margin-top: 20px">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ $t('setting.message') }}</span>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<el-form :model="form" ref="panelFormRef" label-position="left" label-width="160px">
|
||||
<el-card style="margin-top: 10px">
|
||||
<el-card style="margin-top: 20px">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ $t('menu.monitor') }}</span>
|
||||
|
@ -178,7 +178,10 @@ type FormInstance = InstanceType<typeof ElForm>;
|
||||
const passFormRef = ref<FormInstance>();
|
||||
const passRules = reactive({
|
||||
oldPassword: [Rules.requiredInput],
|
||||
newPassword: [Rules.requiredInput, { min: 6, message: i18n.global.t('commons.rule.passwordLen'), trigger: 'blur' }],
|
||||
newPassword: [
|
||||
Rules.requiredInput,
|
||||
{ min: 6, message: i18n.global.t('commons.rule.commonPassword'), trigger: 'blur' },
|
||||
],
|
||||
newPasswordComplexity: [Rules.password],
|
||||
retryPassword: [Rules.requiredInput, { validator: checkPassword, trigger: 'blur' }],
|
||||
});
|
||||
|
@ -135,7 +135,7 @@
|
||||
<li>{{ $t('setting.mfaHelper3') }}</li>
|
||||
<el-input v-model="mfaCode"></el-input>
|
||||
<div style="margin-top: 10px; margin-bottom: 10px; float: right">
|
||||
<el-button @click="form.settingInfo.mfaStatus = 'disable'">
|
||||
<el-button @click="onCancelMfaBind">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button @click="onBind">{{ $t('commons.button.saveAndEnable') }}</el-button>
|
||||
@ -174,7 +174,6 @@ import i18n from '@/lang';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { dateFromat } from '@/utils/util';
|
||||
|
||||
// const emit = defineEmits(['on-save']);
|
||||
const emit = defineEmits<{ (e: 'on-save', formEl: FormInstance | undefined, key: string, val: any): void }>();
|
||||
|
||||
interface Props {
|
||||
@ -227,6 +226,11 @@ const onBind = async () => {
|
||||
isMFAShow.value = false;
|
||||
};
|
||||
|
||||
const onCancelMfaBind = async () => {
|
||||
form.settingInfo.mfaStatus = 'disable';
|
||||
isMFAShow.value = false;
|
||||
};
|
||||
|
||||
const submitTimeout = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
|
Loading…
Reference in New Issue
Block a user