mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-27 20:49:03 +08:00
feat: 运行环境增加 Node.js 管理 (#2390)
Refs https://github.com/1Panel-dev/1Panel/issues/397
This commit is contained in:
parent
38dadf6056
commit
1130a70052
@ -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)
|
||||
}
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 (
|
||||
|
@ -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"
|
||||
|
@ -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."
|
||||
|
@ -99,6 +99,9 @@ ErrFileNotExist: "{{ .detail }} 文件不存在!請檢查源文件完整性!
|
||||
ErrImageBuildErr: "鏡像 build 失敗"
|
||||
ErrImageExist: "鏡像已存在!"
|
||||
ErrDelWithWebsite: "運行環境已經關聯網站,無法刪除"
|
||||
ErrRuntimeStart: "啟動失敗"
|
||||
ErrPackageJsonNotFound: "package.json 文件不存在"
|
||||
ErrScriptsNotFound: "沒有在 package.json 中找到 scripts 配置項"
|
||||
|
||||
#setting
|
||||
ErrBackupInUsed: "該備份帳號已在計劃任務中使用,無法刪除"
|
||||
|
@ -99,6 +99,9 @@ ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性!
|
||||
ErrImageBuildErr: "镜像 build 失败"
|
||||
ErrImageExist: "镜像已存在!"
|
||||
ErrDelWithWebsite: "运行环境已经关联网站,无法删除"
|
||||
ErrRuntimeStart: "启动失败"
|
||||
ErrPackageJsonNotFound: "package.json 文件不存在"
|
||||
ErrScriptsNotFound: "没有在 package.json 中找到 scripts 配置项"
|
||||
|
||||
#setting
|
||||
ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除"
|
||||
|
@ -45,6 +45,7 @@ func Init() {
|
||||
migrations.DropDatabaseLocal,
|
||||
|
||||
migrations.AddDefaultNetwork,
|
||||
migrations.UpdateRuntime,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
@ -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
|
||||
},
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -21,3 +21,7 @@ export const GetRuntime = (id: number) => {
|
||||
export const UpdateRuntime = (req: Runtime.RuntimeUpdate) => {
|
||||
return http.post<any>(`/runtimes/update`, req);
|
||||
};
|
||||
|
||||
export const GetNodeScripts = (req: Runtime.NodeReq) => {
|
||||
return http.post<Runtime.NodeScripts[]>(`/runtimes/node/package`, req);
|
||||
};
|
||||
|
@ -8,7 +8,7 @@
|
||||
popper-class="file-list"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button :icon="Folder" @click="popoverVisible = true"></el-button>
|
||||
<el-button :icon="Folder" :disabled="disabled" @click="popoverVisible = true"></el-button>
|
||||
</template>
|
||||
<div>
|
||||
<el-button class="close" link @click="closePage">
|
||||
@ -116,6 +116,10 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const em = defineEmits(['choose']);
|
||||
|
@ -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;
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
77
frontend/src/views/website/runtime/delete/index.vue
Normal file
77
frontend/src/views/website/runtime/delete/index.vue
Normal file
@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="open"
|
||||
:close-on-click-modal="false"
|
||||
:title="$t('commons.button.delete') + ' - ' + resourceName"
|
||||
width="30%"
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<div :key="key" :loading="loading">
|
||||
<el-form ref="deleteForm" label-position="left">
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="deleteReq.forceDelete" :label="$t('website.forceDelete')" />
|
||||
<span class="input-help">
|
||||
{{ $t('website.forceDeleteHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit()" :loading="loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DeleteRuntime } from '@/api/modules/runtime';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const key = 1;
|
||||
const open = ref(false);
|
||||
const loading = ref(false);
|
||||
const deleteReq = ref({
|
||||
id: 0,
|
||||
forceDelete: false,
|
||||
});
|
||||
const em = defineEmits(['close']);
|
||||
const deleteForm = ref<FormInstance>();
|
||||
const resourceName = ref('');
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
em('close', false);
|
||||
};
|
||||
|
||||
const acceptParams = async (id: number, name: string) => {
|
||||
deleteReq.value = {
|
||||
id: id,
|
||||
forceDelete: false,
|
||||
};
|
||||
resourceName.value = name;
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
const submit = () => {
|
||||
loading.value = true;
|
||||
DeleteRuntime(deleteReq.value)
|
||||
.then(() => {
|
||||
handleClose();
|
||||
MsgSuccess(i18n.global.t('commons.msg.deleteSuccess'));
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
@ -1,161 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<RouterButton
|
||||
:buttons="[
|
||||
{
|
||||
label: 'PHP',
|
||||
path: '/runtimes/php',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<LayoutContent :title="$t('runtime.runtime')" v-loading="loading">
|
||||
<template #toolbar>
|
||||
<el-button type="primary" @click="openCreate">
|
||||
{{ $t('runtime.create') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()">
|
||||
<el-table-column :label="$t('commons.table.name')" fix prop="name" min-width="120px">
|
||||
<template #default="{ row }">
|
||||
<Tooltip :text="row.name" @click="openDetail(row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('runtime.resource')" prop="resource">
|
||||
<template #default="{ row }">
|
||||
<span>{{ $t('runtime.' + toLowerCase(row.resource)) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('runtime.version')" prop="version"></el-table-column>
|
||||
<el-table-column :label="$t('runtime.image')" prop="image" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('commons.table.status')" prop="status">
|
||||
<template #default="{ row }">
|
||||
<el-popover
|
||||
v-if="row.status === 'error'"
|
||||
placement="bottom"
|
||||
:width="400"
|
||||
trigger="hover"
|
||||
:content="row.message"
|
||||
>
|
||||
<template #reference>
|
||||
<Status :key="row.status" :status="row.status"></Status>
|
||||
</template>
|
||||
</el-popover>
|
||||
<div v-else>
|
||||
<Status :key="row.status" :status="row.status"></Status>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
:label="$t('commons.table.date')"
|
||||
:formatter="dateFormat"
|
||||
show-overflow-tooltip
|
||||
min-width="120"
|
||||
fix
|
||||
/>
|
||||
<fu-table-operations
|
||||
:ellipsis="10"
|
||||
width="120px"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
fixed="right"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
<RouterButton :buttons="buttons" />
|
||||
<LayoutContent>
|
||||
<router-view></router-view>
|
||||
</LayoutContent>
|
||||
<CreateRuntime ref="createRef" @close="search" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||
import { Runtime } from '@/api/interface/runtime';
|
||||
import { DeleteRuntime, SearchRuntimes } from '@/api/modules/runtime';
|
||||
import { dateFormat, toLowerCase } from '@/utils/util';
|
||||
import CreateRuntime from '@/views/website/runtime/create/index.vue';
|
||||
import Status from '@/components/status/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'runtime-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
let req = reactive<Runtime.RuntimeReq>({
|
||||
name: '',
|
||||
page: 1,
|
||||
pageSize: 40,
|
||||
});
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
|
||||
<script lang="ts" setup>
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
click: function (row: Runtime.Runtime) {
|
||||
openDetail(row);
|
||||
},
|
||||
disabled: function (row: Runtime.Runtime) {
|
||||
return row.status === 'building';
|
||||
},
|
||||
label: 'PHP',
|
||||
path: '/websites/runtimes/php',
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
click: function (row: Runtime.Runtime) {
|
||||
openDelete(row);
|
||||
},
|
||||
label: 'Node.js',
|
||||
path: '/websites/runtimes/node',
|
||||
},
|
||||
];
|
||||
const loading = ref(false);
|
||||
const items = ref<Runtime.RuntimeDTO[]>([]);
|
||||
const createRef = ref();
|
||||
|
||||
const search = async () => {
|
||||
req.page = paginationConfig.currentPage;
|
||||
req.pageSize = paginationConfig.pageSize;
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await SearchRuntimes(req);
|
||||
items.value = res.data.items;
|
||||
paginationConfig.total = res.data.total;
|
||||
} catch (error) {
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const openCreate = () => {
|
||||
createRef.value.acceptParams({ type: 'php', mode: 'create' });
|
||||
};
|
||||
|
||||
const openDetail = (row: Runtime.Runtime) => {
|
||||
createRef.value.acceptParams({ type: row.type, mode: 'edit', id: row.id });
|
||||
};
|
||||
|
||||
const openDelete = async (row: Runtime.Runtime) => {
|
||||
await useDeleteData(DeleteRuntime, { id: row.id }, 'commons.msg.delete');
|
||||
search();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
timer = setInterval(() => {
|
||||
search();
|
||||
}, 10000 * 3);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(Number(timer));
|
||||
timer = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.open-warn {
|
||||
color: $primary-color;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
337
frontend/src/views/website/runtime/node/create/index.vue
Normal file
337
frontend/src/views/website/runtime/node/create/index.vue
Normal file
@ -0,0 +1,337 @@
|
||||
<template>
|
||||
<el-drawer :close-on-click-modal="false" v-model="open" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('runtime.' + mode)" :resource="runtime.name" :back="handleClose" />
|
||||
</template>
|
||||
<el-row v-loading="loading">
|
||||
<el-col :span="22" :offset="1">
|
||||
<el-form
|
||||
ref="runtimeForm"
|
||||
label-position="top"
|
||||
:model="runtime"
|
||||
label-width="125px"
|
||||
:rules="rules"
|
||||
:validate-on-rule-change="false"
|
||||
>
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input :disabled="mode === 'edit'" v-model="runtime.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('runtime.app')" prop="appId">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-select
|
||||
v-model="runtime.appId"
|
||||
:disabled="mode === 'edit'"
|
||||
@change="changeApp(runtime.appId)"
|
||||
>
|
||||
<el-option
|
||||
v-for="(app, index) in apps"
|
||||
:key="index"
|
||||
:label="app.name"
|
||||
:value="app.id"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-select
|
||||
v-model="runtime.version"
|
||||
:disabled="mode === 'edit'"
|
||||
@change="changeVersion()"
|
||||
>
|
||||
<el-option
|
||||
v-for="(version, index) in appVersions"
|
||||
:key="index"
|
||||
:label="version"
|
||||
:value="version"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('runtime.codeDir')" prop="codeDir">
|
||||
<el-input v-model.trim="runtime.codeDir">
|
||||
<template #prepend>
|
||||
<FileList :path="runtime.codeDir" @choose="getPath" :dir="true"></FileList>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('runtime.runScript')" prop="params.EXEC_SCRIPT">
|
||||
<el-select v-model="runtime.params['EXEC_SCRIPT']">
|
||||
<el-option
|
||||
v-for="(script, index) in scripts"
|
||||
:key="index"
|
||||
:label="script.name + ' 【 ' + script.script + ' 】'"
|
||||
:value="script.name"
|
||||
>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="4">{{ script.name }}</el-col>
|
||||
<el-col :span="10">{{ ' 【 ' + script.script + ' 】' }}</el-col>
|
||||
</el-row>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="10">
|
||||
<el-form-item :label="$t('runtime.appPort')" prop="params.NODE_APP_PORT">
|
||||
<el-input v-model.number="runtime.params['NODE_APP_PORT']" />
|
||||
<span class="input-help">{{ $t('runtime.appPortHelper') }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item :label="$t('runtime.externalPort')" prop="params.PANEL_APP_PORT_HTTP">
|
||||
<el-input v-model.number="runtime.params['PANEL_APP_PORT_HTTP']" />
|
||||
<span class="input-help">{{ $t('runtime.externalPortHelper') }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="4">
|
||||
<el-form-item :label="$t('app.allowPort')" prop="params.HOST_IP">
|
||||
<el-select v-model="runtime.params['HOST_IP']">
|
||||
<el-option label="放开" value="0.0.0.0"></el-option>
|
||||
<el-option label="不放开" value="127.0.0.1"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item :label="$t('runtime.packageManager')" prop="params.PACKAGE_MANAGER">
|
||||
<el-select v-model="runtime.params['PACKAGE_MANAGER']">
|
||||
<el-option label="npm" value="npm"></el-option>
|
||||
<el-option label="yarn" value="yarn"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('app.containerName')" prop="params.CONTAINER_NAME">
|
||||
<el-input v-model.trim="runtime.params['CONTAINER_NAME']"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer>
|
||||
<span>
|
||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit(runtimeForm)" :disabled="loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { App } from '@/api/interface/app';
|
||||
import { Runtime } from '@/api/interface/runtime';
|
||||
import { GetApp, GetAppDetail, SearchApp } from '@/api/modules/app';
|
||||
import { CreateRuntime, GetNodeScripts, GetRuntime, UpdateRuntime } from '@/api/modules/runtime';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
|
||||
interface OperateRrops {
|
||||
id?: number;
|
||||
mode: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
const open = ref(false);
|
||||
const apps = ref<App.App[]>([]);
|
||||
const runtimeForm = ref<FormInstance>();
|
||||
const loading = ref(false);
|
||||
const mode = ref('create');
|
||||
const editParams = ref<App.InstallParams[]>();
|
||||
const appVersions = ref<string[]>([]);
|
||||
const appReq = reactive({
|
||||
type: 'node',
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
const initData = (type: string) => ({
|
||||
name: '',
|
||||
appDetailId: undefined,
|
||||
image: '',
|
||||
params: {
|
||||
PACKAGE_MANAGER: 'npm',
|
||||
HOST_IP: '0.0.0.0',
|
||||
},
|
||||
type: type,
|
||||
resource: 'appstore',
|
||||
rebuild: false,
|
||||
codeDir: '/',
|
||||
});
|
||||
let runtime = reactive<Runtime.RuntimeCreate>(initData('node'));
|
||||
const rules = ref<any>({
|
||||
name: [Rules.appName],
|
||||
appId: [Rules.requiredSelect],
|
||||
codeDir: [Rules.requiredInput],
|
||||
params: {
|
||||
NODE_APP_PORT: [Rules.requiredInput, Rules.port],
|
||||
PANEL_APP_PORT_HTTP: [Rules.requiredInput, Rules.port],
|
||||
PACKAGE_MANAGER: [Rules.requiredSelect],
|
||||
HOST_IP: [Rules.requiredSelect],
|
||||
EXEC_SCRIPT: [Rules.requiredSelect],
|
||||
CONTAINER_NAME: [Rules.requiredInput],
|
||||
},
|
||||
});
|
||||
const scripts = ref<Runtime.NodeScripts[]>([]);
|
||||
const em = defineEmits(['close']);
|
||||
|
||||
watch(
|
||||
() => runtime.params['NODE_APP_PORT'],
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
runtime.params['PANEL_APP_PORT_HTTP'] = newVal;
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => runtime.name,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
runtime.params['CONTAINER_NAME'] = newVal;
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
em('close', false);
|
||||
};
|
||||
|
||||
const getPath = (codeDir: string) => {
|
||||
runtime.codeDir = codeDir;
|
||||
getScripts();
|
||||
};
|
||||
|
||||
const getScripts = () => {
|
||||
GetNodeScripts({ codeDir: runtime.codeDir }).then((res) => {
|
||||
scripts.value = res.data;
|
||||
if (scripts.value.length > 0) {
|
||||
runtime.params['EXEC_SCRIPT'] = scripts.value[0].script;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const searchApp = (appId: number) => {
|
||||
SearchApp(appReq).then((res) => {
|
||||
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;
|
||||
getApp(res.data.items[0].key, mode.value);
|
||||
} else {
|
||||
res.data.items.forEach((item) => {
|
||||
if (item.id === appId) {
|
||||
getApp(item.key, mode.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const changeApp = (appId: number) => {
|
||||
for (const app of apps.value) {
|
||||
if (app.id === appId) {
|
||||
getApp(app.key, mode.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const changeVersion = () => {
|
||||
loading.value = true;
|
||||
GetAppDetail(runtime.appId, runtime.version, 'runtime')
|
||||
.then((res) => {
|
||||
runtime.appDetailId = res.data.id;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const getApp = (appkey: string, mode: string) => {
|
||||
GetApp(appkey).then((res) => {
|
||||
appVersions.value = res.data.versions || [];
|
||||
if (res.data.versions.length > 0) {
|
||||
runtime.version = res.data.versions[0];
|
||||
if (mode === 'create') {
|
||||
changeVersion();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
if (mode.value == 'create') {
|
||||
loading.value = true;
|
||||
CreateRuntime(runtime)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||
handleClose();
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
} else {
|
||||
loading.value = true;
|
||||
UpdateRuntime(runtime)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
|
||||
handleClose();
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getRuntime = async (id: number) => {
|
||||
try {
|
||||
const res = await GetRuntime(id);
|
||||
const data = res.data;
|
||||
Object.assign(runtime, {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
appDetailId: data.appDetailId,
|
||||
image: data.image,
|
||||
params: {},
|
||||
type: data.type,
|
||||
resource: data.resource,
|
||||
appId: data.appId,
|
||||
version: data.version,
|
||||
rebuild: true,
|
||||
source: data.source,
|
||||
});
|
||||
editParams.value = data.appParams;
|
||||
if (mode.value == 'create') {
|
||||
searchApp(data.appId);
|
||||
}
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const acceptParams = async (props: OperateRrops) => {
|
||||
mode.value = props.mode;
|
||||
if (props.mode === 'create') {
|
||||
Object.assign(runtime, initData(props.type));
|
||||
searchApp(null);
|
||||
} else {
|
||||
searchApp(null);
|
||||
getRuntime(props.id);
|
||||
}
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
164
frontend/src/views/website/runtime/node/index.vue
Normal file
164
frontend/src/views/website/runtime/node/index.vue
Normal file
@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<div>
|
||||
<RouterMenu />
|
||||
<LayoutContent :title="'Node.js'" v-loading="loading">
|
||||
<template #toolbar>
|
||||
<el-button type="primary" @click="openCreate">
|
||||
{{ $t('runtime.create') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()">
|
||||
<el-table-column :label="$t('commons.table.name')" fix prop="name" min-width="120px">
|
||||
<template #default="{ row }">
|
||||
<Tooltip :text="row.name" @click="openDetail(row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('runtime.codeDir')" prop="codeDir">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="toFolder(row.codeDir)">
|
||||
<el-icon>
|
||||
<FolderOpened />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('runtime.version')" prop="version"></el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('runtime.externalPort')"
|
||||
prop="params.PANEL_APP_PORT_HTTP"
|
||||
></el-table-column>
|
||||
<el-table-column :label="$t('commons.table.status')" prop="status">
|
||||
<template #default="{ row }">
|
||||
<el-popover
|
||||
v-if="row.status === 'error'"
|
||||
placement="bottom"
|
||||
:width="400"
|
||||
trigger="hover"
|
||||
:content="row.message"
|
||||
>
|
||||
<template #reference>
|
||||
<Status :key="row.status" :status="row.status"></Status>
|
||||
</template>
|
||||
</el-popover>
|
||||
<div v-else>
|
||||
<Status :key="row.status" :status="row.status"></Status>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
:label="$t('commons.table.date')"
|
||||
:formatter="dateFormat"
|
||||
show-overflow-tooltip
|
||||
min-width="120"
|
||||
fix
|
||||
/>
|
||||
<fu-table-operations
|
||||
:ellipsis="10"
|
||||
width="120px"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
fixed="right"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
<OperateNode ref="operateRef" @close="search" />
|
||||
<Delete ref="deleteRef" @close="search()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||
import { Runtime } from '@/api/interface/runtime';
|
||||
import { SearchRuntimes } from '@/api/modules/runtime';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import OperateNode from '@/views/website/runtime/node/operate/index.vue';
|
||||
import Status from '@/components/status/index.vue';
|
||||
import Delete from '@/views/website/runtime/delete/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import RouterMenu from '../index.vue';
|
||||
import router from '@/routers/router';
|
||||
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'runtime-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
const req = reactive<Runtime.RuntimeReq>({
|
||||
name: '',
|
||||
page: 1,
|
||||
pageSize: 40,
|
||||
type: 'node',
|
||||
});
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
click: function (row: Runtime.Runtime) {
|
||||
openDetail(row);
|
||||
},
|
||||
disabled: function (row: Runtime.Runtime) {
|
||||
return row.status === 'starting' || row.status === 'recreating';
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
click: function (row: Runtime.Runtime) {
|
||||
openDelete(row);
|
||||
},
|
||||
},
|
||||
];
|
||||
const loading = ref(false);
|
||||
const items = ref<Runtime.RuntimeDTO[]>([]);
|
||||
const operateRef = ref();
|
||||
const deleteRef = ref();
|
||||
|
||||
const search = async () => {
|
||||
req.page = paginationConfig.currentPage;
|
||||
req.pageSize = paginationConfig.pageSize;
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await SearchRuntimes(req);
|
||||
items.value = res.data.items;
|
||||
paginationConfig.total = res.data.total;
|
||||
} catch (error) {
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const openCreate = () => {
|
||||
operateRef.value.acceptParams({ type: 'node', mode: 'create' });
|
||||
};
|
||||
|
||||
const openDetail = (row: Runtime.Runtime) => {
|
||||
operateRef.value.acceptParams({ type: row.type, mode: 'edit', id: row.id });
|
||||
};
|
||||
|
||||
const openDelete = async (row: Runtime.Runtime) => {
|
||||
deleteRef.value.acceptParams(row.id, row.name);
|
||||
};
|
||||
|
||||
const toFolder = (folder: string) => {
|
||||
router.push({ path: '/hosts/files', query: { path: folder } });
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
timer = setInterval(() => {
|
||||
search();
|
||||
}, 10000 * 3);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(Number(timer));
|
||||
timer = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
351
frontend/src/views/website/runtime/node/operate/index.vue
Normal file
351
frontend/src/views/website/runtime/node/operate/index.vue
Normal file
@ -0,0 +1,351 @@
|
||||
<template>
|
||||
<el-drawer :close-on-click-modal="false" v-model="open" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader
|
||||
:header="$t('runtime.' + mode)"
|
||||
:hideResource="mode == 'create'"
|
||||
:resource="runtime.name"
|
||||
:back="handleClose"
|
||||
/>
|
||||
</template>
|
||||
<el-row v-loading="loading">
|
||||
<el-col :span="22" :offset="1">
|
||||
<el-form
|
||||
ref="runtimeForm"
|
||||
label-position="top"
|
||||
:model="runtime"
|
||||
label-width="125px"
|
||||
:rules="rules"
|
||||
:validate-on-rule-change="false"
|
||||
>
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input :disabled="mode === 'edit'" v-model="runtime.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('runtime.app')" prop="appID">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-select
|
||||
v-model="runtime.appID"
|
||||
:disabled="mode === 'edit'"
|
||||
@change="changeApp(runtime.appID)"
|
||||
>
|
||||
<el-option
|
||||
v-for="(app, index) in apps"
|
||||
:key="index"
|
||||
:label="app.name"
|
||||
:value="app.id"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-select
|
||||
v-model="runtime.version"
|
||||
:disabled="mode === 'edit'"
|
||||
@change="changeVersion()"
|
||||
>
|
||||
<el-option
|
||||
v-for="(version, index) in appVersions"
|
||||
:key="index"
|
||||
:label="version"
|
||||
:value="version"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('runtime.codeDir')" prop="codeDir">
|
||||
<el-input v-model.trim="runtime.codeDir" :disabled="mode === 'edit'">
|
||||
<template #prepend>
|
||||
<FileList
|
||||
:disabled="mode === 'edit'"
|
||||
:path="runtime.codeDir"
|
||||
@choose="getPath"
|
||||
:dir="true"
|
||||
></FileList>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('runtime.runScript')" prop="params.EXEC_SCRIPT">
|
||||
<el-select v-model="runtime.params['EXEC_SCRIPT']">
|
||||
<el-option
|
||||
v-for="(script, index) in scripts"
|
||||
:key="index"
|
||||
:label="script.name + ' 【 ' + script.script + ' 】'"
|
||||
:value="script.name"
|
||||
>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="4">{{ script.name }}</el-col>
|
||||
<el-col :span="10">{{ ' 【 ' + script.script + ' 】' }}</el-col>
|
||||
</el-row>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<span class="input-help">{{ $t('runtime.runScriptHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="10">
|
||||
<el-form-item :label="$t('runtime.appPort')" prop="params.NODE_APP_PORT">
|
||||
<el-input v-model.number="runtime.params['NODE_APP_PORT']" />
|
||||
<span class="input-help">{{ $t('runtime.appPortHelper') }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item :label="$t('runtime.externalPort')" prop="params.PANEL_APP_PORT_HTTP">
|
||||
<el-input v-model.number="runtime.params['PANEL_APP_PORT_HTTP']" />
|
||||
<span class="input-help">{{ $t('runtime.externalPortHelper') }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="4">
|
||||
<el-form-item :label="$t('app.allowPort')" prop="params.HOST_IP">
|
||||
<el-select v-model="runtime.params['HOST_IP']">
|
||||
<el-option :label="$t('runtime.open')" value="0.0.0.0"></el-option>
|
||||
<el-option :label="$t('runtime.close')" value="127.0.0.1"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item :label="$t('runtime.packageManager')" prop="params.PACKAGE_MANAGER">
|
||||
<el-select v-model="runtime.params['PACKAGE_MANAGER']">
|
||||
<el-option label="npm" value="npm"></el-option>
|
||||
<el-option label="yarn" value="yarn"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('app.containerName')" prop="params.CONTAINER_NAME">
|
||||
<el-input v-model.trim="runtime.params['CONTAINER_NAME']"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer>
|
||||
<span>
|
||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit(runtimeForm)" :disabled="loading">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { App } from '@/api/interface/app';
|
||||
import { Runtime } from '@/api/interface/runtime';
|
||||
import { GetApp, GetAppDetail, SearchApp } from '@/api/modules/app';
|
||||
import { CreateRuntime, GetNodeScripts, GetRuntime, UpdateRuntime } from '@/api/modules/runtime';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
|
||||
interface OperateRrops {
|
||||
id?: number;
|
||||
mode: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
const open = ref(false);
|
||||
const apps = ref<App.App[]>([]);
|
||||
const runtimeForm = ref<FormInstance>();
|
||||
const loading = ref(false);
|
||||
const mode = ref('create');
|
||||
const editParams = ref<App.InstallParams[]>();
|
||||
const appVersions = ref<string[]>([]);
|
||||
const appReq = reactive({
|
||||
type: 'node',
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
const initData = (type: string) => ({
|
||||
name: '',
|
||||
appDetailID: undefined,
|
||||
image: '',
|
||||
params: {
|
||||
PACKAGE_MANAGER: 'npm',
|
||||
HOST_IP: '0.0.0.0',
|
||||
},
|
||||
type: type,
|
||||
resource: 'appstore',
|
||||
rebuild: false,
|
||||
codeDir: '/',
|
||||
});
|
||||
let runtime = reactive<Runtime.RuntimeCreate>(initData('node'));
|
||||
const rules = ref<any>({
|
||||
name: [Rules.appName],
|
||||
appID: [Rules.requiredSelect],
|
||||
codeDir: [Rules.requiredInput],
|
||||
params: {
|
||||
NODE_APP_PORT: [Rules.requiredInput, Rules.port],
|
||||
PANEL_APP_PORT_HTTP: [Rules.requiredInput, Rules.port],
|
||||
PACKAGE_MANAGER: [Rules.requiredSelect],
|
||||
HOST_IP: [Rules.requiredSelect],
|
||||
EXEC_SCRIPT: [Rules.requiredSelect],
|
||||
CONTAINER_NAME: [Rules.requiredInput],
|
||||
},
|
||||
});
|
||||
const scripts = ref<Runtime.NodeScripts[]>([]);
|
||||
const em = defineEmits(['close']);
|
||||
|
||||
watch(
|
||||
() => runtime.params['NODE_APP_PORT'],
|
||||
(newVal) => {
|
||||
if (newVal && mode.value == 'create') {
|
||||
runtime.params['PANEL_APP_PORT_HTTP'] = newVal;
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => runtime.name,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
runtime.params['CONTAINER_NAME'] = newVal;
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
em('close', false);
|
||||
runtimeForm.value?.resetFields();
|
||||
};
|
||||
|
||||
const getPath = (codeDir: string) => {
|
||||
runtime.codeDir = codeDir;
|
||||
getScripts();
|
||||
};
|
||||
|
||||
const getScripts = () => {
|
||||
GetNodeScripts({ codeDir: runtime.codeDir }).then((res) => {
|
||||
scripts.value = res.data;
|
||||
if (mode.value == 'create' && scripts.value.length > 0) {
|
||||
runtime.params['EXEC_SCRIPT'] = scripts.value[0].name;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const searchApp = (appID: number) => {
|
||||
SearchApp(appReq).then((res) => {
|
||||
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;
|
||||
getApp(res.data.items[0].key, mode.value);
|
||||
} else {
|
||||
res.data.items.forEach((item) => {
|
||||
if (item.id === appID) {
|
||||
getApp(item.key, mode.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const changeApp = (appID: number) => {
|
||||
for (const app of apps.value) {
|
||||
if (app.id === appID) {
|
||||
getApp(app.key, mode.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const changeVersion = () => {
|
||||
loading.value = true;
|
||||
GetAppDetail(runtime.appID, runtime.version, 'runtime')
|
||||
.then((res) => {
|
||||
runtime.appDetailID = res.data.id;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const getApp = (appkey: string, mode: string) => {
|
||||
GetApp(appkey).then((res) => {
|
||||
appVersions.value = res.data.versions || [];
|
||||
if (res.data.versions.length > 0) {
|
||||
runtime.version = res.data.versions[0];
|
||||
if (mode === 'create') {
|
||||
changeVersion();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
if (mode.value == 'create') {
|
||||
loading.value = true;
|
||||
CreateRuntime(runtime)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||
handleClose();
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
} else {
|
||||
loading.value = true;
|
||||
UpdateRuntime(runtime)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
|
||||
handleClose();
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getRuntime = async (id: number) => {
|
||||
try {
|
||||
const res = await GetRuntime(id);
|
||||
const data = res.data;
|
||||
Object.assign(runtime, {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
appDetailId: data.appDetailID,
|
||||
image: data.image,
|
||||
type: data.type,
|
||||
resource: data.resource,
|
||||
appID: data.appID,
|
||||
version: data.version,
|
||||
rebuild: true,
|
||||
source: data.source,
|
||||
params: data.params,
|
||||
codeDir: data.codeDir,
|
||||
});
|
||||
editParams.value = data.appParams;
|
||||
if (mode.value == 'edit') {
|
||||
searchApp(data.appID);
|
||||
}
|
||||
getScripts();
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const acceptParams = async (props: OperateRrops) => {
|
||||
mode.value = props.mode;
|
||||
scripts.value = [];
|
||||
if (props.mode === 'create') {
|
||||
Object.assign(runtime, initData(props.type));
|
||||
searchApp(null);
|
||||
} else {
|
||||
getRuntime(props.id);
|
||||
}
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
@ -35,9 +35,9 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-select
|
||||
v-model="runtime.appId"
|
||||
v-model="runtime.appID"
|
||||
:disabled="mode === 'edit'"
|
||||
@change="changeApp(runtime.appId)"
|
||||
@change="changeApp(runtime.appID)"
|
||||
>
|
||||
<el-option
|
||||
v-for="(app, index) in apps"
|
||||
@ -178,7 +178,7 @@ const appReq = reactive({
|
||||
});
|
||||
const initData = (type: string) => ({
|
||||
name: '',
|
||||
appDetailId: undefined,
|
||||
appDetailID: undefined,
|
||||
image: '',
|
||||
params: {},
|
||||
type: type,
|
||||
@ -192,7 +192,7 @@ let runtime = reactive<Runtime.RuntimeCreate>(initData('php'));
|
||||
const rules = ref<any>({
|
||||
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;
|
||||
}
|
156
frontend/src/views/website/runtime/php/index.vue
Normal file
156
frontend/src/views/website/runtime/php/index.vue
Normal file
@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div>
|
||||
<RouterMenu />
|
||||
<LayoutContent :title="'PHP'" v-loading="loading">
|
||||
<template #toolbar>
|
||||
<el-button type="primary" @click="openCreate">
|
||||
{{ $t('runtime.create') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()">
|
||||
<el-table-column :label="$t('commons.table.name')" fix prop="name" min-width="120px">
|
||||
<template #default="{ row }">
|
||||
<Tooltip :text="row.name" @click="openDetail(row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('runtime.resource')" prop="resource">
|
||||
<template #default="{ row }">
|
||||
<span>{{ $t('runtime.' + toLowerCase(row.resource)) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('runtime.version')" prop="version"></el-table-column>
|
||||
<el-table-column :label="$t('runtime.image')" prop="image" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('commons.table.status')" prop="status">
|
||||
<template #default="{ row }">
|
||||
<el-popover
|
||||
v-if="row.status === 'error'"
|
||||
placement="bottom"
|
||||
:width="400"
|
||||
trigger="hover"
|
||||
:content="row.message"
|
||||
>
|
||||
<template #reference>
|
||||
<Status :key="row.status" :status="row.status"></Status>
|
||||
</template>
|
||||
</el-popover>
|
||||
<div v-else>
|
||||
<Status :key="row.status" :status="row.status"></Status>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createdAt"
|
||||
:label="$t('commons.table.date')"
|
||||
:formatter="dateFormat"
|
||||
show-overflow-tooltip
|
||||
min-width="120"
|
||||
fix
|
||||
/>
|
||||
<fu-table-operations
|
||||
:ellipsis="10"
|
||||
width="120px"
|
||||
:buttons="buttons"
|
||||
:label="$t('commons.table.operate')"
|
||||
fixed="right"
|
||||
fix
|
||||
/>
|
||||
</ComplexTable>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
<CreateRuntime ref="createRef" @close="search" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||
import { Runtime } from '@/api/interface/runtime';
|
||||
import { DeleteRuntime, SearchRuntimes } from '@/api/modules/runtime';
|
||||
import { dateFormat, toLowerCase } from '@/utils/util';
|
||||
import CreateRuntime from '@/views/website/runtime/php/create/index.vue';
|
||||
import Status from '@/components/status/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||
import RouterMenu from '../index.vue';
|
||||
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'runtime-page-size',
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
let req = reactive<Runtime.RuntimeReq>({
|
||||
name: '',
|
||||
page: 1,
|
||||
pageSize: 40,
|
||||
type: 'php',
|
||||
});
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('commons.button.edit'),
|
||||
click: function (row: Runtime.Runtime) {
|
||||
openDetail(row);
|
||||
},
|
||||
disabled: function (row: Runtime.Runtime) {
|
||||
return row.status === 'building';
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
click: function (row: Runtime.Runtime) {
|
||||
openDelete(row);
|
||||
},
|
||||
},
|
||||
];
|
||||
const loading = ref(false);
|
||||
const items = ref<Runtime.RuntimeDTO[]>([]);
|
||||
const createRef = ref();
|
||||
|
||||
const search = async () => {
|
||||
req.page = paginationConfig.currentPage;
|
||||
req.pageSize = paginationConfig.pageSize;
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await SearchRuntimes(req);
|
||||
items.value = res.data.items;
|
||||
paginationConfig.total = res.data.total;
|
||||
} catch (error) {
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const openCreate = () => {
|
||||
createRef.value.acceptParams({ type: 'php', mode: 'create' });
|
||||
};
|
||||
|
||||
const openDetail = (row: Runtime.Runtime) => {
|
||||
createRef.value.acceptParams({ type: row.type, mode: 'edit', id: row.id });
|
||||
};
|
||||
|
||||
const openDelete = async (row: Runtime.Runtime) => {
|
||||
await useDeleteData(DeleteRuntime, { id: row.id }, 'commons.msg.delete');
|
||||
search();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
timer = setInterval(() => {
|
||||
search();
|
||||
}, 10000 * 3);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(Number(timer));
|
||||
timer = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.open-warn {
|
||||
color: $primary-color;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user