1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-02-12 19: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) {
id, err := helper.GetIntParamByKey(c, "id")
name, err := helper.GetStrParamByKey(c, "name")
if err != nil {
helper.BadRequest(c, err)
return
}
if err := backupService.CheckUsed(id); err != nil {
if err := backupService.CheckUsed(name); err != nil {
helper.BadRequest(c, err)
return
}

View File

@ -17,6 +17,7 @@ type IBackupRepo interface {
Create(backup *model.BackupAccount) error
Save(backup *model.BackupAccount) error
Delete(opts ...DBOption) error
WithByPublic(isPublic bool) DBOption
ListRecord(opts ...DBOption) ([]model.BackupRecord, error)
PageRecord(page, size int, opts ...DBOption) (int64, []model.BackupRecord, error)
@ -35,6 +36,12 @@ func NewIBackupRepo() IBackupRepo {
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) {
var backup model.BackupAccount
db := global.DB

View File

@ -28,7 +28,7 @@ import (
type BackupService struct{}
type IBackupService interface {
CheckUsed(id uint) error
CheckUsed(name string) error
Sync(req dto.SyncFromMaster) error
LoadBackupOptions() ([]dto.BackupOption, error)
@ -103,8 +103,8 @@ func (u *BackupService) SearchWithPage(req dto.SearchPageWithType) (int64, inter
item.Vars = string(itemVars)
}
} else {
item.AccessKey = base64.StdEncoding.EncodeToString([]byte(item.AccessKey))
item.Credential = base64.StdEncoding.EncodeToString([]byte(item.Credential))
item.AccessKey, _ = encrypt.StringDecryptWithBase64(item.AccessKey)
item.Credential, _ = encrypt.StringDecryptWithBase64(item.Credential)
}
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
}
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()
for _, job := range cronjobs {
if job.DownloadAccountID == id {
if job.DownloadAccountID == account.ID {
return buserr.New("ErrBackupInUsed")
}
ids := strings.Split(job.SourceAccountIDs, ",")
for _, idItem := range ids {
if idItem == fmt.Sprintf("%v", id) {
if idItem == fmt.Sprintf("%v", account.ID) {
return buserr.New("ErrBackupInUsed")
}
}

View File

@ -1,6 +1,7 @@
package hook
import (
"os"
"path"
"github.com/1Panel-dev/1Panel/agent/app/dto"
@ -102,4 +103,10 @@ func loadLocalDir() {
return
}
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")
baseApi := v2.ApiGroupApp.BaseApi
{
backupRouter.GET("/check/:id", baseApi.CheckBackupUsed)
backupRouter.GET("/check/:name", baseApi.CheckBackupUsed)
backupRouter.POST("/sync", baseApi.SyncBackupAccount)
backupRouter.GET("/options", baseApi.LoadBackupOptions)
backupRouter.POST("/search", baseApi.SearchBackup)

View File

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

View File

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

View File

@ -33,8 +33,8 @@ type IBackupService interface {
Create(backupDto dto.BackupOperate) error
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
Update(req dto.BackupOperate) error
Delete(id uint) error
RefreshToken(req dto.OperateByID) error
Delete(name string) error
RefreshToken(req dto.OperateByName) error
}
func NewIBackupService() IBackupService {
@ -156,8 +156,8 @@ func (u *BackupService) GetBuckets(req dto.ForBuckets) ([]interface{}, error) {
return client.ListBuckets()
}
func (u *BackupService) Delete(id uint) error {
backup, _ := backupRepo.Get(repo.WithByID(id))
func (u *BackupService) Delete(name string) error {
backup, _ := backupRepo.Get(repo.WithByName(name))
if backup.ID == 0 {
return buserr.New("ErrRecordNotFound")
}
@ -167,21 +167,21 @@ func (u *BackupService) Delete(id uint) error {
if backup.Type == constant.Local {
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)
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)
return buserr.New("ErrBackupInUsed")
}
go syncAccountToAgent(backup, "delete")
return backupRepo.Delete(repo.WithByID(id))
return backupRepo.Delete(repo.WithByName(name))
}
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 {
return buserr.New("ErrRecordNotFound")
}
@ -198,6 +198,7 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
if err := copier.Copy(&newBackup, &req); err != nil {
return buserr.WithDetail("ErrStructTransform", err.Error(), nil)
}
newBackup.ID = backup.ID
itemAccessKey, err := base64.StdEncoding.DecodeString(newBackup.AccessKey)
if err != nil {
return err
@ -235,8 +236,8 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
return nil
}
func (u *BackupService) RefreshToken(req dto.OperateByID) error {
backup, _ := backupRepo.Get(repo.WithByID(req.ID))
func (u *BackupService) RefreshToken(req dto.OperateByName) error {
backup, _ := backupRepo.Get(repo.WithByName(req.Name))
if backup.ID == 0 {
return buserr.New("ErrRecordNotFound")
}

View File

@ -8,7 +8,7 @@ import (
func loadParamFromVars(key string, vars map[string]interface{}) string {
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)
}
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 CheckBackupUsed(id uint) error { return nil }
func CheckBackupUsed(name string) 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
export const refreshToken = (params: { id: number; isPublic: boolean }) => {
let urlItem = '/core/backups/refresh/token';
if (!params.isPublic || !globalStore.isProductPro) {
urlItem = '/backups/refresh/token';
export const refreshToken = (params: { id: number; name: string; isPublic: boolean }) => {
if (!params.isPublic) {
return http.post('/backups/refresh/token', { id: params.id });
}
return http.post(urlItem, { id: params.id });
return http.post('/core/backups/refresh/token', { name: params.name });
};
export const getClientInfo = (clientType: string) => {
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);
}
let urlItem = '/core/backups';
if (!params.isPublic || !globalStore.isProductPro) {
if (!params.isPublic) {
urlItem = '/backups';
}
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);
}
let urlItem = '/core/backups/update';
if (!params.isPublic || !globalStore.isProductPro) {
if (!params.isPublic) {
urlItem = '/backups/update';
}
return http.post(urlItem, request);
};
export const deleteBackup = (params: { id: number; isPublic: boolean }) => {
let urlItem = '/core/backups/del';
if (!params.isPublic || !globalStore.isProductPro) {
urlItem = '/backups/del';
export const deleteBackup = (params: { id: number; name: string; isPublic: boolean }) => {
if (!params.isPublic) {
return http.post('/backups/del', { id: params.id });
}
return http.post(urlItem, { id: params.id });
return http.post('/core/backups/del', { name: params.name });
};
export const listBucket = (params: Backup.ForBucket) => {
let request = deepCopy(params) as Backup.BackupOperate;

View File

@ -167,13 +167,13 @@ const loadEndpoint = (row: any) => {
const onDelete = async (row: Backup.BackupInfo) => {
opRef.value.acceptParams({
title: i18n.global.t('commons.button.delete'),
names: [row.type],
names: ['[ ' + row.type + ' ] ' + row.name],
msg: i18n.global.t('commons.msg.operatorHelper', [
i18n.global.t('setting.backupAccount'),
i18n.global.t('commons.button.delete'),
]),
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) => {
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'));
search();
};

View File

@ -11,13 +11,16 @@
prop="isPublic"
: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="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>
<span class="input-help">
{{ dialogData.rowData!.isPublic ? $t('setting.publicHelper') : $t('setting.privateHelper') }}
</span>
</el-form-item>
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
<el-tag v-if="dialogData.title === 'edit'">{{ $t('setting.' + dialogData.rowData!.type) }}</el-tag>
@ -440,6 +443,9 @@ const dialogData = ref<DialogProps>({
});
const acceptParams = (params: DialogProps): void => {
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);
if (dialogData.value.title === 'create') {
dialogData.value.rowData!.type = 'OSS';