1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-02-08 01:20:07 +08:00

feat: PHP 运行环境增加操作 (#6377)

This commit is contained in:
zhengkunwang 2024-09-05 16:27:47 +08:00 committed by GitHub
parent ba9feb0941
commit 44336c2ea2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 524 additions and 120 deletions

View File

@ -260,7 +260,7 @@ func (b *BaseApi) GetRuntimeExtension(c *gin.Context) {
// @Summary Install php extension // @Summary Install php extension
// @Description 安装 PHP 扩展 // @Description 安装 PHP 扩展
// @Accept json // @Accept json
// @Param request body request.PHPExtensionsCreate true "request" // @Param request body request.PHPExtensionInstallReq true "request"
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /runtimes/php/extensions/install [post] // @Router /runtimes/php/extensions/install [post]
@ -276,3 +276,24 @@ func (b *BaseApi) InstallPHPExtension(c *gin.Context) {
} }
helper.SuccessWithOutData(c) helper.SuccessWithOutData(c)
} }
// @Tags Runtime
// @Summary UnInstall php extension
// @Description 卸载 PHP 扩展
// @Accept json
// @Param request body request.PHPExtensionInstallReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/php/extensions/uninstall [post]
func (b *BaseApi) UnInstallPHPExtension(c *gin.Context) {
var req request.PHPExtensionInstallReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
err := runtimeService.UnInstallPHPExtension(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}

View File

@ -74,5 +74,5 @@ type NodeModuleReq struct {
type PHPExtensionInstallReq struct { type PHPExtensionInstallReq struct {
ID uint `json:"ID" validate:"required"` ID uint `json:"ID" validate:"required"`
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
TaskID string `json:"taskID" validate:"required"` TaskID string `json:"taskID"`
} }

View File

@ -64,6 +64,7 @@ type SupportExtension struct {
Installed bool `json:"installed"` Installed bool `json:"installed"`
Check string `json:"check"` Check string `json:"check"`
Versions []string `json:"versions"` Versions []string `json:"versions"`
File string `json:"file"`
} }
type PHPExtensionRes struct { type PHPExtensionRes struct {

View File

@ -50,6 +50,7 @@ type IRuntimeService interface {
GetPHPExtensions(runtimeID uint) (response.PHPExtensionRes, error) GetPHPExtensions(runtimeID uint) (response.PHPExtensionRes, error)
InstallPHPExtension(req request.PHPExtensionInstallReq) error InstallPHPExtension(req request.PHPExtensionInstallReq) error
UnInstallPHPExtension(req request.PHPExtensionInstallReq) error
} }
func NewRuntimeService() IRuntimeService { func NewRuntimeService() IRuntimeService {
@ -380,6 +381,7 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
return runtimeRepo.Save(runtime) return runtimeRepo.Save(runtime)
} }
oldImage := runtime.Image oldImage := runtime.Image
oldEnv := runtime.Env
switch runtime.Type { switch runtime.Type {
case constant.RuntimePHP: case constant.RuntimePHP:
exist, _ := runtimeRepo.GetFirst(runtimeRepo.WithImage(req.Name), runtimeRepo.WithNotId(req.ID)) exist, _ := runtimeRepo.GetFirst(runtimeRepo.WithImage(req.Name), runtimeRepo.WithNotId(req.ID))
@ -418,18 +420,11 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
} }
} }
if containerName, ok := req.Params["CONTAINER_NAME"]; ok { if containerName, ok := req.Params["CONTAINER_NAME"]; ok && containerName != getRuntimeEnv(runtime.Env, "CONTAINER_NAME") {
envs, err := gotenv.Unmarshal(runtime.Env)
if err != nil {
return err
}
oldContainerName := envs["CONTAINER_NAME"]
if containerName != oldContainerName {
if err := checkContainerName(containerName.(string)); err != nil { if err := checkContainerName(containerName.(string)); err != nil {
return err return err
} }
} }
}
projectDir := path.Join(constant.RuntimeDir, runtime.Type, runtime.Name) projectDir := path.Join(constant.RuntimeDir, runtime.Type, runtime.Name)
create := request.RuntimeCreate{ create := request.RuntimeCreate{
@ -466,7 +461,7 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
if err != nil { if err != nil {
return err return err
} }
go buildRuntime(runtime, imageID, req.Rebuild) go buildRuntime(runtime, imageID, oldEnv, req.Rebuild)
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo: case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
runtime.Version = req.Version runtime.Version = req.Version
runtime.CodeDir = req.CodeDir runtime.CodeDir = req.CodeDir
@ -687,7 +682,6 @@ func (r *RuntimeService) InstallPHPExtension(req request.PHPExtensionInstallReq)
if err != nil { if err != nil {
return err return err
} }
installTask, err := task.NewTaskWithOps(req.Name, task.TaskInstall, task.TaskScopeRuntime, req.TaskID, runtime.ID) installTask, err := task.NewTaskWithOps(req.Name, task.TaskInstall, task.TaskScopeRuntime, req.TaskID, runtime.ID)
if err != nil { if err != nil {
return err return err
@ -739,3 +733,14 @@ func (r *RuntimeService) InstallPHPExtension(req request.PHPExtensionInstallReq)
}() }()
return nil return nil
} }
func (r *RuntimeService) UnInstallPHPExtension(req request.PHPExtensionInstallReq) error {
runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(req.ID))
if err != nil {
return err
}
if err := unInstallPHPExtension(runtime, []string{req.Name}); err != nil {
return err
}
return runtimeRepo.Save(runtime)
}

