1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-02-14 20:40:06 +08:00

fix: Fix the issue of backup account synchronization failure (#7821)

This commit is contained in:
ssongliu 2025-02-08 14:25:26 +08:00 committed by GitHub
parent 6cd72f1a56
commit 8440d77fd7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 73 additions and 46 deletions

View File

@ -11,13 +11,13 @@ import (
) )
func (b *BaseApi) CheckBackupUsed(c *gin.Context) { func (b *BaseApi) CheckBackupUsed(c *gin.Context) {
id, err := helper.GetIntParamByKey(c, "id") name, err := helper.GetStrParamByKey(c, "name")
if err != nil { if err != nil {
helper.BadRequest(c, err) helper.BadRequest(c, err)
return return
} }
if err := backupService.CheckUsed(id); err != nil { if err := backupService.CheckUsed(name); err != nil {
helper.BadRequest(c, err) helper.BadRequest(c, err)
return return
} }

View File

@ -17,6 +17,7 @@ type IBackupRepo interface {
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
WithByPublic(isPublic bool) DBOption
ListRecord(opts ...DBOption) ([]model.BackupRecord, error) ListRecord(opts ...DBOption) ([]model.BackupRecord, error)
PageRecord(page, size int, opts ...DBOption) (int64, []model.BackupRecord, error) PageRecord(page, size int, opts ...DBOption) (int64, []model.BackupRecord, error)
@ -35,6 +36,12 @@ func NewIBackupRepo() IBackupRepo {
return &BackupRepo{} return &BackupRepo{}
} }
func (u *BackupRepo) WithByPublic(isPublic bool) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("is_public = ?", isPublic)
}
}
func (u *BackupRepo) Get(opts ...DBOption) (model.BackupAccount, error) { func (u *BackupRepo) Get(opts ...DBOption) (model.BackupAccount, error) {
var backup model.BackupAccount var backup model.BackupAccount
db := global.DB db := global.DB

View File

@ -28,7 +28,7 @@ import (
type BackupService struct{} type BackupService struct{}
type IBackupService interface { type IBackupService interface {
CheckUsed(id uint) error CheckUsed(name string) error
Sync(req dto.SyncFromMaster) error Sync(req dto.SyncFromMaster) error
LoadBackupOptions() ([]dto.BackupOption, error) LoadBackupOptions() ([]dto.BackupOption, error)
@ -103,8 +103,8 @@ func (u *BackupService) SearchWithPage(req dto.SearchPageWithType) (int64, inter
item.Vars = string(itemVars) item.Vars = string(itemVars)
} }
} else { } else {
item.AccessKey = base64.StdEncoding.EncodeToString([]byte(item.AccessKey)) item.AccessKey, _ = encrypt.StringDecryptWithBase64(item.AccessKey)
item.Credential = base64.StdEncoding.EncodeToString([]byte(item.Credential)) item.Credential, _ = encrypt.StringDecryptWithBase64(item.Credential)
} }
if account.Type == constant.OneDrive || account.Type == constant.ALIYUN || account.Type == constant.GoogleDrive { if account.Type == constant.OneDrive || account.Type == constant.ALIYUN || account.Type == constant.GoogleDrive {
@ -394,15 +394,19 @@ func (u *BackupService) LoadBackupOptions() ([]dto.BackupOption, error) {
return data, nil return data, nil
} }
func (u *BackupService) CheckUsed(id uint) error { func (u *BackupService) CheckUsed(name string) error {
account, _ := backupRepo.Get(repo.WithByName(name), backupRepo.WithByPublic(true))
if account.ID == 0 {
return nil
}
cronjobs, _ := cronjobRepo.List() cronjobs, _ := cronjobRepo.List()
for _, job := range cronjobs { for _, job := range cronjobs {
if job.DownloadAccountID == id { if job.DownloadAccountID == account.ID {
return buserr.New("ErrBackupInUsed") return buserr.New("ErrBackupInUsed")
} }
ids := strings.Split(job.SourceAccountIDs, ",") ids := strings.Split(job.SourceAccountIDs, ",")
for _, idItem := range ids { for _, idItem := range ids {
if idItem == fmt.Sprintf("%v", id) { if idItem == fmt.Sprintf("%v", account.ID) {
return buserr.New("ErrBackupInUsed") return buserr.New("ErrBackupInUsed")
} }
} }

View File

@ -1,6 +1,7 @@
package hook package hook
import ( import (
"os"
"path" "path"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
@ -102,4 +103,10 @@ func loadLocalDir() {
return return
} }
global.Dir.LocalBackupDir = account.BackupPath global.Dir.LocalBackupDir = account.BackupPath
if _, err := os.Stat(account.BackupPath); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(account.BackupPath, os.ModePerm); err != nil {
global.LOG.Errorf("mkdir %s failed, err: %v", account.BackupPath, err)
}
}
} }

View File

@ -11,7 +11,7 @@ func (s *BackupRouter) InitRouter(Router *gin.RouterGroup) {
backupRouter := Router.Group("backups") backupRouter := Router.Group("backups")
baseApi := v2.ApiGroupApp.BaseApi baseApi := v2.ApiGroupApp.BaseApi
{ {
backupRouter.GET("/check/:id", baseApi.CheckBackupUsed) backupRouter.GET("/check/:name", 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.POST("/search", baseApi.SearchBackup)

View File

@ -33,13 +33,13 @@ func (b *BaseApi) CreateBackup(c *gin.Context) {
// @Tags Backup Account // @Tags Backup Account
// @Summary Refresh token // @Summary Refresh token
// @Accept json // @Accept json
// @Param request body dto.BackupOperate true "request" // @Param request body dto.OperateByName true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Security Timestamp // @Security Timestamp
// @Router /core/backups/refresh/token [post] // @Router /core/backups/refresh/token [post]
func (b *BaseApi) RefreshToken(c *gin.Context) { func (b *BaseApi) RefreshToken(c *gin.Context) {
var req dto.OperateByID var req dto.OperateByName
if err := helper.CheckBindAndValidate(&req, c); err != nil { if err := helper.CheckBindAndValidate(&req, c); err != nil {
return return
} }
@ -96,19 +96,19 @@ func (b *BaseApi) LoadBackupClientInfo(c *gin.Context) {
// @Tags Backup Account // @Tags Backup Account
// @Summary Delete backup account // @Summary Delete backup account
// @Accept json // @Accept json
// @Param request body dto.OperateByID true "request" // @Param request body dto.OperateByName true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Security Timestamp // @Security Timestamp
// @Router /core/backups/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":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除备份账号 [name]","formatEN":"delete backup account [name]"}
func (b *BaseApi) DeleteBackup(c *gin.Context) { func (b *BaseApi) DeleteBackup(c *gin.Context) {
var req dto.OperateByID var req dto.OperateByName
if err := helper.CheckBindAndValidate(&req, c); err != nil { if err := helper.CheckBindAndValidate(&req, c); err != nil {
return return
} }
if err := backupService.Delete(req.ID); err != nil { if err := backupService.Delete(req.Name); err != nil {
helper.InternalServer(c, err) helper.InternalServer(c, err)
return return
} }

View File

@ -35,6 +35,10 @@ type OperateByType struct {
Type string `json:"type"` Type string `json:"type"`
} }
type OperateByName struct {
Name string `json:"name"`
}
type OperateByID struct { type OperateByID struct {
ID uint `json:"id"` ID uint `json:"id"`
} }

View File

@ -33,8 +33,8 @@ type IBackupService interface {
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(name string) error
RefreshToken(req dto.OperateByID) error RefreshToken(req dto.OperateByName) error
} }
func NewIBackupService() IBackupService { func NewIBackupService() IBackupService {
@ -156,8 +156,8 @@ func (u *BackupService) GetBuckets(req dto.ForBuckets) ([]interface{}, error) {
return client.ListBuckets() return client.ListBuckets()
} }
func (u *BackupService) Delete(id uint) error { func (u *BackupService) Delete(name string) error {
backup, _ := backupRepo.Get(repo.WithByID(id)) backup, _ := backupRepo.Get(repo.WithByName(name))
if backup.ID == 0 { if backup.ID == 0 {
return buserr.New("ErrRecordNotFound") return buserr.New("ErrRecordNotFound")
} }
@ -167,21 +167,21 @@ func (u *BackupService) Delete(id uint) error {
if backup.Type == constant.Local { if backup.Type == constant.Local {
return buserr.New("ErrBackupLocal") return buserr.New("ErrBackupLocal")
} }
if _, err := req_helper.NewLocalClient(fmt.Sprintf("/api/v2/backups/check/%v", id), http.MethodGet, nil); err != nil { if _, err := req_helper.NewLocalClient(fmt.Sprintf("/api/v2/backups/check/%s", name), 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)
return buserr.New("ErrBackupInUsed") return buserr.New("ErrBackupInUsed")
} }
if err := xpack.CheckBackupUsed(id); err != nil { if err := xpack.CheckBackupUsed(name); err != nil {
global.LOG.Errorf("check used of node cronjob failed, err: %v", err) global.LOG.Errorf("check used of node cronjob failed, err: %v", err)
return buserr.New("ErrBackupInUsed") return buserr.New("ErrBackupInUsed")
} }
go syncAccountToAgent(backup, "delete") go syncAccountToAgent(backup, "delete")
return backupRepo.Delete(repo.WithByID(id)) return backupRepo.Delete(repo.WithByName(name))
} }
func (u *BackupService) Update(req dto.BackupOperate) error { func (u *BackupService) Update(req dto.BackupOperate) error {
backup, _ := backupRepo.Get(repo.WithByID(req.ID)) backup, _ := backupRepo.Get(repo.WithByName(req.Name))
if backup.ID == 0 { if backup.ID == 0 {
return buserr.New("ErrRecordNotFound") return buserr.New("ErrRecordNotFound")
} }
@ -198,6 +198,7 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
if err := copier.Copy(&newBackup, &req); err != nil { if err := copier.Copy(&newBackup, &req); err != nil {
return buserr.WithDetail("ErrStructTransform", err.Error(), nil) return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
} }
newBackup.ID = backup.ID
itemAccessKey, err := base64.StdEncoding.DecodeString(newBackup.AccessKey) itemAccessKey, err := base64.StdEncoding.DecodeString(newBackup.AccessKey)
if err != nil { if err != nil {
return err return err
@ -235,8 +236,8 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
return nil return nil
} }
func (u *BackupService) RefreshToken(req dto.OperateByID) error { func (u *BackupService) RefreshToken(req dto.OperateByName) error {
backup, _ := backupRepo.Get(repo.WithByID(req.ID)) backup, _ := backupRepo.Get(repo.WithByName(req.Name))
if backup.ID == 0 { if backup.ID == 0 {
return buserr.New("ErrRecordNotFound") return buserr.New("ErrRecordNotFound")
} }

View File

@ -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 ""

View File

@ -16,7 +16,7 @@ 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(name string) error { return nil }
func RequestToAllAgent(reqUrl, reqMethod string, reqBody io.Reader) error { return nil } func RequestToAllAgent(reqUrl, reqMethod string, reqBody io.Reader) error { return nil }

View File

@ -46,12 +46,11 @@ export const getFilesFromBackup = (id: number) => {
}; };
// backup-core // backup-core
export const refreshToken = (params: { id: number; isPublic: boolean }) => { export const refreshToken = (params: { id: number; name: string; isPublic: boolean }) => {
let urlItem = '/core/backups/refresh/token'; if (!params.isPublic) {
if (!params.isPublic || !globalStore.isProductPro) { return http.post('/backups/refresh/token', { id: params.id });
urlItem = '/backups/refresh/token';
} }
return http.post(urlItem, { id: params.id }); return http.post('/core/backups/refresh/token', { name: params.name });
}; };
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}`);
@ -65,7 +64,7 @@ export const addBackup = (params: Backup.BackupOperate) => {
request.credential = Base64.encode(request.credential); request.credential = Base64.encode(request.credential);
} }
let urlItem = '/core/backups'; let urlItem = '/core/backups';
if (!params.isPublic || !globalStore.isProductPro) { if (!params.isPublic) {
urlItem = '/backups'; urlItem = '/backups';
} }
return http.post<Backup.BackupOperate>(urlItem, request, TimeoutEnum.T_60S); return http.post<Backup.BackupOperate>(urlItem, request, TimeoutEnum.T_60S);
@ -79,17 +78,16 @@ export const editBackup = (params: Backup.BackupOperate) => {
request.credential = Base64.encode(request.credential); request.credential = Base64.encode(request.credential);
} }
let urlItem = '/core/backups/update'; let urlItem = '/core/backups/update';
if (!params.isPublic || !globalStore.isProductPro) { if (!params.isPublic) {
urlItem = '/backups/update'; urlItem = '/backups/update';
} }
return http.post(urlItem, request); return http.post(urlItem, request);
}; };
export const deleteBackup = (params: { id: number; isPublic: boolean }) => { export const deleteBackup = (params: { id: number; name: string; isPublic: boolean }) => {
let urlItem = '/core/backups/del'; if (!params.isPublic) {
if (!params.isPublic || !globalStore.isProductPro) { return http.post('/backups/del', { id: params.id });
urlItem = '/backups/del';
} }
return http.post(urlItem, { id: params.id }); return http.post('/core/backups/del', { name: params.name });
}; };
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;

View File

@ -167,13 +167,13 @@ const loadEndpoint = (row: any) => {
const onDelete = async (row: Backup.BackupInfo) => { const onDelete = async (row: Backup.BackupInfo) => {
opRef.value.acceptParams({ opRef.value.acceptParams({
title: i18n.global.t('commons.button.delete'), title: i18n.global.t('commons.button.delete'),
names: [row.type], names: ['[ ' + row.type + ' ] ' + row.name],
msg: i18n.global.t('commons.msg.operatorHelper', [ msg: i18n.global.t('commons.msg.operatorHelper', [
i18n.global.t('setting.backupAccount'), i18n.global.t('setting.backupAccount'),
i18n.global.t('commons.button.delete'), i18n.global.t('commons.button.delete'),
]), ]),
api: deleteBackup, api: deleteBackup,
params: { id: row.id, isPublic: row.isPublic }, params: { id: row.id, name: row.name, isPublic: row.isPublic },
}); });
}; };
@ -193,7 +193,7 @@ const onOpenDialog = async (
}; };
const refreshItemToken = async (row: any) => { const refreshItemToken = async (row: any) => {
await refreshToken({ id: row.id, isPublic: row.isPublic }); await refreshToken({ id: row.id, name: row.name, isPublic: row.isPublic });
MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search(); search();
}; };

View File

@ -11,13 +11,16 @@
prop="isPublic" prop="isPublic"
:rules="Rules.requiredSelect" :rules="Rules.requiredSelect"
> >
<el-radio-group v-model="dialogData.rowData!.isPublic"> <el-tag v-if="dialogData.title === 'edit'">
{{ dialogData.rowData!.isPublic ? $t('setting.public') : $t('setting.private') }}
</el-tag>
<el-radio-group v-else v-model="dialogData.rowData!.isPublic">
<el-radio :value="true" size="large">{{ $t('setting.public') }}</el-radio> <el-radio :value="true" size="large">{{ $t('setting.public') }}</el-radio>
<el-radio :value="false" size="large">{{ $t('setting.private') }}</el-radio> <el-radio :value="false" size="large">{{ $t('setting.private') }}</el-radio>
</el-radio-group>
<span class="input-help"> <span class="input-help">
{{ dialogData.rowData!.isPublic ? $t('setting.publicHelper') : $t('setting.privateHelper') }} {{ dialogData.rowData!.isPublic ? $t('setting.publicHelper') : $t('setting.privateHelper') }}
</span> </span>
</el-radio-group>
</el-form-item> </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>
@ -440,6 +443,9 @@ const dialogData = ref<DialogProps>({
}); });
const acceptParams = (params: DialogProps): void => { const acceptParams = (params: DialogProps): void => {
dialogData.value = params; dialogData.value = params;
dialogData.value.rowData.varsJson = dialogData.value.rowData!.vars
? JSON.parse(dialogData.value.rowData!.vars)
: {};
title.value = i18n.global.t('commons.button.' + dialogData.value.title); title.value = i18n.global.t('commons.button.' + dialogData.value.title);
if (dialogData.value.title === 'create') { if (dialogData.value.title === 'create') {
dialogData.value.rowData!.type = 'OSS'; dialogData.value.rowData!.type = 'OSS';