mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-31 14:08:06 +08:00
feat: Add usage scope for backup accounts (#7716)
* feat: Add usage scope for backup accounts * fix: 解决计划任务列表不显示备份账号的问题 * feat: 统一备份文件大小获取接口
This commit is contained in:
parent
c4f9d29bcb
commit
78199a49ed
@ -39,13 +39,121 @@ func (b *BaseApi) SyncBackupAccount(c *gin.Context) {
|
|||||||
helper.SuccessWithOutData(c)
|
helper.SuccessWithOutData(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Tags Backup Account
|
||||||
|
// @Summary Create backup account
|
||||||
|
// @Description 创建备份账号
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.BackupOperate true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /backups [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建备份账号 [type]","formatEN":"create backup account [type]"}
|
||||||
|
func (b *BaseApi) CreateBackup(c *gin.Context) {
|
||||||
|
var req dto.BackupOperate
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := backupService.Create(req); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithOutData(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Backup Account
|
||||||
|
// @Summary Refresh token
|
||||||
|
// @Description 刷新 token
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.BackupOperate true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /backups/refresh/token [post]
|
||||||
|
func (b *BaseApi) RefreshToken(c *gin.Context) {
|
||||||
|
var req dto.OperateByID
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := backupService.RefreshToken(req); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithOutData(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Backup Account
|
||||||
|
// @Summary List buckets
|
||||||
|
// @Description 获取 bucket 列表
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.ForBuckets true "request"
|
||||||
|
// @Success 200 {array} string
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /buckets [post]
|
||||||
|
func (b *BaseApi) ListBuckets(c *gin.Context) {
|
||||||
|
var req dto.ForBuckets
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buckets, err := backupService.GetBuckets(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, buckets)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Backup Account
|
||||||
|
// @Summary Delete backup account
|
||||||
|
// @Description 删除备份账号
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.OperateByID true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /backups/del [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"backup_accounts","output_column":"type","output_value":"types"}],"formatZH":"删除备份账号 [types]","formatEN":"delete backup account [types]"}
|
||||||
|
func (b *BaseApi) DeleteBackup(c *gin.Context) {
|
||||||
|
var req dto.OperateByID
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := backupService.Delete(req.ID); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Backup Account
|
||||||
|
// @Summary Update backup account
|
||||||
|
// @Description 更新备份账号信息
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.BackupOperate true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /backups/update [post]
|
||||||
|
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新备份账号 [types]","formatEN":"update backup account [types]"}
|
||||||
|
func (b *BaseApi) UpdateBackup(c *gin.Context) {
|
||||||
|
var req dto.BackupOperate
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := backupService.Update(req); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// @Tags Backup Account
|
// @Tags Backup Account
|
||||||
// @Summary Load backup account options
|
// @Summary Load backup account options
|
||||||
// @Description 获取备份账号选项
|
// @Description 获取备份账号选项
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Success 200 {array} dto.BackupOption
|
// @Success 200 {array} dto.BackupOption
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /backup/options [get]
|
// @Router /backups/options [get]
|
||||||
func (b *BaseApi) LoadBackupOptions(c *gin.Context) {
|
func (b *BaseApi) LoadBackupOptions(c *gin.Context) {
|
||||||
list, err := backupService.LoadBackupOptions()
|
list, err := backupService.LoadBackupOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -55,6 +163,71 @@ func (b *BaseApi) LoadBackupOptions(c *gin.Context) {
|
|||||||
helper.SuccessWithData(c, list)
|
helper.SuccessWithData(c, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Tags Backup Account
|
||||||
|
// @Summary Search backup accounts with page
|
||||||
|
// @Description 获取备份账号列表
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.SearchPageWithType true "request"
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /backups/search [post]
|
||||||
|
func (b *BaseApi) SearchBackup(c *gin.Context) {
|
||||||
|
var req dto.SearchPageWithType
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
total, list, err := backupService.SearchWithPage(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.SuccessWithData(c, dto.PageResult{
|
||||||
|
Items: list,
|
||||||
|
Total: total,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Backup Account
|
||||||
|
// @Summary get local backup dir
|
||||||
|
// @Description 获取本地备份目录
|
||||||
|
// @Success 200
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /backups/local [get]
|
||||||
|
func (b *BaseApi) GetLocalDir(c *gin.Context) {
|
||||||
|
dir, err := backupService.GetLocalDir()
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.SuccessWithData(c, dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Tags Backup Account
|
||||||
|
// @Summary Page backup records
|
||||||
|
// @Description 获取备份记录列表分页
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.SearchForSize true "request"
|
||||||
|
// @Success 200 {object} dto.RecordFileSize
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /backups/record/size [post]
|
||||||
|
func (b *BaseApi) LoadBackupRecordSize(c *gin.Context) {
|
||||||
|
var req dto.SearchForSize
|
||||||
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := backupRecordService.LoadRecordSize(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.SuccessWithData(c, list)
|
||||||
|
}
|
||||||
|
|
||||||
// @Tags Backup Account
|
// @Tags Backup Account
|
||||||
// @Summary Page backup records
|
// @Summary Page backup records
|
||||||
// @Description 获取备份记录列表分页
|
// @Description 获取备份记录列表分页
|
||||||
@ -62,14 +235,14 @@ func (b *BaseApi) LoadBackupOptions(c *gin.Context) {
|
|||||||
// @Param request body dto.RecordSearch true "request"
|
// @Param request body dto.RecordSearch true "request"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /backup/record/search [post]
|
// @Router /backups/record/search [post]
|
||||||
func (b *BaseApi) SearchBackupRecords(c *gin.Context) {
|
func (b *BaseApi) SearchBackupRecords(c *gin.Context) {
|
||||||
var req dto.RecordSearch
|
var req dto.RecordSearch
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := backupService.SearchRecordsWithPage(req)
|
total, list, err := backupRecordService.SearchRecordsWithPage(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@ -88,14 +261,14 @@ func (b *BaseApi) SearchBackupRecords(c *gin.Context) {
|
|||||||
// @Param request body dto.RecordSearchByCronjob true "request"
|
// @Param request body dto.RecordSearchByCronjob true "request"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /backup/record/search/bycronjob [post]
|
// @Router /backups/record/search/bycronjob [post]
|
||||||
func (b *BaseApi) SearchBackupRecordsByCronjob(c *gin.Context) {
|
func (b *BaseApi) SearchBackupRecordsByCronjob(c *gin.Context) {
|
||||||
var req dto.RecordSearchByCronjob
|
var req dto.RecordSearchByCronjob
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
total, list, err := backupService.SearchRecordsByCronjobWithPage(req)
|
total, list, err := backupRecordService.SearchRecordsByCronjobWithPage(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@ -122,7 +295,7 @@ func (b *BaseApi) DownloadRecord(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
filePath, err := backupService.DownloadRecord(req)
|
filePath, err := backupRecordService.DownloadRecord(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
@ -145,7 +318,7 @@ func (b *BaseApi) DeleteBackupRecord(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := backupService.BatchDeleteRecord(req.Ids); err != nil {
|
if err := backupRecordService.BatchDeleteRecord(req.Ids); err != nil {
|
||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -159,14 +332,14 @@ func (b *BaseApi) DeleteBackupRecord(c *gin.Context) {
|
|||||||
// @Param request body dto.OperateByID true "request"
|
// @Param request body dto.OperateByID true "request"
|
||||||
// @Success 200 {array} string
|
// @Success 200 {array} string
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /backup/search/files [post]
|
// @Router /backups/search/files [post]
|
||||||
func (b *BaseApi) LoadFilesFromBackup(c *gin.Context) {
|
func (b *BaseApi) LoadFilesFromBackup(c *gin.Context) {
|
||||||
var req dto.OperateByID
|
var req dto.OperateByID
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := backupService.ListFiles(req)
|
data := backupRecordService.ListFiles(req)
|
||||||
helper.SuccessWithData(c, data)
|
helper.SuccessWithData(c, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,7 +350,7 @@ func (b *BaseApi) LoadFilesFromBackup(c *gin.Context) {
|
|||||||
// @Param request body dto.CommonBackup true "request"
|
// @Param request body dto.CommonBackup true "request"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /backup/backup [post]
|
// @Router /backups/backup [post]
|
||||||
// @x-panel-log {"bodyKeys":["type","name","detailName"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"备份 [type] 数据 [name][detailName]","formatEN":"backup [type] data [name][detailName]"}
|
// @x-panel-log {"bodyKeys":["type","name","detailName"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"备份 [type] 数据 [name][detailName]","formatEN":"backup [type] data [name][detailName]"}
|
||||||
func (b *BaseApi) Backup(c *gin.Context) {
|
func (b *BaseApi) Backup(c *gin.Context) {
|
||||||
var req dto.CommonBackup
|
var req dto.CommonBackup
|
||||||
@ -222,7 +395,7 @@ func (b *BaseApi) Backup(c *gin.Context) {
|
|||||||
// @Param request body dto.CommonRecover true "request"
|
// @Param request body dto.CommonRecover true "request"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /backup/recover [post]
|
// @Router /backups/recover [post]
|
||||||
// @x-panel-log {"bodyKeys":["type","name","detailName","file"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"从 [file] 恢复 [type] 数据 [name][detailName]","formatEN":"recover [type] data [name][detailName] from [file]"}
|
// @x-panel-log {"bodyKeys":["type","name","detailName","file"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"从 [file] 恢复 [type] 数据 [name][detailName]","formatEN":"recover [type] data [name][detailName] from [file]"}
|
||||||
func (b *BaseApi) Recover(c *gin.Context) {
|
func (b *BaseApi) Recover(c *gin.Context) {
|
||||||
var req dto.CommonRecover
|
var req dto.CommonRecover
|
||||||
@ -230,7 +403,7 @@ func (b *BaseApi) Recover(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadPath, err := backupService.DownloadRecord(dto.DownloadRecord{
|
downloadPath, err := backupRecordService.DownloadRecord(dto.DownloadRecord{
|
||||||
DownloadAccountID: req.DownloadAccountID,
|
DownloadAccountID: req.DownloadAccountID,
|
||||||
FileDir: path.Dir(req.File),
|
FileDir: path.Dir(req.File),
|
||||||
FileName: path.Base(req.File),
|
FileName: path.Base(req.File),
|
||||||
@ -277,7 +450,7 @@ func (b *BaseApi) Recover(c *gin.Context) {
|
|||||||
// @Param request body dto.CommonRecover true "request"
|
// @Param request body dto.CommonRecover true "request"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /backup/recover/byupload [post]
|
// @Router /backups/recover/byupload [post]
|
||||||
// @x-panel-log {"bodyKeys":["type","name","detailName","file"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"从 [file] 恢复 [type] 数据 [name][detailName]","formatEN":"recover [type] data [name][detailName] from [file]"}
|
// @x-panel-log {"bodyKeys":["type","name","detailName","file"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"从 [file] 恢复 [type] 数据 [name][detailName]","formatEN":"recover [type] data [name][detailName] from [file]"}
|
||||||
func (b *BaseApi) RecoverByUpload(c *gin.Context) {
|
func (b *BaseApi) RecoverByUpload(c *gin.Context) {
|
||||||
var req dto.CommonRecover
|
var req dto.CommonRecover
|
||||||
|
@ -40,8 +40,9 @@ var (
|
|||||||
ftpService = service.NewIFtpService()
|
ftpService = service.NewIFtpService()
|
||||||
clamService = service.NewIClamService()
|
clamService = service.NewIClamService()
|
||||||
|
|
||||||
settingService = service.NewISettingService()
|
settingService = service.NewISettingService()
|
||||||
backupService = service.NewIBackupService()
|
backupService = service.NewIBackupService()
|
||||||
|
backupRecordService = service.NewIBackupRecordService()
|
||||||
|
|
||||||
websiteService = service.NewIWebsiteService()
|
websiteService = service.NewIWebsiteService()
|
||||||
websiteDnsAccountService = service.NewIWebsiteDnsAccountService()
|
websiteDnsAccountService = service.NewIWebsiteDnsAccountService()
|
||||||
|
@ -134,28 +134,6 @@ func (b *BaseApi) SearchSnapshot(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags System Setting
|
|
||||||
// @Summary Load system snapshot size
|
|
||||||
// @Description 获取系统快照文件大小
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.SearchWithPage true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /settings/snapshot/size [post]
|
|
||||||
func (b *BaseApi) LoadSnapshotSize(c *gin.Context) {
|
|
||||||
var req dto.SearchWithPage
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
accounts, err := snapshotService.LoadSize(req)
|
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
helper.SuccessWithData(c, accounts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags System Setting
|
// @Tags System Setting
|
||||||
// @Summary Recover system backup
|
// @Summary Recover system backup
|
||||||
// @Description 从系统快照恢复
|
// @Description 从系统快照恢复
|
||||||
|
@ -4,6 +4,42 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type BackupOperate struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type" validate:"required"`
|
||||||
|
IsPublic bool `json:"isPublic"`
|
||||||
|
Bucket string `json:"bucket"`
|
||||||
|
AccessKey string `json:"accessKey"`
|
||||||
|
Credential string `json:"credential"`
|
||||||
|
BackupPath string `json:"backupPath"`
|
||||||
|
Vars string `json:"vars" validate:"required"`
|
||||||
|
|
||||||
|
RememberAuth bool `json:"rememberAuth"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BackupInfo struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
IsPublic bool `json:"isPublic"`
|
||||||
|
Bucket string `json:"bucket"`
|
||||||
|
AccessKey string `json:"accessKey"`
|
||||||
|
Credential string `json:"credential"`
|
||||||
|
BackupPath string `json:"backupPath"`
|
||||||
|
Vars string `json:"vars"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
|
||||||
|
RememberAuth bool `json:"rememberAuth"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ForBuckets struct {
|
||||||
|
Type string `json:"type" validate:"required"`
|
||||||
|
AccessKey string `json:"accessKey"`
|
||||||
|
Credential string `json:"credential" validate:"required"`
|
||||||
|
Vars string `json:"vars" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
type SyncFromMaster struct {
|
type SyncFromMaster struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Operation string `json:"operation" validate:"required,oneof=create delete update"`
|
Operation string `json:"operation" validate:"required,oneof=create delete update"`
|
||||||
@ -11,9 +47,10 @@ type SyncFromMaster struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BackupOption struct {
|
type BackupOption struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
IsPublic bool `json:"isPublic"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommonBackup struct {
|
type CommonBackup struct {
|
||||||
@ -55,7 +92,6 @@ type BackupRecords struct {
|
|||||||
DownloadAccountID uint `json:"downloadAccountID"`
|
DownloadAccountID uint `json:"downloadAccountID"`
|
||||||
FileDir string `json:"fileDir"`
|
FileDir string `json:"fileDir"`
|
||||||
FileName string `json:"fileName"`
|
FileName string `json:"fileName"`
|
||||||
Size int64 `json:"size"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DownloadRecord struct {
|
type DownloadRecord struct {
|
||||||
@ -63,3 +99,17 @@ type DownloadRecord struct {
|
|||||||
FileDir string `json:"fileDir" validate:"required"`
|
FileDir string `json:"fileDir" validate:"required"`
|
||||||
FileName string `json:"fileName" validate:"required"`
|
FileName string `json:"fileName" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SearchForSize struct {
|
||||||
|
PageInfo
|
||||||
|
Type string `json:"type" validate:"required"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
DetailName string `json:"detailName"`
|
||||||
|
Info string `json:"info"`
|
||||||
|
CronjobID uint `json:"cronjobID"`
|
||||||
|
}
|
||||||
|
type RecordFileSize struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
}
|
||||||
|
@ -5,6 +5,12 @@ type SearchWithPage struct {
|
|||||||
Info string `json:"info"`
|
Info string `json:"info"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SearchPageWithType struct {
|
||||||
|
PageInfo
|
||||||
|
Info string `json:"info"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
type PageInfo struct {
|
type PageInfo struct {
|
||||||
Page int `json:"page" validate:"required,number"`
|
Page int `json:"page" validate:"required,number"`
|
||||||
PageSize int `json:"pageSize" validate:"required,number"`
|
PageSize int `json:"pageSize" validate:"required,number"`
|
||||||
|
@ -106,17 +106,17 @@ type CronjobInfo struct {
|
|||||||
ContainerName string `json:"containerName"`
|
ContainerName string `json:"containerName"`
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
|
|
||||||
AppID string `json:"appID"`
|
AppID string `json:"appID"`
|
||||||
Website string `json:"website"`
|
Website string `json:"website"`
|
||||||
ExclusionRules string `json:"exclusionRules"`
|
ExclusionRules string `json:"exclusionRules"`
|
||||||
DBType string `json:"dbType"`
|
DBType string `json:"dbType"`
|
||||||
DBName string `json:"dbName"`
|
DBName string `json:"dbName"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
IsDir bool `json:"isDir"`
|
IsDir bool `json:"isDir"`
|
||||||
SourceDir string `json:"sourceDir"`
|
SourceDir string `json:"sourceDir"`
|
||||||
SourceAccountIDs string `json:"sourceAccountIDs"`
|
SourceAccounts []string `json:"sourceAccounts"`
|
||||||
DownloadAccountID uint `json:"downloadAccountID"`
|
DownloadAccount string `json:"downloadAccount"`
|
||||||
RetainCopies int `json:"retainCopies"`
|
RetainCopies int `json:"retainCopies"`
|
||||||
|
|
||||||
LastRecordStatus string `json:"lastRecordStatus"`
|
LastRecordStatus string `json:"lastRecordStatus"`
|
||||||
LastRecordTime string `json:"lastRecordTime"`
|
LastRecordTime string `json:"lastRecordTime"`
|
||||||
|
@ -88,8 +88,8 @@ type SnapshotInfo struct {
|
|||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description" validate:"max=256"`
|
Description string `json:"description" validate:"max=256"`
|
||||||
From string `json:"from"`
|
SourceAccounts []string `json:"sourceAccounts"`
|
||||||
DefaultDownload string `json:"defaultDownload"`
|
DownloadAccount string `json:"downloadAccount"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
@ -108,11 +108,3 @@ type SnapshotInfo struct {
|
|||||||
RollbackMessage string `json:"rollbackMessage"`
|
RollbackMessage string `json:"rollbackMessage"`
|
||||||
LastRollbackedAt string `json:"lastRollbackedAt"`
|
LastRollbackedAt string `json:"lastRollbackedAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SnapshotFile struct {
|
|
||||||
ID uint `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
From string `json:"from"`
|
|
||||||
DefaultDownload string `json:"defaultDownload"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
}
|
|
||||||
|
@ -4,6 +4,7 @@ type BackupAccount struct {
|
|||||||
BaseModel
|
BaseModel
|
||||||
Name string `gorm:"not null;default:''" json:"name"`
|
Name string `gorm:"not null;default:''" json:"name"`
|
||||||
Type string `gorm:"not null;default:''" json:"type"`
|
Type string `gorm:"not null;default:''" json:"type"`
|
||||||
|
IsPublic bool `json:"isPublic"`
|
||||||
Bucket string `json:"bucket"`
|
Bucket string `json:"bucket"`
|
||||||
AccessKey string `json:"accessKey"`
|
AccessKey string `json:"accessKey"`
|
||||||
Credential string `json:"credential"`
|
Credential string `json:"credential"`
|
||||||
@ -13,8 +14,6 @@ type BackupAccount struct {
|
|||||||
RememberAuth bool `json:"rememberAuth"`
|
RememberAuth bool `json:"rememberAuth"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Source ---> SourceAccountIDs
|
|
||||||
// BackupType ---> DownloadAccountID
|
|
||||||
type BackupRecord struct {
|
type BackupRecord struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
From string `json:"from"`
|
From string `json:"from"`
|
||||||
|
@ -13,6 +13,7 @@ type BackupRepo struct{}
|
|||||||
type IBackupRepo interface {
|
type IBackupRepo interface {
|
||||||
Get(opts ...DBOption) (model.BackupAccount, error)
|
Get(opts ...DBOption) (model.BackupAccount, error)
|
||||||
List(opts ...DBOption) ([]model.BackupAccount, error)
|
List(opts ...DBOption) ([]model.BackupAccount, error)
|
||||||
|
Page(limit, offset int, opts ...DBOption) (int64, []model.BackupAccount, error)
|
||||||
Create(backup *model.BackupAccount) error
|
Create(backup *model.BackupAccount) error
|
||||||
Save(backup *model.BackupAccount) error
|
Save(backup *model.BackupAccount) error
|
||||||
Delete(opts ...DBOption) error
|
Delete(opts ...DBOption) error
|
||||||
|
@ -29,7 +29,7 @@ type ICronjobRepo interface {
|
|||||||
Update(id uint, vars map[string]interface{}) error
|
Update(id uint, vars map[string]interface{}) error
|
||||||
Delete(opts ...DBOption) error
|
Delete(opts ...DBOption) error
|
||||||
DeleteRecord(opts ...DBOption) error
|
DeleteRecord(opts ...DBOption) error
|
||||||
StartRecords(cronjobID uint, targetPath string) model.JobRecords
|
StartRecords(cronjobID uint, targetPath, cronjobType string) model.JobRecords
|
||||||
UpdateRecords(id uint, vars map[string]interface{}) error
|
UpdateRecords(id uint, vars map[string]interface{}) error
|
||||||
EndRecords(record model.JobRecords, status, message, records string)
|
EndRecords(record model.JobRecords, status, message, records string)
|
||||||
PageRecords(page, size int, opts ...DBOption) (int64, []model.JobRecords, error)
|
PageRecords(page, size int, opts ...DBOption) (int64, []model.JobRecords, error)
|
||||||
@ -143,11 +143,13 @@ func (c *CronjobRepo) WithByRecordDropID(id int) DBOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *CronjobRepo) StartRecords(cronjobID uint, targetPath string) model.JobRecords {
|
func (u *CronjobRepo) StartRecords(cronjobID uint, targetPath, cronjobType string) model.JobRecords {
|
||||||
var record model.JobRecords
|
var record model.JobRecords
|
||||||
record.StartTime = time.Now()
|
record.StartTime = time.Now()
|
||||||
record.CronjobID = cronjobID
|
record.CronjobID = cronjobID
|
||||||
record.TaskID = uuid.New().String()
|
if cronjobType != "directory" && cronjobType != "log" {
|
||||||
|
record.TaskID = uuid.New().String()
|
||||||
|
}
|
||||||
record.Status = constant.StatusWaiting
|
record.Status = constant.StatusWaiting
|
||||||
if err := global.DB.Create(&record).Error; err != nil {
|
if err := global.DB.Create(&record).Error; err != nil {
|
||||||
global.LOG.Errorf("create record status failed, err: %v", err)
|
global.LOG.Errorf("create record status failed, err: %v", err)
|
||||||
|
@ -5,10 +5,6 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -21,6 +17,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/nginx"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
@ -562,17 +563,18 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
|||||||
backUpApp := func(t *task.Task) error {
|
backUpApp := func(t *task.Task) error {
|
||||||
if req.Backup {
|
if req.Backup {
|
||||||
backupService := NewIBackupService()
|
backupService := NewIBackupService()
|
||||||
|
backupRecordService := NewIBackupRecordService()
|
||||||
fileName := fmt.Sprintf("upgrade_backup_%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
|
fileName := fmt.Sprintf("upgrade_backup_%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
|
||||||
backupRecord, err := backupService.AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name, FileName: fileName})
|
backupRecord, err := backupService.AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name, FileName: fileName})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
backups, _ := backupService.ListAppRecords(install.App.Key, install.Name, "upgrade_backup")
|
backups, _ := backupRecordService.ListAppRecords(install.App.Key, install.Name, "upgrade_backup")
|
||||||
if len(backups) > 3 {
|
if len(backups) > 3 {
|
||||||
backupsToDelete := backups[:len(backups)-3]
|
backupsToDelete := backups[:len(backups)-3]
|
||||||
var deleteIDs []uint
|
var deleteIDs []uint
|
||||||
for _, backup := range backupsToDelete {
|
for _, backup := range backupsToDelete {
|
||||||
deleteIDs = append(deleteIDs, backup.ID)
|
deleteIDs = append(deleteIDs, backup.ID)
|
||||||
}
|
}
|
||||||
_ = backupService.BatchDeleteRecord(deleteIDs)
|
_ = backupRecordService.BatchDeleteRecord(deleteIDs)
|
||||||
}
|
}
|
||||||
backupFile = path.Join(global.CONF.System.Backup, backupRecord.FileDir, backupRecord.FileName)
|
backupFile = path.Join(global.CONF.System.Backup, backupRecord.FileDir, backupRecord.FileName)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"bufio"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
@ -19,7 +19,9 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
"github.com/1Panel-dev/1Panel/agent/global"
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/cloud_storage"
|
"github.com/1Panel-dev/1Panel/agent/utils/cloud_storage"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/cloud_storage/client"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
@ -31,15 +33,13 @@ type IBackupService interface {
|
|||||||
Sync(req dto.SyncFromMaster) error
|
Sync(req dto.SyncFromMaster) error
|
||||||
|
|
||||||
LoadBackupOptions() ([]dto.BackupOption, error)
|
LoadBackupOptions() ([]dto.BackupOption, error)
|
||||||
|
SearchWithPage(search dto.SearchPageWithType) (int64, interface{}, error)
|
||||||
SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error)
|
Create(backupDto dto.BackupOperate) error
|
||||||
SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error)
|
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
|
||||||
DownloadRecord(info dto.DownloadRecord) (string, error)
|
Update(req dto.BackupOperate) error
|
||||||
DeleteRecordByName(backupType, name, detailName string, withDeleteFile bool) error
|
Delete(id uint) error
|
||||||
BatchDeleteRecord(ids []uint) error
|
RefreshToken(req dto.OperateByID) error
|
||||||
ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error)
|
GetLocalDir() (string, error)
|
||||||
|
|
||||||
ListFiles(req dto.OperateByID) []string
|
|
||||||
|
|
||||||
MysqlBackup(db dto.CommonBackup) error
|
MysqlBackup(db dto.CommonBackup) error
|
||||||
PostgresqlBackup(db dto.CommonBackup) error
|
PostgresqlBackup(db dto.CommonBackup) error
|
||||||
@ -62,6 +62,287 @@ func NewIBackupService() IBackupService {
|
|||||||
return &BackupService{}
|
return &BackupService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *BackupService) GetLocalDir() (string, error) {
|
||||||
|
account, err := backupRepo.Get(repo.WithByType(constant.Local))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return account.BackupPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupService) SearchWithPage(req dto.SearchPageWithType) (int64, interface{}, error) {
|
||||||
|
options := []repo.DBOption{repo.WithOrderBy("created_at desc")}
|
||||||
|
if len(req.Type) != 0 {
|
||||||
|
options = append(options, repo.WithByType(req.Type))
|
||||||
|
}
|
||||||
|
if len(req.Info) != 0 {
|
||||||
|
options = append(options, repo.WithByType(req.Info))
|
||||||
|
}
|
||||||
|
count, accounts, err := backupRepo.Page(req.Page, req.PageSize, options...)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
var data []dto.BackupInfo
|
||||||
|
for _, account := range accounts {
|
||||||
|
var item dto.BackupInfo
|
||||||
|
if err := copier.Copy(&item, &account); err != nil {
|
||||||
|
global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err)
|
||||||
|
}
|
||||||
|
if item.Type != constant.Sftp && item.Type != constant.Local {
|
||||||
|
item.BackupPath = path.Join("/", strings.TrimPrefix(item.BackupPath, "/"))
|
||||||
|
}
|
||||||
|
if !item.RememberAuth {
|
||||||
|
item.AccessKey = ""
|
||||||
|
item.Credential = ""
|
||||||
|
if account.Type == constant.Sftp {
|
||||||
|
varMap := make(map[string]interface{})
|
||||||
|
if err := json.Unmarshal([]byte(item.Vars), &varMap); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete(varMap, "passPhrase")
|
||||||
|
itemVars, _ := json.Marshal(varMap)
|
||||||
|
item.Vars = string(itemVars)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item.AccessKey = base64.StdEncoding.EncodeToString([]byte(item.AccessKey))
|
||||||
|
item.Credential = base64.StdEncoding.EncodeToString([]byte(item.Credential))
|
||||||
|
}
|
||||||
|
|
||||||
|
if account.Type == constant.OneDrive || account.Type == constant.ALIYUN || account.Type == constant.GoogleDrive {
|
||||||
|
varMap := make(map[string]interface{})
|
||||||
|
if err := json.Unmarshal([]byte(item.Vars), &varMap); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete(varMap, "refresh_token")
|
||||||
|
delete(varMap, "drive_id")
|
||||||
|
itemVars, _ := json.Marshal(varMap)
|
||||||
|
item.Vars = string(itemVars)
|
||||||
|
}
|
||||||
|
data = append(data, item)
|
||||||
|
}
|
||||||
|
return count, data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupService) Create(req dto.BackupOperate) error {
|
||||||
|
if req.Type == constant.Local {
|
||||||
|
return buserr.New(constant.ErrBackupLocalCreate)
|
||||||
|
}
|
||||||
|
if req.Type != constant.Sftp && req.BackupPath != "/" {
|
||||||
|
req.BackupPath = strings.TrimPrefix(req.BackupPath, "/")
|
||||||
|
}
|
||||||
|
backup, _ := backupRepo.Get(repo.WithByName(req.Name))
|
||||||
|
if backup.ID != 0 {
|
||||||
|
return constant.ErrRecordExist
|
||||||
|
}
|
||||||
|
if err := copier.Copy(&backup, &req); err != nil {
|
||||||
|
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||||
|
}
|
||||||
|
itemAccessKey, err := base64.StdEncoding.DecodeString(backup.AccessKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
backup.AccessKey = string(itemAccessKey)
|
||||||
|
itemCredential, err := base64.StdEncoding.DecodeString(backup.Credential)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
backup.Credential = string(itemCredential)
|
||||||
|
|
||||||
|
if req.Type == constant.OneDrive || req.Type == constant.GoogleDrive {
|
||||||
|
if err := loadRefreshTokenByCode(&backup); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.Type != "LOCAL" {
|
||||||
|
isOk, err := u.checkBackupConn(&backup)
|
||||||
|
if err != nil || !isOk {
|
||||||
|
return buserr.WithMap(constant.ErrBackupCheck, map[string]interface{}{"err": err.Error()}, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
backup.AccessKey, err = encrypt.StringEncrypt(backup.AccessKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
backup.Credential, err = encrypt.StringEncrypt(backup.Credential)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := backupRepo.Create(&backup); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupService) GetBuckets(req dto.ForBuckets) ([]interface{}, error) {
|
||||||
|
itemAccessKey, err := base64.StdEncoding.DecodeString(req.AccessKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.AccessKey = string(itemAccessKey)
|
||||||
|
itemCredential, err := base64.StdEncoding.DecodeString(req.Credential)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Credential = string(itemCredential)
|
||||||
|
|
||||||
|
varMap := make(map[string]interface{})
|
||||||
|
if err := json.Unmarshal([]byte(req.Vars), &varMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch req.Type {
|
||||||
|
case constant.Sftp, constant.WebDAV:
|
||||||
|
varMap["username"] = req.AccessKey
|
||||||
|
varMap["password"] = req.Credential
|
||||||
|
case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo:
|
||||||
|
varMap["accessKey"] = req.AccessKey
|
||||||
|
varMap["secretKey"] = req.Credential
|
||||||
|
}
|
||||||
|
client, err := cloud_storage.NewCloudStorageClient(req.Type, varMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return client.ListBuckets()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupService) Delete(id uint) error {
|
||||||
|
backup, _ := backupRepo.Get(repo.WithByID(id))
|
||||||
|
if backup.ID == 0 {
|
||||||
|
return constant.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
if backup.Type == constant.Local {
|
||||||
|
return buserr.New(constant.ErrBackupLocalDelete)
|
||||||
|
}
|
||||||
|
return backupRepo.Delete(repo.WithByID(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupService) Update(req dto.BackupOperate) error {
|
||||||
|
backup, _ := backupRepo.Get(repo.WithByID(req.ID))
|
||||||
|
if backup.ID == 0 {
|
||||||
|
return constant.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
if req.Type != constant.Sftp && req.Type != constant.Local && req.BackupPath != "/" {
|
||||||
|
req.BackupPath = strings.TrimPrefix(req.BackupPath, "/")
|
||||||
|
}
|
||||||
|
var newBackup model.BackupAccount
|
||||||
|
if err := copier.Copy(&newBackup, &req); err != nil {
|
||||||
|
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||||
|
}
|
||||||
|
itemAccessKey, err := base64.StdEncoding.DecodeString(newBackup.AccessKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newBackup.AccessKey = string(itemAccessKey)
|
||||||
|
itemCredential, err := base64.StdEncoding.DecodeString(newBackup.Credential)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newBackup.Credential = string(itemCredential)
|
||||||
|
if backup.Type == constant.Local {
|
||||||
|
if newBackup.Vars != backup.Vars {
|
||||||
|
oldPath := backup.BackupPath
|
||||||
|
newPath := newBackup.BackupPath
|
||||||
|
if strings.HasSuffix(newPath, "/") && newPath != "/" {
|
||||||
|
newPath = newPath[:strings.LastIndex(newPath, "/")]
|
||||||
|
}
|
||||||
|
if err := files.NewFileOp().CopyDir(oldPath, newPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if newBackup.Type == constant.OneDrive || newBackup.Type == constant.GoogleDrive {
|
||||||
|
if err := loadRefreshTokenByCode(&backup); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if backup.Type != "LOCAL" {
|
||||||
|
isOk, err := u.checkBackupConn(&newBackup)
|
||||||
|
if err != nil || !isOk {
|
||||||
|
return buserr.WithMap("ErrBackupCheck", map[string]interface{}{"err": err.Error()}, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newBackup.AccessKey, err = encrypt.StringEncrypt(newBackup.AccessKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newBackup.Credential, err = encrypt.StringEncrypt(newBackup.Credential)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newBackup.ID = backup.ID
|
||||||
|
if err := backupRepo.Save(&newBackup); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupService) RefreshToken(req dto.OperateByID) error {
|
||||||
|
backup, _ := backupRepo.Get(repo.WithByID(req.ID))
|
||||||
|
if backup.ID == 0 {
|
||||||
|
return constant.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
varMap := make(map[string]interface{})
|
||||||
|
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||||
|
return fmt.Errorf("Failed to refresh %s - %s token, please retry, err: %v", backup.Type, backup.Name, err)
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
refreshToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch backup.Type {
|
||||||
|
case constant.OneDrive:
|
||||||
|
refreshToken, err = client.RefreshToken("refresh_token", "refreshToken", varMap)
|
||||||
|
case constant.GoogleDrive:
|
||||||
|
refreshToken, err = client.RefreshGoogleToken("refresh_token", "refreshToken", varMap)
|
||||||
|
case constant.ALIYUN:
|
||||||
|
refreshToken, err = client.RefreshALIToken(varMap)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
varMap["refresh_status"] = constant.StatusFailed
|
||||||
|
varMap["refresh_msg"] = err.Error()
|
||||||
|
return fmt.Errorf("Failed to refresh %s-%s token, please retry, err: %v", backup.Type, backup.Name, err)
|
||||||
|
}
|
||||||
|
varMap["refresh_status"] = constant.StatusSuccess
|
||||||
|
varMap["refresh_time"] = time.Now().Format(constant.DateTimeLayout)
|
||||||
|
varMap["refresh_token"] = refreshToken
|
||||||
|
|
||||||
|
varsItem, _ := json.Marshal(varMap)
|
||||||
|
backup.Vars = string(varsItem)
|
||||||
|
return backupRepo.Save(&backup)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, error) {
|
||||||
|
client, err := newClient(backup)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
fileItem := path.Join(global.CONF.System.BaseDir, "1panel/tmp/test/1panel")
|
||||||
|
if _, err := os.Stat(path.Dir(fileItem)); err != nil && os.IsNotExist(err) {
|
||||||
|
if err = os.MkdirAll(path.Dir(fileItem), os.ModePerm); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file, err := os.OpenFile(fileItem, os.O_WRONLY|os.O_CREATE, constant.FilePerm)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
write := bufio.NewWriter(file)
|
||||||
|
_, _ = write.WriteString("1Panel 备份账号测试文件。\n")
|
||||||
|
_, _ = write.WriteString("1Panel 備份賬號測試文件。\n")
|
||||||
|
_, _ = write.WriteString("1Panel Backs up account test files.\n")
|
||||||
|
_, _ = write.WriteString("1Panelアカウントのテストファイルをバックアップします。\n")
|
||||||
|
write.Flush()
|
||||||
|
|
||||||
|
targetPath := path.Join(backup.BackupPath, "test/1panel")
|
||||||
|
if backup.Type != constant.Sftp && backup.Type != constant.Local && targetPath != "/" {
|
||||||
|
targetPath = strings.TrimPrefix(targetPath, "/")
|
||||||
|
}
|
||||||
|
return client.Upload(fileItem, targetPath)
|
||||||
|
}
|
||||||
|
|
||||||
func (u *BackupService) Sync(req dto.SyncFromMaster) error {
|
func (u *BackupService) Sync(req dto.SyncFromMaster) error {
|
||||||
var accountItem model.BackupAccount
|
var accountItem model.BackupAccount
|
||||||
if err := json.Unmarshal([]byte(req.Data), &accountItem); err != nil {
|
if err := json.Unmarshal([]byte(req.Data), &accountItem); err != nil {
|
||||||
@ -109,48 +390,6 @@ func (u *BackupService) LoadBackupOptions() ([]dto.BackupOption, error) {
|
|||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *BackupService) SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error) {
|
|
||||||
total, records, err := backupRepo.PageRecord(
|
|
||||||
search.Page, search.PageSize,
|
|
||||||
repo.WithOrderBy("created_at desc"),
|
|
||||||
repo.WithByName(search.Name),
|
|
||||||
repo.WithByType(search.Type),
|
|
||||||
repo.WithByDetailName(search.DetailName),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if total == 0 {
|
|
||||||
return 0, nil, nil
|
|
||||||
}
|
|
||||||
datas, err := u.loadRecordSize(records)
|
|
||||||
sort.Slice(datas, func(i, j int) bool {
|
|
||||||
return datas[i].CreatedAt.After(datas[j].CreatedAt)
|
|
||||||
})
|
|
||||||
return total, datas, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *BackupService) SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error) {
|
|
||||||
total, records, err := backupRepo.PageRecord(
|
|
||||||
search.Page, search.PageSize,
|
|
||||||
repo.WithOrderBy("created_at desc"),
|
|
||||||
backupRepo.WithByCronID(search.CronjobID),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if total == 0 {
|
|
||||||
return 0, nil, nil
|
|
||||||
}
|
|
||||||
datas, err := u.loadRecordSize(records)
|
|
||||||
sort.Slice(datas, func(i, j int) bool {
|
|
||||||
return datas[i].CreatedAt.After(datas[j].CreatedAt)
|
|
||||||
})
|
|
||||||
return total, datas, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *BackupService) CheckUsed(id uint) error {
|
func (u *BackupService) CheckUsed(id uint) error {
|
||||||
cronjobs, _ := cronjobRepo.List()
|
cronjobs, _ := cronjobRepo.List()
|
||||||
for _, job := range cronjobs {
|
for _, job := range cronjobs {
|
||||||
@ -167,178 +406,8 @@ func (u *BackupService) CheckUsed(id uint) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type loadSizeHelper struct {
|
|
||||||
isOk bool
|
|
||||||
backupName string
|
|
||||||
backupPath string
|
|
||||||
client cloud_storage.CloudStorageClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error) {
|
|
||||||
account, client, err := NewBackupClientWithID(info.DownloadAccountID)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("new cloud storage client failed, err: %v", err)
|
|
||||||
}
|
|
||||||
if account.Type == "LOCAL" {
|
|
||||||
return path.Join(global.CONF.System.Backup, info.FileDir, info.FileName), nil
|
|
||||||
}
|
|
||||||
targetPath := fmt.Sprintf("%s/download/%s/%s", constant.DataDir, info.FileDir, info.FileName)
|
|
||||||
if _, err := os.Stat(path.Dir(targetPath)); err != nil && os.IsNotExist(err) {
|
|
||||||
if err = os.MkdirAll(path.Dir(targetPath), os.ModePerm); err != nil {
|
|
||||||
global.LOG.Errorf("mkdir %s failed, err: %v", path.Dir(targetPath), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
srcPath := fmt.Sprintf("%s/%s", info.FileDir, info.FileName)
|
|
||||||
if len(account.BackupPath) != 0 {
|
|
||||||
srcPath = path.Join(strings.TrimPrefix(account.BackupPath, "/"), srcPath)
|
|
||||||
}
|
|
||||||
if exist, _ := client.Exist(srcPath); exist {
|
|
||||||
isOK, err := client.Download(srcPath, targetPath)
|
|
||||||
if !isOK {
|
|
||||||
return "", fmt.Errorf("cloud storage download failed, err: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return targetPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *BackupService) DeleteRecordByName(backupType, name, detailName string, withDeleteFile bool) error {
|
|
||||||
if !withDeleteFile {
|
|
||||||
return backupRepo.DeleteRecord(context.Background(), repo.WithByType(backupType), repo.WithByName(name), repo.WithByDetailName(detailName))
|
|
||||||
}
|
|
||||||
|
|
||||||
records, err := backupRepo.ListRecord(repo.WithByType(backupType), repo.WithByName(name), repo.WithByDetailName(detailName))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, record := range records {
|
|
||||||
_, client, err := NewBackupClientWithID(record.DownloadAccountID)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("new client for backup account failed, err: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, err = client.Delete(path.Join(record.FileDir, record.FileName)); err != nil {
|
|
||||||
global.LOG.Errorf("remove file %s failed, err: %v", path.Join(record.FileDir, record.FileName), err)
|
|
||||||
}
|
|
||||||
_ = backupRepo.DeleteRecord(context.Background(), repo.WithByID(record.ID))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *BackupService) BatchDeleteRecord(ids []uint) error {
|
|
||||||
records, err := backupRepo.ListRecord(repo.WithByIDs(ids))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, record := range records {
|
|
||||||
_, client, err := NewBackupClientWithID(record.DownloadAccountID)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("new client for backup account failed, err: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, err = client.Delete(path.Join(record.FileDir, record.FileName)); err != nil {
|
|
||||||
global.LOG.Errorf("remove file %s failed, err: %v", path.Join(record.FileDir, record.FileName), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return backupRepo.DeleteRecord(context.Background(), repo.WithByIDs(ids))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *BackupService) ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error) {
|
|
||||||
records, err := backupRepo.ListRecord(
|
|
||||||
repo.WithOrderBy("created_at asc"),
|
|
||||||
repo.WithByName(name),
|
|
||||||
repo.WithByType("app"),
|
|
||||||
backupRepo.WithFileNameStartWith(fileName),
|
|
||||||
backupRepo.WithByDetailName(detailName),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return records, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *BackupService) ListFiles(req dto.OperateByID) []string {
|
|
||||||
var datas []string
|
|
||||||
account, client, err := NewBackupClientWithID(req.ID)
|
|
||||||
if err != nil {
|
|
||||||
return datas
|
|
||||||
}
|
|
||||||
prefix := "system_snapshot"
|
|
||||||
if len(account.BackupPath) != 0 {
|
|
||||||
prefix = path.Join(strings.TrimPrefix(account.BackupPath, "/"), prefix)
|
|
||||||
}
|
|
||||||
files, err := client.ListObjects(prefix)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Debugf("load files failed, err: %v", err)
|
|
||||||
return datas
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if len(file) != 0 {
|
|
||||||
datas = append(datas, path.Base(file))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return datas
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *BackupService) loadRecordSize(records []model.BackupRecord) ([]dto.BackupRecords, error) {
|
|
||||||
recordMap := make(map[uint]struct{})
|
|
||||||
var recordIds []string
|
|
||||||
for _, record := range records {
|
|
||||||
if _, ok := recordMap[record.DownloadAccountID]; !ok {
|
|
||||||
recordMap[record.DownloadAccountID] = struct{}{}
|
|
||||||
recordIds = append(recordIds, fmt.Sprintf("%v", record.DownloadAccountID))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clientMap, err := NewBackupClientMap(recordIds)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var datas []dto.BackupRecords
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for i := 0; i < len(records); i++ {
|
|
||||||
var item dto.BackupRecords
|
|
||||||
if err := copier.Copy(&item, &records[i]); err != nil {
|
|
||||||
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
itemPath := path.Join(records[i].FileDir, records[i].FileName)
|
|
||||||
if val, ok := clientMap[fmt.Sprintf("%v", records[i].DownloadAccountID)]; ok {
|
|
||||||
item.AccountName = val.name
|
|
||||||
item.AccountType = val.accountType
|
|
||||||
item.DownloadAccountID = val.id
|
|
||||||
wg.Add(1)
|
|
||||||
go func(index int) {
|
|
||||||
item.Size, _ = val.client.Size(path.Join(strings.TrimLeft(val.backupPath, "/"), itemPath))
|
|
||||||
datas = append(datas, item)
|
|
||||||
wg.Done()
|
|
||||||
}(i)
|
|
||||||
} else {
|
|
||||||
datas = append(datas, item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
return datas, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBackupClientWithID(id uint) (*model.BackupAccount, cloud_storage.CloudStorageClient, error) {
|
func NewBackupClientWithID(id uint) (*model.BackupAccount, cloud_storage.CloudStorageClient, error) {
|
||||||
var account model.BackupAccount
|
account, _ := backupRepo.Get(repo.WithByID(id))
|
||||||
if global.IsMaster {
|
|
||||||
var setting model.Setting
|
|
||||||
if err := global.CoreDB.Where("key = ?", "EncryptKey").First(&setting).Error; err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := global.CoreDB.Where("id = ?", id).First(&account).Error; err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if account.ID == 0 {
|
|
||||||
return nil, nil, constant.ErrRecordNotFound
|
|
||||||
}
|
|
||||||
account.AccessKey, _ = encrypt.StringDecryptWithKey(account.AccessKey, setting.Value)
|
|
||||||
account.Credential, _ = encrypt.StringDecryptWithKey(account.Credential, setting.Value)
|
|
||||||
} else {
|
|
||||||
account, _ = backupRepo.Get(repo.WithByID(id))
|
|
||||||
}
|
|
||||||
backClient, err := newClient(&account)
|
backClient, err := newClient(&account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@ -356,55 +425,22 @@ type backupClientHelper struct {
|
|||||||
|
|
||||||
func NewBackupClientMap(ids []string) (map[string]backupClientHelper, error) {
|
func NewBackupClientMap(ids []string) (map[string]backupClientHelper, error) {
|
||||||
var accounts []model.BackupAccount
|
var accounts []model.BackupAccount
|
||||||
if global.IsMaster {
|
var idItems []uint
|
||||||
var setting model.Setting
|
for i := 0; i < len(ids); i++ {
|
||||||
if err := global.CoreDB.Where("key = ?", "EncryptKey").First(&setting).Error; err != nil {
|
item, _ := strconv.Atoi(ids[i])
|
||||||
return nil, err
|
idItems = append(idItems, uint(item))
|
||||||
}
|
|
||||||
if err := global.CoreDB.Where("id in (?)", ids).Find(&accounts).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(accounts) == 0 {
|
|
||||||
return nil, constant.ErrRecordNotFound
|
|
||||||
}
|
|
||||||
for i := 0; i < len(accounts); i++ {
|
|
||||||
accounts[i].AccessKey, _ = encrypt.StringDecryptWithKey(accounts[i].AccessKey, setting.Value)
|
|
||||||
accounts[i].Credential, _ = encrypt.StringDecryptWithKey(accounts[i].Credential, setting.Value)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var idItems []uint
|
|
||||||
for i := 0; i < len(ids); i++ {
|
|
||||||
item, _ := strconv.Atoi(ids[i])
|
|
||||||
idItems = append(idItems, uint(item))
|
|
||||||
}
|
|
||||||
accounts, _ = backupRepo.List(repo.WithByIDs(idItems))
|
|
||||||
}
|
}
|
||||||
|
accounts, _ = backupRepo.List(repo.WithByIDs(idItems))
|
||||||
clientMap := make(map[string]backupClientHelper)
|
clientMap := make(map[string]backupClientHelper)
|
||||||
for _, item := range accounts {
|
for _, item := range accounts {
|
||||||
if !global.IsMaster {
|
|
||||||
accessItem, err := base64.StdEncoding.DecodeString(item.AccessKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
item.AccessKey = string(accessItem)
|
|
||||||
secretItem, err := base64.StdEncoding.DecodeString(item.Credential)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
item.Credential = string(secretItem)
|
|
||||||
}
|
|
||||||
backClient, err := newClient(&item)
|
backClient, err := newClient(&item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pathItem := item.BackupPath
|
|
||||||
if item.BackupPath != "/" {
|
|
||||||
pathItem = strings.TrimPrefix(item.BackupPath, "/")
|
|
||||||
}
|
|
||||||
clientMap[fmt.Sprintf("%v", item.ID)] = backupClientHelper{
|
clientMap[fmt.Sprintf("%v", item.ID)] = backupClientHelper{
|
||||||
client: backClient,
|
client: backClient,
|
||||||
backupPath: pathItem,
|
|
||||||
name: item.Name,
|
name: item.Name,
|
||||||
|
backupPath: item.BackupPath,
|
||||||
accountType: item.Type,
|
accountType: item.Type,
|
||||||
id: item.ID,
|
id: item.ID,
|
||||||
}
|
}
|
||||||
@ -414,10 +450,15 @@ func NewBackupClientMap(ids []string) (map[string]backupClientHelper, error) {
|
|||||||
|
|
||||||
func newClient(account *model.BackupAccount) (cloud_storage.CloudStorageClient, error) {
|
func newClient(account *model.BackupAccount) (cloud_storage.CloudStorageClient, error) {
|
||||||
varMap := make(map[string]interface{})
|
varMap := make(map[string]interface{})
|
||||||
if err := json.Unmarshal([]byte(account.Vars), &varMap); err != nil {
|
if len(account.Vars) != 0 {
|
||||||
return nil, err
|
if err := json.Unmarshal([]byte(account.Vars), &varMap); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
varMap["bucket"] = account.Bucket
|
varMap["bucket"] = account.Bucket
|
||||||
|
varMap["backupPath"] = account.BackupPath
|
||||||
|
account.AccessKey, _ = encrypt.StringDecrypt(account.AccessKey)
|
||||||
|
account.Credential, _ = encrypt.StringDecrypt(account.Credential)
|
||||||
switch account.Type {
|
switch account.Type {
|
||||||
case constant.Sftp, constant.WebDAV:
|
case constant.Sftp, constant.WebDAV:
|
||||||
varMap["username"] = account.AccessKey
|
varMap["username"] = account.AccessKey
|
||||||
@ -437,23 +478,57 @@ func newClient(account *model.BackupAccount) (cloud_storage.CloudStorageClient,
|
|||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadLocalDirByStr(vars string) (string, error) {
|
func loadRefreshTokenByCode(backup *model.BackupAccount) error {
|
||||||
varMap := make(map[string]interface{})
|
varMap := make(map[string]interface{})
|
||||||
if err := json.Unmarshal([]byte(vars), &varMap); err != nil {
|
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||||
return "", err
|
return fmt.Errorf("unmarshal backup vars failed, err: %v", err)
|
||||||
}
|
}
|
||||||
if _, ok := varMap["dir"]; !ok {
|
refreshToken := ""
|
||||||
return "", errors.New("load local backup dir failed")
|
var err error
|
||||||
}
|
if backup.Type == constant.GoogleDrive {
|
||||||
baseDir, ok := varMap["dir"].(string)
|
refreshToken, err = client.RefreshGoogleToken("authorization_code", "refreshToken", varMap)
|
||||||
if ok {
|
if err != nil {
|
||||||
if _, err := os.Stat(baseDir); err != nil && os.IsNotExist(err) {
|
return err
|
||||||
if err = os.MkdirAll(baseDir, os.ModePerm); err != nil {
|
}
|
||||||
return "", fmt.Errorf("mkdir %s failed, err: %v", baseDir, err)
|
} else {
|
||||||
}
|
refreshToken, err = client.RefreshToken("authorization_code", "refreshToken", varMap)
|
||||||
return baseDir, nil
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return baseDir, nil
|
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf("error type dir: %T", varMap["dir"])
|
delete(varMap, "code")
|
||||||
|
varMap["refresh_status"] = constant.StatusSuccess
|
||||||
|
varMap["refresh_time"] = time.Now().Format(constant.DateTimeLayout)
|
||||||
|
varMap["refresh_token"] = refreshToken
|
||||||
|
itemVars, err := json.Marshal(varMap)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("json marshal var map failed, err: %v", err)
|
||||||
|
}
|
||||||
|
backup.Vars = string(itemVars)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadBackupNamesByID(accountIDs string, downloadID uint) ([]string, string, error) {
|
||||||
|
accountIDList := strings.Split(accountIDs, ",")
|
||||||
|
var ids []uint
|
||||||
|
for _, item := range accountIDList {
|
||||||
|
if len(item) != 0 {
|
||||||
|
itemID, _ := strconv.Atoi(item)
|
||||||
|
ids = append(ids, uint(itemID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list, err := backupRepo.List(repo.WithByIDs(ids))
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
var accounts []string
|
||||||
|
var downloadAccount string
|
||||||
|
for _, item := range list {
|
||||||
|
itemName := fmt.Sprintf("%s - %s", item.Type, item.Name)
|
||||||
|
accounts = append(accounts, itemName)
|
||||||
|
if item.ID == downloadID {
|
||||||
|
downloadAccount = itemName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return accounts, downloadAccount, nil
|
||||||
}
|
}
|
||||||
|
251
agent/app/service/backup_record.go
Normal file
251
agent/app/service/backup_record.go
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BackupRecordService struct{}
|
||||||
|
|
||||||
|
type IBackupRecordService interface {
|
||||||
|
SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error)
|
||||||
|
SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error)
|
||||||
|
DownloadRecord(info dto.DownloadRecord) (string, error)
|
||||||
|
DeleteRecordByName(backupType, name, detailName string, withDeleteFile bool) error
|
||||||
|
BatchDeleteRecord(ids []uint) error
|
||||||
|
ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error)
|
||||||
|
|
||||||
|
ListFiles(req dto.OperateByID) []string
|
||||||
|
LoadRecordSize(req dto.SearchForSize) ([]dto.RecordFileSize, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIBackupRecordService() IBackupRecordService {
|
||||||
|
return &BackupRecordService{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupRecordService) SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error) {
|
||||||
|
total, records, err := backupRepo.PageRecord(
|
||||||
|
search.Page, search.PageSize,
|
||||||
|
repo.WithOrderBy("created_at desc"),
|
||||||
|
repo.WithByName(search.Name),
|
||||||
|
repo.WithByType(search.Type),
|
||||||
|
repo.WithByDetailName(search.DetailName),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
var data []dto.BackupRecords
|
||||||
|
for _, account := range records {
|
||||||
|
var item dto.BackupRecords
|
||||||
|
if err := copier.Copy(&item, &account); err != nil {
|
||||||
|
global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err)
|
||||||
|
}
|
||||||
|
data = append(data, item)
|
||||||
|
}
|
||||||
|
return total, data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupRecordService) SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error) {
|
||||||
|
total, records, err := backupRepo.PageRecord(
|
||||||
|
search.Page, search.PageSize,
|
||||||
|
repo.WithOrderBy("created_at desc"),
|
||||||
|
backupRepo.WithByCronID(search.CronjobID),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
var data []dto.BackupRecords
|
||||||
|
for _, account := range records {
|
||||||
|
var item dto.BackupRecords
|
||||||
|
if err := copier.Copy(&item, &account); err != nil {
|
||||||
|
global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err)
|
||||||
|
}
|
||||||
|
data = append(data, item)
|
||||||
|
}
|
||||||
|
return total, data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupRecordService) DownloadRecord(info dto.DownloadRecord) (string, error) {
|
||||||
|
account, client, err := NewBackupClientWithID(info.DownloadAccountID)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("new cloud storage client failed, err: %v", err)
|
||||||
|
}
|
||||||
|
if account.Type == "LOCAL" {
|
||||||
|
return path.Join(global.CONF.System.Backup, info.FileDir, info.FileName), nil
|
||||||
|
}
|
||||||
|
targetPath := fmt.Sprintf("%s/download/%s/%s", constant.DataDir, info.FileDir, info.FileName)
|
||||||
|
if _, err := os.Stat(path.Dir(targetPath)); err != nil && os.IsNotExist(err) {
|
||||||
|
if err = os.MkdirAll(path.Dir(targetPath), os.ModePerm); err != nil {
|
||||||
|
global.LOG.Errorf("mkdir %s failed, err: %v", path.Dir(targetPath), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
srcPath := fmt.Sprintf("%s/%s", info.FileDir, info.FileName)
|
||||||
|
if len(account.BackupPath) != 0 {
|
||||||
|
srcPath = path.Join(account.BackupPath, srcPath)
|
||||||
|
}
|
||||||
|
if exist, _ := client.Exist(srcPath); exist {
|
||||||
|
isOK, err := client.Download(srcPath, targetPath)
|
||||||
|
if !isOK {
|
||||||
|
return "", fmt.Errorf("cloud storage download failed, err: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return targetPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupRecordService) DeleteRecordByName(backupType, name, detailName string, withDeleteFile bool) error {
|
||||||
|
if !withDeleteFile {
|
||||||
|
return backupRepo.DeleteRecord(context.Background(), repo.WithByType(backupType), repo.WithByName(name), repo.WithByDetailName(detailName))
|
||||||
|
}
|
||||||
|
|
||||||
|
records, err := backupRepo.ListRecord(repo.WithByType(backupType), repo.WithByName(name), repo.WithByDetailName(detailName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, record := range records {
|
||||||
|
_, client, err := NewBackupClientWithID(record.DownloadAccountID)
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("new client for backup account failed, err: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err = client.Delete(path.Join(record.FileDir, record.FileName)); err != nil {
|
||||||
|
global.LOG.Errorf("remove file %s failed, err: %v", path.Join(record.FileDir, record.FileName), err)
|
||||||
|
}
|
||||||
|
_ = backupRepo.DeleteRecord(context.Background(), repo.WithByID(record.ID))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupRecordService) BatchDeleteRecord(ids []uint) error {
|
||||||
|
records, err := backupRepo.ListRecord(repo.WithByIDs(ids))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, record := range records {
|
||||||
|
_, client, err := NewBackupClientWithID(record.DownloadAccountID)
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("new client for backup account failed, err: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err = client.Delete(path.Join(record.FileDir, record.FileName)); err != nil {
|
||||||
|
global.LOG.Errorf("remove file %s failed, err: %v", path.Join(record.FileDir, record.FileName), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return backupRepo.DeleteRecord(context.Background(), repo.WithByIDs(ids))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupRecordService) ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error) {
|
||||||
|
records, err := backupRepo.ListRecord(
|
||||||
|
repo.WithOrderBy("created_at asc"),
|
||||||
|
repo.WithByName(name),
|
||||||
|
repo.WithByType("app"),
|
||||||
|
backupRepo.WithFileNameStartWith(fileName),
|
||||||
|
backupRepo.WithByDetailName(detailName),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return records, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupRecordService) ListFiles(req dto.OperateByID) []string {
|
||||||
|
var datas []string
|
||||||
|
_, client, err := NewBackupClientWithID(req.ID)
|
||||||
|
if err != nil {
|
||||||
|
return datas
|
||||||
|
}
|
||||||
|
prefix := "system_snapshot"
|
||||||
|
files, err := client.ListObjects(prefix)
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Debugf("load files failed, err: %v", err)
|
||||||
|
return datas
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
if len(file) != 0 {
|
||||||
|
datas = append(datas, path.Base(file))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return datas
|
||||||
|
}
|
||||||
|
|
||||||
|
type backupSizeHelper struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
DownloadID uint `json:"downloadID"`
|
||||||
|
FilePath string `json:"filePath"`
|
||||||
|
Size uint `json:"size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *BackupRecordService) LoadRecordSize(req dto.SearchForSize) ([]dto.RecordFileSize, error) {
|
||||||
|
var list []backupSizeHelper
|
||||||
|
switch req.Type {
|
||||||
|
case "snapshot":
|
||||||
|
_, records, err := snapshotRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, item := range records {
|
||||||
|
list = append(list, backupSizeHelper{ID: item.ID, DownloadID: item.DownloadAccountID, FilePath: fmt.Sprintf("system_snapshot/%s.tar.gz", item.Name)})
|
||||||
|
}
|
||||||
|
case "cronjob":
|
||||||
|
_, records, err := backupRepo.PageRecord(req.Page, req.PageSize, backupRepo.WithByCronID(req.CronjobID))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, item := range records {
|
||||||
|
list = append(list, backupSizeHelper{ID: item.ID, DownloadID: item.DownloadAccountID, FilePath: path.Join(item.FileDir, item.FileName)})
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
_, records, err := backupRepo.PageRecord(
|
||||||
|
req.Page, req.PageSize,
|
||||||
|
repo.WithByName(req.Name),
|
||||||
|
repo.WithByType(req.Type),
|
||||||
|
repo.WithByDetailName(req.DetailName),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, item := range records {
|
||||||
|
list = append(list, backupSizeHelper{ID: item.ID, DownloadID: item.DownloadAccountID, FilePath: path.Join(item.FileDir, item.FileName)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recordMap := make(map[uint]struct{})
|
||||||
|
var recordIds []string
|
||||||
|
for _, record := range list {
|
||||||
|
if _, ok := recordMap[record.DownloadID]; !ok {
|
||||||
|
recordMap[record.DownloadID] = struct{}{}
|
||||||
|
recordIds = append(recordIds, fmt.Sprintf("%v", record.DownloadID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clientMap, err := NewBackupClientMap(recordIds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var datas []dto.RecordFileSize
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < len(list); i++ {
|
||||||
|
item := dto.RecordFileSize{ID: list[i].ID}
|
||||||
|
if val, ok := clientMap[fmt.Sprintf("%v", list[i].DownloadID)]; ok {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(index int) {
|
||||||
|
item.Size, _ = val.client.Size(path.Join(val.backupPath, list[i].FilePath))
|
||||||
|
datas = append(datas, item)
|
||||||
|
wg.Done()
|
||||||
|
}(i)
|
||||||
|
} else {
|
||||||
|
datas = append(datas, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
return datas, nil
|
||||||
|
}
|
@ -6,8 +6,6 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -22,6 +20,9 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
|
@ -3,13 +3,14 @@ package service
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
@ -56,6 +57,7 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interfac
|
|||||||
} else {
|
} else {
|
||||||
item.LastRecordTime = "-"
|
item.LastRecordTime = "-"
|
||||||
}
|
}
|
||||||
|
item.SourceAccounts, item.DownloadAccount, _ = loadBackupNamesByID(cronjob.SourceAccountIDs, cronjob.DownloadAccountID)
|
||||||
dtoCronjobs = append(dtoCronjobs, item)
|
dtoCronjobs = append(dtoCronjobs, item)
|
||||||
}
|
}
|
||||||
return total, dtoCronjobs, err
|
return total, dtoCronjobs, err
|
||||||
|
@ -25,7 +25,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
|||||||
message []byte
|
message []byte
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
record := cronjobRepo.StartRecords(cronjob.ID, "")
|
record := cronjobRepo.StartRecords(cronjob.ID, "", cronjob.Type)
|
||||||
go func() {
|
go func() {
|
||||||
switch cronjob.Type {
|
switch cronjob.Type {
|
||||||
case "shell":
|
case "shell":
|
||||||
|
@ -3,12 +3,11 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
@ -29,7 +28,6 @@ type SnapshotService struct {
|
|||||||
|
|
||||||
type ISnapshotService interface {
|
type ISnapshotService interface {
|
||||||
SearchWithPage(req dto.PageSnapshot) (int64, interface{}, error)
|
SearchWithPage(req dto.PageSnapshot) (int64, interface{}, error)
|
||||||
LoadSize(req dto.SearchWithPage) ([]dto.SnapshotFile, error)
|
|
||||||
LoadSnapshotData() (dto.SnapshotData, error)
|
LoadSnapshotData() (dto.SnapshotData, error)
|
||||||
SnapshotCreate(req dto.SnapshotCreate, isCron bool) error
|
SnapshotCreate(req dto.SnapshotCreate, isCron bool) error
|
||||||
SnapshotReCreate(id uint) error
|
SnapshotReCreate(id uint) error
|
||||||
@ -56,58 +54,12 @@ func (u *SnapshotService) SearchWithPage(req dto.PageSnapshot) (int64, interface
|
|||||||
if err := copier.Copy(&item, &records[i]); err != nil {
|
if err := copier.Copy(&item, &records[i]); err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
|
item.SourceAccounts, item.DownloadAccount, _ = loadBackupNamesByID(records[i].SourceAccountIDs, records[i].DownloadAccountID)
|
||||||
datas = append(datas, item)
|
datas = append(datas, item)
|
||||||
}
|
}
|
||||||
return total, datas, err
|
return total, datas, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *SnapshotService) LoadSize(req dto.SearchWithPage) ([]dto.SnapshotFile, error) {
|
|
||||||
_, records, err := snapshotRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var datas []dto.SnapshotFile
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
clientMap := make(map[uint]loadSizeHelper)
|
|
||||||
for i := 0; i < len(records); i++ {
|
|
||||||
itemPath := fmt.Sprintf("system_snapshot/%s.tar.gz", records[i].Name)
|
|
||||||
data := dto.SnapshotFile{ID: records[i].ID, Name: records[i].Name}
|
|
||||||
accounts := strings.Split(records[i].SourceAccountIDs, ",")
|
|
||||||
var accountNames []string
|
|
||||||
for _, account := range accounts {
|
|
||||||
itemVal, _ := strconv.Atoi(account)
|
|
||||||
if _, ok := clientMap[uint(itemVal)]; !ok {
|
|
||||||
backup, client, err := NewBackupClientWithID(uint(itemVal))
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("load backup client from db failed, err: %v", err)
|
|
||||||
clientMap[records[i].DownloadAccountID] = loadSizeHelper{}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
backupName := fmt.Sprintf("%s - %s", backup.Type, backup.Name)
|
|
||||||
clientMap[uint(itemVal)] = loadSizeHelper{backupPath: strings.TrimLeft(backup.BackupPath, "/"), client: client, isOk: true, backupName: backupName}
|
|
||||||
accountNames = append(accountNames, backupName)
|
|
||||||
} else {
|
|
||||||
accountNames = append(accountNames, clientMap[uint(itemVal)].backupName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data.DefaultDownload = clientMap[records[i].DownloadAccountID].backupName
|
|
||||||
data.From = strings.Join(accountNames, ",")
|
|
||||||
if clientMap[records[i].DownloadAccountID].isOk {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(index int) {
|
|
||||||
data.Size, _ = clientMap[records[index].DownloadAccountID].client.Size(path.Join(clientMap[records[index].DownloadAccountID].backupPath, itemPath))
|
|
||||||
datas = append(datas, data)
|
|
||||||
wg.Done()
|
|
||||||
}(i)
|
|
||||||
} else {
|
|
||||||
datas = append(datas, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
return datas, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
|
func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
|
||||||
if len(req.Names) == 0 {
|
if len(req.Names) == 0 {
|
||||||
return fmt.Errorf("incorrect snapshot request body: %v", req.Names)
|
return fmt.Errorf("incorrect snapshot request body: %v", req.Names)
|
||||||
@ -195,7 +147,7 @@ func (u *SnapshotService) Delete(req dto.SnapshotBatchDelete) error {
|
|||||||
}
|
}
|
||||||
for _, item := range accounts {
|
for _, item := range accounts {
|
||||||
global.LOG.Debugf("remove snapshot file %s.tar.gz from %s", snap.Name, item.name)
|
global.LOG.Debugf("remove snapshot file %s.tar.gz from %s", snap.Name, item.name)
|
||||||
_, _ = item.client.Delete(path.Join(item.backupPath, "system_snapshot", snap.Name+".tar.gz"))
|
_, _ = item.client.Delete(path.Join("system_snapshot", snap.Name+".tar.gz"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,13 +4,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
@ -490,8 +491,8 @@ func snapUpload(snap snapHelper, accounts string, file string) error {
|
|||||||
|
|
||||||
targetAccounts := strings.Split(accounts, ",")
|
targetAccounts := strings.Split(accounts, ",")
|
||||||
for _, item := range targetAccounts {
|
for _, item := range targetAccounts {
|
||||||
snap.Task.LogStart(i18n.GetWithName("SnapUploadTo", fmt.Sprintf("[%s] %s", accountMap[item].name, path.Join(accountMap[item].backupPath, "system_snapshot", path.Base(file)))))
|
snap.Task.LogStart(i18n.GetWithName("SnapUploadTo", fmt.Sprintf("[%s] %s", accountMap[item].name, path.Join("system_snapshot", path.Base(file)))))
|
||||||
_, err := accountMap[item].client.Upload(source, path.Join(accountMap[item].backupPath, "system_snapshot", path.Base(file)))
|
_, err := accountMap[item].client.Upload(source, path.Join("system_snapshot", path.Base(file)))
|
||||||
snap.Task.LogWithStatus(i18n.GetWithName("SnapUploadRes", accountMap[item].name), err)
|
snap.Task.LogWithStatus(i18n.GetWithName("SnapUploadRes", accountMap[item].name), err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@ -21,6 +20,8 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
||||||
@ -604,7 +605,7 @@ func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error {
|
|||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_ = NewIBackupService().DeleteRecordByName("website", website.PrimaryDomain, website.Alias, req.DeleteBackup)
|
_ = NewIBackupRecordService().DeleteRecordByName("website", website.PrimaryDomain, website.Alias, req.DeleteBackup)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := websiteRepo.DeleteBy(ctx, repo.WithByID(req.ID)); err != nil {
|
if err := websiteRepo.DeleteBy(ctx, repo.WithByID(req.ID)); err != nil {
|
||||||
|
@ -122,9 +122,8 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrBackupInUsed = "ErrBackupInUsed"
|
ErrOSSConn = "ErrOSSConn"
|
||||||
ErrOSSConn = "ErrOSSConn"
|
ErrEntrance = "ErrEntrance"
|
||||||
ErrEntrance = "ErrEntrance"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -132,6 +131,14 @@ var (
|
|||||||
ErrFirewallBoth = "ErrFirewallBoth"
|
ErrFirewallBoth = "ErrFirewallBoth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// backup
|
||||||
|
var (
|
||||||
|
ErrBackupInUsed = "ErrBackupInUsed"
|
||||||
|
ErrBackupCheck = "ErrBackupCheck"
|
||||||
|
ErrBackupLocalDelete = "ErrBackupLocalDelete"
|
||||||
|
ErrBackupLocalCreate = "ErrBackupLocalCreate"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNotExistUser = "ErrNotExistUser"
|
ErrNotExistUser = "ErrNotExistUser"
|
||||||
)
|
)
|
||||||
|
@ -46,6 +46,9 @@ func Run() {
|
|||||||
if _, err := global.Cron.AddJob(fmt.Sprintf("%v %v * * *", mathRand.Intn(60), mathRand.Intn(3)), job.NewAppStoreJob()); err != nil {
|
if _, err := global.Cron.AddJob(fmt.Sprintf("%v %v * * *", mathRand.Intn(60), mathRand.Intn(3)), job.NewAppStoreJob()); err != nil {
|
||||||
global.LOG.Errorf("can not add appstore corn job: %s", err.Error())
|
global.LOG.Errorf("can not add appstore corn job: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
if _, err := global.Cron.AddJob("0 3 */31 * *", job.NewBackupJob()); err != nil {
|
||||||
|
global.LOG.Errorf("can not add backup token refresh corn job: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
var cronJobs []model.Cronjob
|
var cronJobs []model.Cronjob
|
||||||
if err := global.DB.Where("status = ?", constant.StatusEnable).Find(&cronJobs).Error; err != nil {
|
if err := global.DB.Where("status = ?", constant.StatusEnable).Find(&cronJobs).Error; err != nil {
|
||||||
|
61
agent/cron/job/backup.go
Normal file
61
agent/cron/job/backup.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package job
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/agent/utils/cloud_storage/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type backup struct{}
|
||||||
|
|
||||||
|
func NewBackupJob() *backup {
|
||||||
|
return &backup{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backup) Run() {
|
||||||
|
var backups []model.BackupAccount
|
||||||
|
_ = global.DB.Where("`type` in (?) AND is_public = 0", []string{constant.OneDrive, constant.ALIYUN, constant.GoogleDrive}).Find(&backups)
|
||||||
|
if len(backups) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, backupItem := range backups {
|
||||||
|
if backupItem.ID == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
global.LOG.Infof("Start to refresh %s-%s access_token ...", backupItem.Type, backupItem.Name)
|
||||||
|
varMap := make(map[string]interface{})
|
||||||
|
if err := json.Unmarshal([]byte(backupItem.Vars), &varMap); err != nil {
|
||||||
|
global.LOG.Errorf("Failed to refresh %s - %s token, please retry, err: %v", backupItem.Type, backupItem.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
refreshToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch backupItem.Type {
|
||||||
|
case constant.OneDrive:
|
||||||
|
refreshToken, err = client.RefreshToken("refresh_token", "refreshToken", varMap)
|
||||||
|
case constant.GoogleDrive:
|
||||||
|
refreshToken, err = client.RefreshGoogleToken("refresh_token", "refreshToken", varMap)
|
||||||
|
case constant.ALIYUN:
|
||||||
|
refreshToken, err = client.RefreshALIToken(varMap)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
varMap["refresh_status"] = constant.StatusFailed
|
||||||
|
varMap["refresh_msg"] = err.Error()
|
||||||
|
global.LOG.Errorf("Failed to refresh OneDrive token, please retry, err: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
varMap["refresh_status"] = constant.StatusSuccess
|
||||||
|
varMap["refresh_time"] = time.Now().Format(constant.DateTimeLayout)
|
||||||
|
varMap["refresh_token"] = refreshToken
|
||||||
|
|
||||||
|
varsItem, _ := json.Marshal(varMap)
|
||||||
|
_ = global.DB.Model(&model.BackupAccount{}).Where("id = ?", backupItem.ID).Updates(map[string]interface{}{"vars": string(varsItem)}).Error
|
||||||
|
global.LOG.Infof("Refresh %s-%s access_token successful!", backupItem.Type, backupItem.Name)
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,12 @@ Success: "Success"
|
|||||||
Failed: "Failed"
|
Failed: "Failed"
|
||||||
SystemRestart: "System restart causes task interruption"
|
SystemRestart: "System restart causes task interruption"
|
||||||
|
|
||||||
|
#backup
|
||||||
|
ErrBackupInUsed: "The backup account is currently in use in a scheduled task and cannot be deleted."
|
||||||
|
ErrBackupCheck: "Backup account test connection failed {{.err}}"
|
||||||
|
ErrBackupLocalDelete: "Deleting local server backup accounts is not currently supported."
|
||||||
|
ErrBackupLocalCreate: "Creating local server backup accounts is not currently supported."
|
||||||
|
|
||||||
#app
|
#app
|
||||||
ErrPortInUsed: "{{ .detail }} port already in use"
|
ErrPortInUsed: "{{ .detail }} port already in use"
|
||||||
ErrAppLimit: "App exceeds install limit"
|
ErrAppLimit: "App exceeds install limit"
|
||||||
|
@ -20,6 +20,12 @@ Failed: "失敗"
|
|||||||
SystemRestart: "系統重啟導致任務中斷"
|
SystemRestart: "系統重啟導致任務中斷"
|
||||||
ErrInvalidChar: "禁止使用非法字元"
|
ErrInvalidChar: "禁止使用非法字元"
|
||||||
|
|
||||||
|
#backup
|
||||||
|
ErrBackupInUsed: "該備份帳號已在計劃任務中使用,無法刪除"
|
||||||
|
ErrBackupCheck: "備份帳號測試連接失敗 {{.err}}"
|
||||||
|
ErrBackupLocalDelete: "暫不支持刪除本地伺服器備份帳號"
|
||||||
|
ErrBackupLocalCreate: "暫不支持創建本地伺服器備份帳號"
|
||||||
|
|
||||||
#app
|
#app
|
||||||
ErrPortInUsed: "{{ .detail }} 端口已被佔用!"
|
ErrPortInUsed: "{{ .detail }} 端口已被佔用!"
|
||||||
ErrAppLimit: "應用超出安裝數量限制"
|
ErrAppLimit: "應用超出安裝數量限制"
|
||||||
|
@ -19,6 +19,12 @@ Success: "成功"
|
|||||||
Failed: "失败"
|
Failed: "失败"
|
||||||
SystemRestart: "系统重启导致任务中断"
|
SystemRestart: "系统重启导致任务中断"
|
||||||
|
|
||||||
|
#backup
|
||||||
|
ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除"
|
||||||
|
ErrBackupCheck: "备份账号测试连接失败 {{ .err}}"
|
||||||
|
ErrBackupLocalDelete: "暂不支持删除本地服务器备份账号"
|
||||||
|
ErrBackupLocalCreate: "暂不支持创建本地服务器备份账号"
|
||||||
|
|
||||||
#app
|
#app
|
||||||
ErrPortInUsed: "{{ .detail }} 端口已被占用!"
|
ErrPortInUsed: "{{ .detail }} 端口已被占用!"
|
||||||
ErrAppLimit: "应用超出安装数量限制"
|
ErrAppLimit: "应用超出安装数量限制"
|
||||||
|
@ -66,26 +66,10 @@ func handleCronjobStatus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadLocalDir() {
|
func loadLocalDir() {
|
||||||
var vars string
|
var account model.BackupAccount
|
||||||
if global.IsMaster {
|
if err := global.DB.Where("`type` = ?", constant.Local).First(&account).Error; err != nil {
|
||||||
var account model.BackupAccount
|
global.LOG.Errorf("load local backup account info failed, err: %v", err)
|
||||||
if err := global.CoreDB.Where("id = 1").First(&account).Error; err != nil {
|
|
||||||
global.LOG.Errorf("load local backup account info failed, err: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
vars = account.Vars
|
|
||||||
} else {
|
|
||||||
account, _, err := service.NewBackupClientWithID(1)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("load local backup account info failed, err: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
vars = account.Vars
|
|
||||||
}
|
|
||||||
localDir, err := service.LoadLocalDirByStr(vars)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("load local backup dir failed, err: %v", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
global.CONF.System.Backup = localDir
|
global.CONF.System.Backup = account.BackupPath
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ func InitAgentDB() {
|
|||||||
migrations.InitDefaultCA,
|
migrations.InitDefaultCA,
|
||||||
migrations.InitPHPExtensions,
|
migrations.InitPHPExtensions,
|
||||||
migrations.InitNodePort,
|
migrations.InitNodePort,
|
||||||
|
migrations.InitBackup,
|
||||||
})
|
})
|
||||||
if err := m.Migrate(); err != nil {
|
if err := m.Migrate(); err != nil {
|
||||||
global.LOG.Error(err)
|
global.LOG.Error(err)
|
||||||
|
@ -2,6 +2,7 @@ package migrations
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||||
@ -17,7 +18,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var AddTable = &gormigrate.Migration{
|
var AddTable = &gormigrate.Migration{
|
||||||
ID: "20241231-add-table",
|
ID: "20240108-add-table",
|
||||||
Migrate: func(tx *gorm.DB) error {
|
Migrate: func(tx *gorm.DB) error {
|
||||||
return tx.AutoMigrate(
|
return tx.AutoMigrate(
|
||||||
&model.AppDetail{},
|
&model.AppDetail{},
|
||||||
@ -244,3 +245,17 @@ var InitNodePort = &gormigrate.Migration{
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var InitBackup = &gormigrate.Migration{
|
||||||
|
ID: "20241226-init-backup",
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
if err := tx.Create(&model.BackupAccount{
|
||||||
|
Name: "localhost",
|
||||||
|
Type: "LOCAL",
|
||||||
|
BackupPath: path.Join(global.CONF.System.BaseDir, "1panel/backup"),
|
||||||
|
}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
@ -14,12 +14,21 @@ func (s *BackupRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
backupRouter.GET("/check/:id", baseApi.CheckBackupUsed)
|
backupRouter.GET("/check/:id", baseApi.CheckBackupUsed)
|
||||||
backupRouter.POST("/sync", baseApi.SyncBackupAccount)
|
backupRouter.POST("/sync", baseApi.SyncBackupAccount)
|
||||||
backupRouter.GET("/options", baseApi.LoadBackupOptions)
|
backupRouter.GET("/options", baseApi.LoadBackupOptions)
|
||||||
|
backupRouter.POST("/search", baseApi.SearchBackup)
|
||||||
|
|
||||||
|
backupRouter.GET("/local", baseApi.GetLocalDir)
|
||||||
|
backupRouter.POST("/refresh/token", baseApi.RefreshToken)
|
||||||
|
backupRouter.POST("/buckets", baseApi.ListBuckets)
|
||||||
|
backupRouter.POST("", baseApi.CreateBackup)
|
||||||
|
backupRouter.POST("/del", baseApi.DeleteBackup)
|
||||||
|
backupRouter.POST("/update", baseApi.UpdateBackup)
|
||||||
|
|
||||||
backupRouter.POST("/backup", baseApi.Backup)
|
backupRouter.POST("/backup", baseApi.Backup)
|
||||||
backupRouter.POST("/recover", baseApi.Recover)
|
backupRouter.POST("/recover", baseApi.Recover)
|
||||||
backupRouter.POST("/recover/byupload", baseApi.RecoverByUpload)
|
backupRouter.POST("/recover/byupload", baseApi.RecoverByUpload)
|
||||||
backupRouter.POST("/search/files", baseApi.LoadFilesFromBackup)
|
backupRouter.POST("/search/files", baseApi.LoadFilesFromBackup)
|
||||||
backupRouter.POST("/record/search", baseApi.SearchBackupRecords)
|
backupRouter.POST("/record/search", baseApi.SearchBackupRecords)
|
||||||
|
backupRouter.POST("/record/size", baseApi.LoadBackupRecordSize)
|
||||||
backupRouter.POST("/record/search/bycronjob", baseApi.SearchBackupRecordsByCronjob)
|
backupRouter.POST("/record/search/bycronjob", baseApi.SearchBackupRecordsByCronjob)
|
||||||
backupRouter.POST("/record/download", baseApi.DownloadRecord)
|
backupRouter.POST("/record/download", baseApi.DownloadRecord)
|
||||||
backupRouter.POST("/record/del", baseApi.DeleteBackupRecord)
|
backupRouter.POST("/record/del", baseApi.DeleteBackupRecord)
|
||||||
|
@ -20,7 +20,6 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
settingRouter.POST("/snapshot", baseApi.CreateSnapshot)
|
settingRouter.POST("/snapshot", baseApi.CreateSnapshot)
|
||||||
settingRouter.POST("/snapshot/recreate", baseApi.RecreateSnapshot)
|
settingRouter.POST("/snapshot/recreate", baseApi.RecreateSnapshot)
|
||||||
settingRouter.POST("/snapshot/search", baseApi.SearchSnapshot)
|
settingRouter.POST("/snapshot/search", baseApi.SearchSnapshot)
|
||||||
settingRouter.POST("/snapshot/size", baseApi.LoadSnapshotSize)
|
|
||||||
settingRouter.POST("/snapshot/import", baseApi.ImportSnapshot)
|
settingRouter.POST("/snapshot/import", baseApi.ImportSnapshot)
|
||||||
settingRouter.POST("/snapshot/del", baseApi.DeleteSnapshot)
|
settingRouter.POST("/snapshot/del", baseApi.DeleteSnapshot)
|
||||||
settingRouter.POST("/snapshot/recover", baseApi.RecoverSnapshot)
|
settingRouter.POST("/snapshot/recover", baseApi.RecoverSnapshot)
|
||||||
|
@ -502,3 +502,32 @@ func loadToken(refresh_token string) (string, error) {
|
|||||||
}
|
}
|
||||||
return respItem.AccessToken, nil
|
return respItem.AccessToken, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RefreshALIToken(varMap map[string]interface{}) (string, error) {
|
||||||
|
refresh_token := loadParamFromVars("refresh_token", varMap)
|
||||||
|
if len(refresh_token) == 0 {
|
||||||
|
return "", errors.New("no such refresh token find in db")
|
||||||
|
}
|
||||||
|
client := resty.New()
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"grant_type": "refresh_token",
|
||||||
|
"refresh_token": refresh_token,
|
||||||
|
}
|
||||||
|
|
||||||
|
url := "https://api.aliyundrive.com/token/refresh"
|
||||||
|
resp, err := client.R().
|
||||||
|
SetBody(data).
|
||||||
|
Post(url)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("load account token failed, err: %v", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode() != 200 {
|
||||||
|
return "", fmt.Errorf("load account token failed, code: %v", resp.StatusCode())
|
||||||
|
}
|
||||||
|
var respItem tokenResp
|
||||||
|
if err := json.Unmarshal(resp.Body(), &respItem); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return respItem.AccessToken, nil
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
func loadParamFromVars(key string, vars map[string]interface{}) string {
|
func loadParamFromVars(key string, vars map[string]interface{}) string {
|
||||||
if _, ok := vars[key]; !ok {
|
if _, ok := vars[key]; !ok {
|
||||||
if key != "bucket" && key != "port" {
|
if key != "bucket" && key != "port" && key != "authMode" && key != "passPhrase" {
|
||||||
global.LOG.Errorf("load param %s from vars failed, err: not exist!", key)
|
global.LOG.Errorf("load param %s from vars failed, err: not exist!", key)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
|
@ -9,13 +9,10 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
||||||
)
|
)
|
||||||
|
|
||||||
type localClient struct {
|
type localClient struct{}
|
||||||
dir string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLocalClient(vars map[string]interface{}) (*localClient, error) {
|
func NewLocalClient(vars map[string]interface{}) (*localClient, error) {
|
||||||
dir := loadParamFromVars("dir", vars)
|
return &localClient{}, nil
|
||||||
return &localClient{dir: dir}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c localClient) ListBuckets() ([]interface{}, error) {
|
func (c localClient) ListBuckets() ([]interface{}, error) {
|
||||||
@ -23,12 +20,12 @@ func (c localClient) ListBuckets() ([]interface{}, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c localClient) Exist(file string) (bool, error) {
|
func (c localClient) Exist(file string) (bool, error) {
|
||||||
_, err := os.Stat(path.Join(c.dir, file))
|
_, err := os.Stat(file)
|
||||||
return err == nil, err
|
return err == nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c localClient) Size(file string) (int64, error) {
|
func (c localClient) Size(file string) (int64, error) {
|
||||||
fileInfo, err := os.Stat(path.Join(c.dir, file))
|
fileInfo, err := os.Stat(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -36,7 +33,7 @@ func (c localClient) Size(file string) (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c localClient) Delete(file string) (bool, error) {
|
func (c localClient) Delete(file string) (bool, error) {
|
||||||
if err := os.RemoveAll(path.Join(c.dir, file)); err != nil {
|
if err := os.RemoveAll(file); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
@ -44,26 +41,6 @@ func (c localClient) Delete(file string) (bool, error) {
|
|||||||
|
|
||||||
func (c localClient) Upload(src, target string) (bool, error) {
|
func (c localClient) Upload(src, target string) (bool, error) {
|
||||||
fileOp := files.NewFileOp()
|
fileOp := files.NewFileOp()
|
||||||
targetFilePath := path.Join(c.dir, target)
|
|
||||||
if _, err := os.Stat(path.Dir(targetFilePath)); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
if err = os.MkdirAll(path.Dir(targetFilePath), os.ModePerm); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := fileOp.CopyAndReName(src, targetFilePath, "", true); err != nil {
|
|
||||||
return false, fmt.Errorf("cp file failed, err: %v", err)
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c localClient) Download(src, target string) (bool, error) {
|
|
||||||
fileOp := files.NewFileOp()
|
|
||||||
localPath := path.Join(c.dir, src)
|
|
||||||
if _, err := os.Stat(path.Dir(target)); err != nil {
|
if _, err := os.Stat(path.Dir(target)); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
if err = os.MkdirAll(path.Dir(target), os.ModePerm); err != nil {
|
if err = os.MkdirAll(path.Dir(target), os.ModePerm); err != nil {
|
||||||
@ -74,7 +51,25 @@ func (c localClient) Download(src, target string) (bool, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := fileOp.CopyAndReName(localPath, target, "", true); err != nil {
|
if err := fileOp.CopyAndReName(src, target, "", true); err != nil {
|
||||||
|
return false, fmt.Errorf("cp file failed, err: %v", err)
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c localClient) Download(src, target string) (bool, error) {
|
||||||
|
fileOp := files.NewFileOp()
|
||||||
|
if _, err := os.Stat(path.Dir(target)); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
if err = os.MkdirAll(path.Dir(target), os.ModePerm); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fileOp.CopyAndReName(src, target, "", true); err != nil {
|
||||||
return false, fmt.Errorf("cp file failed, err: %v", err)
|
return false, fmt.Errorf("cp file failed, err: %v", err)
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
@ -82,11 +77,10 @@ func (c localClient) Download(src, target string) (bool, error) {
|
|||||||
|
|
||||||
func (c localClient) ListObjects(prefix string) ([]string, error) {
|
func (c localClient) ListObjects(prefix string) ([]string, error) {
|
||||||
var files []string
|
var files []string
|
||||||
itemPath := path.Join(c.dir, prefix)
|
if _, err := os.Stat(prefix); err != nil {
|
||||||
if _, err := os.Stat(itemPath); err != nil {
|
|
||||||
return files, nil
|
return files, nil
|
||||||
}
|
}
|
||||||
if err := filepath.Walk(itemPath, func(path string, info os.FileInfo, err error) error {
|
if err := filepath.Walk(prefix, func(path string, info os.FileInfo, err error) error {
|
||||||
if !info.IsDir() {
|
if !info.IsDir() {
|
||||||
files = append(files, info.Name())
|
files = append(files, info.Name())
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type sftpClient struct {
|
type sftpClient struct {
|
||||||
bucket string
|
|
||||||
connInfo string
|
connInfo string
|
||||||
config *ssh.ClientConfig
|
config *ssh.ClientConfig
|
||||||
}
|
}
|
||||||
@ -29,7 +28,6 @@ func NewSftpClient(vars map[string]interface{}) (*sftpClient, error) {
|
|||||||
passPhrase := loadParamFromVars("passPhrase", vars)
|
passPhrase := loadParamFromVars("passPhrase", vars)
|
||||||
username := loadParamFromVars("username", vars)
|
username := loadParamFromVars("username", vars)
|
||||||
password := loadParamFromVars("password", vars)
|
password := loadParamFromVars("password", vars)
|
||||||
bucket := loadParamFromVars("bucket", vars)
|
|
||||||
|
|
||||||
var auth []ssh.AuthMethod
|
var auth []ssh.AuthMethod
|
||||||
if authMode == "key" {
|
if authMode == "key" {
|
||||||
@ -59,7 +57,7 @@ func NewSftpClient(vars map[string]interface{}) (*sftpClient, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &sftpClient{bucket: bucket, connInfo: fmt.Sprintf("%s:%s", address, port), config: clientConfig}, nil
|
return &sftpClient{connInfo: fmt.Sprintf("%s:%s", address, port), config: clientConfig}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s sftpClient) Upload(src, target string) (bool, error) {
|
func (s sftpClient) Upload(src, target string) (bool, error) {
|
||||||
@ -80,8 +78,7 @@ func (s sftpClient) Upload(src, target string) (bool, error) {
|
|||||||
}
|
}
|
||||||
defer srcFile.Close()
|
defer srcFile.Close()
|
||||||
|
|
||||||
targetFilePath := path.Join(s.bucket, target)
|
targetDir, _ := path.Split(target)
|
||||||
targetDir, _ := path.Split(targetFilePath)
|
|
||||||
if _, err = client.Stat(targetDir); err != nil {
|
if _, err = client.Stat(targetDir); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
if err = client.MkdirAll(targetDir); err != nil {
|
if err = client.MkdirAll(targetDir); err != nil {
|
||||||
@ -91,7 +88,7 @@ func (s sftpClient) Upload(src, target string) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dstFile, err := client.Create(path.Join(s.bucket, target))
|
dstFile, err := client.Create(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -120,7 +117,7 @@ func (s sftpClient) Download(src, target string) (bool, error) {
|
|||||||
defer client.Close()
|
defer client.Close()
|
||||||
defer sshClient.Close()
|
defer sshClient.Close()
|
||||||
|
|
||||||
srcFile, err := client.Open(s.bucket + "/" + src)
|
srcFile, err := client.Open(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -150,7 +147,7 @@ func (s sftpClient) Exist(filePath string) (bool, error) {
|
|||||||
defer client.Close()
|
defer client.Close()
|
||||||
defer sshClient.Close()
|
defer sshClient.Close()
|
||||||
|
|
||||||
srcFile, err := client.Open(path.Join(s.bucket, filePath))
|
srcFile, err := client.Open(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return false, nil
|
return false, nil
|
||||||
@ -174,7 +171,7 @@ func (s sftpClient) Size(filePath string) (int64, error) {
|
|||||||
defer client.Close()
|
defer client.Close()
|
||||||
defer sshClient.Close()
|
defer sshClient.Close()
|
||||||
|
|
||||||
files, err := client.Stat(path.Join(s.bucket, filePath))
|
files, err := client.Stat(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -193,7 +190,7 @@ func (s sftpClient) Delete(filePath string) (bool, error) {
|
|||||||
defer client.Close()
|
defer client.Close()
|
||||||
defer sshClient.Close()
|
defer sshClient.Close()
|
||||||
|
|
||||||
if err := client.Remove(path.Join(s.bucket, filePath)); err != nil {
|
if err := client.Remove(filePath); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
@ -211,7 +208,7 @@ func (s sftpClient) ListObjects(prefix string) ([]string, error) {
|
|||||||
defer client.Close()
|
defer client.Close()
|
||||||
defer sshClient.Close()
|
defer sshClient.Close()
|
||||||
|
|
||||||
files, err := client.ReadDir(path.Join(s.bucket, prefix))
|
files, err := client.ReadDir(prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,14 @@ func StringEncrypt(text string) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StringDecryptWithBase64(text string) (string, error) {
|
||||||
|
decryptItem, err := StringDecrypt(text)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(decryptItem)), nil
|
||||||
|
}
|
||||||
|
|
||||||
func StringDecryptWithKey(text, key string) (string, error) {
|
func StringDecryptWithKey(text, key string) (string, error) {
|
||||||
if len(text) == 0 {
|
if len(text) == 0 {
|
||||||
return "", nil
|
return "", nil
|
||||||
|
@ -730,6 +730,11 @@ func ZipFile(files []archiver.File, dst afero.File) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f FileOp) TarGzCompressPro(withDir bool, src, dst, secret, exclusionRules string) error {
|
func (f FileOp) TarGzCompressPro(withDir bool, src, dst, secret, exclusionRules string) error {
|
||||||
|
if !f.Stat(path.Dir(dst)) {
|
||||||
|
if err := f.Fs.MkdirAll(path.Dir(dst), constant.FilePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
workdir := src
|
workdir := src
|
||||||
srcItem := "."
|
srcItem := "."
|
||||||
if withDir {
|
if withDir {
|
||||||
@ -758,10 +763,10 @@ func (f FileOp) TarGzCompressPro(withDir bool, src, dst, secret, exclusionRules
|
|||||||
itemPrefix = ""
|
itemPrefix = ""
|
||||||
}
|
}
|
||||||
if len(secret) != 0 {
|
if len(secret) != 0 {
|
||||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -print-printf '%s' | sed 's|^|%s/|') -zcf - %s | openssl enc -aes-256-cbc -salt -pbkdf2 -k '%s' -out %s", src, "%P\n", itemPrefix, srcItem, secret, dst)
|
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -print '%s' | sed 's|^|%s/|') -zcf - %s | openssl enc -aes-256-cbc -salt -pbkdf2 -k '%s' -out %s", src, "%P\n", itemPrefix, srcItem, secret, dst)
|
||||||
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
|
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
|
||||||
} else {
|
} else {
|
||||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -print-printf '%s' | sed 's|^|%s/|') -zcf %s %s %s", src, "%P\n", itemPrefix, dst, exStr, srcItem)
|
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -printf '%s' | sed 's|^|%s/|') -zcf %s %s %s", src, "%P\n", itemPrefix, dst, exStr, srcItem)
|
||||||
global.LOG.Debug(commands)
|
global.LOG.Debug(commands)
|
||||||
}
|
}
|
||||||
return cmd.ExecCmdWithDir(commands, workdir)
|
return cmd.ExecCmdWithDir(commands, workdir)
|
||||||
|
@ -15,7 +15,7 @@ import (
|
|||||||
// @Param request body dto.BackupOperate true "request"
|
// @Param request body dto.BackupOperate true "request"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /core/backup [post]
|
// @Router /core/backups [post]
|
||||||
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建备份账号 [type]","formatEN":"create backup account [type]"}
|
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建备份账号 [type]","formatEN":"create backup account [type]"}
|
||||||
func (b *BaseApi) CreateBackup(c *gin.Context) {
|
func (b *BaseApi) CreateBackup(c *gin.Context) {
|
||||||
var req dto.BackupOperate
|
var req dto.BackupOperate
|
||||||
@ -27,18 +27,27 @@ func (b *BaseApi) CreateBackup(c *gin.Context) {
|
|||||||
helper.InternalServer(c, err)
|
helper.InternalServer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
helper.SuccessWithData(c, nil)
|
helper.SuccessWithOutData(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Backup Account
|
// @Tags Backup Account
|
||||||
// @Summary Refresh token
|
// @Summary Refresh token
|
||||||
// @Description 刷新 token
|
// @Description 刷新 token
|
||||||
|
// @Accept json
|
||||||
|
// @Param request body dto.BackupOperate true "request"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /core/backup/refresh/token [post]
|
// @Router /core/backups/refresh/token [post]
|
||||||
func (b *BaseApi) RefreshToken(c *gin.Context) {
|
func (b *BaseApi) RefreshToken(c *gin.Context) {
|
||||||
backupService.Run()
|
var req dto.OperateByID
|
||||||
helper.SuccessWithData(c, nil)
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := backupService.RefreshToken(req); err != nil {
|
||||||
|
helper.InternalServer(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithOutData(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Backup Account
|
// @Tags Backup Account
|
||||||
@ -48,7 +57,7 @@ func (b *BaseApi) RefreshToken(c *gin.Context) {
|
|||||||
// @Param request body dto.ForBuckets true "request"
|
// @Param request body dto.ForBuckets true "request"
|
||||||
// @Success 200 {array} string
|
// @Success 200 {array} string
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /core/backup/search [post]
|
// @Router /core/backups/buckets [post]
|
||||||
func (b *BaseApi) ListBuckets(c *gin.Context) {
|
func (b *BaseApi) ListBuckets(c *gin.Context) {
|
||||||
var req dto.ForBuckets
|
var req dto.ForBuckets
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||||
@ -69,7 +78,7 @@ func (b *BaseApi) ListBuckets(c *gin.Context) {
|
|||||||
// @Accept json
|
// @Accept json
|
||||||
// @Success 200 {object} dto.OneDriveInfo
|
// @Success 200 {object} dto.OneDriveInfo
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /core/backup/client/:clientType [get]
|
// @Router /core/backups/client/:clientType [get]
|
||||||
func (b *BaseApi) LoadBackupClientInfo(c *gin.Context) {
|
func (b *BaseApi) LoadBackupClientInfo(c *gin.Context) {
|
||||||
clientType, ok := c.Params.Get("clientType")
|
clientType, ok := c.Params.Get("clientType")
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -91,7 +100,7 @@ func (b *BaseApi) LoadBackupClientInfo(c *gin.Context) {
|
|||||||
// @Param request body dto.OperateByID true "request"
|
// @Param request body dto.OperateByID true "request"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /core/backup/del [post]
|
// @Router /core/backups/del [post]
|
||||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"backup_accounts","output_column":"type","output_value":"types"}],"formatZH":"删除备份账号 [types]","formatEN":"delete backup account [types]"}
|
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"backup_accounts","output_column":"type","output_value":"types"}],"formatZH":"删除备份账号 [types]","formatEN":"delete backup account [types]"}
|
||||||
func (b *BaseApi) DeleteBackup(c *gin.Context) {
|
func (b *BaseApi) DeleteBackup(c *gin.Context) {
|
||||||
var req dto.OperateByID
|
var req dto.OperateByID
|
||||||
@ -113,7 +122,7 @@ func (b *BaseApi) DeleteBackup(c *gin.Context) {
|
|||||||
// @Param request body dto.BackupOperate true "request"
|
// @Param request body dto.BackupOperate true "request"
|
||||||
// @Success 200
|
// @Success 200
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /core/backup/update [post]
|
// @Router /core/backups/update [post]
|
||||||
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新备份账号 [types]","formatEN":"update backup account [types]"}
|
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新备份账号 [types]","formatEN":"update backup account [types]"}
|
||||||
func (b *BaseApi) UpdateBackup(c *gin.Context) {
|
func (b *BaseApi) UpdateBackup(c *gin.Context) {
|
||||||
var req dto.BackupOperate
|
var req dto.BackupOperate
|
||||||
@ -127,45 +136,3 @@ func (b *BaseApi) UpdateBackup(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
helper.SuccessWithData(c, nil)
|
helper.SuccessWithData(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Tags Backup Account
|
|
||||||
// @Summary Search backup accounts with page
|
|
||||||
// @Description 获取备份账号列表
|
|
||||||
// @Accept json
|
|
||||||
// @Param request body dto.SearchPageWithType true "request"
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /core/backup/search [post]
|
|
||||||
func (b *BaseApi) SearchBackup(c *gin.Context) {
|
|
||||||
var req dto.SearchPageWithType
|
|
||||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
total, list, err := backupService.SearchWithPage(req)
|
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.SuccessWithData(c, dto.PageResult{
|
|
||||||
Items: list,
|
|
||||||
Total: total,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Tags Backup Account
|
|
||||||
// @Summary get local backup dir
|
|
||||||
// @Description 获取本地备份目录
|
|
||||||
// @Success 200
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /core/backup/local [get]
|
|
||||||
func (b *BaseApi) GetLocalDir(c *gin.Context) {
|
|
||||||
dir, err := backupService.GetLocalDir()
|
|
||||||
if err != nil {
|
|
||||||
helper.InternalServer(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.SuccessWithData(c, dir)
|
|
||||||
}
|
|
||||||
|
@ -12,6 +12,7 @@ type BackupOperate struct {
|
|||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type" validate:"required"`
|
Type string `json:"type" validate:"required"`
|
||||||
|
IsPublic bool `json:"isPublic"`
|
||||||
Bucket string `json:"bucket"`
|
Bucket string `json:"bucket"`
|
||||||
AccessKey string `json:"accessKey"`
|
AccessKey string `json:"accessKey"`
|
||||||
Credential string `json:"credential"`
|
Credential string `json:"credential"`
|
||||||
@ -25,6 +26,7 @@ type BackupInfo struct {
|
|||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
IsPublic bool `json:"isPublic"`
|
||||||
Bucket string `json:"bucket"`
|
Bucket string `json:"bucket"`
|
||||||
AccessKey string `json:"accessKey"`
|
AccessKey string `json:"accessKey"`
|
||||||
Credential string `json:"credential"`
|
Credential string `json:"credential"`
|
||||||
|
@ -52,7 +52,7 @@ type SettingUpdate struct {
|
|||||||
type SSLUpdate struct {
|
type SSLUpdate struct {
|
||||||
SSLType string `json:"sslType" validate:"required,oneof=self select import import-paste import-local"`
|
SSLType string `json:"sslType" validate:"required,oneof=self select import import-paste import-local"`
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
SSL string `json:"ssl" validate:"required,oneof=enable disable"`
|
SSL string `json:"ssl" validate:"required,oneof=Enable Disable"`
|
||||||
Cert string `json:"cert"`
|
Cert string `json:"cert"`
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
SSLID uint `json:"sslID"`
|
SSLID uint `json:"sslID"`
|
||||||
@ -143,7 +143,7 @@ type SyncTime struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BindInfo struct {
|
type BindInfo struct {
|
||||||
Ipv6 string `json:"ipv6" validate:"required,oneof=enable disable"`
|
Ipv6 string `json:"ipv6" validate:"required,oneof=Enable Disable"`
|
||||||
BindAddress string `json:"bindAddress" validate:"required"`
|
BindAddress string `json:"bindAddress" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,9 @@ package model
|
|||||||
|
|
||||||
type BackupAccount struct {
|
type BackupAccount struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
Name string `gorm:"not null" json:"name"`
|
Name string `gorm:"not null;default:''" json:"name"`
|
||||||
Type string `gorm:"not null" json:"type"`
|
Type string `gorm:"not null;default:''" json:"type"`
|
||||||
|
IsPublic bool `json:"isPublic"`
|
||||||
Bucket string `json:"bucket"`
|
Bucket string `json:"bucket"`
|
||||||
AccessKey string `json:"accessKey"`
|
AccessKey string `json:"accessKey"`
|
||||||
Credential string `json:"credential"`
|
Credential string `json:"credential"`
|
||||||
|
@ -62,5 +62,5 @@ func syncLauncherToAgent(launcher model.AppLauncher, operation string) {
|
|||||||
itemData, _ := json.Marshal(launcher)
|
itemData, _ := json.Marshal(launcher)
|
||||||
itemJson := dto.SyncToAgent{Name: launcher.Key, Operation: operation, Data: string(itemData)}
|
itemJson := dto.SyncToAgent{Name: launcher.Key, Operation: operation, Data: string(itemData)}
|
||||||
bodyItem, _ := json.Marshal(itemJson)
|
bodyItem, _ := json.Marshal(itemJson)
|
||||||
_ = xpack.RequestToAgent("/api/v2/backups/sync", http.MethodPost, bytes.NewReader((bodyItem)))
|
_ = xpack.RequestToAllAgent("/api/v2/backups/sync", http.MethodPost, bytes.NewReader((bodyItem)))
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ import (
|
|||||||
"github.com/1Panel-dev/1Panel/core/utils/cloud_storage"
|
"github.com/1Panel-dev/1Panel/core/utils/cloud_storage"
|
||||||
"github.com/1Panel-dev/1Panel/core/utils/cloud_storage/client"
|
"github.com/1Panel-dev/1Panel/core/utils/cloud_storage/client"
|
||||||
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
|
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
|
||||||
fileUtils "github.com/1Panel-dev/1Panel/core/utils/files"
|
|
||||||
httpUtils "github.com/1Panel-dev/1Panel/core/utils/http"
|
httpUtils "github.com/1Panel-dev/1Panel/core/utils/http"
|
||||||
"github.com/1Panel-dev/1Panel/core/utils/xpack"
|
"github.com/1Panel-dev/1Panel/core/utils/xpack"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
@ -31,131 +30,18 @@ import (
|
|||||||
type BackupService struct{}
|
type BackupService struct{}
|
||||||
|
|
||||||
type IBackupService interface {
|
type IBackupService interface {
|
||||||
Get(req dto.OperateByID) (dto.BackupInfo, error)
|
|
||||||
List(req dto.OperateByIDs) ([]dto.BackupInfo, error)
|
|
||||||
|
|
||||||
GetLocalDir() (string, error)
|
|
||||||
SearchWithPage(search dto.SearchPageWithType) (int64, interface{}, error)
|
|
||||||
LoadBackupClientInfo(clientType string) (dto.BackupClientInfo, error)
|
LoadBackupClientInfo(clientType string) (dto.BackupClientInfo, error)
|
||||||
Create(backupDto dto.BackupOperate) error
|
Create(backupDto dto.BackupOperate) error
|
||||||
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
|
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
|
||||||
Update(req dto.BackupOperate) error
|
Update(req dto.BackupOperate) error
|
||||||
Delete(id uint) error
|
Delete(id uint) error
|
||||||
NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error)
|
RefreshToken(req dto.OperateByID) error
|
||||||
|
|
||||||
Run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIBackupService() IBackupService {
|
func NewIBackupService() IBackupService {
|
||||||
return &BackupService{}
|
return &BackupService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *BackupService) Get(req dto.OperateByID) (dto.BackupInfo, error) {
|
|
||||||
var data dto.BackupInfo
|
|
||||||
account, err := backupRepo.Get(repo.WithByID(req.ID))
|
|
||||||
if err != nil {
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
if err := copier.Copy(&data, &account); err != nil {
|
|
||||||
global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err)
|
|
||||||
}
|
|
||||||
data.AccessKey, err = encrypt.StringDecryptWithBase64(data.AccessKey)
|
|
||||||
if err != nil {
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
data.Credential, err = encrypt.StringDecryptWithBase64(data.Credential)
|
|
||||||
if err != nil {
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *BackupService) List(req dto.OperateByIDs) ([]dto.BackupInfo, error) {
|
|
||||||
accounts, err := backupRepo.List(repo.WithByIDs(req.IDs), repo.WithOrderBy("created_at desc"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var data []dto.BackupInfo
|
|
||||||
for _, account := range accounts {
|
|
||||||
var item dto.BackupInfo
|
|
||||||
if err := copier.Copy(&item, &account); err != nil {
|
|
||||||
global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err)
|
|
||||||
}
|
|
||||||
item.AccessKey, err = encrypt.StringDecryptWithBase64(item.AccessKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
item.Credential, err = encrypt.StringDecryptWithBase64(item.Credential)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data = append(data, item)
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *BackupService) GetLocalDir() (string, error) {
|
|
||||||
account, err := backupRepo.Get(repo.WithByType(constant.Local))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
dir, err := LoadLocalDirByStr(account.Vars)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return dir, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *BackupService) SearchWithPage(req dto.SearchPageWithType) (int64, interface{}, error) {
|
|
||||||
options := []global.DBOption{repo.WithOrderBy("created_at desc")}
|
|
||||||
if len(req.Type) != 0 {
|
|
||||||
options = append(options, repo.WithByType(req.Type))
|
|
||||||
}
|
|
||||||
if len(req.Info) != 0 {
|
|
||||||
options = append(options, repo.WithByType(req.Info))
|
|
||||||
}
|
|
||||||
count, accounts, err := backupRepo.Page(req.Page, req.PageSize, options...)
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
var data []dto.BackupInfo
|
|
||||||
for _, account := range accounts {
|
|
||||||
var item dto.BackupInfo
|
|
||||||
if err := copier.Copy(&item, &account); err != nil {
|
|
||||||
global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err)
|
|
||||||
}
|
|
||||||
if !item.RememberAuth {
|
|
||||||
item.AccessKey = ""
|
|
||||||
item.Credential = ""
|
|
||||||
if account.Type == constant.Sftp {
|
|
||||||
varMap := make(map[string]interface{})
|
|
||||||
if err := json.Unmarshal([]byte(item.Vars), &varMap); err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
delete(varMap, "passPhrase")
|
|
||||||
itemVars, _ := json.Marshal(varMap)
|
|
||||||
item.Vars = string(itemVars)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
item.AccessKey = base64.StdEncoding.EncodeToString([]byte(item.AccessKey))
|
|
||||||
item.Credential = base64.StdEncoding.EncodeToString([]byte(item.Credential))
|
|
||||||
}
|
|
||||||
|
|
||||||
if account.Type == constant.OneDrive || account.Type == constant.ALIYUN || account.Type == constant.GoogleDrive {
|
|
||||||
varMap := make(map[string]interface{})
|
|
||||||
if err := json.Unmarshal([]byte(item.Vars), &varMap); err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
delete(varMap, "refresh_token")
|
|
||||||
delete(varMap, "drive_id")
|
|
||||||
itemVars, _ := json.Marshal(varMap)
|
|
||||||
item.Vars = string(itemVars)
|
|
||||||
}
|
|
||||||
data = append(data, item)
|
|
||||||
}
|
|
||||||
return count, data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *BackupService) LoadBackupClientInfo(clientType string) (dto.BackupClientInfo, error) {
|
func (u *BackupService) LoadBackupClientInfo(clientType string) (dto.BackupClientInfo, error) {
|
||||||
var data dto.BackupClientInfo
|
var data dto.BackupClientInfo
|
||||||
clientIDKey := "OneDriveID"
|
clientIDKey := "OneDriveID"
|
||||||
@ -190,10 +76,16 @@ func (u *BackupService) LoadBackupClientInfo(clientType string) (dto.BackupClien
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *BackupService) Create(req dto.BackupOperate) error {
|
func (u *BackupService) Create(req dto.BackupOperate) error {
|
||||||
|
if !req.IsPublic {
|
||||||
|
return buserr.New(constant.ErrBackupPublic)
|
||||||
|
}
|
||||||
backup, _ := backupRepo.Get(repo.WithByName(req.Name))
|
backup, _ := backupRepo.Get(repo.WithByName(req.Name))
|
||||||
if backup.ID != 0 {
|
if backup.ID != 0 {
|
||||||
return constant.ErrRecordExist
|
return constant.ErrRecordExist
|
||||||
}
|
}
|
||||||
|
if req.Type != constant.Sftp && req.BackupPath != "/" {
|
||||||
|
req.BackupPath = strings.TrimPrefix(req.BackupPath, "/")
|
||||||
|
}
|
||||||
if err := copier.Copy(&backup, &req); err != nil {
|
if err := copier.Copy(&backup, &req); err != nil {
|
||||||
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||||
}
|
}
|
||||||
@ -270,8 +162,11 @@ func (u *BackupService) Delete(id uint) error {
|
|||||||
if backup.ID == 0 {
|
if backup.ID == 0 {
|
||||||
return constant.ErrRecordNotFound
|
return constant.ErrRecordNotFound
|
||||||
}
|
}
|
||||||
|
if !backup.IsPublic {
|
||||||
|
return buserr.New(constant.ErrBackupPublic)
|
||||||
|
}
|
||||||
if backup.Type == constant.Local {
|
if backup.Type == constant.Local {
|
||||||
return buserr.New(constant.ErrBackupLocalDelete)
|
return buserr.New(constant.ErrBackupLocal)
|
||||||
}
|
}
|
||||||
if _, err := httpUtils.NewLocalClient(fmt.Sprintf("/api/v2/backups/check/%v", id), http.MethodGet, nil); err != nil {
|
if _, err := httpUtils.NewLocalClient(fmt.Sprintf("/api/v2/backups/check/%v", id), http.MethodGet, nil); err != nil {
|
||||||
global.LOG.Errorf("check used of local cronjob failed, err: %v", err)
|
global.LOG.Errorf("check used of local cronjob failed, err: %v", err)
|
||||||
@ -291,6 +186,15 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
|
|||||||
if backup.ID == 0 {
|
if backup.ID == 0 {
|
||||||
return constant.ErrRecordNotFound
|
return constant.ErrRecordNotFound
|
||||||
}
|
}
|
||||||
|
if !backup.IsPublic {
|
||||||
|
return buserr.New(constant.ErrBackupPublic)
|
||||||
|
}
|
||||||
|
if backup.Type == constant.Local {
|
||||||
|
return buserr.New(constant.ErrBackupLocal)
|
||||||
|
}
|
||||||
|
if req.Type != constant.Sftp && req.BackupPath != "/" {
|
||||||
|
req.BackupPath = strings.TrimPrefix(req.BackupPath, "/")
|
||||||
|
}
|
||||||
var newBackup model.BackupAccount
|
var newBackup model.BackupAccount
|
||||||
if err := copier.Copy(&newBackup, &req); err != nil {
|
if err := copier.Copy(&newBackup, &req); err != nil {
|
||||||
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
return errors.WithMessage(constant.ErrStructTransform, err.Error())
|
||||||
@ -305,36 +209,15 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
newBackup.Credential = string(itemCredential)
|
newBackup.Credential = string(itemCredential)
|
||||||
if backup.Type == constant.Local {
|
|
||||||
if newBackup.Vars != backup.Vars {
|
|
||||||
oldPath, err := LoadLocalDirByStr(backup.Vars)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
newPath, err := LoadLocalDirByStr(newBackup.Vars)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(newPath, "/") && newPath != "/" {
|
|
||||||
newPath = newPath[:strings.LastIndex(newPath, "/")]
|
|
||||||
}
|
|
||||||
if err := copyDir(oldPath, newPath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
global.CONF.System.BackupDir = newPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if newBackup.Type == constant.OneDrive || newBackup.Type == constant.GoogleDrive {
|
if newBackup.Type == constant.OneDrive || newBackup.Type == constant.GoogleDrive {
|
||||||
if err := u.loadRefreshTokenByCode(&backup); err != nil {
|
if err := u.loadRefreshTokenByCode(&backup); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if backup.Type != "LOCAL" {
|
isOk, err := u.checkBackupConn(&newBackup)
|
||||||
isOk, err := u.checkBackupConn(&newBackup)
|
if err != nil || !isOk {
|
||||||
if err != nil || !isOk {
|
return buserr.WithMap("ErrBackupCheck", map[string]interface{}{"err": err.Error()}, err)
|
||||||
return buserr.WithMap("ErrBackupCheck", map[string]interface{}{"err": err.Error()}, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newBackup.AccessKey, err = encrypt.StringEncrypt(newBackup.AccessKey)
|
newBackup.AccessKey, err = encrypt.StringEncrypt(newBackup.AccessKey)
|
||||||
@ -353,6 +236,44 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *BackupService) RefreshToken(req dto.OperateByID) error {
|
||||||
|
backup, _ := backupRepo.Get(repo.WithByID(req.ID))
|
||||||
|
if backup.ID == 0 {
|
||||||
|
return constant.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
if !backup.IsPublic {
|
||||||
|
return buserr.New(constant.ErrBackupPublic)
|
||||||
|
}
|
||||||
|
varMap := make(map[string]interface{})
|
||||||
|
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||||
|
return fmt.Errorf("Failed to refresh %s - %s token, please retry, err: %v", backup.Type, backup.Name, err)
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
refreshToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch backup.Type {
|
||||||
|
case constant.OneDrive:
|
||||||
|
refreshToken, err = client.RefreshToken("refresh_token", "refreshToken", varMap)
|
||||||
|
case constant.GoogleDrive:
|
||||||
|
refreshToken, err = client.RefreshGoogleToken("refresh_token", "refreshToken", varMap)
|
||||||
|
case constant.ALIYUN:
|
||||||
|
refreshToken, err = client.RefreshALIToken(varMap)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
varMap["refresh_status"] = constant.StatusFailed
|
||||||
|
varMap["refresh_msg"] = err.Error()
|
||||||
|
return fmt.Errorf("Failed to refresh %s-%s token, please retry, err: %v", backup.Type, backup.Name, err)
|
||||||
|
}
|
||||||
|
varMap["refresh_status"] = constant.StatusSuccess
|
||||||
|
varMap["refresh_time"] = time.Now().Format(constant.DateTimeLayout)
|
||||||
|
varMap["refresh_token"] = refreshToken
|
||||||
|
|
||||||
|
varsItem, _ := json.Marshal(varMap)
|
||||||
|
backup.Vars = string(varsItem)
|
||||||
|
return backupRepo.Save(&backup)
|
||||||
|
}
|
||||||
|
|
||||||
func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error) {
|
func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error) {
|
||||||
varMap := make(map[string]interface{})
|
varMap := make(map[string]interface{})
|
||||||
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
|
||||||
@ -409,55 +330,6 @@ func (u *BackupService) loadRefreshTokenByCode(backup *model.BackupAccount) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadLocalDirByStr(vars string) (string, error) {
|
|
||||||
varMap := make(map[string]interface{})
|
|
||||||
if err := json.Unmarshal([]byte(vars), &varMap); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if _, ok := varMap["dir"]; !ok {
|
|
||||||
return "", errors.New("load local backup dir failed")
|
|
||||||
}
|
|
||||||
baseDir, ok := varMap["dir"].(string)
|
|
||||||
if ok {
|
|
||||||
if _, err := os.Stat(baseDir); err != nil && os.IsNotExist(err) {
|
|
||||||
if err = os.MkdirAll(baseDir, os.ModePerm); err != nil {
|
|
||||||
return "", fmt.Errorf("mkdir %s failed, err: %v", baseDir, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return baseDir, nil
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("error type dir: %T", varMap["dir"])
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyDir(src, dst string) error {
|
|
||||||
srcInfo, err := os.Stat(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = os.MkdirAll(dst, srcInfo.Mode()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
files, err := os.ReadDir(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
srcPath := fmt.Sprintf("%s/%s", src, file.Name())
|
|
||||||
dstPath := fmt.Sprintf("%s/%s", dst, file.Name())
|
|
||||||
if file.IsDir() {
|
|
||||||
if err = copyDir(srcPath, dstPath); err != nil {
|
|
||||||
global.LOG.Errorf("copy dir %s to %s failed, err: %v", srcPath, dstPath, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := fileUtils.CopyFile(srcPath, dst, false); err != nil {
|
|
||||||
global.LOG.Errorf("copy file %s to %s failed, err: %v", srcPath, dstPath, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, error) {
|
func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, error) {
|
||||||
client, err := u.NewClient(backup)
|
client, err := u.NewClient(backup)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -481,74 +353,22 @@ func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, erro
|
|||||||
_, _ = write.WriteString("1Panelアカウントのテストファイルをバックアップします。\n")
|
_, _ = write.WriteString("1Panelアカウントのテストファイルをバックアップします。\n")
|
||||||
write.Flush()
|
write.Flush()
|
||||||
|
|
||||||
targetPath := strings.TrimPrefix(path.Join(backup.BackupPath, "test/1panel"), "/")
|
targetPath := path.Join(backup.BackupPath, "test/1panel")
|
||||||
|
if backup.Type != constant.Sftp && backup.Type != constant.Local && targetPath != "/" {
|
||||||
|
targetPath = strings.TrimPrefix(targetPath, "/")
|
||||||
|
}
|
||||||
return client.Upload(fileItem, targetPath)
|
return client.Upload(fileItem, targetPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartRefreshForToken() error {
|
func syncAccountToAgent(backup model.BackupAccount, operation string) {
|
||||||
service := NewIBackupService()
|
if !backup.IsPublic {
|
||||||
refreshID, err := global.Cron.AddJob("0 3 */31 * *", service)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("add cron job of refresh backup account token failed, err: %s", err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
global.BackupAccountTokenEntryID = refreshID
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *BackupService) Run() {
|
|
||||||
refreshToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
func refreshToken() {
|
|
||||||
var backups []model.BackupAccount
|
|
||||||
_ = global.DB.Where("`type` in (?)", []string{constant.OneDrive, constant.ALIYUN, constant.GoogleDrive}).Find(&backups)
|
|
||||||
if len(backups) == 0 {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, backupItem := range backups {
|
|
||||||
if backupItem.ID == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
varMap := make(map[string]interface{})
|
|
||||||
if err := json.Unmarshal([]byte(backupItem.Vars), &varMap); err != nil {
|
|
||||||
global.LOG.Errorf("Failed to refresh %s - %s token, please retry, err: %v", backupItem.Type, backupItem.Name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
refreshToken string
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
switch backupItem.Type {
|
|
||||||
case constant.OneDrive:
|
|
||||||
refreshToken, err = client.RefreshToken("refresh_token", "refreshToken", varMap)
|
|
||||||
case constant.GoogleDrive:
|
|
||||||
refreshToken, err = client.RefreshGoogleToken("refresh_token", "refreshToken", varMap)
|
|
||||||
case constant.ALIYUN:
|
|
||||||
refreshToken, err = client.RefreshALIToken(varMap)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
varMap["refresh_status"] = constant.StatusFailed
|
|
||||||
varMap["refresh_msg"] = err.Error()
|
|
||||||
global.LOG.Errorf("Failed to refresh OneDrive token, please retry, err: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
varMap["refresh_status"] = constant.StatusSuccess
|
|
||||||
varMap["refresh_time"] = time.Now().Format(constant.DateTimeLayout)
|
|
||||||
varMap["refresh_token"] = refreshToken
|
|
||||||
|
|
||||||
varsItem, _ := json.Marshal(varMap)
|
|
||||||
_ = global.DB.Model(&model.BackupAccount{}).Where("id = ?", backupItem.ID).Updates(map[string]interface{}{"vars": varsItem}).Error
|
|
||||||
|
|
||||||
go syncAccountToAgent(backupItem, "update")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func syncAccountToAgent(backup model.BackupAccount, operation string) {
|
|
||||||
backup.AccessKey, _ = encrypt.StringDecryptWithBase64(backup.AccessKey)
|
backup.AccessKey, _ = encrypt.StringDecryptWithBase64(backup.AccessKey)
|
||||||
backup.Credential, _ = encrypt.StringDecryptWithBase64(backup.Credential)
|
backup.Credential, _ = encrypt.StringDecryptWithBase64(backup.Credential)
|
||||||
itemData, _ := json.Marshal(backup)
|
itemData, _ := json.Marshal(backup)
|
||||||
itemJson := dto.SyncToAgent{Name: backup.Name, Operation: operation, Data: string(itemData)}
|
itemJson := dto.SyncToAgent{Name: backup.Name, Operation: operation, Data: string(itemData)}
|
||||||
bodyItem, _ := json.Marshal(itemJson)
|
bodyItem, _ := json.Marshal(itemJson)
|
||||||
_ = xpack.RequestToAgent("/api/v2/backups/sync", http.MethodPost, bytes.NewReader((bodyItem)))
|
_ = xpack.RequestToAllAgent("/api/v2/backups/sync", http.MethodPost, bytes.NewReader((bodyItem)))
|
||||||
|
_, _ = httpUtils.NewLocalClient("/api/v2/backups/sync", http.MethodPost, bytes.NewReader((bodyItem)))
|
||||||
}
|
}
|
||||||
|
@ -62,8 +62,9 @@ var (
|
|||||||
|
|
||||||
// backup
|
// backup
|
||||||
var (
|
var (
|
||||||
ErrBackupInUsed = "ErrBackupInUsed"
|
ErrBackupInUsed = "ErrBackupInUsed"
|
||||||
ErrBackupLocalDelete = "ErrBackupLocalDelete"
|
ErrBackupLocal = "ErrBackupLocal"
|
||||||
|
ErrBackupPublic = "ErrBackupPublic"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -23,8 +23,6 @@ var (
|
|||||||
I18n *i18n.Localizer
|
I18n *i18n.Localizer
|
||||||
|
|
||||||
Cron *cron.Cron
|
Cron *cron.Cron
|
||||||
|
|
||||||
BackupAccountTokenEntryID cron.EntryID
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DBOption func(*gorm.DB) *gorm.DB
|
type DBOption func(*gorm.DB) *gorm.DB
|
||||||
|
@ -28,7 +28,8 @@ ErrNoSuchHost: "Network connection failed"
|
|||||||
#backup
|
#backup
|
||||||
ErrBackupInUsed: "The backup account is currently in use in a scheduled task and cannot be deleted."
|
ErrBackupInUsed: "The backup account is currently in use in a scheduled task and cannot be deleted."
|
||||||
ErrBackupCheck: "Backup account test connection failed {{.err}}"
|
ErrBackupCheck: "Backup account test connection failed {{.err}}"
|
||||||
ErrBackupLocalDelete: "Deleting local server backup accounts is not currently supported."
|
ErrBackupLocal: "The local server backup account does not support this operation at the moment!"
|
||||||
|
ErrBackupPublic: "Detected that the backup account is non-public, please check and try again!"
|
||||||
|
|
||||||
#license
|
#license
|
||||||
ErrLicense: "License format error, please check and try again!"
|
ErrLicense: "License format error, please check and try again!"
|
||||||
|
@ -28,7 +28,8 @@ ErrNoSuchHost: "網路連接失敗"
|
|||||||
#backup
|
#backup
|
||||||
ErrBackupInUsed: "該備份帳號已在計劃任務中使用,無法刪除"
|
ErrBackupInUsed: "該備份帳號已在計劃任務中使用,無法刪除"
|
||||||
ErrBackupCheck: "備份帳號測試連接失敗 {{.err}}"
|
ErrBackupCheck: "備份帳號測試連接失敗 {{.err}}"
|
||||||
ErrBackupLocalDelete: "暫不支持刪除本地伺服器備份帳號"
|
ErrBackupLocal: "本地伺服器備份帳號暫不支持該操作!"
|
||||||
|
ErrBackupPublic: "檢測到該備份帳號為非公用,請檢查後重試!"
|
||||||
|
|
||||||
#license
|
#license
|
||||||
ErrLicense: "許可證格式錯誤,請檢查後重試!"
|
ErrLicense: "許可證格式錯誤,請檢查後重試!"
|
||||||
|
@ -29,7 +29,8 @@ ErrNoSuchHost: "网络连接失败"
|
|||||||
#backup
|
#backup
|
||||||
ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除"
|
ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除"
|
||||||
ErrBackupCheck: "备份账号测试连接失败 {{ .err}}"
|
ErrBackupCheck: "备份账号测试连接失败 {{ .err}}"
|
||||||
ErrBackupLocalDelete: "暂不支持删除本地服务器备份账号"
|
ErrBackupLocal: "本地服务器备份账号暂不支持该操作!"
|
||||||
|
ErrBackupPublic: "检测到该备份账号为非公用,请检查后重试!"
|
||||||
|
|
||||||
#license
|
#license
|
||||||
ErrLicense: "许可证格式错误,请检查后重试!"
|
ErrLicense: "许可证格式错误,请检查后重试!"
|
||||||
|
@ -3,8 +3,8 @@ package cron
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/core/app/service"
|
|
||||||
"github.com/1Panel-dev/1Panel/core/global"
|
"github.com/1Panel-dev/1Panel/core/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/init/cron/job"
|
||||||
"github.com/1Panel-dev/1Panel/core/utils/common"
|
"github.com/1Panel-dev/1Panel/core/utils/common"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
)
|
)
|
||||||
@ -13,6 +13,8 @@ func Init() {
|
|||||||
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||||
global.Cron = cron.New(cron.WithLocation(nyc), cron.WithChain(cron.Recover(cron.DefaultLogger)), cron.WithChain(cron.DelayIfStillRunning(cron.DefaultLogger)))
|
global.Cron = cron.New(cron.WithLocation(nyc), cron.WithChain(cron.Recover(cron.DefaultLogger)), cron.WithChain(cron.DelayIfStillRunning(cron.DefaultLogger)))
|
||||||
|
|
||||||
_ = service.StartRefreshForToken()
|
if _, err := global.Cron.AddJob("0 3 */31 * *", job.NewBackupJob()); err != nil {
|
||||||
|
global.LOG.Errorf("[core] can not add backup token refresh corn job: %s", err.Error())
|
||||||
|
}
|
||||||
global.Cron.Start()
|
global.Cron.Start()
|
||||||
}
|
}
|
||||||
|
61
core/init/cron/job/backup.go
Normal file
61
core/init/cron/job/backup.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package job
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/core/app/model"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/global"
|
||||||
|
"github.com/1Panel-dev/1Panel/core/utils/cloud_storage/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type backup struct{}
|
||||||
|
|
||||||
|
func NewBackupJob() *backup {
|
||||||
|
return &backup{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backup) Run() {
|
||||||
|
var backups []model.BackupAccount
|
||||||
|
_ = global.DB.Where("`type` in (?) AND is_public = 0", []string{constant.OneDrive, constant.ALIYUN, constant.GoogleDrive}).Find(&backups)
|
||||||
|
if len(backups) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, backupItem := range backups {
|
||||||
|
if backupItem.ID == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
global.LOG.Infof("Start to refresh %s-%s access_token ...", backupItem.Type, backupItem.Name)
|
||||||
|
varMap := make(map[string]interface{})
|
||||||
|
if err := json.Unmarshal([]byte(backupItem.Vars), &varMap); err != nil {
|
||||||
|
global.LOG.Errorf("Failed to refresh %s - %s token, please retry, err: %v", backupItem.Type, backupItem.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
refreshToken string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch backupItem.Type {
|
||||||
|
case constant.OneDrive:
|
||||||
|
refreshToken, err = client.RefreshToken("refresh_token", "refreshToken", varMap)
|
||||||
|
case constant.GoogleDrive:
|
||||||
|
refreshToken, err = client.RefreshGoogleToken("refresh_token", "refreshToken", varMap)
|
||||||
|
case constant.ALIYUN:
|
||||||
|
refreshToken, err = client.RefreshALIToken(varMap)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
varMap["refresh_status"] = constant.StatusFailed
|
||||||
|
varMap["refresh_msg"] = err.Error()
|
||||||
|
global.LOG.Errorf("Failed to refresh %s-%s token, please retry, err: %v", backupItem.Type, backupItem.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
varMap["refresh_status"] = constant.StatusSuccess
|
||||||
|
varMap["refresh_time"] = time.Now().Format(constant.DateTimeLayout)
|
||||||
|
varMap["refresh_token"] = refreshToken
|
||||||
|
|
||||||
|
varsItem, _ := json.Marshal(varMap)
|
||||||
|
_ = global.DB.Model(&model.BackupAccount{}).Where("id = ?", backupItem.ID).Updates(map[string]interface{}{"vars": string(varsItem)}).Error
|
||||||
|
global.LOG.Infof("Refresh %s-%s access_token successful!", backupItem.Type, backupItem.Name)
|
||||||
|
}
|
||||||
|
}
|
@ -3,9 +3,7 @@ package hook
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/core/app/model"
|
|
||||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||||
"github.com/1Panel-dev/1Panel/core/app/service"
|
|
||||||
"github.com/1Panel-dev/1Panel/core/global"
|
"github.com/1Panel-dev/1Panel/core/global"
|
||||||
"github.com/1Panel-dev/1Panel/core/utils/cmd"
|
"github.com/1Panel-dev/1Panel/core/utils/cmd"
|
||||||
"github.com/1Panel-dev/1Panel/core/utils/common"
|
"github.com/1Panel-dev/1Panel/core/utils/common"
|
||||||
@ -48,7 +46,6 @@ func Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleUserInfo(global.CONF.System.ChangeUserInfo, settingRepo)
|
handleUserInfo(global.CONF.System.ChangeUserInfo, settingRepo)
|
||||||
loadLocalDir()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleUserInfo(tags string, settingRepo repo.ISettingRepo) {
|
func handleUserInfo(tags string, settingRepo repo.ISettingRepo) {
|
||||||
@ -88,18 +85,3 @@ func handleUserInfo(tags string, settingRepo repo.ISettingRepo) {
|
|||||||
sudo := cmd.SudoHandleCmd()
|
sudo := cmd.SudoHandleCmd()
|
||||||
_, _ = cmd.Execf("%s sed -i '/CHANGE_USER_INFO=%v/d' /usr/local/bin/1pctl", sudo, global.CONF.System.ChangeUserInfo)
|
_, _ = cmd.Execf("%s sed -i '/CHANGE_USER_INFO=%v/d' /usr/local/bin/1pctl", sudo, global.CONF.System.ChangeUserInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadLocalDir() {
|
|
||||||
var backup model.BackupAccount
|
|
||||||
_ = global.DB.Where("type = ?", "LOCAL").First(&backup).Error
|
|
||||||
if backup.ID == 0 {
|
|
||||||
global.LOG.Errorf("no such backup account `%s` in db", "LOCAL")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dir, err := service.LoadLocalDirByStr(backup.Vars)
|
|
||||||
if err != nil {
|
|
||||||
global.LOG.Errorf("load local backup dir failed,err: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
global.CONF.System.BackupDir = dir
|
|
||||||
}
|
|
||||||
|
@ -19,6 +19,7 @@ func Init() {
|
|||||||
migrations.InitGoogle,
|
migrations.InitGoogle,
|
||||||
migrations.AddTaskDB,
|
migrations.AddTaskDB,
|
||||||
migrations.UpdateSettingStatus,
|
migrations.UpdateSettingStatus,
|
||||||
|
migrations.RemoveLocalBackup,
|
||||||
})
|
})
|
||||||
if err := m.Migrate(); err != nil {
|
if err := m.Migrate(); err != nil {
|
||||||
global.LOG.Error(err)
|
global.LOG.Error(err)
|
||||||
|
@ -15,7 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var AddTable = &gormigrate.Migration{
|
var AddTable = &gormigrate.Migration{
|
||||||
ID: "20241224-add-table",
|
ID: "20240109-add-table",
|
||||||
Migrate: func(tx *gorm.DB) error {
|
Migrate: func(tx *gorm.DB) error {
|
||||||
return tx.AutoMigrate(
|
return tx.AutoMigrate(
|
||||||
&model.OperationLog{},
|
&model.OperationLog{},
|
||||||
@ -273,3 +273,13 @@ var UpdateSettingStatus = &gormigrate.Migration{
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var RemoveLocalBackup = &gormigrate.Migration{
|
||||||
|
ID: "20250109-remove-local-backup",
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
if err := tx.Where("`type` = ?", constant.Local).Delete(&model.BackupAccount{}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
@ -211,7 +211,7 @@ func newDB(pathItem string) (*gorm.DB, error) {
|
|||||||
case strings.HasPrefix(pathItem, "/core"):
|
case strings.HasPrefix(pathItem, "/core"):
|
||||||
dbFile = path.Join(global.CONF.System.BaseDir, "1panel/db/core.db")
|
dbFile = path.Join(global.CONF.System.BaseDir, "1panel/db/core.db")
|
||||||
case strings.HasPrefix(pathItem, "/xpack"):
|
case strings.HasPrefix(pathItem, "/xpack"):
|
||||||
dbFile = path.Join(global.CONF.System.BaseDir, "1panel/db/xpack/xpack.db")
|
dbFile = path.Join(global.CONF.System.BaseDir, "1panel/db/xpack.db")
|
||||||
default:
|
default:
|
||||||
dbFile = path.Join(global.CONF.System.BaseDir, "1panel/db/agent.db")
|
dbFile = path.Join(global.CONF.System.BaseDir, "1panel/db/agent.db")
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,7 @@ func (s *BackupRouter) InitRouter(Router *gin.RouterGroup) {
|
|||||||
Use(middleware.PasswordExpired())
|
Use(middleware.PasswordExpired())
|
||||||
baseApi := v2.ApiGroupApp.BaseApi
|
baseApi := v2.ApiGroupApp.BaseApi
|
||||||
{
|
{
|
||||||
backupRouter.GET("/local", baseApi.GetLocalDir)
|
|
||||||
backupRouter.GET("/client/:clientType", baseApi.LoadBackupClientInfo)
|
backupRouter.GET("/client/:clientType", baseApi.LoadBackupClientInfo)
|
||||||
backupRouter.POST("/search", baseApi.SearchBackup)
|
|
||||||
backupRouter.POST("/refresh/token", baseApi.RefreshToken)
|
backupRouter.POST("/refresh/token", baseApi.RefreshToken)
|
||||||
backupRouter.POST("/buckets", baseApi.ListBuckets)
|
backupRouter.POST("/buckets", baseApi.ListBuckets)
|
||||||
backupRouter.POST("", baseApi.CreateBackup)
|
backupRouter.POST("", baseApi.CreateBackup)
|
||||||
|
@ -8,10 +8,10 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Proxy(c *gin.Context, currentNode string) { return }
|
func Proxy(c *gin.Context, currentNode string) {}
|
||||||
|
|
||||||
func UpdateGroup(name string, group, newGroup uint) error { return nil }
|
func UpdateGroup(name string, group, newGroup uint) error { return nil }
|
||||||
|
|
||||||
func CheckBackupUsed(id uint) error { return nil }
|
func CheckBackupUsed(id uint) error { return nil }
|
||||||
|
|
||||||
func RequestToAgent(reqUrl, reqMethod string, reqBody io.Reader) error { return nil }
|
func RequestToAllAgent(reqUrl, reqMethod string, reqBody io.Reader) error { return nil }
|
||||||
|
@ -14,6 +14,7 @@ export namespace Backup {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
isPublic: boolean;
|
||||||
accessKey: string;
|
accessKey: string;
|
||||||
bucket: string;
|
bucket: string;
|
||||||
credential: string;
|
credential: string;
|
||||||
@ -32,6 +33,8 @@ export namespace Backup {
|
|||||||
export interface BackupOperate {
|
export interface BackupOperate {
|
||||||
id: number;
|
id: number;
|
||||||
type: string;
|
type: string;
|
||||||
|
name: string;
|
||||||
|
isPublic: boolean;
|
||||||
accessKey: string;
|
accessKey: string;
|
||||||
bucket: string;
|
bucket: string;
|
||||||
credential: string;
|
credential: string;
|
||||||
@ -55,6 +58,7 @@ export namespace Backup {
|
|||||||
}
|
}
|
||||||
export interface ForBucket {
|
export interface ForBucket {
|
||||||
type: string;
|
type: string;
|
||||||
|
isPublic: boolean;
|
||||||
accessKey: string;
|
accessKey: string;
|
||||||
credential: string;
|
credential: string;
|
||||||
vars: string;
|
vars: string;
|
||||||
@ -64,6 +68,17 @@ export namespace Backup {
|
|||||||
name: string;
|
name: string;
|
||||||
detailName: string;
|
detailName: string;
|
||||||
}
|
}
|
||||||
|
export interface SearchForSize extends ReqPage {
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
detailName: string;
|
||||||
|
info: string;
|
||||||
|
cronjobID: number;
|
||||||
|
}
|
||||||
|
export interface RecordFileSize extends ReqPage {
|
||||||
|
id: number;
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
export interface SearchBackupRecordByCronjob extends ReqPage {
|
export interface SearchBackupRecordByCronjob extends ReqPage {
|
||||||
cronjobID: number;
|
cronjobID: number;
|
||||||
}
|
}
|
||||||
|
@ -29,9 +29,8 @@ export namespace Cronjob {
|
|||||||
files: Array<Item>;
|
files: Array<Item>;
|
||||||
sourceDir: string;
|
sourceDir: string;
|
||||||
|
|
||||||
sourceAccountIDs: string;
|
sourceAccounts: Array<string>;
|
||||||
downloadAccountID: number;
|
downloadAccount: string;
|
||||||
sourceAccounts: Array<number>;
|
|
||||||
retainCopies: number;
|
retainCopies: number;
|
||||||
status: string;
|
status: string;
|
||||||
secret: string;
|
secret: string;
|
||||||
|
@ -146,8 +146,8 @@ export namespace Setting {
|
|||||||
export interface SnapshotInfo {
|
export interface SnapshotInfo {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
from: string;
|
sourceAccounts: Array<string>;
|
||||||
defaultDownload: string;
|
downloadAccount: string;
|
||||||
description: string;
|
description: string;
|
||||||
status: string;
|
status: string;
|
||||||
message: string;
|
message: string;
|
||||||
@ -165,13 +165,6 @@ export namespace Setting {
|
|||||||
rollbackStatus: string;
|
rollbackStatus: string;
|
||||||
rollbackMessage: string;
|
rollbackMessage: string;
|
||||||
}
|
}
|
||||||
export interface SnapshotFile {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
from: string;
|
|
||||||
defaultDownload: string;
|
|
||||||
size: number;
|
|
||||||
}
|
|
||||||
export interface SnapshotData {
|
export interface SnapshotData {
|
||||||
appData: Array<DataTree>;
|
appData: Array<DataTree>;
|
||||||
panelData: Array<DataTree>;
|
panelData: Array<DataTree>;
|
||||||
|
@ -4,8 +4,16 @@ import { Base64 } from 'js-base64';
|
|||||||
import { ResPage } from '../interface';
|
import { ResPage } from '../interface';
|
||||||
import { Backup } from '../interface/backup';
|
import { Backup } from '../interface/backup';
|
||||||
import { TimeoutEnum } from '@/enums/http-enum';
|
import { TimeoutEnum } from '@/enums/http-enum';
|
||||||
|
import { GlobalStore } from '@/store';
|
||||||
|
const globalStore = GlobalStore();
|
||||||
|
|
||||||
// backup-agent
|
// backup-agent
|
||||||
|
export const getLocalBackupDir = () => {
|
||||||
|
return http.get<string>(`/backups/local`);
|
||||||
|
};
|
||||||
|
export const searchBackup = (params: Backup.SearchWithType) => {
|
||||||
|
return http.post<ResPage<Backup.BackupInfo>>(`/backups/search`, params);
|
||||||
|
};
|
||||||
export const handleBackup = (params: Backup.Backup) => {
|
export const handleBackup = (params: Backup.Backup) => {
|
||||||
return http.post(`/backups/backup`, params, TimeoutEnum.T_1H);
|
return http.post(`/backups/backup`, params, TimeoutEnum.T_1H);
|
||||||
};
|
};
|
||||||
@ -27,6 +35,9 @@ export const deleteBackupRecord = (params: { ids: number[] }) => {
|
|||||||
export const searchBackupRecords = (params: Backup.SearchBackupRecord) => {
|
export const searchBackupRecords = (params: Backup.SearchBackupRecord) => {
|
||||||
return http.post<ResPage<Backup.RecordInfo>>(`/backups/record/search`, params, TimeoutEnum.T_5M);
|
return http.post<ResPage<Backup.RecordInfo>>(`/backups/record/search`, params, TimeoutEnum.T_5M);
|
||||||
};
|
};
|
||||||
|
export const loadRecordSize = (param: Backup.SearchForSize) => {
|
||||||
|
return http.post<Array<Backup.RecordFileSize>>(`/backups/record/size`, param);
|
||||||
|
};
|
||||||
export const searchBackupRecordsByCronjob = (params: Backup.SearchBackupRecordByCronjob) => {
|
export const searchBackupRecordsByCronjob = (params: Backup.SearchBackupRecordByCronjob) => {
|
||||||
return http.post<ResPage<Backup.RecordInfo>>(`/backups/record/search/bycronjob`, params, TimeoutEnum.T_5M);
|
return http.post<ResPage<Backup.RecordInfo>>(`/backups/record/search/bycronjob`, params, TimeoutEnum.T_5M);
|
||||||
};
|
};
|
||||||
@ -35,14 +46,12 @@ export const getFilesFromBackup = (id: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// backup-core
|
// backup-core
|
||||||
export const refreshToken = () => {
|
export const refreshToken = (params: { id: number; isPublic: boolean }) => {
|
||||||
return http.post(`/core/backups/refresh/token`, {});
|
let urlItem = '/core/backups/refresh/token';
|
||||||
};
|
if (!params.isPublic || !globalStore.isProductPro) {
|
||||||
export const getLocalBackupDir = () => {
|
urlItem = '/backups/refresh/token';
|
||||||
return http.get<string>(`/core/backups/local`);
|
}
|
||||||
};
|
return http.post(urlItem, { id: params.id });
|
||||||
export const searchBackup = (params: Backup.SearchWithType) => {
|
|
||||||
return http.post<ResPage<Backup.BackupInfo>>(`/core/backups/search`, params);
|
|
||||||
};
|
};
|
||||||
export const getClientInfo = (clientType: string) => {
|
export const getClientInfo = (clientType: string) => {
|
||||||
return http.get<Backup.ClientInfo>(`/core/backups/client/${clientType}`);
|
return http.get<Backup.ClientInfo>(`/core/backups/client/${clientType}`);
|
||||||
@ -55,7 +64,11 @@ export const addBackup = (params: Backup.BackupOperate) => {
|
|||||||
if (request.credential) {
|
if (request.credential) {
|
||||||
request.credential = Base64.encode(request.credential);
|
request.credential = Base64.encode(request.credential);
|
||||||
}
|
}
|
||||||
return http.post<Backup.BackupOperate>(`/core/backups`, request, TimeoutEnum.T_60S);
|
let urlItem = '/core/backups';
|
||||||
|
if (!params.isPublic || !globalStore.isProductPro) {
|
||||||
|
urlItem = '/backups';
|
||||||
|
}
|
||||||
|
return http.post<Backup.BackupOperate>(urlItem, request, TimeoutEnum.T_60S);
|
||||||
};
|
};
|
||||||
export const editBackup = (params: Backup.BackupOperate) => {
|
export const editBackup = (params: Backup.BackupOperate) => {
|
||||||
let request = deepCopy(params) as Backup.BackupOperate;
|
let request = deepCopy(params) as Backup.BackupOperate;
|
||||||
@ -65,10 +78,18 @@ export const editBackup = (params: Backup.BackupOperate) => {
|
|||||||
if (request.credential) {
|
if (request.credential) {
|
||||||
request.credential = Base64.encode(request.credential);
|
request.credential = Base64.encode(request.credential);
|
||||||
}
|
}
|
||||||
return http.post(`/core/backups/update`, request);
|
let urlItem = '/core/backups/update';
|
||||||
|
if (!params.isPublic || !globalStore.isProductPro) {
|
||||||
|
urlItem = '/backups/update';
|
||||||
|
}
|
||||||
|
return http.post(urlItem, request);
|
||||||
};
|
};
|
||||||
export const deleteBackup = (params: { id: number }) => {
|
export const deleteBackup = (params: { id: number; isPublic: boolean }) => {
|
||||||
return http.post(`/core/backups/del`, params);
|
let urlItem = '/core/backups/del';
|
||||||
|
if (!params.isPublic || !globalStore.isProductPro) {
|
||||||
|
urlItem = '/backups/del';
|
||||||
|
}
|
||||||
|
return http.post(urlItem, { id: params.id });
|
||||||
};
|
};
|
||||||
export const listBucket = (params: Backup.ForBucket) => {
|
export const listBucket = (params: Backup.ForBucket) => {
|
||||||
let request = deepCopy(params) as Backup.BackupOperate;
|
let request = deepCopy(params) as Backup.BackupOperate;
|
||||||
@ -78,5 +99,9 @@ export const listBucket = (params: Backup.ForBucket) => {
|
|||||||
if (request.credential) {
|
if (request.credential) {
|
||||||
request.credential = Base64.encode(request.credential);
|
request.credential = Base64.encode(request.credential);
|
||||||
}
|
}
|
||||||
return http.post(`/core/backups/buckets`, request);
|
let urlItem = '/core/backups/buckets';
|
||||||
|
if (!params.isPublic || !globalStore.isProductPro) {
|
||||||
|
urlItem = '/backups/buckets';
|
||||||
|
}
|
||||||
|
return http.post(urlItem, request);
|
||||||
};
|
};
|
||||||
|
@ -139,9 +139,6 @@ export const snapshotRollback = (param: Setting.SnapshotRecover) => {
|
|||||||
export const searchSnapshotPage = (param: SearchWithPage) => {
|
export const searchSnapshotPage = (param: SearchWithPage) => {
|
||||||
return http.post<ResPage<Setting.SnapshotInfo>>(`/settings/snapshot/search`, param);
|
return http.post<ResPage<Setting.SnapshotInfo>>(`/settings/snapshot/search`, param);
|
||||||
};
|
};
|
||||||
export const loadSnapshotSize = (param: SearchWithPage) => {
|
|
||||||
return http.post<Array<Setting.SnapshotFile>>(`/settings/snapshot/size`, param);
|
|
||||||
};
|
|
||||||
|
|
||||||
// upgrade
|
// upgrade
|
||||||
export const loadUpgradeInfo = () => {
|
export const loadUpgradeInfo = () => {
|
||||||
|
@ -38,10 +38,15 @@
|
|||||||
<el-table-column :label="$t('commons.table.name')" prop="fileName" show-overflow-tooltip />
|
<el-table-column :label="$t('commons.table.name')" prop="fileName" show-overflow-tooltip />
|
||||||
<el-table-column :label="$t('file.size')" prop="size" show-overflow-tooltip>
|
<el-table-column :label="$t('file.size')" prop="size" show-overflow-tooltip>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="row.size">
|
<div v-if="row.hasLoad">
|
||||||
{{ computeSize(row.size) }}
|
<span v-if="row.size">
|
||||||
</span>
|
{{ computeSize(row.size) }}
|
||||||
<span v-else>-</span>
|
</span>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="!row.hasLoad">
|
||||||
|
<el-button link loading></el-button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('database.source')" prop="backupType">
|
<el-table-column :label="$t('database.source')" prop="backupType">
|
||||||
@ -108,6 +113,7 @@ import {
|
|||||||
deleteBackupRecord,
|
deleteBackupRecord,
|
||||||
downloadBackupRecord,
|
downloadBackupRecord,
|
||||||
searchBackupRecords,
|
searchBackupRecords,
|
||||||
|
loadRecordSize,
|
||||||
} from '@/api/modules/backup';
|
} from '@/api/modules/backup';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { Backup } from '@/api/interface/backup';
|
import { Backup } from '@/api/interface/backup';
|
||||||
@ -188,6 +194,7 @@ const search = async () => {
|
|||||||
await searchBackupRecords(params)
|
await searchBackupRecords(params)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
loadSize(params);
|
||||||
data.value = res.data.items || [];
|
data.value = res.data.items || [];
|
||||||
paginationConfig.total = res.data.total;
|
paginationConfig.total = res.data.total;
|
||||||
})
|
})
|
||||||
@ -196,6 +203,28 @@ const search = async () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadSize = async (params: any) => {
|
||||||
|
await loadRecordSize(params)
|
||||||
|
.then((res) => {
|
||||||
|
let stats = res.data || [];
|
||||||
|
if (stats.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const backup of data.value) {
|
||||||
|
for (const item of stats) {
|
||||||
|
if (backup.id === item.id) {
|
||||||
|
backup.hasLoad = true;
|
||||||
|
backup.size = item.size;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const openTaskLog = (taskID: string) => {
|
const openTaskLog = (taskID: string) => {
|
||||||
taskLogRef.value.openWithTaskID(taskID);
|
taskLogRef.value.openWithTaskID(taskID);
|
||||||
};
|
};
|
||||||
|
@ -1479,6 +1479,13 @@ const message = {
|
|||||||
developerModeHelper: 'Get a preview version of 1Panel to provide feedback on new features and updates',
|
developerModeHelper: 'Get a preview version of 1Panel to provide feedback on new features and updates',
|
||||||
|
|
||||||
thirdParty: 'Third-party Account',
|
thirdParty: 'Third-party Account',
|
||||||
|
scope: 'Scope',
|
||||||
|
public: 'Public',
|
||||||
|
publicHelper:
|
||||||
|
'Public type backup accounts will be synchronized to each sub-node, and sub-nodes can use them together',
|
||||||
|
private: 'Private',
|
||||||
|
privateHelper:
|
||||||
|
'Private type backup accounts are only created on the current node and are for the use of the current node only',
|
||||||
createBackupAccount: 'Add {0}',
|
createBackupAccount: 'Add {0}',
|
||||||
noTypeForCreate: 'No backup type is currently created',
|
noTypeForCreate: 'No backup type is currently created',
|
||||||
LOCAL: 'Server Disks',
|
LOCAL: 'Server Disks',
|
||||||
|
@ -1390,6 +1390,11 @@ const message = {
|
|||||||
developerModeHelper: '獲取 1Panel 的預覽版本,以分享有關新功能和更新的反饋',
|
developerModeHelper: '獲取 1Panel 的預覽版本,以分享有關新功能和更新的反饋',
|
||||||
|
|
||||||
thirdParty: '第三方賬號',
|
thirdParty: '第三方賬號',
|
||||||
|
scope: '使用範圍',
|
||||||
|
public: '公有',
|
||||||
|
publicHelper: '公有類型的備份帳號會同步到各個子節點,子節點可以一起使用',
|
||||||
|
private: '私有',
|
||||||
|
privateHelper: '私有類型的備份帳號只創建在當前節點上,僅供當前節點使用',
|
||||||
createBackupAccount: '添加 {0}',
|
createBackupAccount: '添加 {0}',
|
||||||
noTypeForCreate: '當前無可創建備份類型',
|
noTypeForCreate: '當前無可創建備份類型',
|
||||||
LOCAL: '服務器磁盤',
|
LOCAL: '服務器磁盤',
|
||||||
|
@ -1391,6 +1391,11 @@ const message = {
|
|||||||
developerModeHelper: '获取 1Panel 的预览版本,以分享有关新功能和更新的反馈',
|
developerModeHelper: '获取 1Panel 的预览版本,以分享有关新功能和更新的反馈',
|
||||||
|
|
||||||
thirdParty: '第三方账号',
|
thirdParty: '第三方账号',
|
||||||
|
scope: '使用范围',
|
||||||
|
public: '公有',
|
||||||
|
publicHelper: '公有类型的备份账号会同步到各个子节点,子节点可以一起使用',
|
||||||
|
private: '私有',
|
||||||
|
privateHelper: '私有类型的备份账号只创建在当前节点上,仅供当前节点使用',
|
||||||
createBackupAccount: '添加 {0}',
|
createBackupAccount: '添加 {0}',
|
||||||
noTypeForCreate: '当前无可创建备份类型',
|
noTypeForCreate: '当前无可创建备份类型',
|
||||||
LOCAL: '服务器磁盘',
|
LOCAL: '服务器磁盘',
|
||||||
|
@ -170,7 +170,7 @@ const loadNodes = async () => {
|
|||||||
nodes.value = [];
|
nodes.value = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
nodes.value = res.data;
|
nodes.value = res.data || [];
|
||||||
if (nodes.value.length === 0) {
|
if (nodes.value.length === 0) {
|
||||||
globalStore.currentNode = 'local';
|
globalStore.currentNode = 'local';
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,15 @@
|
|||||||
<el-table-column :label="$t('commons.table.name')" prop="fileName" show-overflow-tooltip />
|
<el-table-column :label="$t('commons.table.name')" prop="fileName" show-overflow-tooltip />
|
||||||
<el-table-column :label="$t('file.size')" prop="size" show-overflow-tooltip>
|
<el-table-column :label="$t('file.size')" prop="size" show-overflow-tooltip>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="row.size">
|
<div v-if="row.hasLoad">
|
||||||
{{ computeSize(row.size) }}
|
<span v-if="row.size">
|
||||||
</span>
|
{{ computeSize(row.size) }}
|
||||||
<span v-else>-</span>
|
</span>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="!row.hasLoad">
|
||||||
|
<el-button link loading></el-button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :label="$t('database.source')" prop="accountType" show-overflow-tooltip>
|
<el-table-column :label="$t('database.source')" prop="accountType" show-overflow-tooltip>
|
||||||
@ -51,7 +56,7 @@
|
|||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { computeSize, dateFormat, downloadFile } from '@/utils/util';
|
import { computeSize, dateFormat, downloadFile } from '@/utils/util';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { downloadBackupRecord, searchBackupRecordsByCronjob } from '@/api/modules/backup';
|
import { downloadBackupRecord, loadRecordSize, searchBackupRecordsByCronjob } from '@/api/modules/backup';
|
||||||
import { Backup } from '@/api/interface/backup';
|
import { Backup } from '@/api/interface/backup';
|
||||||
import { MsgError } from '@/utils/message';
|
import { MsgError } from '@/utils/message';
|
||||||
import { GlobalStore } from '@/store';
|
import { GlobalStore } from '@/store';
|
||||||
@ -96,6 +101,7 @@ const search = async () => {
|
|||||||
await searchBackupRecordsByCronjob(params)
|
await searchBackupRecordsByCronjob(params)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
loadSize(params);
|
||||||
data.value = res.data.items || [];
|
data.value = res.data.items || [];
|
||||||
paginationConfig.total = res.data.total;
|
paginationConfig.total = res.data.total;
|
||||||
})
|
})
|
||||||
@ -104,6 +110,29 @@ const search = async () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadSize = async (params: any) => {
|
||||||
|
params.type = 'cronjob';
|
||||||
|
await loadRecordSize(params)
|
||||||
|
.then((res) => {
|
||||||
|
let stats = res.data || [];
|
||||||
|
if (stats.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const backup of data.value) {
|
||||||
|
for (const item of stats) {
|
||||||
|
if (backup.id === item.id) {
|
||||||
|
backup.hasLoad = true;
|
||||||
|
backup.size = item.size;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const onDownload = async (row: Backup.RecordInfo) => {
|
const onDownload = async (row: Backup.RecordInfo) => {
|
||||||
if (row.accountType === 'ALIYUN' && row.size < 100 * 1024 * 1024) {
|
if (row.accountType === 'ALIYUN' && row.size < 100 * 1024 * 1024) {
|
||||||
MsgError(i18n.global.t('setting.ALIYUNHelper'));
|
MsgError(i18n.global.t('setting.ALIYUNHelper'));
|
||||||
|
@ -99,33 +99,34 @@
|
|||||||
{{ row.lastRecordTime }}
|
{{ row.lastRecordTime }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column :min-width="80" :label="$t('setting.backupAccount')" prop="defaultDownload">
|
<el-table-column :min-width="80" :label="$t('setting.backupAccount')">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="!hasBackup(row.type)">-</span>
|
<span v-if="!hasBackup(row.type)">-</span>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div v-for="(item, index) of row.backupAccounts?.split(',')" :key="index">
|
<div v-for="(item, index) of row.sourceAccounts" :key="index">
|
||||||
<div v-if="row.accountExpand || (!row.accountExpand && index < 3)">
|
<div v-if="row.accountExpand || (!row.accountExpand && index < 3)">
|
||||||
<span v-if="row.backupAccounts">
|
<div v-if="row.expand || (!row.expand && index < 3)">
|
||||||
<span>
|
<span type="info">
|
||||||
{{ $t('setting.' + item) }}
|
<span>
|
||||||
|
{{ loadName(item) }}
|
||||||
|
</span>
|
||||||
|
<el-icon
|
||||||
|
v-if="item === row.downloadAccount"
|
||||||
|
size="12"
|
||||||
|
class="relative top-px left-1"
|
||||||
|
>
|
||||||
|
<Star />
|
||||||
|
</el-icon>
|
||||||
</span>
|
</span>
|
||||||
<el-icon
|
</div>
|
||||||
size="12"
|
|
||||||
v-if="item === row.defaultDownload"
|
|
||||||
class="relative top-px left-1"
|
|
||||||
>
|
|
||||||
<Star />
|
|
||||||
</el-icon>
|
|
||||||
</span>
|
|
||||||
<span v-else>-</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!row.accountExpand && row.backupAccounts?.split(',').length > 3">
|
<div v-if="!row.accountExpand && row.sourceAccounts?.length > 3">
|
||||||
<el-button type="primary" link @click="row.accountExpand = true">
|
<el-button type="primary" link @click="row.accountExpand = true">
|
||||||
{{ $t('commons.button.expand') }}...
|
{{ $t('commons.button.expand') }}...
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="row.accountExpand && row.backupAccounts?.split(',').length > 3">
|
<div v-if="row.accountExpand && row.sourceAccounts?.length > 3">
|
||||||
<el-button type="primary" link @click="row.accountExpand = false">
|
<el-button type="primary" link @click="row.accountExpand = false">
|
||||||
{{ $t('commons.button.collapse') }}
|
{{ $t('commons.button.collapse') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -209,18 +210,6 @@ const search = async (column?: any) => {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
data.value = res.data.items || [];
|
data.value = res.data.items || [];
|
||||||
for (const item of data.value) {
|
|
||||||
let itemAccounts = item.backupAccounts.split(',') || [];
|
|
||||||
let accounts = [];
|
|
||||||
for (const account of itemAccounts) {
|
|
||||||
if (account == item.defaultDownload) {
|
|
||||||
accounts.unshift(account);
|
|
||||||
} else {
|
|
||||||
accounts.push(account);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item.itemAccounts = accounts.join(',');
|
|
||||||
}
|
|
||||||
paginationConfig.total = res.data.total;
|
paginationConfig.total = res.data.total;
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@ -364,6 +353,11 @@ const loadDetail = (row: any) => {
|
|||||||
dialogRecordRef.value!.acceptParams(params);
|
dialogRecordRef.value!.acceptParams(params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadName = (from: any) => {
|
||||||
|
let items = from.split(' - ');
|
||||||
|
return i18n.global.t('setting.' + items[0]) + ' ' + items[1];
|
||||||
|
};
|
||||||
|
|
||||||
const buttons = [
|
const buttons = [
|
||||||
{
|
{
|
||||||
label: i18n.global.t('commons.button.handle'),
|
label: i18n.global.t('commons.button.handle'),
|
||||||
|
@ -409,7 +409,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="isBackup()">
|
<div v-if="isBackup()">
|
||||||
<el-form-item :label="$t('setting.backupAccount')" prop="backupAccountList">
|
<el-form-item :label="$t('setting.backupAccount')" prop="sourceAccounts">
|
||||||
<el-select
|
<el-select
|
||||||
multiple
|
multiple
|
||||||
class="selectClass"
|
class="selectClass"
|
||||||
@ -561,7 +561,6 @@ const acceptParams = (params: DialogProps): void => {
|
|||||||
changeType();
|
changeType();
|
||||||
dialogData.value.rowData.scriptMode = 'input';
|
dialogData.value.rowData.scriptMode = 'input';
|
||||||
dialogData.value.rowData.dbType = 'mysql';
|
dialogData.value.rowData.dbType = 'mysql';
|
||||||
dialogData.value.rowData.downloadAccountID = 1;
|
|
||||||
dialogData.value.rowData.isDir = true;
|
dialogData.value.rowData.isDir = true;
|
||||||
}
|
}
|
||||||
if (dialogData.value.rowData.sourceAccountIDs) {
|
if (dialogData.value.rowData.sourceAccountIDs) {
|
||||||
@ -748,8 +747,8 @@ const rules = reactive({
|
|||||||
url: [Rules.requiredInput],
|
url: [Rules.requiredInput],
|
||||||
files: [{ validator: verifyFiles, trigger: 'blur', required: true }],
|
files: [{ validator: verifyFiles, trigger: 'blur', required: true }],
|
||||||
sourceDir: [Rules.requiredInput],
|
sourceDir: [Rules.requiredInput],
|
||||||
backupAccounts: [Rules.requiredSelect],
|
sourceAccounts: [Rules.requiredSelect],
|
||||||
defaultDownload: [Rules.requiredSelect],
|
downloadAccountID: [Rules.requiredSelect],
|
||||||
retainCopies: [Rules.number],
|
retainCopies: [Rules.number],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -867,15 +866,19 @@ const loadBackups = async () => {
|
|||||||
const res = await listBackupOptions();
|
const res = await listBackupOptions();
|
||||||
let options = res.data || [];
|
let options = res.data || [];
|
||||||
backupOptions.value = [];
|
backupOptions.value = [];
|
||||||
if (!dialogData.value.rowData!.sourceAccounts) {
|
let local = 0;
|
||||||
dialogData.value.rowData!.sourceAccounts = [1];
|
|
||||||
}
|
|
||||||
for (const item of options) {
|
for (const item of options) {
|
||||||
if (item.id === 0) {
|
if (item.id === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (item.type == 'LOCAL') {
|
||||||
|
local = item.id;
|
||||||
|
}
|
||||||
backupOptions.value.push({ id: item.id, type: i18n.global.t('setting.' + item.type), name: item.name });
|
backupOptions.value.push({ id: item.id, type: i18n.global.t('setting.' + item.type), name: item.name });
|
||||||
}
|
}
|
||||||
|
if (!dialogData.value.rowData!.sourceAccounts) {
|
||||||
|
dialogData.value.rowData!.sourceAccounts = local === 0 ? [local] : [];
|
||||||
|
}
|
||||||
changeAccount();
|
changeAccount();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -34,10 +34,22 @@
|
|||||||
prop="name"
|
prop="name"
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
/>
|
/>
|
||||||
|
<el-table-column
|
||||||
|
v-if="globalStore.isProductPro"
|
||||||
|
:label="$t('setting.scope')"
|
||||||
|
:min-width="80"
|
||||||
|
prop="isPublic"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button plain size="small">
|
||||||
|
{{ row.isPublic ? $t('setting.public') : $t('setting.private') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column :label="$t('commons.table.type')" :min-width="80" prop="type">
|
<el-table-column :label="$t('commons.table.type')" :min-width="80" prop="type">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag>{{ $t('setting.' + row.type) }}</el-tag>
|
<el-tag>{{ $t('setting.' + row.type) }}</el-tag>
|
||||||
<el-tooltip>
|
<el-tooltip v-if="row.type === 'OneDrive'">
|
||||||
<template #content>
|
<template #content>
|
||||||
{{ $t('setting.clickToRefresh') }}
|
{{ $t('setting.clickToRefresh') }}
|
||||||
<br />
|
<br />
|
||||||
@ -56,7 +68,7 @@
|
|||||||
<br />
|
<br />
|
||||||
{{ $t('setting.refreshTime') + ':' + row.varsJson['refresh_time'] }}
|
{{ $t('setting.refreshTime') + ':' + row.varsJson['refresh_time'] }}
|
||||||
</template>
|
</template>
|
||||||
<el-tag @click="refreshItemToken" v-if="row.type === 'OneDrive'" class="ml-1">
|
<el-tag @click="refreshItemToken(row)" class="ml-1">
|
||||||
{{ 'Token ' + $t('commons.button.refresh') }}
|
{{ 'Token ' + $t('commons.button.refresh') }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
@ -102,6 +114,8 @@ import Operate from '@/views/setting/backup-account/operate/index.vue';
|
|||||||
import { Backup } from '@/api/interface/backup';
|
import { Backup } from '@/api/interface/backup';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
|
import { GlobalStore } from '@/store';
|
||||||
|
const globalStore = GlobalStore();
|
||||||
|
|
||||||
const loading = ref();
|
const loading = ref();
|
||||||
const data = ref();
|
const data = ref();
|
||||||
@ -159,7 +173,7 @@ const onDelete = async (row: Backup.BackupInfo) => {
|
|||||||
i18n.global.t('commons.button.delete'),
|
i18n.global.t('commons.button.delete'),
|
||||||
]),
|
]),
|
||||||
api: deleteBackup,
|
api: deleteBackup,
|
||||||
params: { id: row.id },
|
params: { id: row.id, isPublic: row.isPublic },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -167,6 +181,7 @@ const onOpenDialog = async (
|
|||||||
title: string,
|
title: string,
|
||||||
rowData: Partial<Backup.BackupInfo> = {
|
rowData: Partial<Backup.BackupInfo> = {
|
||||||
id: 0,
|
id: 0,
|
||||||
|
isPublic: false,
|
||||||
varsJson: {},
|
varsJson: {},
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
@ -177,8 +192,8 @@ const onOpenDialog = async (
|
|||||||
dialogRef.value!.acceptParams(params);
|
dialogRef.value!.acceptParams(params);
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshItemToken = async () => {
|
const refreshItemToken = async (row: any) => {
|
||||||
await refreshToken();
|
await refreshToken({ id: row.id, isPublic: row.isPublic });
|
||||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
search();
|
search();
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,20 @@
|
|||||||
<el-tag v-if="dialogData.title === 'edit'">{{ dialogData.rowData!.name }}</el-tag>
|
<el-tag v-if="dialogData.title === 'edit'">{{ dialogData.rowData!.name }}</el-tag>
|
||||||
<el-input v-else v-model="dialogData.rowData!.name" />
|
<el-input v-else v-model="dialogData.rowData!.name" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="globalStore.isProductPro"
|
||||||
|
:label="$t('setting.scope')"
|
||||||
|
prop="isPublic"
|
||||||
|
:rules="Rules.requiredSelect"
|
||||||
|
>
|
||||||
|
<el-radio-group v-model="dialogData.rowData!.isPublic">
|
||||||
|
<el-radio :value="true" size="large">{{ $t('setting.public') }}</el-radio>
|
||||||
|
<el-radio :value="false" size="large">{{ $t('setting.private') }}</el-radio>
|
||||||
|
<span class="input-help">
|
||||||
|
{{ dialogData.rowData!.isPublic ? $t('setting.publicHelper') : $t('setting.privateHelper') }}
|
||||||
|
</span>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
|
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
|
||||||
<el-tag v-if="dialogData.title === 'edit'">{{ $t('setting.' + dialogData.rowData!.type) }}</el-tag>
|
<el-tag v-if="dialogData.title === 'edit'">{{ $t('setting.' + dialogData.rowData!.type) }}</el-tag>
|
||||||
<el-select v-else v-model="dialogData.rowData!.type" @change="changeType">
|
<el-select v-else v-model="dialogData.rowData!.type" @change="changeType">
|
||||||
@ -147,7 +161,7 @@
|
|||||||
>
|
>
|
||||||
<el-input v-model.trim="dialogData.rowData!.varsJson['endpointItem']">
|
<el-input v-model.trim="dialogData.rowData!.varsJson['endpointItem']">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<el-select v-model.trim="domainProto" class="p-w-120">
|
<el-select v-model.trim="domainProto" class="p-w-100">
|
||||||
<el-option label="http" value="http" />
|
<el-option label="http" value="http" />
|
||||||
<el-option label="https" value="https" />
|
<el-option label="https" value="https" />
|
||||||
</el-select>
|
</el-select>
|
||||||
@ -348,18 +362,18 @@
|
|||||||
<el-form-item
|
<el-form-item
|
||||||
v-if="dialogData.rowData!.type === 'SFTP'"
|
v-if="dialogData.rowData!.type === 'SFTP'"
|
||||||
:label="$t('setting.backupDir')"
|
:label="$t('setting.backupDir')"
|
||||||
prop="bucket"
|
prop="backupPath"
|
||||||
:rules="[Rules.requiredInput]"
|
:rules="[Rules.requiredInput]"
|
||||||
>
|
>
|
||||||
<el-input v-model.trim="dialogData.rowData!.bucket" />
|
<el-input v-model.trim="dialogData.rowData!.backupPath" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item
|
<el-form-item
|
||||||
v-if="dialogData.rowData!.type === 'LOCAL'"
|
v-if="dialogData.rowData!.type === 'LOCAL'"
|
||||||
:label="$t('setting.backupDir')"
|
:label="$t('setting.backupDir')"
|
||||||
prop="varsJson['dir']"
|
prop="backupPath"
|
||||||
:rules="Rules.requiredInput"
|
:rules="Rules.requiredInput"
|
||||||
>
|
>
|
||||||
<el-input v-model="dialogData.rowData!.varsJson['dir']">
|
<el-input v-model="dialogData.rowData!.backupPath">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<FileList @choose="loadDir" :dir="true"></FileList>
|
<FileList @choose="loadDir" :dir="true"></FileList>
|
||||||
</template>
|
</template>
|
||||||
@ -388,6 +402,8 @@ import { cities } from './../helper';
|
|||||||
import { deepCopy, spliceHttp, splitHttp } from '@/utils/util';
|
import { deepCopy, spliceHttp, splitHttp } from '@/utils/util';
|
||||||
import { MsgSuccess } from '@/utils/message';
|
import { MsgSuccess } from '@/utils/message';
|
||||||
import { Base64 } from 'js-base64';
|
import { Base64 } from 'js-base64';
|
||||||
|
import { GlobalStore } from '@/store';
|
||||||
|
const globalStore = GlobalStore();
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
type FormInstance = InstanceType<typeof ElForm>;
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
@ -527,13 +543,14 @@ const hasPassword = () => {
|
|||||||
let itemType = dialogData.value.rowData!.type;
|
let itemType = dialogData.value.rowData!.type;
|
||||||
return itemType === 'SFTP' || itemType === 'WebDAV';
|
return itemType === 'SFTP' || itemType === 'WebDAV';
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasBackDir = () => {
|
const hasBackDir = () => {
|
||||||
let itemType = dialogData.value.rowData!.type;
|
let itemType = dialogData.value.rowData!.type;
|
||||||
return itemType !== 'LOCAL' && itemType !== 'SFTP';
|
return itemType !== 'LOCAL' && itemType !== 'SFTP';
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadDir = async (path: string) => {
|
const loadDir = async (path: string) => {
|
||||||
dialogData.value.rowData!.varsJson['dir'] = path;
|
dialogData.value.rowData!.backupPath = path;
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeType = async () => {
|
const changeType = async () => {
|
||||||
@ -610,6 +627,7 @@ const getBuckets = async () => {
|
|||||||
}
|
}
|
||||||
item['endpointItem'] = undefined;
|
item['endpointItem'] = undefined;
|
||||||
listBucket({
|
listBucket({
|
||||||
|
isPublic: dialogData.value.rowData!.isPublic,
|
||||||
type: dialogData.value.rowData!.type,
|
type: dialogData.value.rowData!.type,
|
||||||
vars: JSON.stringify(item),
|
vars: JSON.stringify(item),
|
||||||
accessKey: dialogData.value.rowData!.accessKey,
|
accessKey: dialogData.value.rowData!.accessKey,
|
||||||
|
@ -41,30 +41,29 @@
|
|||||||
<el-table-column prop="version" :label="$t('app.version')" />
|
<el-table-column prop="version" :label="$t('app.version')" />
|
||||||
<el-table-column :label="$t('setting.backupAccount')" min-width="80" prop="from">
|
<el-table-column :label="$t('setting.backupAccount')" min-width="80" prop="from">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div v-if="row.hasLoad">
|
<div>
|
||||||
<div v-for="(item, index) of row.from.split(',')" :key="index" class="mt-1">
|
<div v-for="(item, index) of row.sourceAccounts" :key="index" class="mt-1">
|
||||||
<div v-if="row.expand || (!row.expand && index < 3)">
|
<div v-if="row.expand || (!row.expand && index < 3)">
|
||||||
<span v-if="row.from" type="info">
|
<span type="info">
|
||||||
<span>
|
<span>
|
||||||
{{ loadName(item) }}
|
{{ loadName(item) }}
|
||||||
</span>
|
</span>
|
||||||
<el-icon
|
<el-icon
|
||||||
v-if="item === row.defaultDownload"
|
v-if="item === row.downloadAccount"
|
||||||
size="12"
|
size="12"
|
||||||
class="relative top-px left-1"
|
class="relative top-px left-1"
|
||||||
>
|
>
|
||||||
<Star />
|
<Star />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</span>
|
</span>
|
||||||
<span v-else>-</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!row.expand && row.from.split(',').length > 3">
|
<div v-if="!row.expand && row.sourceAccounts.length > 3">
|
||||||
<el-button type="primary" link @click="row.expand = true">
|
<el-button type="primary" link @click="row.expand = true">
|
||||||
{{ $t('commons.button.expand') }}...
|
{{ $t('commons.button.expand') }}...
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="row.expand && row.from.split(',').length > 3">
|
<div v-if="row.expand && row.sourceAccounts.length > 3">
|
||||||
<el-button type="primary" link @click="row.expand = false">
|
<el-button type="primary" link @click="row.expand = false">
|
||||||
{{ $t('commons.button.collapse') }}
|
{{ $t('commons.button.collapse') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -182,7 +181,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
searchSnapshotPage,
|
searchSnapshotPage,
|
||||||
loadSnapshotSize,
|
|
||||||
snapshotDelete,
|
snapshotDelete,
|
||||||
snapshotRecreate,
|
snapshotRecreate,
|
||||||
snapshotRollback,
|
snapshotRollback,
|
||||||
@ -201,6 +199,7 @@ import SnapshotCreate from '@/views/setting/snapshot/create/index.vue';
|
|||||||
import SnapRecover from '@/views/setting/snapshot/recover/index.vue';
|
import SnapRecover from '@/views/setting/snapshot/recover/index.vue';
|
||||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||||
import { loadOsInfo } from '@/api/modules/dashboard';
|
import { loadOsInfo } from '@/api/modules/dashboard';
|
||||||
|
import { loadRecordSize } from '@/api/modules/backup';
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const data = ref();
|
const data = ref();
|
||||||
@ -296,7 +295,7 @@ const onChange = async (info: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onRecover = async (row: any) => {
|
const onRecover = async (row: any) => {
|
||||||
if (row.defaultDownload.indexOf('ALIYUN') !== -1 && row.size > 100 * 1024 * 1024) {
|
if (row.downloadAccount.indexOf('ALIYUN') !== -1 && row.size > 100 * 1024 * 1024) {
|
||||||
MsgError(i18n.global.t('setting.ALIYUNRecover'));
|
MsgError(i18n.global.t('setting.ALIYUNRecover'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -396,7 +395,7 @@ const search = async (column?: any) => {
|
|||||||
await searchSnapshotPage(params)
|
await searchSnapshotPage(params)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
loadSize();
|
loadSize(params);
|
||||||
cleanData.value = false;
|
cleanData.value = false;
|
||||||
data.value = res.data.items || [];
|
data.value = res.data.items || [];
|
||||||
paginationConfig.total = res.data.total;
|
paginationConfig.total = res.data.total;
|
||||||
@ -406,13 +405,9 @@ const search = async (column?: any) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadSize = async () => {
|
const loadSize = async (params: any) => {
|
||||||
let params = {
|
params.type = 'snapshot';
|
||||||
info: searchName.value,
|
await loadRecordSize(params)
|
||||||
page: paginationConfig.currentPage,
|
|
||||||
pageSize: paginationConfig.pageSize,
|
|
||||||
};
|
|
||||||
await loadSnapshotSize(params)
|
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
let stats = res.data || [];
|
let stats = res.data || [];
|
||||||
if (stats.length === 0) {
|
if (stats.length === 0) {
|
||||||
@ -422,8 +417,6 @@ const loadSize = async () => {
|
|||||||
for (const item of stats) {
|
for (const item of stats) {
|
||||||
if (snap.id === item.id) {
|
if (snap.id === item.id) {
|
||||||
snap.hasLoad = true;
|
snap.hasLoad = true;
|
||||||
snap.from = item.from;
|
|
||||||
snap.defaultDownload = item.defaultDownload;
|
|
||||||
snap.size = item.size;
|
snap.size = item.size;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
esbuild: {
|
esbuild: {
|
||||||
pure: viteEnv.VITE_DROP_CONSOLE ? ['console.log', 'debugger'] : [],
|
pure: viteEnv.VITE_DROP_CONSOLE ? ['console.log'] : [],
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: '../core/cmd/server/web',
|
outDir: '../core/cmd/server/web',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user