mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-27 12:39:01 +08:00
feat: 增加定时刷新组件
This commit is contained in:
parent
1d3738be39
commit
3f3d24648b
4
.gitignore
vendored
4
.gitignore
vendored
@ -5,10 +5,6 @@
|
||||
*.so
|
||||
*.dylib
|
||||
.idea
|
||||
cmd/server/web/assets/
|
||||
cmd/server/web/monacoeditorwork
|
||||
cmd/server/web/favicon.ico
|
||||
cmd/server/web/index.html
|
||||
|
||||
build
|
||||
|
||||
|
@ -38,12 +38,12 @@ func (b *BaseApi) CreateSnapshot(c *gin.Context) {
|
||||
// @Summary Page system snapshot
|
||||
// @Description 获取系统快照列表分页
|
||||
// @Accept json
|
||||
// @Param request body dto.PageInfo true "request"
|
||||
// @Param request body dto.SearchWithPage true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /settings/snapshot/search [post]
|
||||
func (b *BaseApi) SearchSnapshot(c *gin.Context) {
|
||||
var req dto.PageInfo
|
||||
var req dto.SearchWithPage
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
|
@ -25,7 +25,7 @@ type SnapshotService struct {
|
||||
}
|
||||
|
||||
type ISnapshotService interface {
|
||||
SearchWithPage(req dto.PageInfo) (int64, interface{}, error)
|
||||
SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error)
|
||||
SnapshotCreate(req dto.SnapshotCreate) error
|
||||
SnapshotRecover(req dto.SnapshotRecover) error
|
||||
SnapshotRollback(req dto.SnapshotRecover) error
|
||||
@ -38,8 +38,8 @@ func NewISnapshotService() ISnapshotService {
|
||||
return &SnapshotService{}
|
||||
}
|
||||
|
||||
func (u *SnapshotService) SearchWithPage(req dto.PageInfo) (int64, interface{}, error) {
|
||||
total, systemBackups, err := snapshotRepo.Page(req.Page, req.PageSize)
|
||||
func (u *SnapshotService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) {
|
||||
total, systemBackups, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info))
|
||||
var dtoSnap []dto.SnapshotInfo
|
||||
for _, systemBackup := range systemBackups {
|
||||
var item dto.SnapshotInfo
|
||||
@ -90,7 +90,7 @@ func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate) error {
|
||||
Description: req.Description,
|
||||
From: req.From,
|
||||
Version: versionItem.Value,
|
||||
Status: constant.StatusSuccess,
|
||||
Status: constant.StatusWaiting,
|
||||
}
|
||||
_ = snapshotRepo.Create(&snap)
|
||||
_ = settingRepo.Update("SystemStatus", "Snapshoting")
|
||||
@ -134,11 +134,10 @@ func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate) error {
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handlePanelDatas(fileOp, "snapshot", global.CONF.BaseDir+"/1panel", backupPanelDir, localDir, dockerDataDir); err != nil {
|
||||
if err := u.handlePanelDatas(snap.ID, fileOp, "snapshot", global.CONF.BaseDir+"/1panel", backupPanelDir, localDir, dockerDataDir); err != nil {
|
||||
updateSnapshotStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusWaiting})
|
||||
|
||||
snapJson := SnapshotJson{DockerDataDir: dockerDataDir, BackupDataDir: localDir, PanelDataDir: global.CONF.BaseDir + "/1panel", LiveRestoreEnabled: liveRestoreStatus}
|
||||
if err := u.saveJson(snapJson, rootDir); err != nil {
|
||||
@ -154,6 +153,7 @@ func (u *SnapshotService) SnapshotCreate(req dto.SnapshotCreate) error {
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
|
||||
global.LOG.Infof("start to upload snapshot to %s, please wait", backup.Type)
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusUploading})
|
||||
localPath := fmt.Sprintf("%s/system/1panel_snapshot_%s.tar.gz", localDir, timeNow)
|
||||
if ok, err := backupAccont.Upload(localPath, fmt.Sprintf("system_snapshot/1panel_snapshot_%s.tar.gz", timeNow)); err != nil || !ok {
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
|
||||
@ -311,7 +311,7 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
|
||||
}
|
||||
|
||||
if !isReTry || snap.InterruptStep == "1PanelData" {
|
||||
if err := u.handlePanelDatas(fileOp, operation, rootDir, snapJson.PanelDataDir, localDir, snapJson.OldDockerDataDir); err != nil {
|
||||
if err := u.handlePanelDatas(snap.ID, fileOp, operation, rootDir, snapJson.PanelDataDir, localDir, snapJson.OldDockerDataDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, "1PanelData", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
@ -344,89 +344,92 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
|
||||
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")
|
||||
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"rollback_status": constant.StatusWaiting})
|
||||
snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", rootDir))
|
||||
if err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
|
||||
return err
|
||||
}
|
||||
|
||||
_, _ = cmd.Exec("systemctl stop docker")
|
||||
if err := u.handleDockerDatas(fileOp, "rollback", u.OriginalPath, snapJson.OldDockerDataDir); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return err
|
||||
}
|
||||
if snap.InterruptStep == "DockerDir" {
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := u.handleDaemonJson(fileOp, "rollback", u.OriginalPath+"/daemon.json", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return err
|
||||
}
|
||||
if snap.InterruptStep == "DaemonJson" {
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
return nil
|
||||
}
|
||||
if snapJson.LiveRestoreEnabled {
|
||||
if err := u.updateLiveRestore(true); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return err
|
||||
go func() {
|
||||
snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", rootDir))
|
||||
if err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
if snap.InterruptStep == "UpdateLiveRestore" {
|
||||
_, _ = cmd.Exec("systemctl restart dockere")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := u.handlePanelBinary(fileOp, "rollback", u.OriginalPath+"/1panel", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return err
|
||||
}
|
||||
if snap.InterruptStep == "1PanelBinary" {
|
||||
return nil
|
||||
}
|
||||
_, _ = cmd.Exec("systemctl stop docker")
|
||||
if err := u.handleDockerDatas(fileOp, "rollback", u.OriginalPath, snapJson.OldDockerDataDir); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "DockerDir" {
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handlePanelctlBinary(fileOp, "rollback", u.OriginalPath+"/1pctl", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return err
|
||||
}
|
||||
if snap.InterruptStep == "1PctlBinary" {
|
||||
return nil
|
||||
}
|
||||
if err := u.handleDaemonJson(fileOp, "rollback", u.OriginalPath+"/daemon.json", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "DaemonJson" {
|
||||
_, _ = cmd.Exec("systemctl restart docker")
|
||||
return
|
||||
}
|
||||
if snapJson.LiveRestoreEnabled {
|
||||
if err := u.updateLiveRestore(true); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
if snap.InterruptStep == "UpdateLiveRestore" {
|
||||
_, _ = cmd.Exec("systemctl restart dockere")
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handlePanelService(fileOp, "rollback", u.OriginalPath+"/1panel.service", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return err
|
||||
}
|
||||
if snap.InterruptStep == "1PanelService" {
|
||||
if err := u.handlePanelBinary(fileOp, "rollback", u.OriginalPath+"/1panel", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "1PanelBinary" {
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handlePanelctlBinary(fileOp, "rollback", u.OriginalPath+"/1pctl", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "1PctlBinary" {
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handlePanelService(fileOp, "rollback", u.OriginalPath+"/1panel.service", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "1PanelService" {
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handleBackupDatas(fileOp, "rollback", u.OriginalPath, snapJson.OldBackupDataDir); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "1PanelBackups" {
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
return
|
||||
}
|
||||
|
||||
if err := u.handlePanelDatas(snap.ID, fileOp, "rollback", u.OriginalPath, snapJson.OldPanelDataDir, "", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
if snap.InterruptStep == "1PanelData" {
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
return
|
||||
}
|
||||
|
||||
_ = os.RemoveAll(rootDir)
|
||||
global.LOG.Info("rollback successful")
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := u.handleBackupDatas(fileOp, "rollback", u.OriginalPath, snapJson.OldBackupDataDir); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return err
|
||||
}
|
||||
if snap.InterruptStep == "1PanelBackups" {
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := u.handlePanelDatas(fileOp, "rollback", u.OriginalPath, snapJson.OldPanelDataDir, "", ""); err != nil {
|
||||
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
|
||||
return err
|
||||
}
|
||||
if snap.InterruptStep == "1PanelData" {
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
return nil
|
||||
}
|
||||
|
||||
_ = os.RemoveAll(rootDir)
|
||||
global.LOG.Info("rollback successful")
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
updateRollbackStatus(snap.ID, constant.StatusSuccess, "")
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -604,7 +607,7 @@ func (u *SnapshotService) handleBackupDatas(fileOp files.FileOp, operation strin
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SnapshotService) handlePanelDatas(fileOp files.FileOp, operation string, source, target, backupDir, dockerDir string) error {
|
||||
func (u *SnapshotService) handlePanelDatas(snapID uint, fileOp files.FileOp, operation string, source, target, backupDir, dockerDir string) error {
|
||||
switch operation {
|
||||
case "snapshot":
|
||||
exclusionRules := "./tmp;./cache;"
|
||||
@ -614,9 +617,12 @@ func (u *SnapshotService) handlePanelDatas(fileOp files.FileOp, operation string
|
||||
if strings.Contains(dockerDir, source) {
|
||||
exclusionRules += ("." + strings.ReplaceAll(dockerDir, source, "") + ";")
|
||||
}
|
||||
|
||||
_ = snapshotRepo.Update(snapID, map[string]interface{}{"status": constant.StatusSuccess})
|
||||
if err := u.handleTar(source, target, "1panel_data.tar.gz", exclusionRules); err != nil {
|
||||
return fmt.Errorf("backup panel data failed, err: %v", err)
|
||||
}
|
||||
_ = snapshotRepo.Update(snapID, map[string]interface{}{"status": constant.StatusWaiting})
|
||||
case "recover":
|
||||
exclusionRules := "./tmp/;./cache;"
|
||||
if strings.Contains(backupDir, target) {
|
||||
@ -625,9 +631,12 @@ func (u *SnapshotService) handlePanelDatas(fileOp files.FileOp, operation string
|
||||
if strings.Contains(dockerDir, target) {
|
||||
exclusionRules += ("." + strings.ReplaceAll(dockerDir, target, "") + ";")
|
||||
}
|
||||
|
||||
_ = snapshotRepo.Update(snapID, map[string]interface{}{"recover_status": ""})
|
||||
if err := u.handleTar(target, u.OriginalPath, "1panel_data.tar.gz", exclusionRules); err != nil {
|
||||
return fmt.Errorf("restore original panel data failed, err: %v", err)
|
||||
}
|
||||
_ = snapshotRepo.Update(snapID, map[string]interface{}{"recover_status": constant.StatusWaiting})
|
||||
|
||||
if err := u.handleUnTar(source+"/1panel/1panel_data.tar.gz", target); err != nil {
|
||||
return fmt.Errorf("recover panel data failed, err: %v", err)
|
||||
@ -701,6 +710,7 @@ func updateRecoverStatus(id uint, interruptStep, status string, message string)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
}
|
||||
func updateRollbackStatus(id uint, status string, message string) {
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
if status == constant.StatusSuccess {
|
||||
if err := snapshotRepo.Update(id, map[string]interface{}{
|
||||
"recover_status": "",
|
||||
|
@ -1,11 +1,12 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
StatusRunning = "Running"
|
||||
StatusStoped = "Stoped"
|
||||
StatusWaiting = "Waiting"
|
||||
StatusSuccess = "Success"
|
||||
StatusFailed = "Failed"
|
||||
StatusEnable = "Enable"
|
||||
StatusDisable = "Disable"
|
||||
StatusRunning = "Running"
|
||||
StatusStoped = "Stoped"
|
||||
StatusWaiting = "Waiting"
|
||||
StatusSuccess = "Success"
|
||||
StatusFailed = "Failed"
|
||||
StatusUploading = "Uploading"
|
||||
StatusEnable = "Enable"
|
||||
StatusDisable = "Disable"
|
||||
)
|
||||
|
@ -28,26 +28,26 @@ var doc = `{
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {
|
||||
"/apps/:id": {
|
||||
"/apps/:key": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "通过 id 获取应用信息",
|
||||
"description": "通过 key 获取应用信息",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"App"
|
||||
],
|
||||
"summary": "Search app by id",
|
||||
"summary": "Search app by key",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "app id",
|
||||
"name": "id",
|
||||
"type": "string",
|
||||
"description": "app key",
|
||||
"name": "key",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
@ -6489,7 +6489,7 @@ var doc = `{
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.PageInfo"
|
||||
"$ref": "#/definitions/dto.SearchWithPage"
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -10668,6 +10668,9 @@ var doc = `{
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"recommend": {
|
||||
"type": "integer"
|
||||
},
|
||||
"required": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -11105,6 +11108,9 @@ var doc = `{
|
||||
"pageSize": {
|
||||
"type": "integer"
|
||||
},
|
||||
"recommend": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -11985,6 +11991,9 @@ var doc = `{
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"recommend": {
|
||||
"type": "integer"
|
||||
},
|
||||
"required": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -14,26 +14,26 @@
|
||||
"host": "localhost",
|
||||
"basePath": "/api/v1",
|
||||
"paths": {
|
||||
"/apps/:id": {
|
||||
"/apps/:key": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "通过 id 获取应用信息",
|
||||
"description": "通过 key 获取应用信息",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"App"
|
||||
],
|
||||
"summary": "Search app by id",
|
||||
"summary": "Search app by key",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "app id",
|
||||
"name": "id",
|
||||
"type": "string",
|
||||
"description": "app key",
|
||||
"name": "key",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
@ -6475,7 +6475,7 @@
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.PageInfo"
|
||||
"$ref": "#/definitions/dto.SearchWithPage"
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -10654,6 +10654,9 @@
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"recommend": {
|
||||
"type": "integer"
|
||||
},
|
||||
"required": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -11091,6 +11094,9 @@
|
||||
"pageSize": {
|
||||
"type": "integer"
|
||||
},
|
||||
"recommend": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -11971,6 +11977,9 @@
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"recommend": {
|
||||
"type": "integer"
|
||||
},
|
||||
"required": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -1496,6 +1496,8 @@ definitions:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
recommend:
|
||||
type: integer
|
||||
required:
|
||||
type: string
|
||||
shortDesc:
|
||||
@ -1782,6 +1784,8 @@ definitions:
|
||||
type: integer
|
||||
pageSize:
|
||||
type: integer
|
||||
recommend:
|
||||
type: boolean
|
||||
tags:
|
||||
items:
|
||||
type: string
|
||||
@ -2375,6 +2379,8 @@ definitions:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
recommend:
|
||||
type: integer
|
||||
required:
|
||||
type: string
|
||||
shortDesc:
|
||||
@ -2629,17 +2635,17 @@ info:
|
||||
title: 1Panel
|
||||
version: "1.0"
|
||||
paths:
|
||||
/apps/:id:
|
||||
/apps/:key:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 通过 id 获取应用信息
|
||||
description: 通过 key 获取应用信息
|
||||
parameters:
|
||||
- description: app id
|
||||
- description: app key
|
||||
in: path
|
||||
name: id
|
||||
name: key
|
||||
required: true
|
||||
type: integer
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
@ -2647,7 +2653,7 @@ paths:
|
||||
$ref: '#/definitions/response.AppDTO'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Search app by id
|
||||
summary: Search app by key
|
||||
tags:
|
||||
- App
|
||||
/apps/detail/:appId/:version:
|
||||
@ -6736,7 +6742,7 @@ paths:
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.PageInfo'
|
||||
$ref: '#/definitions/dto.SearchWithPage'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
|
2
frontend/components.d.ts
vendored
2
frontend/components.d.ts
vendored
@ -60,7 +60,6 @@ declare module 'vue' {
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSpan: typeof import('element-plus/es')['ElSpan']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
@ -83,6 +82,7 @@ declare module 'vue' {
|
||||
Status: typeof import('./src/components/status/index.vue')['default']
|
||||
SubItem: typeof import('./src/components/app-layout/menu/components/sub-item.vue')['default']
|
||||
SvgIcon: typeof import('./src/components/svg-icon/svg-icon.vue')['default']
|
||||
TableSetting: typeof import('./src/components/table-setting/index.vue')['default']
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import http from '@/api';
|
||||
import { ReqPage, ResPage } from '../interface';
|
||||
import { ResPage, SearchWithPage } from '../interface';
|
||||
import { Setting } from '../interface/setting';
|
||||
|
||||
export const getSettingInfo = () => {
|
||||
@ -62,7 +62,7 @@ export const snapshotRecover = (param: Setting.SnapshotRecover) => {
|
||||
export const snapshotRollback = (param: Setting.SnapshotRecover) => {
|
||||
return http.post(`/settings/snapshot/rollback`, param);
|
||||
};
|
||||
export const searchSnapshotPage = (param: ReqPage) => {
|
||||
export const searchSnapshotPage = (param: SearchWithPage) => {
|
||||
return http.post<ResPage<Setting.SnapshotInfo>>(`/settings/snapshot/search`, param);
|
||||
};
|
||||
|
||||
|
@ -65,7 +65,7 @@ const loadStatus = async () => {
|
||||
clearInterval(Number(timer));
|
||||
timer = null;
|
||||
});
|
||||
}, 1000 * 5);
|
||||
}, 1000 * 20);
|
||||
}
|
||||
};
|
||||
|
||||
|
79
frontend/src/components/table-setting/index.vue
Normal file
79
frontend/src/components/table-setting/index.vue
Normal file
@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-popover placement="bottom-start" :width="200" trigger="click">
|
||||
<template #reference>
|
||||
<el-button round class="timer-button">{{ $t('commons.table.tableSetting') }}</el-button>
|
||||
</template>
|
||||
<div style="margin-left: 15px">
|
||||
<div>
|
||||
<span>{{ $t('commons.table.autoRefresh') }}</span>
|
||||
<el-switch style="margin-left: 5px" v-model="autoRefresh" @change="changeRefresh"></el-switch>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ $t('commons.table.refreshRate') }}</span>
|
||||
<el-select style="margin-left: 5px; width: 80px" v-model="refreshRate" @change="changeRefresh">
|
||||
<el-option label="5s" :value="5"></el-option>
|
||||
<el-option label="10s" :value="10"></el-option>
|
||||
<el-option label="30s" :value="30"></el-option>
|
||||
<el-option label="1min" :value="60"></el-option>
|
||||
<el-option label="2min" :value="120"></el-option>
|
||||
<el-option label="5min" :value="300"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onUnmounted, ref } from 'vue';
|
||||
defineOptions({ name: 'TableSetting' });
|
||||
|
||||
const autoRefresh = ref<boolean>(false);
|
||||
const refreshRate = ref<number>(10);
|
||||
const emit = defineEmits(['search']);
|
||||
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
|
||||
const changeRefresh = () => {
|
||||
if (autoRefresh.value) {
|
||||
if (timer) {
|
||||
clearInterval(Number(timer));
|
||||
timer = null;
|
||||
}
|
||||
timer = setInterval(() => {
|
||||
emit('search');
|
||||
}, 1000 * refreshRate.value);
|
||||
} else {
|
||||
if (timer) {
|
||||
clearInterval(Number(timer));
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const startTimer = () => {
|
||||
autoRefresh.value = true;
|
||||
changeRefresh();
|
||||
};
|
||||
const endTimer = () => {
|
||||
autoRefresh.value = false;
|
||||
changeRefresh();
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(Number(timer));
|
||||
timer = null;
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
startTimer,
|
||||
endTimer,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.timer-button {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
@ -61,12 +61,16 @@ export default {
|
||||
description: 'Description',
|
||||
interval: 'Interval',
|
||||
title: 'Title',
|
||||
tableSetting: 'Table setting',
|
||||
autoRefresh: 'Auto refresh',
|
||||
refreshRate: 'Refresh rate',
|
||||
},
|
||||
loadingText: {
|
||||
Upgrading: 'System upgrade, please wait...',
|
||||
Restarting: 'System restart, please wait...',
|
||||
Snapshoting: 'Making snapshots, please wait...',
|
||||
Recovering: 'Recovering from snapshot, please wait...',
|
||||
Rollbacking: 'Rollbacking from snapshot, please wait...',
|
||||
},
|
||||
msg: {
|
||||
delete: 'This operation cannot be rolled back. Do you want to continue',
|
||||
@ -155,6 +159,7 @@ export default {
|
||||
error: 'error',
|
||||
created: 'created',
|
||||
restarting: 'restarting',
|
||||
uploading: 'uploading',
|
||||
removing: 'removing',
|
||||
paused: 'paused',
|
||||
exited: 'exited',
|
||||
|
@ -64,12 +64,16 @@ export default {
|
||||
description: '描述信息',
|
||||
interval: '耗时',
|
||||
title: '标题',
|
||||
tableSetting: '列表设置',
|
||||
autoRefresh: '定时刷新',
|
||||
refreshRate: '刷新频率',
|
||||
},
|
||||
loadingText: {
|
||||
Upgrading: '系统升级中,请稍候...',
|
||||
Restarting: '系统重启中,请稍候...',
|
||||
Snapshoting: '制作快照中,请稍候...',
|
||||
Recovering: '从快照恢复中,请稍候...',
|
||||
Rollbacking: '快照回滚中,请稍候...',
|
||||
},
|
||||
msg: {
|
||||
delete: '删除 操作不可回滚,是否继续',
|
||||
@ -165,6 +169,7 @@ export default {
|
||||
error: '失败',
|
||||
created: '已创建',
|
||||
restarting: '重启中',
|
||||
uploading: '上传中',
|
||||
unhealthy: '异常',
|
||||
removing: '迁移中',
|
||||
paused: '暂停',
|
||||
|
@ -19,12 +19,13 @@
|
||||
>
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="20">
|
||||
<el-col :span="16">
|
||||
<el-button type="primary" @click="onOpenDialog()">
|
||||
{{ $t('container.createCompose') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-col :span="8">
|
||||
<TableSetting @search="search()" />
|
||||
<div class="search-button">
|
||||
<el-input
|
||||
v-model="searchName"
|
||||
@ -88,6 +89,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import { reactive, onMounted, ref } from 'vue';
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import EditDialog from '@/views/container/compose/edit/index.vue';
|
||||
|
@ -14,7 +14,7 @@
|
||||
>
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="20">
|
||||
<el-col :span="16">
|
||||
<el-button type="primary" @click="onCreate()">
|
||||
{{ $t('container.createContainer') }}
|
||||
</el-button>
|
||||
@ -42,7 +42,8 @@
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-col :span="8">
|
||||
<TableSetting @search="search()" />
|
||||
<div class="search-button">
|
||||
<el-input
|
||||
v-model="searchName"
|
||||
@ -118,6 +119,7 @@
|
||||
<script lang="ts" setup>
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import ReNameDialog from '@/views/container/container/rename/index.vue';
|
||||
import CreateDialog from '@/views/container/container/create/index.vue';
|
||||
import MonitorDialog from '@/views/container/container/monitor/index.vue';
|
||||
|
@ -47,6 +47,7 @@ interface DialogProps {
|
||||
tags: Array<string>;
|
||||
}
|
||||
const acceptParams = (params: DialogProps) => {
|
||||
isByID.value = false;
|
||||
deleteVisiable.value = true;
|
||||
deleteForm.id = params.id.replaceAll('sha256:', '').substring(0, 12);
|
||||
deleteForm.deleteTags = [];
|
||||
|
@ -11,7 +11,7 @@
|
||||
<LayoutContent v-loading="loading" :title="$t('container.image')" :class="{ mask: dockerStatus != 'Running' }">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="20">
|
||||
<el-col :span="16">
|
||||
<el-button type="primary" plain @click="onOpenPull">
|
||||
{{ $t('container.imagePull') }}
|
||||
</el-button>
|
||||
@ -22,7 +22,8 @@
|
||||
{{ $t('container.build') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-col :span="8">
|
||||
<TableSetting @search="search()" />
|
||||
<div class="search-button">
|
||||
<el-input
|
||||
v-model="searchName"
|
||||
@ -75,6 +76,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import { reactive, onMounted, ref } from 'vue';
|
||||
import { dateFormatSimple } from '@/utils/util';
|
||||
import { Container } from '@/api/interface/container';
|
||||
|
@ -15,7 +15,7 @@
|
||||
>
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="20">
|
||||
<el-col :span="16">
|
||||
<el-button type="primary" @click="onCreate()">
|
||||
{{ $t('container.createNetwork') }}
|
||||
</el-button>
|
||||
@ -23,7 +23,8 @@
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-col :span="8">
|
||||
<TableSetting @search="search()" />
|
||||
<div class="search-button">
|
||||
<el-input
|
||||
v-model="searchName"
|
||||
@ -99,6 +100,7 @@
|
||||
<script lang="ts" setup>
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import CreateDialog from '@/views/container/network/create/index.vue';
|
||||
import CodemirrorDialog from '@/components/codemirror-dialog/codemirror.vue';
|
||||
import { reactive, onMounted, ref } from 'vue';
|
||||
|
@ -11,7 +11,7 @@
|
||||
<LayoutContent v-loading="loading" :title="$t('container.repo')" :class="{ mask: dockerStatus != 'Running' }">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="20">
|
||||
<el-col :span="16">
|
||||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
{{ $t('container.createRepo') }}
|
||||
</el-button>
|
||||
@ -19,7 +19,8 @@
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-col :span="8">
|
||||
<TableSetting @search="search()" />
|
||||
<div class="search-button">
|
||||
<el-input
|
||||
v-model="searchName"
|
||||
@ -78,6 +79,7 @@
|
||||
<script lang="ts" setup>
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import OperatorDialog from '@/views/container/repo/operator/index.vue';
|
||||
import DeleteDialog from '@/views/container/repo/delete/index.vue';
|
||||
import { reactive, onMounted, ref } from 'vue';
|
||||
|
@ -15,7 +15,7 @@
|
||||
>
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="20">
|
||||
<el-col :span="16">
|
||||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
{{ $t('container.createComposeTemplate') }}
|
||||
</el-button>
|
||||
@ -23,7 +23,8 @@
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-col :span="8">
|
||||
<TableSetting @search="search()" />
|
||||
<div class="search-button">
|
||||
<el-input
|
||||
v-model="searchName"
|
||||
@ -102,6 +103,7 @@
|
||||
<script lang="ts" setup>
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import { Codemirror } from 'vue-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
|
@ -11,7 +11,7 @@
|
||||
<LayoutContent v-loading="loading" :title="$t('container.volume')" :class="{ mask: dockerStatus != 'Running' }">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="20">
|
||||
<el-col :span="16">
|
||||
<el-button type="primary" @click="onCreate()">
|
||||
{{ $t('container.createVolume') }}
|
||||
</el-button>
|
||||
@ -19,7 +19,8 @@
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-col :span="8">
|
||||
<TableSetting @search="search()" />
|
||||
<div class="search-button">
|
||||
<el-input
|
||||
v-model="searchName"
|
||||
@ -84,6 +85,7 @@
|
||||
<script lang="ts" setup>
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import CreateDialog from '@/views/container/volume/create/index.vue';
|
||||
import CodemirrorDialog from '@/components/codemirror-dialog/codemirror.vue';
|
||||
import { reactive, onMounted, ref } from 'vue';
|
||||
|
@ -11,7 +11,7 @@
|
||||
<LayoutContent v-loading="loading" v-if="!isRecordShow" :title="$t('cronjob.cronTask')">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="20">
|
||||
<el-col :span="16">
|
||||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
{{ $t('commons.button.create') }}{{ $t('cronjob.cronTask') }}
|
||||
</el-button>
|
||||
@ -19,7 +19,8 @@
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-col :span="8">
|
||||
<TableSetting @search="search()" />
|
||||
<div class="search-button">
|
||||
<el-input
|
||||
v-model="searchName"
|
||||
@ -124,6 +125,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import OperatrDialog from '@/views/cronjob/operate/index.vue';
|
||||
import Records from '@/views/cronjob/record/index.vue';
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
|
@ -3,12 +3,13 @@
|
||||
<LayoutContent v-loading="loading" :title="$t('logs.login')">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="20">
|
||||
<el-col :span="16">
|
||||
<el-button type="primary" plain @click="onClean()">
|
||||
{{ $t('logs.deleteLogs') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-col :span="8">
|
||||
<TableSetting @search="search()" />
|
||||
<div class="search-button">
|
||||
<el-input
|
||||
v-model="searchIP"
|
||||
@ -64,6 +65,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
|
@ -3,12 +3,13 @@
|
||||
<LayoutContent v-loading="loading" :title="$t('logs.operation')">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="20">
|
||||
<el-col :span="16">
|
||||
<el-button type="primary" plain @click="onClean()">
|
||||
{{ $t('logs.deleteLogs') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-col :span="8">
|
||||
<TableSetting @search="search()" />
|
||||
<div class="search-button">
|
||||
<el-input
|
||||
v-model="searchName"
|
||||
@ -91,6 +92,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
|
@ -2,12 +2,30 @@
|
||||
<div>
|
||||
<LayoutContent v-loading="loading" v-if="!isRecordShow" :title="$t('setting.snapshot')">
|
||||
<template #toolbar>
|
||||
<el-button type="primary" @click="onCreate()">
|
||||
{{ $t('setting.createSnapshot') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain :disabled="selects.length === 0" @click="batchDelete(null)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<el-button type="primary" @click="onCreate()">
|
||||
{{ $t('setting.createSnapshot') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain :disabled="selects.length === 0" @click="batchDelete(null)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<TableSetting ref="timerRef" @search="search()" />
|
||||
<div class="search-button">
|
||||
<el-input
|
||||
v-model="searchName"
|
||||
clearable
|
||||
@clear="search()"
|
||||
suffix-icon="Search"
|
||||
@keyup.enter="search()"
|
||||
@blur="search()"
|
||||
:placeholder="$t('commons.button.search')"
|
||||
></el-input>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable
|
||||
@ -40,6 +58,9 @@
|
||||
<el-tag v-if="row.status === 'Waiting'" type="info">
|
||||
{{ $t('commons.table.statusWaiting') }}
|
||||
</el-tag>
|
||||
<el-tag v-if="row.status === 'Uploading'" type="info">
|
||||
{{ $t('commons.status.uploading') }}...
|
||||
</el-tag>
|
||||
<el-tooltip
|
||||
v-if="row.status === 'Failed'"
|
||||
class="box-item"
|
||||
@ -67,7 +88,7 @@
|
||||
</ComplexTable>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
<RecoverStatus @search="search()" ref="recoverStatusRef"></RecoverStatus>
|
||||
<RecoverStatus ref="recoverStatusRef" @search="search()"></RecoverStatus>
|
||||
<el-drawer v-model="drawerVisiable" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('setting.createSnapshot')" :back="handleClose" />
|
||||
@ -117,6 +138,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import { snapshotCreate, searchSnapshotPage, snapshotDelete } from '@/api/modules/setting';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
@ -139,6 +161,7 @@ const paginationConfig = reactive({
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
const searchName = ref();
|
||||
|
||||
const recoverStatusRef = ref();
|
||||
const isRecordShow = ref();
|
||||
@ -231,6 +254,7 @@ const buttons = [
|
||||
|
||||
const search = async () => {
|
||||
let params = {
|
||||
info: searchName.value,
|
||||
page: paginationConfig.currentPage,
|
||||
pageSize: paginationConfig.pageSize,
|
||||
};
|
||||
|
@ -198,7 +198,7 @@ const acceptParams = (params: DialogProps): void => {
|
||||
snapInfo.value = params.snapInfo;
|
||||
drawerVisiable.value = true;
|
||||
};
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
const emit = defineEmits(['search']);
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisiable.value = false;
|
||||
@ -211,6 +211,7 @@ const doRecover = async (isNew: boolean) => {
|
||||
emit('search');
|
||||
loading.value = false;
|
||||
dialogVisiable.value = false;
|
||||
drawerVisiable.value = false;
|
||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.catch(() => {
|
||||
@ -240,6 +241,7 @@ const rollbackSnapshot = async () => {
|
||||
emit('search');
|
||||
loading.value = false;
|
||||
dialogVisiable.value = false;
|
||||
drawerVisiable.value = false;
|
||||
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.catch(() => {
|
||||
|
Loading…
Reference in New Issue
Block a user