2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 01:57:19 +08:00

feat: 支持更多运行环境

This commit is contained in:
2026-01-16 00:59:10 +08:00
parent 3412709f9b
commit c7ed032500
22 changed files with 913 additions and 100 deletions

View File

@@ -96,7 +96,11 @@ func initWeb() (*app.Web, error) {
certAccountService := service.NewCertAccountService(certAccountRepo)
appService := service.NewAppService(locale, appRepo, cacheRepo, settingRepo)
environmentService := service.NewEnvironmentService(locale, environmentRepo, taskRepo)
environmentGoService := service.NewEnvironmentGoService(locale, environmentRepo)
environmentJavaService := service.NewEnvironmentJavaService(locale, environmentRepo)
environmentNodejsService := service.NewEnvironmentNodejsService(locale, environmentRepo)
environmentPHPService := service.NewEnvironmentPHPService(locale, config, environmentRepo, taskRepo)
environmentPythonService := service.NewEnvironmentPythonService(locale, environmentRepo)
cronService := service.NewCronService(cronRepo)
processService := service.NewProcessService()
safeRepo := data.NewSafeRepo(logger)
@@ -152,7 +156,7 @@ func initWeb() (*app.Web, error) {
s3fsApp := s3fs.NewApp(locale)
supervisorApp := supervisor.NewApp(locale)
loader := bootstrap.NewLoader(apacheApp, codeserverApp, dockerApp, fail2banApp, frpApp, giteaApp, mariadbApp, memcachedApp, minioApp, mysqlApp, nginxApp, openrestyApp, perconaApp, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp)
http := route.NewHttp(config, userService, userTokenService, homeService, taskService, websiteService, projectService, databaseService, databaseServerService, databaseUserService, backupService, certService, certDNSService, certAccountService, appService, environmentService, environmentPHPService, cronService, processService, safeService, firewallService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, logService, monitorService, settingService, systemctlService, toolboxSystemService, toolboxBenchmarkService, toolboxSSHService, toolboxDiskService, toolboxLogService, webHookService, templateService, loader)
http := route.NewHttp(config, userService, userTokenService, homeService, taskService, websiteService, projectService, databaseService, databaseServerService, databaseUserService, backupService, certService, certDNSService, certAccountService, appService, environmentService, environmentGoService, environmentJavaService, environmentNodejsService, environmentPHPService, environmentPythonService, cronService, processService, safeService, firewallService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, logService, monitorService, settingService, systemctlService, toolboxSystemService, toolboxBenchmarkService, toolboxSSHService, toolboxDiskService, toolboxLogService, webHookService, templateService, loader)
wsService := service.NewWsService(locale, config, logger, sshRepo)
ws := route.NewWs(wsService)
mux, err := bootstrap.NewRouter(locale, middlewares, http, ws)

7
go.sum
View File

@@ -125,6 +125,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=
@@ -276,6 +278,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/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
@@ -386,6 +389,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=
@@ -459,6 +464,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=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=

View File

@@ -36,7 +36,11 @@ func NewEnvironmentRepo(t *gotext.Locale, conf *config.Config, cache biz.CacheRe
func (r *environmentRepo) Types() []types.LV {
return []types.LV{
{Label: "Go", Value: "go"},
{Label: "Java", Value: "java"},
{Label: "Node.js", Value: "nodejs"},
{Label: "PHP", Value: "php"},
{Label: "Python", Value: "python"},
}
}
@@ -72,8 +76,16 @@ func (r *environmentRepo) IsInstalled(typ, slug string) bool {
path := filepath.Join(app.Root, "server", typ, slug)
var binFile string
switch typ {
case "go":
binFile = filepath.Join(path, "bin", "go")
case "java":
binFile = filepath.Join(path, "bin", "java")
case "nodejs":
binFile = filepath.Join(path, "bin", "node")
case "php":
binFile = filepath.Join(path, "bin", "php")
case "python":
binFile = filepath.Join(path, "bin", "python3")
default:
return false
}
@@ -102,17 +114,33 @@ func (r *environmentRepo) InstalledVersion(typ, slug string) string {
}
var basePath = filepath.Join(app.Root, "server", typ, slug)
var version string
var err error
switch typ {
case "go":
// go version go1.21.0 linux/amd64 -> 1.21.0
version, err = shell.Exec(filepath.Join(basePath, "bin", "go") + " version | awk '{print $3}' | sed 's/go//'")
case "java":
// openjdk version "17.0.8" 2023-07-18 LTS -> 17.0.8
version, err = shell.Exec(filepath.Join(basePath, "bin", "java") + " -version 2>&1 | head -n 1 | awk -F'\"' '{print $2}'")
case "nodejs":
// v20.10.0 -> 20.10.0
version, err = shell.Exec(filepath.Join(basePath, "bin", "node") + " -v | sed 's/v//'")
case "php":
version, err := shell.Exec(filepath.Join(basePath, "bin", "php") + " -v | head -n 1 | awk '{print $2}'")
if err != nil {
return ""
}
return version
// PHP 8.3.0 (cli) -> 8.3.0
version, err = shell.Exec(filepath.Join(basePath, "bin", "php") + " -v | head -n 1 | awk '{print $2}'")
case "python":
// Python 3.11.5 -> 3.11.5
version, err = shell.Exec(filepath.Join(basePath, "bin", "python3") + " --version | awk '{print $2}'")
default:
return ""
}
if err != nil {
return ""
}
return version
}
func (r *environmentRepo) HasUpdate(typ, slug string) bool {

View File

@@ -0,0 +1,24 @@
package request
// EnvironmentSlug 环境版本请求(通用)
type EnvironmentSlug struct {
Slug string `json:"slug"`
}
// EnvironmentProxy Go 代理设置请求
type EnvironmentProxy struct {
Slug string `json:"slug"`
Proxy string `form:"proxy" json:"proxy" validate:"required"`
}
// EnvironmentRegistry Node.js 镜像设置请求
type EnvironmentRegistry struct {
Slug string `json:"slug"`
Registry string `form:"registry" json:"registry" validate:"required"`
}
// EnvironmentMirror Python 镜像设置请求
type EnvironmentMirror struct {
Slug string `json:"slug"`
Mirror string `form:"mirror" json:"mirror" validate:"required"`
}

View File

@@ -16,46 +16,50 @@ import (
)
type Http struct {
conf *config.Config
user *service.UserService
userToken *service.UserTokenService
home *service.HomeService
task *service.TaskService
website *service.WebsiteService
project *service.ProjectService
database *service.DatabaseService
databaseServer *service.DatabaseServerService
databaseUser *service.DatabaseUserService
backup *service.BackupService
cert *service.CertService
certDNS *service.CertDNSService
certAccount *service.CertAccountService
app *service.AppService
environment *service.EnvironmentService
environmentPHP *service.EnvironmentPHPService
cron *service.CronService
process *service.ProcessService
safe *service.SafeService
firewall *service.FirewallService
ssh *service.SSHService
container *service.ContainerService
containerCompose *service.ContainerComposeService
containerNetwork *service.ContainerNetworkService
containerImage *service.ContainerImageService
containerVolume *service.ContainerVolumeService
file *service.FileService
log *service.LogService
monitor *service.MonitorService
setting *service.SettingService
systemctl *service.SystemctlService
toolboxSystem *service.ToolboxSystemService
toolboxBenchmark *service.ToolboxBenchmarkService
toolboxSSH *service.ToolboxSSHService
toolboxDisk *service.ToolboxDiskService
toolboxLog *service.ToolboxLogService
webhook *service.WebHookService
template *service.TemplateService
apps *apploader.Loader
conf *config.Config
user *service.UserService
userToken *service.UserTokenService
home *service.HomeService
task *service.TaskService
website *service.WebsiteService
project *service.ProjectService
database *service.DatabaseService
databaseServer *service.DatabaseServerService
databaseUser *service.DatabaseUserService
backup *service.BackupService
cert *service.CertService
certDNS *service.CertDNSService
certAccount *service.CertAccountService
app *service.AppService
environment *service.EnvironmentService
environmentGo *service.EnvironmentGoService
environmentJava *service.EnvironmentJavaService
environmentNodejs *service.EnvironmentNodejsService
environmentPHP *service.EnvironmentPHPService
environmentPython *service.EnvironmentPythonService
cron *service.CronService
process *service.ProcessService
safe *service.SafeService
firewall *service.FirewallService
ssh *service.SSHService
container *service.ContainerService
containerCompose *service.ContainerComposeService
containerNetwork *service.ContainerNetworkService
containerImage *service.ContainerImageService
containerVolume *service.ContainerVolumeService
file *service.FileService
log *service.LogService
monitor *service.MonitorService
setting *service.SettingService
systemctl *service.SystemctlService
toolboxSystem *service.ToolboxSystemService
toolboxBenchmark *service.ToolboxBenchmarkService
toolboxSSH *service.ToolboxSSHService
toolboxDisk *service.ToolboxDiskService
toolboxLog *service.ToolboxLogService
webhook *service.WebHookService
template *service.TemplateService
apps *apploader.Loader
}
func NewHttp(
@@ -75,7 +79,11 @@ func NewHttp(
certAccount *service.CertAccountService,
app *service.AppService,
environment *service.EnvironmentService,
environmentGo *service.EnvironmentGoService,
environmentJava *service.EnvironmentJavaService,
environmentNodejs *service.EnvironmentNodejsService,
environmentPHP *service.EnvironmentPHPService,
environmentPython *service.EnvironmentPythonService,
cron *service.CronService,
process *service.ProcessService,
safe *service.SafeService,
@@ -101,46 +109,50 @@ func NewHttp(
apps *apploader.Loader,
) *Http {
return &Http{
conf: conf,
user: user,
userToken: userToken,
home: home,
task: task,
website: website,
project: project,
database: database,
databaseServer: databaseServer,
databaseUser: databaseUser,
backup: backup,
cert: cert,
certDNS: certDNS,
certAccount: certAccount,
app: app,
environment: environment,
environmentPHP: environmentPHP,
cron: cron,
process: process,
safe: safe,
firewall: firewall,
ssh: ssh,
container: container,
containerCompose: containerCompose,
containerNetwork: containerNetwork,
containerImage: containerImage,
containerVolume: containerVolume,
file: file,
log: log,
monitor: monitor,
setting: setting,
systemctl: systemctl,
toolboxSystem: toolboxSystem,
toolboxBenchmark: toolboxBenchmark,
toolboxSSH: toolboxSSH,
toolboxDisk: toolboxDisk,
toolboxLog: toolboxLog,
webhook: webhook,
template: template,
apps: apps,
conf: conf,
user: user,
userToken: userToken,
home: home,
task: task,
website: website,
project: project,
database: database,
databaseServer: databaseServer,
databaseUser: databaseUser,
backup: backup,
cert: cert,
certDNS: certDNS,
certAccount: certAccount,
app: app,
environment: environment,
environmentGo: environmentGo,
environmentJava: environmentJava,
environmentNodejs: environmentNodejs,
environmentPHP: environmentPHP,
environmentPython: environmentPython,
cron: cron,
process: process,
safe: safe,
firewall: firewall,
ssh: ssh,
container: container,
containerCompose: containerCompose,
containerNetwork: containerNetwork,
containerImage: containerImage,
containerVolume: containerVolume,
file: file,
log: log,
monitor: monitor,
setting: setting,
systemctl: systemctl,
toolboxSystem: toolboxSystem,
toolboxBenchmark: toolboxBenchmark,
toolboxSSH: toolboxSSH,
toolboxDisk: toolboxDisk,
toolboxLog: toolboxLog,
webhook: webhook,
template: template,
apps: apps,
}
}
@@ -306,6 +318,19 @@ func (route *Http) Register(r *chi.Mux) {
r.Get("/uninstall", route.environment.Uninstall)
r.Put("/update", route.environment.Update)
r.Get("/is_installed", route.environment.IsInstalled)
r.Route("/go", func(r chi.Router) {
r.Post("/{slug}/set_cli", route.environmentGo.SetCli)
r.Get("/{slug}/proxy", route.environmentGo.GetProxy)
r.Post("/{slug}/proxy", route.environmentGo.SetProxy)
})
r.Route("/java", func(r chi.Router) {
r.Post("/{slug}/set_cli", route.environmentJava.SetCli)
})
r.Route("/nodejs", func(r chi.Router) {
r.Post("/{slug}/set_cli", route.environmentNodejs.SetCli)
r.Get("/{slug}/registry", route.environmentNodejs.GetRegistry)
r.Post("/{slug}/registry", route.environmentNodejs.SetRegistry)
})
r.Route("/php", func(r chi.Router) {
r.Post("/{version}/set_cli", route.environmentPHP.SetCli)
r.Get("/{version}/phpinfo", route.environmentPHP.PHPInfo)
@@ -322,6 +347,11 @@ func (route *Http) Register(r *chi.Mux) {
r.Post("/{version}/modules", route.environmentPHP.InstallModule)
r.Delete("/{version}/modules", route.environmentPHP.UninstallModule)
})
r.Route("/python", func(r chi.Router) {
r.Post("/{slug}/set_cli", route.environmentPython.SetCli)
r.Get("/{slug}/mirror", route.environmentPython.GetMirror)
r.Post("/{slug}/mirror", route.environmentPython.SetMirror)
})
})
r.Route("/cron", func(r chi.Router) {

View File

@@ -0,0 +1,90 @@
package service
import (
"fmt"
"net/http"
"strings"
"github.com/leonelquinteros/gotext"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/biz"
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/pkg/shell"
)
type EnvironmentGoService struct {
t *gotext.Locale
environmentRepo biz.EnvironmentRepo
}
func NewEnvironmentGoService(t *gotext.Locale, environmentRepo biz.EnvironmentRepo) *EnvironmentGoService {
return &EnvironmentGoService{
t: t,
environmentRepo: environmentRepo,
}
}
func (s *EnvironmentGoService) SetCli(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.EnvironmentSlug](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if !s.environmentRepo.IsInstalled("go", req.Slug) {
Error(w, http.StatusUnprocessableEntity, s.t.Get("Go-%s is not installed", req.Slug))
return
}
binPath := fmt.Sprintf("%s/server/go/%s/bin", app.Root, req.Slug)
if _, err = shell.Execf("ln -sf %s/go /usr/local/bin/go", binPath); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
if _, err = shell.Execf("ln -sf %s/gofmt /usr/local/bin/gofmt", binPath); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}
func (s *EnvironmentGoService) GetProxy(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.EnvironmentSlug](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if !s.environmentRepo.IsInstalled("go", req.Slug) {
Error(w, http.StatusUnprocessableEntity, s.t.Get("Go-%s is not installed", req.Slug))
return
}
goBin := fmt.Sprintf("%s/server/go/%s/bin/go", app.Root, req.Slug)
proxy, err := shell.Execf("%s env GOPROXY", goBin)
if err != nil {
proxy = "https://proxy.golang.org,direct"
}
Success(w, strings.TrimSpace(proxy))
}
func (s *EnvironmentGoService) SetProxy(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.EnvironmentProxy](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if !s.environmentRepo.IsInstalled("go", req.Slug) {
Error(w, http.StatusUnprocessableEntity, s.t.Get("Go-%s is not installed", req.Slug))
return
}
goBin := fmt.Sprintf("%s/server/go/%s/bin/go", app.Root, req.Slug)
if _, err = shell.Execf("%s env -w GOPROXY=%s", goBin, req.Proxy); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}

View File

@@ -0,0 +1,48 @@
package service
import (
"fmt"
"net/http"
"github.com/leonelquinteros/gotext"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/biz"
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/pkg/shell"
)
type EnvironmentJavaService struct {
t *gotext.Locale
environmentRepo biz.EnvironmentRepo
}
func NewEnvironmentJavaService(t *gotext.Locale, environmentRepo biz.EnvironmentRepo) *EnvironmentJavaService {
return &EnvironmentJavaService{
t: t,
environmentRepo: environmentRepo,
}
}
func (s *EnvironmentJavaService) SetCli(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.EnvironmentSlug](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if !s.environmentRepo.IsInstalled("java", req.Slug) {
Error(w, http.StatusUnprocessableEntity, s.t.Get("Java-%s is not installed", req.Slug))
return
}
binPath := fmt.Sprintf("%s/server/java/%s/bin", app.Root, req.Slug)
binaries := []string{"java", "javac", "jar", "jshell"}
for _, bin := range binaries {
if _, err = shell.Execf("ln -sf %s/%s /usr/local/bin/%s", binPath, bin, bin); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
}
Success(w, nil)
}

View File

@@ -0,0 +1,89 @@
package service
import (
"fmt"
"net/http"
"strings"
"github.com/leonelquinteros/gotext"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/biz"
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/pkg/shell"
)
type EnvironmentNodejsService struct {
t *gotext.Locale
environmentRepo biz.EnvironmentRepo
}
func NewEnvironmentNodejsService(t *gotext.Locale, environmentRepo biz.EnvironmentRepo) *EnvironmentNodejsService {
return &EnvironmentNodejsService{
t: t,
environmentRepo: environmentRepo,
}
}
func (s *EnvironmentNodejsService) SetCli(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.EnvironmentSlug](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if !s.environmentRepo.IsInstalled("nodejs", req.Slug) {
Error(w, http.StatusUnprocessableEntity, s.t.Get("Node.js-%s is not installed", req.Slug))
return
}
binPath := fmt.Sprintf("%s/server/nodejs/%s/bin", app.Root, req.Slug)
binaries := []string{"node", "npm", "npx", "corepack"}
for _, bin := range binaries {
if _, err = shell.Execf("ln -sf %s/%s /usr/local/bin/%s", binPath, bin, bin); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
}
Success(w, nil)
}
func (s *EnvironmentNodejsService) GetRegistry(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.EnvironmentSlug](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if !s.environmentRepo.IsInstalled("nodejs", req.Slug) {
Error(w, http.StatusUnprocessableEntity, s.t.Get("Node.js-%s is not installed", req.Slug))
return
}
npmBin := fmt.Sprintf("%s/server/nodejs/%s/bin/npm", app.Root, req.Slug)
registry, err := shell.Execf("%s config get --global registry", npmBin)
if err != nil {
registry = "https://registry.npmjs.org/"
}
Success(w, strings.TrimSpace(registry))
}
func (s *EnvironmentNodejsService) SetRegistry(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.EnvironmentRegistry](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if !s.environmentRepo.IsInstalled("nodejs", req.Slug) {
Error(w, http.StatusUnprocessableEntity, s.t.Get("Node.js-%s is not installed", req.Slug))
return
}
npmBin := fmt.Sprintf("%s/server/nodejs/%s/bin/npm", app.Root, req.Slug)
if _, err = shell.Execf("%s config set --global registry %s", npmBin, req.Registry); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}

View File

@@ -88,13 +88,13 @@ func (s *EnvironmentPHPService) GetConfig(w http.ResponseWriter, r *http.Request
return
}
config, err := io.Read(fmt.Sprintf("%s/server/php/%d/etc/php.ini", app.Root, req.Version))
ini, 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)
Success(w, ini)
}
func (s *EnvironmentPHPService) UpdateConfig(w http.ResponseWriter, r *http.Request) {
@@ -127,13 +127,13 @@ func (s *EnvironmentPHPService) GetFPMConfig(w http.ResponseWriter, r *http.Requ
return
}
config, err := io.Read(fmt.Sprintf("%s/server/php/%d/etc/php-fpm.conf", app.Root, req.Version))
ini, 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)
Success(w, ini)
}
func (s *EnvironmentPHPService) UpdateFPMConfig(w http.ResponseWriter, r *http.Request) {

View File

@@ -0,0 +1,89 @@
package service
import (
"fmt"
"net/http"
"strings"
"github.com/leonelquinteros/gotext"
"github.com/acepanel/panel/internal/app"
"github.com/acepanel/panel/internal/biz"
"github.com/acepanel/panel/internal/http/request"
"github.com/acepanel/panel/pkg/shell"
)
type EnvironmentPythonService struct {
t *gotext.Locale
environmentRepo biz.EnvironmentRepo
}
func NewEnvironmentPythonService(t *gotext.Locale, environmentRepo biz.EnvironmentRepo) *EnvironmentPythonService {
return &EnvironmentPythonService{
t: t,
environmentRepo: environmentRepo,
}
}
func (s *EnvironmentPythonService) SetCli(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.EnvironmentSlug](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if !s.environmentRepo.IsInstalled("python", req.Slug) {
Error(w, http.StatusUnprocessableEntity, s.t.Get("Python-%s is not installed", req.Slug))
return
}
binPath := fmt.Sprintf("%s/server/python/%s/bin", app.Root, req.Slug)
binaries := []string{"python3", "pip3"}
for _, bin := range binaries {
if _, err = shell.Execf("ln -sf %s/%s /usr/local/bin/%s", binPath, bin, bin); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
}
Success(w, nil)
}
func (s *EnvironmentPythonService) GetMirror(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.EnvironmentSlug](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if !s.environmentRepo.IsInstalled("python", req.Slug) {
Error(w, http.StatusUnprocessableEntity, s.t.Get("Python-%s is not installed", req.Slug))
return
}
pipBin := fmt.Sprintf("%s/server/python/%s/bin/pip3", app.Root, req.Slug)
mirror, err := shell.Execf("%s config --global get global.index-url", pipBin)
if err != nil {
mirror = "https://pypi.org/simple"
}
Success(w, strings.TrimSpace(mirror))
}
func (s *EnvironmentPythonService) SetMirror(w http.ResponseWriter, r *http.Request) {
req, err := Bind[request.EnvironmentMirror](r)
if err != nil {
Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if !s.environmentRepo.IsInstalled("python", req.Slug) {
Error(w, http.StatusUnprocessableEntity, s.t.Get("Python-%s is not installed", req.Slug))
return
}
pipBin := fmt.Sprintf("%s/server/python/%s/bin/pip3", app.Root, req.Slug)
if _, err = shell.Execf("%s config --global set global.index-url %s", pipBin, req.Mirror); err != nil {
Error(w, http.StatusInternalServerError, "%v", err)
return
}
Success(w, nil)
}

View File

@@ -20,7 +20,11 @@ var ProviderSet = wire.NewSet(
NewDatabaseServerService,
NewDatabaseUserService,
NewEnvironmentService,
NewEnvironmentGoService,
NewEnvironmentJavaService,
NewEnvironmentNodejsService,
NewEnvironmentPHPService,
NewEnvironmentPythonService,
NewFileService,
NewFirewallService,
NewHomeService,

View File

@@ -8,7 +8,6 @@ type Environment struct {
Name string `json:"name"`
Version string `json:"version"`
Description string `json:"description"`
Order int `json:"order"`
}
type Environments []*Environment

View File

@@ -0,0 +1,11 @@
import { http } from '@/utils'
export default {
// 设为 CLI 版本
setCli: (slug: string): any => http.Post(`/environment/go/${slug}/set_cli`),
// 获取代理
getProxy: (slug: string): any => http.Get(`/environment/go/${slug}/proxy`),
// 设置代理
setProxy: (slug: string, proxy: string): any =>
http.Post(`/environment/go/${slug}/proxy`, { proxy })
}

View File

@@ -0,0 +1,6 @@
import { http } from '@/utils'
export default {
// 设为 CLI 版本
setCli: (slug: string): any => http.Post(`/environment/java/${slug}/set_cli`)
}

View File

@@ -0,0 +1,11 @@
import { http } from '@/utils'
export default {
// 设为 CLI 版本
setCli: (slug: string): any => http.Post(`/environment/nodejs/${slug}/set_cli`),
// 获取镜像
getRegistry: (slug: string): any => http.Get(`/environment/nodejs/${slug}/registry`),
// 设置镜像
setRegistry: (slug: string, registry: string): any =>
http.Post(`/environment/nodejs/${slug}/registry`, { registry })
}

View File

@@ -0,0 +1,11 @@
import { http } from '@/utils'
export default {
// 设为 CLI 版本
setCli: (slug: string): any => http.Post(`/environment/python/${slug}/set_cli`),
// 获取镜像
getMirror: (slug: string): any => http.Get(`/environment/python/${slug}/mirror`),
// 设置镜像
setMirror: (slug: string, mirror: string): any =>
http.Post(`/environment/python/${slug}/mirror`, { mirror })
}

View File

@@ -0,0 +1,90 @@
<script setup lang="ts">
import { useGettext } from 'vue3-gettext'
import goApi from '@/api/panel/environment/go'
const route = useRoute()
const slug = route.params.slug as string
const { $gettext } = useGettext()
const proxy = ref('')
const proxyLoading = ref(false)
// 预设的代理选项
const proxyOptions = [
{ label: $gettext('Official (proxy.golang.org)'), value: 'https://proxy.golang.org,direct' },
{ label: $gettext('China - Qiniu (goproxy.cn)'), value: 'https://goproxy.cn,direct' },
{ label: $gettext('China - Alibaba (mirrors.aliyun.com)'), value: 'https://mirrors.aliyun.com/goproxy/,direct' },
{ label: $gettext('China - Tencent (mirrors.cloud.tencent.com)'), value: 'https://mirrors.cloud.tencent.com/go/,direct' }
]
// 获取当前代理设置
const fetchProxy = async () => {
proxyLoading.value = true
useRequest(goApi.getProxy(slug))
.onSuccess((res) => {
proxy.value = res.data
})
.onComplete(() => {
proxyLoading.value = false
})
}
onMounted(() => {
fetchProxy()
})
const handleSetCli = async () => {
useRequest(goApi.setCli(slug)).onSuccess(() => {
window.$message.success($gettext('Set successfully'))
})
}
const handleSaveProxy = async () => {
useRequest(goApi.setProxy(slug, proxy.value)).onSuccess(() => {
window.$message.success($gettext('Saved successfully'))
})
}
</script>
<template>
<common-page show-footer>
<n-flex vertical>
<n-card>
<template #header>
Go {{ slug }}
</template>
<template #header-extra>
<n-button type="info" @click="handleSetCli">
{{ $gettext('Set as CLI Default Version') }}
</n-button>
</template>
</n-card>
<n-card :title="$gettext('Proxy Settings')">
<n-spin :show="proxyLoading">
<n-flex vertical>
<n-alert type="info" :show-icon="false">
{{ $gettext('GOPROXY is used to configure the Go module proxy. Using a domestic mirror can speed up dependency downloads.') }}
</n-alert>
<n-form-item :label="$gettext('Proxy Address')">
<n-select
v-model:value="proxy"
:options="proxyOptions"
filterable
tag
:placeholder="$gettext('Select or enter proxy address')"
/>
</n-form-item>
<n-flex>
<n-button type="primary" @click="handleSaveProxy">
{{ $gettext('Save') }}
</n-button>
</n-flex>
</n-flex>
</n-spin>
</n-card>
</n-flex>
</common-page>
</template>

View File

@@ -0,0 +1,36 @@
<script setup lang="ts">
import { useGettext } from 'vue3-gettext'
import javaApi from '@/api/panel/environment/java'
const route = useRoute()
const slug = route.params.slug as string
const { $gettext } = useGettext()
const handleSetCli = async () => {
useRequest(javaApi.setCli(slug)).onSuccess(() => {
window.$message.success($gettext('Set successfully'))
})
}
</script>
<template>
<common-page show-footer>
<n-flex vertical>
<n-card>
<template #header>
Java {{ slug }} (Amazon Corretto)
</template>
<template #header-extra>
<n-button type="info" @click="handleSetCli">
{{ $gettext('Set as CLI Default Version') }}
</n-button>
</template>
<n-alert type="info" :show-icon="false">
{{ $gettext('Amazon Corretto is a no-cost, multiplatform, production-ready distribution of the Open Java Development Kit (OpenJDK).') }}
</n-alert>
</n-card>
</n-flex>
</common-page>
</template>

View File

@@ -0,0 +1,90 @@
<script setup lang="ts">
import { useGettext } from 'vue3-gettext'
import nodejsApi from '@/api/panel/environment/nodejs'
const route = useRoute()
const slug = route.params.slug as string
const { $gettext } = useGettext()
const registry = ref('')
const registryLoading = ref(false)
// 预设的镜像选项
const registryOptions = [
{ label: $gettext('Official (registry.npmjs.org)'), value: 'https://registry.npmjs.org/' },
{ label: $gettext('China - npmmirror (npmmirror.com)'), value: 'https://registry.npmmirror.com/' },
{ label: $gettext('China - Tencent (mirrors.cloud.tencent.com)'), value: 'https://mirrors.cloud.tencent.com/npm/' },
{ label: $gettext('China - Huawei (repo.huaweicloud.com)'), value: 'https://repo.huaweicloud.com/repository/npm/' }
]
// 获取当前镜像设置
const fetchRegistry = async () => {
registryLoading.value = true
useRequest(nodejsApi.getRegistry(slug))
.onSuccess((res) => {
registry.value = res.data
})
.onComplete(() => {
registryLoading.value = false
})
}
onMounted(() => {
fetchRegistry()
})
const handleSetCli = async () => {
useRequest(nodejsApi.setCli(slug)).onSuccess(() => {
window.$message.success($gettext('Set successfully'))
})
}
const handleSaveRegistry = async () => {
useRequest(nodejsApi.setRegistry(slug, registry.value)).onSuccess(() => {
window.$message.success($gettext('Saved successfully'))
})
}
</script>
<template>
<common-page show-footer>
<n-flex vertical>
<n-card>
<template #header>
Node.js {{ slug }}
</template>
<template #header-extra>
<n-button type="info" @click="handleSetCli">
{{ $gettext('Set as CLI Default Version') }}
</n-button>
</template>
</n-card>
<n-card :title="$gettext('Registry Settings')">
<n-spin :show="registryLoading">
<n-flex vertical>
<n-alert type="info" :show-icon="false">
{{ $gettext('npm registry is used to configure the npm package source. Using a domestic mirror can speed up package downloads.') }}
</n-alert>
<n-form-item :label="$gettext('Registry Address')">
<n-select
v-model:value="registry"
:options="registryOptions"
filterable
tag
:placeholder="$gettext('Select or enter registry address')"
/>
</n-form-item>
<n-flex>
<n-button type="primary" @click="handleSaveRegistry">
{{ $gettext('Save') }}
</n-button>
</n-flex>
</n-flex>
</n-spin>
</n-card>
</n-flex>
</common-page>
</template>

View File

@@ -191,16 +191,22 @@ const handleUninstallModule = async (module: string) => {
<n-tabs v-model:value="currentTab" type="line" animated>
<n-tab-pane name="status" :tab="$gettext('Running Status')">
<n-flex vertical>
<n-card> PHP {{ slug }} </n-card>
<n-card>
<template #header>
PHP {{ slug }}
</template>
<template #header-extra>
<n-flex>
<n-button type="info" @click="handleSetCli">
{{ $gettext('Set as CLI Default Version') }}
</n-button>
<n-button type="primary" @click="handlePHPInfo">
{{ $gettext('View PHPInfo') }}
</n-button>
</n-flex>
</template>
</n-card>
<service-status :service="`php-fpm-${slug}`" show-reload />
<n-flex>
<n-button type="info" @click="handleSetCli">
{{ $gettext('Set as CLI Default Version') }}
</n-button>
<n-button type="primary" @click="handlePHPInfo">
{{ $gettext('View PHPInfo') }}
</n-button>
</n-flex>
</n-flex>
</n-tab-pane>
<n-tab-pane name="modules" :tab="$gettext('Module Management')">

View File

@@ -0,0 +1,92 @@
<script setup lang="ts">
import { useGettext } from 'vue3-gettext'
import pythonApi from '@/api/panel/environment/python'
const route = useRoute()
const slug = route.params.slug as string
const { $gettext } = useGettext()
const mirror = ref('')
const mirrorLoading = ref(false)
// 预设的镜像选项
const mirrorOptions = [
{ label: $gettext('Official (pypi.org)'), value: 'https://pypi.org/simple' },
{ label: $gettext('China - Tsinghua (tuna.tsinghua.edu.cn)'), value: 'https://pypi.tuna.tsinghua.edu.cn/simple' },
{ label: $gettext('China - Alibaba (mirrors.aliyun.com)'), value: 'https://mirrors.aliyun.com/pypi/simple/' },
{ label: $gettext('China - Tencent (mirrors.cloud.tencent.com)'), value: 'https://mirrors.cloud.tencent.com/pypi/simple/' },
{ label: $gettext('China - Douban (pypi.douban.com)'), value: 'https://pypi.douban.com/simple/' },
{ label: $gettext('China - USTC (pypi.mirrors.ustc.edu.cn)'), value: 'https://pypi.mirrors.ustc.edu.cn/simple/' }
]
// 获取当前镜像设置
const fetchMirror = async () => {
mirrorLoading.value = true
useRequest(pythonApi.getMirror(slug))
.onSuccess((res) => {
mirror.value = res.data
})
.onComplete(() => {
mirrorLoading.value = false
})
}
onMounted(() => {
fetchMirror()
})
const handleSetCli = async () => {
useRequest(pythonApi.setCli(slug)).onSuccess(() => {
window.$message.success($gettext('Set successfully'))
})
}
const handleSaveMirror = async () => {
useRequest(pythonApi.setMirror(slug, mirror.value)).onSuccess(() => {
window.$message.success($gettext('Saved successfully'))
})
}
</script>
<template>
<common-page show-footer>
<n-flex vertical>
<n-card>
<template #header>
Python {{ slug }}
</template>
<template #header-extra>
<n-button type="info" @click="handleSetCli">
{{ $gettext('Set as CLI Default Version') }}
</n-button>
</template>
</n-card>
<n-card :title="$gettext('Mirror Settings')">
<n-spin :show="mirrorLoading">
<n-flex vertical>
<n-alert type="info" :show-icon="false">
{{ $gettext('pip mirror is used to configure the Python package source. Using a domestic mirror can speed up package downloads.') }}
</n-alert>
<n-form-item :label="$gettext('Mirror Address')">
<n-select
v-model:value="mirror"
:options="mirrorOptions"
filterable
tag
:placeholder="$gettext('Select or enter mirror address')"
/>
</n-form-item>
<n-flex>
<n-button type="primary" @click="handleSaveMirror">
{{ $gettext('Save') }}
</n-button>
</n-flex>
</n-flex>
</n-spin>
</n-card>
</n-flex>
</common-page>
</template>

View File

@@ -8,6 +8,42 @@ export default {
isHidden: true,
component: Layout,
children: [
{
name: 'environment-go',
path: 'go/:slug',
isHidden: true,
component: () => import('./GoView.vue'),
meta: {
title: 'Go',
icon: 'mdi:language-go',
role: ['admin'],
requireAuth: true
}
},
{
name: 'environment-java',
path: 'java/:slug',
isHidden: true,
component: () => import('./JavaView.vue'),
meta: {
title: 'Java',
icon: 'mdi:language-java',
role: ['admin'],
requireAuth: true
}
},
{
name: 'environment-nodejs',
path: 'nodejs/:slug',
isHidden: true,
component: () => import('./NodejsView.vue'),
meta: {
title: 'Node.js',
icon: 'mdi:nodejs',
role: ['admin'],
requireAuth: true
}
},
{
name: 'environment-php',
path: 'php/:slug',
@@ -19,6 +55,18 @@ export default {
role: ['admin'],
requireAuth: true
}
},
{
name: 'environment-python',
path: 'python/:slug',
isHidden: true,
component: () => import('./PythonView.vue'),
meta: {
title: 'Python',
icon: 'mdi:language-python',
role: ['admin'],
requireAuth: true
}
}
]
} as RouteType