1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-19 16:29:17 +08:00

feat: 完成快照同步功能

This commit is contained in:
ssongliu 2023-02-13 15:48:18 +08:00 committed by ssongliu
parent 71349d2a63
commit 205d406761
27 changed files with 816 additions and 85 deletions

View File

@ -210,3 +210,30 @@ func (b *BaseApi) ListBackup(c *gin.Context) {
helper.SuccessWithData(c, data) helper.SuccessWithData(c, data)
} }
// @Tags Backup Account
// @Summary List files from backup accounts
// @Description 获取备份账号内文件列表
// @Accept json
// @Param request body dto.BackupSearchFile true "request"
// @Success 200 {anrry} string
// @Security ApiKeyAuth
// @Router /backups/search/files [post]
func (b *BaseApi) LoadFilesFromBackup(c *gin.Context) {
var req dto.BackupSearchFile
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
}
data, err := backupService.ListFiles(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}

View File

@ -40,13 +40,13 @@ func (b *BaseApi) CreateMysql(c *gin.Context) {
// @Summary Update mysql database description // @Summary Update mysql database description
// @Description 更新 mysql 数据库库描述信息 // @Description 更新 mysql 数据库库描述信息
// @Accept json // @Accept json
// @Param request body dto.MysqlDescription true "request" // @Param request body dto.UpdateDescription true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /databases/description/update [post] // @Router /databases/description/update [post]
// @x-panel-log {"bodyKeys":["id","description"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"database_mysqls","output_colume":"name","output_value":"name"}],"formatZH":"mysql 数据库 [name] 描述信息修改 [description]","formatEN":"The description of the mysql database [name] is modified => [description]"} // @x-panel-log {"bodyKeys":["id","description"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"database_mysqls","output_colume":"name","output_value":"name"}],"formatZH":"mysql 数据库 [name] 描述信息修改 [description]","formatEN":"The description of the mysql database [name] is modified => [description]"}
func (b *BaseApi) UpdateMysqlDescription(c *gin.Context) { func (b *BaseApi) UpdateMysqlDescription(c *gin.Context) {
var req dto.MysqlDescription var req dto.UpdateDescription
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return return

View File

@ -9,7 +9,7 @@ import (
) )
// @Tags System Setting // @Tags System Setting
// @Summary Create system backup // @Summary Create system snapshot
// @Description 创建系统快照 // @Description 创建系统快照
// @Accept json // @Accept json
// @Param request body dto.SnapshotCreate true "request" // @Param request body dto.SnapshotCreate true "request"
@ -34,6 +34,58 @@ func (b *BaseApi) CreateSnapshot(c *gin.Context) {
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, nil)
} }
// @Tags System Setting
// @Summary Import system snapshot
// @Description 导入已有快照
// @Accept json
// @Param request body dto.SnapshotImport true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/snapshot/import [post]
// @x-panel-log {"bodyKeys":["from", "names"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"从 [from] 同步系统快照 [names]","formatEN":"Sync system snapshots [names] from [from]"}
func (b *BaseApi) ImportSnapshot(c *gin.Context) {
var req dto.SnapshotImport
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.SnapshotImport(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags System Setting
// @Summary Update snapshot description
// @Description 更新快照描述信息
// @Accept json
// @Param request body dto.UpdateDescription true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/snapshot/description/update [post]
// @x-panel-log {"bodyKeys":["id","description"],"paramKeys":[],"BeforeFuntions":[{"input_colume":"id","input_value":"id","isList":false,"db":"snapshots","output_colume":"name","output_value":"name"}],"formatZH":"快照 [name] 描述信息修改 [description]","formatEN":"The description of the snapshot [name] is modified => [description]"}
func (b *BaseApi) UpdateSnapDescription(c *gin.Context) {
var req dto.UpdateDescription
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.UpdateDescription(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags System Setting // @Tags System Setting
// @Summary Page system snapshot // @Summary Page system snapshot
// @Description 获取系统快照列表分页 // @Description 获取系统快照列表分页

View File

@ -26,6 +26,10 @@ type BackupSearch struct {
DetailName string `json:"detailName"` DetailName string `json:"detailName"`
} }
type BackupSearchFile struct {
Type string `json:"type" validate:"required"`
}
type RecordSearch struct { type RecordSearch struct {
PageInfo PageInfo
Type string `json:"type" validate:"required"` Type string `json:"type" validate:"required"`

View File

@ -10,6 +10,11 @@ type PageInfo struct {
PageSize int `json:"pageSize" validate:"required,number"` PageSize int `json:"pageSize" validate:"required,number"`
} }
type UpdateDescription struct {
ID uint `json:"id" validate:"required"`
Description string `json:"description"`
}
type OperationWithName struct { type OperationWithName struct {
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
} }

View File

@ -2,11 +2,6 @@ package dto
import "time" import "time"
type MysqlDescription struct {
ID uint `json:"id" validate:"required"`
Description string `json:"description"`
}
type MysqlDBInfo struct { type MysqlDBInfo struct {
ID uint `json:"id"` ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`

View File

@ -57,6 +57,11 @@ type SnapshotRecover struct {
ReDownload bool `json:"reDownload"` ReDownload bool `json:"reDownload"`
ID uint `json:"id" validate:"required"` ID uint `json:"id" validate:"required"`
} }
type SnapshotImport struct {
From string `json:"from"`
Names []string `json:"names"`
Description string `json:"description"`
}
type SnapshotInfo struct { type SnapshotInfo struct {
ID uint `json:"id"` ID uint `json:"id"`
Name string `json:"name"` Name string `json:"name"`

View File

@ -29,6 +29,8 @@ type IBackupService interface {
BatchDelete(ids []uint) error BatchDelete(ids []uint) error
BatchDeleteRecord(ids []uint) error BatchDeleteRecord(ids []uint) error
NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error) NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error)
ListFiles(req dto.BackupSearchFile) ([]interface{}, error)
} }
func NewIBackupService() IBackupService { func NewIBackupService() IBackupService {
@ -226,6 +228,18 @@ func (u *BackupService) Update(req dto.BackupOperate) error {
return nil return nil
} }
func (u *BackupService) ListFiles(req dto.BackupSearchFile) ([]interface{}, error) {
backup, err := backupRepo.Get(backupRepo.WithByType(req.Type))
if err != nil {
return nil, err
}
client, err := u.NewClient(&backup)
if err != nil {
return nil, err
}
return client.ListObjects("system_snapshot/")
}
func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error) { func (u *BackupService) NewClient(backup *model.BackupAccount) (cloud_storage.CloudStorageClient, error) {
varMap := make(map[string]interface{}) varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil { if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {

View File

@ -37,7 +37,7 @@ type IMysqlService interface {
ChangePassword(info dto.ChangeDBInfo) error ChangePassword(info dto.ChangeDBInfo) error
UpdateVariables(updatas []dto.MysqlVariablesUpdate) error UpdateVariables(updatas []dto.MysqlVariablesUpdate) error
UpdateConfByFile(info dto.MysqlConfUpdateByFile) error UpdateConfByFile(info dto.MysqlConfUpdateByFile) error
UpdateDescription(req dto.MysqlDescription) error UpdateDescription(req dto.UpdateDescription) error
RecoverByUpload(req dto.UploadRecover) error RecoverByUpload(req dto.UploadRecover) error
Backup(db dto.BackupDB) error Backup(db dto.BackupDB) error
@ -201,7 +201,7 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
return &mysql, nil return &mysql, nil
} }
func (u *MysqlService) UpdateDescription(req dto.MysqlDescription) error { func (u *MysqlService) UpdateDescription(req dto.UpdateDescription) error {
return mysqlRepo.Update(req.ID, map[string]interface{}{"description": req.Description}) return mysqlRepo.Update(req.ID, map[string]interface{}{"description": req.Description})
} }

View File

@ -29,8 +29,10 @@ type ISnapshotService interface {
SnapshotCreate(req dto.SnapshotCreate) error SnapshotCreate(req dto.SnapshotCreate) error
SnapshotRecover(req dto.SnapshotRecover) error SnapshotRecover(req dto.SnapshotRecover) error
SnapshotRollback(req dto.SnapshotRecover) error SnapshotRollback(req dto.SnapshotRecover) error
SnapshotImport(req dto.SnapshotImport) error
Delete(req dto.BatchDeleteReq) error Delete(req dto.BatchDeleteReq) error
UpdateDescription(req dto.UpdateDescription) error
readFromJson(path string) (SnapshotJson, error) readFromJson(path string) (SnapshotJson, error)
} }
@ -51,11 +53,48 @@ func (u *SnapshotService) SearchWithPage(req dto.SearchWithPage) (int64, interfa
return total, dtoSnap, err return total, dtoSnap, err
} }
func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
if len(req.Names) == 0 {
return fmt.Errorf("incorrect snapshot request body: %v", req.Names)
}
for _, snap := range req.Names {
nameItems := strings.Split(snap, "_")
if !strings.HasPrefix(snap, "1panel_v") || !strings.HasSuffix(snap, ".tar.gz") || len(nameItems) != 3 {
return fmt.Errorf("incorrect snapshot name format of %s", snap)
}
formatTime, err := time.Parse("20060102150405", strings.ReplaceAll(nameItems[2], ".tar.gz", ""))
if err != nil {
return fmt.Errorf("incorrect snapshot name format of %s", snap)
}
itemSnap := model.Snapshot{
Name: snap,
From: req.From,
Version: nameItems[1],
Description: req.Description,
Status: constant.StatusSuccess,
BaseModel: model.BaseModel{
CreatedAt: formatTime,
UpdatedAt: formatTime,
},
}
if err := snapshotRepo.Create(&itemSnap); err != nil {
return err
}
}
return nil
}
func (u *SnapshotService) UpdateDescription(req dto.UpdateDescription) error {
return snapshotRepo.Update(req.ID, map[string]interface{}{"description": req.Description})
}
type SnapshotJson struct { type SnapshotJson struct {
OldBaseDir string `json:"oldBaseDir"`
OldDockerDataDir string `json:"oldDockerDataDir"` OldDockerDataDir string `json:"oldDockerDataDir"`
OldBackupDataDir string `json:"oldDackupDataDir"` OldBackupDataDir string `json:"oldDackupDataDir"`
OldPanelDataDir string `json:"oldPanelDataDir"` OldPanelDataDir string `json:"oldPanelDataDir"`
BaseDir string `json:"baseDir"`
DockerDataDir string `json:"dockerDataDir"` DockerDataDir string `json:"dockerDataDir"`
BackupDataDir string `json:"backupDataDir"` BackupDataDir string `json:"backupDataDir"`
PanelDataDir string `json:"panelDataDir"` PanelDataDir string `json:"panelDataDir"`
@ -78,15 +117,15 @@ func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate) error {
} }
timeNow := time.Now().Format("20060102150405") timeNow := time.Now().Format("20060102150405")
rootDir := fmt.Sprintf("%s/system/1panel_snapshot_%s", localDir, timeNow) versionItem, _ := settingRepo.Get(settingRepo.WithByKey("SystemVersion"))
rootDir := fmt.Sprintf("%s/system/1panel_%s_%s", localDir, versionItem.Value, timeNow)
backupPanelDir := fmt.Sprintf("%s/1panel", rootDir) backupPanelDir := fmt.Sprintf("%s/1panel", rootDir)
_ = os.MkdirAll(backupPanelDir, os.ModePerm) _ = os.MkdirAll(backupPanelDir, os.ModePerm)
backupDockerDir := fmt.Sprintf("%s/docker", rootDir) backupDockerDir := fmt.Sprintf("%s/docker", rootDir)
_ = os.MkdirAll(backupDockerDir, os.ModePerm) _ = os.MkdirAll(backupDockerDir, os.ModePerm)
versionItem, _ := settingRepo.Get(settingRepo.WithByKey("SystemVersion"))
snap := model.Snapshot{ snap := model.Snapshot{
Name: "1panel_snapshot_" + timeNow, Name: fmt.Sprintf("1panel_%s_%s", versionItem.Value, timeNow),
Description: req.Description, Description: req.Description,
From: req.From, From: req.From,
Version: versionItem.Value, Version: versionItem.Value,
@ -139,13 +178,19 @@ func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate) error {
return return
} }
snapJson := SnapshotJson{DockerDataDir: dockerDataDir, BackupDataDir: localDir, PanelDataDir: global.CONF.BaseDir + "/1panel", LiveRestoreEnabled: liveRestoreStatus} snapJson := SnapshotJson{
BaseDir: global.CONF.BaseDir,
DockerDataDir: dockerDataDir,
BackupDataDir: localDir,
PanelDataDir: global.CONF.BaseDir + "/1panel",
LiveRestoreEnabled: liveRestoreStatus,
}
if err := u.saveJson(snapJson, rootDir); err != nil { if err := u.saveJson(snapJson, rootDir); err != nil {
updateSnapshotStatus(snap.ID, constant.StatusFailed, fmt.Sprintf("save snapshot json failed, err: %v", err)) updateSnapshotStatus(snap.ID, constant.StatusFailed, fmt.Sprintf("save snapshot json failed, err: %v", err))
return return
} }
if err := fileOp.Compress([]string{rootDir}, fmt.Sprintf("%s/system", localDir), fmt.Sprintf("1panel_snapshot_%s.tar.gz", timeNow), files.TarGz); err != nil { if err := fileOp.Compress([]string{rootDir}, fmt.Sprintf("%s/system", localDir), fmt.Sprintf("1panel_%s_%s.tar.gz", versionItem.Value, timeNow), files.TarGz); err != nil {
updateSnapshotStatus(snap.ID, constant.StatusFailed, err.Error()) updateSnapshotStatus(snap.ID, constant.StatusFailed, err.Error())
return return
} }
@ -154,14 +199,14 @@ func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate) error {
global.LOG.Infof("start to upload snapshot to %s, please wait", backup.Type) global.LOG.Infof("start to upload snapshot to %s, please wait", backup.Type)
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusUploading}) _ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusUploading})
localPath := fmt.Sprintf("%s/system/1panel_snapshot_%s.tar.gz", localDir, timeNow) localPath := fmt.Sprintf("%s/system/1panel_%s_%s.tar.gz", localDir, versionItem.Value, timeNow)
if ok, err := backupAccont.Upload(localPath, fmt.Sprintf("system_snapshot/1panel_snapshot_%s.tar.gz", timeNow)); err != nil || !ok { if ok, err := backupAccont.Upload(localPath, fmt.Sprintf("system_snapshot/1panel_%s_%s.tar.gz", versionItem.Value, timeNow)); err != nil || !ok {
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()}) _ = 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) global.LOG.Errorf("upload snapshot to %s failed, err: %v", backup.Type, err)
return return
} }
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusSuccess}) _ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusSuccess})
_ = os.RemoveAll(fmt.Sprintf("%s/system/1panel_snapshot_%s.tar.gz", localDir, timeNow)) _ = os.RemoveAll(fmt.Sprintf("%s/system/1panel_%s_%s.tar.gz", localDir, versionItem.Value, timeNow))
global.LOG.Infof("upload snapshot to %s success", backup.Type) global.LOG.Infof("upload snapshot to %s success", backup.Type)
}() }()
@ -230,8 +275,6 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
isReTry = false isReTry = false
} }
rootDir := fmt.Sprintf("%s/%s", baseDir, snap.Name) rootDir := fmt.Sprintf("%s/%s", baseDir, snap.Name)
u.OriginalPath = fmt.Sprintf("%s/original_%s", global.CONF.BaseDir, snap.Name)
_ = os.MkdirAll(u.OriginalPath, os.ModePerm)
snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", rootDir)) snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", rootDir))
if err != nil { if err != nil {
@ -241,7 +284,10 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
if snap.InterruptStep == "Readjson" { if snap.InterruptStep == "Readjson" {
isReTry = false isReTry = false
} }
u.OriginalPath = fmt.Sprintf("%s/original_%s", snapJson.BaseDir, snap.Name)
_ = os.MkdirAll(u.OriginalPath, os.ModePerm)
snapJson.OldBaseDir = global.CONF.BaseDir
snapJson.OldPanelDataDir = global.CONF.BaseDir + "/1panel" snapJson.OldPanelDataDir = global.CONF.BaseDir + "/1panel"
snapJson.OldBackupDataDir = localDir snapJson.OldBackupDataDir = localDir
recoverPanelDir := fmt.Sprintf("%s/%s/1panel", baseDir, snap.Name) recoverPanelDir := fmt.Sprintf("%s/%s/1panel", baseDir, snap.Name)
@ -340,10 +386,6 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
fileOp := files.NewFileOp() fileOp := files.NewFileOp()
rootDir := fmt.Sprintf("%s/system/%s/%s", localDir, snap.Name, snap.Name) rootDir := fmt.Sprintf("%s/system/%s/%s", localDir, snap.Name, snap.Name)
u.OriginalPath = fmt.Sprintf("%s/original_%s", global.CONF.BaseDir, snap.Name)
if _, err := os.Stat(u.OriginalPath); err != nil && os.IsNotExist(err) {
return fmt.Errorf("load original dir failed, err: %s", err)
}
_ = settingRepo.Update("SystemStatus", "Rollbacking") _ = settingRepo.Update("SystemStatus", "Rollbacking")
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"rollback_status": constant.StatusWaiting}) _ = snapshotRepo.Update(snap.ID, map[string]interface{}{"rollback_status": constant.StatusWaiting})
@ -353,6 +395,10 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
updateRollbackStatus(snap.ID, constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err)) updateRollbackStatus(snap.ID, constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
return return
} }
u.OriginalPath = fmt.Sprintf("%s/original_%s", snapJson.OldBaseDir, snap.Name)
if _, err := os.Stat(u.OriginalPath); err != nil && os.IsNotExist(err) {
return
}
_, _ = cmd.Exec("systemctl stop docker") _, _ = cmd.Exec("systemctl stop docker")
if err := u.handleDockerDatas(fileOp, "rollback", u.OriginalPath, snapJson.OldDockerDataDir); err != nil { if err := u.handleDockerDatas(fileOp, "rollback", u.OriginalPath, snapJson.OldDockerDataDir); err != nil {

View File

@ -58,7 +58,7 @@ func (u *UpgradeService) SearchUpgrade() (*dto.UpgradeInfo, error) {
} }
if len(releaseInfo.NewVersion) != 0 { if len(releaseInfo.NewVersion) != 0 {
isNew, err := compareVersion(currentVersion.Value, releaseInfo.NewVersion) isNew, err := compareVersion(currentVersion.Value, releaseInfo.NewVersion)
if !isNew && err != nil { if !isNew || err != nil {
return nil, err return nil, err
} }
return &releaseInfo, nil return &releaseInfo, nil

View File

@ -17,6 +17,7 @@ func (s *BackupRouter) InitBackupRouter(Router *gin.RouterGroup) {
baseApi := v1.ApiGroupApp.BaseApi baseApi := v1.ApiGroupApp.BaseApi
{ {
baRouter.GET("/search", baseApi.ListBackup) baRouter.GET("/search", baseApi.ListBackup)
baRouter.POST("/search/files", baseApi.LoadFilesFromBackup)
baRouter.POST("/buckets", baseApi.ListBuckets) baRouter.POST("/buckets", baseApi.ListBuckets)
baRouter.POST("", baseApi.CreateBackup) baRouter.POST("", baseApi.CreateBackup)
baRouter.POST("/del", baseApi.DeleteBackup) baRouter.POST("/del", baseApi.DeleteBackup)

View File

@ -27,9 +27,11 @@ func (s *SettingRouter) InitSettingRouter(Router *gin.RouterGroup) {
settingRouter.POST("/mfa/bind", baseApi.MFABind) settingRouter.POST("/mfa/bind", baseApi.MFABind)
settingRouter.POST("/snapshot", baseApi.CreateSnapshot) settingRouter.POST("/snapshot", baseApi.CreateSnapshot)
settingRouter.POST("/snapshot/search", baseApi.SearchSnapshot) settingRouter.POST("/snapshot/search", baseApi.SearchSnapshot)
settingRouter.POST("/snapshot/import", baseApi.ImportSnapshot)
settingRouter.POST("/snapshot/del", baseApi.DeleteSnapshot) settingRouter.POST("/snapshot/del", baseApi.DeleteSnapshot)
settingRouter.POST("/snapshot/recover", baseApi.RecoverSnapshot) settingRouter.POST("/snapshot/recover", baseApi.RecoverSnapshot)
settingRouter.POST("/snapshot/rollback", baseApi.RollbackSnapshot) settingRouter.POST("/snapshot/rollback", baseApi.RollbackSnapshot)
settingRouter.POST("/snapshot/description/update", baseApi.UpdateSnapDescription)
settingRouter.POST("/upgrade", baseApi.Upgrade) settingRouter.POST("/upgrade", baseApi.Upgrade)
settingRouter.GET("/upgrade", baseApi.GetUpgradeInfo) settingRouter.GET("/upgrade", baseApi.GetUpgradeInfo)
settingRouter.GET("/basedir", baseApi.LoadBaseDir) settingRouter.GET("/basedir", baseApi.LoadBaseDir)

View File

@ -15,7 +15,7 @@ func init() {
} }
var userinfoCmd = &cobra.Command{ var userinfoCmd = &cobra.Command{
Use: "userinfo", Use: "user-info",
Short: "获取用户信息", Short: "获取用户信息",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
fullPath := "/opt/1panel/db/1Panel.db" fullPath := "/opt/1panel/db/1Panel.db"

View File

@ -1169,6 +1169,42 @@ var doc = `{
} }
} }
}, },
"/backups/search/files": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取备份账号内文件列表",
"consumes": [
"application/json"
],
"tags": [
"Backup Account"
],
"summary": "List files from backup accounts",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.BackupSearchFile"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "anrry"
}
}
}
}
},
"/backups/update": { "/backups/update": {
"post": { "post": {
"security": [ "security": [
@ -3907,7 +3943,7 @@ var doc = `{
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/dto.MysqlDescription" "$ref": "#/definitions/dto.UpdateDescription"
} }
} }
], ],
@ -6285,7 +6321,7 @@ var doc = `{
"tags": [ "tags": [
"System Setting" "System Setting"
], ],
"summary": "Create system backup", "summary": "Create system snapshot",
"parameters": [ "parameters": [
{ {
"description": "request", "description": "request",
@ -6365,6 +6401,101 @@ var doc = `{
} }
} }
}, },
"/settings/snapshot/description/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新快照描述信息",
"consumes": [
"application/json"
],
"tags": [
"System Setting"
],
"summary": "Update snapshot description",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.UpdateDescription"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [
{
"db": "snapshots",
"input_colume": "id",
"input_value": "id",
"isList": false,
"output_colume": "name",
"output_value": "name"
}
],
"bodyKeys": [
"id",
"description"
],
"formatEN": "The description of the snapshot [name] is modified =\u003e [description]",
"formatZH": "快照 [name] 描述信息修改 [description]",
"paramKeys": []
}
}
},
"/settings/snapshot/import": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "导入已有快照",
"consumes": [
"application/json"
],
"tags": [
"System Setting"
],
"summary": "Import system snapshot",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SnapshotImport"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"from",
"names"
],
"formatEN": "Sync system snapshots [names] from [from]",
"formatZH": "从 [from] 同步系统快照 [names]",
"paramKeys": []
}
}
},
"/settings/snapshot/recover": { "/settings/snapshot/recover": {
"post": { "post": {
"security": [ "security": [
@ -8474,6 +8605,17 @@ var doc = `{
} }
} }
}, },
"dto.BackupSearchFile": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string"
}
}
},
"dto.BatchDelete": { "dto.BatchDelete": {
"type": "object", "type": "object",
"required": [ "required": [
@ -9751,20 +9893,6 @@ var doc = `{
} }
} }
}, },
"dto.MysqlDescription": {
"type": "object",
"required": [
"id"
],
"properties": {
"description": {
"type": "string"
},
"id": {
"type": "integer"
}
}
},
"dto.MysqlStatus": { "dto.MysqlStatus": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -10451,6 +10579,23 @@ var doc = `{
} }
} }
}, },
"dto.SnapshotImport": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"from": {
"type": "string"
},
"names": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"dto.SnapshotRecover": { "dto.SnapshotRecover": {
"type": "object", "type": "object",
"required": [ "required": [
@ -10468,6 +10613,20 @@ var doc = `{
} }
} }
}, },
"dto.UpdateDescription": {
"type": "object",
"required": [
"id"
],
"properties": {
"description": {
"type": "string"
},
"id": {
"type": "integer"
}
}
},
"dto.Upgrade": { "dto.Upgrade": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -1155,6 +1155,42 @@
} }
} }
}, },
"/backups/search/files": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取备份账号内文件列表",
"consumes": [
"application/json"
],
"tags": [
"Backup Account"
],
"summary": "List files from backup accounts",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.BackupSearchFile"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "anrry"
}
}
}
}
},
"/backups/update": { "/backups/update": {
"post": { "post": {
"security": [ "security": [
@ -3893,7 +3929,7 @@
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/dto.MysqlDescription" "$ref": "#/definitions/dto.UpdateDescription"
} }
} }
], ],
@ -6271,7 +6307,7 @@
"tags": [ "tags": [
"System Setting" "System Setting"
], ],
"summary": "Create system backup", "summary": "Create system snapshot",
"parameters": [ "parameters": [
{ {
"description": "request", "description": "request",
@ -6351,6 +6387,101 @@
} }
} }
}, },
"/settings/snapshot/description/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新快照描述信息",
"consumes": [
"application/json"
],
"tags": [
"System Setting"
],
"summary": "Update snapshot description",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.UpdateDescription"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [
{
"db": "snapshots",
"input_colume": "id",
"input_value": "id",
"isList": false,
"output_colume": "name",
"output_value": "name"
}
],
"bodyKeys": [
"id",
"description"
],
"formatEN": "The description of the snapshot [name] is modified =\u003e [description]",
"formatZH": "快照 [name] 描述信息修改 [description]",
"paramKeys": []
}
}
},
"/settings/snapshot/import": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "导入已有快照",
"consumes": [
"application/json"
],
"tags": [
"System Setting"
],
"summary": "Import system snapshot",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SnapshotImport"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"from",
"names"
],
"formatEN": "Sync system snapshots [names] from [from]",
"formatZH": "从 [from] 同步系统快照 [names]",
"paramKeys": []
}
}
},
"/settings/snapshot/recover": { "/settings/snapshot/recover": {
"post": { "post": {
"security": [ "security": [
@ -8460,6 +8591,17 @@
} }
} }
}, },
"dto.BackupSearchFile": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string"
}
}
},
"dto.BatchDelete": { "dto.BatchDelete": {
"type": "object", "type": "object",
"required": [ "required": [
@ -9737,20 +9879,6 @@
} }
} }
}, },
"dto.MysqlDescription": {
"type": "object",
"required": [
"id"
],
"properties": {
"description": {
"type": "string"
},
"id": {
"type": "integer"
}
}
},
"dto.MysqlStatus": { "dto.MysqlStatus": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -10437,6 +10565,23 @@
} }
} }
}, },
"dto.SnapshotImport": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"from": {
"type": "string"
},
"names": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"dto.SnapshotRecover": { "dto.SnapshotRecover": {
"type": "object", "type": "object",
"required": [ "required": [
@ -10454,6 +10599,20 @@
} }
} }
}, },
"dto.UpdateDescription": {
"type": "object",
"required": [
"id"
],
"properties": {
"description": {
"type": "string"
},
"id": {
"type": "integer"
}
}
},
"dto.Upgrade": { "dto.Upgrade": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -28,6 +28,13 @@ definitions:
- type - type
- vars - vars
type: object type: object
dto.BackupSearchFile:
properties:
type:
type: string
required:
- type
type: object
dto.BatchDelete: dto.BatchDelete:
properties: properties:
names: names:
@ -890,15 +897,6 @@ definitions:
required: required:
- id - id
type: object type: object
dto.MysqlDescription:
properties:
description:
type: string
id:
type: integer
required:
- id
type: object
dto.MysqlStatus: dto.MysqlStatus:
properties: properties:
Aborted_clients: Aborted_clients:
@ -1354,6 +1352,17 @@ definitions:
required: required:
- from - from
type: object type: object
dto.SnapshotImport:
properties:
description:
type: string
from:
type: string
names:
items:
type: string
type: array
type: object
dto.SnapshotRecover: dto.SnapshotRecover:
properties: properties:
id: id:
@ -1365,6 +1374,15 @@ definitions:
required: required:
- id - id
type: object type: object
dto.UpdateDescription:
properties:
description:
type: string
id:
type: integer
required:
- id
type: object
dto.Upgrade: dto.Upgrade:
properties: properties:
version: version:
@ -3358,6 +3376,28 @@ paths:
summary: List buckets summary: List buckets
tags: tags:
- Backup Account - Backup Account
/backups/search/files:
post:
consumes:
- application/json
description: 获取备份账号内文件列表
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.BackupSearchFile'
responses:
"200":
description: OK
schema:
type: anrry
security:
- ApiKeyAuth: []
summary: List files from backup accounts
tags:
- Backup Account
/backups/update: /backups/update:
post: post:
consumes: consumes:
@ -5100,7 +5140,7 @@ paths:
name: request name: request
required: true required: true
schema: schema:
$ref: '#/definitions/dto.MysqlDescription' $ref: '#/definitions/dto.UpdateDescription'
responses: responses:
"200": "200":
description: "" description: ""
@ -6621,7 +6661,7 @@ paths:
description: "" description: ""
security: security:
- ApiKeyAuth: [] - ApiKeyAuth: []
summary: Create system backup summary: Create system snapshot
tags: tags:
- System Setting - System Setting
x-panel-log: x-panel-log:
@ -6665,6 +6705,68 @@ paths:
formatEN: Delete system backup [name] formatEN: Delete system backup [name]
formatZH: 删除系统快照 [name] formatZH: 删除系统快照 [name]
paramKeys: [] paramKeys: []
/settings/snapshot/description/update:
post:
consumes:
- application/json
description: 更新快照描述信息
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.UpdateDescription'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Update snapshot description
tags:
- System Setting
x-panel-log:
BeforeFuntions:
- db: snapshots
input_colume: id
input_value: id
isList: false
output_colume: name
output_value: name
bodyKeys:
- id
- description
formatEN: The description of the snapshot [name] is modified => [description]
formatZH: 快照 [name] 描述信息修改 [description]
paramKeys: []
/settings/snapshot/import:
post:
consumes:
- application/json
description: 导入已有快照
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.SnapshotImport'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Import system snapshot
tags:
- System Setting
x-panel-log:
BeforeFuntions: []
bodyKeys:
- from
- names
formatEN: Sync system snapshots [names] from [from]
formatZH: 从 [from] 同步系统快照 [names]
paramKeys: []
/settings/snapshot/recover: /settings/snapshot/recover:
post: post:
consumes: consumes:

View File

@ -5,10 +5,6 @@ export namespace Database {
mysqlName: string; mysqlName: string;
dbName: string; dbName: string;
} }
export interface DescriptionUpdate {
id: number;
description: string;
}
export interface Backup { export interface Backup {
mysqlName: string; mysqlName: string;
dbName: string; dbName: string;

View File

@ -28,6 +28,10 @@ export interface CommonModel {
CreatedAt?: string; CreatedAt?: string;
UpdatedAt?: string; UpdatedAt?: string;
} }
export interface DescriptionUpdate {
id: number;
description: string;
}
// * 文件上传模块 // * 文件上传模块
export namespace Upload { export namespace Upload {

View File

@ -53,6 +53,11 @@ export namespace Setting {
from: string; from: string;
description: string; description: string;
} }
export interface SnapshotImport {
from: string;
names: Array<string>;
description: string;
}
export interface SnapshotRecover { export interface SnapshotRecover {
id: number; id: number;
isNew: boolean; isNew: boolean;

View File

@ -6,6 +6,10 @@ export const getBackupList = () => {
return http.get<Array<Backup.BackupInfo>>(`/backups/search`); return http.get<Array<Backup.BackupInfo>>(`/backups/search`);
}; };
export const getFilesFromBackup = (type: string) => {
return http.post<Array<any>>(`/backups/search/files`, { type: type });
};
export const addBackup = (params: Backup.BackupOperate) => { export const addBackup = (params: Backup.BackupOperate) => {
return http.post<Backup.BackupOperate>(`/backups`, params); return http.post<Backup.BackupOperate>(`/backups`, params);
}; };

View File

@ -1,5 +1,5 @@
import http from '@/api'; import http from '@/api';
import { SearchWithPage, ReqPage, ResPage } from '../interface'; import { SearchWithPage, ReqPage, ResPage, DescriptionUpdate } from '../interface';
import { Database } from '../interface/database'; import { Database } from '../interface/database';
export const searchMysqlDBs = (params: SearchWithPage) => { export const searchMysqlDBs = (params: SearchWithPage) => {
@ -25,7 +25,7 @@ export const updateMysqlAccess = (params: Database.ChangeInfo) => {
export const updateMysqlPassword = (params: Database.ChangeInfo) => { export const updateMysqlPassword = (params: Database.ChangeInfo) => {
return http.post(`/databases/change/password`, params); return http.post(`/databases/change/password`, params);
}; };
export const updateMysqlDescription = (params: Database.DescriptionUpdate) => { export const updateMysqlDescription = (params: DescriptionUpdate) => {
return http.post(`/databases/description/update`, params); return http.post(`/databases/description/update`, params);
}; };
export const updateMysqlVariables = (params: Array<Database.VariablesUpdate>) => { export const updateMysqlVariables = (params: Array<Database.VariablesUpdate>) => {

View File

@ -1,5 +1,5 @@
import http from '@/api'; import http from '@/api';
import { ResPage, SearchWithPage } from '../interface'; import { ResPage, SearchWithPage, DescriptionUpdate } from '../interface';
import { Setting } from '../interface/setting'; import { Setting } from '../interface/setting';
export const getSettingInfo = () => { export const getSettingInfo = () => {
@ -53,6 +53,12 @@ export const loadBaseDir = () => {
export const snapshotCreate = (param: Setting.SnapshotCreate) => { export const snapshotCreate = (param: Setting.SnapshotCreate) => {
return http.post(`/settings/snapshot`, param); return http.post(`/settings/snapshot`, param);
}; };
export const snapshotImport = (param: Setting.SnapshotImport) => {
return http.post(`/settings/snapshot/import`, param);
};
export const updateSnapshotDescription = (param: DescriptionUpdate) => {
return http.post(`/settings/snapshot/description/update`, param);
};
export const snapshotDelete = (param: { ids: number[] }) => { export const snapshotDelete = (param: { ids: number[] }) => {
return http.post(`/settings/snapshot/del`, param); return http.post(`/settings/snapshot/del`, param);
}; };

View File

@ -770,6 +770,7 @@ export default {
thirdPartySupport: 'Only third-party accounts are supported', thirdPartySupport: 'Only third-party accounts are supported',
recoverDetail: 'Recover detail', recoverDetail: 'Recover detail',
createSnapshot: 'Create snapshot', createSnapshot: 'Create snapshot',
importSnapshot: 'Sync snapshot',
recover: 'Recover', recover: 'Recover',
noRecoverRecord: 'No recovery record has been recorded', noRecoverRecord: 'No recovery record has been recorded',
lastRecoverAt: 'Last recovery time', lastRecoverAt: 'Last recovery time',

View File

@ -768,6 +768,7 @@ export default {
thirdPartySupport: '仅支持第三方账号', thirdPartySupport: '仅支持第三方账号',
recoverDetail: '恢复详情', recoverDetail: '恢复详情',
createSnapshot: '创建快照', createSnapshot: '创建快照',
importSnapshot: '同步快照',
recover: '恢复', recover: '恢复',
noRecoverRecord: '暂无恢复记录', noRecoverRecord: '暂无恢复记录',
lastRecoverAt: '上次恢复时间', lastRecoverAt: '上次恢复时间',

View File

@ -0,0 +1,124 @@
<template>
<div v-loading="loading">
<el-drawer v-model="drawerVisiable" size="50%">
<template #header>
<DrawerHeader :header="$t('setting.importSnapshot')" :back="handleClose" />
</template>
<el-form ref="formRef" label-position="top" :model="form" :rules="rules">
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form-item :label="$t('setting.backupAccount')" prop="from">
<el-select style="width: 100%" v-model="form.from" @change="loadFiles" clearable>
<el-option
v-for="item in backupOptions"
:key="item.label"
:value="item.value"
:label="item.label"
/>
</el-select>
</el-form-item>
<el-form-item :label="$t('commons.table.name')" prop="names">
<el-select style="width: 100%" v-model="form.names" multiple clearable>
<el-option v-for="item in fileNames" :key="item" :value="item" :label="item" />
</el-select>
</el-form-item>
<el-form-item :label="$t('commons.table.description')" prop="description">
<el-input type="textarea" clearable v-model="form.description" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button :disabled="loading" @click="drawerVisiable = false">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="loading" type="primary" @click="submitImport(formRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-drawer>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
import { ElMessage, FormInstance } from 'element-plus';
import i18n from '@/lang';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { snapshotImport } from '@/api/modules/setting';
import { getBackupList, getFilesFromBackup } from '@/api/modules/backup';
import { loadBackupName } from '../../helper';
import { Rules } from '@/global/form-rules';
const drawerVisiable = ref(false);
const loading = ref();
const formRef = ref();
const backupOptions = ref();
const fileNames = ref();
const form = reactive({
from: '',
names: [],
description: '',
});
const rules = reactive({
from: [Rules.requiredSelect],
name: [Rules.requiredSelect],
});
const acceptParams = (): void => {
form.from = '';
form.names = [] as Array<string>;
loadBackups();
drawerVisiable.value = true;
};
const emit = defineEmits(['search']);
const handleClose = () => {
drawerVisiable.value = false;
};
const submitImport = async (formEl: FormInstance | undefined) => {
loading.value = true;
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
await snapshotImport(form)
.then(() => {
emit('search');
loading.value = false;
drawerVisiable.value = false;
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
})
.catch(() => {
loading.value = false;
});
});
};
const loadBackups = async () => {
const res = await getBackupList();
backupOptions.value = [];
for (const item of res.data) {
if (item.type !== 'LOCAL' && item.id !== 0) {
backupOptions.value.push({ label: loadBackupName(item.type), value: item.type });
}
}
};
const loadFiles = async () => {
const res = await getFilesFromBackup(form.from);
fileNames.value = res.data || [];
for (let i = 0; i < fileNames.value.length; i++) {
fileNames.value[i] = fileNames.value[i].replaceAll('system_snapshot/', '');
}
};
defineExpose({
acceptParams,
});
</script>

View File

@ -7,6 +7,9 @@
<el-button type="primary" @click="onCreate()"> <el-button type="primary" @click="onCreate()">
{{ $t('setting.createSnapshot') }} {{ $t('setting.createSnapshot') }}
</el-button> </el-button>
<el-button @click="onImport()">
{{ $t('setting.importSnapshot') }}
</el-button>
<el-button type="primary" plain :disabled="selects.length === 0" @click="batchDelete(null)"> <el-button type="primary" plain :disabled="selects.length === 0" @click="batchDelete(null)">
{{ $t('commons.button.delete') }} {{ $t('commons.button.delete') }}
</el-button> </el-button>
@ -43,14 +46,9 @@
prop="name" prop="name"
fix fix
/> />
<el-table-column <el-table-column prop="version" :label="$t('app.version')" />
:label="$t('commons.table.description')" <el-table-column :label="$t('setting.backupAccount')" min-width="80" prop="from" />
min-width="150" <el-table-column :label="$t('commons.table.status')" min-width="80" prop="status">
show-overflow-tooltip
prop="description"
/>
<el-table-column :label="$t('setting.backupAccount')" min-width="150" prop="from" />
<el-table-column :label="$t('setting.backup')" min-width="80" prop="status">
<template #default="{ row }"> <template #default="{ row }">
<el-tag v-if="row.status === 'Success'" type="success"> <el-tag v-if="row.status === 'Success'" type="success">
{{ $t('commons.table.statusSuccess') }} {{ $t('commons.table.statusSuccess') }}
@ -72,6 +70,13 @@
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('commons.table.description')" prop="description">
<template #default="{ row }">
<fu-read-write-switch :data="row.description" v-model="row.edit" @change="onChange(row)">
<el-input v-model="row.description" @blur="row.edit = false" />
</fu-read-write-switch>
</template>
</el-table-column>
<el-table-column <el-table-column
prop="createdAt" prop="createdAt"
:label="$t('commons.table.date')" :label="$t('commons.table.date')"
@ -89,6 +94,7 @@
</template> </template>
</LayoutContent> </LayoutContent>
<RecoverStatus ref="recoverStatusRef" @search="search()"></RecoverStatus> <RecoverStatus ref="recoverStatusRef" @search="search()"></RecoverStatus>
<SnapshotImport ref="importRef" @search="search()" />
<el-drawer v-model="drawerVisiable" size="50%"> <el-drawer v-model="drawerVisiable" size="50%">
<template #header> <template #header>
<DrawerHeader :header="$t('setting.createSnapshot')" :back="handleClose" /> <DrawerHeader :header="$t('setting.createSnapshot')" :back="handleClose" />
@ -139,7 +145,7 @@
<script setup lang="ts"> <script setup lang="ts">
import ComplexTable from '@/components/complex-table/index.vue'; import ComplexTable from '@/components/complex-table/index.vue';
import TableSetting from '@/components/table-setting/index.vue'; import TableSetting from '@/components/table-setting/index.vue';
import { snapshotCreate, searchSnapshotPage, snapshotDelete } from '@/api/modules/setting'; import { snapshotCreate, searchSnapshotPage, snapshotDelete, updateSnapshotDescription } from '@/api/modules/setting';
import { onMounted, reactive, ref } from 'vue'; import { onMounted, reactive, ref } from 'vue';
import { dateFormat } from '@/utils/util'; import { dateFormat } from '@/utils/util';
import { useDeleteData } from '@/hooks/use-delete-data'; import { useDeleteData } from '@/hooks/use-delete-data';
@ -150,6 +156,7 @@ import { ElMessage } from 'element-plus';
import { Setting } from '@/api/interface/setting'; import { Setting } from '@/api/interface/setting';
import LayoutContent from '@/layout/layout-content.vue'; import LayoutContent from '@/layout/layout-content.vue';
import RecoverStatus from '@/views/setting/snapshot/status/index.vue'; import RecoverStatus from '@/views/setting/snapshot/status/index.vue';
import SnapshotImport from '@/views/setting/snapshot/import/index.vue';
import { getBackupList } from '@/api/modules/backup'; import { getBackupList } from '@/api/modules/backup';
import { loadBackupName } from '../helper'; import { loadBackupName } from '../helper';
@ -164,6 +171,7 @@ const paginationConfig = reactive({
const searchName = ref(); const searchName = ref();
const recoverStatusRef = ref(); const recoverStatusRef = ref();
const importRef = ref();
const isRecordShow = ref(); const isRecordShow = ref();
const backupOptions = ref(); const backupOptions = ref();
type FormInstance = InstanceType<typeof ElForm>; type FormInstance = InstanceType<typeof ElForm>;
@ -184,10 +192,21 @@ const onCreate = async () => {
drawerVisiable.value = true; drawerVisiable.value = true;
}; };
const onImport = () => {
importRef.value.acceptParams();
};
const handleClose = () => { const handleClose = () => {
drawerVisiable.value = false; drawerVisiable.value = false;
}; };
const onChange = async (info: any) => {
if (!info.edit) {
await updateSnapshotDescription({ id: info.id, description: info.description });
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
}
};
const submitAddSnapshot = (formEl: FormInstance | undefined) => { const submitAddSnapshot = (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
formEl.validate(async (valid) => { formEl.validate(async (valid) => {