feat: 增加运行环境编辑功能

pull/515/head
zhengkunwang223 2 years ago committed by zhengkunwang223
parent 22d9bdacf6
commit d4c1caa26a

@ -22,4 +22,9 @@ build_linux_on_mac:
cd $(SERVER_PATH) \ cd $(SERVER_PATH) \
&& CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-linux-musl-gcc CXX=x86_64-linux-musl-g++ $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-static -fpic"' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN) && CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-linux-musl-gcc CXX=x86_64-linux-musl-g++ $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-static -fpic"' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
build_on_archlinux:
cd $(SERVER_PATH) \
&& CGO_ENABLED=1 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-fpic"' -tags osusergo -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
build_all: build_web build_bin build_all: build_web build_bin

@ -77,3 +77,47 @@ func (b *BaseApi) DeleteRuntime(c *gin.Context) {
} }
helper.SuccessWithOutData(c) helper.SuccessWithOutData(c)
} }
// @Tags Runtime
// @Summary Update runtime
// @Description 更新运行环境
// @Accept json
// @Param request body request.RuntimeUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/update [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"更新运行环境 [name]","formatEN":"Update runtime [name]"}
func (b *BaseApi) UpdateRuntime(c *gin.Context) {
var req request.RuntimeUpdate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := runtimeService.Update(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}
// @Tags Runtime
// @Summary Get runtime
// @Description 获取运行环境
// @Accept json
// @Param id path string true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/:id [get]
func (b *BaseApi) GetRuntime(c *gin.Context) {
id, err := helper.GetIntParamByKey(c, "id")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil)
return
}
res, err := runtimeService.Get(id)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, res)
}

@ -78,6 +78,7 @@ type AppFormFields struct {
Disabled bool `json:"disabled"` Disabled bool `json:"disabled"`
Edit bool `json:"edit"` Edit bool `json:"edit"`
Rule string `json:"rule"` Rule string `json:"rule"`
Multiple bool `json:"multiple"`
Values []AppFormValue `json:"values"` Values []AppFormValue `json:"values"`
} }

@ -19,5 +19,13 @@ type RuntimeCreate struct {
} }
type RuntimeDelete struct { type RuntimeDelete struct {
ID uint `json:"Id"` ID uint `json:"id"`
}
type RuntimeUpdate struct {
Name string `json:"name"`
ID uint `json:"id"`
Params map[string]interface{} `json:"params"`
Image string `json:"image"`
Version string `json:"version"`
} }