View File

@ -6,10 +6,13 @@ import (
"fmt" "fmt"
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/dto/request" "github.com/1Panel-dev/1Panel/agent/app/dto/request"
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
"github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf"
"github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/global"
cmd2 "github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/compose" "github.com/1Panel-dev/1Panel/agent/utils/compose"
"github.com/1Panel-dev/1Panel/agent/utils/docker" "github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/1Panel-dev/1Panel/agent/utils/files" "github.com/1Panel-dev/1Panel/agent/utils/files"
@ -24,6 +27,7 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
) )
func handleNodeAndJava(create request.RuntimeCreate, runtime *model.Runtime, fileOp files.FileOp, appVersionDir string) (err error) { func handleNodeAndJava(create request.RuntimeCreate, runtime *model.Runtime, fileOp files.FileOp, appVersionDir string) (err error) {
@ -86,7 +90,7 @@ func handlePHP(create request.RuntimeCreate, runtime *model.Runtime, fileOp file
runtime.Params = string(forms) runtime.Params = string(forms)
runtime.Status = constant.RuntimeBuildIng runtime.Status = constant.RuntimeBuildIng
go buildRuntime(runtime, "", false) go buildRuntime(runtime, "", "", false)
return return
} }
@ -127,9 +131,9 @@ func reCreateRuntime(runtime *model.Runtime) {
} }
func runComposeCmdWithLog(operate string, composePath string, logPath string) error { func runComposeCmdWithLog(operate string, composePath string, logPath string) error {
cmd := exec.Command("docker compose", "-f", composePath, operate) cmd := exec.Command("docker", "compose", "-f", composePath, operate)
if operate == "up" { if operate == "up" {
cmd = exec.Command("docker compose", "-f", composePath, operate, "-d") cmd = exec.Command("docker", "compose", "-f", composePath, operate, "-d")
} }
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil { if err != nil {
@ -191,7 +195,33 @@ func SyncRuntimeContainerStatus(runtime *model.Runtime) error {
return runtimeRepo.Save(runtime) return runtimeRepo.Save(runtime)
} }
func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) { func getRuntimeEnv(envStr, key string) string {
env, err := gotenv.Unmarshal(envStr)
if err != nil {
return ""
}
if v, ok := env[key]; ok {
return v
}
return ""
}
func getFileEnv(filePath, key string) (string, error) {
envContent, err := files.NewFileOp().GetContent(filePath)
if err != nil {
return "", err
}
env, err := gotenv.Unmarshal(string(envContent))
if err != nil {
return "", err
}
if v, ok := env[key]; ok {
return v, nil
}
return "", nil
}
func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebuild bool) {
runtimePath := runtime.GetPath() runtimePath := runtime.GetPath()
composePath := runtime.GetComposePath() composePath := runtime.GetComposePath()
logPath := path.Join(runtimePath, "build.log") logPath := path.Join(runtimePath, "build.log")
@ -206,10 +236,9 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) {
}() }()
cmd := exec.Command("docker", "compose", "-f", composePath, "build") cmd := exec.Command("docker", "compose", "-f", composePath, "build")
multiWriterStdout := io.MultiWriter(os.Stdout, logFile) cmd.Stdout = logFile
cmd.Stdout = multiWriterStdout
var stderrBuf bytes.Buffer var stderrBuf bytes.Buffer
multiWriterStderr := io.MultiWriter(&stderrBuf, logFile, os.Stderr) multiWriterStderr := io.MultiWriter(&stderrBuf, logFile)
cmd.Stderr = multiWriterStderr cmd.Stderr = multiWriterStderr
err = cmd.Run() err = cmd.Run()
@ -239,34 +268,44 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) {
} }
} }
if rebuild && runtime.ID > 0 { if rebuild && runtime.ID > 0 {
websites, _ := websiteRepo.GetBy(websiteRepo.WithRuntimeID(runtime.ID)) extensionsStr := getRuntimeEnv(runtime.Env, "PHP_EXTENSIONS")
if len(websites) > 0 { extensionsArray := strings.Split(extensionsStr, ",")
installService := NewIAppInstalledService() oldExtensionStr := getRuntimeEnv(oldEnv, "PHP_EXTENSIONS")
installMap := make(map[uint]string) oldExtensionArray := strings.Split(oldExtensionStr, ",")
for _, website := range websites { var delExtensions []string
if website.AppInstallID > 0 { for _, oldExt := range oldExtensionArray {
installMap[website.AppInstallID] = website.PrimaryDomain exist := false
for _, ext := range extensionsArray {
if oldExt == ext {
exist = true
break
} }
} }
for installID, domain := range installMap { if !exist {
go func(installID uint, domain string) { delExtensions = append(delExtensions, oldExt)
global.LOG.Infof("rebuild php runtime [%s] domain [%s]", runtime.Name, domain)
if err := installService.Operate(request.AppInstalledOperate{
InstallId: installID,
Operate: constant.Rebuild,
}); err != nil {
global.LOG.Errorf("rebuild php runtime [%s] domain [%s] error %v", runtime.Name, domain, err)
}
}(installID, domain)
} }
} }
if err = unInstallPHPExtension(runtime, delExtensions); err != nil {
global.LOG.Errorf("unInstallPHPExtension error %v", err)
} }
runtime.Status = constant.RuntimeStarting }
_ = runtimeRepo.Save(runtime)
if out, err := compose.Up(composePath); err != nil { if out, err := compose.Up(composePath); err != nil {
runtime.Status = constant.RuntimeStartErr runtime.Status = constant.RuntimeStartErr
runtime.Message = out runtime.Message = out
} else { } else {
extensions := getRuntimeEnv(runtime.Env, "PHP_EXTENSIONS")
if extensions != "" {
installCmd := fmt.Sprintf("docker exec -i %s %s %s", runtime.ContainerName, "install-ext", extensions)
err = cmd2.ExecWithLogFile(installCmd, 60*time.Minute, logPath)
if err != nil {
runtime.Status = constant.RuntimeError
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + err.Error()
_ = runtimeRepo.Save(runtime)
return
}
}
runtime.Status = constant.RuntimeRunning runtime.Status = constant.RuntimeRunning
} }
} }
@ -446,3 +485,49 @@ func checkContainerName(name string) error {
} }
return nil return nil
} }
func unInstallPHPExtension(runtime *model.Runtime, delExtensions []string) error {
dir := runtime.GetPath()
fileOP := files.NewFileOp()
var phpExtensions []response.SupportExtension
if err := json.Unmarshal(nginx_conf.PHPExtensionsJson, &phpExtensions); err != nil {
return err
}
delMap := make(map[string]struct{})
for _, ext := range phpExtensions {
for _, del := range delExtensions {
if ext.Check == del {
delMap[ext.Check] = struct{}{}
_ = fileOP.DeleteFile(path.Join(dir, "extensions", ext.File))
_ = fileOP.DeleteFile(path.Join(dir, "conf", "conf.d", "docker-php-ext-"+ext.Check+".ini"))
break
}
}
}
extensions := getRuntimeEnv(runtime.Env, "PHP_EXTENSIONS")
var (
oldExts []string
newExts []string
)
oldExts = strings.Split(extensions, ",")
for _, ext := range oldExts {
if _, ok := delMap[ext]; !ok {
newExts = append(newExts, ext)
}
}
newExtensions := strings.Join(newExts, ",")
envs, err := gotenv.Unmarshal(runtime.Env)
if err != nil {
return err
}
envs["PHP_EXTENSIONS"] = newExtensions
if err = gotenv.Write(envs, runtime.GetEnvPath()); err != nil {
return err
}
envContent, err := gotenv.Marshal(envs)
if err != nil {
return err
}
runtime.Env = envContent
return nil
}

