diff --git a/.gitignore b/.gitignore index d3c5a3b49..39c52c06a 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/backend/app/api/v1/snapshot.go b/backend/app/api/v1/snapshot.go index 043031ea9..2f9e40b1f 100644 --- a/backend/app/api/v1/snapshot.go +++ b/backend/app/api/v1/snapshot.go @@ -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 diff --git a/backend/app/service/snapshot.go b/backend/app/service/snapshot.go index a2d90a20e..4ff88dc5a 100644 --- a/backend/app/service/snapshot.go +++ b/backend/app/service/snapshot.go @@ -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": "", diff --git a/backend/constant/status.go b/backend/constant/status.go index bac25a059..864be16c3 100644 --- a/backend/constant/status.go +++ b/backend/constant/status.go @@ -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" ) diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index 8addf541d..cc05de2bc 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -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" }, diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index 281472950..935f47004 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -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" }, diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index 70339dbde..787614360 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -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 diff --git a/frontend/components.d.ts b/frontend/components.d.ts index 139847b66..ebd5165b1 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -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'] } } diff --git a/frontend/src/api/modules/setting.ts b/frontend/src/api/modules/setting.ts index 4d70b30be..0c73a165f 100644 --- a/frontend/src/api/modules/setting.ts +++ b/frontend/src/api/modules/setting.ts @@ -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>(`/settings/snapshot/search`, param); }; diff --git a/frontend/src/components/app-layout/index.vue b/frontend/src/components/app-layout/index.vue index ee8db7977..ffe46eb29 100644 --- a/frontend/src/components/app-layout/index.vue +++ b/frontend/src/components/app-layout/index.vue @@ -65,7 +65,7 @@ const loadStatus = async () => { clearInterval(Number(timer)); timer = null; }); - }, 1000 * 5); + }, 1000 * 20); } }; diff --git a/frontend/src/components/table-setting/index.vue b/frontend/src/components/table-setting/index.vue new file mode 100644 index 000000000..303d8ce84 --- /dev/null +++ b/frontend/src/components/table-setting/index.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index adc80f786..aae718731 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -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', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 798baa0bb..2e5e1bff6 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -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: '暂停', diff --git a/frontend/src/views/container/compose/index.vue b/frontend/src/views/container/compose/index.vue index 0b25e7e26..ff886041a 100644 --- a/frontend/src/views/container/compose/index.vue +++ b/frontend/src/views/container/compose/index.vue @@ -19,12 +19,13 @@ >