@ -43,6 +43,7 @@ type AppDetailDTO struct {
model.AppDetail model.AppDetail
Enable bool `json:"enable"` Enable bool `json:"enable"`
Params interface{} `json:"params"` Params interface{} `json:"params"`
Image string `json:"image"`
} }
type AppInstalledDTO struct { type AppInstalledDTO struct {
@ -70,4 +71,6 @@ type AppParam struct {
Type string `json:"type"` Type string `json:"type"`
Values interface{} `json:"values"` Values interface{} `json:"values"`
ShowValue string `json:"showValue"` ShowValue string `json:"showValue"`
Required bool `json:"required"`
Multiple bool `json:"multiple"`
} }

@ -4,4 +4,7 @@ import "github.com/1Panel-dev/1Panel/backend/app/model"
type RuntimeRes struct { type RuntimeRes struct {
model.Runtime model.Runtime
AppParams []AppParam `json:"appParams"`
AppID uint `json:"appId"`
Version string `json:"version"`
} }

@ -3,12 +3,15 @@ package repo
import ( import (
"context" "context"
"github.com/1Panel-dev/1Panel/backend/app/model" "github.com/1Panel-dev/1Panel/backend/app/model"
"gorm.io/gorm"
) )
type RuntimeRepo struct { type RuntimeRepo struct {
} }
type IRuntimeRepo interface { type IRuntimeRepo interface {
WithNameOrImage(name string, image string) DBOption
WithOtherNameOrImage(name string, image string, id uint) DBOption
Page(page, size int, opts ...DBOption) (int64, []model.Runtime, error) Page(page, size int, opts ...DBOption) (int64, []model.Runtime, error)
Create(ctx context.Context, runtime *model.Runtime) error Create(ctx context.Context, runtime *model.Runtime) error
Save(runtime *model.Runtime) error Save(runtime *model.Runtime) error
@ -20,6 +23,18 @@ func NewIRunTimeRepo() IRuntimeRepo {
return &RuntimeRepo{} return &RuntimeRepo{}
} }
func (r *RuntimeRepo) WithNameOrImage(name string, image string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("name = ? or image = ?", name, image)
}
}
func (r *RuntimeRepo) WithOtherNameOrImage(name string, image string, id uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("name = ? or image = ? and id != ?", name, image, id)
}
}
func (r *RuntimeRepo) Page(page, size int, opts ...DBOption) (int64, []model.Runtime, error) { func (r *RuntimeRepo) Page(page, size int, opts ...DBOption) (int64, []model.Runtime, error) {
var runtimes []model.Runtime var runtimes []model.Runtime
db := getDb(opts...).Model(&model.Runtime{}) db := getDb(opts...).Model(&model.Runtime{})

@ -156,8 +156,9 @@ func (a AppService) GetAppDetail(appId uint, version, appType string) (response.
if err != nil { if err != nil {
return appDetailDTO, err return appDetailDTO, err
} }
paramsPath := path.Join(constant.AppResourceDir, app.Key, "versions", detail.Version, "build", "config.json")
fileOp := files.NewFileOp() fileOp := files.NewFileOp()
buildPath := path.Join(constant.AppResourceDir, app.Key, "versions", detail.Version, "build")
paramsPath := path.Join(buildPath, "config.json")
if !fileOp.Stat(paramsPath) { if !fileOp.Stat(paramsPath) {
return appDetailDTO, buserr.New(constant.ErrFileNotExist) return appDetailDTO, buserr.New(constant.ErrFileNotExist)
} }
@ -170,6 +171,24 @@ func (a AppService) GetAppDetail(appId uint, version, appType string) (response.
return appDetailDTO, err return appDetailDTO, err
} }
appDetailDTO.Params = paramMap 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 { } else {
paramMap := make(map[string]interface{}) paramMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(detail.Params), &paramMap); err != nil { if err := json.Unmarshal([]byte(detail.Params), &paramMap); err != nil {

@ -2,18 +2,20 @@ package service
import ( import (
"context" "context"
"encoding/json"
"fmt" "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/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response" "github.com/1Panel-dev/1Panel/backend/app/dto/response"
"github.com/1Panel-dev/1Panel/backend/app/model" "github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo" "github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/files" "github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/subosito/gotenv" "github.com/subosito/gotenv"
"path" "path"
"path/filepath" "path/filepath"
"strings"
"time" "time"
) )
@ -24,13 +26,19 @@ type IRuntimeService interface {
Page(req request.RuntimeSearch) (int64, []response.RuntimeRes, error) Page(req request.RuntimeSearch) (int64, []response.RuntimeRes, error)
Create(create request.RuntimeCreate) error Create(create request.RuntimeCreate) error
Delete(id uint) error Delete(id uint) error
Update(req request.RuntimeUpdate) error
Get(id uint) (res *response.RuntimeRes, err error)
} }
func NewRuntimeService() IRuntimeService { func NewRuntimeService() IRuntimeService {
return &RuntimeService{} return &RuntimeService{}
} }
func (r *RuntimeService) Create(create request.RuntimeCreate) error { func (r *RuntimeService) Create(create request.RuntimeCreate) (err error) {
exist, _ := runtimeRepo.GetFirst(runtimeRepo.WithNameOrImage(create.Name, create.Image))
if exist != nil {
return buserr.New(constant.ErrNameOrImageIsExist)
}
if create.Resource == constant.ResourceLocal { if create.Resource == constant.ResourceLocal {
runtime := &model.Runtime{ runtime := &model.Runtime{
Name: create.Name, Name: create.Name,
@ -40,7 +48,6 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) error {
} }
return runtimeRepo.Create(context.Background(), runtime) return runtimeRepo.Create(context.Background(), runtime)
} }
var err error
appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(create.AppDetailID)) appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(create.AppDetailID))
if err != nil { if err != nil {
return err return err
@ -56,8 +63,8 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) error {
} }
runtimeDir := path.Join(constant.RuntimeDir, create.Type) runtimeDir := path.Join(constant.RuntimeDir, create.Type)
tempDir := filepath.Join(runtimeDir, fmt.Sprintf("%d", time.Now().UnixNano())) tempDir := filepath.Join(runtimeDir, fmt.Sprintf("%d", time.Now().UnixNano()))
if err := fileOp.CopyDir(buildDir, tempDir); err != nil { if err = fileOp.CopyDir(buildDir, tempDir); err != nil {
return err return
} }
oldDir := path.Join(tempDir, "build") oldDir := path.Join(tempDir, "build")
newNameDir := path.Join(runtimeDir, create.Name) newNameDir := path.Join(runtimeDir, create.Name)
@ -67,58 +74,38 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) error {
} }
}() }()
if oldDir != newNameDir { if oldDir != newNameDir {
if err := fileOp.Rename(oldDir, newNameDir); err != nil { if err = fileOp.Rename(oldDir, newNameDir); err != nil {
return err return
} }
if err := fileOp.DeleteDir(tempDir); err != nil { if err = fileOp.DeleteDir(tempDir); err != nil {
return err return
} }
} }
composeFile, err := fileOp.GetContent(path.Join(newNameDir, "docker-compose.yml")) composeContent, envContent, forms, err := handleParams(create.Image, create.Type, newNameDir, create.Params)
if err != nil { if err != nil {
return err return
} }
env, err := gotenv.Read(path.Join(newNameDir, ".env")) composeService, err := getComposeService(create.Name, newNameDir, composeContent, envContent)
if err != nil { if err != nil {
return err return
}
newMap := make(map[string]string)
handleMap(create.Params, newMap)
for k, v := range newMap {
env[k] = v
}
envStr, err := gotenv.Marshal(env)
if err != nil {
return err
}
if err := gotenv.Write(env, path.Join(newNameDir, ".env")); err != nil {
return err
}
project, err := docker.GetComposeProject(create.Name, newNameDir, composeFile, []byte(envStr))
if err != nil {
return err
} }
composeService, err := docker.NewComposeService()
if err != nil {
return err
}
composeService.SetProject(project)
runtime := &model.Runtime{ runtime := &model.Runtime{
Name: create.Name, Name: create.Name,
DockerCompose: string(composeFile), DockerCompose: string(composeContent),
Env: envStr, Env: string(envContent),
AppDetailID: create.AppDetailID, AppDetailID: create.AppDetailID,
Type: create.Type, Type: create.Type,
Image: create.Image, Image: create.Image,
Resource: create.Resource, Resource: create.Resource,
Status: constant.RuntimeBuildIng, Status: constant.RuntimeBuildIng,
Version: create.Version, Version: create.Version,
Params: string(forms),
} }
if err := runtimeRepo.Create(context.Background(), runtime); err != nil { if err = runtimeRepo.Create(context.Background(), runtime); err != nil {
return err return
} }
go buildRuntime(runtime, composeService) go buildRuntime(runtime, composeService)
return nil return
} }
func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.RuntimeRes, error) { func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.RuntimeRes, error) {
@ -155,3 +142,97 @@ func (r *RuntimeService) Delete(id uint) error {
} }
return runtimeRepo.DeleteBy(commonRepo.WithByID(id)) return runtimeRepo.DeleteBy(commonRepo.WithByID(id))
} }
func (r *RuntimeService) Get(id uint) (*response.RuntimeRes, error) {
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(id))
if err != nil {
return nil, err
}
res := &response.RuntimeRes{}
res.Runtime = *runtime
if runtime.Resource == constant.ResourceLocal {
return res, nil
}
appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(runtime.AppDetailID))
if err != nil {
return nil, err
}
res.AppID = appDetail.AppId
res.Version = appDetail.Version
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
}
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 {
appParam.Value = strings.Split(v, ",")
} else {
for _, fv := range form.Values {
if fv.Value == v {
appParam.ShowValue = fv.Label
break
}
}
}
appParam.Values = form.Values
}
appParams = append(appParams, appParam)
}
}
res.AppParams = appParams
return res, nil
}
func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
exist, _ := runtimeRepo.GetFirst(runtimeRepo.WithOtherNameOrImage(req.Name, req.Image, req.ID))
if exist != nil {
return buserr.New(constant.ErrNameOrImageIsExist)
}
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(req.ID))
if err != nil {
return err
}
if runtime.Resource == constant.ResourceLocal {
runtime.Version = req.Version
return runtimeRepo.Save(runtime)
}
runtimeDir := path.Join(constant.RuntimeDir, runtime.Type, runtime.Name)
composeContent, envContent, _, err := handleParams(req.Image, runtime.Type, runtimeDir, req.Params)
if err != nil {
return err
}
composeService, err := getComposeService(runtime.Name, runtimeDir, composeContent, envContent)
if err != nil {
return err
}
runtime.Image = req.Image
runtime.Env = string(envContent)
runtime.DockerCompose = string(composeContent)
runtime.Status = constant.RuntimeBuildIng
_ = runtimeRepo.Save(runtime)
go buildRuntime(runtime, composeService)
return nil
}

