From 1130a70052939d530dc888101b5f2768506d6429 Mon Sep 17 00:00:00 2001 From: zhengkunwang <31820853+zhengkunwang223@users.noreply.github.com> Date: Mon, 25 Sep 2023 17:50:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=BF=90=E8=A1=8C=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20Node.js=20=E7=AE=A1=E7=90=86=20(#2390)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs https://github.com/1Panel-dev/1Panel/issues/397 --- backend/app/api/v1/runtime.go | 24 +- backend/app/dto/request/runtime.go | 16 +- backend/app/dto/response/runtime.go | 47 ++- backend/app/model/runtime.go | 22 + backend/app/service/app.go | 63 +-- backend/app/service/runtime.go | 385 +++++++++++------- backend/app/service/runtime_utils.go | 181 +++++++- backend/constant/errs.go | 13 +- backend/constant/runtime.go | 12 +- backend/i18n/lang/en.yaml | 3 + backend/i18n/lang/zh-Hant.yaml | 3 + backend/i18n/lang/zh.yaml | 3 + backend/init/migration/migrate.go | 1 + backend/init/migration/migrations/v_1_7.go | 10 + backend/router/ro_runtime.go | 1 + cmd/server/docs/docs.go | 84 +++- cmd/server/docs/swagger.json | 80 ++++ cmd/server/docs/swagger.yaml | 51 +++ frontend/src/api/interface/runtime.ts | 25 +- frontend/src/api/modules/runtime.ts | 4 + frontend/src/components/file-list/index.vue | 6 +- frontend/src/components/status/index.vue | 2 +- frontend/src/lang/modules/en.ts | 12 + frontend/src/lang/modules/tw.ts | 12 + frontend/src/lang/modules/zh.ts | 12 + frontend/src/routers/modules/website.ts | 16 +- frontend/src/views/app-store/apps/index.vue | 19 +- .../views/website/runtime/delete/index.vue | 77 ++++ frontend/src/views/website/runtime/index.vue | 156 +------ .../website/runtime/node/create/index.vue | 337 +++++++++++++++ .../src/views/website/runtime/node/index.vue | 164 ++++++++ .../website/runtime/node/operate/index.vue | 351 ++++++++++++++++ .../runtime/{ => php}/create/index.vue | 22 +- .../website/runtime/{ => php}/edit/index.vue | 0 .../src/views/website/runtime/php/index.vue | 156 +++++++ .../website/runtime/{ => php}/param/index.vue | 0 36 files changed, 1974 insertions(+), 396 deletions(-) create mode 100644 frontend/src/views/website/runtime/delete/index.vue create mode 100644 frontend/src/views/website/runtime/node/create/index.vue create mode 100644 frontend/src/views/website/runtime/node/index.vue create mode 100644 frontend/src/views/website/runtime/node/operate/index.vue rename frontend/src/views/website/runtime/{ => php}/create/index.vue (96%) rename frontend/src/views/website/runtime/{ => php}/edit/index.vue (100%) create mode 100644 frontend/src/views/website/runtime/php/index.vue rename frontend/src/views/website/runtime/{ => php}/param/index.vue (100%) diff --git a/backend/app/api/v1/runtime.go b/backend/app/api/v1/runtime.go index 0a5a73256..b529d48a1 100644 --- a/backend/app/api/v1/runtime.go +++ b/backend/app/api/v1/runtime.go @@ -70,7 +70,7 @@ func (b *BaseApi) DeleteRuntime(c *gin.Context) { helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) return } - err := runtimeService.Delete(req.ID) + err := runtimeService.Delete(req) if err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) return @@ -121,3 +121,25 @@ func (b *BaseApi) GetRuntime(c *gin.Context) { } helper.SuccessWithData(c, res) } + +// @Tags Runtime +// @Summary Get Node package scripts +// @Description 获取 Node 项目的 scripts +// @Accept json +// @Param request body request.NodePackageReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /runtimes/node/package [post] +func (b *BaseApi) GetNodePackageRunScript(c *gin.Context) { + var req request.NodePackageReq + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + res, err := runtimeService.GetNodePackageRunScript(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, res) +} diff --git a/backend/app/dto/request/runtime.go b/backend/app/dto/request/runtime.go index 56f89939d..503e02d5a 100644 --- a/backend/app/dto/request/runtime.go +++ b/backend/app/dto/request/runtime.go @@ -18,10 +18,18 @@ type RuntimeCreate struct { Type string `json:"type"` Version string `json:"version"` Source string `json:"source"` + CodeDir string `json:"codeDir"` + NodeConfig +} + +type NodeConfig struct { + Install bool `json:"install"` + Clean bool `json:"clean"` } type RuntimeDelete struct { - ID uint `json:"id"` + ID uint `json:"id"` + ForceDelete bool `json:"forceDelete"` } type RuntimeUpdate struct { @@ -32,4 +40,10 @@ type RuntimeUpdate struct { Version string `json:"version"` Rebuild bool `json:"rebuild"` Source string `json:"source"` + CodeDir string `json:"codeDir"` + NodeConfig +} + +type NodePackageReq struct { + CodeDir string `json:"codeDir"` } diff --git a/backend/app/dto/response/runtime.go b/backend/app/dto/response/runtime.go index 3c0a652e2..456661f68 100644 --- a/backend/app/dto/response/runtime.go +++ b/backend/app/dto/response/runtime.go @@ -1,10 +1,45 @@ package response -import "github.com/1Panel-dev/1Panel/backend/app/model" +import ( + "github.com/1Panel-dev/1Panel/backend/app/model" + "time" +) -type RuntimeRes struct { - model.Runtime - AppParams []AppParam `json:"appParams"` - AppID uint `json:"appId"` - Source string `json:"source"` +type RuntimeDTO struct { + ID uint `json:"id"` + Name string `json:"name"` + Resource string `json:"resource"` + AppDetailID uint `json:"appDetailID"` + AppID uint `json:"appID"` + Source string `json:"source"` + Status string `json:"status"` + Type string `json:"type"` + Image string `json:"image"` + Params map[string]interface{} `json:"params"` + Message string `json:"message"` + Version string `json:"version"` + CreatedAt time.Time `json:"createdAt"` + CodeDir string `json:"codeDir"` + AppParams []AppParam `json:"appParams"` +} + +type PackageScripts struct { + Name string `json:"name"` + Script string `json:"script"` +} + +func NewRuntimeDTO(runtime model.Runtime) RuntimeDTO { + return RuntimeDTO{ + ID: runtime.ID, + Name: runtime.Name, + Resource: runtime.Resource, + AppDetailID: runtime.AppDetailID, + Status: runtime.Status, + Type: runtime.Type, + Image: runtime.Image, + Message: runtime.Message, + CreatedAt: runtime.CreatedAt, + CodeDir: runtime.CodeDir, + Version: runtime.Version, + } } diff --git a/backend/app/model/runtime.go b/backend/app/model/runtime.go index de2e002f6..937c367ae 100644 --- a/backend/app/model/runtime.go +++ b/backend/app/model/runtime.go @@ -1,5 +1,10 @@ package model +import ( + "github.com/1Panel-dev/1Panel/backend/constant" + "path" +) + type Runtime struct { BaseModel Name string `gorm:"type:varchar;not null" json:"name"` @@ -14,4 +19,21 @@ type Runtime struct { Status string `gorm:"type:varchar;not null" json:"status"` Resource string `gorm:"type:varchar;not null" json:"resource"` Message string `gorm:"type:longtext;" json:"message"` + CodeDir string `gorm:"type:varchar;" json:"codeDir"` +} + +func (r *Runtime) GetComposePath() string { + return path.Join(r.GetPath(), "docker-compose.yml") +} + +func (r *Runtime) GetEnvPath() string { + return path.Join(r.GetPath(), ".env") +} + +func (r *Runtime) GetPath() string { + return path.Join(constant.RuntimeDir, r.Type, r.Name) +} + +func (r *Runtime) GetLogPath() string { + return path.Join(r.GetPath(), "build.log") } diff --git a/backend/app/service/app.go b/backend/app/service/app.go index 4a490a74a..fb8ee14b4 100644 --- a/backend/app/service/app.go +++ b/backend/app/service/app.go @@ -176,36 +176,39 @@ func (a AppService) GetAppDetail(appId uint, version, appType string) (response. return appDetailDTO, err } } - buildPath := path.Join(versionPath, "build") - paramsPath := path.Join(buildPath, "config.json") - if !fileOp.Stat(paramsPath) { - return appDetailDTO, buserr.New(constant.ErrFileNotExist) - } - param, err := fileOp.GetContent(paramsPath) - if err != nil { - return appDetailDTO, err - } - paramMap := make(map[string]interface{}) - if err := json.Unmarshal(param, ¶mMap); err != nil { - return appDetailDTO, err - } - appDetailDTO.Params = paramMap - composePath := path.Join(buildPath, "docker-compose.yml") - if !fileOp.Stat(composePath) { - return appDetailDTO, buserr.New(constant.ErrFileNotExist) - } - compose, err := fileOp.GetContent(composePath) - if err != nil { - return appDetailDTO, err - } - composeMap := make(map[string]interface{}) - if err := yaml.Unmarshal(compose, &composeMap); err != nil { - return appDetailDTO, err - } - if service, ok := composeMap["services"]; ok { - servicesMap := service.(map[string]interface{}) - for k := range servicesMap { - appDetailDTO.Image = k + switch app.Type { + case constant.RuntimePHP: + buildPath := path.Join(versionPath, "build") + paramsPath := path.Join(buildPath, "config.json") + if !fileOp.Stat(paramsPath) { + return appDetailDTO, buserr.New(constant.ErrFileNotExist) + } + param, err := fileOp.GetContent(paramsPath) + if err != nil { + return appDetailDTO, err + } + paramMap := make(map[string]interface{}) + if err := json.Unmarshal(param, ¶mMap); err != nil { + return appDetailDTO, err + } + appDetailDTO.Params = paramMap + composePath := path.Join(buildPath, "docker-compose.yml") + if !fileOp.Stat(composePath) { + return appDetailDTO, buserr.New(constant.ErrFileNotExist) + } + compose, err := fileOp.GetContent(composePath) + if err != nil { + return appDetailDTO, err + } + composeMap := make(map[string]interface{}) + if err := yaml.Unmarshal(compose, &composeMap); err != nil { + return appDetailDTO, err + } + if service, ok := composeMap["services"]; ok { + servicesMap := service.(map[string]interface{}) + for k := range servicesMap { + appDetailDTO.Image = k + } } } } else { diff --git a/backend/app/service/runtime.go b/backend/app/service/runtime.go index 7d0ad3def..b9e2b50fd 100644 --- a/backend/app/service/runtime.go +++ b/backend/app/service/runtime.go @@ -3,7 +3,6 @@ package service import ( "context" "encoding/json" - "fmt" "github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto/request" "github.com/1Panel-dev/1Panel/backend/app/dto/response" @@ -12,24 +11,26 @@ import ( "github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/global" + "github.com/1Panel-dev/1Panel/backend/utils/compose" "github.com/1Panel-dev/1Panel/backend/utils/docker" "github.com/1Panel-dev/1Panel/backend/utils/files" + "github.com/pkg/errors" "github.com/subosito/gotenv" "path" - "path/filepath" + "strconv" "strings" - "time" ) type RuntimeService struct { } type IRuntimeService interface { - Page(req request.RuntimeSearch) (int64, []response.RuntimeRes, error) + Page(req request.RuntimeSearch) (int64, []response.RuntimeDTO, error) Create(create request.RuntimeCreate) error - Delete(id uint) error + Delete(delete request.RuntimeDelete) error Update(req request.RuntimeUpdate) error - Get(id uint) (res *response.RuntimeRes, err error) + Get(id uint) (res *response.RuntimeDTO, err error) + GetNodePackageRunScript(req request.NodePackageReq) ([]response.PackageScripts, error) } func NewRuntimeService() IRuntimeService { @@ -37,24 +38,35 @@ func NewRuntimeService() IRuntimeService { } func (r *RuntimeService) Create(create request.RuntimeCreate) (err error) { - exist, _ := runtimeRepo.GetFirst(runtimeRepo.WithName(create.Name)) + exist, _ := runtimeRepo.GetFirst(runtimeRepo.WithName(create.Name), commonRepo.WithByType(create.Type)) if exist != nil { return buserr.New(constant.ErrNameIsExist) } - if create.Resource == constant.ResourceLocal { - runtime := &model.Runtime{ - Name: create.Name, - Resource: create.Resource, - Type: create.Type, - Version: create.Version, - Status: constant.RuntimeNormal, + fileOp := files.NewFileOp() + + switch create.Type { + case constant.RuntimePHP: + if create.Resource == constant.ResourceLocal { + runtime := &model.Runtime{ + Name: create.Name, + Resource: create.Resource, + Type: create.Type, + Version: create.Version, + Status: constant.RuntimeNormal, + } + return runtimeRepo.Create(context.Background(), runtime) } - return runtimeRepo.Create(context.Background(), runtime) - } - exist, _ = runtimeRepo.GetFirst(runtimeRepo.WithImage(create.Image)) - if exist != nil { - return buserr.New(constant.ErrImageExist) + exist, _ = runtimeRepo.GetFirst(runtimeRepo.WithImage(create.Image)) + if exist != nil { + return buserr.New(constant.ErrImageExist) + } + case constant.RuntimeNode: + if !fileOp.Stat(create.CodeDir) { + return buserr.New(constant.ErrPathNotFound) + } + create.Install = true } + appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(create.AppDetailID)) if err != nil { return err @@ -63,64 +75,39 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (err error) { if err != nil { return err } - fileOp := files.NewFileOp() appVersionDir := path.Join(constant.AppResourceDir, app.Resource, app.Key, appDetail.Version) if !fileOp.Stat(appVersionDir) || appDetail.Update { if err := downloadApp(app, appDetail, nil); err != nil { return err } } - buildDir := path.Join(appVersionDir, "build") - if !fileOp.Stat(buildDir) { - return buserr.New(constant.ErrDirNotFound) - } - runtimeDir := path.Join(constant.RuntimeDir, create.Type) - tempDir := filepath.Join(runtimeDir, fmt.Sprintf("%d", time.Now().UnixNano())) - if err = fileOp.CopyDir(buildDir, tempDir); err != nil { - return - } - oldDir := path.Join(tempDir, "build") - newNameDir := path.Join(runtimeDir, create.Name) - defer func() { - if err != nil { - _ = fileOp.DeleteDir(newNameDir) - } - }() - if oldDir != newNameDir { - if err = fileOp.Rename(oldDir, newNameDir); err != nil { - return - } - if err = fileOp.DeleteDir(tempDir); err != nil { - return - } - } - composeContent, envContent, forms, err := handleParams(create.Image, create.Type, newNameDir, create.Source, create.Params) - if err != nil { - return - } + runtime := &model.Runtime{ - Name: create.Name, - DockerCompose: string(composeContent), - Env: string(envContent), - AppDetailID: create.AppDetailID, - Type: create.Type, - Image: create.Image, - Resource: create.Resource, - Status: constant.RuntimeBuildIng, - Version: create.Version, - Params: string(forms), + Name: create.Name, + AppDetailID: create.AppDetailID, + Type: create.Type, + Image: create.Image, + Resource: create.Resource, + Version: create.Version, } - if err = runtimeRepo.Create(context.Background(), runtime); err != nil { - return + + switch create.Type { + case constant.RuntimePHP: + if err = handlePHP(create, runtime, fileOp, appVersionDir); err != nil { + return + } + case constant.RuntimeNode: + if err = handleNode(create, runtime, fileOp, appVersionDir); err != nil { + return + } } - go buildRuntime(runtime, "", false) - return + return runtimeRepo.Create(context.Background(), runtime) } -func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.RuntimeRes, error) { +func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.RuntimeDTO, error) { var ( opts []repo.DBOption - res []response.RuntimeRes + res []response.RuntimeDTO ) if req.Name != "" { opts = append(opts, commonRepo.WithLikeName(req.Name)) @@ -128,116 +115,158 @@ func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.Runt if req.Status != "" { opts = append(opts, runtimeRepo.WithStatus(req.Status)) } + if req.Type != "" { + opts = append(opts, commonRepo.WithByType(req.Type)) + } total, runtimes, err := runtimeRepo.Page(req.Page, req.PageSize, opts...) if err != nil { return 0, nil, err } for _, runtime := range runtimes { - res = append(res, response.RuntimeRes{ - Runtime: runtime, - }) + runtimeDTO := response.NewRuntimeDTO(runtime) + runtimeDTO.Params = make(map[string]interface{}) + envs, err := gotenv.Unmarshal(runtime.Env) + if err != nil { + return 0, nil, err + } + for k, v := range envs { + runtimeDTO.Params[k] = v + } + res = append(res, runtimeDTO) } return total, res, nil } -func (r *RuntimeService) Delete(id uint) error { - runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(id)) +func (r *RuntimeService) Delete(runtimeDelete request.RuntimeDelete) error { + runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(runtimeDelete.ID)) if err != nil { return err } - website, _ := websiteRepo.GetFirst(websiteRepo.WithRuntimeID(id)) + website, _ := websiteRepo.GetFirst(websiteRepo.WithRuntimeID(runtimeDelete.ID)) if website.ID > 0 { return buserr.New(constant.ErrDelWithWebsite) } if runtime.Resource == constant.ResourceAppstore { - client, err := docker.NewClient() - if err != nil { - return err - } - imageID, err := client.GetImageIDByName(runtime.Image) - if err != nil { - return err - } - if imageID != "" { - if err := client.DeleteImage(imageID); err != nil { - global.LOG.Errorf("delete image id [%s] error %v", imageID, err) + projectDir := runtime.GetPath() + switch runtime.Type { + case constant.RuntimePHP: + client, err := docker.NewClient() + if err != nil { + return err + } + imageID, err := client.GetImageIDByName(runtime.Image) + if err != nil { + return err + } + if imageID != "" { + if err := client.DeleteImage(imageID); err != nil { + global.LOG.Errorf("delete image id [%s] error %v", imageID, err) + } + } + case constant.RuntimeNode: + if out, err := compose.Down(runtime.GetComposePath()); err != nil && !runtimeDelete.ForceDelete { + if out != "" { + return errors.New(out) + } + return err } } - runtimeDir := path.Join(constant.RuntimeDir, runtime.Type, runtime.Name) - if err := files.NewFileOp().DeleteDir(runtimeDir); err != nil { + if err := files.NewFileOp().DeleteDir(projectDir); err != nil && !runtimeDelete.ForceDelete { return err } } - return runtimeRepo.DeleteBy(commonRepo.WithByID(id)) + return runtimeRepo.DeleteBy(commonRepo.WithByID(runtimeDelete.ID)) } -func (r *RuntimeService) Get(id uint) (*response.RuntimeRes, error) { +func (r *RuntimeService) Get(id uint) (*response.RuntimeDTO, error) { runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(id)) if err != nil { return nil, err } - res := &response.RuntimeRes{} - res.Runtime = *runtime + + res := response.NewRuntimeDTO(*runtime) if runtime.Resource == constant.ResourceLocal { - return res, nil + return &res, nil } appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(runtime.AppDetailID)) if err != nil { return nil, err } res.AppID = appDetail.AppId - var ( - appForm dto.AppForm - appParams []response.AppParam - ) - if err := json.Unmarshal([]byte(runtime.Params), &appForm); err != nil { - return nil, err - } - envs, err := gotenv.Unmarshal(runtime.Env) - if err != nil { - return nil, err - } - if v, ok := envs["CONTAINER_PACKAGE_URL"]; ok { - res.Source = v - } - 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, - Required: form.Required, - } - if form.Edit { - appParam.Edit = true - } - appParam.LabelZh = form.LabelZh - appParam.LabelEn = form.LabelEn - appParam.Multiple = form.Multiple - appParam.Value = v - if form.Type == "select" { - if form.Multiple { - if v == "" { - appParam.Value = []string{} + switch runtime.Type { + case constant.RuntimePHP: + var ( + appForm dto.AppForm + appParams []response.AppParam + ) + if err := json.Unmarshal([]byte(runtime.Params), &appForm); err != nil { + return nil, err + } + envs, err := gotenv.Unmarshal(runtime.Env) + if err != nil { + return nil, err + } + if v, ok := envs["CONTAINER_PACKAGE_URL"]; ok { + res.Source = v + } + 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, + Required: form.Required, + } + if form.Edit { + appParam.Edit = true + } + appParam.LabelZh = form.LabelZh + appParam.LabelEn = form.LabelEn + appParam.Multiple = form.Multiple + appParam.Value = v + if form.Type == "select" { + if form.Multiple { + if v == "" { + appParam.Value = []string{} + } else { + appParam.Value = strings.Split(v, ",") + } } else { - appParam.Value = strings.Split(v, ",") - } - } else { - for _, fv := range form.Values { - if fv.Value == v { - appParam.ShowValue = fv.Label - break + for _, fv := range form.Values { + if fv.Value == v { + appParam.ShowValue = fv.Label + break + } } } + appParam.Values = form.Values } - appParam.Values = form.Values + appParams = append(appParams, appParam) + } + } + res.AppParams = appParams + case constant.RuntimeNode: + res.Params = make(map[string]interface{}) + envs, err := gotenv.Unmarshal(runtime.Env) + if err != nil { + return nil, err + } + for k, v := range envs { + switch k { + case "NODE_APP_PORT", "PANEL_APP_PORT_HTTP": + port, err := strconv.Atoi(v) + if err != nil { + return nil, err + } + res.Params[k] = port + default: + res.Params[k] = v } - appParams = append(appParams, appParam) } } - res.AppParams = appParams - return res, nil + + return &res, nil } func (r *RuntimeService) Update(req request.RuntimeUpdate) error { @@ -245,36 +274,86 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error { if err != nil { return err } - oldImage := runtime.Image if runtime.Resource == constant.ResourceLocal { runtime.Version = req.Version return runtimeRepo.Save(runtime) } - exist, _ := runtimeRepo.GetFirst(runtimeRepo.WithImage(req.Name), runtimeRepo.WithNotId(req.ID)) - if exist != nil { - return buserr.New(constant.ErrImageExist) + oldImage := runtime.Image + switch runtime.Type { + case constant.RuntimePHP: + exist, _ := runtimeRepo.GetFirst(runtimeRepo.WithImage(req.Name), runtimeRepo.WithNotId(req.ID)) + if exist != nil { + return buserr.New(constant.ErrImageExist) + } } - runtimeDir := path.Join(constant.RuntimeDir, runtime.Type, runtime.Name) - composeContent, envContent, _, err := handleParams(req.Image, runtime.Type, runtimeDir, req.Source, req.Params) + + projectDir := path.Join(constant.RuntimeDir, runtime.Type, runtime.Name) + create := request.RuntimeCreate{ + Image: req.Image, + Type: runtime.Type, + Source: req.Source, + Params: req.Params, + CodeDir: req.CodeDir, + Version: req.Version, + } + composeContent, envContent, _, err := handleParams(create, projectDir) if err != nil { return err } - if err != nil { - return err - } - runtime.Image = req.Image runtime.Env = string(envContent) runtime.DockerCompose = string(composeContent) - runtime.Status = constant.RuntimeBuildIng - _ = runtimeRepo.Save(runtime) - client, err := docker.NewClient() - if err != nil { - return err + + switch runtime.Type { + case constant.RuntimePHP: + runtime.Image = req.Image + runtime.Status = constant.RuntimeBuildIng + _ = runtimeRepo.Save(runtime) + client, err := docker.NewClient() + if err != nil { + return err + } + imageID, err := client.GetImageIDByName(oldImage) + if err != nil { + return err + } + go buildRuntime(runtime, imageID, req.Rebuild) + case constant.RuntimeNode: + runtime.Version = req.Version + runtime.CodeDir = req.CodeDir + runtime.Status = constant.RuntimeReCreating + _ = runtimeRepo.Save(runtime) + go reCreateRuntime(runtime) } - imageID, err := client.GetImageIDByName(oldImage) - if err != nil { - return err - } - go buildRuntime(runtime, imageID, req.Rebuild) return nil } + +func (r *RuntimeService) GetNodePackageRunScript(req request.NodePackageReq) ([]response.PackageScripts, error) { + fileOp := files.NewFileOp() + if !fileOp.Stat(req.CodeDir) { + return nil, buserr.New(constant.ErrPathNotFound) + } + if !fileOp.Stat(path.Join(req.CodeDir, "package.json")) { + return nil, buserr.New(constant.ErrPackageJsonNotFound) + } + content, err := fileOp.GetContent(path.Join(req.CodeDir, "package.json")) + if err != nil { + return nil, err + } + var packageMap map[string]interface{} + err = json.Unmarshal(content, &packageMap) + if err != nil { + return nil, err + } + scripts, ok := packageMap["scripts"] + if !ok { + return nil, buserr.New(constant.ErrScriptsNotFound) + } + var packageScripts []response.PackageScripts + for k, v := range scripts.(map[string]interface{}) { + packageScripts = append(packageScripts, response.PackageScripts{ + Name: k, + Script: v.(string), + }) + } + return packageScripts, nil +} diff --git a/backend/app/service/runtime_utils.go b/backend/app/service/runtime_utils.go index da81b4103..045ad618d 100644 --- a/backend/app/service/runtime_utils.go +++ b/backend/app/service/runtime_utils.go @@ -10,22 +10,159 @@ import ( "github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/utils/docker" "github.com/1Panel-dev/1Panel/backend/utils/files" + "github.com/pkg/errors" "github.com/subosito/gotenv" "io" "os" "os/exec" "path" + "path/filepath" "strings" + "time" ) +func handleNode(create request.RuntimeCreate, runtime *model.Runtime, fileOp files.FileOp, appVersionDir string) (err error) { + runtimeDir := path.Join(constant.RuntimeDir, create.Type) + if err = fileOp.CopyDir(appVersionDir, runtimeDir); err != nil { + return + } + versionDir := path.Join(runtimeDir, filepath.Base(appVersionDir)) + projectDir := path.Join(runtimeDir, create.Name) + defer func() { + if err != nil { + _ = fileOp.DeleteDir(projectDir) + } + }() + if err = fileOp.Rename(versionDir, projectDir); err != nil { + return + } + composeContent, envContent, _, err := handleParams(create, projectDir) + if err != nil { + return + } + runtime.DockerCompose = string(composeContent) + runtime.Env = string(envContent) + runtime.Status = constant.RuntimeStarting + runtime.CodeDir = create.CodeDir + + go startRuntime(runtime) + return +} + +func handlePHP(create request.RuntimeCreate, runtime *model.Runtime, fileOp files.FileOp, appVersionDir string) (err error) { + buildDir := path.Join(appVersionDir, "build") + if !fileOp.Stat(buildDir) { + return buserr.New(constant.ErrDirNotFound) + } + runtimeDir := path.Join(constant.RuntimeDir, create.Type) + tempDir := filepath.Join(runtimeDir, fmt.Sprintf("%d", time.Now().UnixNano())) + if err = fileOp.CopyDir(buildDir, tempDir); err != nil { + return + } + oldDir := path.Join(tempDir, "build") + projectDir := path.Join(runtimeDir, create.Name) + defer func() { + if err != nil { + _ = fileOp.DeleteDir(projectDir) + } + }() + if oldDir != projectDir { + if err = fileOp.Rename(oldDir, projectDir); err != nil { + return + } + if err = fileOp.DeleteDir(tempDir); err != nil { + return + } + } + composeContent, envContent, forms, err := handleParams(create, projectDir) + if err != nil { + return + } + runtime.DockerCompose = string(composeContent) + runtime.Env = string(envContent) + runtime.Params = string(forms) + runtime.Status = constant.RuntimeBuildIng + + go buildRuntime(runtime, "", false) + return +} + +func startRuntime(runtime *model.Runtime) { + cmd := exec.Command("docker-compose", "-f", runtime.GetComposePath(), "up", "-d") + logPath := runtime.GetLogPath() + logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) + if err != nil { + global.LOG.Errorf("Failed to open log file: %v", err) + return + } + multiWriterStdout := io.MultiWriter(os.Stdout, logFile) + cmd.Stdout = multiWriterStdout + var stderrBuf bytes.Buffer + multiWriterStderr := io.MultiWriter(&stderrBuf, logFile, os.Stderr) + cmd.Stderr = multiWriterStderr + + err = cmd.Run() + if err != nil { + runtime.Status = constant.RuntimeError + runtime.Message = buserr.New(constant.ErrRuntimeStart).Error() + ":" + stderrBuf.String() + } else { + runtime.Status = constant.RuntimeRunning + runtime.Message = "" + } + + _ = runtimeRepo.Save(runtime) +} + +func runComposeCmdWithLog(operate string, composePath string, logPath string) error { + cmd := exec.Command("docker-compose", "-f", composePath, operate) + if operate == "up" { + cmd = exec.Command("docker-compose", "-f", composePath, operate, "-d") + } + logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) + if err != nil { + global.LOG.Errorf("Failed to open log file: %v", err) + return err + } + multiWriterStdout := io.MultiWriter(os.Stdout, logFile) + cmd.Stdout = multiWriterStdout + var stderrBuf bytes.Buffer + multiWriterStderr := io.MultiWriter(&stderrBuf, logFile, os.Stderr) + cmd.Stderr = multiWriterStderr + + err = cmd.Run() + if err != nil { + return errors.New(buserr.New(constant.ErrRuntimeStart).Error() + ":" + stderrBuf.String()) + } + return nil +} + +func reCreateRuntime(runtime *model.Runtime) { + var err error + defer func() { + if err != nil { + runtime.Status = constant.RuntimeError + runtime.Message = err.Error() + _ = runtimeRepo.Save(runtime) + } + }() + if err = runComposeCmdWithLog("down", runtime.GetComposePath(), runtime.GetLogPath()); err != nil { + return + } + if err = runComposeCmdWithLog("up", runtime.GetComposePath(), runtime.GetLogPath()); err != nil { + return + } + runtime.Status = constant.RuntimeRunning + _ = runtimeRepo.Save(runtime) +} + func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) { - runtimePath := path.Join(constant.RuntimeDir, runtime.Type, runtime.Name) - composePath := path.Join(runtimePath, "docker-compose.yml") + runtimePath := runtime.GetPath() + composePath := runtime.GetComposePath() logPath := path.Join(runtimePath, "build.log") logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) if err != nil { - fmt.Println("Failed to open log file:", err) + global.LOG.Errorf("failed to open log file: %v", err) return } defer func() { @@ -89,35 +226,45 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) { _ = runtimeRepo.Save(runtime) } -func handleParams(image, runtimeType, runtimeDir, source string, params map[string]interface{}) (composeContent []byte, envContent []byte, forms []byte, err error) { +func handleParams(create request.RuntimeCreate, projectDir string) (composeContent []byte, envContent []byte, forms []byte, err error) { fileOp := files.NewFileOp() - composeContent, err = fileOp.GetContent(path.Join(runtimeDir, "docker-compose.yml")) + composeContent, err = fileOp.GetContent(path.Join(projectDir, "docker-compose.yml")) if err != nil { return } - env, err := gotenv.Read(path.Join(runtimeDir, ".env")) + env, err := gotenv.Read(path.Join(projectDir, ".env")) if err != nil { return } - forms, err = fileOp.GetContent(path.Join(runtimeDir, "config.json")) - if err != nil { - return - } - params["IMAGE_NAME"] = image - if runtimeType == constant.RuntimePHP { - if extends, ok := params["PHP_EXTENSIONS"]; ok { + switch create.Type { + case constant.RuntimePHP: + create.Params["IMAGE_NAME"] = create.Image + forms, err = fileOp.GetContent(path.Join(projectDir, "config.json")) + if err != nil { + return + } + if extends, ok := create.Params["PHP_EXTENSIONS"]; ok { if extendsArray, ok := extends.([]interface{}); ok { strArray := make([]string, len(extendsArray)) for i, v := range extendsArray { strArray[i] = strings.ToLower(fmt.Sprintf("%v", v)) } - params["PHP_EXTENSIONS"] = strings.Join(strArray, ",") + create.Params["PHP_EXTENSIONS"] = strings.Join(strArray, ",") } } - params["CONTAINER_PACKAGE_URL"] = source + create.Params["CONTAINER_PACKAGE_URL"] = create.Source + case constant.RuntimeNode: + create.Params["CODE_DIR"] = create.CodeDir + create.Params["NODE_VERSION"] = create.Version + if create.NodeConfig.Install { + create.Params["RUN_INSTALL"] = "1" + } else { + create.Params["RUN_INSTALL"] = "0" + } } + newMap := make(map[string]string) - handleMap(params, newMap) + handleMap(create.Params, newMap) for k, v := range newMap { env[k] = v } @@ -125,7 +272,7 @@ func handleParams(image, runtimeType, runtimeDir, source string, params map[stri if err != nil { return } - if err = gotenv.Write(env, path.Join(runtimeDir, ".env")); err != nil { + if err = gotenv.Write(env, path.Join(projectDir, ".env")); err != nil { return } envContent = []byte(envStr) diff --git a/backend/constant/errs.go b/backend/constant/errs.go index 114101885..450f29b64 100644 --- a/backend/constant/errs.go +++ b/backend/constant/errs.go @@ -114,11 +114,14 @@ var ( // runtime var ( - ErrDirNotFound = "ErrDirNotFound" - ErrFileNotExist = "ErrFileNotExist" - ErrImageBuildErr = "ErrImageBuildErr" - ErrImageExist = "ErrImageExist" - ErrDelWithWebsite = "ErrDelWithWebsite" + ErrDirNotFound = "ErrDirNotFound" + ErrFileNotExist = "ErrFileNotExist" + ErrImageBuildErr = "ErrImageBuildErr" + ErrImageExist = "ErrImageExist" + ErrDelWithWebsite = "ErrDelWithWebsite" + ErrRuntimeStart = "ErrRuntimeStart" + ErrPackageJsonNotFound = "ErrPackageJsonNotFound" + ErrScriptsNotFound = "ErrScriptsNotFound" ) var ( diff --git a/backend/constant/runtime.go b/backend/constant/runtime.go index e854c8648..177baee71 100644 --- a/backend/constant/runtime.go +++ b/backend/constant/runtime.go @@ -4,11 +4,15 @@ const ( ResourceLocal = "local" ResourceAppstore = "appstore" - RuntimeNormal = "normal" - RuntimeError = "error" - RuntimeBuildIng = "building" + RuntimeNormal = "normal" + RuntimeError = "error" + RuntimeBuildIng = "building" + RuntimeStarting = "starting" + RuntimeRunning = "running" + RuntimeReCreating = "recreating" - RuntimePHP = "php" + RuntimePHP = "php" + RuntimeNode = "node" RuntimeProxyUnix = "unix" RuntimeProxyTcp = "tcp" diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index d8718d7e9..4c5ef4127 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -99,6 +99,9 @@ ErrFileNotExist: "{{ .detail }} file does not exist! Please check source file in ErrImageBuildErr: "Image build failed" ErrImageExist: "Image is already exist!" ErrDelWithWebsite: "The operating environment has been associated with a website and cannot be deleted" +ErrRuntimeStart: "Failed to start" +ErrPackageJsonNotFound: "package.json file does not exist" +ErrScriptsNotFound: "No scripts configuration item was found in package.json" #setting ErrBackupInUsed: "The backup account is already being used in a cronjob and cannot be deleted." diff --git a/backend/i18n/lang/zh-Hant.yaml b/backend/i18n/lang/zh-Hant.yaml index b0993ba82..bd9821cca 100644 --- a/backend/i18n/lang/zh-Hant.yaml +++ b/backend/i18n/lang/zh-Hant.yaml @@ -99,6 +99,9 @@ ErrFileNotExist: "{{ .detail }} 文件不存在!請檢查源文件完整性! ErrImageBuildErr: "鏡像 build 失敗" ErrImageExist: "鏡像已存在!" ErrDelWithWebsite: "運行環境已經關聯網站,無法刪除" +ErrRuntimeStart: "啟動失敗" +ErrPackageJsonNotFound: "package.json 文件不存在" +ErrScriptsNotFound: "沒有在 package.json 中找到 scripts 配置項" #setting ErrBackupInUsed: "該備份帳號已在計劃任務中使用,無法刪除" diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index d65565924..d1d6000fc 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -99,6 +99,9 @@ ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性! ErrImageBuildErr: "镜像 build 失败" ErrImageExist: "镜像已存在!" ErrDelWithWebsite: "运行环境已经关联网站,无法删除" +ErrRuntimeStart: "启动失败" +ErrPackageJsonNotFound: "package.json 文件不存在" +ErrScriptsNotFound: "没有在 package.json 中找到 scripts 配置项" #setting ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除" diff --git a/backend/init/migration/migrate.go b/backend/init/migration/migrate.go index 461b61cbf..c9a9530e8 100644 --- a/backend/init/migration/migrate.go +++ b/backend/init/migration/migrate.go @@ -45,6 +45,7 @@ func Init() { migrations.DropDatabaseLocal, migrations.AddDefaultNetwork, + migrations.UpdateRuntime, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/backend/init/migration/migrations/v_1_7.go b/backend/init/migration/migrations/v_1_7.go index b1c7f3617..6f1816795 100644 --- a/backend/init/migration/migrations/v_1_7.go +++ b/backend/init/migration/migrations/v_1_7.go @@ -15,3 +15,13 @@ var AddDefaultNetwork = &gormigrate.Migration{ return nil }, } + +var UpdateRuntime = &gormigrate.Migration{ + ID: "20230920-update-runtime", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.Runtime{}); err != nil { + return err + } + return nil + }, +} diff --git a/backend/router/ro_runtime.go b/backend/router/ro_runtime.go index 1c01298e3..148d380d2 100644 --- a/backend/router/ro_runtime.go +++ b/backend/router/ro_runtime.go @@ -20,5 +20,6 @@ func (r *RuntimeRouter) InitRuntimeRouter(Router *gin.RouterGroup) { groupRouter.POST("/del", baseApi.DeleteRuntime) groupRouter.POST("/update", baseApi.UpdateRuntime) groupRouter.GET("/:id", baseApi.GetRuntime) + groupRouter.POST("/node/package", baseApi.GetNodePackageRunScript) } } diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index 68071c20d..8879e48bd 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -1,5 +1,5 @@ -// Code generated by swaggo/swag. DO NOT EDIT. - +// Package docs GENERATED BY SWAG; DO NOT EDIT +// This file was generated by swaggo/swag package docs import "github.com/swaggo/swag" @@ -7830,6 +7830,39 @@ const docTemplate = `{ } } }, + "/runtimes/node/package": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取 Node 项目的 scripts", + "consumes": [ + "application/json" + ], + "tags": [ + "Runtime" + ], + "summary": "Get Node package scripts", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.NodePackageReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/runtimes/search": { "post": { "security": [ @@ -12262,8 +12295,26 @@ const docTemplate = `{ "cpuPercent": { "type": "number" }, + "cpuTotalUsage": { + "type": "integer" + }, + "memoryCache": { + "type": "integer" + }, + "memoryLimit": { + "type": "integer" + }, "memoryPercent": { "type": "number" + }, + "memoryUsage": { + "type": "integer" + }, + "percpuUsage": { + "type": "integer" + }, + "systemUsage": { + "type": "integer" } } }, @@ -16357,6 +16408,14 @@ const docTemplate = `{ } } }, + "request.NodePackageReq": { + "type": "object", + "properties": { + "codeDir": { + "type": "string" + } + } + }, "request.PortUpdate": { "type": "object", "properties": { @@ -16388,9 +16447,18 @@ const docTemplate = `{ "appDetailId": { "type": "integer" }, + "clean": { + "type": "boolean" + }, + "codeDir": { + "type": "string" + }, "image": { "type": "string" }, + "install": { + "type": "boolean" + }, "name": { "type": "string" }, @@ -16415,6 +16483,9 @@ const docTemplate = `{ "request.RuntimeDelete": { "type": "object", "properties": { + "forceDelete": { + "type": "boolean" + }, "id": { "type": "integer" } @@ -16447,12 +16518,21 @@ const docTemplate = `{ "request.RuntimeUpdate": { "type": "object", "properties": { + "clean": { + "type": "boolean" + }, + "codeDir": { + "type": "string" + }, "id": { "type": "integer" }, "image": { "type": "string" }, + "install": { + "type": "boolean" + }, "name": { "type": "string" }, diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index cbc43ccee..12ac5cf97 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -7823,6 +7823,39 @@ } } }, + "/runtimes/node/package": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取 Node 项目的 scripts", + "consumes": [ + "application/json" + ], + "tags": [ + "Runtime" + ], + "summary": "Get Node package scripts", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.NodePackageReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/runtimes/search": { "post": { "security": [ @@ -12255,8 +12288,26 @@ "cpuPercent": { "type": "number" }, + "cpuTotalUsage": { + "type": "integer" + }, + "memoryCache": { + "type": "integer" + }, + "memoryLimit": { + "type": "integer" + }, "memoryPercent": { "type": "number" + }, + "memoryUsage": { + "type": "integer" + }, + "percpuUsage": { + "type": "integer" + }, + "systemUsage": { + "type": "integer" } } }, @@ -16350,6 +16401,14 @@ } } }, + "request.NodePackageReq": { + "type": "object", + "properties": { + "codeDir": { + "type": "string" + } + } + }, "request.PortUpdate": { "type": "object", "properties": { @@ -16381,9 +16440,18 @@ "appDetailId": { "type": "integer" }, + "clean": { + "type": "boolean" + }, + "codeDir": { + "type": "string" + }, "image": { "type": "string" }, + "install": { + "type": "boolean" + }, "name": { "type": "string" }, @@ -16408,6 +16476,9 @@ "request.RuntimeDelete": { "type": "object", "properties": { + "forceDelete": { + "type": "boolean" + }, "id": { "type": "integer" } @@ -16440,12 +16511,21 @@ "request.RuntimeUpdate": { "type": "object", "properties": { + "clean": { + "type": "boolean" + }, + "codeDir": { + "type": "string" + }, "id": { "type": "integer" }, "image": { "type": "string" }, + "install": { + "type": "boolean" + }, "name": { "type": "string" }, diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index 6b4682b68..6f60ff723 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -339,8 +339,20 @@ definitions: type: string cpuPercent: type: number + cpuTotalUsage: + type: integer + memoryCache: + type: integer + memoryLimit: + type: integer memoryPercent: type: number + memoryUsage: + type: integer + percpuUsage: + type: integer + systemUsage: + type: integer type: object dto.ContainerOperate: properties: @@ -3085,6 +3097,11 @@ definitions: required: - scope type: object + request.NodePackageReq: + properties: + codeDir: + type: string + type: object request.PortUpdate: properties: key: @@ -3105,8 +3122,14 @@ definitions: properties: appDetailId: type: integer + clean: + type: boolean + codeDir: + type: string image: type: string + install: + type: boolean name: type: string params: @@ -3123,6 +3146,8 @@ definitions: type: object request.RuntimeDelete: properties: + forceDelete: + type: boolean id: type: integer type: object @@ -3144,10 +3169,16 @@ definitions: type: object request.RuntimeUpdate: properties: + clean: + type: boolean + codeDir: + type: string id: type: integer image: type: string + install: + type: boolean name: type: string params: @@ -9035,6 +9066,26 @@ paths: formatEN: Delete website [name] formatZH: 删除网站 [name] paramKeys: [] + /runtimes/node/package: + post: + consumes: + - application/json + description: 获取 Node 项目的 scripts + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/request.NodePackageReq' + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Get Node package scripts + tags: + - Runtime /runtimes/search: post: consumes: diff --git a/frontend/src/api/interface/runtime.ts b/frontend/src/api/interface/runtime.ts index d951a2fc7..aa88a226d 100644 --- a/frontend/src/api/interface/runtime.ts +++ b/frontend/src/api/interface/runtime.ts @@ -3,7 +3,7 @@ import { App } from './app'; export namespace Runtime { export interface Runtime extends CommonModel { name: string; - appDetailId: number; + appDetailID: number; image: string; workDir: string; dockerCompose: string; @@ -13,46 +13,59 @@ export namespace Runtime { resource: string; version: string; status: string; + codeDir: string; } export interface RuntimeReq extends ReqPage { name?: string; status?: string; + type?: string; + } + + export interface NodeReq { + codeDir: string; + } + + export interface NodeScripts { + name: string; + script: string; } export interface RuntimeDTO extends Runtime { appParams: App.InstallParams[]; - appId: number; + appID: number; source?: string; } export interface RuntimeCreate { id?: number; name: string; - appDetailId: number; + appDetailID: number; image: string; params: object; type: string; resource: string; - appId?: number; + appID?: number; version?: string; rebuild?: boolean; source?: string; + codeDir?: string; } export interface RuntimeUpdate { name: string; - appDetailId: number; + appDetailID: number; image: string; params: object; type: string; resource: string; - appId?: number; + appID?: number; version?: string; rebuild?: boolean; } export interface RuntimeDelete { id: number; + forceDelete: boolean; } } diff --git a/frontend/src/api/modules/runtime.ts b/frontend/src/api/modules/runtime.ts index 8bd7be961..b4c60c24d 100644 --- a/frontend/src/api/modules/runtime.ts +++ b/frontend/src/api/modules/runtime.ts @@ -21,3 +21,7 @@ export const GetRuntime = (id: number) => { export const UpdateRuntime = (req: Runtime.RuntimeUpdate) => { return http.post(`/runtimes/update`, req); }; + +export const GetNodeScripts = (req: Runtime.NodeReq) => { + return http.post(`/runtimes/node/package`, req); +}; diff --git a/frontend/src/components/file-list/index.vue b/frontend/src/components/file-list/index.vue index f70f8ab8e..14cdaeb89 100644 --- a/frontend/src/components/file-list/index.vue +++ b/frontend/src/components/file-list/index.vue @@ -8,7 +8,7 @@ popper-class="file-list" >
@@ -116,6 +116,10 @@ const props = defineProps({ type: Boolean, default: false, }, + disabled: { + type: Boolean, + default: false, + }, }); const em = defineEmits(['choose']); diff --git a/frontend/src/components/status/index.vue b/frontend/src/components/status/index.vue index 8d4611e94..ab52b35ec 100644 --- a/frontend/src/components/status/index.vue +++ b/frontend/src/components/status/index.vue @@ -34,7 +34,7 @@ const getType = (status: string) => { } }; -const loadingStatus = ['installing', 'building', 'restarting', 'upgrading', 'rebuilding']; +const loadingStatus = ['installing', 'building', 'restarting', 'upgrading', 'rebuilding', 'recreating', 'creating']; const loadingIcon = (status: string): boolean => { return loadingStatus.indexOf(status) > -1; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 54c0aa676..c7bffe060 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -228,6 +228,8 @@ const message = { accept: 'Accepted', used: 'Used', unUsed: 'Unused', + starting: 'Starting', + recreating: 'Recreating', }, units: { second: 'Second', @@ -1691,6 +1693,16 @@ const message = { xtomhk: 'XTOM Mirror Station (Hong Kong)', xtom: 'XTOM Mirror Station (Global)', phpsourceHelper: 'Choose the appropriate source according to your network environment', + appPort: 'App Port', + externalPort: 'External Port', + packageManager: 'Package Manager', + codeDir: 'Code Directory', + appPortHelper: 'The port used by the application', + externalPortHelper: 'The port exposed to the outside world', + runScript: 'Run Script', + runScriptHelper: 'The startup command list is parsed from the package.json file in the source directory', + open: 'Open', + close: 'Close', }, process: { pid: 'Process ID', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 994c4a3bd..ba434c641 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -226,6 +226,8 @@ const message = { accept: '已放行', used: '已使用', unUsed: '未使用', + starting: '啟動中', + recreating: '重建中', }, units: { second: '秒', @@ -1600,6 +1602,16 @@ const message = { xtomhk: 'XTOM 鏡像站(香港)', xtom: 'XTOM 鏡像站(全球)', phpsourceHelper: '根據你的網絡環境選擇合適的源', + appPort: '應用端口', + externalPort: '外部映射端口', + packageManager: '包管理器', + codeDir: '源碼目錄', + appPortHelper: '應用端口是指容器內部運行的端口', + externalPortHelper: '外部映射端口是指將容器內部端口映射到外部的端口', + runScript: '啟動命令', + runScriptHelper: '啟動命令是指容器啟動後運行的命令', + open: '開啟', + close: '關閉', }, process: { pid: '進程ID', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index f2907aada..d06d2a516 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -226,6 +226,8 @@ const message = { accept: '已放行', used: '已使用', unUsed: '未使用', + starting: '启动中', + recreating: '重建中', }, units: { second: '秒', @@ -1600,6 +1602,16 @@ const message = { xtomhk: 'XTOM 镜像站(香港)', xtom: 'XTOM 镜像站(全球)', phpsourceHelper: '根据你的网络环境选择合适的源', + appPort: '应用端口', + externalPort: '外部映射端口', + packageManager: '包管理器', + codeDir: '源码目录', + appPortHelper: '应用端口是指容器内部的端口', + externalPortHelper: '外部映射端口是指容器对外暴露的端口', + runScript: '启动命令', + runScriptHelper: '启动命令列表是从源码目录下的 package.json 文件中解析而来', + open: '放开', + close: '关闭', }, process: { pid: '进程ID', diff --git a/frontend/src/routers/modules/website.ts b/frontend/src/routers/modules/website.ts index 2e8a22aba..be69178cf 100644 --- a/frontend/src/routers/modules/website.ts +++ b/frontend/src/routers/modules/website.ts @@ -40,14 +40,24 @@ const webSiteRouter = { }, }, { - path: '/websites/runtime/php', - name: 'Runtime', - component: () => import('@/views/website/runtime/index.vue'), + path: '/websites/runtimes/php', + name: 'PHP', + component: () => import('@/views/website/runtime/php/index.vue'), meta: { title: 'menu.runtime', requiresAuth: false, }, }, + { + path: '/websites/runtimes/node', + name: 'Node', + hidden: true, + component: () => import('@/views/website/runtime/node/index.vue'), + meta: { + activeMenu: '/websites/runtimes/php', + requiresAuth: false, + }, + }, ], }; diff --git a/frontend/src/views/app-store/apps/index.vue b/frontend/src/views/app-store/apps/index.vue index 18a9513ff..9d3acadde 100644 --- a/frontend/src/views/app-store/apps/index.vue +++ b/frontend/src/views/app-store/apps/index.vue @@ -203,13 +203,18 @@ const search = async (req: App.AppReq) => { }; const openInstall = (app: App.App) => { - if (app.type === 'php') { - router.push({ path: '/websites/runtime/php' }); - } else { - const params = { - app: app, - }; - installRef.value.acceptParams(params); + switch (app.type) { + case 'php': + router.push({ path: '/websites/runtimes/php' }); + break; + case 'node': + router.push({ path: '/websites/runtimes/node' }); + break; + default: + const params = { + app: app, + }; + installRef.value.acceptParams(params); } }; diff --git a/frontend/src/views/website/runtime/delete/index.vue b/frontend/src/views/website/runtime/delete/index.vue new file mode 100644 index 000000000..b8f98d5ba --- /dev/null +++ b/frontend/src/views/website/runtime/delete/index.vue @@ -0,0 +1,77 @@ + + + diff --git a/frontend/src/views/website/runtime/index.vue b/frontend/src/views/website/runtime/index.vue index 980b4cdeb..fc7b2f58c 100644 --- a/frontend/src/views/website/runtime/index.vue +++ b/frontend/src/views/website/runtime/index.vue @@ -1,161 +1,21 @@ - - - diff --git a/frontend/src/views/website/runtime/node/create/index.vue b/frontend/src/views/website/runtime/node/create/index.vue new file mode 100644 index 000000000..d1efc08cb --- /dev/null +++ b/frontend/src/views/website/runtime/node/create/index.vue @@ -0,0 +1,337 @@ + + + diff --git a/frontend/src/views/website/runtime/node/index.vue b/frontend/src/views/website/runtime/node/index.vue new file mode 100644 index 000000000..500e1627d --- /dev/null +++ b/frontend/src/views/website/runtime/node/index.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/frontend/src/views/website/runtime/node/operate/index.vue b/frontend/src/views/website/runtime/node/operate/index.vue new file mode 100644 index 000000000..22dd7196e --- /dev/null +++ b/frontend/src/views/website/runtime/node/operate/index.vue @@ -0,0 +1,351 @@ + + + diff --git a/frontend/src/views/website/runtime/create/index.vue b/frontend/src/views/website/runtime/php/create/index.vue similarity index 96% rename from frontend/src/views/website/runtime/create/index.vue rename to frontend/src/views/website/runtime/php/create/index.vue index 957e7129c..fc58eb745 100644 --- a/frontend/src/views/website/runtime/create/index.vue +++ b/frontend/src/views/website/runtime/php/create/index.vue @@ -35,9 +35,9 @@ ({ name: '', - appDetailId: undefined, + appDetailID: undefined, image: '', params: {}, type: type, @@ -192,7 +192,7 @@ let runtime = reactive(initData('php')); const rules = ref({ name: [Rules.appName], resource: [Rules.requiredInput], - appId: [Rules.requiredSelect], + appID: [Rules.requiredSelect], version: [Rules.requiredInput, Rules.paramCommon], image: [Rules.requiredInput, Rules.imageName], source: [Rules.requiredSelect], @@ -238,7 +238,7 @@ const handleClose = () => { const changeResource = (resource: string) => { if (resource === 'local') { - runtime.appDetailId = undefined; + runtime.appDetailID = undefined; runtime.version = ''; runtime.params = {}; runtime.image = ''; @@ -253,7 +253,7 @@ const searchApp = (appId: number) => { apps.value = res.data.items || []; if (res.data && res.data.items && res.data.items.length > 0) { if (appId == null) { - runtime.appId = res.data.items[0].id; + runtime.appID = res.data.items[0].id; getApp(res.data.items[0].key, mode.value); } else { res.data.items.forEach((item) => { @@ -279,9 +279,9 @@ const changeApp = (appId: number) => { const changeVersion = () => { loading.value = true; initParam.value = false; - GetAppDetail(runtime.appId, runtime.version, 'runtime') + GetAppDetail(runtime.appID, runtime.version, 'runtime') .then((res) => { - runtime.appDetailId = res.data.id; + runtime.appDetailID = res.data.id; runtime.image = res.data.image + ':' + runtime.version; appParams.value = res.data.params; initParam.value = true; @@ -342,19 +342,19 @@ const getRuntime = async (id: number) => { Object.assign(runtime, { id: data.id, name: data.name, - appDetailId: data.appDetailId, + appDetailID: data.appDetailID, image: data.image, params: {}, type: data.type, resource: data.resource, - appId: data.appId, + appID: data.appID, version: data.version, rebuild: true, source: data.source, }); editParams.value = data.appParams; if (mode.value == 'create') { - searchApp(data.appId); + searchApp(data.appID); } else { initParam.value = true; } diff --git a/frontend/src/views/website/runtime/edit/index.vue b/frontend/src/views/website/runtime/php/edit/index.vue similarity index 100% rename from frontend/src/views/website/runtime/edit/index.vue rename to frontend/src/views/website/runtime/php/edit/index.vue diff --git a/frontend/src/views/website/runtime/php/index.vue b/frontend/src/views/website/runtime/php/index.vue new file mode 100644 index 000000000..6ca522ead --- /dev/null +++ b/frontend/src/views/website/runtime/php/index.vue @@ -0,0 +1,156 @@ + + + + + diff --git a/frontend/src/views/website/runtime/param/index.vue b/frontend/src/views/website/runtime/php/param/index.vue similarity index 100% rename from frontend/src/views/website/runtime/param/index.vue rename to frontend/src/views/website/runtime/php/param/index.vue