View File

@ -1,135 +1,331 @@
[ [
{
"name": "ZendGuardLoader",
"description": "用于解密ZendGuard加密脚本!",
"check": "ZendGuardLoader",
"versions": ["53", "54", "55", "56"],
"installed": false
},
{ {
"name": "ionCube", "name": "ionCube",
"description": "用于解密ionCube Encoder加密脚本!", "check": "ioncube_loader",
"check": "ionCube", "file": "ioncube_loader.so",
"versions": ["56", "70", "71", "72", "73", "74", "81", "82"], "versions": ["56", "70", "71", "72", "73", "74", "81", "82"],
"installed": false "installed": false
}, },
{
"name": "fileinfo",
"description": "",
"check": "fileinfo",
"versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{ {
"name": "opcache", "name": "opcache",
"description": "用于加速PHP脚本!",
"check": "opcache", "check": "opcache",
"file": "opcache.so",
"versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false "installed": false
}, },
{
"name": "xcache",
"description": "支持脚本缓存和变量缓存!",
"check": "xcache",
"versions": ["56"],
"installed": false
},
{ {
"name": "memcache", "name": "memcache",
"description": "强大的内容缓存器",
"check": "memcache", "check": "memcache",
"file": "memcache.so",
"versions": ["56", "70", "71", "72", "73", "74", "80"], "versions": ["56", "70", "71", "72", "73", "74", "80"],
"installed": false "installed": false
}, },
{ {
"name": "memcached", "name": "memcached",
"description": "比memcache支持更多高级功能",
"check": "memcached", "check": "memcached",
"file": "memcached.so",
"versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false "installed": false
}, },
{ {
"name": "redis", "name": "redis",
"description": "基于内存亦可持久化的Key-Value数据库",
"check": "redis", "check": "redis",
"file": "redis.so",
"versions": [ "56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], "versions": [ "56", "70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false "installed": false
}, },
{ {
"name": "mcrypt", "name": "mcrypt",
"description": "mcrypt加密/解密",
"check": "mcrypt", "check": "mcrypt",
"file": "mcrypt.so",
"versions": ["70", "71", "72", "73", "74", "80", "81", "82", "83"], "versions": ["70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false "installed": false
}, },
{ {
"name": "apcu", "name": "apcu",
"description": "脚本缓存器",
"check": "apcu", "check": "apcu",
"file": "apcu.so",
"versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false "installed": false
}, },
{ {
"name": "imagemagick", "name": "imagick",
"description": "Imagick高性能图形库",
"check": "imagick", "check": "imagick",
"versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], "file": "imagick.so",
"versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82"],
"installed": false "installed": false
}, },
{ {
"name": "xdebug", "name": "xdebug",
"description": "开源的PHP程序调试器",
"check": "xdebug", "check": "xdebug",
"file": "xdebug.so",
"versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false "installed": false
}, },
{ {
"name": "imap", "name": "imap",
"description": "邮件服务器必备",
"check": "imap", "check": "imap",
"file": "imap.so",
"versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false "installed": false
}, },
{ {
"name": "exif", "name": "exif",
"description": "用于读取图片EXIF信息",
"check": "exif", "check": "exif",
"file": "exif.so",
"versions": [ "56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], "versions": [ "56", "70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false "installed": false
}, },
{ {
"name": "intl", "name": "intl",
"description": "提供国际化支持",
"check": "intl", "check": "intl",
"file": "intl.so",
"versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"], "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false "installed": false
}, },
{ {
"name": "xsl", "name": "xsl",
"description": "xsl解析扩展",
"check": "xsl", "check": "xsl",
"file": "xsl.so",
"versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82"], "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82"],
"installed": false "installed": false
}, },
{
"name": "mbstring",
"description": "mbstring扩展",
"check": "mbstring",
"versions": ["83"],
"installed": false
},
{ {
"name": "Swoole", "name": "Swoole",
"description": "异步、协程高性能网络通信引擎",
"check": "swoole", "check": "swoole",
"versions": ["70", "71", "72"], "file": "swoole.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false "installed": false
}, },
{ {
"name": "zstd", "name": "zstd",
"description": "使用 Zstandard 库进行压缩和解压缩的 PHP 扩展",
"check": "zstd", "check": "zstd",
"versions": ["70", "71", "72"], "file": "zstd.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "xlswriter",
"check": "xlswriter",
"file": "xlswriter.so",
"versions": ["70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "oci8",
"check": "oci8",
"file": "oci8.so",
"versions": ["70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "pdo_oci",
"check": "pdo_oci",
"file": "pdo_oci.so",
"versions": ["70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "pdo_sqlsrv",
"check": "pdo_sqlsrv",
"file": "pdo_sqlsrv.so",
"versions": ["70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "sqlsrv",
"check": "sqlsrv",
"file": "sqlsrv.so",
"versions": ["70", "71", "72", "73", "74","80", "81", "82", "83"],
"installed": false
},
{
"name": "yaf",
"check": "yaf",
"file": "yaf.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "mongodb",
"check": "mongodb",
"file": "mongodb.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "yac",
"check": "yac",
"file": "yac.so",
"versions": ["70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "pgsql",
"check": "pgsql",
"file": "pgsql.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "ssh2",
"check": "ssh2",
"file": "ssh2.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "grpc",
"check": "grpc",
"file": "grpc.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "xhprof",
"check": "xhprof",
"file": "xhprof.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "protobuf",
"check": "protobuf",
"file": "protobuf.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "pdo_pgsql",
"check": "pdo_pgsql",
"file": "pdo_pgsql.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "snmp",
"check": "snmp",
"file": "snmp.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "ldap",
"check": "ldap",
"file": "ldap.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "recode",
"check": "recode",
"file": "recode.so",
"versions": ["56","70", "71", "72", "73"],
"installed": false
},
{
"name": "enchant",
"check": "enchant",
"file": "enchant.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "pspell",
"check": "pspell",
"file": "pspell.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "bz2",
"check": "bz2",
"file": "bz2.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "sysvshm",
"check": "sysvshm",
"file": "sysvshm.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "calendar",
"check": "calendar",
"file": "calendar.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "gmp",
"check": "gmp",
"file": "gmp.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "wddx",
"check": "wddx",
"file": "wddx.so",
"versions": ["56","70", "71", "72", "73", "74"],
"installed": false
},
{
"name": "sysvmsg",
"check": "sysvmsg",
"file": "sysvmsg.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "igbinary",
"check": "igbinary",
"file": "igbinary.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "zmq",
"check": "zmq",
"file": "zmq.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "smbclient",
"check": "smbclient",
"file": "smbclient.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "event",
"check": "event",
"file": "event.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "mailparse",
"check": "mailparse",
"file": "mailparse.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "yaml",
"check": "yaml",
"file": "yaml.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false
},
{
"name": "sg11",
"check": "SourceGuardian",
"file": "sourceguardian.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83"],
"installed": false "installed": false
} }
] ]

View File

@ -33,6 +33,7 @@ func (r *RuntimeRouter) InitRouter(Router *gin.RouterGroup) {
groupRouter.GET("/php/:id/extensions", baseApi.GetRuntimeExtension) groupRouter.GET("/php/:id/extensions", baseApi.GetRuntimeExtension)
groupRouter.POST("/php/extensions/install", baseApi.InstallPHPExtension) groupRouter.POST("/php/extensions/install", baseApi.InstallPHPExtension)
groupRouter.POST("/php/extensions/uninstall", baseApi.UnInstallPHPExtension)
} }
} }