@ -1,10 +1,15 @@
package service package service
import ( import (
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/model" "github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/docker" "github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/subosito/gotenv"
"path"
"strings"
) )
func buildRuntime(runtime *model.Runtime, service *docker.ComposeService) { func buildRuntime(runtime *model.Runtime, service *docker.ComposeService) {
@ -17,3 +22,58 @@ func buildRuntime(runtime *model.Runtime, service *docker.ComposeService) {
} }
_ = runtimeRepo.Save(runtime) _ = runtimeRepo.Save(runtime)
} }
func handleParams(image, runtimeType, runtimeDir string, params map[string]interface{}) (composeContent []byte, envContent []byte, forms []byte, err error) {
fileOp := files.NewFileOp()
composeContent, err = fileOp.GetContent(path.Join(runtimeDir, "docker-compose.yml"))
if err != nil {
return
}
env, err := gotenv.Read(path.Join(runtimeDir, ".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 {
if extendsArray, ok := extends.([]interface{}); ok {
strArray := make([]string, len(extendsArray))
for i, v := range extendsArray {
strArray[i] = fmt.Sprintf("%v", v)
}
params["PHP_EXTENSIONS"] = strings.Join(strArray, ",")
}
}
}
newMap := make(map[string]string)
handleMap(params, newMap)
for k, v := range newMap {
env[k] = v
}
envStr, err := gotenv.Marshal(env)
if err != nil {
return
}
if err = gotenv.Write(env, path.Join(runtimeDir, ".env")); err != nil {
return
}
envContent = []byte(envStr)
return
}
func getComposeService(name, runtimeDir string, composeFile, env []byte) (*docker.ComposeService, error) {
project, err := docker.GetComposeProject(name, runtimeDir, composeFile, env)
if err != nil {
return nil, err
}
composeService, err := docker.NewComposeService()
if err != nil {
return nil, err
}
composeService.SetProject(project)
return composeService, nil
}

