From 046105a542aee12f552bb1b0a7331a0aab7c7ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Mon, 19 Jan 2026 23:41:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BB=8E=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E8=AF=BB=E5=8F=96=E5=A4=87=E4=BB=BD=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/backup.go | 19 +++++++-- internal/biz/backup_account.go | 9 ++-- internal/data/backup.go | 55 +++++++++++-------------- internal/http/request/backup_account.go | 4 +- web/src/views/backup/AccountView.vue | 24 ++++++++++- 5 files changed, 70 insertions(+), 41 deletions(-) diff --git a/internal/biz/backup.go b/internal/biz/backup.go index 77b85057..d40d869f 100644 --- a/internal/biz/backup.go +++ b/internal/biz/backup.go @@ -2,8 +2,7 @@ package biz import ( "context" - - "github.com/acepanel/panel/pkg/types" + "time" ) type BackupType string @@ -17,8 +16,22 @@ const ( BackupTypePanel BackupType = "panel" ) +type Backup struct { + ID uint `gorm:"primaryKey" json:"id"` // 备份 ID + AccountID uint `gorm:"not null;default:0" json:"account_id"` // 关联的备份账号 ID + Type BackupType `gorm:"not null;default:''" json:"type"` // 备份类型 + Name string `gorm:"not null;default:''" json:"name"` // 备份文件名 + Size int64 `gorm:"not null;default:0" json:"size"` // 备份文件大小 + Status bool `gorm:"not null;default:false" json:"status"` // 备份状态 + Log string `gorm:"not null;default:''" json:"log"` // 备份日志 + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + + Account *BackupAccount `gorm:"foreignKey:AccountID" json:"account"` +} + type BackupRepo interface { - List(typ BackupType) ([]*types.BackupFile, error) + List(page, limit uint, typ BackupType) ([]*Backup, int64, error) Create(ctx context.Context, typ BackupType, target string, path ...string) error Delete(ctx context.Context, typ BackupType, name string) error Restore(ctx context.Context, typ BackupType, backup, target string) error diff --git a/internal/biz/backup_account.go b/internal/biz/backup_account.go index 6d82b82d..02359db2 100644 --- a/internal/biz/backup_account.go +++ b/internal/biz/backup_account.go @@ -11,9 +11,10 @@ import ( type BackupAccountType string const ( - BackupTypeS3 BackupAccountType = "s3" - BackupTypeSFTP BackupAccountType = "sftp" - BackupTypeWebDAV BackupAccountType = "webdav" + BackupAccountTypeLocal BackupAccountType = "local" + BackupAccountTypeS3 BackupAccountType = "s3" + BackupAccountTypeSFTP BackupAccountType = "sftp" + BackupAccountTypeWebDAV BackupAccountType = "webdav" ) type BackupAccount struct { @@ -23,6 +24,8 @@ type BackupAccount struct { Info types.BackupAccountInfo `gorm:"not null;default:'{}';serializer:json" json:"info"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` + + Backups []*Backup `gorm:"foreignKey:AccountID" json:"-"` } type BackupAccountRepo interface { diff --git a/internal/data/backup.go b/internal/data/backup.go index f64fc82a..ad197473 100644 --- a/internal/data/backup.go +++ b/internal/data/backup.go @@ -22,7 +22,6 @@ import ( "github.com/acepanel/panel/pkg/io" "github.com/acepanel/panel/pkg/shell" "github.com/acepanel/panel/pkg/tools" - "github.com/acepanel/panel/pkg/types" ) type backupRepo struct { @@ -46,32 +45,11 @@ func NewBackupRepo(t *gotext.Locale, conf *config.Config, db *gorm.DB, log *slog } // List 备份列表 -func (r *backupRepo) List(typ biz.BackupType) ([]*types.BackupFile, error) { - path, err := r.GetPath(typ) - if err != nil { - return nil, err - } - - files, err := os.ReadDir(path) - if err != nil { - return nil, err - } - - list := make([]*types.BackupFile, 0) - for _, file := range files { - info, err := file.Info() - if err != nil { - continue - } - list = append(list, &types.BackupFile{ - Name: file.Name(), - Path: filepath.Join(path, file.Name()), - Size: tools.FormatBytes(float64(info.Size())), - Time: info.ModTime(), - }) - } - - return list, nil +func (r *backupRepo) List(page, limit uint, typ biz.BackupType) ([]*biz.Backup, int64, error) { + backups := make([]*biz.Backup, 0) + var total int64 + err := r.db.Model(&biz.Backup{}).Where("type = ?", typ).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Preload("Account").Find(&backups).Error + return backups, total, err } // Create 创建备份 @@ -632,19 +610,32 @@ func (r *backupRepo) FixPanel() error { } // 从备份目录中找最新的备份文件 - list, err := r.List(biz.BackupTypePanel) + backupPath, err := r.GetPath(biz.BackupTypePanel) if err != nil { return err } - slices.SortFunc(list, func(a *types.BackupFile, b *types.BackupFile) int { - return int(b.Time.Unix() - a.Time.Unix()) + files, err := os.ReadDir(backupPath) + if err != nil { + return err + } + var list []os.FileInfo + for _, file := range files { + info, infoErr := file.Info() + if infoErr != nil { + continue + } + list = append(list, info) + } + slices.SortFunc(list, func(a os.FileInfo, b os.FileInfo) int { + return int(b.ModTime().Unix() - a.ModTime().Unix()) }) if len(list) == 0 { return errors.New(r.t.Get("No backup file found, unable to automatically repair")) } latest := list[0] + latestPath := filepath.Join(backupPath, latest.Name()) if app.IsCli { - fmt.Println(r.t.Get("|-Backup file used: %s", latest.Name)) + fmt.Println(r.t.Get("|-Backup file used: %s", latest.Name())) } // 解压备份文件 @@ -654,7 +645,7 @@ func (r *backupRepo) FixPanel() error { if err = io.Remove("/tmp/panel-fix"); err != nil { return errors.New(r.t.Get("Cleaning temporary directory failed: %v", err)) } - if err = io.UnCompress(latest.Path, "/tmp/panel-fix"); err != nil { + if err = io.UnCompress(latestPath, "/tmp/panel-fix"); err != nil { return errors.New(r.t.Get("Unzip backup file failed: %v", err)) } diff --git a/internal/http/request/backup_account.go b/internal/http/request/backup_account.go index c4c4f216..8cbd0c5f 100644 --- a/internal/http/request/backup_account.go +++ b/internal/http/request/backup_account.go @@ -3,14 +3,14 @@ package request import "github.com/acepanel/panel/pkg/types" type BackupAccountCreate struct { - Type string `form:"type" json:"type" validate:"required|in:s3,sftp,webdav"` + Type string `form:"type" json:"type" validate:"required|in:local,s3,sftp,webdav"` Name string `form:"name" json:"name" validate:"required"` Info types.BackupAccountInfo `form:"info" json:"info"` } type BackupAccountUpdate struct { ID uint `form:"id" json:"id" validate:"required|exists:backup_accounts,id"` - Type string `form:"type" json:"type" validate:"required|in:s3,sftp,webdav"` + Type string `form:"type" json:"type" validate:"required|in:local,s3,sftp,webdav"` Name string `form:"name" json:"name" validate:"required"` Info types.BackupAccountInfo `form:"info" json:"info"` } diff --git a/web/src/views/backup/AccountView.vue b/web/src/views/backup/AccountView.vue index 2ebb34f3..8196b9aa 100644 --- a/web/src/views/backup/AccountView.vue +++ b/web/src/views/backup/AccountView.vue @@ -11,6 +11,7 @@ const editModal = ref(false) const editId = ref(0) const typeOptions = [ + { label: $gettext('Local'), value: 'local' }, { label: 'S3', value: 's3' }, { label: 'SFTP', value: 'sftp' }, { label: 'WebDAV', value: 'webdav' } @@ -22,7 +23,7 @@ const styleOptions = [ ] const defaultModel = { - type: 's3', + type: 'local', name: '', info: { access_key: '', @@ -56,6 +57,7 @@ const columns: any = [ width: 120, render(row: any) { const typeMap: Record = { + local: $gettext('Local'), s3: 'S3', sftp: 'SFTP', webdav: 'WebDAV' @@ -216,6 +218,16 @@ onMounted(() => { + + +