diff --git a/agent/app/api/v2/runtime.go b/agent/app/api/v2/runtime.go index cb6f885cb..edade0b54 100644 --- a/agent/app/api/v2/runtime.go +++ b/agent/app/api/v2/runtime.go @@ -233,3 +233,46 @@ func (b *BaseApi) SyncStatus(c *gin.Context) { } helper.SuccessWithOutData(c) } + +// @Tags Runtime +// @Summary Get php runtime extension +// @Description 获取 PHP 运行环境扩展 +// @Accept json +// @Param id path string true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /runtimes/php/:id/extensions [get] +func (b *BaseApi) GetRuntimeExtension(c *gin.Context) { + id, err := helper.GetIntParamByKey(c, "id") + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInternalServer, nil) + return + } + res, err := runtimeService.GetPHPExtensions(id) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Runtime +// @Summary Install php extension +// @Description 安装 PHP 扩展 +// @Accept json +// @Param request body request.PHPExtensionsCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /runtimes/php/extensions/install [post] +func (b *BaseApi) InstallPHPExtension(c *gin.Context) { + var req request.PHPExtensionInstallReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := runtimeService.InstallPHPExtension(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithOutData(c) +} diff --git a/agent/app/dto/request/runtime.go b/agent/app/dto/request/runtime.go index b35bbdb6d..23cb1dfb0 100644 --- a/agent/app/dto/request/runtime.go +++ b/agent/app/dto/request/runtime.go @@ -70,3 +70,9 @@ type NodeModuleOperateReq struct { type NodeModuleReq struct { ID uint `json:"ID" validate:"required"` } + +type PHPExtensionInstallReq struct { + ID uint `json:"ID" validate:"required"` + Name string `json:"name" validate:"required"` + TaskID string `json:"taskID" validate:"required"` +} diff --git a/agent/app/dto/response/runtime.go b/agent/app/dto/response/runtime.go index 25d408f6f..c5085b555 100644 --- a/agent/app/dto/response/runtime.go +++ b/agent/app/dto/response/runtime.go @@ -57,3 +57,16 @@ type NodeModule struct { License string `json:"license"` Description string `json:"description"` } + +type SupportExtension struct { + Name string `json:"name"` + Description string `json:"description"` + Installed bool `json:"installed"` + Check string `json:"check"` + Versions []string `json:"versions"` +} + +type PHPExtensionRes struct { + Extensions []string `json:"extensions"` + SupportExtensions []SupportExtension `json:"supportExtensions"` +} diff --git a/agent/app/model/runtime.go b/agent/app/model/runtime.go index 7c9085c3c..69e67fa7d 100644 --- a/agent/app/model/runtime.go +++ b/agent/app/model/runtime.go @@ -22,6 +22,7 @@ type Runtime struct { Port int `json:"port"` Message string `json:"message"` CodeDir string `json:"codeDir"` + ContainerName string `json:"containerName"` } func (r *Runtime) GetComposePath() string { diff --git a/agent/app/service/runtime.go b/agent/app/service/runtime.go index a6cfaa0f6..e1b08d150 100644 --- a/agent/app/service/runtime.go +++ b/agent/app/service/runtime.go @@ -4,6 +4,8 @@ import ( "context" "encoding/json" "fmt" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf" "os" "path" "path/filepath" @@ -45,6 +47,9 @@ type IRuntimeService interface { SyncForRestart() error SyncRuntimeStatus() error DeleteCheck(installID uint) ([]dto.AppResource, error) + + GetPHPExtensions(runtimeID uint) (response.PHPExtensionRes, error) + InstallPHPExtension(req request.PHPExtensionInstallReq) error } func NewRuntimeService() IRuntimeService { @@ -101,10 +106,12 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e } } } - if containerName, ok := create.Params["CONTAINER_NAME"]; ok { - if err := checkContainerName(containerName.(string)); err != nil { - return nil, err - } + containerName, ok := create.Params["CONTAINER_NAME"] + if !ok { + return nil, buserr.New("ErrContainerNameIsNull") + } + if err := checkContainerName(containerName.(string)); err != nil { + return nil, err } appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(create.AppDetailID)) @@ -124,12 +131,13 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e } runtime := &model.Runtime{ - Name: create.Name, - AppDetailID: create.AppDetailID, - Type: create.Type, - Image: create.Image, - Resource: create.Resource, - Version: create.Version, + Name: create.Name, + AppDetailID: create.AppDetailID, + Type: create.Type, + Image: create.Image, + Resource: create.Resource, + Version: create.Version, + ContainerName: containerName.(string), } switch create.Type { @@ -204,35 +212,34 @@ func (r *RuntimeService) Delete(runtimeDelete request.RuntimeDelete) error { if website.ID > 0 { return buserr.New(constant.ErrDelWithWebsite) } - if runtime.Resource == constant.ResourceAppstore { - projectDir := runtime.GetPath() - switch runtime.Type { - case constant.RuntimePHP: - client, err := docker.NewClient() - if err != nil { - return err - } - defer client.Close() - 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, constant.RuntimeJava, constant.RuntimeGo: - if out, err := compose.Down(runtime.GetComposePath()); err != nil && !runtimeDelete.ForceDelete { - if out != "" { - return errors.New(out) - } - return err - } + if runtime.Resource != constant.ResourceAppstore { + return runtimeRepo.DeleteBy(commonRepo.WithByID(runtimeDelete.ID)) + } + projectDir := runtime.GetPath() + if out, err := compose.Down(runtime.GetComposePath()); err != nil && !runtimeDelete.ForceDelete { + if out != "" { + return errors.New(out) } - if err := files.NewFileOp().DeleteDir(projectDir); err != nil && !runtimeDelete.ForceDelete { + return err + } + if runtime.Type == constant.RuntimePHP { + client, err := docker.NewClient() + if err != nil { return err } + defer client.Close() + 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) + } + } + } + if err := files.NewFileOp().DeleteDir(projectDir); err != nil && !runtimeDelete.ForceDelete { + return err } return runtimeRepo.DeleteBy(commonRepo.WithByID(runtimeDelete.ID)) } @@ -635,3 +642,100 @@ func (r *RuntimeService) SyncRuntimeStatus() error { } return nil } + +func (r *RuntimeService) GetPHPExtensions(runtimeID uint) (response.PHPExtensionRes, error) { + var res response.PHPExtensionRes + runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(runtimeID)) + if err != nil { + return res, err + } + phpCmd := fmt.Sprintf("docker exec -i %s %s", runtime.ContainerName, "php -m") + out, err := cmd2.ExecWithTimeOut(phpCmd, 20*time.Second) + if err != nil { + if out != "" { + return res, errors.New(out) + } + return res, err + } + extensions := strings.Split(out, "\n") + var cleanExtensions []string + for _, ext := range extensions { + extStr := strings.TrimSpace(ext) + if extStr != "" && extStr != "[Zend Modules]" && extStr != "[PHP Modules]" { + cleanExtensions = append(cleanExtensions, extStr) + } + } + res.Extensions = cleanExtensions + var phpExtensions []response.SupportExtension + if err = json.Unmarshal(nginx_conf.PHPExtensionsJson, &phpExtensions); err != nil { + return res, err + } + for _, ext := range phpExtensions { + for _, cExt := range cleanExtensions { + if ext.Check == cExt { + ext.Installed = true + break + } + } + res.SupportExtensions = append(res.SupportExtensions, ext) + } + return res, nil +} + +func (r *RuntimeService) InstallPHPExtension(req request.PHPExtensionInstallReq) error { + runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(req.ID)) + if err != nil { + return err + } + + installTask, err := task.NewTaskWithOps(req.Name, task.TaskInstall, task.TaskScopeRuntime, req.TaskID, runtime.ID) + if err != nil { + return err + } + installTask.AddSubTask("", func(t *task.Task) error { + installCmd := fmt.Sprintf("docker exec -i %s %s %s", runtime.ContainerName, "install-ext", req.Name) + err = cmd2.ExecWithLogFile(installCmd, 15*time.Minute, t.Task.LogFile) + if err != nil { + return err + } + return nil + }, nil) + go func() { + err = installTask.Execute() + if err == nil { + envs, err := gotenv.Unmarshal(runtime.Env) + if err != nil { + global.LOG.Errorf("get runtime env error %v", err) + return + } + extensions, ok := envs["PHP_EXTENSIONS"] + exist := false + var extensionArray []string + if ok { + extensionArray = strings.Split(extensions, ",") + for _, ext := range extensionArray { + if ext == req.Name { + exist = true + break + } + } + } + if !exist { + extensionArray = append(extensionArray, req.Name) + envs["PHP_EXTENSIONS"] = strings.Join(extensionArray, ",") + if err = gotenv.Write(envs, runtime.GetEnvPath()); err != nil { + global.LOG.Errorf("write runtime env error %v", err) + return + } + envStr, err := gotenv.Marshal(envs) + if err != nil { + global.LOG.Errorf("marshal runtime env error %v", err) + return + } + runtime.Env = envStr + _ = runtimeRepo.Save(runtime) + } + } + }() + return nil +} diff --git a/agent/cmd/server/nginx_conf/nginx_conf.go b/agent/cmd/server/nginx_conf/nginx_conf.go index ab265cbe4..144e94ee8 100644 --- a/agent/cmd/server/nginx_conf/nginx_conf.go +++ b/agent/cmd/server/nginx_conf/nginx_conf.go @@ -43,3 +43,6 @@ var PathAuth []byte //go:embed upstream.conf var Upstream []byte + +//go:embed php_extensions.json +var PHPExtensionsJson []byte diff --git a/agent/cmd/server/nginx_conf/php_extensions.json b/agent/cmd/server/nginx_conf/php_extensions.json new file mode 100644 index 000000000..57d063015 --- /dev/null +++ b/agent/cmd/server/nginx_conf/php_extensions.json @@ -0,0 +1,135 @@ +[ + { + "name": "ZendGuardLoader", + "description": "用于解密ZendGuard加密脚本!", + "check": "ZendGuardLoader", + "versions": ["53", "54", "55", "56"], + "installed": false + }, + { + "name": "ionCube", + "description": "用于解密ionCube Encoder加密脚本!", + "check": "ionCube", + "versions": ["56", "70", "71", "72", "73", "74", "81", "82"], + "installed": false + }, + { + "name": "fileinfo", + "description": "", + "check": "fileinfo", + "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], + "installed": false + }, + { + "name": "opcache", + "description": "用于加速PHP脚本!", + "check": "opcache", + "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], + "installed": false + }, + { + "name": "xcache", + "description": "支持脚本缓存和变量缓存!", + "check": "xcache", + "versions": ["56"], + "installed": false + }, + { + "name": "memcache", + "description": "强大的内容缓存器", + "check": "memcache", + "versions": ["56", "70", "71", "72", "73", "74", "80"], + "installed": false + }, + { + "name": "memcached", + "description": "比memcache支持更多高级功能", + "check": "memcached", + "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], + "installed": false + }, + { + "name": "redis", + "description": "基于内存亦可持久化的Key-Value数据库", + "check": "redis", + "versions": [ "56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], + "installed": false + }, + { + "name": "mcrypt", + "description": "mcrypt加密/解密", + "check": "mcrypt", + "versions": ["70", "71", "72", "73", "74", "80", "81", "82", "83"], + "installed": false + }, + { + "name": "apcu", + "description": "脚本缓存器", + "check": "apcu", + "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], + "installed": false + }, + { + "name": "imagemagick", + "description": "Imagick高性能图形库", + "check": "imagick", + "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], + "installed": false + }, + { + "name": "xdebug", + "description": "开源的PHP程序调试器", + "check": "xdebug", + "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], + "installed": false + }, + { + "name": "imap", + "description": "邮件服务器必备", + "check": "imap", + "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], + "installed": false + }, + { + "name": "exif", + "description": "用于读取图片EXIF信息", + "check": "exif", + "versions": [ "56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], + "installed": false + }, + { + "name": "intl", + "description": "提供国际化支持", + "check": "intl", + "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], + "installed": false + }, + { + "name": "xsl", + "description": "xsl解析扩展", + "check": "xsl", + "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82"], + "installed": false + }, + { + "name": "mbstring", + "description": "mbstring扩展", + "check": "mbstring", + "versions": ["83"], + "installed": false + }, + { + "name": "Swoole", + "description": "异步、协程高性能网络通信引擎", + "check": "swoole", + "versions": ["70", "71", "72"], + "installed": false + }, + { + "name": "zstd", + "description": "使用 Zstandard 库进行压缩和解压缩的 PHP 扩展", + "check": "zstd", + "versions": ["70", "71", "72"], + "installed": false + } +] diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index fa702a9af..d1a21bf6f 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -153,6 +153,7 @@ ErrPackageJsonNotFound: "package.json file does not exist" ErrScriptsNotFound: "No scripts configuration item was found in package.json" ErrContainerNameNotFound: "Unable to get container name, please check .env file" ErrNodeModulesNotFound: "The node_modules folder does not exist! Please edit the running environment or wait for the running environment to start successfully" +ErrContainerNameIsNull: "Container name cannot be empty" #setting ErrBackupInUsed: "The backup account is already being used in a cronjob and cannot be deleted." diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index e0e437c6f..f2850e07d 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -154,6 +154,7 @@ ErrPackageJsonNotFound: "package.json 文件不存在" ErrScriptsNotFound: "沒有在 package.json 中找到 scripts 配置項" ErrContainerNameNotFound: "無法取得容器名稱,請檢查 .env 文件" ErrNodeModulesNotFound: "node_modules 文件夾不存在!請編輯運行環境或者等待運行環境啟動成功" +ErrContainerNameIsNull: "容器名稱不能為空" #setting ErrBackupInUsed: "該備份帳號已在計劃任務中使用,無法刪除" diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index 8a2dd7ddc..aa584db79 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -156,6 +156,7 @@ ErrPackageJsonNotFound: "package.json 文件不存在" ErrScriptsNotFound: "没有在 package.json 中找到 scripts 配置项" ErrContainerNameNotFound: "无法获取容器名称,请检查 .env 文件" ErrNodeModulesNotFound: "node_modules 文件夹不存在!请编辑运行环境或者等待运行环境启动成功" +ErrContainerNameIsNull: "容器名称不存在" #setting ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除" diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index 3537f183f..ca3c0833d 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -16,7 +16,7 @@ import ( ) var AddTable = &gormigrate.Migration{ - ID: "20240902-add-table", + ID: "20240903-add-table", Migrate: func(tx *gorm.DB) error { return tx.AutoMigrate( &model.AppDetail{}, diff --git a/agent/router/ro_runtime.go b/agent/router/ro_runtime.go index b0f2af8e1..2b822d047 100644 --- a/agent/router/ro_runtime.go +++ b/agent/router/ro_runtime.go @@ -30,6 +30,9 @@ func (r *RuntimeRouter) InitRouter(Router *gin.RouterGroup) { groupRouter.POST("/php/extensions", baseApi.CreatePHPExtensions) groupRouter.POST("/php/extensions/update", baseApi.UpdatePHPExtensions) groupRouter.POST("/php/extensions/del", baseApi.DeletePHPExtensions) + + groupRouter.GET("/php/:id/extensions", baseApi.GetRuntimeExtension) + groupRouter.POST("/php/extensions/install", baseApi.InstallPHPExtension) } } diff --git a/agent/utils/cmd/cmd.go b/agent/utils/cmd/cmd.go index 23799377c..a331e85a3 100644 --- a/agent/utils/cmd/cmd.go +++ b/agent/utils/cmd/cmd.go @@ -60,6 +60,41 @@ func ExecWithTimeOut(cmdStr string, timeout time.Duration) (string, error) { return stdout.String(), nil } +func ExecWithLogFile(cmdStr string, timeout time.Duration, outputFile string) error { + cmd := exec.Command("bash", "-c", cmdStr) + + outFile, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return err + } + defer outFile.Close() + + cmd.Stdout = outFile + cmd.Stderr = outFile + + if err := cmd.Start(); err != nil { + return err + } + + done := make(chan error, 1) + go func() { + done <- cmd.Wait() + }() + + after := time.After(timeout) + select { + case <-after: + _ = cmd.Process.Kill() + return buserr.New(constant.ErrCmdTimeout) + case err := <-done: + if err != nil { + return err + } + } + + return nil +} + func ExecContainerScript(containerName, cmdStr string, timeout time.Duration) error { cmdStr = fmt.Sprintf("docker exec -i %s bash -c '%s'", containerName, cmdStr) out, err := ExecWithTimeOut(cmdStr, timeout) diff --git a/frontend/src/api/interface/runtime.ts b/frontend/src/api/interface/runtime.ts index 228b219d5..1a20ea0db 100644 --- a/frontend/src/api/interface/runtime.ts +++ b/frontend/src/api/interface/runtime.ts @@ -122,4 +122,23 @@ export namespace Runtime { export interface PHPExtensionsDelete { id: number; } + + export interface PHPExtensionsRes { + extensions: string[]; + supportExtensions: SupportExtension[]; + } + + export interface SupportExtension { + name: string; + description: string; + installed: boolean; + check: string; + versions: string[]; + } + + export interface PHPExtensionInstall { + name: string; + id: number; + taskID: string; + } } diff --git a/frontend/src/api/modules/runtime.ts b/frontend/src/api/modules/runtime.ts index 2f6dd775f..39e8c4d3a 100644 --- a/frontend/src/api/modules/runtime.ts +++ b/frontend/src/api/modules/runtime.ts @@ -67,3 +67,11 @@ export const DeletePHPExtensions = (req: Runtime.PHPExtensionsDelete) => { export const SyncRuntime = () => { return http.post(`/runtimes/sync`, {}); }; + +export const GetPHPExtensions = (id: number) => { + return http.get(`/runtimes/php/${id}/extensions`); +}; + +export const InstallPHPExtension = (req: Runtime.PHPExtensionInstall) => { + return http.post(`/runtimes/php/extensions/install`, req); +}; diff --git a/frontend/src/components/task-log/index.vue b/frontend/src/components/task-log/index.vue index f7ed9cfb8..724e86ec3 100644 --- a/frontend/src/components/task-log/index.vue +++ b/frontend/src/components/task-log/index.vue @@ -195,6 +195,7 @@ const initCodemirror = () => { }); let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement; hljsDom.style['min-height'] = '100px'; + hljsDom.style['max-height'] = '400px'; } }); }; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index efb810ba1..ed8b5095b 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -152,6 +152,7 @@ const message = { rootInfoErr: "It's already the root directory", resetSuccess: 'Reset successful', creatingInfo: 'Creating, no need for this operation', + installSuccess: 'Install successful', }, login: { username: 'Username', @@ -2434,6 +2435,10 @@ const message = { javaDirHelper: 'The directory must contain jar files, subdirectories are also acceptable', goHelper: 'Please provide a complete start command, for example: go run main.go or ./main', goDirHelper: 'The directory must contain go files or binary files, subdirectories are also acceptable', + extension: 'Extension', + installExtension: 'Do you confirm to install the extension {0}', + loadedExtension: 'Loaded Extension', + popularExtension: 'Popular Extension', }, process: { pid: 'Process ID', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 511dfab1d..5633e676f 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -152,6 +152,7 @@ const message = { rootInfoErr: '已經是根目錄了', resetSuccess: '重置成功', creatingInfo: '正在創建,無需此操作', + installSuccess: '安裝成功', }, login: { username: '用戶名', @@ -2258,6 +2259,10 @@ const message = { javaDirHelper: '目錄中要包含 jar 包,子目錄中包含也可', goHelper: '請填寫完整啟動命令,例如:go run main.go 或 ./main', goDirHelper: '目錄中要包含 go 文件或者二進制文件,子目錄中包含也可', + extension: '擴充', + installExtension: '是否確認安裝擴充功能 {0}', + loadedExtension: '已載入擴充功能', + popularExtension: '常用擴充', }, process: { pid: '進程ID', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 03a1c0289..b71c530f5 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -152,6 +152,7 @@ const message = { rootInfoErr: '已经是根目录了', resetSuccess: '重置成功', creatingInfo: '正在创建,无需此操作', + installSuccess: '安装成功', }, login: { username: '用户名', @@ -2260,6 +2261,10 @@ const message = { javaDirHelper: '目录中要包含 jar 包,子目录中包含也可', goHelper: '请填写完整启动命令,例如:go run main.go 或 ./main', goDirHelper: '目录中要包含 go 文件或者二进制文件,子目录中包含也可', + extension: '扩展', + installExtension: '是否确认安装扩展 {0}', + loadedExtension: '已加载扩展', + popularExtension: '常用扩展', }, process: { pid: '进程ID', diff --git a/frontend/src/views/website/runtime/php/edit/index.vue b/frontend/src/views/website/runtime/php/edit/index.vue deleted file mode 100644 index 220e01362..000000000 --- a/frontend/src/views/website/runtime/php/edit/index.vue +++ /dev/null @@ -1,112 +0,0 @@ - - - diff --git a/frontend/src/views/website/runtime/php/extension-management/index.vue b/frontend/src/views/website/runtime/php/extension-management/index.vue new file mode 100644 index 000000000..7b7dfc66a --- /dev/null +++ b/frontend/src/views/website/runtime/php/extension-management/index.vue @@ -0,0 +1,98 @@ + + + diff --git a/frontend/src/views/website/runtime/php/extensions/index.vue b/frontend/src/views/website/runtime/php/extension-template/index.vue similarity index 100% rename from frontend/src/views/website/runtime/php/extensions/index.vue rename to frontend/src/views/website/runtime/php/extension-template/index.vue diff --git a/frontend/src/views/website/runtime/php/extensions/operate/index.vue b/frontend/src/views/website/runtime/php/extension-template/operate/index.vue similarity index 100% rename from frontend/src/views/website/runtime/php/extensions/operate/index.vue rename to frontend/src/views/website/runtime/php/extension-template/operate/index.vue diff --git a/frontend/src/views/website/runtime/php/index.vue b/frontend/src/views/website/runtime/php/index.vue index 4416ed133..dd8d21de0 100644 --- a/frontend/src/views/website/runtime/php/index.vue +++ b/frontend/src/views/website/runtime/php/index.vue @@ -14,7 +14,7 @@ {{ $t('runtime.create') }} - + {{ $t('php.extensions') }} @@ -94,7 +94,7 @@ /> + @@ -117,16 +118,17 @@ import { onMounted, onUnmounted, reactive, ref } from 'vue'; import { Runtime } from '@/api/interface/runtime'; import { DeleteRuntime, RuntimeDeleteCheck, 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 RouterMenu from '../index.vue'; -import Log from '@/components/log-dialog/index.vue'; -import Extensions from './extensions/index.vue'; -import AppResources from '@/views/website/runtime/php/check/index.vue'; import { ElMessageBox } from 'element-plus'; import { containerPrune } from '@/api/modules/container'; import { MsgSuccess } from '@/utils/message'; +import i18n from '@/lang'; +import ExtManagement from './extension-management/index.vue'; +import Extensions from './extension-template/index.vue'; +import AppResources from '@/views/website/runtime/php/check/index.vue'; +import CreateRuntime from '@/views/website/runtime/php/create/index.vue'; +import Status from '@/components/status/index.vue'; +import RouterMenu from '../index.vue'; +import Log from '@/components/log-dialog/index.vue'; const paginationConfig = reactive({ cacheSizeKey: 'runtime-page-size', @@ -144,10 +146,22 @@ let timer: NodeJS.Timer | null = null; const opRef = ref(); const logRef = ref(); const extensionsRef = ref(); - +const extManagementRef = ref(); const checkRef = ref(); +const createRef = ref(); +const loading = ref(false); +const items = ref([]); const buttons = [ + { + label: i18n.global.t('runtime.extension'), + click: function (row: Runtime.Runtime) { + openExtensionsManagement(row); + }, + disabled: function (row: Runtime.Runtime) { + return row.status != 'running'; + }, + }, { label: i18n.global.t('commons.button.edit'), click: function (row: Runtime.Runtime) { @@ -167,9 +181,6 @@ const buttons = [ }, }, ]; -const loading = ref(false); -const items = ref([]); -const createRef = ref(); const search = async () => { req.page = paginationConfig.currentPage; @@ -205,6 +216,10 @@ const openExtensions = () => { extensionsRef.value.acceptParams(); }; +const openExtensionsManagement = (row: Runtime.Runtime) => { + extManagementRef.value.acceptParams(row); +}; + const openDelete = async (row: Runtime.Runtime) => { RuntimeDeleteCheck(row.id).then(async (res) => { const items = res.data; diff --git a/frontend/src/views/website/runtime/php/param/index.vue b/frontend/src/views/website/runtime/php/param/index.vue deleted file mode 100644 index 609daae6c..000000000 --- a/frontend/src/views/website/runtime/php/param/index.vue +++ /dev/null @@ -1,271 +0,0 @@ - -