@ -109,4 +109,5 @@ var (
ErrDirNotFound = "ErrDirNotFound" ErrDirNotFound = "ErrDirNotFound"
ErrFileNotExist = "ErrFileNotExist" ErrFileNotExist = "ErrFileNotExist"
ErrImageBuildErr = "ErrImageBuildErr" ErrImageBuildErr = "ErrImageBuildErr"
ErrNameOrImageIsExist = "ErrNameOrImageIsExist"
) )

@ -7,4 +7,6 @@ const (
RuntimeNormal = "normal" RuntimeNormal = "normal"
RuntimeError = "error" RuntimeError = "error"
RuntimeBuildIng = "building" RuntimeBuildIng = "building"
RuntimePHP = "php"
) )

@ -64,3 +64,4 @@ ErrObjectInUsed: "This object is in use and cannot be deleted"
ErrDirNotFound: "The build folder does not exist! Please check file integrity" ErrDirNotFound: "The build folder does not exist! Please check file integrity"
ErrFileNotExist: "{{ .detail }} file does not exist! Please check source file integrity" ErrFileNotExist: "{{ .detail }} file does not exist! Please check source file integrity"
ErrImageBuildErr: "Image build failed" ErrImageBuildErr: "Image build failed"
ErrNameOrImageIsExist: "Duplicate name or image"

@ -64,3 +64,4 @@ ErrObjectInUsed: "该对象正被使用,无法删除"
ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!" ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!"
ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性!" ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性!"
ErrImageBuildErr: "镜像 build 失败" ErrImageBuildErr: "镜像 build 失败"
ErrNameOrImageIsExist: "名称或者镜像重复"

@ -20,7 +20,7 @@ func Init() {
baseDir := "/opt" baseDir := "/opt"
port := "9999" port := "9999"
mode := "" mode := ""
version := "" version := "v1.0.0"
fileOp := files.NewFileOp() fileOp := files.NewFileOp()
v := viper.NewWithOptions() v := viper.NewWithOptions()
v.SetConfigType("yaml") v.SetConfigType("yaml")

@ -18,5 +18,7 @@ func (r *RuntimeRouter) InitRuntimeRouter(Router *gin.RouterGroup) {
groupRouter.POST("/search", baseApi.SearchRuntimes) groupRouter.POST("/search", baseApi.SearchRuntimes)
groupRouter.POST("", baseApi.CreateRuntime) groupRouter.POST("", baseApi.CreateRuntime)
groupRouter.POST("/del", baseApi.DeleteRuntime) groupRouter.POST("/del", baseApi.DeleteRuntime)
groupRouter.POST("/update", baseApi.UpdateRuntime)
groupRouter.GET("/:id", baseApi.GetRuntime)
} }
} }