View File

@ -139,6 +139,6 @@ export namespace Runtime {
export interface PHPExtensionInstall { export interface PHPExtensionInstall {
name: string; name: string;
id: number; id: number;
taskID: string; taskID?: string;
} }
} }

View File

@ -75,3 +75,7 @@ export const GetPHPExtensions = (id: number) => {
export const InstallPHPExtension = (req: Runtime.PHPExtensionInstall) => { export const InstallPHPExtension = (req: Runtime.PHPExtensionInstall) => {
return http.post(`/runtimes/php/extensions/install`, req); return http.post(`/runtimes/php/extensions/install`, req);
}; };
export const UnInstallPHPExtension = (req: Runtime.PHPExtensionInstall) => {
return http.post(`/runtimes/php/extensions/uninstall`, req);
};

View File

@ -16,7 +16,7 @@
</DrawerHeader> </DrawerHeader>
</template> </template>
<div> <div>
<LogFile :config="config"></LogFile> <LogFile :config="config" :height-diff="config.heightDiff"></LogFile>
</div> </div>
</el-drawer> </el-drawer>
</template> </template>
@ -39,6 +39,7 @@ interface LogProps {
style: string; style: string;
name: string; name: string;
tail: boolean; tail: boolean;
heightDiff: number;
} }
const open = ref(false); const open = ref(false);
@ -62,6 +63,7 @@ const loadTooltip = () => {
const acceptParams = (props: LogProps) => { const acceptParams = (props: LogProps) => {
config.value = props; config.value = props;
console.log('config', config.value);
open.value = true; open.value = true;
if (!mobile.value) { if (!mobile.value) {

View File

@ -266,6 +266,7 @@ onUnmounted(() => {
}); });
onMounted(() => { onMounted(() => {
console.log(props.heightDiff);
initCodemirror(); initCodemirror();
init(); init();
}); });

View File

@ -9,7 +9,13 @@
:width="width" :width="width"
> >
<div> <div>
<highlightjs ref="editorRef" language="JavaScript" :autodetect="false" :code="content"></highlightjs> <highlightjs
class="editor-main"
ref="editorRef"
language="JavaScript"
:autodetect="false"
:code="content"
></highlightjs>
</div> </div>
</el-dialog> </el-dialog>
</template> </template>
@ -115,6 +121,7 @@ const getContent = (pre: boolean) => {
} }
end.value = res.data.end; end.value = res.data.end;
nextTick(() => { nextTick(() => {
console.log('scrollerElement', scrollerElement.value);
if (pre) { if (pre) {
if (scrollerElement.value.scrollHeight > 2000) { if (scrollerElement.value.scrollHeight > 2000) {
scrollerElement.value.scrollTop = 2000; scrollerElement.value.scrollTop = 2000;
@ -122,6 +129,8 @@ const getContent = (pre: boolean) => {
} else { } else {
scrollerElement.value.scrollTop = scrollerElement.value.scrollHeight; scrollerElement.value.scrollTop = scrollerElement.value.scrollHeight;
} }
console.log('scrollHeight', scrollerElement.value.scrollHeight);
console.log('scrollTop', scrollerElement.value.scrollTop);
}); });
if (readReq.latest) { if (readReq.latest) {
@ -179,6 +188,7 @@ const initCodemirror = () => {
nextTick(() => { nextTick(() => {
if (editorRef.value) { if (editorRef.value) {
scrollerElement.value = editorRef.value.$el as HTMLElement; scrollerElement.value = editorRef.value.$el as HTMLElement;
console.log('scrollerElement', scrollerElement.value);
scrollerElement.value.addEventListener('scroll', function () { scrollerElement.value.addEventListener('scroll', function () {
if (isScrolledToBottom(scrollerElement.value)) { if (isScrolledToBottom(scrollerElement.value)) {
readReq.page = maxPage.value; readReq.page = maxPage.value;
@ -194,8 +204,7 @@ const initCodemirror = () => {
} }
}); });
let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement; let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement;
hljsDom.style['min-height'] = '100px'; hljsDom.style['min-height'] = '400px';
hljsDom.style['max-height'] = '400px';
} }
}); });
}; };
@ -227,8 +236,11 @@ defineExpose({ openWithResourceID, openWithTaskID });
height: calc(var(--dialog-max-height) - var(--dialog-header-height) - var(--dialog-padding) * 2); height: calc(var(--dialog-max-height) - var(--dialog-header-height) - var(--dialog-padding) * 2);
overflow: hidden; overflow: hidden;
} }
.log-file {
height: 100%;
} }
.editor-main {
width: 100%;
overflow: auto;
height: 420px;
} }
</style> </style>

View File

@ -153,6 +153,7 @@ const message = {
resetSuccess: 'Reset successful', resetSuccess: 'Reset successful',
creatingInfo: 'Creating, no need for this operation', creatingInfo: 'Creating, no need for this operation',
installSuccess: 'Install successful', installSuccess: 'Install successful',
uninstallSuccess: 'Uninstall successful',
}, },
login: { login: {
username: 'Username', username: 'Username',

View File

@ -153,6 +153,7 @@ const message = {
resetSuccess: '重置成功', resetSuccess: '重置成功',
creatingInfo: '正在創建無需此操作', creatingInfo: '正在創建無需此操作',
installSuccess: '安裝成功', installSuccess: '安裝成功',
uninstallSuccess: '卸載成功',
}, },
login: { login: {
username: '用戶名', username: '用戶名',

View File

@ -153,6 +153,7 @@ const message = {
resetSuccess: '重置成功', resetSuccess: '重置成功',
creatingInfo: '正在创建无需此操作', creatingInfo: '正在创建无需此操作',
installSuccess: '安装成功', installSuccess: '安装成功',
uninstallSuccess: '卸载成功',
}, },
login: { login: {
username: '用户名', username: '用户名',
@ -2265,6 +2266,7 @@ const message = {
installExtension: '是否确认安装扩展 {0}', installExtension: '是否确认安装扩展 {0}',
loadedExtension: '已加载扩展', loadedExtension: '已加载扩展',
popularExtension: '常用扩展', popularExtension: '常用扩展',
uninstallExtension: '是否确认卸载扩展 {0}',
}, },
process: { process: {
pid: '进程ID', pid: '进程ID',

View File

@ -156,20 +156,6 @@
></el-option> ></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<div v-if="mode == 'edit'">
<el-form-item>
<el-checkbox v-model="runtime.rebuild">
{{ $t('runtime.rebuild') }}
</el-checkbox>
</el-form-item>
<el-form-item>
<el-alert type="info" :closable="false">
<span>{{ $t('runtime.rebuildHelper') }}</span>
<br />
</el-alert>
</el-form-item>
</div>
</div> </div>
</div> </div>
<div v-else> <div v-else>
@ -410,7 +396,9 @@ const getRuntime = async (id: number) => {
forms[fileds[index].key] = fileds[index]; forms[fileds[index].key] = fileds[index];
} }
formFields.value = forms; formFields.value = forms;
if (data.params['PHP_EXTENSIONS'] != '') {
runtime.params['PHP_EXTENSIONS'] = runtime.params['PHP_EXTENSIONS'].split(','); runtime.params['PHP_EXTENSIONS'] = runtime.params['PHP_EXTENSIONS'].split(',');
}
initParam.value = true; initParam.value = true;
} catch (error) {} } catch (error) {}
}; };

View File

@ -9,16 +9,15 @@
<el-text>{{ $t('runtime.popularExtension') }}</el-text> <el-text>{{ $t('runtime.popularExtension') }}</el-text>
</div> </div>
<ComplexTable :data="supportExtensions" @search="search()" :heightDiff="350" :loading="loading"> <ComplexTable :data="supportExtensions" @search="search()" :heightDiff="350" :loading="loading">
<el-table-column prop="name" :label="$t('commons.table.name')" width="150" /> <el-table-column prop="name" :label="$t('commons.table.name')" />
<el-table-column prop="description" :label="$t('commons.table.description')" /> <el-table-column prop="installed" :label="$t('commons.table.status')">
<el-table-column prop="installed" :label="$t('commons.table.status')" width="100">
<template #default="{ row }"> <template #default="{ row }">
<el-icon v-if="row.installed" color="green"><Select /></el-icon> <el-icon v-if="row.installed" color="green"><Select /></el-icon>
<el-icon v-else><CloseBold /></el-icon> <el-icon v-else><CloseBold /></el-icon>
</template> </template>
</el-table-column> </el-table-column>
<fu-table-operations <fu-table-operations
:ellipsis="10" :ellipsis="2"
width="100px" width="100px"
:buttons="buttons" :buttons="buttons"
:label="$t('commons.table.operate')" :label="$t('commons.table.operate')"
@ -27,15 +26,16 @@
/> />
</ComplexTable> </ComplexTable>
</DrawerPro> </DrawerPro>
<TaskLog ref="taskLogRef" /> <TaskLog ref="taskLogRef" @clos="search()" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Runtime } from '@/api/interface/runtime'; import { Runtime } from '@/api/interface/runtime';
import { GetPHPExtensions, InstallPHPExtension } from '@/api/modules/runtime'; import { GetPHPExtensions, InstallPHPExtension, UnInstallPHPExtension } from '@/api/modules/runtime';
import i18n from '@/lang'; import i18n from '@/lang';
import { ref } from 'vue'; import { ref } from 'vue';
import { newUUID } from '@/utils/util'; import { newUUID } from '@/utils/util';
import { MsgSuccess } from '@/utils/message';
const open = ref(false); const open = ref(false);
const runtime = ref(); const runtime = ref();
@ -58,6 +58,15 @@ const buttons = [
return !row.installed; return !row.installed;
}, },
}, },
{
label: i18n.global.t('commons.operate.uninstall'),
click: function (row: Runtime.SupportExtension) {
unInstallPHPExtension(row);
},
show: function (row: Runtime.SupportExtension) {
return row.installed;
},
},
]; ];
const installExtension = async (row: Runtime.SupportExtension) => { const installExtension = async (row: Runtime.SupportExtension) => {
@ -80,6 +89,25 @@ const installExtension = async (row: Runtime.SupportExtension) => {
}); });
}; };
const unInstallPHPExtension = async (row: Runtime.SupportExtension) => {
ElMessageBox.confirm(i18n.global.t('runtime.uninstallExtension', [row.name]), i18n.global.t('runtime.extension'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
}).then(async () => {
const req = {
id: runtime.value.id,
name: row.check,
};
loading.value = true;
try {
await UnInstallPHPExtension(req);
MsgSuccess(i18n.global.t('commons.msg.uninstallSuccess'));
loading.value = false;
search();
} catch (error) {}
});
};
const search = async () => { const search = async () => {
try { try {
const res = await GetPHPExtensions(runtime.value.id); const res = await GetPHPExtensions(runtime.value.id);

View File

@ -94,7 +94,7 @@
/> />
<fu-table-operations <fu-table-operations
:ellipsis="10" :ellipsis="10"
width="200px" width="300px"
:buttons="buttons" :buttons="buttons"
:label="$t('commons.table.operate')" :label="$t('commons.table.operate')"
fixed="right" fixed="right"
@ -110,13 +110,14 @@
<Extensions ref="extensionsRef" @close="search" /> <Extensions ref="extensionsRef" @close="search" />
<AppResources ref="checkRef" @close="search" /> <AppResources ref="checkRef" @close="search" />
<ExtManagement ref="extManagementRef" @close="search" /> <ExtManagement ref="extManagementRef" @close="search" />
<ComposeLogs ref="composeLogRef" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, onUnmounted, reactive, ref } from 'vue'; import { onMounted, onUnmounted, reactive, ref } from 'vue';
import { Runtime } from '@/api/interface/runtime'; import { Runtime } from '@/api/interface/runtime';
import { DeleteRuntime, RuntimeDeleteCheck, SearchRuntimes } from '@/api/modules/runtime'; import { DeleteRuntime, OperateRuntime, RuntimeDeleteCheck, SearchRuntimes } from '@/api/modules/runtime';
import { dateFormat, toLowerCase } from '@/utils/util'; import { dateFormat, toLowerCase } from '@/utils/util';
import { ElMessageBox } from 'element-plus'; import { ElMessageBox } from 'element-plus';
import { containerPrune } from '@/api/modules/container'; import { containerPrune } from '@/api/modules/container';
@ -129,6 +130,7 @@ import CreateRuntime from '@/views/website/runtime/php/create/index.vue';
import Status from '@/components/status/index.vue'; import Status from '@/components/status/index.vue';
import RouterMenu from '../index.vue'; import RouterMenu from '../index.vue';
import Log from '@/components/log-dialog/index.vue'; import Log from '@/components/log-dialog/index.vue';
import ComposeLogs from '@/components/compose-log/index.vue';
const paginationConfig = reactive({ const paginationConfig = reactive({
cacheSizeKey: 'runtime-page-size', cacheSizeKey: 'runtime-page-size',
@ -151,6 +153,7 @@ const checkRef = ref();
const createRef = ref(); const createRef = ref();
const loading = ref(false); const loading = ref(false);
const items = ref<Runtime.RuntimeDTO[]>([]); const items = ref<Runtime.RuntimeDTO[]>([]);
const composeLogRef = ref();
const buttons = [ const buttons = [
{ {
@ -162,6 +165,33 @@ const buttons = [
return row.status != 'running'; return row.status != 'running';
}, },
}, },
{
label: i18n.global.t('container.stop'),
click: function (row: Runtime.Runtime) {
operateRuntime('down', row.id);
},
disabled: function (row: Runtime.Runtime) {
return row.status === 'recreating' || row.status === 'stopped';
},
},
{
label: i18n.global.t('container.start'),
click: function (row: Runtime.Runtime) {
operateRuntime('up', row.id);
},
disabled: function (row: Runtime.Runtime) {
return row.status === 'starting' || row.status === 'recreating' || row.status === 'running';
},
},
{
label: i18n.global.t('container.restart'),
click: function (row: Runtime.Runtime) {
operateRuntime('restart', row.id);
},
disabled: function (row: Runtime.Runtime) {
return row.status === 'recreating';
},
},
{ {
label: i18n.global.t('commons.button.edit'), label: i18n.global.t('commons.button.edit'),
click: function (row: Runtime.Runtime) { click: function (row: Runtime.Runtime) {
@ -205,11 +235,15 @@ const openDetail = (row: Runtime.Runtime) => {
}; };
const openLog = (row: Runtime.RuntimeDTO) => { const openLog = (row: Runtime.RuntimeDTO) => {
logRef.value.acceptParams({ id: row.id, type: 'php', tail: row.status == 'building' }); if (row.status == 'running') {
composeLogRef.value.acceptParams({ compose: row.path + '/docker-compose.yml', resource: row.name });
} else {
logRef.value.acceptParams({ id: row.id, type: 'php', tail: row.status == 'building', heightDiff: 220 });
}
}; };
const openCreateLog = (id: number) => { const openCreateLog = (id: number) => {
logRef.value.acceptParams({ id: id, type: 'php', tail: true }); logRef.value.acceptParams({ id: id, type: 'php', tail: true, heightDiff: 220 });
}; };
const openExtensions = () => { const openExtensions = () => {
@ -240,6 +274,28 @@ const openDelete = async (row: Runtime.Runtime) => {
}); });
}; };
const operateRuntime = async (operate: string, ID: number) => {
try {
const action = await ElMessageBox.confirm(
i18n.global.t('runtime.operatorHelper', [i18n.global.t('commons.operate.' + operate)]),
i18n.global.t('commons.operate.' + operate),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
},
);
if (action === 'confirm') {
loading.value = true;
await OperateRuntime({ operate: operate, ID: ID });
search();
}
} catch (error) {
} finally {
loading.value = false;
}
};
const onOpenBuildCache = () => { const onOpenBuildCache = () => {
ElMessageBox.confirm(i18n.global.t('container.delBuildCacheHelper'), i18n.global.t('container.cleanBuildCache'), { ElMessageBox.confirm(i18n.global.t('container.delBuildCacheHelper'), i18n.global.t('container.cleanBuildCache'), {
confirmButtonText: i18n.global.t('commons.button.confirm'), confirmButtonText: i18n.global.t('commons.button.confirm'),