diff --git a/backend/app/api/v1/runtime.go b/backend/app/api/v1/runtime.go index d5af2a1b2..3bafdf5af 100644 --- a/backend/app/api/v1/runtime.go +++ b/backend/app/api/v1/runtime.go @@ -5,6 +5,7 @@ import ( "github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto/request" "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/1Panel-dev/1Panel/backend/global" "github.com/gin-gonic/gin" ) @@ -188,3 +189,29 @@ func (b *BaseApi) GetNodeModules(c *gin.Context) { } helper.SuccessWithData(c, res) } + +// @Tags Runtime +// @Summary Operate Node modules +// @Description 操作 Node 项目 modules +// @Accept json +// @Param request body request.NodeModuleReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /runtimes/node/modules/operate [post] +func (b *BaseApi) OperateNodeModules(c *gin.Context) { + var req request.NodeModuleReq + 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 + } + err := runtimeService.OperateNodeModules(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithOutData(c) +} diff --git a/backend/app/dto/request/runtime.go b/backend/app/dto/request/runtime.go index 16aa5b3e7..e171b70c1 100644 --- a/backend/app/dto/request/runtime.go +++ b/backend/app/dto/request/runtime.go @@ -55,7 +55,8 @@ type RuntimeOperate struct { } type NodeModuleReq struct { - Operate string `json:"operate"` - ID uint `json:"ID"` - Module string `json:"module"` + Operate string `json:"operate" validate:"oneof=install uninstall update"` + ID uint `json:"ID" validate:"required"` + Module string `json:"module"` + PkgManager string `json:"pkgManager" validate:"oneof=npm yarn"` } diff --git a/backend/app/service/runtime.go b/backend/app/service/runtime.go index 239d00fe1..d8e963f69 100644 --- a/backend/app/service/runtime.go +++ b/backend/app/service/runtime.go @@ -11,8 +11,10 @@ import ( "github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/global" + cmd2 "github.com/1Panel-dev/1Panel/backend/utils/cmd" "github.com/1Panel-dev/1Panel/backend/utils/compose" "github.com/1Panel-dev/1Panel/backend/utils/docker" + "github.com/1Panel-dev/1Panel/backend/utils/env" "github.com/1Panel-dev/1Panel/backend/utils/files" "github.com/pkg/errors" "github.com/subosito/gotenv" @@ -20,6 +22,7 @@ import ( "path" "strconv" "strings" + "time" ) type RuntimeService struct { @@ -34,6 +37,7 @@ type IRuntimeService interface { GetNodePackageRunScript(req request.NodePackageReq) ([]response.PackageScripts, error) OperateRuntime(req request.RuntimeOperate) error GetNodeModules(req request.NodeModuleReq) ([]response.NodeModule, error) + OperateNodeModules(req request.NodeModuleReq) error } func NewRuntimeService() IRuntimeService { @@ -477,3 +481,33 @@ func (r *RuntimeService) GetNodeModules(req request.NodeModuleReq) ([]response.N } return res, nil } + +func (r *RuntimeService) OperateNodeModules(req request.NodeModuleReq) error { + runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(req.ID)) + if err != nil { + return err + } + containerName, err := env.GetEnvValueByKey(runtime.GetEnvPath(), "CONTAINER_NAME") + if err != nil { + return err + } + cmd := req.PkgManager + switch req.Operate { + case constant.RuntimeInstall: + if req.PkgManager == constant.RuntimeNpm { + cmd += " install" + } else { + cmd += " add" + } + case constant.RuntimeUninstall: + if req.PkgManager == constant.RuntimeNpm { + cmd += " uninstall" + } else { + cmd += " remove" + } + case constant.RuntimeUpdate: + cmd += " update" + } + cmd += " " + req.Module + return cmd2.ExecContainerScript(containerName, cmd, 5*time.Minute) +} diff --git a/backend/constant/runtime.go b/backend/constant/runtime.go index 7381d79e5..8e07c7a57 100644 --- a/backend/constant/runtime.go +++ b/backend/constant/runtime.go @@ -23,4 +23,11 @@ const ( RuntimeUp = "up" RuntimeDown = "down" RuntimeRestart = "restart" + + RuntimeInstall = "install" + RuntimeUninstall = "uninstall" + RuntimeUpdate = "update" + + RuntimeNpm = "npm" + RuntimeYarn = "yarn" ) diff --git a/backend/router/ro_runtime.go b/backend/router/ro_runtime.go index 48a6da4a3..fbb10163c 100644 --- a/backend/router/ro_runtime.go +++ b/backend/router/ro_runtime.go @@ -23,6 +23,7 @@ func (r *RuntimeRouter) InitRuntimeRouter(Router *gin.RouterGroup) { groupRouter.POST("/node/package", baseApi.GetNodePackageRunScript) groupRouter.POST("/operate", baseApi.OperateRuntime) groupRouter.POST("/node/modules", baseApi.GetNodeModules) + groupRouter.POST("/node/modules/operate", baseApi.OperateNodeModules) } } diff --git a/backend/utils/cmd/cmd.go b/backend/utils/cmd/cmd.go index 4c8214994..7ccaba64d 100644 --- a/backend/utils/cmd/cmd.go +++ b/backend/utils/cmd/cmd.go @@ -68,6 +68,18 @@ func ExecWithTimeOut(cmdStr string, timeout time.Duration) (string, error) { return stdout.String(), nil } +func ExecContainerScript(containerName, cmdStr string, timeout time.Duration) error { + cmdStr = fmt.Sprintf("docker exec -i %s bash -c '%s'", containerName, cmdStr) + out, err := ExecWithTimeOut(cmdStr, timeout) + if err != nil { + if out != "" { + return fmt.Errorf("%s; err: %v", out, err) + } + return err + } + return nil +} + func ExecCronjobWithTimeOut(cmdStr string, workdir string, timeout time.Duration) (string, error) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() diff --git a/backend/utils/env/env.go b/backend/utils/env/env.go index 17728e899..630511618 100644 --- a/backend/utils/env/env.go +++ b/backend/utils/env/env.go @@ -2,6 +2,7 @@ package env import ( "fmt" + "github.com/joho/godotenv" "os" "sort" "strconv" @@ -37,3 +38,15 @@ func Marshal(envMap map[string]string) (string, error) { sort.Strings(lines) return strings.Join(lines, "\n"), nil } + +func GetEnvValueByKey(envPath, key string) (string, error) { + envMap, err := godotenv.Read(envPath) + if err != nil { + return "", err + } + value, ok := envMap[key] + if !ok { + return "", fmt.Errorf("key %s not found in %s", key, envPath) + } + return value, nil +} diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index 233324dff..ffb9dd34f 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -7943,6 +7943,39 @@ const docTemplate = `{ } } }, + "/runtimes/node/modules/operate": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "操作 Node 项目的 modules", + "consumes": [ + "application/json" + ], + "tags": [ + "Runtime" + ], + "summary": "Operate Node modules", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.NodeModuleReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/runtimes/node/package": { "post": { "security": [ @@ -16691,6 +16724,9 @@ const docTemplate = `{ }, "request.NodeModuleReq": { "type": "object", + "required": [ + "ID" + ], "properties": { "ID": { "type": "integer" @@ -16699,7 +16735,19 @@ const docTemplate = `{ "type": "string" }, "operate": { - "type": "string" + "type": "string", + "enum": [ + "install", + "uninstall", + "update" + ] + }, + "pkgManager": { + "type": "string", + "enum": [ + "npm", + "yarn" + ] } } }, diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index c0203fd6f..64efc6f2e 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -7936,6 +7936,39 @@ } } }, + "/runtimes/node/modules/operate": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "操作 Node 项目的 modules", + "consumes": [ + "application/json" + ], + "tags": [ + "Runtime" + ], + "summary": "Operate Node modules", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.NodeModuleReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/runtimes/node/package": { "post": { "security": [ @@ -16684,6 +16717,9 @@ }, "request.NodeModuleReq": { "type": "object", + "required": [ + "ID" + ], "properties": { "ID": { "type": "integer" @@ -16692,7 +16728,19 @@ "type": "string" }, "operate": { - "type": "string" + "type": "string", + "enum": [ + "install", + "uninstall", + "update" + ] + }, + "pkgManager": { + "type": "string", + "enum": [ + "npm", + "yarn" + ] } } }, diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index 3875d9e48..25f95f694 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -3138,7 +3138,18 @@ definitions: module: type: string operate: + enum: + - install + - uninstall + - update + type: string + pkgManager: + enum: + - npm + - yarn type: string + required: + - ID type: object request.NodePackageReq: properties: @@ -9192,6 +9203,26 @@ paths: summary: Get Node modules tags: - Runtime + /runtimes/node/modules/operate: + post: + consumes: + - application/json + description: 操作 Node 项目的 modules + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/request.NodeModuleReq' + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Operate Node modules + tags: + - Runtime /runtimes/node/package: post: consumes: diff --git a/frontend/src/api/interface/runtime.ts b/frontend/src/api/interface/runtime.ts index 957d620da..9c2e1ac48 100644 --- a/frontend/src/api/interface/runtime.ts +++ b/frontend/src/api/interface/runtime.ts @@ -44,7 +44,7 @@ export namespace Runtime { name: string; appDetailID: number; image: string; - params: object; + params: Object; type: string; resource: string; appID?: number; @@ -85,5 +85,8 @@ export namespace Runtime { export interface NodeModuleReq { ID: number; + Operate?: string; + Module?: string; + PkgManager?: string; } } diff --git a/frontend/src/api/modules/runtime.ts b/frontend/src/api/modules/runtime.ts index 98d4b101d..12e081bb1 100644 --- a/frontend/src/api/modules/runtime.ts +++ b/frontend/src/api/modules/runtime.ts @@ -1,6 +1,7 @@ import http from '@/api'; import { ResPage } from '../interface'; import { Runtime } from '../interface/runtime'; +import { TimeoutEnum } from '@/enums/http-enum'; export const SearchRuntimes = (req: Runtime.RuntimeReq) => { return http.post>(`/runtimes/search`, req); @@ -33,3 +34,7 @@ export const OperateRuntime = (req: Runtime.RuntimeOperate) => { export const GetNodeModules = (req: Runtime.NodeModuleReq) => { return http.post(`/runtimes/node/modules`, req); }; + +export const OperateNodeModule = (req: Runtime.NodeModuleReq) => { + return http.post(`/runtimes/node/modules/operate`, req, TimeoutEnum.T_10M); +}; diff --git a/frontend/src/components/compose-log/index.vue b/frontend/src/components/compose-log/index.vue index c2819cff6..be2541b96 100644 --- a/frontend/src/components/compose-log/index.vue +++ b/frontend/src/components/compose-log/index.vue @@ -82,7 +82,7 @@ const logSearch = reactive({ isWatch: true, compose: '', mode: 'all', - tail: 1000, + tail: 500, }); const handleClose = () => { @@ -167,7 +167,7 @@ interface DialogProps { const acceptParams = (props: DialogProps): void => { logSearch.compose = props.compose; - logSearch.tail = 1000; + logSearch.tail = 200; logSearch.mode = timeOptions.value[3].value; logSearch.isWatch = true; resource.value = props.resource; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 4e40e2551..2005cc72c 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -246,6 +246,9 @@ const message = { down: 'Stop', up: 'Start', restart: 'Restart', + install: 'Install', + uninstall: 'Uninstall', + update: 'Update', }, }, menu: { @@ -1783,6 +1786,8 @@ const message = { imageSource: 'Image source', moduleManager: 'Module Management', module: 'Module', + nodeOperatorHelper: + 'Is {0} {1} module? The operation may cause abnormality in the operating environment, please confirm before proceeding', }, process: { pid: 'Process ID', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index a43a32ccd..c1013d6a3 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -242,8 +242,11 @@ const message = { }, operate: { down: '停止', - up: '启动', - restart: '重启', + up: '啟動', + restart: '重啟', + install: '安裝', + uninstall: '卸載', + update: '更新', }, }, menu: { @@ -1685,6 +1688,7 @@ const message = { imageSource: '鏡像源', moduleManager: '模塊管理', module: '模塊', + nodeOperatorHelper: '是否{0} {1} 模組? 操作可能導致運轉環境異常,請確認後操作', }, process: { pid: '進程ID', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index d702fe783..bddc5ea1a 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -244,6 +244,9 @@ const message = { down: '停止', up: '启动', restart: '重启', + install: '安装', + uninstall: '卸载', + update: '更新', }, }, menu: { @@ -1684,6 +1687,7 @@ const message = { imageSource: '镜像源', moduleManager: '模块管理', module: '模块', + nodeOperatorHelper: '是否{0} {1} 模块?操作可能导致运行环境异常,请确认后操作', }, process: { pid: '进程ID', diff --git a/frontend/src/views/website/runtime/node/index.vue b/frontend/src/views/website/runtime/node/index.vue index e95ffed69..5c32c1659 100644 --- a/frontend/src/views/website/runtime/node/index.vue +++ b/frontend/src/views/website/runtime/node/index.vue @@ -193,7 +193,7 @@ const search = async () => { }; const openModules = (row: Runtime.Runtime) => { - moduleRef.value.acceptParams({ id: row.id }); + moduleRef.value.acceptParams({ id: row.id, packageManager: row.params['PACKAGE_MANAGER'] }); }; const openCreate = () => { diff --git a/frontend/src/views/website/runtime/node/module/index.vue b/frontend/src/views/website/runtime/node/module/index.vue index ef56f4bb6..aac7c545b 100644 --- a/frontend/src/views/website/runtime/node/module/index.vue +++ b/frontend/src/views/website/runtime/node/module/index.vue @@ -3,9 +3,19 @@ - - - + + + + + + + + + {{ $t('commons.operate.install') }} + + + + + @@ -26,7 +44,10 @@