From e3a15e88d7baf84b03a9f7baf7177b62c70bb8a1 Mon Sep 17 00:00:00 2001 From: ssongliu Date: Fri, 6 Jan 2023 18:53:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E5=BF=AB=E7=85=A7=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 0 backend/app/api/v1/entry.go | 3 +- backend/app/api/v1/snapshot.go | 60 +++++++ backend/app/dto/setting.go | 16 ++ backend/app/model/snapshot.go | 11 ++ backend/app/repo/common.go | 6 +- backend/app/repo/entry.go | 1 + backend/app/repo/snapshot.go | 58 ++++++ backend/app/service/container.go | 2 +- backend/app/service/database_mysql.go | 2 +- backend/app/service/entry.go | 4 +- backend/app/service/snapshot.go | 150 ++++++++++++++++ backend/app/service/snapshot_test.go | 34 ++++ backend/init/migration/migrate.go | 1 + backend/init/migration/migrations/init.go | 10 ++ backend/init/viper/viper.go | 9 + backend/router/ro_setting.go | 2 + frontend/src/api/interface/setting.ts | 15 ++ frontend/src/api/modules/setting.ts | 9 + frontend/src/lang/modules/en.ts | 3 + frontend/src/lang/modules/zh.ts | 3 + frontend/src/routers/modules/setting.ts | 15 ++ frontend/src/views/setting/index.vue | 5 + frontend/src/views/setting/snapshot/index.vue | 167 ++++++++++++++++++ 24 files changed, 580 insertions(+), 6 deletions(-) delete mode 100644 Dockerfile create mode 100644 backend/app/api/v1/snapshot.go create mode 100644 backend/app/model/snapshot.go create mode 100644 backend/app/repo/snapshot.go create mode 100644 backend/app/service/snapshot.go create mode 100644 backend/app/service/snapshot_test.go create mode 100644 frontend/src/views/setting/snapshot/index.vue diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/app/api/v1/entry.go b/backend/app/api/v1/entry.go index 9e268db1e..a15f195b3 100644 --- a/backend/app/api/v1/entry.go +++ b/backend/app/api/v1/entry.go @@ -43,5 +43,6 @@ var ( nginxService = service.ServiceGroupApp.NginxService - logService = service.ServiceGroupApp.LogService + logService = service.ServiceGroupApp.LogService + snapshotService = service.ServiceGroupApp.SnapshotService ) diff --git a/backend/app/api/v1/snapshot.go b/backend/app/api/v1/snapshot.go new file mode 100644 index 000000000..8dca968b2 --- /dev/null +++ b/backend/app/api/v1/snapshot.go @@ -0,0 +1,60 @@ +package v1 + +import ( + "github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/1Panel-dev/1Panel/backend/global" + "github.com/gin-gonic/gin" +) + +// @Tags System Setting +// @Summary Create snapshot +// @Description 创建系统快照 +// @Accept json +// @Param request body dto.SnapshotCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /settings/snapshot [post] +// @x-panel-log {"bodyKeys":["name", "description"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建系统快照 [name][description]","formatEN":"Create system snapshot [name][description]"} +func (b *BaseApi) CreateSnapshot(c *gin.Context) { + var req dto.SnapshotCreate + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := global.VALID.Struct(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := snapshotService.Create(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} + +// @Tags System Setting +// @Summary Page system snapshot +// @Description 获取系统快照列表分页 +// @Accept json +// @Param request body dto.PageInfo true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Router /websites/acme/search [post] +func (b *BaseApi) SearchSnapshot(c *gin.Context) { + var req dto.PageInfo + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + total, accounts, err := snapshotService.SearchWithPage(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Total: total, + Items: accounts, + }) +} diff --git a/backend/app/dto/setting.go b/backend/app/dto/setting.go index 258236348..ea2a20205 100644 --- a/backend/app/dto/setting.go +++ b/backend/app/dto/setting.go @@ -1,5 +1,7 @@ package dto +import "time" + type SettingInfo struct { UserName string `json:"userName"` Email string `json:"email"` @@ -37,3 +39,17 @@ type PasswordUpdate struct { OldPassword string `json:"oldPassword" validate:"required"` NewPassword string `json:"newPassword" validate:"required"` } + +type SnapshotCreate struct { + BackupType string `json:"backupType" validate:"required,oneof=OSS S3 SFTP MINIO"` + Description string `json:"description"` +} +type SnapshotInfo struct { + ID uint `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + BackupType string `json:"backupType"` + Status string `json:"status"` + Message string `json:"message"` + CreatedAt time.Time `json:"createdAt"` +} diff --git a/backend/app/model/snapshot.go b/backend/app/model/snapshot.go new file mode 100644 index 000000000..93476e9e9 --- /dev/null +++ b/backend/app/model/snapshot.go @@ -0,0 +1,11 @@ +package model + +type Snapshot struct { + BaseModel + Name string `json:"name" gorm:"type:varchar(64);not null;unique"` + Description string `json:"description" gorm:"type:varchar(256)"` + BackupType string `json:"backupType" gorm:"type:varchar(64)"` + Status string `json:"status" gorm:"type:varchar(64)"` + Message string `json:"message" gorm:"type:varchar(256)"` + Version string `json:"version" gorm:"type:varchar(256)"` +} diff --git a/backend/app/repo/common.go b/backend/app/repo/common.go index aa3272d88..ecf882092 100644 --- a/backend/app/repo/common.go +++ b/backend/app/repo/common.go @@ -2,6 +2,7 @@ package repo import ( "context" + "github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/global" "gorm.io/gorm" @@ -12,6 +13,7 @@ type DBOption func(*gorm.DB) *gorm.DB type ICommonRepo interface { WithByID(id uint) DBOption WithByName(name string) DBOption + WithByType(tp string) DBOption WithOrderBy(orderStr string) DBOption WithLikeName(name string) DBOption WithIdsIn(ids []uint) DBOption @@ -31,9 +33,9 @@ func (c *CommonRepo) WithByName(name string) DBOption { } } -func (c *CommonRepo) WithByType(name string) DBOption { +func (c *CommonRepo) WithByType(tp string) DBOption { return func(g *gorm.DB) *gorm.DB { - return g.Where("type = ?", name) + return g.Where("type = ?", tp) } } diff --git a/backend/app/repo/entry.go b/backend/app/repo/entry.go index 88d82fc1d..e120dc052 100644 --- a/backend/app/repo/entry.go +++ b/backend/app/repo/entry.go @@ -25,6 +25,7 @@ type RepoGroup struct { WebsiteSSLRepo WebsiteAcmeAccountRepo LogRepo + SnapshotRepo } var RepoGroupApp = new(RepoGroup) diff --git a/backend/app/repo/snapshot.go b/backend/app/repo/snapshot.go new file mode 100644 index 000000000..286e78ff2 --- /dev/null +++ b/backend/app/repo/snapshot.go @@ -0,0 +1,58 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/backend/app/model" + "github.com/1Panel-dev/1Panel/backend/global" +) + +type ISnapshotRepo interface { + Get(opts ...DBOption) (model.Snapshot, error) + Page(limit, offset int, opts ...DBOption) (int64, []model.Snapshot, error) + Create(snapshot *model.Snapshot) error + Update(id uint, vars map[string]interface{}) error + Delete(opts ...DBOption) error +} + +func NewISnapshotRepo() ISnapshotRepo { + return &SnapshotRepo{} +} + +type SnapshotRepo struct{} + +func (u *SnapshotRepo) Get(opts ...DBOption) (model.Snapshot, error) { + var snapshot model.Snapshot + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&snapshot).Error + return snapshot, err +} + +func (u *SnapshotRepo) Page(page, size int, opts ...DBOption) (int64, []model.Snapshot, error) { + var users []model.Snapshot + db := global.DB.Model(&model.Snapshot{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error + return count, users, err +} + +func (u *SnapshotRepo) Create(snapshot *model.Snapshot) error { + return global.DB.Create(snapshot).Error +} + +func (u *SnapshotRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.Snapshot{}).Where("id = ?", id).Updates(vars).Error +} + +func (u *SnapshotRepo) Delete(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.Snapshot{}).Error +} diff --git a/backend/app/service/container.go b/backend/app/service/container.go index d5ac9ddac..a1b812315 100644 --- a/backend/app/service/container.go +++ b/backend/app/service/container.go @@ -167,7 +167,7 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error { _ = client.ContainerRemove(context.Background(), req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true}) return err } - global.LOG.Infof("create container successful! now check if the container is started and delete the container information if it is not.", req.Name) + global.LOG.Infof("create container %s successful! now check if the container is started and delete the container information if it is not.", req.Name) if err := client.ContainerStart(context.TODO(), container.ID, types.ContainerStartOptions{}); err != nil { _ = client.ContainerRemove(context.Background(), req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true}) return fmt.Errorf("create successful but start failed, err: %v", err) diff --git a/backend/app/service/database_mysql.go b/backend/app/service/database_mysql.go index 6f59782dc..75b6d53d9 100644 --- a/backend/app/service/database_mysql.go +++ b/backend/app/service/database_mysql.go @@ -641,7 +641,7 @@ func backupMysql(backupType, baseDir, backupDir, mysqlName, dbName, fileName str } } outfile, _ := os.OpenFile(fullDir+"/"+fileName, os.O_RDWR|os.O_CREATE, 0755) - global.LOG.Infof("start to mysqldump | gzip > %s.gzip", outfile) + global.LOG.Infof("start to mysqldump | gzip > %s.gzip", fullDir+"/"+fileName) cmd := exec.Command("docker", "exec", app.ContainerName, "mysqldump", "-uroot", "-p"+app.Password, dbName) gzipCmd := exec.Command("gzip", "-cf") gzipCmd.Stdin, _ = cmd.StdoutPipe() diff --git a/backend/app/service/entry.go b/backend/app/service/entry.go index 9b4b16928..d6624e397 100644 --- a/backend/app/service/entry.go +++ b/backend/app/service/entry.go @@ -37,6 +37,7 @@ type ServiceGroup struct { NginxService LogService + SnapshotService } var ServiceGroupApp = new(ServiceGroup) @@ -73,5 +74,6 @@ var ( websiteSSLRepo = repo.NewISSLRepo() websiteAcmeRepo = repo.NewIAcmeAccountRepo() - logRepo = repo.RepoGroupApp.LogRepo + logRepo = repo.RepoGroupApp.LogRepo + snapshotRepo = repo.NewISnapshotRepo() ) diff --git a/backend/app/service/snapshot.go b/backend/app/service/snapshot.go new file mode 100644 index 000000000..1403014fb --- /dev/null +++ b/backend/app/service/snapshot.go @@ -0,0 +1,150 @@ +package service + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/1Panel-dev/1Panel/backend/app/dto" + "github.com/1Panel-dev/1Panel/backend/app/model" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/1Panel-dev/1Panel/backend/global" + "github.com/1Panel-dev/1Panel/backend/utils/cmd" + "github.com/1Panel-dev/1Panel/backend/utils/docker" + "github.com/1Panel-dev/1Panel/backend/utils/files" + "github.com/jinzhu/copier" + "github.com/pkg/errors" +) + +type SnapshotService struct{} + +type ISnapshotService interface { + SearchWithPage(req dto.PageInfo) (int64, interface{}, error) + Create(req dto.SnapshotCreate) error +} + +func NewISnapshotService() ISnapshotService { + return &SnapshotService{} +} + +func (u *SnapshotService) SearchWithPage(req dto.PageInfo) (int64, interface{}, error) { + total, snapshots, err := snapshotRepo.Page(req.Page, req.PageSize) + var dtoSnap []dto.SnapshotInfo + for _, snapshot := range snapshots { + var item dto.SnapshotInfo + if err := copier.Copy(&item, &snapshot); err != nil { + return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) + } + dtoSnap = append(dtoSnap, item) + } + return total, dtoSnap, err +} + +func (u *SnapshotService) Create(req dto.SnapshotCreate) error { + localDir, err := loadLocalDir() + if err != nil { + return err + } + backup, err := backupRepo.Get(commonRepo.WithByType(req.BackupType)) + if err != nil { + return err + } + backupAccont, err := NewIBackupService().NewClient(&backup) + if err != nil { + return err + } + + timeNow := time.Now().Format("20060102150405") + rootDir := fmt.Sprintf("/tmp/songliu/1panel_backup_%s", timeNow) + backupPanelDir := fmt.Sprintf("%s/1panel", rootDir) + _ = os.MkdirAll(backupPanelDir, os.ModePerm) + backupDockerDir := fmt.Sprintf("%s/docker", rootDir) + _ = os.MkdirAll(backupDockerDir, os.ModePerm) + + defer func() { + _, _ = cmd.Exec("systemctl start docker") + _ = os.RemoveAll(rootDir) + }() + + fileOp := files.NewFileOp() + if err := fileOp.Compress([]string{localDir}, backupPanelDir, "1panel_backup.tar.gz", files.TarGz); err != nil { + global.LOG.Errorf("snapshot backup 1panel backup datas %s failed, err: %v", localDir, err) + return err + } + client, err := docker.NewDockerClient() + if err != nil { + return err + } + ctx := context.Background() + info, err := client.Info(ctx) + if err != nil { + return err + } + dataDir := info.DockerRootDir + stdout, err := cmd.Exec("systemctl stop docker") + if err != nil { + return errors.New(stdout) + } + + if _, err := os.Stat("/etc/systemd/system/1panel.service"); err == nil { + if err := fileOp.Compress([]string{dataDir}, backupDockerDir, "docker_data.tar.gz", files.TarGz); err != nil { + global.LOG.Errorf("snapshot backup docker data dir %s failed, err: %v", dataDir, err) + return err + } + } + if _, err := os.Stat(constant.DaemonJsonPath); err == nil { + if err := fileOp.CopyFile(constant.DaemonJsonPath, backupDockerDir); err != nil { + global.LOG.Errorf("snapshot backup daemon.json failed, err: %v", err) + return err + } + } + + if _, err := os.Stat("/Users/slooop/go/bin/swag"); err == nil { + if err := fileOp.CopyFile("/Users/slooop/go/bin/swag", backupPanelDir); err != nil { + global.LOG.Errorf("snapshot backup 1panel failed, err: %v", err) + return err + } + } + if _, err := os.Stat("/etc/systemd/system/1panel.service"); err == nil { + if err := fileOp.CopyFile("/etc/systemd/system/1panel.service", backupPanelDir); err != nil { + global.LOG.Errorf("snapshot backup 1panel.service failed, err: %v", err) + return err + } + } + if _, err := os.Stat("/usr/local/bin/1panelctl"); err == nil { + if err := fileOp.CopyFile("/usr/local/bin/1panelctl", backupPanelDir); err != nil { + global.LOG.Errorf("snapshot backup 1panelctl failed, err: %v", err) + return err + } + } + if _, err := os.Stat(global.CONF.System.DataDir); err == nil { + if err := fileOp.Compress([]string{global.CONF.System.DataDir}, backupPanelDir, "1panel_data.tar.gz", files.TarGz); err != nil { + global.LOG.Errorf("snapshot backup 1panel data %s failed, err: %v", global.CONF.System.DataDir, err) + return err + } + } + if err := fileOp.Compress([]string{rootDir}, fmt.Sprintf("%s/system", localDir), fmt.Sprintf("1panel_backup_%s.tar.gz", timeNow), files.TarGz); err != nil { + return err + } + + snap := model.Snapshot{ + Name: "1panel_backup_" + timeNow, + Description: req.Description, + BackupType: req.BackupType, + Status: constant.StatusWaiting, + } + _ = snapshotRepo.Create(&snap) + go func() { + localPath := fmt.Sprintf("%s/system/1panel_backup_%s.tar.gz", localDir, timeNow) + if ok, err := backupAccont.Upload(localPath, fmt.Sprintf("system_snapshot/1panel_backup_%s.tar.gz", timeNow)); err != nil || !ok { + _ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()}) + global.LOG.Errorf("upload snapshot to %s failed, err: %v", backup.Type, err) + return + } + snap.Status = constant.StatusSuccess + _ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusSuccess}) + global.LOG.Infof("upload snapshot to %s success", backup.Type) + }() + return nil +} diff --git a/backend/app/service/snapshot_test.go b/backend/app/service/snapshot_test.go new file mode 100644 index 000000000..91549ba6b --- /dev/null +++ b/backend/app/service/snapshot_test.go @@ -0,0 +1,34 @@ +package service + +import ( + "fmt" + "testing" + + "github.com/1Panel-dev/1Panel/backend/app/model" + "github.com/1Panel-dev/1Panel/backend/global" + "github.com/1Panel-dev/1Panel/backend/init/db" + "github.com/1Panel-dev/1Panel/backend/init/viper" + "github.com/1Panel-dev/1Panel/backend/utils/files" +) + +func TestSnaa(t *testing.T) { + fileOp := files.NewFileOp() + + fmt.Println(fileOp.CopyFile("/Users/slooop/Documents/编码规范.pdf", "/Users/slooop/Downloads")) + // fmt.Println(fileOp.Compress([]string{"/Users/slooop/Documents/编码规范.pdf", "/Users/slooop/Downloads/1Panel.db"}, "/Users/slooop/Downloads/", "test.tar.gz", files.TarGz)) +} + +func TestOss(t *testing.T) { + viper.Init() + db.Init() + + var backup model.BackupAccount + if err := global.DB.Where("id = ?", 6).First(&backup).Error; err != nil { + fmt.Println(err) + } + backupAccont, err := NewIBackupService().NewClient(&backup) + if err != nil { + fmt.Println(err) + } + fmt.Println(backupAccont.Upload("/Users/slooop/Downloads/1Panel.db", "database/1Panel.db")) +} diff --git a/backend/init/migration/migrate.go b/backend/init/migration/migrate.go index 89fc76b44..4bb9bca79 100644 --- a/backend/init/migration/migrate.go +++ b/backend/init/migration/migrate.go @@ -19,6 +19,7 @@ func Init() { migrations.AddTableImageRepo, migrations.AddTableWebsite, migrations.AddTableDatabaseMysql, + migrations.AddTableSnapshot, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/backend/init/migration/migrations/init.go b/backend/init/migration/migrations/init.go index ffb8cc413..94c12ed16 100644 --- a/backend/init/migration/migrations/init.go +++ b/backend/init/migration/migrations/init.go @@ -202,3 +202,13 @@ var AddTableWebsite = &gormigrate.Migration{ return nil }, } + +var AddTableSnapshot = &gormigrate.Migration{ + ID: "20230106-add-table-snapshot", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.Snapshot{}); err != nil { + return err + } + return nil + }, +} diff --git a/backend/init/viper/viper.go b/backend/init/viper/viper.go index e362b676a..6c9a05c6b 100644 --- a/backend/init/viper/viper.go +++ b/backend/init/viper/viper.go @@ -2,6 +2,8 @@ package viper import ( "fmt" + "strings" + "github.com/1Panel-dev/1Panel/backend/configs" "github.com/1Panel-dev/1Panel/backend/global" "github.com/fsnotify/fsnotify" @@ -23,6 +25,13 @@ func Init() { panic(err) } }) + for _, k := range v.AllKeys() { + value := v.GetString(k) + if strings.HasPrefix(value, "${") && strings.Contains(value, "}") { + itemKey := strings.ReplaceAll(value[strings.Index(value, "${"):strings.Index(value, "}")], "${", "") + v.Set(k, strings.ReplaceAll(value, fmt.Sprintf("${%s}", itemKey), v.GetString(itemKey))) + } + } serverConfig := configs.ServerConfig{} if err := v.Unmarshal(&serverConfig); err != nil { panic(err) diff --git a/backend/router/ro_setting.go b/backend/router/ro_setting.go index 4071995a1..fe8347b8c 100644 --- a/backend/router/ro_setting.go +++ b/backend/router/ro_setting.go @@ -24,5 +24,7 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) { settingRouter.POST("/monitor/clean", baseApi.CleanMonitor) settingRouter.GET("/mfa", baseApi.GetMFA) settingRouter.POST("/mfa/bind", baseApi.MFABind) + settingRouter.POST("/snapshot", baseApi.CreateSnapshot) + settingRouter.POST("/snapshot/search", baseApi.SearchSnapshot) } } diff --git a/frontend/src/api/interface/setting.ts b/frontend/src/api/interface/setting.ts index d8e632218..4c342648b 100644 --- a/frontend/src/api/interface/setting.ts +++ b/frontend/src/api/interface/setting.ts @@ -1,3 +1,5 @@ +import { DateTimeFormats } from '@intlify/core-base'; + export namespace Setting { export interface SettingInfo { userName: string; @@ -43,4 +45,17 @@ export namespace Setting { secret: string; code: string; } + export interface SnapshotCreate { + description: string; + backupType: string; + } + export interface SnapshotInfo { + id: number; + name: string; + description: string; + backupType: string; + status: string; + message: string; + createdAt: DateTimeFormats; + } } diff --git a/frontend/src/api/modules/setting.ts b/frontend/src/api/modules/setting.ts index 5ce1d4205..d5abdd110 100644 --- a/frontend/src/api/modules/setting.ts +++ b/frontend/src/api/modules/setting.ts @@ -1,4 +1,5 @@ import http from '@/api'; +import { ReqPage, ResPage } from '../interface'; import { Setting } from '../interface/setting'; export const getSettingInfo = () => { @@ -36,3 +37,11 @@ export const loadDaemonJsonPath = () => { export const bindMFA = (param: Setting.MFABind) => { return http.post(`/settings/mfa/bind`, param); }; + +// snapshot +export const snapshotCreate = (param: Setting.SnapshotCreate) => { + return http.post(`/settings/snapshot`, param); +}; +export const searchSnapshotPage = (param: ReqPage) => { + return http.post>(`/settings/snapshot/search`, param); +}; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index c78cf17ba..f9f17ff35 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -603,6 +603,7 @@ export default { containers: 'Container', commands: 'Command', groups: 'System Group', + files: 'File Manage', backups: 'Backup Account', logs: 'Panel Logs', settings: 'Panel Setting', @@ -735,6 +736,8 @@ export default { mfaHelper2: 'Scan the following QR code using the mobile app to obtain the 6-digit verification code', mfaHelper3: 'Enter six digits from the app', + snapshot: 'Snapshot', + enableMonitor: 'Enable', storeDays: 'Expiration time (day)', cleanMonitor: 'Clearing monitoring records', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 2c3d86aa5..33150fc52 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -617,6 +617,7 @@ export default { containers: '容器', groups: '系统组', commands: '快捷命令', + files: '文件管理', backups: '备份账号', logs: '面板日志', settings: '面板设置', @@ -724,6 +725,8 @@ export default { password: '密码', path: '路径', + snapshot: '快照', + safe: '安全', panelPort: '面板端口', portHelper: '建议端口范围8888 - 65535,注意:有安全组的服务器请提前在安全组放行新端口', diff --git a/frontend/src/routers/modules/setting.ts b/frontend/src/routers/modules/setting.ts index 5cba0ca4e..2a5de4aa2 100644 --- a/frontend/src/routers/modules/setting.ts +++ b/frontend/src/routers/modules/setting.ts @@ -16,6 +16,7 @@ const settingRouter = { component: () => import('@/views/setting/panel/index.vue'), hidden: true, meta: { + requiresAuth: true, key: 'Setting', }, }, @@ -25,6 +26,7 @@ const settingRouter = { component: () => import('@/views/setting/backup-account/index.vue'), hidden: true, meta: { + requiresAuth: true, key: 'Setting', }, }, @@ -34,6 +36,7 @@ const settingRouter = { component: () => import('@/views/setting/about/index.vue'), hidden: true, meta: { + requiresAuth: true, key: 'Setting', }, }, @@ -43,6 +46,7 @@ const settingRouter = { component: () => import('@/views/setting/monitor/index.vue'), hidden: true, meta: { + requiresAuth: true, key: 'Setting', }, }, @@ -52,6 +56,17 @@ const settingRouter = { component: () => import('@/views/setting/safe/index.vue'), hidden: true, meta: { + requiresAuth: true, + key: 'Setting', + }, + }, + { + path: '/setting/snapshot', + name: 'Snapshot', + hidden: true, + component: () => import('@/views/setting/snapshot/index.vue'), + meta: { + requiresAuth: true, key: 'Setting', }, }, diff --git a/frontend/src/views/setting/index.vue b/frontend/src/views/setting/index.vue index 57b659176..53d6b298c 100644 --- a/frontend/src/views/setting/index.vue +++ b/frontend/src/views/setting/index.vue @@ -11,6 +11,9 @@ {{ $t('setting.backupAccount') }} + + {{ $t('setting.snapshot') }} + {{ $t('menu.monitor') }} @@ -53,6 +56,8 @@ const handleChange = (val: string) => { case 'about': routerTo('/setting/about'); break; + case 'snapshot': + routerTo('/setting/snapshot'); } }; diff --git a/frontend/src/views/setting/snapshot/index.vue b/frontend/src/views/setting/snapshot/index.vue new file mode 100644 index 000000000..789e10572 --- /dev/null +++ b/frontend/src/views/setting/snapshot/index.vue @@ -0,0 +1,167 @@ + + +