From 39b8de7adaca9639ceb7839cad538bc2829ecaef Mon Sep 17 00:00:00 2001 From: ssongliu <73214554+ssongliu@users.noreply.github.com> Date: Mon, 4 Mar 2024 22:59:11 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=99=90=E5=88=B6=E5=BF=AB=E7=85=A7?= =?UTF-8?q?=E8=B7=A8=E6=9E=B6=E6=9E=84=E6=81=A2=E5=A4=8D=20(#4064)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs #4056 --- backend/app/api/v1/dashboard.go | 16 +++++++ backend/app/dto/dashboard.go | 8 ++++ backend/app/service/dashboard.go | 22 +++++++++ backend/app/service/snapshot.go | 29 +++++++++++- cmd/server/docs/docs.go | 46 ++++++++++++++++++- cmd/server/docs/swagger.json | 46 ++++++++++++++++++- cmd/server/docs/swagger.yaml | 29 +++++++++++- frontend/src/api/interface/dashboard.ts | 7 +++ frontend/src/api/modules/dashboard.ts | 4 ++ frontend/src/lang/modules/en.ts | 5 +- frontend/src/lang/modules/tw.ts | 7 +-- frontend/src/lang/modules/zh.ts | 7 +-- .../views/setting/snapshot/status/index.vue | 25 +++++++++- 13 files changed, 232 insertions(+), 19 deletions(-) diff --git a/backend/app/api/v1/dashboard.go b/backend/app/api/v1/dashboard.go index 61fa05047..0ada8bdb8 100644 --- a/backend/app/api/v1/dashboard.go +++ b/backend/app/api/v1/dashboard.go @@ -8,6 +8,22 @@ import ( "github.com/gin-gonic/gin" ) +// @Tags Dashboard +// @Summary Load os info +// @Description 获取服务器基础数据 +// @Accept json +// @Success 200 {object} dto.OsInfo +// @Security ApiKeyAuth +// @Router /dashboard/base/os [get] +func (b *BaseApi) LoadDashboardOsInfo(c *gin.Context) { + data, err := dashboardService.LoadOsInfo() + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, data) +} + // @Tags Dashboard // @Summary Load dashboard base info // @Description 获取首页基础数据 diff --git a/backend/app/dto/dashboard.go b/backend/app/dto/dashboard.go index 1d2f46efe..85fd9a8f7 100644 --- a/backend/app/dto/dashboard.go +++ b/backend/app/dto/dashboard.go @@ -24,6 +24,14 @@ type DashboardBase struct { CurrentInfo DashboardCurrent `json:"currentInfo"` } +type OsInfo struct { + OS string `json:"os"` + Platform string `json:"platform"` + PlatformFamily string `json:"platformFamily"` + KernelArch string `json:"kernelArch"` + KernelVersion string `json:"kernelVersion"` +} + type DashboardCurrent struct { Uptime uint64 `json:"uptime"` TimeSinceUptime string `json:"timeSinceUptime"` diff --git a/backend/app/service/dashboard.go b/backend/app/service/dashboard.go index 47a99b520..b4407d71d 100644 --- a/backend/app/service/dashboard.go +++ b/backend/app/service/dashboard.go @@ -22,6 +22,7 @@ import ( type DashboardService struct{} type IDashboardService interface { + LoadOsInfo() (*dto.OsInfo, error) LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error) LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent @@ -49,6 +50,27 @@ func (u *DashboardService) Restart(operation string) error { return nil } +func (u *DashboardService) LoadOsInfo() (*dto.OsInfo, error) { + var baseInfo dto.OsInfo + hostInfo, err := host.Info() + if err != nil { + return nil, err + } + baseInfo.OS = hostInfo.OS + baseInfo.Platform = hostInfo.Platform + baseInfo.PlatformFamily = hostInfo.PlatformFamily + baseInfo.KernelArch = hostInfo.KernelArch + baseInfo.KernelVersion = hostInfo.KernelVersion + + if baseInfo.KernelArch == "armv7l" { + baseInfo.KernelArch = "armv7" + } + if baseInfo.KernelArch == "x86_64" { + baseInfo.KernelArch = "amd64" + } + return &baseInfo, nil +} + func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error) { var baseInfo dto.DashboardBase hostInfo, err := host.Info() diff --git a/backend/app/service/snapshot.go b/backend/app/service/snapshot.go index c81bbd3e0..5b5a21f05 100644 --- a/backend/app/service/snapshot.go +++ b/backend/app/service/snapshot.go @@ -19,6 +19,7 @@ import ( "github.com/1Panel-dev/1Panel/backend/utils/files" "github.com/jinzhu/copier" "github.com/pkg/errors" + "github.com/shirou/gopsutil/v3/host" ) type SnapshotService struct { @@ -134,6 +135,9 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error { if err != nil { return err } + if hasOs(snap.Name) && !strings.Contains(snap.Name, loadOs()) { + return fmt.Errorf("Restoring snapshots(%s) between different server architectures(%s) is not supported.", snap.Name, loadOs()) + } if !req.IsNew && len(snap.InterruptStep) != 0 && len(snap.RollbackStatus) != 0 { return fmt.Errorf("the snapshot has been rolled back and cannot be restored again") } @@ -189,9 +193,10 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, logPath string, req dto if req.ID == 0 { versionItem, _ := settingRepo.Get(settingRepo.WithByKey("SystemVersion")) - name := fmt.Sprintf("1panel_%s_%s", versionItem.Value, timeNow) + + name := fmt.Sprintf("1panel_%s_%s_%s", versionItem.Value, loadOs(), timeNow) if isCronjob { - name = fmt.Sprintf("snapshot_1panel_%s_%s", versionItem.Value, timeNow) + name = fmt.Sprintf("snapshot_1panel_%s_%s_%s", versionItem.Value, loadOs(), timeNow) } rootDir = path.Join(localDir, "system", name) @@ -481,3 +486,23 @@ func loadLogByStatus(status model.SnapshotStatus, logPath string) { defer file.Close() _, _ = file.Write([]byte(logs)) } + +func hasOs(name string) bool { + return strings.Contains(name, "amd64") || + strings.Contains(name, "arm64") || + strings.Contains(name, "armv7") || + strings.Contains(name, "ppc64le") || + strings.Contains(name, "s390x") +} + +func loadOs() string { + hostInfo, _ := host.Info() + switch hostInfo.KernelArch { + case "x86_64": + return "amd64" + case "armv7l": + return "armv7" + default: + return hostInfo.KernelArch + } +} diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index f640ab313..6b518da0e 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -3830,6 +3830,31 @@ const docTemplate = `{ } } }, + "/dashboard/base/os": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取服务器基础数据", + "consumes": [ + "application/json" + ], + "tags": [ + "Dashboard" + ], + "summary": "Load os info", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.OsInfo" + } + } + } + } + }, "/dashboard/current/:ioOption/:netOption": { "get": { "security": [ @@ -16828,6 +16853,26 @@ const docTemplate = `{ } } }, + "dto.OsInfo": { + "type": "object", + "properties": { + "kernelArch": { + "type": "string" + }, + "kernelVersion": { + "type": "string" + }, + "os": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "platformFamily": { + "type": "string" + } + } + }, "dto.PageContainer": { "type": "object", "required": [ @@ -18967,7 +19012,6 @@ const docTemplate = `{ "request.FileEdit": { "type": "object", "required": [ - "content", "path" ], "properties": { diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index 89d2e7c47..552c206ab 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -3823,6 +3823,31 @@ } } }, + "/dashboard/base/os": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取服务器基础数据", + "consumes": [ + "application/json" + ], + "tags": [ + "Dashboard" + ], + "summary": "Load os info", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.OsInfo" + } + } + } + } + }, "/dashboard/current/:ioOption/:netOption": { "get": { "security": [ @@ -16821,6 +16846,26 @@ } } }, + "dto.OsInfo": { + "type": "object", + "properties": { + "kernelArch": { + "type": "string" + }, + "kernelVersion": { + "type": "string" + }, + "os": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "platformFamily": { + "type": "string" + } + } + }, "dto.PageContainer": { "type": "object", "required": [ @@ -18960,7 +19005,6 @@ "request.FileEdit": { "type": "object", "required": [ - "content", "path" ], "properties": { diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index da9fb4e92..d611baf6c 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -1887,6 +1887,19 @@ definitions: option: type: string type: object + dto.OsInfo: + properties: + kernelArch: + type: string + kernelVersion: + type: string + os: + type: string + platform: + type: string + platformFamily: + type: string + type: object dto.PageContainer: properties: excludeAppStore: @@ -3319,7 +3332,6 @@ definitions: path: type: string required: - - content - path type: object request.FileMove: @@ -7412,6 +7424,21 @@ paths: summary: Load dashboard base info tags: - Dashboard + /dashboard/base/os: + get: + consumes: + - application/json + description: 获取服务器基础数据 + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.OsInfo' + security: + - ApiKeyAuth: [] + summary: Load os info + tags: + - Dashboard /dashboard/current/:ioOption/:netOption: get: consumes: diff --git a/frontend/src/api/interface/dashboard.ts b/frontend/src/api/interface/dashboard.ts index 77ef5515a..930addb71 100644 --- a/frontend/src/api/interface/dashboard.ts +++ b/frontend/src/api/interface/dashboard.ts @@ -1,4 +1,11 @@ export namespace Dashboard { + export interface OsInfo { + os: string; + platform: string; + platformFamily: string; + kernelArch: string; + kernelVersion: string; + } export interface BaseInfo { websiteNumber: number; databaseNumber: number; diff --git a/frontend/src/api/modules/dashboard.ts b/frontend/src/api/modules/dashboard.ts index 07624b0ff..59697cd44 100644 --- a/frontend/src/api/modules/dashboard.ts +++ b/frontend/src/api/modules/dashboard.ts @@ -1,6 +1,10 @@ import http from '@/api'; import { Dashboard } from '../interface/dashboard'; +export const loadOsInfo = () => { + return http.get(`/dashboard/base/os`); +}; + export const loadBaseInfo = (ioOption: string, netOption: string) => { return http.get(`/dashboard/base/${ioOption}/${netOption}`); }; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index a3fb67d9f..36af3f917 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1405,14 +1405,15 @@ const message = { createSnapshot: 'Create snapshot', importSnapshot: 'Sync snapshot', recover: 'Recover', - noRecoverRecord: 'No recovery record has been recorded', lastRecoverAt: 'Last recovery time', lastRollbackAt: 'Last rollback time', - noRollbackRecord: 'No rollback record has been recorded', reDownload: 'Download the backup file again', recoverRecord: 'Recover record', recoverHelper: 'The recovery is about to start from snapshot {0}, and the recovery needs to restart docker and 1panel service, do you want to continue?', + recoverHelper1: + 'Will start restoring from snapshot {0}, please ensure that the server architecture matches the one where the snapshot was created.', + recoverHelper2: 'Restoring snapshots between different server architectures is not supported.', rollback: 'Rollback', rollbackHelper: 'This recovery is about to be rolled back, which will replace all the files recovered this time. In the process, docker and 1panel services may need to be restarted. Do you want to continue?', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 15e42da81..2010bfcf2 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -1246,17 +1246,14 @@ const message = { createSnapshot: '創建快照', importSnapshot: '同步快照', recover: '恢復', - noRecoverRecord: '暫無恢復記錄', lastRecoverAt: '上次恢復時間', lastRollbackAt: '上次回滾時間', - noRollbackRecord: '暫無回滾記錄', reDownload: '重新下載備份文件', - statusAll: '全部', statusSuccess: '成功', statusFailed: '失敗', - versionChange: '版本變化', - snapshotFrom: '快照存儲位置', recoverHelper: '即將從快照 {0} 開始恢復,恢復需要重啟 docker 以及 1panel 服務,是否繼續?', + recoverHelper1: '即將從快照 {0} 開始恢復,請確保伺服器架構與創建快照伺服器架構信息保持一致。', + recoverHelper2: '不支持在不同伺服器架構之間進行快照恢復操作。', rollback: '回滾', rollbackHelper: '即將回滾本次恢復,回滾將替換所有本次恢復的文件,過程中可能需要重啟 docker 以及 1panel 服務,是否繼續?', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 9cbb3f4b3..edb51acd5 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1247,17 +1247,14 @@ const message = { createSnapshot: '创建快照', importSnapshot: '同步快照', recover: '恢复', - noRecoverRecord: '暂无恢复记录', lastRecoverAt: '上次恢复时间', lastRollbackAt: '上次回滚时间', - noRollbackRecord: '暂无回滚记录', reDownload: '重新下载备份文件', - statusAll: '全部', statusSuccess: '成功', statusFailed: '失败', - versionChange: '版本变化', - snapshotFrom: '快照存储位置', recoverHelper: '即将从快照 {0} 开始恢复,恢复需要重启 docker 以及 1panel 服务,是否继续?', + recoverHelper1: '即将从快照 {0} 开始恢复,请确保服务器架构与创建快照服务器架构信息保持一致。', + recoverHelper2: '不支持在不同服务器架构之间进行快照恢复操作。', rollback: '回滚', rollbackHelper: '即将回滚本次恢复,回滚将替换所有本次恢复的文件,过程中可能需要重启 docker 以及 1panel 服务,是否继续?', diff --git a/frontend/src/views/setting/snapshot/status/index.vue b/frontend/src/views/setting/snapshot/status/index.vue index bcf85262f..30b29a7ec 100644 --- a/frontend/src/views/setting/snapshot/status/index.vue +++ b/frontend/src/views/setting/snapshot/status/index.vue @@ -172,7 +172,8 @@ import { ElMessageBox } from 'element-plus'; import i18n from '@/lang'; import DrawerHeader from '@/components/drawer-header/index.vue'; import { snapshotRecover, snapshotRollback } from '@/api/modules/setting'; -import { MsgSuccess } from '@/utils/message'; +import { MsgError, MsgSuccess } from '@/utils/message'; +import { loadOsInfo } from '@/api/modules/dashboard'; const drawerVisible = ref(false); const snapInfo = ref(); @@ -210,7 +211,27 @@ const doRecover = async (isNew: boolean) => { }; const recoverSnapshot = async (isNew: boolean) => { - ElMessageBox.confirm(i18n.global.t('setting.recoverHelper', [snapInfo.value.name]), { + let msg = i18n.global.t('setting.recoverHelper', [snapInfo.value.name]); + if ( + snapInfo.value.name.indexOf('amd64') === -1 && + snapInfo.value.name.indexOf('arm64') === -1 && + snapInfo.value.name.indexOf('armv7') === -1 && + snapInfo.value.name.indexOf('ppc64le') === -1 && + snapInfo.value.name.indexOf('s390x') === -1 + ) { + msg = i18n.global.t('setting.recoverHelper1', [snapInfo.value.name]); + } else { + const res = await loadOsInfo(); + let osVal = res.data.kernelArch; + if (osVal === '') { + msg = i18n.global.t('setting.recoverHelper1', [snapInfo.value.name]); + } else if (snapInfo.value.name.indexOf(osVal) === -1) { + MsgError(i18n.global.t('setting.recoverHelper2')); + return; + } + } + + ElMessageBox.confirm(msg, i18n.global.t('commons.button.recover'), { confirmButtonText: i18n.global.t('commons.button.confirm'), cancelButtonText: i18n.global.t('commons.button.cancel'), type: 'info',