From 1e9833d7b7705e440924a6417abe0515596dd04b Mon Sep 17 00:00:00 2001 From: zhengkunwang223 Date: Wed, 8 Mar 2023 11:04:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E4=BF=AE=E6=94=B9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/v1/app_install.go | 22 +++ backend/app/dto/app.go | 2 + backend/app/dto/request/app.go | 5 + backend/app/dto/response/app.go | 9 +- backend/app/service/app_install.go | 83 ++++++++--- backend/app/service/app_utils.go | 13 ++ backend/constant/app.go | 1 + backend/router/ro_app.go | 1 + frontend/auto-imports.d.ts | 2 +- frontend/src/api/interface/app.ts | 9 +- frontend/src/api/modules/app.ts | 4 + frontend/src/global/form-rules.ts | 4 +- frontend/src/lang/modules/en.ts | 6 +- frontend/src/lang/modules/zh.ts | 7 +- frontend/src/routers/modules/app-store.ts | 4 +- frontend/src/views/app-store/index.vue | 6 +- .../app-store/installed/detail/index.vue | 138 ++++++++++++++++-- .../src/views/app-store/installed/index.vue | 18 +-- .../installed/{update => upgrade}/index.vue | 6 +- 19 files changed, 285 insertions(+), 55 deletions(-) rename frontend/src/views/app-store/installed/{update => upgrade}/index.vue (97%) diff --git a/backend/app/api/v1/app_install.go b/backend/app/api/v1/app_install.go index 3d84b1f20..a7acd74ee 100644 --- a/backend/app/api/v1/app_install.go +++ b/backend/app/api/v1/app_install.go @@ -283,3 +283,25 @@ func (b *BaseApi) GetParams(c *gin.Context) { } helper.SuccessWithData(c, content) } + +// @Tags App +// @Summary Change app params +// @Description 修改应用参数 +// @Accept json +// @Param request body request.AppInstalledUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /apps/installed/params/update [post] +// @x-panel-log {"bodyKeys":["installId"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"应用参数修改 [installId]","formatEN":"Application param update [installId]"} +func (b *BaseApi) UpdateInstalled(c *gin.Context) { + var req request.AppInstalledUpdate + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := appInstallService.Update(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} diff --git a/backend/app/dto/app.go b/backend/app/dto/app.go index fb77ec31d..f8245e994 100644 --- a/backend/app/dto/app.go +++ b/backend/app/dto/app.go @@ -76,6 +76,8 @@ type AppFormFields struct { Default interface{} `json:"default"` EnvKey string `json:"envKey"` Disabled bool `json:"disabled"` + Edit bool `json:"edit"` + Rule string `json:"rule"` } type AppResource struct { diff --git a/backend/app/dto/request/app.go b/backend/app/dto/request/app.go index 054ee87b3..af0c4eb66 100644 --- a/backend/app/dto/request/app.go +++ b/backend/app/dto/request/app.go @@ -48,6 +48,11 @@ type AppInstalledOperate struct { DeleteDB bool `json:"deleteDB"` } +type AppInstalledUpdate struct { + InstallId uint `json:"installId" validate:"required"` + Params map[string]interface{} `json:"params" validate:"required"` +} + type PortUpdate struct { Key string `json:"key"` Name string `json:"name"` diff --git a/backend/app/dto/response/app.go b/backend/app/dto/response/app.go index b6a92520a..338865dd5 100644 --- a/backend/app/dto/response/app.go +++ b/backend/app/dto/response/app.go @@ -61,6 +61,11 @@ type AppService struct { } type AppParam struct { - Label string `json:"label"` - Value interface{} `json:"value"` + Value interface{} `json:"value"` + Edit bool `json:"edit"` + Key string `json:"key"` + Rule string `json:"rule"` + LabelZh string `json:"labelZh"` + LabelEn string `json:"labelEn"` + Type string `json:"type"` } diff --git a/backend/app/service/app_install.go b/backend/app/service/app_install.go index a15b69ab2..e02372c79 100644 --- a/backend/app/service/app_install.go +++ b/backend/app/service/app_install.go @@ -3,7 +3,9 @@ package service import ( "encoding/json" "fmt" + "github.com/joho/godotenv" "io/ioutil" + "math" "os" "path" "reflect" @@ -156,15 +158,7 @@ func (a AppInstallService) Operate(req request.AppInstalledOperate) error { dockerComposePath := install.GetComposePath() switch req.Operate { case constant.Rebuild: - out, err := compose.Down(dockerComposePath) - if err != nil { - return handleErr(install, err, out) - } - out, err = compose.Up(dockerComposePath) - if err != nil { - return handleErr(install, err, out) - } - return syncById(install.ID) + return rebuildApp(install) case constant.Start: out, err := compose.Start(dockerComposePath) if err != nil { @@ -193,13 +187,59 @@ func (a AppInstallService) Operate(req request.AppInstalledOperate) error { return nil case constant.Sync: return syncById(install.ID) - case constant.Update: + case constant.Upgrade: return updateInstall(install.ID, req.DetailId) default: return errors.New("operate not support") } } +func (a AppInstallService) Update(req request.AppInstalledUpdate) error { + installed, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallId)) + if err != nil { + return err + } + port, ok := req.Params["PANEL_APP_PORT_HTTP"] + if ok { + portN := int(math.Ceil(port.(float64))) + if portN != installed.HttpPort { + httpPort, err := checkPort("PANEL_APP_PORT_HTTP", req.Params) + if err != nil { + return err + } + installed.HttpPort = httpPort + } + } + ports, ok := req.Params["PANEL_APP_PORT_HTTPS"] + if ok { + portN := int(math.Ceil(ports.(float64))) + if portN != installed.HttpsPort { + httpsPort, err := checkPort("PANEL_APP_PORT_HTTPS", req.Params) + if err != nil { + return err + } + installed.HttpsPort = httpsPort + } + } + + envPath := path.Join(installed.GetPath(), ".env") + oldEnvMaps, err := godotenv.Read(envPath) + if err != nil { + return err + } + handleMap(req.Params, oldEnvMaps) + paramByte, err := json.Marshal(oldEnvMaps) + if err != nil { + return err + } + installed.Env = string(paramByte) + if err := godotenv.Write(oldEnvMaps, envPath); err != nil { + return err + } + _ = appInstallRepo.Save(&installed) + return rebuildApp(installed) +} + func (a AppInstallService) SyncAll() error { allList, err := appInstallRepo.ListBy() if err != nil { @@ -391,17 +431,24 @@ func (a AppInstallService) GetParams(id uint) ([]response.AppParam, error) { } for _, form := range appForm.FormFields { if v, ok := envs[form.EnvKey]; ok { + appParam := response.AppParam{ + Edit: false, + Key: form.EnvKey, + Rule: form.Rule, + Type: form.Type, + } + if form.Edit { + appParam.Edit = true + } + appParam.LabelZh = form.LabelZh + appParam.LabelEn = form.LabelEn if form.Type == "service" { appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithServiceName(v.(string))) - res = append(res, response.AppParam{ - Label: form.LabelZh, - Value: appInstall.Name, - }) + appParam.Value = appInstall.Name + res = append(res, appParam) } else { - res = append(res, response.AppParam{ - Label: form.LabelZh, - Value: v, - }) + appParam.Value = v + res = append(res, appParam) } } } diff --git a/backend/app/service/app_utils.go b/backend/app/service/app_utils.go index 34c35c440..0350f651a 100644 --- a/backend/app/service/app_utils.go +++ b/backend/app/service/app_utils.go @@ -345,6 +345,19 @@ func upApp(composeFilePath string, appInstall model.AppInstall) { } } +func rebuildApp(appInstall model.AppInstall) error { + dockerComposePath := appInstall.GetComposePath() + out, err := compose.Down(dockerComposePath) + if err != nil { + return handleErr(appInstall, err, out) + } + out, err = compose.Up(dockerComposePath) + if err != nil { + return handleErr(appInstall, err, out) + } + return syncById(appInstall.ID) +} + func getAppDetails(details []model.AppDetail, versions []string) map[string]model.AppDetail { appDetails := make(map[string]model.AppDetail, len(details)) for _, old := range details { diff --git a/backend/constant/app.go b/backend/constant/app.go index 655b4e3da..526a0afb4 100644 --- a/backend/constant/app.go +++ b/backend/constant/app.go @@ -32,4 +32,5 @@ var ( Restore AppOperate = "restore" Update AppOperate = "update" Rebuild AppOperate = "rebuild" + Upgrade AppOperate = "upgrade" ) diff --git a/backend/router/ro_app.go b/backend/router/ro_app.go index 79144d425..ac9ffa1bc 100644 --- a/backend/router/ro_app.go +++ b/backend/router/ro_app.go @@ -33,5 +33,6 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) { appRouter.GET("/services/:key", baseApi.GetServices) appRouter.GET("/installed/conf/:key", baseApi.GetDefaultConfig) appRouter.GET("/installed/params/:appInstallId", baseApi.GetParams) + appRouter.POST("/installed/params/update", baseApi.UpdateInstalled) } } diff --git a/frontend/auto-imports.d.ts b/frontend/auto-imports.d.ts index 5e073f1fc..cf8e88c6e 100644 --- a/frontend/auto-imports.d.ts +++ b/frontend/auto-imports.d.ts @@ -1,6 +1,6 @@ // Generated by 'unplugin-auto-import' // We suggest you to commit this file into source control declare global { - + const ElMessageBox: typeof import('element-plus/es')['ElMessageBox'] } export {} diff --git a/frontend/src/api/interface/app.ts b/frontend/src/api/interface/app.ts index a140281fd..925e81473 100644 --- a/frontend/src/api/interface/app.ts +++ b/frontend/src/api/interface/app.ts @@ -151,7 +151,12 @@ export namespace App { } export interface InstallParams { - label: string; - value: string; + labelZh: string; + labelEn: string; + value: any; + edit: boolean; + key: string; + rule: string; + type: string; } } diff --git a/frontend/src/api/modules/app.ts b/frontend/src/api/modules/app.ts index caef4ed69..bf40e4d4b 100644 --- a/frontend/src/api/modules/app.ts +++ b/frontend/src/api/modules/app.ts @@ -77,3 +77,7 @@ export const GetAppDefaultConfig = (key: string) => { export const GetAppInstallParams = (id: number) => { return http.get(`apps/installed/params/${id}`); }; + +export const UpdateAppInstallParams = (req: any) => { + return http.post(`apps/installed/params/update`, req); +}; diff --git a/frontend/src/global/form-rules.ts b/frontend/src/global/form-rules.ts index 44ffdaee5..b1cd152b6 100644 --- a/frontend/src/global/form-rules.ts +++ b/frontend/src/global/form-rules.ts @@ -150,7 +150,7 @@ const checkParamCommon = (rule: any, value: any, callback: any) => { if (value === '' || typeof value === 'undefined' || value == null) { callback(new Error(i18n.global.t('commons.rule.paramName'))); } else { - const reg = /^[a-zA-Z0-9]{1}[a-zA-Z0-9._-]{2,30}$/; + const reg = /^[a-zA-Z0-9]{1}[a-zA-Z0-9._-]{1,29}$/; if (!reg.test(value) && value !== '') { callback(new Error(i18n.global.t('commons.rule.paramName'))); } else { @@ -163,7 +163,7 @@ const checkParamComplexity = (rule: any, value: any, callback: any) => { if (value === '' || typeof value === 'undefined' || value == null) { callback(new Error(i18n.global.t('commons.rule.paramComplexity', ['.%@$!&~_']))); } else { - const reg = /^[a-zA-Z0-9]{1}[a-zA-Z0-9.%@$!&~_]{6,30}$/; + const reg = /^[a-zA-Z0-9]{1}[a-zA-Z0-9.%@$!&~_]{5,29}$/; if (!reg.test(value) && value !== '') { callback(new Error(i18n.global.t('commons.rule.paramComplexity', ['.%@$!&~_']))); } else { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index e2ef5ca88..207e88471 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -881,7 +881,7 @@ export default { deleteWarn: 'The delete operation will delete all data and backups together. This operation cannot be rolled back. Do you want to continue? ', syncSuccess: 'Sync successfully', - canUpdate: 'Upgrade', + canUpgrade: 'Upgrade', backup: 'Backup', backupName: 'File Name', backupPath: 'File Path', @@ -889,7 +889,8 @@ export default { restore: 'Restore', restoreWarn: 'The restore operation will restart the application and replace the data. This operation cannot be rolled back. Do you want to continue?', - update: 'upgrade', + update: 'update', + upgrade: 'upgrade', versioneSelect: 'Please select a version', operatorHelper: 'Operation {0} will be performed on the selected application, continue? ', checkInstalledWarn: '{0} is not detected, please enter the app store and click to install!', @@ -919,6 +920,7 @@ export default { syncAppList: 'Sync', updatePrompt: 'The current application is the latest version', installPrompt: 'No apps installed yet', + updateHelper: 'Updating parameters may cause the application to fail to start, please operate with caution', }, website: { website: 'Website', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index ee4998889..879bdf059 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -885,14 +885,15 @@ export default { delete: '删除', deleteWarn: '删除操作会把所有数据和备份一并删除,此操作不可回滚,是否继续?', syncSuccess: '同步成功', - canUpdate: '可升级', + canUpgrade: '可升级', backup: '备份', backupName: '文件名称', backupPath: '文件路径', backupdate: '备份时间', restore: '恢复', restoreWarn: '恢复操作会重启应用,并替换数据,此操作不可回滚,是否继续?', - update: '升级', + update: '更新', + upgrade: '升级', versioneSelect: '请选择版本', operatorHelper: '将对选中应用进行 {0} 操作,是否继续?', checkInstalledWarn: '未检测到 {0} ,请进入应用商店点击安装!', @@ -929,6 +930,8 @@ export default { document: '文档说明', updatePrompt: '当前应用均为最新版本', installPrompt: '尚未安装任何应用', + updateHelper: '更新参数可能导致应用无法启动,请提前备份并谨慎操作', + updateWarn: '更新参数需要重建应用,是否继续?', }, website: { website: '网站', diff --git a/frontend/src/routers/modules/app-store.ts b/frontend/src/routers/modules/app-store.ts index 0e78e50a8..61dc2367c 100644 --- a/frontend/src/routers/modules/app-store.ts +++ b/frontend/src/routers/modules/app-store.ts @@ -51,8 +51,8 @@ const appStoreRouter = { }, }, { - path: 'update', - name: 'AppUpdate', + path: 'upgrade', + name: 'AppUpgrade', component: () => import('@/views/app-store/installed/index.vue'), props: true, hidden: true, diff --git a/frontend/src/views/app-store/index.vue b/frontend/src/views/app-store/index.vue index 3a228ba6d..f93a4b664 100644 --- a/frontend/src/views/app-store/index.vue +++ b/frontend/src/views/app-store/index.vue @@ -27,8 +27,8 @@ const buttons = [ path: '/apps/installed', }, { - label: i18n.global.t('app.canUpdate'), - path: '/apps/update', + label: i18n.global.t('app.canUpgrade'), + path: '/apps/upgrade', count: 0, }, ]; @@ -49,7 +49,7 @@ const search = () => { onMounted(() => { search(); - bus.on('update', () => { + bus.on('upgrade', () => { showButton.value = false; search(); }); diff --git a/frontend/src/views/app-store/installed/detail/index.vue b/frontend/src/views/app-store/installed/detail/index.vue index 835a7b2f1..f71c1fe31 100644 --- a/frontend/src/views/app-store/installed/detail/index.vue +++ b/frontend/src/views/app-store/installed/detail/index.vue @@ -1,20 +1,57 @@ + + diff --git a/frontend/src/views/app-store/installed/index.vue b/frontend/src/views/app-store/installed/index.vue index aeb623180..040fa4c35 100644 --- a/frontend/src/views/app-store/installed/index.vue +++ b/frontend/src/views/app-store/installed/index.vue @@ -50,7 +50,7 @@