feat: 增加创建 php 运行环境功能
This commit is contained in:

committed by
zhengkunwang223

parent
1949be2490
commit
64a954df53
@@ -76,9 +76,10 @@ func (b *BaseApi) GetApp(c *gin.Context) {
|
||||
// @Accept json
|
||||
// @Param appId path integer true "app id"
|
||||
// @Param version path string true "app 版本"
|
||||
// @Param version path string true "app 类型"
|
||||
// @Success 200 {object} response.AppDetailDTO
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /apps/detail/:appId/:version [get]
|
||||
// @Router /apps/detail/:appId/:version/:type [get]
|
||||
func (b *BaseApi) GetAppDetail(c *gin.Context) {
|
||||
appId, err := helper.GetIntParamByKey(c, "appId")
|
||||
if err != nil {
|
||||
@@ -86,7 +87,8 @@ func (b *BaseApi) GetAppDetail(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
version := c.Param("version")
|
||||
appDetailDTO, err := appService.GetAppDetail(appId, version)
|
||||
appType := c.Param("type")
|
||||
appDetailDTO, err := appService.GetAppDetail(appId, version, appType)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
|
@@ -32,3 +32,25 @@ func (b *BaseApi) SearchRuntimes(c *gin.Context) {
|
||||
Items: items,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Runtime
|
||||
// @Summary Create runtime
|
||||
// @Description 创建运行环境
|
||||
// @Accept json
|
||||
// @Param request body request.RuntimeCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /runtimes [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建运行环境 [name]","formatEN":"Create runtime [name]"}
|
||||
func (b *BaseApi) CreateRuntime(c *gin.Context) {
|
||||
var req request.RuntimeCreate
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := runtimeService.Create(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
@@ -7,3 +7,13 @@ type RuntimeSearch struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type RuntimeCreate struct {
|
||||
AppDetailID uint `json:"appDetailId"`
|
||||
Name string `json:"name"`
|
||||
Params map[string]interface{} `json:"params"`
|
||||
Resource string `json:"resource"`
|
||||
Image string `json:"image"`
|
||||
Type string `json:"type"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
@@ -4,10 +4,14 @@ type Runtime struct {
|
||||
BaseModel
|
||||
Name string `gorm:"type:varchar;not null" json:"name"`
|
||||
AppDetailID uint `gorm:"type:integer" json:"appDetailId"`
|
||||
Image string `gorm:"type:varchar;not null" json:"image"`
|
||||
WorkDir string `gorm:"type:varchar;not null" json:"workDir"`
|
||||
DockerCompose string `gorm:"type:varchar;not null" json:"dockerCompose"`
|
||||
Env string `gorm:"type:varchar;not null" json:"env"`
|
||||
Params string `gorm:"type:varchar;not null" json:"params"`
|
||||
Image string `gorm:"type:varchar" json:"image"`
|
||||
WorkDir string `gorm:"type:varchar" json:"workDir"`
|
||||
DockerCompose string `gorm:"type:varchar" json:"dockerCompose"`
|
||||
Env string `gorm:"type:varchar" json:"env"`
|
||||
Params string `gorm:"type:varchar" json:"params"`
|
||||
Version string `gorm:"type:varchar;not null" json:"version"`
|
||||
Type string `gorm:"type:varchar;not null" json:"type"`
|
||||
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"`
|
||||
}
|
||||
|
@@ -58,7 +58,7 @@ func (a AppRepo) Page(page, size int, opts ...DBOption) (int64, []model.App, err
|
||||
db := getDb(opts...).Model(&model.App{})
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Preload("AppTags").Find(&apps).Error
|
||||
err := db.Debug().Limit(size).Offset(size * (page - 1)).Preload("AppTags").Find(&apps).Error
|
||||
return count, apps, err
|
||||
}
|
||||
|
||||
|
@@ -32,7 +32,7 @@ type IAppService interface {
|
||||
PageApp(req request.AppSearch) (interface{}, error)
|
||||
GetAppTags() ([]response.TagDTO, error)
|
||||
GetApp(key string) (*response.AppDTO, error)
|
||||
GetAppDetail(appId uint, version string) (response.AppDetailDTO, error)
|
||||
GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error)
|
||||
Install(ctx context.Context, req request.AppInstallCreate) (*model.AppInstall, error)
|
||||
SyncAppList() error
|
||||
GetAppUpdate() (*response.AppUpdateRes, error)
|
||||
@@ -138,7 +138,7 @@ func (a AppService) GetApp(key string) (*response.AppDTO, error) {
|
||||
return &appDTO, nil
|
||||
}
|
||||
|
||||
func (a AppService) GetAppDetail(appId uint, version string) (response.AppDetailDTO, error) {
|
||||
func (a AppService) GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error) {
|
||||
var (
|
||||
appDetailDTO response.AppDetailDTO
|
||||
opts []repo.DBOption
|
||||
@@ -148,13 +148,35 @@ func (a AppService) GetAppDetail(appId uint, version string) (response.AppDetail
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
appDetailDTO.AppDetail = detail
|
||||
appDetailDTO.Enable = true
|
||||
|
||||
if appType == "runtime" {
|
||||
app, err := appRepo.GetFirst(commonRepo.WithByID(appId))
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
paramsPath := path.Join(constant.AppResourceDir, app.Key, "versions", detail.Version, "build", "config.json")
|
||||
fileOp := files.NewFileOp()
|
||||
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
|
||||
} else {
|
||||
paramMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(detail.Params), ¶mMap); err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
appDetailDTO.AppDetail = detail
|
||||
appDetailDTO.Params = paramMap
|
||||
appDetailDTO.Enable = true
|
||||
}
|
||||
|
||||
app, err := appRepo.GetFirst(commonRepo.WithByID(detail.AppId))
|
||||
if err != nil {
|
||||
|
@@ -1,9 +1,17 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"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/subosito/gotenv"
|
||||
"path"
|
||||
)
|
||||
|
||||
type RuntimeService struct {
|
||||
@@ -11,14 +19,96 @@ type RuntimeService struct {
|
||||
|
||||
type IRuntimeService interface {
|
||||
Page(req request.RuntimeSearch) (int64, []response.RuntimeRes, error)
|
||||
Create(create request.RuntimeCreate) error
|
||||
}
|
||||
|
||||
func NewRuntimeService() IRuntimeService {
|
||||
return &RuntimeService{}
|
||||
}
|
||||
|
||||
func (r *RuntimeService) Create() {
|
||||
|
||||
func (r *RuntimeService) Create(create request.RuntimeCreate) error {
|
||||
if create.Resource == constant.ResourceLocal {
|
||||
runtime := &model.Runtime{
|
||||
Name: create.Name,
|
||||
Resource: create.Resource,
|
||||
Type: create.Type,
|
||||
Status: constant.RuntimeNormal,
|
||||
}
|
||||
return runtimeRepo.Create(context.Background(), runtime)
|
||||
}
|
||||
var err error
|
||||
appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(create.AppDetailID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
app, err := appRepo.GetFirst(commonRepo.WithByID(appDetail.AppId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
buildDir := path.Join(constant.AppResourceDir, app.Key, "versions", appDetail.Version, "build")
|
||||
if !fileOp.Stat(buildDir) {
|
||||
return buserr.New(constant.ErrDirNotFound)
|
||||
}
|
||||
tempDir := path.Join(constant.RuntimeDir, app.Key)
|
||||
if err := fileOp.CopyDir(buildDir, tempDir); err != nil {
|
||||
return err
|
||||
}
|
||||
oldDir := path.Join(tempDir, "build")
|
||||
newNameDir := path.Join(tempDir, create.Name)
|
||||
defer func(defErr *error) {
|
||||
if defErr != nil {
|
||||
_ = fileOp.DeleteDir(newNameDir)
|
||||
}
|
||||
}(&err)
|
||||
if oldDir != newNameDir {
|
||||
if err := fileOp.Rename(oldDir, newNameDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
composeFile, err := fileOp.GetContent(path.Join(newNameDir, "docker-compose.yml"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := gotenv.Read(path.Join(newNameDir, ".env"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
if err := composeService.ComposeBuild(); err != nil {
|
||||
return err
|
||||
}
|
||||
runtime := &model.Runtime{
|
||||
Name: create.Name,
|
||||
DockerCompose: string(composeFile),
|
||||
Env: envStr,
|
||||
AppDetailID: create.AppDetailID,
|
||||
Type: create.Type,
|
||||
Image: create.Image,
|
||||
Resource: create.Resource,
|
||||
Status: constant.RuntimeNormal,
|
||||
}
|
||||
return runtimeRepo.Create(context.Background(), runtime)
|
||||
}
|
||||
|
||||
func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.RuntimeRes, error) {
|
||||
|
1
backend/app/service/runtime_utils.go
Normal file
1
backend/app/service/runtime_utils.go
Normal file
@@ -0,0 +1 @@
|
||||
package service
|
@@ -103,3 +103,10 @@ var (
|
||||
ErrInUsed = "ErrInUsed"
|
||||
ErrObjectInUsed = "ErrObjectInUsed"
|
||||
)
|
||||
|
||||
//runtime
|
||||
|
||||
var (
|
||||
ErrDirNotFound = "ErrDirNotFound"
|
||||
ErrFileNotExist = "ErrFileNotExist"
|
||||
)
|
||||
|
10
backend/constant/runtime.go
Normal file
10
backend/constant/runtime.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
ResourceLocal = "Local"
|
||||
ResourceAppstore = "Appstore"
|
||||
|
||||
RuntimeNormal = "Normal"
|
||||
RuntimeBuildSuccess = "BuildSuccess"
|
||||
RuntimeBuildFailed = "BuildFailed"
|
||||
)
|
@@ -59,3 +59,7 @@ ErrTypeOfRedis: "The recovery file type does not match the current persistence m
|
||||
#container
|
||||
ErrInUsed: "{{ .detail }} is in use and cannot be deleted"
|
||||
ErrObjectInUsed: "This object is in use and cannot be deleted"
|
||||
|
||||
#runtime
|
||||
ErrDirNotFound: "The build folder does not exist! Please check file integrity!"
|
||||
ErrFileNotExist: "{{ .detail }} file does not exist! Please check source file integrity!"
|
@@ -59,3 +59,7 @@ ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后
|
||||
#container
|
||||
ErrInUsed: "{{ .detail }} 正被使用,无法删除"
|
||||
ErrObjectInUsed: "该对象正被使用,无法删除"
|
||||
|
||||
#runtime
|
||||
ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!"
|
||||
ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性!"
|
@@ -14,8 +14,9 @@ func Init() {
|
||||
constant.ResourceDir = path.Join(constant.DataDir, "resource")
|
||||
constant.AppResourceDir = path.Join(constant.ResourceDir, "apps")
|
||||
constant.AppInstallDir = path.Join(constant.DataDir, "apps")
|
||||
constant.RuntimeDir = path.Join(constant.DataDir, "runtime")
|
||||
|
||||
dirs := []string{constant.DataDir, constant.ResourceDir, constant.AppResourceDir, constant.AppInstallDir, global.CONF.System.Backup}
|
||||
dirs := []string{constant.DataDir, constant.ResourceDir, constant.AppResourceDir, constant.AppInstallDir, global.CONF.System.Backup, constant.RuntimeDir}
|
||||
|
||||
fileOp := files.NewFileOp()
|
||||
for _, dir := range dirs {
|
||||
|
@@ -249,7 +249,7 @@ var AddDefaultGroup = &gormigrate.Migration{
|
||||
}
|
||||
|
||||
var AddTableRuntime = &gormigrate.Migration{
|
||||
ID: "20230328-add-table-runtime",
|
||||
ID: "20230330-add-table-runtime",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(&model.Runtime{})
|
||||
},
|
||||
|
@@ -19,7 +19,7 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) {
|
||||
appRouter.GET("/checkupdate", baseApi.GetAppListUpdate)
|
||||
appRouter.POST("/search", baseApi.SearchApp)
|
||||
appRouter.GET("/:key", baseApi.GetApp)
|
||||
appRouter.GET("/detail/:appId/:version", baseApi.GetAppDetail)
|
||||
appRouter.GET("/detail/:appId/:version/:type", baseApi.GetAppDetail)
|
||||
appRouter.POST("/install", baseApi.InstallApp)
|
||||
appRouter.GET("/tags", baseApi.GetAppTags)
|
||||
appRouter.GET("/installed/:appInstallId/versions", baseApi.GetUpdateVersions)
|
||||
|
@@ -16,5 +16,6 @@ func (r *RuntimeRouter) InitRuntimeRouter(Router *gin.RouterGroup) {
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
groupRouter.POST("/search", baseApi.SearchRuntimes)
|
||||
groupRouter.POST("", baseApi.CreateRuntime)
|
||||
}
|
||||
}
|
||||
|
@@ -83,6 +83,10 @@ func (s *ComposeService) ComposeCreate() error {
|
||||
return s.Create(context.Background(), s.project, api.CreateOptions{})
|
||||
}
|
||||
|
||||
func (s *ComposeService) ComposeBuild() error {
|
||||
return s.Build(context.Background(), s.project, api.BuildOptions{})
|
||||
}
|
||||
|
||||
func GetComposeProject(projectName, workDir string, yml []byte, env []byte) (*types.Project, error) {
|
||||
var configFiles []types.ConfigFile
|
||||
configFiles = append(configFiles, types.ConfigFile{
|
||||
|
@@ -37,6 +37,15 @@ func (f FileOp) OpenFile(dst string) (fs.File, error) {
|
||||
return f.Fs.Open(dst)
|
||||
}
|
||||
|
||||
func (f FileOp) GetContent(dst string) ([]byte, error) {
|
||||
afs := &afero.Afero{Fs: f.Fs}
|
||||
cByte, err := afs.ReadFile(dst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cByte, nil
|
||||
}
|
||||
|
||||
func (f FileOp) CreateDir(dst string, mode fs.FileMode) error {
|
||||
return f.Fs.MkdirAll(dst, mode)
|
||||
}
|
||||
|
@@ -64,6 +64,7 @@ export namespace App {
|
||||
values?: ServiceParam[];
|
||||
child?: FromFieldChild;
|
||||
params?: FromParam[];
|
||||
multiple?: boolean;
|
||||
}
|
||||
|
||||
export interface FromFieldChild extends FromField {
|
||||
|
@@ -18,4 +18,15 @@ export namespace Runtime {
|
||||
export interface RuntimeDTO extends Runtime {
|
||||
websites: string[];
|
||||
}
|
||||
|
||||
export interface RuntimeCreate {
|
||||
name: string;
|
||||
appDetailId: number;
|
||||
image: string;
|
||||
params: object;
|
||||
type: string;
|
||||
resource: string;
|
||||
appId?: number;
|
||||
version?: string;
|
||||
}
|
||||
}
|
||||
|
@@ -22,8 +22,8 @@ export const GetAppTags = () => {
|
||||
return http.get<App.Tag[]>('apps/tags');
|
||||
};
|
||||
|
||||
export const GetAppDetail = (id: number, version: string) => {
|
||||
return http.get<App.AppDetail>(`apps/detail/${id}/${version}`);
|
||||
export const GetAppDetail = (id: number, version: string, type: string) => {
|
||||
return http.get<App.AppDetail>(`apps/detail/${id}/${version}/${type}`);
|
||||
};
|
||||
|
||||
export const InstallApp = (install: App.AppInstall) => {
|
||||
|
@@ -5,3 +5,7 @@ import { Runtime } from '../interface/runtime';
|
||||
export const SearchRuntimes = (req: Runtime.RuntimeReq) => {
|
||||
return http.post<ResPage<Runtime.RuntimeDTO>>(`/runtimes/search`, req);
|
||||
};
|
||||
|
||||
export const CreateRuntime = (req: Runtime.RuntimeCreate) => {
|
||||
return http.post<any>(`/runtimes`, req);
|
||||
};
|
||||
|
@@ -1228,6 +1228,14 @@ const message = {
|
||||
runtime: '运行环境',
|
||||
image: '镜像',
|
||||
workDir: '工作目录',
|
||||
create: '创建运行环境',
|
||||
name: '名称',
|
||||
resource: '来源',
|
||||
appStore: '应用商店',
|
||||
local: '本地',
|
||||
app: '应用',
|
||||
localHelper: '本地运行环境需要自行安装',
|
||||
version: '版本',
|
||||
},
|
||||
};
|
||||
export default {
|
||||
|
@@ -40,7 +40,7 @@ const webSiteRouter = {
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/websites/runtime',
|
||||
path: '/websites/runtime/php',
|
||||
name: 'Runtime',
|
||||
component: () => import('@/views/website/runtime/index.vue'),
|
||||
meta: {
|
||||
|
@@ -131,7 +131,7 @@ const getApp = async () => {
|
||||
const getDetail = async (id: number, version: string) => {
|
||||
loadingDetail.value = true;
|
||||
try {
|
||||
const res = await GetAppDetail(id, version);
|
||||
const res = await GetAppDetail(id, version, 'app');
|
||||
appDetail.value = res.data;
|
||||
} finally {
|
||||
loadingDetail.value = false;
|
||||
|
190
frontend/src/views/website/runtime/create/index.vue
Normal file
190
frontend/src/views/website/runtime/create/index.vue
Normal file
@@ -0,0 +1,190 @@
|
||||
<template>
|
||||
<el-drawer :close-on-click-modal="false" v-model="open" size="50%">
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('runtime.create')" :back="handleClose" />
|
||||
</template>
|
||||
<el-row v-loading="loading">
|
||||
<el-col :span="22" :offset="1">
|
||||
<el-form
|
||||
ref="runtimeForm"
|
||||
label-position="top"
|
||||
:model="runtimeCreate"
|
||||
label-width="125px"
|
||||
:rules="rules"
|
||||
:validate-on-rule-change="false"
|
||||
>
|
||||
<el-form-item :label="$t('runtime.name')" prop="name">
|
||||
<el-input v-model="runtimeCreate.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('runtime.resource')" prop="resource">
|
||||
<el-radio-group
|
||||
v-model="runtimeCreate.resource"
|
||||
@change="changeResource(runtimeCreate.resource)"
|
||||
>
|
||||
<el-radio :label="'AppStore'" :value="'AppStore'">
|
||||
{{ $t('runtime.appStore') }}
|
||||
</el-radio>
|
||||
<el-radio :label="'Local'" :value="'Local'">
|
||||
{{ $t('runtime.local') }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<div v-if="runtimeCreate.resource === 'AppStore'">
|
||||
<el-form-item :label="$t('runtime.app')" prop="appId">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-select v-model="runtimeCreate.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="runtimeCreate.version">
|
||||
<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>
|
||||
<Params
|
||||
v-if="initParam"
|
||||
v-model:form="runtimeCreate"
|
||||
v-model:params="appParams"
|
||||
v-model:rules="rules"
|
||||
></Params>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-alert :title="$t('runtime.localHelper')" type="info" :closable="false" />
|
||||
<el-form-item :label="$t('runtime.version')" prop="version">
|
||||
<el-input v-model="runtimeCreate.version"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</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 } 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 } from 'vue';
|
||||
import Params from '../param/index.vue';
|
||||
|
||||
const open = ref(false);
|
||||
const apps = ref<App.App[]>([]);
|
||||
const runtimeForm = ref<FormInstance>();
|
||||
const loading = ref(false);
|
||||
const initParam = ref(false);
|
||||
let appParams = ref<App.AppParams>();
|
||||
let appVersions = ref<string[]>([]);
|
||||
let appReq = reactive({
|
||||
type: 'php',
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
const runtimeCreate = ref<Runtime.RuntimeCreate>({
|
||||
name: '',
|
||||
appDetailId: undefined,
|
||||
image: '',
|
||||
params: {},
|
||||
type: '',
|
||||
resource: 'AppStore',
|
||||
});
|
||||
let rules = ref<any>({
|
||||
name: [Rules.appName],
|
||||
resource: [Rules.requiredInput],
|
||||
appId: [Rules.requiredSelect],
|
||||
version: [Rules.requiredInput],
|
||||
});
|
||||
|
||||
const em = defineEmits(['close']);
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
em('close', false);
|
||||
};
|
||||
|
||||
const changeResource = (resource: string) => {
|
||||
if (resource === 'Local') {
|
||||
runtimeCreate.value.appDetailId = undefined;
|
||||
runtimeCreate.value.version = '';
|
||||
runtimeCreate.value.params = {};
|
||||
runtimeCreate.value.image = '';
|
||||
} else {
|
||||
runtimeCreate.value.version = '';
|
||||
}
|
||||
};
|
||||
|
||||
const searchApp = () => {
|
||||
SearchApp(appReq).then((res) => {
|
||||
apps.value = res.data.items || [];
|
||||
if (res.data && res.data.items && res.data.items.length > 0) {
|
||||
runtimeCreate.value.appId = res.data.items[0].id;
|
||||
getApp(res.data.items[0].key);
|
||||
}
|
||||
});
|
||||
};
|
||||
const getApp = (appkey: string) => {
|
||||
GetApp(appkey).then((res) => {
|
||||
appVersions.value = res.data.versions || [];
|
||||
if (res.data.versions.length > 0) {
|
||||
runtimeCreate.value.version = res.data.versions[0];
|
||||
GetAppDetail(runtimeCreate.value.appId, runtimeCreate.value.version, 'runtime').then((res) => {
|
||||
runtimeCreate.value.appDetailId = res.data.id;
|
||||
appParams.value = res.data.params;
|
||||
initParam.value = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
CreateRuntime(runtimeCreate.value)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||
handleClose();
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const acceptParams = async () => {
|
||||
searchApp();
|
||||
open.value = true;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
@@ -4,12 +4,16 @@
|
||||
:buttons="[
|
||||
{
|
||||
label: 'PHP',
|
||||
path: '/runtimes',
|
||||
path: '/runtimes/php',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<LayoutContent :title="$t('runtime.runtime')" v-loading="loading">
|
||||
<template #toolbar></template>
|
||||
<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">
|
||||
@@ -28,6 +32,7 @@
|
||||
</ComplexTable>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
<CreateRuntime ref="createRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -35,10 +40,11 @@
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { Runtime } from '@/api/interface/runtime';
|
||||
import { SearchRuntimes } from '@/api/modules/runtime';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import RouterButton from '@/components/router-button/index.vue';
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import CreateRuntime from '@/views/website/runtime/create/index.vue';
|
||||
|
||||
const paginationConfig = reactive({
|
||||
currentPage: 1,
|
||||
@@ -52,6 +58,7 @@ let req = reactive<Runtime.RuntimeReq>({
|
||||
});
|
||||
const loading = ref(false);
|
||||
const items = ref<Runtime.RuntimeDTO[]>([]);
|
||||
const createRef = ref();
|
||||
|
||||
const search = async () => {
|
||||
req.page = paginationConfig.currentPage;
|
||||
@@ -67,6 +74,10 @@ const search = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const openCreate = () => {
|
||||
createRef.value.acceptParams();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
|
94
frontend/src/views/website/runtime/param/index.vue
Normal file
94
frontend/src/views/website/runtime/param/index.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<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.envKey]" v-if="p.type == 'select'" :multiple="p.multiple">
|
||||
<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.FromField {
|
||||
services: App.AppService[];
|
||||
prop: string;
|
||||
disabled: false;
|
||||
childProp: string;
|
||||
}
|
||||
const props = defineProps({
|
||||
form: {
|
||||
type: Object,
|
||||
default: function () {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
params: {
|
||||
type: Object,
|
||||
default: function () {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
type: Object,
|
||||
default: function () {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let form = reactive({});
|
||||
// let rules = reactive({});
|
||||
const params = computed({
|
||||
get() {
|
||||
return props.params;
|
||||
},
|
||||
set() {},
|
||||
});
|
||||
|
||||
const paramObjs = ref<ParamObj[]>([]);
|
||||
|
||||
const handleParams = () => {
|
||||
// rules = props.rules;
|
||||
form = props.form;
|
||||
if (params.value != undefined && params.value.formFields != undefined) {
|
||||
for (const p of params.value.formFields) {
|
||||
const pObj = p;
|
||||
pObj.prop = p.envKey;
|
||||
pObj.disabled = p.disabled;
|
||||
form[p.envKey] = p.default;
|
||||
paramObjs.value.push(pObj);
|
||||
// if (p.required) {
|
||||
// if (p.type === 'select') {
|
||||
// rules[p.envKey] = [Rules.requiredSelect];
|
||||
// } else {
|
||||
// rules[p.envKey] = [Rules.requiredInput];
|
||||
// }
|
||||
// }
|
||||
}
|
||||
console.log(form);
|
||||
}
|
||||
};
|
||||
|
||||
const getLabel = (row: ParamObj): string => {
|
||||
const language = useI18n().locale.value;
|
||||
if (language == 'zh') {
|
||||
return row.labelZh;
|
||||
} else {
|
||||
return row.labelEn;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
handleParams();
|
||||
});
|
||||
</script>
|
@@ -310,7 +310,7 @@ const getApp = () => {
|
||||
};
|
||||
|
||||
const getAppDetail = (version: string) => {
|
||||
GetAppDetail(website.value.appinstall.appId, version).then((res) => {
|
||||
GetAppDetail(website.value.appinstall.appId, version, 'app').then((res) => {
|
||||
website.value.appinstall.appDetailId = res.data.id;
|
||||
appDetail.value = res.data;
|
||||
appParams.value = res.data.params;
|
||||
|
Reference in New Issue
Block a user