@ -40,6 +40,7 @@ export namespace App {
params: AppParams; params: AppParams;
dockerCompose: string; dockerCompose: string;
enbale: boolean; enbale: boolean;
image: string;
} }
export interface AppReq extends ReqPage { export interface AppReq extends ReqPage {
@ -166,5 +167,7 @@ export namespace App {
type: string; type: string;
values?: any; values?: any;
showValue?: string; showValue?: string;
required?: boolean;
multiple?: boolean;
} }
} }

@ -1,14 +1,16 @@
import { CommonModel, ReqPage } from '.'; import { CommonModel, ReqPage } from '.';
import { App } from './app';
export namespace Runtime { export namespace Runtime {
export interface Runtime extends CommonModel { export interface Runtime extends CommonModel {
name: string; name: string;
appDetailId: string; appDetailId: number;
image: string; image: string;
workDir: string; workDir: string;
dockerCompose: string; dockerCompose: string;
env: string; env: string;
params: string; params: string;
type: string; type: string;
resource: string;
} }
export interface RuntimeReq extends ReqPage { export interface RuntimeReq extends ReqPage {
@ -16,10 +18,24 @@ export namespace Runtime {
} }
export interface RuntimeDTO extends Runtime { export interface RuntimeDTO extends Runtime {
websites: string[]; appParams: App.InstallParams[];
appId: number;
version: string;
} }
export interface RuntimeCreate { export interface RuntimeCreate {
id?: number;
name: string;
appDetailId: number;
image: string;
params: object;
type: string;
resource: string;
appId?: number;
version?: string;
}
export interface RuntimeUpdate {
name: string; name: string;
appDetailId: number; appDetailId: number;
image: string; image: string;

@ -13,3 +13,11 @@ export const CreateRuntime = (req: Runtime.RuntimeCreate) => {
export const DeleteRuntime = (req: Runtime.RuntimeDelete) => { export const DeleteRuntime = (req: Runtime.RuntimeDelete) => {
return http.post<any>(`/runtimes/del`, req); return http.post<any>(`/runtimes/del`, req);
}; };
export const GetRuntime = (id: number) => {
return http.get<Runtime.RuntimeDTO>(`/runtimes/${id}`);
};
export const UpdateRuntime = (req: Runtime.RuntimeUpdate) => {
return http.post<any>(`/runtimes/update`, req);
};

@ -8,18 +8,19 @@
<el-form <el-form
ref="runtimeForm" ref="runtimeForm"
label-position="top" label-position="top"
:model="runtimeCreate" :model="runtime"
label-width="125px" label-width="125px"
:rules="rules" :rules="rules"
:validate-on-rule-change="false" :validate-on-rule-change="false"
> >
<el-form-item :label="$t('runtime.name')" prop="name"> <el-form-item :label="$t('runtime.name')" prop="name">
<el-input v-model="runtimeCreate.name"></el-input> <el-input :disabled="mode === 'edit'" v-model="runtime.name"></el-input>
</el-form-item> </el-form-item>
<el-form-item :label="$t('runtime.resource')" prop="resource"> <el-form-item :label="$t('runtime.resource')" prop="resource">
<el-radio-group <el-radio-group
v-model="runtimeCreate.resource" :disabled="mode === 'edit'"
@change="changeResource(runtimeCreate.resource)" v-model="runtime.resource"
@change="changeResource(runtime.resource)"
> >
<el-radio :label="'AppStore'" :value="'AppStore'"> <el-radio :label="'AppStore'" :value="'AppStore'">
{{ $t('runtime.appstore') }} {{ $t('runtime.appstore') }}
@ -29,11 +30,11 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<div v-if="runtimeCreate.resource === 'AppStore'"> <div v-if="runtime.resource === 'AppStore'">
<el-form-item :label="$t('runtime.app')" prop="appId"> <el-form-item :label="$t('runtime.app')" prop="appId">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-select v-model="runtimeCreate.appId"> <el-select v-model="runtime.appId" :disabled="mode === 'edit'">
<el-option <el-option
v-for="(app, index) in apps" v-for="(app, index) in apps"
:key="index" :key="index"
@ -43,7 +44,7 @@
</el-select> </el-select>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-select v-model="runtimeCreate.version"> <el-select v-model="runtime.version" :disabled="mode === 'edit'">
<el-option <el-option
v-for="(version, index) in appVersions" v-for="(version, index) in appVersions"
:key="index" :key="index"
@ -54,17 +55,30 @@
</el-col> </el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
<el-form-item :label="$t('runtime.image')" prop="image">
<el-input v-model="runtime.image"></el-input>
</el-form-item>
<div v-if="initParam">
<Params <Params
v-if="initParam" v-if="mode === 'create'"
v-model:form="runtimeCreate" v-model:form="runtime.params"
v-model:params="appParams" v-model:params="appParams"
v-model:rules="rules" v-model:rules="rules"
></Params> ></Params>
<EditParams
v-if="mode === 'edit'"
v-model:form="runtime.params"
v-model:params="editParams"
v-model:rules="rules"
></EditParams>
</div>
</div> </div>
<div v-else> <div v-else>
<el-form-item>
<el-alert :title="$t('runtime.localHelper')" type="info" :closable="false" /> <el-alert :title="$t('runtime.localHelper')" type="info" :closable="false" />
</el-form-item>
<el-form-item :label="$t('runtime.version')" prop="version"> <el-form-item :label="$t('runtime.version')" prop="version">
<el-input v-model="runtimeCreate.version"></el-input> <el-input v-model="runtime.version"></el-input>
</el-form-item> </el-form-item>
</div> </div>
</el-form> </el-form>
@ -85,32 +99,41 @@
import { App } from '@/api/interface/app'; import { App } from '@/api/interface/app';
import { Runtime } from '@/api/interface/runtime'; import { Runtime } from '@/api/interface/runtime';
import { GetApp, GetAppDetail, SearchApp } from '@/api/modules/app'; import { GetApp, GetAppDetail, SearchApp } from '@/api/modules/app';
import { CreateRuntime } from '@/api/modules/runtime'; import { CreateRuntime, GetRuntime, UpdateRuntime } from '@/api/modules/runtime';
import { Rules } from '@/global/form-rules'; import { Rules } from '@/global/form-rules';
import i18n from '@/lang'; import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import { FormInstance } from 'element-plus'; import { FormInstance } from 'element-plus';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import Params from '../param/index.vue'; import Params from '../param/index.vue';
import EditParams from '../edit/index.vue';
interface OperateRrops {
id?: number;
mode: string;
type: string;
}
const open = ref(false); const open = ref(false);
const apps = ref<App.App[]>([]); const apps = ref<App.App[]>([]);
const runtimeForm = ref<FormInstance>(); const runtimeForm = ref<FormInstance>();
const loading = ref(false); const loading = ref(false);
const initParam = ref(false); const initParam = ref(false);
const mode = ref('create');
let appParams = ref<App.AppParams>(); let appParams = ref<App.AppParams>();
let editParams = ref<App.InstallParams[]>();
let appVersions = ref<string[]>([]); let appVersions = ref<string[]>([]);
let appReq = reactive({ let appReq = reactive({
type: 'php', type: 'php',
page: 1, page: 1,
pageSize: 20, pageSize: 20,
}); });
const runtimeCreate = ref<Runtime.RuntimeCreate>({ const runtime = ref<Runtime.RuntimeCreate>({
name: '', name: '',
appDetailId: undefined, appDetailId: undefined,
image: '', image: '',
params: {}, params: {},
type: '', type: 'php',
resource: 'AppStore', resource: 'AppStore',
}); });
let rules = ref<any>({ let rules = ref<any>({
@ -118,6 +141,7 @@ let rules = ref<any>({
resource: [Rules.requiredInput], resource: [Rules.requiredInput],
appId: [Rules.requiredSelect], appId: [Rules.requiredSelect],
version: [Rules.requiredInput], version: [Rules.requiredInput],
image: [Rules.requiredInput, Rules.imageName],
}); });
const em = defineEmits(['close']); const em = defineEmits(['close']);
@ -129,35 +153,48 @@ const handleClose = () => {
const changeResource = (resource: string) => { const changeResource = (resource: string) => {
if (resource === 'Local') { if (resource === 'Local') {
runtimeCreate.value.appDetailId = undefined; runtime.value.appDetailId = undefined;
runtimeCreate.value.version = ''; runtime.value.version = '';
runtimeCreate.value.params = {}; runtime.value.params = {};
runtimeCreate.value.image = ''; runtime.value.image = '';
} else { } else {
runtimeCreate.value.version = ''; runtime.value.version = '';
searchApp(); searchApp(null);
} }
}; };
const searchApp = () => { const searchApp = (appId: number) => {
SearchApp(appReq).then((res) => { SearchApp(appReq).then((res) => {
apps.value = res.data.items || []; apps.value = res.data.items || [];
if (res.data && res.data.items && res.data.items.length > 0) { if (res.data && res.data.items && res.data.items.length > 0) {
runtimeCreate.value.appId = res.data.items[0].id; if (appId == null) {
getApp(res.data.items[0].key); runtime.value.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 getApp = (appkey: string) => { const getApp = (appkey: string, mode: string) => {
GetApp(appkey).then((res) => { GetApp(appkey).then((res) => {
appVersions.value = res.data.versions || []; appVersions.value = res.data.versions || [];
if (res.data.versions.length > 0) { if (res.data.versions.length > 0) {
runtimeCreate.value.version = res.data.versions[0]; runtime.value.version = res.data.versions[0];
GetAppDetail(runtimeCreate.value.appId, runtimeCreate.value.version, 'runtime').then((res) => { if (mode === 'create') {
runtimeCreate.value.appDetailId = res.data.id; GetAppDetail(runtime.value.appId, runtime.value.version, 'runtime').then((res) => {
runtime.value.appDetailId = res.data.id;
runtime.value.image = res.data.image + ':' + runtime.value.version;
appParams.value = res.data.params; appParams.value = res.data.params;
initParam.value = true; initParam.value = true;
}); });
} else {
initParam.value = true;
}
} }
}); });
}; };
@ -169,7 +206,8 @@ const submit = async (formEl: FormInstance | undefined) => {
return; return;
} }
loading.value = true; loading.value = true;
CreateRuntime(runtimeCreate.value) if (mode.value == 'create') {
CreateRuntime(runtime.value)
.then(() => { .then(() => {
MsgSuccess(i18n.global.t('commons.msg.createSuccess')); MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
handleClose(); handleClose();
@ -177,19 +215,54 @@ const submit = async (formEl: FormInstance | undefined) => {
.finally(() => { .finally(() => {
loading.value = false; loading.value = false;
}); });
} else {
UpdateRuntime(runtime.value)
.then(() => {
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
handleClose();
})
.finally(() => {
loading.value = false;
});
}
}); });
}; };
const acceptParams = async (type: string) => { const getRuntime = async (id: number) => {
runtimeCreate.value = { try {
const res = await GetRuntime(id);
const data = res.data;
runtime.value = {
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,
};
editParams.value = data.appParams;
searchApp(data.appId);
} catch (error) {}
};
const acceptParams = async (props: OperateRrops) => {
mode.value = props.mode;
if (props.mode === 'create') {
runtime.value = {
name: '', name: '',
appDetailId: undefined, appDetailId: undefined,
image: '', image: '',
params: {}, params: {},
type: type, type: props.type,
resource: 'AppStore', resource: 'AppStore',
}; };
searchApp(); searchApp(null);
} else {
getRuntime(props.id);
}
open.value = true; open.value = true;
}; };

@ -0,0 +1,109 @@
<template>
<div v-for="(p, index) in paramObjs" :key="index">
<el-form-item :label="getLabel(p)" :prop="p.prop">
<el-select
v-model="form[p.key]"
v-if="p.type == 'select'"
:multiple="p.multiple"
filterable
@change="updateParam"
>
<el-option
v-for="service in p.values"
:key="service.label"
:value="service.value"
:label="service.label"
></el-option>
</el-select>
</el-form-item>
</div>
</template>
<script setup lang="ts">
import { App } from '@/api/interface/app';
import { Rules } from '@/global/form-rules';
import { computed, onMounted, reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
interface ParamObj extends App.InstallParams {
prop: string;
}
const props = defineProps({
form: {
type: Object,
default: function () {
return {};
},
},
params: {
type: Array<App.InstallParams>,
default: function () {
return [];
},
},
rules: {
type: Object,
default: function () {
return {};
},
},
});
let form = reactive({});
let rules = reactive({});
const params = computed({
get() {
return props.params;
},
set() {},
});
const emit = defineEmits(['update:form', 'update:rules']);
const updateParam = () => {
emit('update:form', form);
};
const paramObjs = ref<ParamObj[]>([]);
const handleParams = () => {
rules = props.rules;
if (params.value != undefined) {
for (const p of params.value) {
form[p.key] = p.value;
paramObjs.value.push({
prop: p.key,
labelEn: p.labelEn,
labelZh: p.labelZh,
values: p.values,
value: p.value,
required: p.required,
edit: p.edit,
key: p.key,
rule: p.rule,
type: p.type,
multiple: p.multiple,
});
if (p.required) {
if (p.type === 'select') {
rules[p.key] = [Rules.requiredSelect];
} else {
rules[p.key] = [Rules.requiredInput];
}
}
}
emit('update:rules', rules);
updateParam();
}
};
const getLabel = (row: ParamObj): string => {
const language = useI18n().locale.value;
if (language == 'zh') {
return row.labelZh;
} else {
return row.labelEn;
}
};
onMounted(() => {
handleParams();
});
</script>

@ -27,7 +27,7 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('runtime.version')" prop="version"></el-table-column> <el-table-column :label="$t('runtime.version')" prop="version"></el-table-column>
<el-table-column :label="$t('runtime.image')" prop="image"></el-table-column> <el-table-column :label="$t('runtime.image')" prop="image" show-overflow-tooltip></el-table-column>
<el-table-column :label="$t('runtime.status')" prop="status"> <el-table-column :label="$t('runtime.status')" prop="status">
<template #default="{ row }"> <template #default="{ row }">
<el-popover <el-popover
@ -92,6 +92,12 @@ let req = reactive<Runtime.RuntimeReq>({
}); });
const buttons = [ const buttons = [
{
label: i18n.global.t('commons.button.edit'),
click: function (row: Runtime.Runtime) {
openDetail(row);
},
},
{ {
label: i18n.global.t('commons.button.delete'), label: i18n.global.t('commons.button.delete'),
click: function (row: Runtime.Runtime) { click: function (row: Runtime.Runtime) {
@ -118,7 +124,11 @@ const search = async () => {
}; };
const openCreate = () => { const openCreate = () => {
createRef.value.acceptParams('php'); 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) => { const openDelete = async (row: Runtime.Runtime) => {

@ -1,7 +1,13 @@
<template> <template>
<div v-for="(p, index) in paramObjs" :key="index"> <div v-for="(p, index) in paramObjs" :key="index">
<el-form-item :label="getLabel(p)" :prop="p.prop"> <el-form-item :label="getLabel(p)" :prop="p.prop">
<el-select v-model="form[p.envKey]" v-if="p.type == 'select'" :multiple="p.multiple" filterable> <el-select
v-model="form[p.envKey]"
v-if="p.type == 'select'"
:multiple="p.multiple"
filterable
@change="updateParam"
>
<el-option <el-option
v-for="service in p.values" v-for="service in p.values"
:key="service.label" :key="service.label"
@ -54,20 +60,21 @@ const params = computed({
}, },
set() {}, set() {},
}); });
const emit = defineEmits(['update:form', 'update:rules']);
const updateParam = () => {
emit('update:form', form);
};
const paramObjs = ref<ParamObj[]>([]); const paramObjs = ref<ParamObj[]>([]);
const handleParams = () => { const handleParams = () => {
rules = props.rules; rules = props.rules;
form = props.form;
if (params.value != undefined && params.value.formFields != undefined) { if (params.value != undefined && params.value.formFields != undefined) {
for (const p of params.value.formFields) { for (const p of params.value.formFields) {
const pObj = p; const pObj = p;
pObj.prop = p.envKey; pObj.prop = p.envKey;
pObj.disabled = p.disabled; pObj.disabled = p.disabled;
form[p.envKey] = p.default; form[p.envKey] = '';
paramObjs.value.push(pObj); paramObjs.value.push(pObj);
console.log(p);
if (p.required) { if (p.required) {
if (p.type === 'select') { if (p.type === 'select') {
rules[p.envKey] = [Rules.requiredSelect]; rules[p.envKey] = [Rules.requiredSelect];
@ -76,6 +83,8 @@ const handleParams = () => {
} }
} }
} }
emit('update:rules', rules);
updateParam();
} }
}; };

Loading…
Cancel
Save