mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 05:31:44 +08:00
feat: 初步支持环境管理
This commit is contained in:
@@ -90,6 +90,9 @@ func initWeb() (*app.Web, error) {
|
||||
certDNSService := service.NewCertDNSService(certDNSRepo)
|
||||
certAccountService := service.NewCertAccountService(certAccountRepo)
|
||||
appService := service.NewAppService(locale, appRepo, cacheRepo, settingRepo)
|
||||
environmentRepo := data.NewEnvironmentRepo(locale, config, cacheRepo, taskRepo)
|
||||
environmentService := service.NewEnvironmentService(locale, environmentRepo, taskRepo)
|
||||
environmentPHPService := service.NewEnvironmentPHPService(locale, environmentRepo, taskRepo)
|
||||
cronService := service.NewCronService(cronRepo)
|
||||
processService := service.NewProcessService()
|
||||
safeRepo := data.NewSafeRepo()
|
||||
@@ -134,7 +137,7 @@ func initWeb() (*app.Web, error) {
|
||||
s3fsApp := s3fs.NewApp(locale)
|
||||
supervisorApp := supervisor.NewApp(locale)
|
||||
loader := bootstrap.NewLoader(codeserverApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, minioApp, mysqlApp, nginxApp, openrestyApp, perconaApp, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp)
|
||||
http := route.NewHttp(config, userService, userTokenService, homeService, taskService, websiteService, databaseService, databaseServerService, databaseUserService, backupService, certService, certDNSService, certAccountService, appService, cronService, processService, safeService, firewallService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, monitorService, settingService, systemctlService, toolboxSystemService, toolboxBenchmarkService, loader)
|
||||
http := route.NewHttp(config, userService, userTokenService, homeService, taskService, websiteService, databaseService, databaseServerService, databaseUserService, backupService, certService, certDNSService, certAccountService, appService, environmentService, environmentPHPService, cronService, processService, safeService, firewallService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, monitorService, settingService, systemctlService, toolboxSystemService, toolboxBenchmarkService, loader)
|
||||
wsService := service.NewWsService(locale, config, logger, sshRepo)
|
||||
ws := route.NewWs(wsService)
|
||||
mux, err := bootstrap.NewRouter(locale, middlewares, http, ws)
|
||||
|
||||
7
go.sum
7
go.sum
@@ -118,6 +118,8 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
|
||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
|
||||
github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
@@ -267,6 +269,7 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||
@@ -375,6 +378,8 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -447,6 +452,8 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
|
||||
@@ -1,523 +0,0 @@
|
||||
package php
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/biz"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/shell"
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
version uint
|
||||
t *gotext.Locale
|
||||
taskRepo biz.TaskRepo
|
||||
}
|
||||
|
||||
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
taskRepo: task,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(version uint) func(r chi.Router) {
|
||||
return func(r chi.Router) {
|
||||
php := new(App)
|
||||
php.version = version
|
||||
php.t = s.t
|
||||
php.taskRepo = s.taskRepo
|
||||
r.Post("/set_cli", php.SetCli)
|
||||
r.Get("/config", php.GetConfig)
|
||||
r.Post("/config", php.UpdateConfig)
|
||||
r.Get("/fpm_config", php.GetFPMConfig)
|
||||
r.Post("/fpm_config", php.UpdateFPMConfig)
|
||||
r.Get("/load", php.Load)
|
||||
r.Get("/log", php.Log)
|
||||
r.Get("/slow_log", php.SlowLog)
|
||||
r.Post("/clear_log", php.ClearLog)
|
||||
r.Post("/clear_slow_log", php.ClearSlowLog)
|
||||
r.Get("/extensions", php.ExtensionList)
|
||||
r.Post("/extensions", php.InstallExtension)
|
||||
r.Delete("/extensions", php.UninstallExtension)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) SetCli(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := shell.Execf("ln -sf %s/server/php/%d/bin/php /usr/local/bin/php", app.Root, s.version); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config, err := io.Read(fmt.Sprintf("%s/server/php/%d/etc/php.ini", app.Root, s.version))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, config)
|
||||
}
|
||||
|
||||
func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[UpdateConfig](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write(fmt.Sprintf("%s/server/php/%d/etc/php.ini", app.Root, s.version), req.Config, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) GetFPMConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config, err := io.Read(fmt.Sprintf("%s/server/php/%d/etc/php-fpm.conf", app.Root, s.version))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, config)
|
||||
}
|
||||
|
||||
func (s *App) UpdateFPMConfig(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[UpdateConfig](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write(fmt.Sprintf("%s/server/php/%d/etc/php-fpm.conf", app.Root, s.version), req.Config, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
var raw map[string]any
|
||||
client := resty.New().SetTimeout(10 * time.Second)
|
||||
_, err := client.R().SetResult(&raw).Get(fmt.Sprintf("http://127.0.0.1/phpfpm_status/%d?json", s.version))
|
||||
if err != nil {
|
||||
service.Success(w, []types.NV{})
|
||||
return
|
||||
}
|
||||
|
||||
dataKeys := []string{
|
||||
s.t.Get("Application Pool"),
|
||||
s.t.Get("Process Manager"),
|
||||
s.t.Get("Start Time"),
|
||||
s.t.Get("Accepted Connections"),
|
||||
s.t.Get("Listen Queue"),
|
||||
s.t.Get("Max Listen Queue"),
|
||||
s.t.Get("Listen Queue Length"),
|
||||
s.t.Get("Idle Processes"),
|
||||
s.t.Get("Active Processes"),
|
||||
s.t.Get("Total Processes"),
|
||||
s.t.Get("Max Active Processes"),
|
||||
s.t.Get("Max Children Reached"),
|
||||
s.t.Get("Slow Requests"),
|
||||
}
|
||||
rawKeys := []string{
|
||||
"pool",
|
||||
"process manager",
|
||||
"start time",
|
||||
"accepted conn",
|
||||
"listen queue",
|
||||
"max listen queue",
|
||||
"listen queue len",
|
||||
"idle processes",
|
||||
"active processes",
|
||||
"total processes",
|
||||
"max active processes",
|
||||
"max children reached",
|
||||
"slow requests",
|
||||
}
|
||||
|
||||
loads := make([]types.NV, 0)
|
||||
for i := range dataKeys {
|
||||
v, ok := raw[rawKeys[i]]
|
||||
if ok {
|
||||
loads = append(loads, types.NV{
|
||||
Name: dataKeys[i],
|
||||
Value: cast.ToString(v),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
service.Success(w, loads)
|
||||
}
|
||||
|
||||
func (s *App) Log(w http.ResponseWriter, r *http.Request) {
|
||||
service.Success(w, fmt.Sprintf("%s/server/php/%d/var/log/php-fpm.log", app.Root, s.version))
|
||||
}
|
||||
|
||||
func (s *App) SlowLog(w http.ResponseWriter, r *http.Request) {
|
||||
service.Success(w, fmt.Sprintf("%s/server/php/%d/var/log/slow.log", app.Root, s.version))
|
||||
}
|
||||
|
||||
func (s *App) ClearLog(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := shell.Execf("cat /dev/null > %s/server/php/%d/var/log/php-fpm.log", app.Root, s.version); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) ClearSlowLog(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := shell.Execf("cat /dev/null > %s/server/php/%d/var/log/slow.log", app.Root, s.version); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) ExtensionList(w http.ResponseWriter, r *http.Request) {
|
||||
extensions := s.getExtensions()
|
||||
raw, err := shell.Execf("%s/server/php/%d/bin/php -m", app.Root, s.version)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
extensionMap := make(map[string]*Extension)
|
||||
for i := range extensions {
|
||||
extensionMap[extensions[i].Slug] = &extensions[i]
|
||||
}
|
||||
|
||||
rawExtensionList := strings.Split(raw, "\n")
|
||||
for _, item := range rawExtensionList {
|
||||
if ext, exists := extensionMap[item]; exists && !strings.Contains(item, "[") && item != "" {
|
||||
ext.Installed = true
|
||||
}
|
||||
}
|
||||
|
||||
service.Success(w, extensions)
|
||||
}
|
||||
|
||||
func (s *App) InstallExtension(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[ExtensionSlug](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !s.checkExtension(req.Slug) {
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("extension %s does not exist", req.Slug))
|
||||
return
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf(`curl -sSLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/%s.sh' | bash -s -- 'install' '%d' >> '/tmp/%s.log' 2>&1`, url.PathEscape(req.Slug), s.version, req.Slug)
|
||||
officials := []string{"fileinfo", "exif", "imap", "pgsql", "pdo_pgsql", "zip", "bz2", "readline", "snmp", "ldap", "enchant", "pspell", "calendar", "gmp", "sysvmsg", "sysvsem", "sysvshm", "xsl", "intl", "gettext"}
|
||||
if slices.Contains(officials, req.Slug) {
|
||||
cmd = fmt.Sprintf(`curl -sSLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/official.sh' | bash -s -- 'install' '%d' '%s' >> '/tmp/%s.log' 2>&1`, s.version, req.Slug, req.Slug)
|
||||
}
|
||||
|
||||
task := new(biz.Task)
|
||||
task.Name = s.t.Get("Install PHP-%d %s extension", s.version, req.Slug)
|
||||
task.Status = biz.TaskStatusWaiting
|
||||
task.Shell = cmd
|
||||
task.Log = "/tmp/" + req.Slug + ".log"
|
||||
if err = s.taskRepo.Push(task); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) UninstallExtension(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[ExtensionSlug](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !s.checkExtension(req.Slug) {
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("extension %s does not exist", req.Slug))
|
||||
return
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf(`curl -sSLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/%s.sh' | bash -s -- 'uninstall' '%d' >> '/tmp/%s.log' 2>&1`, url.PathEscape(req.Slug), s.version, req.Slug)
|
||||
officials := []string{"fileinfo", "exif", "imap", "pgsql", "pdo_pgsql", "zip", "bz2", "readline", "snmp", "ldap", "enchant", "pspell", "calendar", "gmp", "sysvmsg", "sysvsem", "sysvshm", "xsl", "intl", "gettext"}
|
||||
if slices.Contains(officials, req.Slug) {
|
||||
cmd = fmt.Sprintf(`curl -sSLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/official.sh' | bash -s -- 'uninstall' '%d' '%s' >> '/tmp/%s.log' 2>&1`, s.version, req.Slug, req.Slug)
|
||||
}
|
||||
|
||||
task := new(biz.Task)
|
||||
task.Name = s.t.Get("Uninstall PHP-%d %s extension", s.version, req.Slug)
|
||||
task.Status = biz.TaskStatusWaiting
|
||||
task.Shell = cmd
|
||||
task.Log = "/tmp/" + req.Slug + ".log"
|
||||
if err = s.taskRepo.Push(task); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) getExtensions() []Extension {
|
||||
extensions := []Extension{
|
||||
{
|
||||
Name: "fileinfo",
|
||||
Slug: "fileinfo",
|
||||
Description: s.t.Get("Fileinfo is a library used to identify file types"),
|
||||
},
|
||||
{
|
||||
Name: "OPcache",
|
||||
Slug: "Zend OPcache",
|
||||
Description: s.t.Get("OPcache stores precompiled PHP script bytecode in shared memory to improve PHP performance"),
|
||||
},
|
||||
{
|
||||
Name: "igbinary",
|
||||
Slug: "igbinary",
|
||||
Description: s.t.Get("Igbinary is a library for serializing and deserializing data"),
|
||||
},
|
||||
{
|
||||
Name: "Redis",
|
||||
Slug: "redis",
|
||||
Description: s.t.Get("PhpRedis connects to and operates on data in Redis databases (requires the igbinary extension installed above)"),
|
||||
},
|
||||
{
|
||||
Name: "Memcached",
|
||||
Slug: "memcached",
|
||||
Description: s.t.Get("Memcached is a driver for connecting to Memcached servers"),
|
||||
},
|
||||
{
|
||||
Name: "ImageMagick",
|
||||
Slug: "imagick",
|
||||
Description: s.t.Get("ImageMagick is free software for creating, editing, and composing images"),
|
||||
},
|
||||
{
|
||||
Name: "exif",
|
||||
Slug: "exif",
|
||||
Description: s.t.Get("Exif is a library for reading and writing image metadata"),
|
||||
},
|
||||
{
|
||||
Name: "pgsql",
|
||||
Slug: "pgsql",
|
||||
Description: s.t.Get("pgsql is a driver for connecting to PostgreSQL (requires PostgreSQL installed)"),
|
||||
},
|
||||
{
|
||||
Name: "pdo_pgsql",
|
||||
Slug: "pdo_pgsql",
|
||||
Description: s.t.Get("pdo_pgsql is a PDO driver for connecting to PostgreSQL (requires PostgreSQL installed)"),
|
||||
},
|
||||
{
|
||||
Name: "sqlsrv",
|
||||
Slug: "sqlsrv",
|
||||
Description: s.t.Get("sqlsrv is a driver for connecting to SQL Server"),
|
||||
},
|
||||
{
|
||||
Name: "pdo_sqlsrv",
|
||||
Slug: "pdo_sqlsrv",
|
||||
Description: s.t.Get("pdo_sqlsrv is a PDO driver for connecting to SQL Server"),
|
||||
},
|
||||
{
|
||||
Name: "imap",
|
||||
Slug: "imap",
|
||||
Description: s.t.Get("IMAP extension allows PHP to read, search, delete, download, and manage emails"),
|
||||
},
|
||||
{
|
||||
Name: "zip",
|
||||
Slug: "zip",
|
||||
Description: s.t.Get("Zip is a library for handling ZIP files"),
|
||||
},
|
||||
{
|
||||
Name: "bz2",
|
||||
Slug: "bz2",
|
||||
Description: s.t.Get("Bzip2 is a library for compressing and decompressing files"),
|
||||
},
|
||||
{
|
||||
Name: "ssh2",
|
||||
Slug: "ssh2",
|
||||
Description: s.t.Get("SSH2 is a library for connecting to SSH servers"),
|
||||
},
|
||||
{
|
||||
Name: "event",
|
||||
Slug: "event",
|
||||
Description: s.t.Get("Event is a library for handling events"),
|
||||
},
|
||||
{
|
||||
Name: "readline",
|
||||
Slug: "readline",
|
||||
Description: s.t.Get("Readline is a library for processing text"),
|
||||
},
|
||||
{
|
||||
Name: "snmp",
|
||||
Slug: "snmp",
|
||||
Description: s.t.Get("SNMP is a protocol for network management"),
|
||||
},
|
||||
{
|
||||
Name: "ldap",
|
||||
Slug: "ldap",
|
||||
Description: s.t.Get("LDAP is a protocol for accessing directory services"),
|
||||
},
|
||||
{
|
||||
Name: "enchant",
|
||||
Slug: "enchant",
|
||||
Description: s.t.Get("Enchant is a spell-checking library"),
|
||||
},
|
||||
{
|
||||
Name: "pspell",
|
||||
Slug: "pspell",
|
||||
Description: s.t.Get("Pspell is a spell-checking library"),
|
||||
},
|
||||
{
|
||||
Name: "calendar",
|
||||
Slug: "calendar",
|
||||
Description: s.t.Get("Calendar is a library for handling dates"),
|
||||
},
|
||||
{
|
||||
Name: "gmp",
|
||||
Slug: "gmp",
|
||||
Description: s.t.Get("GMP is a library for handling large integers"),
|
||||
},
|
||||
{
|
||||
Name: "xlswriter",
|
||||
Slug: "xlswriter",
|
||||
Description: s.t.Get("XLSWriter is a high-performance library for reading and writing Excel files"),
|
||||
},
|
||||
{
|
||||
Name: "xsl",
|
||||
Slug: "xsl",
|
||||
Description: s.t.Get("XSL is a library for processing XML documents"),
|
||||
},
|
||||
{
|
||||
Name: "intl",
|
||||
Slug: "intl",
|
||||
Description: s.t.Get("Intl is a library for handling internationalization and localization"),
|
||||
},
|
||||
{
|
||||
Name: "gettext",
|
||||
Slug: "gettext",
|
||||
Description: s.t.Get("Gettext is a library for handling multilingual support"),
|
||||
},
|
||||
{
|
||||
Name: "grpc",
|
||||
Slug: "grpc",
|
||||
Description: s.t.Get("gRPC is a high-performance, open-source, and general-purpose RPC framework"),
|
||||
},
|
||||
{
|
||||
Name: "protobuf",
|
||||
Slug: "protobuf",
|
||||
Description: s.t.Get("protobuf is a library for serializing and deserializing data"),
|
||||
},
|
||||
{
|
||||
Name: "rdkafka",
|
||||
Slug: "rdkafka",
|
||||
Description: s.t.Get("rdkafka is a library for connecting to Apache Kafka"),
|
||||
},
|
||||
{
|
||||
Name: "xhprof",
|
||||
Slug: "xhprof",
|
||||
Description: s.t.Get("xhprof is a library for performance profiling"),
|
||||
},
|
||||
{
|
||||
Name: "xdebug",
|
||||
Slug: "xdebug",
|
||||
Description: s.t.Get("xdebug is a library for debugging and profiling PHP code"),
|
||||
},
|
||||
{
|
||||
Name: "yaml",
|
||||
Slug: "yaml",
|
||||
Description: s.t.Get("yaml is a library for handling YAML"),
|
||||
},
|
||||
{
|
||||
Name: "zstd",
|
||||
Slug: "zstd",
|
||||
Description: s.t.Get("zstd is a library for compressing and decompressing files"),
|
||||
},
|
||||
{
|
||||
Name: "sysvmsg",
|
||||
Slug: "sysvmsg",
|
||||
Description: s.t.Get("Sysvmsg is a library for handling System V message queues"),
|
||||
},
|
||||
{
|
||||
Name: "sysvsem",
|
||||
Slug: "sysvsem",
|
||||
Description: s.t.Get("Sysvsem is a library for handling System V semaphores"),
|
||||
},
|
||||
{
|
||||
Name: "sysvshm",
|
||||
Slug: "sysvshm",
|
||||
Description: s.t.Get("Sysvshm is a library for handling System V shared memory"),
|
||||
},
|
||||
{
|
||||
Name: "ionCube",
|
||||
Slug: "ionCube Loader",
|
||||
Description: s.t.Get("ionCube is a professional-grade PHP encryption and decryption tool (must be installed after OPcache)"),
|
||||
},
|
||||
{
|
||||
Name: "Swoole",
|
||||
Slug: "swoole",
|
||||
Description: s.t.Get("Swoole is a PHP extension for building high-performance asynchronous concurrent servers"),
|
||||
},
|
||||
}
|
||||
|
||||
// Swow 不支持 PHP 8.0 以下版本且目前不支持 PHP 8.4
|
||||
if cast.ToUint(s.version) >= 80 && cast.ToUint(s.version) < 84 {
|
||||
extensions = append(extensions, Extension{
|
||||
Name: "Swow",
|
||||
Slug: "Swow",
|
||||
Description: s.t.Get("Swow is a PHP extension for building high-performance asynchronous concurrent servers"),
|
||||
})
|
||||
}
|
||||
// PHP 8.4 移除了 pspell 和 imap 并且不再建议使用
|
||||
if cast.ToUint(s.version) >= 84 {
|
||||
extensions = slices.DeleteFunc(extensions, func(extension Extension) bool {
|
||||
return extension.Slug == "pspell" || extension.Slug == "imap"
|
||||
})
|
||||
}
|
||||
|
||||
raw, _ := shell.Execf("%s/server/php/%d/bin/php -m", app.Root, s.version)
|
||||
extensionMap := make(map[string]*Extension)
|
||||
for i := range extensions {
|
||||
extensionMap[extensions[i].Slug] = &extensions[i]
|
||||
}
|
||||
|
||||
rawExtensionList := strings.Split(raw, "\n")
|
||||
for _, item := range rawExtensionList {
|
||||
if ext, exists := extensionMap[item]; exists && !strings.Contains(item, "[") && item != "" {
|
||||
ext.Installed = true
|
||||
}
|
||||
}
|
||||
|
||||
return extensions
|
||||
}
|
||||
|
||||
func (s *App) checkExtension(slug string) bool {
|
||||
extensions := s.getExtensions()
|
||||
|
||||
for _, item := range extensions {
|
||||
if item.Slug == slug {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package php
|
||||
|
||||
type UpdateConfig struct {
|
||||
Config string `form:"config" json:"config" validate:"required"`
|
||||
}
|
||||
|
||||
type ExtensionSlug struct {
|
||||
Slug string `form:"slug" json:"slug" validate:"required"`
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package php74
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/acepanel/panel/internal/apps/php"
|
||||
"github.com/acepanel/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(t, task),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.php.Route(74)(r)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package php80
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/acepanel/panel/internal/apps/php"
|
||||
"github.com/acepanel/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(t, task),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.php.Route(80)(r)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package php81
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/acepanel/panel/internal/apps/php"
|
||||
"github.com/acepanel/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(t, task),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.php.Route(81)(r)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package php82
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/acepanel/panel/internal/apps/php"
|
||||
"github.com/acepanel/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(t, task),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.php.Route(82)(r)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package php83
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/acepanel/panel/internal/apps/php"
|
||||
"github.com/acepanel/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(t, task),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.php.Route(83)(r)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package php84
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/acepanel/panel/internal/apps/php"
|
||||
"github.com/acepanel/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(t, task),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.php.Route(84)(r)
|
||||
}
|
||||
@@ -5,9 +5,10 @@ import "time"
|
||||
type CacheKey string
|
||||
|
||||
const (
|
||||
CacheKeyCategories CacheKey = "categories"
|
||||
CacheKeyApps CacheKey = "apps"
|
||||
CacheKeyRewrites CacheKey = "rewrites"
|
||||
CacheKeyCategories CacheKey = "categories"
|
||||
CacheKeyApps CacheKey = "apps"
|
||||
CacheKeyEnvironment CacheKey = "environment"
|
||||
CacheKeyRewrites CacheKey = "rewrites"
|
||||
)
|
||||
|
||||
type Cache struct {
|
||||
@@ -22,5 +23,6 @@ type CacheRepo interface {
|
||||
Set(key CacheKey, value string) error
|
||||
UpdateCategories() error
|
||||
UpdateApps() error
|
||||
UpdateEnvironments() error
|
||||
UpdateRewrites() error
|
||||
}
|
||||
|
||||
16
internal/biz/environment.go
Normal file
16
internal/biz/environment.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"github.com/acepanel/panel/pkg/api"
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
)
|
||||
|
||||
type EnvironmentRepo interface {
|
||||
Types() []types.LV
|
||||
All(typ ...string) api.Environments
|
||||
IsInstalled(typ, slug string) bool
|
||||
HasUpdate(typ, slug string) bool
|
||||
Install(typ, slug string) error
|
||||
Uninstall(typ, slug string) error
|
||||
Update(typ, slug string) error
|
||||
}
|
||||
@@ -155,7 +155,6 @@ func (r *appRepo) GetHomeShow() ([]map[string]string, error) {
|
||||
"name": loaded.Name,
|
||||
"description": loaded.Description,
|
||||
"slug": loaded.Slug,
|
||||
"icon": loaded.Icon,
|
||||
"version": item.Version,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -83,6 +83,20 @@ func (r *cacheRepo) UpdateApps() error {
|
||||
return r.Set(biz.CacheKeyApps, string(encoded))
|
||||
}
|
||||
|
||||
func (r *cacheRepo) UpdateEnvironments() error {
|
||||
environments, err := r.api.Environments()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encoded, err := json.Marshal(environments)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.Set(biz.CacheKeyEnvironment, string(encoded))
|
||||
}
|
||||
|
||||
func (r *cacheRepo) UpdateRewrites() error {
|
||||
rewrites, err := r.api.RewritesByType("nginx")
|
||||
if err != nil {
|
||||
|
||||
@@ -19,6 +19,7 @@ var ProviderSet = wire.NewSet(
|
||||
NewDatabaseRepo,
|
||||
NewDatabaseServerRepo,
|
||||
NewDatabaseUserRepo,
|
||||
NewEnvironmentRepo,
|
||||
NewMonitorRepo,
|
||||
NewSafeRepo,
|
||||
NewSettingRepo,
|
||||
|
||||
141
internal/data/environment.go
Normal file
141
internal/data/environment.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/biz"
|
||||
"github.com/acepanel/panel/pkg/api"
|
||||
"github.com/acepanel/panel/pkg/config"
|
||||
"github.com/acepanel/panel/pkg/shell"
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
)
|
||||
|
||||
type environmentRepo struct {
|
||||
t *gotext.Locale
|
||||
conf *config.Config
|
||||
cache biz.CacheRepo
|
||||
task biz.TaskRepo
|
||||
}
|
||||
|
||||
func NewEnvironmentRepo(t *gotext.Locale, conf *config.Config, cache biz.CacheRepo, task biz.TaskRepo) biz.EnvironmentRepo {
|
||||
return &environmentRepo{
|
||||
t: t,
|
||||
conf: conf,
|
||||
cache: cache,
|
||||
task: task,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *environmentRepo) Types() []types.LV {
|
||||
return []types.LV{
|
||||
{Label: "PHP", Value: "php"},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *environmentRepo) All(typ ...string) api.Environments {
|
||||
cached, err := r.cache.Get(biz.CacheKeyEnvironment)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var environments api.Environments
|
||||
if err = json.Unmarshal([]byte(cached), &environments); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 过滤
|
||||
slices.DeleteFunc(environments, func(env *api.Environment) bool {
|
||||
return len(typ) > 0 && typ[0] != "" && env.Type != typ[0]
|
||||
})
|
||||
|
||||
return environments
|
||||
}
|
||||
|
||||
func (r *environmentRepo) GetByTypeAndSlug(typ, slug string) *api.Environment {
|
||||
all := r.All()
|
||||
for _, env := range all {
|
||||
if env.Type == typ && env.Slug == slug {
|
||||
return env
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *environmentRepo) IsInstalled(typ, slug string) bool {
|
||||
path := filepath.Join(app.Root, "server", typ, slug)
|
||||
exist, _ := os.Stat(path)
|
||||
return exist != nil && exist.IsDir()
|
||||
}
|
||||
|
||||
func (r *environmentRepo) HasUpdate(typ, slug string) bool {
|
||||
if !r.IsInstalled(typ, slug) {
|
||||
return false
|
||||
}
|
||||
|
||||
var basePath = filepath.Join(app.Root, "server", typ, slug)
|
||||
env := r.GetByTypeAndSlug(typ, slug)
|
||||
if env == nil {
|
||||
return false
|
||||
}
|
||||
mainlineVersion := env.Version
|
||||
|
||||
switch typ {
|
||||
case "php":
|
||||
installedVersion, err := shell.Exec(filepath.Join(basePath, "bin", "php") + " -v | head -n 1 | awk '{print $2}'")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return installedVersion != mainlineVersion
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (r *environmentRepo) Install(typ, slug string) error {
|
||||
return r.do(typ, slug, "install")
|
||||
}
|
||||
|
||||
func (r *environmentRepo) Uninstall(typ, slug string) error {
|
||||
return r.do(typ, slug, "uninstall")
|
||||
}
|
||||
|
||||
func (r *environmentRepo) Update(typ, slug string) error {
|
||||
return r.do(typ, slug, "update")
|
||||
}
|
||||
|
||||
func (r *environmentRepo) do(typ, slug, action string) error {
|
||||
env := r.GetByTypeAndSlug(typ, slug)
|
||||
if env == nil {
|
||||
return fmt.Errorf("environment not found: %s-%s", typ, slug)
|
||||
}
|
||||
|
||||
shellUrl := fmt.Sprintf("https://%s/%s/%s.sh", r.conf.App.DownloadEndpoint, typ, action)
|
||||
|
||||
if app.IsCli {
|
||||
return shell.ExecfWithOutput(`curl -sSLm 10 --retry 3 "%s" | bash -s -- "%s"`, shellUrl, slug)
|
||||
}
|
||||
|
||||
var name string
|
||||
switch action {
|
||||
case "install":
|
||||
name = r.t.Get("Install environment %s", env.Name)
|
||||
case "uninstall":
|
||||
name = r.t.Get("Uninstall environment %s", env.Name)
|
||||
case "update":
|
||||
name = r.t.Get("Update environment %s", env.Name)
|
||||
}
|
||||
|
||||
task := new(biz.Task)
|
||||
task.Name = name
|
||||
task.Status = biz.TaskStatusWaiting
|
||||
task.Shell = fmt.Sprintf(`curl -sSLm 10 --retry 3 "%s" | bash -s -- "%s" >> /tmp/%s-%s.log 2>&1`, shellUrl, slug, typ, slug)
|
||||
task.Log = fmt.Sprintf("/tmp/%s-%s.log", typ, slug)
|
||||
|
||||
return r.task.Push(task)
|
||||
}
|
||||
7
internal/http/request/environment.go
Normal file
7
internal/http/request/environment.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package request
|
||||
|
||||
// EnvironmentAction 环境操作请求
|
||||
type EnvironmentAction struct {
|
||||
Type string `json:"type"`
|
||||
Slug string `json:"slug"`
|
||||
}
|
||||
15
internal/http/request/environment_php.go
Normal file
15
internal/http/request/environment_php.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package request
|
||||
|
||||
type EnvironmentPHPVersion struct {
|
||||
Version uint `json:"version"`
|
||||
}
|
||||
|
||||
type EnvironmentPHPModule struct {
|
||||
Version uint `json:"version"`
|
||||
Slug string `form:"slug" json:"slug" validate:"required"`
|
||||
}
|
||||
|
||||
type EnvironmentPHPUpdateConfig struct {
|
||||
Version uint `json:"version"`
|
||||
Config string `form:"config" json:"config" validate:"required"`
|
||||
}
|
||||
@@ -30,6 +30,8 @@ type Http struct {
|
||||
certDNS *service.CertDNSService
|
||||
certAccount *service.CertAccountService
|
||||
app *service.AppService
|
||||
environment *service.EnvironmentService
|
||||
environmentPHP *service.EnvironmentPHPService
|
||||
cron *service.CronService
|
||||
process *service.ProcessService
|
||||
safe *service.SafeService
|
||||
@@ -64,6 +66,8 @@ func NewHttp(
|
||||
certDNS *service.CertDNSService,
|
||||
certAccount *service.CertAccountService,
|
||||
app *service.AppService,
|
||||
environment *service.EnvironmentService,
|
||||
environmentPHP *service.EnvironmentPHPService,
|
||||
cron *service.CronService,
|
||||
process *service.ProcessService,
|
||||
safe *service.SafeService,
|
||||
@@ -97,6 +101,8 @@ func NewHttp(
|
||||
certDNS: certDNS,
|
||||
certAccount: certAccount,
|
||||
app: app,
|
||||
environment: environment,
|
||||
environmentPHP: environmentPHP,
|
||||
cron: cron,
|
||||
process: process,
|
||||
safe: safe,
|
||||
@@ -262,6 +268,30 @@ func (route *Http) Register(r *chi.Mux) {
|
||||
r.Get("/update_cache", route.app.UpdateCache)
|
||||
})
|
||||
|
||||
r.Route("/environment", func(r chi.Router) {
|
||||
r.Get("/types", route.environment.Types)
|
||||
r.Get("/list", route.environment.List)
|
||||
r.Post("/install", route.environment.Install)
|
||||
r.Get("/uninstall", route.environment.Uninstall)
|
||||
r.Put("/update", route.environment.Update)
|
||||
r.Get("/is_installed", route.environment.IsInstalled)
|
||||
r.Route("/php", func(r chi.Router) {
|
||||
r.Post("/{version}/set_cli", route.environmentPHP.SetCli)
|
||||
r.Get("/{version}/config", route.environmentPHP.GetConfig)
|
||||
r.Post("/{version}/config", route.environmentPHP.UpdateConfig)
|
||||
r.Get("/{version}/fpm_config", route.environmentPHP.GetFPMConfig)
|
||||
r.Post("/{version}/fpm_config", route.environmentPHP.UpdateFPMConfig)
|
||||
r.Get("/{version}/load", route.environmentPHP.Load)
|
||||
r.Get("/{version}/log", route.environmentPHP.Log)
|
||||
r.Get("/{version}/slow_log", route.environmentPHP.SlowLog)
|
||||
r.Post("/{version}/clear_log", route.environmentPHP.ClearLog)
|
||||
r.Post("/{version}/clear_slow_log", route.environmentPHP.ClearSlowLog)
|
||||
r.Get("/{version}/modules", route.environmentPHP.ModuleList)
|
||||
r.Post("/{version}/modules", route.environmentPHP.InstallModule)
|
||||
r.Delete("/{version}/modules", route.environmentPHP.UninstallModule)
|
||||
})
|
||||
})
|
||||
|
||||
r.Route("/cron", func(r chi.Router) {
|
||||
r.Get("/", route.cron.List)
|
||||
r.Post("/", route.cron.Create)
|
||||
|
||||
@@ -49,7 +49,7 @@ func (s *AppService) List(w http.ResponseWriter, r *http.Request) {
|
||||
installedAppMap[p.Slug] = p
|
||||
}
|
||||
|
||||
var apps []types.AppCenter
|
||||
var apps []types.AppDetail
|
||||
for _, item := range all {
|
||||
installed, installedChannel, installedVersion, updateExist, show := false, "", "", false, false
|
||||
if _, ok := installedAppMap[item.Slug]; ok {
|
||||
@@ -63,8 +63,7 @@ func (s *AppService) List(w http.ResponseWriter, r *http.Request) {
|
||||
continue
|
||||
}
|
||||
|
||||
app := types.AppCenter{
|
||||
Icon: item.Icon,
|
||||
app := types.AppDetail{
|
||||
Name: item.Name,
|
||||
Description: item.Description,
|
||||
Categories: item.Categories,
|
||||
@@ -201,6 +200,10 @@ func (s *AppService) UpdateCache(w http.ResponseWriter, r *http.Request) {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if err := s.cacheRepo.UpdateEnvironments(); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
@@ -903,6 +903,8 @@ func (s *CliService) Init(ctx context.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
conf.App.Key = str.Random(32)
|
||||
conf.App.APIEndpoint = "api.acepanel.net"
|
||||
conf.App.DownloadEndpoint = "dl.acepanel.net"
|
||||
conf.HTTP.Entrance = "/" + str.Random(6)
|
||||
|
||||
// 随机默认端口
|
||||
|
||||
@@ -1,13 +1,107 @@
|
||||
package service
|
||||
|
||||
import "github.com/leonelquinteros/gotext"
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/acepanel/panel/internal/biz"
|
||||
"github.com/acepanel/panel/internal/http/request"
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
)
|
||||
|
||||
type EnvironmentService struct {
|
||||
t *gotext.Locale
|
||||
t *gotext.Locale
|
||||
environmentRepo biz.EnvironmentRepo
|
||||
taskRepo biz.TaskRepo
|
||||
}
|
||||
|
||||
func NewEnvironmentService(t *gotext.Locale) *EnvironmentService {
|
||||
func NewEnvironmentService(t *gotext.Locale, environmentRepo biz.EnvironmentRepo, taskRepo biz.TaskRepo) *EnvironmentService {
|
||||
return &EnvironmentService{
|
||||
t: t,
|
||||
t: t,
|
||||
environmentRepo: environmentRepo,
|
||||
taskRepo: taskRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EnvironmentService) Types(w http.ResponseWriter, r *http.Request) {
|
||||
Success(w, s.environmentRepo.Types())
|
||||
}
|
||||
|
||||
func (s *EnvironmentService) List(w http.ResponseWriter, r *http.Request) {
|
||||
typ := r.URL.Query().Get("type")
|
||||
all := s.environmentRepo.All()
|
||||
var environments []types.EnvironmentDetail
|
||||
for _, item := range all {
|
||||
if typ != "" && !strings.EqualFold(item.Type, typ) {
|
||||
continue
|
||||
}
|
||||
environments = append(environments, types.EnvironmentDetail{
|
||||
Type: item.Type,
|
||||
Name: item.Name,
|
||||
Description: item.Description,
|
||||
Slug: item.Slug,
|
||||
Installed: s.environmentRepo.IsInstalled(item.Type, item.Slug),
|
||||
HasUpdate: s.environmentRepo.HasUpdate(item.Type, item.Slug),
|
||||
})
|
||||
}
|
||||
|
||||
Success(w, environments)
|
||||
}
|
||||
|
||||
func (s *EnvironmentService) Install(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.EnvironmentAction](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.environmentRepo.Install(req.Type, req.Slug); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *EnvironmentService) Uninstall(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.EnvironmentAction](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.environmentRepo.Uninstall(req.Type, req.Slug); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *EnvironmentService) Update(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.EnvironmentAction](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.environmentRepo.Update(req.Type, req.Slug); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *EnvironmentService) IsInstalled(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.EnvironmentAction](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
installed := s.environmentRepo.IsInstalled(req.Type, req.Slug)
|
||||
Success(w, installed)
|
||||
}
|
||||
|
||||
613
internal/service/environment_php.go
Normal file
613
internal/service/environment_php.go
Normal file
@@ -0,0 +1,613 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/biz"
|
||||
"github.com/acepanel/panel/internal/http/request"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/shell"
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
)
|
||||
|
||||
type EnvironmentPHPService struct {
|
||||
t *gotext.Locale
|
||||
environmentRepo biz.EnvironmentRepo
|
||||
taskRepo biz.TaskRepo
|
||||
}
|
||||
|
||||
func NewEnvironmentPHPService(t *gotext.Locale, environmentRepo biz.EnvironmentRepo, taskRepo biz.TaskRepo) *EnvironmentPHPService {
|
||||
return &EnvironmentPHPService{
|
||||
t: t,
|
||||
environmentRepo: environmentRepo,
|
||||
taskRepo: taskRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EnvironmentPHPService) SetCli(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.EnvironmentPHPVersion](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
if !s.environmentRepo.IsInstalled("php", fmt.Sprintf("%d", req.Version)) {
|
||||
Error(w, http.StatusUnprocessableEntity, s.t.Get("PHP-%d is not installed", req.Version))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := shell.Execf("ln -sf %s/server/php/%d/bin/php /usr/local/bin/php", app.Root, req.Version); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *EnvironmentPHPService) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.EnvironmentPHPVersion](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
if !s.environmentRepo.IsInstalled("php", fmt.Sprintf("%d", req.Version)) {
|
||||
Error(w, http.StatusUnprocessableEntity, s.t.Get("PHP-%d is not installed", req.Version))
|
||||
return
|
||||
}
|
||||
|
||||
config, err := io.Read(fmt.Sprintf("%s/server/php/%d/etc/php.ini", app.Root, req.Version))
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, config)
|
||||
}
|
||||
|
||||
func (s *EnvironmentPHPService) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.EnvironmentPHPUpdateConfig](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
if !s.environmentRepo.IsInstalled("php", fmt.Sprintf("%d", req.Version)) {
|
||||
Error(w, http.StatusUnprocessableEntity, s.t.Get("PHP-%d is not installed", req.Version))
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write(fmt.Sprintf("%s/server/php/%d/etc/php.ini", app.Root, req.Version), req.Config, 0644); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *EnvironmentPHPService) GetFPMConfig(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.EnvironmentPHPVersion](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
if !s.environmentRepo.IsInstalled("php", fmt.Sprintf("%d", req.Version)) {
|
||||
Error(w, http.StatusUnprocessableEntity, s.t.Get("PHP-%d is not installed", req.Version))
|
||||
return
|
||||
}
|
||||
|
||||
config, err := io.Read(fmt.Sprintf("%s/server/php/%d/etc/php-fpm.conf", app.Root, req.Version))
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, config)
|
||||
}
|
||||
|
||||
func (s *EnvironmentPHPService) UpdateFPMConfig(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.EnvironmentPHPUpdateConfig](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
if !s.environmentRepo.IsInstalled("php", fmt.Sprintf("%d", req.Version)) {
|
||||
Error(w, http.StatusUnprocessableEntity, s.t.Get("PHP-%d is not installed", req.Version))
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write(fmt.Sprintf("%s/server/php/%d/etc/php-fpm.conf", app.Root, req.Version), req.Config, 0644); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *EnvironmentPHPService) Load(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.EnvironmentPHPVersion](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
if !s.environmentRepo.IsInstalled("php", fmt.Sprintf("%d", req.Version)) {
|
||||
Error(w, http.StatusUnprocessableEntity, s.t.Get("PHP-%d is not installed", req.Version))
|
||||
return
|
||||
}
|
||||
|
||||
var raw map[string]any
|
||||
client := resty.New().SetTimeout(10 * time.Second)
|
||||
_, err = client.R().SetResult(&raw).Get(fmt.Sprintf("http://127.0.0.1/phpfpm_status/%d?json", req.Version))
|
||||
if err != nil {
|
||||
Success(w, []types.NV{})
|
||||
return
|
||||
}
|
||||
|
||||
dataKeys := []string{
|
||||
s.t.Get("Application Pool"),
|
||||
s.t.Get("Process Manager"),
|
||||
s.t.Get("Start Time"),
|
||||
s.t.Get("Accepted Connections"),
|
||||
s.t.Get("Listen Queue"),
|
||||
s.t.Get("Max Listen Queue"),
|
||||
s.t.Get("Listen Queue Length"),
|
||||
s.t.Get("Idle Processes"),
|
||||
s.t.Get("Active Processes"),
|
||||
s.t.Get("Total Processes"),
|
||||
s.t.Get("Max Active Processes"),
|
||||
s.t.Get("Max Children Reached"),
|
||||
s.t.Get("Slow Requests"),
|
||||
}
|
||||
rawKeys := []string{
|
||||
"pool",
|
||||
"process manager",
|
||||
"start time",
|
||||
"accepted conn",
|
||||
"listen queue",
|
||||
"max listen queue",
|
||||
"listen queue len",
|
||||
"idle processes",
|
||||
"active processes",
|
||||
"total processes",
|
||||
"max active processes",
|
||||
"max children reached",
|
||||
"slow requests",
|
||||
}
|
||||
|
||||
loads := make([]types.NV, 0)
|
||||
for i := range dataKeys {
|
||||
v, ok := raw[rawKeys[i]]
|
||||
if ok {
|
||||
loads = append(loads, types.NV{
|
||||
Name: dataKeys[i],
|
||||
Value: cast.ToString(v),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Success(w, loads)
|
||||
}
|
||||
|
||||
func (s *EnvironmentPHPService) Log(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.EnvironmentPHPVersion](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
if !s.environmentRepo.IsInstalled("php", fmt.Sprintf("%d", req.Version)) {
|
||||
Error(w, http.StatusUnprocessableEntity, s.t.Get("PHP-%d is not installed", req.Version))
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, fmt.Sprintf("%s/server/php/%d/var/log/php-fpm.log", app.Root, req.Version))
|
||||
}
|
||||
|
||||
func (s *EnvironmentPHPService) SlowLog(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.EnvironmentPHPVersion](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
if !s.environmentRepo.IsInstalled("php", fmt.Sprintf("%d", req.Version)) {
|
||||
Error(w, http.StatusUnprocessableEntity, s.t.Get("PHP-%d is not installed", req.Version))
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, fmt.Sprintf("%s/server/php/%d/var/log/slow.log", app.Root, req.Version))
|
||||
}
|
||||
|
||||
func (s *EnvironmentPHPService) ClearLog(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.EnvironmentPHPVersion](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
if !s.environmentRepo.IsInstalled("php", fmt.Sprintf("%d", req.Version)) {
|
||||
Error(w, http.StatusUnprocessableEntity, s.t.Get("PHP-%d is not installed", req.Version))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf("cat /dev/null > %s/server/php/%d/var/log/php-fpm.log", app.Root, req.Version); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *EnvironmentPHPService) ClearSlowLog(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.EnvironmentPHPVersion](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
if !s.environmentRepo.IsInstalled("php", fmt.Sprintf("%d", req.Version)) {
|
||||
Error(w, http.StatusUnprocessableEntity, s.t.Get("PHP-%d is not installed", req.Version))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf("cat /dev/null > %s/server/php/%d/var/log/slow.log", app.Root, req.Version); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *EnvironmentPHPService) ModuleList(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.EnvironmentPHPVersion](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
if !s.environmentRepo.IsInstalled("php", fmt.Sprintf("%d", req.Version)) {
|
||||
Error(w, http.StatusUnprocessableEntity, s.t.Get("PHP-%d is not installed", req.Version))
|
||||
return
|
||||
}
|
||||
|
||||
modules := s.getModules(req.Version)
|
||||
raw, err := shell.Execf("%s/server/php/%d/bin/php -m", app.Root, req.Version)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
moduleMap := make(map[string]*types.EnvironmentPHPModule)
|
||||
for i := range modules {
|
||||
moduleMap[modules[i].Slug] = &modules[i]
|
||||
}
|
||||
|
||||
rawModuleList := strings.Split(raw, "\n")
|
||||
for _, item := range rawModuleList {
|
||||
if ext, exists := moduleMap[item]; exists && !strings.Contains(item, "[") && item != "" {
|
||||
ext.Installed = true
|
||||
}
|
||||
}
|
||||
|
||||
Success(w, modules)
|
||||
}
|
||||
|
||||
func (s *EnvironmentPHPService) InstallModule(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.EnvironmentPHPModule](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
if !s.environmentRepo.IsInstalled("php", fmt.Sprintf("%d", req.Version)) {
|
||||
Error(w, http.StatusUnprocessableEntity, s.t.Get("PHP-%d is not installed", req.Version))
|
||||
return
|
||||
}
|
||||
|
||||
if !s.checkModule(req.Version, req.Slug) {
|
||||
Error(w, http.StatusUnprocessableEntity, s.t.Get("module %s does not exist", req.Slug))
|
||||
return
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf(`curl -sSLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/%s.sh' | bash -s -- 'install' '%d' >> '/tmp/%s.log' 2>&1`, url.PathEscape(req.Slug), req.Version, req.Slug)
|
||||
officials := []string{"fileinfo", "exif", "imap", "pgsql", "pdo_pgsql", "zip", "bz2", "readline", "snmp", "ldap", "enchant", "pspell", "calendar", "gmp", "sysvmsg", "sysvsem", "sysvshm", "xsl", "intl", "gettext"}
|
||||
if slices.Contains(officials, req.Slug) {
|
||||
cmd = fmt.Sprintf(`curl -sSLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/official.sh' | bash -s -- 'install' '%d' '%s' >> '/tmp/%s.log' 2>&1`, req.Version, req.Slug, req.Slug)
|
||||
}
|
||||
|
||||
task := new(biz.Task)
|
||||
task.Name = s.t.Get("Install PHP-%d %s module", req.Version, req.Slug)
|
||||
task.Status = biz.TaskStatusWaiting
|
||||
task.Shell = cmd
|
||||
task.Log = "/tmp/" + req.Slug + ".log"
|
||||
if err = s.taskRepo.Push(task); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *EnvironmentPHPService) UninstallModule(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.EnvironmentPHPModule](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
if !s.environmentRepo.IsInstalled("php", fmt.Sprintf("%d", req.Version)) {
|
||||
Error(w, http.StatusUnprocessableEntity, s.t.Get("PHP-%d is not installed", req.Version))
|
||||
return
|
||||
}
|
||||
|
||||
if !s.checkModule(req.Version, req.Slug) {
|
||||
Error(w, http.StatusUnprocessableEntity, s.t.Get("module %s does not exist", req.Slug))
|
||||
return
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf(`curl -sSLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/%s.sh' | bash -s -- 'uninstall' '%d' >> '/tmp/%s.log' 2>&1`, url.PathEscape(req.Slug), req.Version, req.Slug)
|
||||
officials := []string{"fileinfo", "exif", "imap", "pgsql", "pdo_pgsql", "zip", "bz2", "readline", "snmp", "ldap", "enchant", "pspell", "calendar", "gmp", "sysvmsg", "sysvsem", "sysvshm", "xsl", "intl", "gettext"}
|
||||
if slices.Contains(officials, req.Slug) {
|
||||
cmd = fmt.Sprintf(`curl -sSLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/official.sh' | bash -s -- 'uninstall' '%d' '%s' >> '/tmp/%s.log' 2>&1`, req.Version, req.Slug, req.Slug)
|
||||
}
|
||||
|
||||
task := new(biz.Task)
|
||||
task.Name = s.t.Get("Uninstall PHP-%d %s module", req.Version, req.Slug)
|
||||
task.Status = biz.TaskStatusWaiting
|
||||
task.Shell = cmd
|
||||
task.Log = "/tmp/" + req.Slug + ".log"
|
||||
if err = s.taskRepo.Push(task); err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *EnvironmentPHPService) getModules(version uint) []types.EnvironmentPHPModule {
|
||||
modules := []types.EnvironmentPHPModule{
|
||||
{
|
||||
Name: "fileinfo",
|
||||
Slug: "fileinfo",
|
||||
Description: s.t.Get("Fileinfo is a library used to identify file types"),
|
||||
},
|
||||
{
|
||||
Name: "OPcache",
|
||||
Slug: "Zend OPcache",
|
||||
Description: s.t.Get("OPcache stores precompiled PHP script bytecode in shared memory to improve PHP performance"),
|
||||
},
|
||||
{
|
||||
Name: "igbinary",
|
||||
Slug: "igbinary",
|
||||
Description: s.t.Get("Igbinary is a library for serializing and deserializing data"),
|
||||
},
|
||||
{
|
||||
Name: "Redis",
|
||||
Slug: "redis",
|
||||
Description: s.t.Get("PhpRedis connects to and operates on data in Redis databases (requires the igbinary module installed above)"),
|
||||
},
|
||||
{
|
||||
Name: "Memcached",
|
||||
Slug: "memcached",
|
||||
Description: s.t.Get("Memcached is a driver for connecting to Memcached servers"),
|
||||
},
|
||||
{
|
||||
Name: "ImageMagick",
|
||||
Slug: "imagick",
|
||||
Description: s.t.Get("ImageMagick is free software for creating, editing, and composing images"),
|
||||
},
|
||||
{
|
||||
Name: "exif",
|
||||
Slug: "exif",
|
||||
Description: s.t.Get("Exif is a library for reading and writing image metadata"),
|
||||
},
|
||||
{
|
||||
Name: "pgsql",
|
||||
Slug: "pgsql",
|
||||
Description: s.t.Get("pgsql is a driver for connecting to PostgreSQL (requires PostgreSQL installed)"),
|
||||
},
|
||||
{
|
||||
Name: "pdo_pgsql",
|
||||
Slug: "pdo_pgsql",
|
||||
Description: s.t.Get("pdo_pgsql is a PDO driver for connecting to PostgreSQL (requires PostgreSQL installed)"),
|
||||
},
|
||||
{
|
||||
Name: "sqlsrv",
|
||||
Slug: "sqlsrv",
|
||||
Description: s.t.Get("sqlsrv is a driver for connecting to SQL Server"),
|
||||
},
|
||||
{
|
||||
Name: "pdo_sqlsrv",
|
||||
Slug: "pdo_sqlsrv",
|
||||
Description: s.t.Get("pdo_sqlsrv is a PDO driver for connecting to SQL Server"),
|
||||
},
|
||||
{
|
||||
Name: "imap",
|
||||
Slug: "imap",
|
||||
Description: s.t.Get("IMAP module allows PHP to read, search, delete, download, and manage emails"),
|
||||
},
|
||||
{
|
||||
Name: "zip",
|
||||
Slug: "zip",
|
||||
Description: s.t.Get("Zip is a library for handling ZIP files"),
|
||||
},
|
||||
{
|
||||
Name: "bz2",
|
||||
Slug: "bz2",
|
||||
Description: s.t.Get("Bzip2 is a library for compressing and decompressing files"),
|
||||
},
|
||||
{
|
||||
Name: "ssh2",
|
||||
Slug: "ssh2",
|
||||
Description: s.t.Get("SSH2 is a library for connecting to SSH servers"),
|
||||
},
|
||||
{
|
||||
Name: "event",
|
||||
Slug: "event",
|
||||
Description: s.t.Get("Event is a library for handling events"),
|
||||
},
|
||||
{
|
||||
Name: "readline",
|
||||
Slug: "readline",
|
||||
Description: s.t.Get("Readline is a library for processing text"),
|
||||
},
|
||||
{
|
||||
Name: "snmp",
|
||||
Slug: "snmp",
|
||||
Description: s.t.Get("SNMP is a protocol for network management"),
|
||||
},
|
||||
{
|
||||
Name: "ldap",
|
||||
Slug: "ldap",
|
||||
Description: s.t.Get("LDAP is a protocol for accessing directory services"),
|
||||
},
|
||||
{
|
||||
Name: "enchant",
|
||||
Slug: "enchant",
|
||||
Description: s.t.Get("Enchant is a spell-checking library"),
|
||||
},
|
||||
{
|
||||
Name: "pspell",
|
||||
Slug: "pspell",
|
||||
Description: s.t.Get("Pspell is a spell-checking library"),
|
||||
},
|
||||
{
|
||||
Name: "calendar",
|
||||
Slug: "calendar",
|
||||
Description: s.t.Get("Calendar is a library for handling dates"),
|
||||
},
|
||||
{
|
||||
Name: "gmp",
|
||||
Slug: "gmp",
|
||||
Description: s.t.Get("GMP is a library for handling large integers"),
|
||||
},
|
||||
{
|
||||
Name: "xlswriter",
|
||||
Slug: "xlswriter",
|
||||
Description: s.t.Get("XLSWriter is a high-performance library for reading and writing Excel files"),
|
||||
},
|
||||
{
|
||||
Name: "xsl",
|
||||
Slug: "xsl",
|
||||
Description: s.t.Get("XSL is a library for processing XML documents"),
|
||||
},
|
||||
{
|
||||
Name: "intl",
|
||||
Slug: "intl",
|
||||
Description: s.t.Get("Intl is a library for handling internationalization and localization"),
|
||||
},
|
||||
{
|
||||
Name: "gettext",
|
||||
Slug: "gettext",
|
||||
Description: s.t.Get("Gettext is a library for handling multilingual support"),
|
||||
},
|
||||
{
|
||||
Name: "grpc",
|
||||
Slug: "grpc",
|
||||
Description: s.t.Get("gRPC is a high-performance, open-source, and general-purpose RPC framework"),
|
||||
},
|
||||
{
|
||||
Name: "protobuf",
|
||||
Slug: "protobuf",
|
||||
Description: s.t.Get("protobuf is a library for serializing and deserializing data"),
|
||||
},
|
||||
{
|
||||
Name: "rdkafka",
|
||||
Slug: "rdkafka",
|
||||
Description: s.t.Get("rdkafka is a library for connecting to Apache Kafka"),
|
||||
},
|
||||
{
|
||||
Name: "xhprof",
|
||||
Slug: "xhprof",
|
||||
Description: s.t.Get("xhprof is a library for performance profiling"),
|
||||
},
|
||||
{
|
||||
Name: "xdebug",
|
||||
Slug: "xdebug",
|
||||
Description: s.t.Get("xdebug is a library for debugging and profiling PHP code"),
|
||||
},
|
||||
{
|
||||
Name: "yaml",
|
||||
Slug: "yaml",
|
||||
Description: s.t.Get("yaml is a library for handling YAML"),
|
||||
},
|
||||
{
|
||||
Name: "zstd",
|
||||
Slug: "zstd",
|
||||
Description: s.t.Get("zstd is a library for compressing and decompressing files"),
|
||||
},
|
||||
{
|
||||
Name: "sysvmsg",
|
||||
Slug: "sysvmsg",
|
||||
Description: s.t.Get("Sysvmsg is a library for handling System V message queues"),
|
||||
},
|
||||
{
|
||||
Name: "sysvsem",
|
||||
Slug: "sysvsem",
|
||||
Description: s.t.Get("Sysvsem is a library for handling System V semaphores"),
|
||||
},
|
||||
{
|
||||
Name: "sysvshm",
|
||||
Slug: "sysvshm",
|
||||
Description: s.t.Get("Sysvshm is a library for handling System V shared memory"),
|
||||
},
|
||||
{
|
||||
Name: "ionCube",
|
||||
Slug: "ionCube Loader",
|
||||
Description: s.t.Get("ionCube is a professional-grade PHP encryption and decryption tool (must be installed after OPcache)"),
|
||||
},
|
||||
{
|
||||
Name: "Swoole",
|
||||
Slug: "swoole",
|
||||
Description: s.t.Get("Swoole is a PHP module for building high-performance asynchronous concurrent servers"),
|
||||
},
|
||||
}
|
||||
|
||||
// Swow 不支持 PHP 8.0 以下版本且目前不支持 PHP 8.4
|
||||
if version >= 80 && version < 84 {
|
||||
modules = append(modules, types.EnvironmentPHPModule{
|
||||
Name: "Swow",
|
||||
Slug: "Swow",
|
||||
Description: s.t.Get("Swow is a PHP module for building high-performance asynchronous concurrent servers"),
|
||||
})
|
||||
}
|
||||
// PHP 8.4 移除了 pspell 和 imap 并且不再建议使用
|
||||
if version >= 84 {
|
||||
modules = slices.DeleteFunc(modules, func(module types.EnvironmentPHPModule) bool {
|
||||
return module.Slug == "pspell" || module.Slug == "imap"
|
||||
})
|
||||
}
|
||||
// PHP 8.5 原生支持 OPcache,不再作为扩展提供安装
|
||||
if version >= 85 {
|
||||
modules = slices.DeleteFunc(modules, func(module types.EnvironmentPHPModule) bool {
|
||||
return module.Slug == "Zend OPcache"
|
||||
})
|
||||
}
|
||||
|
||||
raw, _ := shell.Execf("%s/server/php/%d/bin/php -m", app.Root, version)
|
||||
moduleMap := make(map[string]*types.EnvironmentPHPModule)
|
||||
for i := range modules {
|
||||
moduleMap[modules[i].Slug] = &modules[i]
|
||||
}
|
||||
|
||||
rawModuleList := strings.Split(raw, "\n")
|
||||
for _, item := range rawModuleList {
|
||||
if ext, exists := moduleMap[item]; exists && !strings.Contains(item, "[") && item != "" {
|
||||
ext.Installed = true
|
||||
}
|
||||
}
|
||||
|
||||
return modules
|
||||
}
|
||||
|
||||
func (s *EnvironmentPHPService) checkModule(version uint, slug string) bool {
|
||||
modules := s.getModules(version)
|
||||
|
||||
for _, item := range modules {
|
||||
if item.Slug == slug {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -19,6 +19,8 @@ var ProviderSet = wire.NewSet(
|
||||
NewDatabaseService,
|
||||
NewDatabaseServerService,
|
||||
NewDatabaseUserService,
|
||||
NewEnvironmentService,
|
||||
NewEnvironmentPHPService,
|
||||
NewFileService,
|
||||
NewFirewallService,
|
||||
NewHomeService,
|
||||
|
||||
@@ -39,9 +39,7 @@ func (s httpSolver) Present(_ context.Context, challenge acme.Challenge) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open nginx config %q: %w", s.conf, err)
|
||||
}
|
||||
defer func(file *os.File) {
|
||||
_ = file.Close()
|
||||
}(file)
|
||||
defer func(file *os.File) { _ = file.Close() }(file)
|
||||
|
||||
if _, err = file.Write([]byte(conf)); err != nil {
|
||||
return fmt.Errorf("failed to write to nginx config %q: %w", s.conf, err)
|
||||
@@ -254,9 +252,7 @@ func (s *manualDNSSolver) Present(ctx context.Context, challenge acme.Challenge)
|
||||
}
|
||||
|
||||
func (s *manualDNSSolver) CleanUp(_ context.Context, _ acme.Challenge) error {
|
||||
defer func() {
|
||||
_ = recover()
|
||||
}()
|
||||
defer func() { _ = recover() }()
|
||||
close(s.controlChan)
|
||||
close(s.dnsChan)
|
||||
close(s.certChan)
|
||||
|
||||
@@ -9,7 +9,6 @@ type App struct {
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Slug string `json:"slug"`
|
||||
Icon string `json:"icon"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Categories []string `json:"categories"`
|
||||
|
||||
47
pkg/api/environment.go
Normal file
47
pkg/api/environment.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package api
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Environment struct {
|
||||
Type string `json:"type"`
|
||||
Slug string `json:"slug"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Description string `json:"description"`
|
||||
Order int `json:"order"`
|
||||
}
|
||||
|
||||
type Environments []*Environment
|
||||
|
||||
// Environments 返回所有环境
|
||||
func (r *API) Environments() (*Environments, error) {
|
||||
resp, err := r.client.R().SetResult(&Response{}).Get("/environments")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !resp.IsSuccess() {
|
||||
return nil, fmt.Errorf("failed to get environments: %s", resp.String())
|
||||
}
|
||||
|
||||
environments, err := getResponseData[Environments](resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return environments, nil
|
||||
}
|
||||
|
||||
// EnvironmentCallback 环境下载回调
|
||||
func (r *API) EnvironmentCallback(typ, slug string) error {
|
||||
resp, err := r.client.R().
|
||||
SetResult(&Response{}).
|
||||
Post(fmt.Sprintf("/environments/%s/%s/callback", typ, slug))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !resp.IsSuccess() {
|
||||
return fmt.Errorf("failed to callback environment: %s", resp.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -19,11 +19,13 @@ type Config struct {
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
Debug bool `yaml:"debug"`
|
||||
Key string `yaml:"key"`
|
||||
Locale string `yaml:"locale"`
|
||||
Timezone string `yaml:"timezone"`
|
||||
Root string `yaml:"root"`
|
||||
Debug bool `yaml:"debug"`
|
||||
Key string `yaml:"key"`
|
||||
Locale string `yaml:"locale"`
|
||||
Timezone string `yaml:"timezone"`
|
||||
Root string `yaml:"root"`
|
||||
APIEndpoint string `yaml:"api_endpoint"`
|
||||
DownloadEndpoint string `yaml:"download_endpoint"`
|
||||
}
|
||||
|
||||
type HTTPConfig struct {
|
||||
|
||||
@@ -77,9 +77,7 @@ func (r *MySQL) DatabaseExists(name string) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func(rows *sql.Rows) {
|
||||
_ = rows.Close()
|
||||
}(rows)
|
||||
defer func(rows *sql.Rows) { _ = rows.Close() }(rows)
|
||||
|
||||
for rows.Next() {
|
||||
var database string
|
||||
@@ -223,9 +221,7 @@ func (r *MySQL) Databases() ([]Database, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(rows *sql.Rows) {
|
||||
_ = rows.Close()
|
||||
}(rows)
|
||||
defer func(rows *sql.Rows) { _ = rows.Close() }(rows)
|
||||
|
||||
var databases []Database
|
||||
for rows.Next() {
|
||||
@@ -248,9 +244,7 @@ func (r *MySQL) userGrants(user, host string) ([]string, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(rows *sql.Rows) {
|
||||
_ = rows.Close()
|
||||
}(rows)
|
||||
defer func(rows *sql.Rows) { _ = rows.Close() }(rows)
|
||||
|
||||
var grants []string
|
||||
for rows.Next() {
|
||||
|
||||
@@ -145,9 +145,7 @@ func (r *Postgres) UserPrivileges(user string, host ...string) ([]string, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(rows *sql.Rows) {
|
||||
_ = rows.Close()
|
||||
}(rows)
|
||||
defer func(rows *sql.Rows) { _ = rows.Close() }(rows)
|
||||
|
||||
var databases []string
|
||||
|
||||
@@ -197,9 +195,7 @@ func (r *Postgres) Users() ([]User, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(rows *sql.Rows) {
|
||||
_ = rows.Close()
|
||||
}(rows)
|
||||
defer func(rows *sql.Rows) { _ = rows.Close() }(rows)
|
||||
|
||||
var users []User
|
||||
for rows.Next() {
|
||||
@@ -250,9 +246,7 @@ func (r *Postgres) Databases() ([]Database, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(rows *sql.Rows) {
|
||||
_ = rows.Close()
|
||||
}(rows)
|
||||
defer func(rows *sql.Rows) { _ = rows.Close() }(rows)
|
||||
|
||||
var databases []Database
|
||||
for rows.Next() {
|
||||
|
||||
@@ -35,9 +35,7 @@ func Write(path string, data string, permission os.FileMode) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(file *os.File) {
|
||||
_ = file.Close()
|
||||
}(file)
|
||||
defer func(file *os.File) { _ = file.Close() }(file)
|
||||
|
||||
_, err = file.WriteString(data)
|
||||
if err != nil {
|
||||
@@ -73,9 +71,7 @@ func WriteAppend(path string, data string, permission os.FileMode) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(file *os.File) {
|
||||
_ = file.Close()
|
||||
}(file)
|
||||
defer func(file *os.File) { _ = file.Close() }(file)
|
||||
|
||||
_, err = file.WriteString(data)
|
||||
if err != nil {
|
||||
|
||||
12
pkg/os/os.go
12
pkg/os/os.go
@@ -13,9 +13,7 @@ func readOSRelease() map[string]string {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer func(file *os.File) {
|
||||
_ = file.Close()
|
||||
}(file)
|
||||
defer func(file *os.File) { _ = file.Close() }(file)
|
||||
|
||||
osRelease := make(map[string]string)
|
||||
scanner := bufio.NewScanner(file)
|
||||
@@ -70,9 +68,7 @@ func TCPPortInUse(port uint) bool {
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
defer func(conn net.Listener) {
|
||||
_ = conn.Close()
|
||||
}(conn)
|
||||
defer func(conn net.Listener) { _ = conn.Close() }(conn)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -82,8 +78,6 @@ func UDPPortInUse(port uint) bool {
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
defer func(conn net.PacketConn) {
|
||||
_ = conn.Close()
|
||||
}(conn)
|
||||
defer func(conn net.PacketConn) { _ = conn.Close() }(conn)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -203,9 +203,7 @@ func ExecfWithTTY(shell string, args ...any) (string, error) {
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("run %s failed", shell)
|
||||
}
|
||||
defer func(f *os.File) {
|
||||
_ = f.Close()
|
||||
}(f)
|
||||
defer func(f *os.File) { _ = f.Close() }(f)
|
||||
|
||||
if _, err = io.Copy(&out, f); ptyError(err) != nil {
|
||||
return "", fmt.Errorf("run %s failed, out: %s, err: %w", shell, strings.TrimSpace(out.String()), err)
|
||||
|
||||
@@ -159,9 +159,7 @@ func GetLocalIPv4() (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func(conn stdnet.Conn) {
|
||||
_ = conn.Close()
|
||||
}(conn)
|
||||
defer func(conn stdnet.Conn) { _ = conn.Close() }(conn)
|
||||
|
||||
local := conn.LocalAddr().(*stdnet.UDPAddr)
|
||||
return local.IP.String(), nil
|
||||
@@ -173,9 +171,7 @@ func GetLocalIPv6() (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func(conn stdnet.Conn) {
|
||||
_ = conn.Close()
|
||||
}(conn)
|
||||
defer func(conn stdnet.Conn) { _ = conn.Close() }(conn)
|
||||
|
||||
local := conn.LocalAddr().(*stdnet.UDPAddr)
|
||||
return local.IP.String(), nil
|
||||
|
||||
@@ -7,9 +7,8 @@ type App interface {
|
||||
Route(r chi.Router)
|
||||
}
|
||||
|
||||
// AppCenter 应用中心结构
|
||||
type AppCenter struct {
|
||||
Icon string `json:"icon"`
|
||||
// AppDetail 应用详情
|
||||
type AppDetail struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Categories []string `json:"categories"`
|
||||
|
||||
11
pkg/types/environment.go
Normal file
11
pkg/types/environment.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package types
|
||||
|
||||
// EnvironmentDetail 环境详情
|
||||
type EnvironmentDetail struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Slug string `json:"slug"`
|
||||
Installed bool `json:"installed"`
|
||||
HasUpdate bool `json:"has_update"`
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package php
|
||||
package types
|
||||
|
||||
type Extension struct {
|
||||
type EnvironmentPHPModule struct {
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
Description string `json:"description"`
|
||||
16
web/src/api/panel/environment/index.ts
Normal file
16
web/src/api/panel/environment/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { http } from '@/utils'
|
||||
|
||||
export default {
|
||||
// 获取环境类型列表
|
||||
types: (): any => http.Get('/environment/types'),
|
||||
// 获取环境列表
|
||||
list: (page: number, limit: number, type?: string): any =>
|
||||
http.Get('/environment/list', { params: { page, limit, type } }),
|
||||
// 安装环境
|
||||
install: (type: string, slug: string): any => http.Post('/environment/install', { type, slug }),
|
||||
// 卸载环境
|
||||
uninstall: (type: string, slug: string): any =>
|
||||
http.Post('/environment/uninstall', { type, slug }),
|
||||
// 更新环境
|
||||
update: (type: string, slug: string): any => http.Post('/environment/update', { type, slug })
|
||||
}
|
||||
@@ -29,7 +29,6 @@ export function renderIcon(icon: string, props: Props = { size: 12 }) {
|
||||
}
|
||||
|
||||
export function renderLocalIcon(type: string, icon: string, props: Props = { size: 12 }) {
|
||||
console.log('type, icon', type, icon)
|
||||
const svgContent = getLocalIconSvg(type, icon)
|
||||
return () => h(NIcon, { ...props, innerHTML: svgContent })
|
||||
}
|
||||
|
||||
@@ -1,7 +1,244 @@
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import environment from '@/api/panel/environment'
|
||||
import { renderLocalIcon } from '@/utils'
|
||||
import { NButton, NDataTable, NFlex, NPopconfirm, NTag } from 'naive-ui'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
|
||||
// 当前选中的类型(默认为全部)
|
||||
const selectedType = ref<string>('')
|
||||
|
||||
// 环境类型列表
|
||||
const { data: types } = useRequest(environment.types, {
|
||||
initialData: []
|
||||
})
|
||||
|
||||
const columns: any = [
|
||||
{
|
||||
key: 'icon',
|
||||
fixed: 'left',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
render(row: any) {
|
||||
return renderLocalIcon('environment', row.type, { size: 26 })()
|
||||
}
|
||||
},
|
||||
{
|
||||
title: $gettext('Name'),
|
||||
key: 'name',
|
||||
width: 200,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: $gettext('Description'),
|
||||
key: 'description',
|
||||
minWidth: 300,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: $gettext('Installed Version'),
|
||||
key: 'installed_version',
|
||||
width: 160,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: $gettext('Actions'),
|
||||
key: 'actions',
|
||||
width: 350,
|
||||
hideInExcel: true,
|
||||
render(row: any) {
|
||||
return h(NFlex, null, {
|
||||
default: () => [
|
||||
row.installed && row.has_update
|
||||
? h(
|
||||
NPopconfirm,
|
||||
{
|
||||
onPositiveClick: () => handleUpdate(row.type, row.slug)
|
||||
},
|
||||
{
|
||||
default: () => {
|
||||
return $gettext('Are you sure to update environment %{ environment }?', {
|
||||
environment: row.name
|
||||
})
|
||||
},
|
||||
trigger: () => {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'warning'
|
||||
},
|
||||
{
|
||||
default: () => $gettext('Update')
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
: null,
|
||||
row.installed
|
||||
? h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'success'
|
||||
//onClick: () => handleManage(row.slug)
|
||||
},
|
||||
{
|
||||
default: () => $gettext('Manage')
|
||||
}
|
||||
)
|
||||
: null,
|
||||
row.installed
|
||||
? h(
|
||||
NPopconfirm,
|
||||
{
|
||||
onPositiveClick: () => handleUninstall(row.type, row.slug)
|
||||
},
|
||||
{
|
||||
default: () => {
|
||||
return $gettext('Are you sure to uninstall environment %{ environment }?', {
|
||||
environment: row.name
|
||||
})
|
||||
},
|
||||
trigger: () => {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'error'
|
||||
},
|
||||
{
|
||||
default: () => $gettext('Uninstall')
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
: null,
|
||||
!row.installed
|
||||
? h(
|
||||
NPopconfirm,
|
||||
{
|
||||
onPositiveClick: () => handleInstall(row.type, row.slug)
|
||||
},
|
||||
{
|
||||
default: () => {
|
||||
return $gettext('Are you sure to install environment %{ environment }?', {
|
||||
environment: row.name
|
||||
})
|
||||
},
|
||||
trigger: () => {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'primary'
|
||||
},
|
||||
{
|
||||
default: () => $gettext('Install')
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
: null
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const { loading, data, page, total, pageSize, pageCount, refresh } = usePagination(
|
||||
(page, pageSize) => environment.list(page, pageSize, selectedType.value || undefined),
|
||||
{
|
||||
initialData: { total: 0, list: [] },
|
||||
initialPageSize: 20,
|
||||
total: (res: any) => res.total,
|
||||
data: (res: any) => res.items,
|
||||
watchingStates: [selectedType]
|
||||
}
|
||||
)
|
||||
|
||||
// 处理类型切换
|
||||
const handleTypeChange = (type: string) => {
|
||||
selectedType.value = type
|
||||
page.value = 1
|
||||
}
|
||||
|
||||
const handleInstall = (type: string, slug: string) => {
|
||||
useRequest(environment.install(type, slug)).onSuccess(() => {
|
||||
window.$message.success(
|
||||
$gettext('Task submitted, please check the progress in background tasks')
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const handleUpdate = (type: string, slug: string) => {
|
||||
useRequest(environment.update(type, slug)).onSuccess(() => {
|
||||
window.$message.success(
|
||||
$gettext('Task submitted, please check the progress in background tasks')
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const handleUninstall = (type: string, slug: string) => {
|
||||
useRequest(environment.uninstall(type, slug)).onSuccess(() => {
|
||||
window.$message.success(
|
||||
$gettext('Task submitted, please check the progress in background tasks')
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
refresh()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-empty></n-empty>
|
||||
<n-flex vertical>
|
||||
<n-flex>
|
||||
<n-tag
|
||||
:type="selectedType === '' ? 'primary' : 'default'"
|
||||
:bordered="selectedType !== ''"
|
||||
style="cursor: pointer"
|
||||
@click="handleTypeChange('')"
|
||||
>
|
||||
{{ $gettext('All') }}
|
||||
</n-tag>
|
||||
<n-tag
|
||||
v-for="type in types"
|
||||
:key="type.value"
|
||||
:type="selectedType === type.value ? 'primary' : 'default'"
|
||||
:bordered="selectedType !== type.value"
|
||||
style="cursor: pointer"
|
||||
@click="handleTypeChange(type.value)"
|
||||
>
|
||||
{{ type.label }}
|
||||
</n-tag>
|
||||
</n-flex>
|
||||
<n-data-table
|
||||
striped
|
||||
remote
|
||||
:scroll-x="1200"
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
:row-key="(row: any) => row.slug"
|
||||
v-model:page="page"
|
||||
v-model:pageSize="pageSize"
|
||||
:pagination="{
|
||||
page: page,
|
||||
pageCount: pageCount,
|
||||
pageSize: pageSize,
|
||||
itemCount: total,
|
||||
showQuickJumper: true,
|
||||
showSizePicker: true,
|
||||
pageSizes: [20, 50, 100, 200]
|
||||
}"
|
||||
/>
|
||||
</n-flex>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
||||
@@ -59,7 +59,6 @@ const columns: any = [
|
||||
rubberBand: false,
|
||||
value: row.two_fa !== '',
|
||||
onUpdateValue: (v) => {
|
||||
console.log(v)
|
||||
if (v) {
|
||||
twoFaModal.value = true
|
||||
currentID.value = row.id
|
||||
|
||||
Reference in New Issue
Block a user