diff --git a/cmd/ace/wire_gen.go b/cmd/ace/wire_gen.go
index 8904a963..3ca41f0b 100644
--- a/cmd/ace/wire_gen.go
+++ b/cmd/ace/wire_gen.go
@@ -90,6 +90,8 @@ func initWeb() (*app.Web, error) {
databaseServerService := service.NewDatabaseServerService(databaseServerRepo)
databaseUserService := service.NewDatabaseUserService(databaseUserRepo)
backupService := service.NewBackupService(locale, backupRepo)
+ backupAccountRepo := data.NewBackupAccountRepo(locale, db, logger)
+ backupAccountService := service.NewBackupAccountService(backupAccountRepo)
certService := service.NewCertService(locale, certRepo)
certDNSRepo := data.NewCertDNSRepo(db, logger)
certDNSService := service.NewCertDNSService(certDNSRepo)
@@ -156,7 +158,7 @@ func initWeb() (*app.Web, error) {
s3fsApp := s3fs.NewApp(locale)
supervisorApp := supervisor.NewApp(locale)
loader := bootstrap.NewLoader(apacheApp, codeserverApp, dockerApp, fail2banApp, frpApp, giteaApp, mariadbApp, memcachedApp, minioApp, mysqlApp, nginxApp, openrestyApp, perconaApp, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp)
- http := route.NewHttp(config, userService, userTokenService, homeService, taskService, websiteService, projectService, databaseService, databaseServerService, databaseUserService, backupService, certService, certDNSService, certAccountService, appService, environmentService, environmentGoService, environmentJavaService, environmentNodejsService, environmentPHPService, environmentPythonService, cronService, processService, safeService, firewallService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, logService, monitorService, settingService, systemctlService, toolboxSystemService, toolboxBenchmarkService, toolboxSSHService, toolboxDiskService, toolboxLogService, webHookService, templateService, loader)
+ http := route.NewHttp(config, userService, userTokenService, homeService, taskService, websiteService, projectService, databaseService, databaseServerService, databaseUserService, backupService, backupAccountService, certService, certDNSService, certAccountService, appService, environmentService, environmentGoService, environmentJavaService, environmentNodejsService, environmentPHPService, environmentPythonService, cronService, processService, safeService, firewallService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, logService, monitorService, settingService, systemctlService, toolboxSystemService, toolboxBenchmarkService, toolboxSSHService, toolboxDiskService, toolboxLogService, webHookService, templateService, loader)
wsService := service.NewWsService(locale, config, logger, sshRepo)
ws := route.NewWs(wsService)
mux, err := bootstrap.NewRouter(locale, middlewares, http, ws)
diff --git a/go.mod b/go.mod
index 982e5287..37e78489 100644
--- a/go.mod
+++ b/go.mod
@@ -80,13 +80,13 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/libtnb/securecookie v1.2.0 // indirect
- github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
+ github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/ncruces/julianday v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
- github.com/rogpeppe/go-internal v1.13.1 // indirect
+ github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/stretchr/objx v0.5.3 // indirect
github.com/tetratelabs/wazero v1.11.0 // indirect
github.com/timtadh/data-structures v0.6.2 // indirect
@@ -95,7 +95,7 @@ require (
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
- golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect
+ golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
diff --git a/go.sum b/go.sum
index 9e037282..65823fd8 100644
--- a/go.sum
+++ b/go.sum
@@ -116,7 +116,6 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
@@ -236,8 +235,8 @@ github.com/libtnb/testify v0.0.0-20260103194301-c7a63ea79696 h1:GN0Y3DG27mMruX53
github.com/libtnb/testify v0.0.0-20260103194301-c7a63ea79696/go.mod h1:HeQeTfKU6tj2Lx1z79UacwYeDioo6M4ZD7BDDI6+rrg=
github.com/libtnb/utils v1.2.1 h1:LJmReRREnpqfHyy9PZtNgBh3ZaIGct81b8ZaAsolMkM=
github.com/libtnb/utils v1.2.1/go.mod h1:o6LEDeC42PXI21uLWdWJWTVYvR9BtAZfzzTGJVQoQiU=
-github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
-github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
+github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
+github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@@ -295,8 +294,8 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
-github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
@@ -371,8 +370,8 @@ golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
-golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
+golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
+golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -460,7 +459,6 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
diff --git a/internal/biz/backup_account.go b/internal/biz/backup_account.go
new file mode 100644
index 00000000..6d82b82d
--- /dev/null
+++ b/internal/biz/backup_account.go
@@ -0,0 +1,34 @@
+package biz
+
+import (
+ "context"
+ "time"
+
+ "github.com/acepanel/panel/internal/http/request"
+ "github.com/acepanel/panel/pkg/types"
+)
+
+type BackupAccountType string
+
+const (
+ BackupTypeS3 BackupAccountType = "s3"
+ BackupTypeSFTP BackupAccountType = "sftp"
+ BackupTypeWebDAV BackupAccountType = "webdav"
+)
+
+type BackupAccount struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ Type BackupAccountType `gorm:"not null;default:''" json:"type"`
+ Name string `gorm:"not null;default:''" json:"name"`
+ Info types.BackupAccountInfo `gorm:"not null;default:'{}';serializer:json" json:"info"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+}
+
+type BackupAccountRepo interface {
+ List(page, limit uint) ([]*BackupAccount, int64, error)
+ Get(id uint) (*BackupAccount, error)
+ Create(ctx context.Context, req *request.BackupAccountCreate) (*BackupAccount, error)
+ Update(ctx context.Context, req *request.BackupAccountUpdate) error
+ Delete(ctx context.Context, id uint) error
+}
diff --git a/internal/biz/cert_dns.go b/internal/biz/cert_dns.go
index 781cda7f..404a44f3 100644
--- a/internal/biz/cert_dns.go
+++ b/internal/biz/cert_dns.go
@@ -12,7 +12,7 @@ type CertDNS struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"not null;default:''" json:"name"` // 备注名称
Type acme.DnsType `gorm:"not null;default:'aliyun'" json:"type"` // DNS 提供商
- Data acme.DNSParam `gorm:"not null;serializer:json" json:"dns_param"`
+ Data acme.DNSParam `gorm:"not null;default:'{}';serializer:json" json:"dns_param"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
diff --git a/internal/data/backup_account.go b/internal/data/backup_account.go
new file mode 100644
index 00000000..eab4bff6
--- /dev/null
+++ b/internal/data/backup_account.go
@@ -0,0 +1,84 @@
+package data
+
+import (
+ "context"
+ "log/slog"
+
+ "github.com/leonelquinteros/gotext"
+ "gorm.io/gorm"
+
+ "github.com/acepanel/panel/internal/biz"
+ "github.com/acepanel/panel/internal/http/request"
+)
+
+type backupAccountRepo struct {
+ t *gotext.Locale
+ db *gorm.DB
+ log *slog.Logger
+}
+
+func NewBackupAccountRepo(t *gotext.Locale, db *gorm.DB, log *slog.Logger) biz.BackupAccountRepo {
+ return &backupAccountRepo{
+ t: t,
+ db: db,
+ log: log,
+ }
+}
+
+func (r backupAccountRepo) List(page, limit uint) ([]*biz.BackupAccount, int64, error) {
+ accounts := make([]*biz.BackupAccount, 0)
+ var total int64
+ err := r.db.Model(&biz.BackupAccount{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&accounts).Error
+ return accounts, total, err
+}
+
+func (r backupAccountRepo) Get(id uint) (*biz.BackupAccount, error) {
+ account := new(biz.BackupAccount)
+ err := r.db.Model(&biz.BackupAccount{}).Where("id = ?", id).First(account).Error
+ return account, err
+}
+
+func (r backupAccountRepo) Create(ctx context.Context, req *request.BackupAccountCreate) (*biz.BackupAccount, error) {
+ account := &biz.BackupAccount{
+ Type: biz.BackupAccountType(req.Type),
+ Name: req.Name,
+ Info: req.Info,
+ }
+
+ if err := r.db.Create(account).Error; err != nil {
+ return nil, err
+ }
+
+ r.log.Info("backup account created", slog.String("type", biz.OperationTypeBackup), slog.Uint64("operator_id", getOperatorID(ctx)), slog.Uint64("id", uint64(account.ID)), slog.String("account_type", req.Type), slog.String("name", req.Name))
+
+ return account, nil
+}
+
+func (r backupAccountRepo) Update(ctx context.Context, req *request.BackupAccountUpdate) error {
+ account, err := r.Get(req.ID)
+ if err != nil {
+ return err
+ }
+
+ account.Type = biz.BackupAccountType(req.Type)
+ account.Name = req.Name
+ account.Info = req.Info
+
+ if err = r.db.Save(account).Error; err != nil {
+ return err
+ }
+
+ r.log.Info("backup account updated", slog.String("type", biz.OperationTypeBackup), slog.Uint64("operator_id", getOperatorID(ctx)), slog.Uint64("id", uint64(req.ID)), slog.String("account_type", req.Type))
+
+ return nil
+}
+
+func (r backupAccountRepo) Delete(ctx context.Context, id uint) error {
+ if err := r.db.Model(&biz.BackupAccount{}).Where("id = ?", id).Delete(&biz.BackupAccount{}).Error; err != nil {
+ return err
+ }
+
+ r.log.Info("backup account deleted", slog.String("type", biz.OperationTypeBackup), slog.Uint64("operator_id", getOperatorID(ctx)), slog.Uint64("id", uint64(id)))
+
+ return nil
+}
diff --git a/internal/data/data.go b/internal/data/data.go
index 433b0e1b..d2a80b4b 100644
--- a/internal/data/data.go
+++ b/internal/data/data.go
@@ -6,6 +6,7 @@ import "github.com/google/wire"
var ProviderSet = wire.NewSet(
NewAppRepo,
NewBackupRepo,
+ NewBackupAccountRepo,
NewCacheRepo,
NewCertRepo,
NewCertAccountRepo,
diff --git a/internal/http/request/backup_account.go b/internal/http/request/backup_account.go
new file mode 100644
index 00000000..c4c4f216
--- /dev/null
+++ b/internal/http/request/backup_account.go
@@ -0,0 +1,16 @@
+package request
+
+import "github.com/acepanel/panel/pkg/types"
+
+type BackupAccountCreate struct {
+ Type string `form:"type" json:"type" validate:"required|in: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"`
+ Name string `form:"name" json:"name" validate:"required"`
+ Info types.BackupAccountInfo `form:"info" json:"info"`
+}
diff --git a/internal/route/http.go b/internal/route/http.go
index 5892ed70..1c8c5494 100644
--- a/internal/route/http.go
+++ b/internal/route/http.go
@@ -27,6 +27,7 @@ type Http struct {
databaseServer *service.DatabaseServerService
databaseUser *service.DatabaseUserService
backup *service.BackupService
+ backupAccount *service.BackupAccountService
cert *service.CertService
certDNS *service.CertDNSService
certAccount *service.CertAccountService
@@ -74,6 +75,7 @@ func NewHttp(
databaseServer *service.DatabaseServerService,
databaseUser *service.DatabaseUserService,
backup *service.BackupService,
+ backupAccount *service.BackupAccountService,
cert *service.CertService,
certDNS *service.CertDNSService,
certAccount *service.CertAccountService,
@@ -120,6 +122,7 @@ func NewHttp(
databaseServer: databaseServer,
databaseUser: databaseUser,
backup: backup,
+ backupAccount: backupAccount,
cert: cert,
certDNS: certDNS,
certAccount: certAccount,
@@ -265,6 +268,14 @@ func (route *Http) Register(r *chi.Mux) {
r.Post("/{type}/restore", route.backup.Restore)
})
+ r.Route("/backup_account", func(r chi.Router) {
+ r.Get("/", route.backupAccount.List)
+ r.Post("/", route.backupAccount.Create)
+ r.Put("/{id}", route.backupAccount.Update)
+ r.Get("/{id}", route.backupAccount.Get)
+ r.Delete("/{id}", route.backupAccount.Delete)
+ })
+
r.Route("/cert", func(r chi.Router) {
r.Get("/ca_providers", route.cert.CAProviders)
r.Get("/dns_providers", route.cert.DNSProviders)
diff --git a/internal/service/backup_account.go b/internal/service/backup_account.go
new file mode 100644
index 00000000..2f0ee9ce
--- /dev/null
+++ b/internal/service/backup_account.go
@@ -0,0 +1,101 @@
+package service
+
+import (
+ "net/http"
+
+ "github.com/libtnb/chix"
+
+ "github.com/acepanel/panel/internal/biz"
+ "github.com/acepanel/panel/internal/http/request"
+)
+
+type BackupAccountService struct {
+ backupAccountRepo biz.BackupAccountRepo
+}
+
+func NewBackupAccountService(backupAccount biz.BackupAccountRepo) *BackupAccountService {
+ return &BackupAccountService{
+ backupAccountRepo: backupAccount,
+ }
+}
+
+func (s *BackupAccountService) List(w http.ResponseWriter, r *http.Request) {
+ req, err := Bind[request.Paginate](r)
+ if err != nil {
+ Error(w, http.StatusUnprocessableEntity, "%v", err)
+ return
+ }
+
+ accounts, total, err := s.backupAccountRepo.List(req.Page, req.Limit)
+ if err != nil {
+ Error(w, http.StatusInternalServerError, "%v", err)
+ return
+ }
+
+ Success(w, chix.M{
+ "total": total,
+ "items": accounts,
+ })
+}
+
+func (s *BackupAccountService) Create(w http.ResponseWriter, r *http.Request) {
+ req, err := Bind[request.BackupAccountCreate](r)
+ if err != nil {
+ Error(w, http.StatusUnprocessableEntity, "%v", err)
+ return
+ }
+
+ account, err := s.backupAccountRepo.Create(r.Context(), req)
+ if err != nil {
+ Error(w, http.StatusInternalServerError, "%v", err)
+ return
+ }
+
+ Success(w, account)
+}
+
+func (s *BackupAccountService) Update(w http.ResponseWriter, r *http.Request) {
+ req, err := Bind[request.BackupAccountUpdate](r)
+ if err != nil {
+ Error(w, http.StatusUnprocessableEntity, "%v", err)
+ return
+ }
+
+ if err = s.backupAccountRepo.Update(r.Context(), req); err != nil {
+ Error(w, http.StatusInternalServerError, "%v", err)
+ return
+ }
+
+ Success(w, nil)
+}
+
+func (s *BackupAccountService) Get(w http.ResponseWriter, r *http.Request) {
+ req, err := Bind[request.ID](r)
+ if err != nil {
+ Error(w, http.StatusUnprocessableEntity, "%v", err)
+ return
+ }
+
+ account, err := s.backupAccountRepo.Get(req.ID)
+ if err != nil {
+ Error(w, http.StatusInternalServerError, "%v", err)
+ return
+ }
+
+ Success(w, account)
+}
+
+func (s *BackupAccountService) Delete(w http.ResponseWriter, r *http.Request) {
+ req, err := Bind[request.ID](r)
+ if err != nil {
+ Error(w, http.StatusUnprocessableEntity, "%v", err)
+ return
+ }
+
+ if err = s.backupAccountRepo.Delete(r.Context(), req.ID); err != nil {
+ Error(w, http.StatusInternalServerError, "%v", err)
+ return
+ }
+
+ Success(w, nil)
+}
diff --git a/internal/service/service.go b/internal/service/service.go
index 48ecb04d..eb7ad5d7 100644
--- a/internal/service/service.go
+++ b/internal/service/service.go
@@ -6,6 +6,7 @@ import "github.com/google/wire"
var ProviderSet = wire.NewSet(
NewAppService,
NewBackupService,
+ NewBackupAccountService,
NewCertService,
NewCertAccountService,
NewCertDNSService,
diff --git a/pkg/types/backup.go b/pkg/types/backup.go
index 0907ab1d..bb36eaab 100644
--- a/pkg/types/backup.go
+++ b/pkg/types/backup.go
@@ -2,6 +2,24 @@ package types
import "time"
+type BackupAccountInfo struct {
+ // S3
+ AccessKey string `json:"access_key"` // 访问密钥
+ SecretKey string `json:"secret_key"` // 私钥
+ Style string `json:"style"` // virtual_hosted, path
+ Region string `json:"region"` // 地区
+ Endpoint string `json:"endpoint"` // 端点
+ Bucket string `json:"bucket"` // 存储桶
+
+ // SFTP / WebDAV
+ Host string `json:"host"` // 主机
+ Port int `json:"port"` // 端口
+ User string `json:"user"` // 用户名
+ Password string `json:"password"` // 密码
+
+ Path string `json:"path"` // 路径
+}
+
type BackupFile struct {
Name string `json:"name"`
Path string `json:"path"`
diff --git a/web/src/api/panel/backupAccount/index.ts b/web/src/api/panel/backupAccount/index.ts
new file mode 100644
index 00000000..b93289f1
--- /dev/null
+++ b/web/src/api/panel/backupAccount/index.ts
@@ -0,0 +1,15 @@
+import { http } from '@/utils'
+
+export default {
+ // 获取备份账号列表
+ list: (page: number, limit: number): any =>
+ http.Get('/backup_account', { params: { page, limit } }),
+ // 获取备份账号
+ get: (id: number): any => http.Get(`/backup_account/${id}`),
+ // 创建备份账号
+ create: (data: any): any => http.Post('/backup_account', data),
+ // 更新备份账号
+ update: (id: number, data: any): any => http.Put(`/backup_account/${id}`, data),
+ // 删除备份账号
+ delete: (id: number): any => http.Delete(`/backup_account/${id}`)
+}
diff --git a/web/src/views/backup/AccountView.vue b/web/src/views/backup/AccountView.vue
new file mode 100644
index 00000000..2ebb34f3
--- /dev/null
+++ b/web/src/views/backup/AccountView.vue
@@ -0,0 +1,474 @@
+
+
+
+
+
+ {{
+ $gettext('Add Account')
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $gettext('Submit') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $gettext('Submit') }}
+
+
+
+
diff --git a/web/src/views/backup/IndexView.vue b/web/src/views/backup/IndexView.vue
index 1ae3779e..a761ca5b 100644
--- a/web/src/views/backup/IndexView.vue
+++ b/web/src/views/backup/IndexView.vue
@@ -5,6 +5,7 @@ defineOptions({
import home from '@/api/panel/home'
import ListView from '@/views/backup/ListView.vue'
+import AccountView from '@/views/backup/AccountView.vue'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
@@ -37,8 +38,10 @@ const postgreSQLInstalled = computed(() => {
+
-
+
+