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

feat: 完成应用翻译

This commit is contained in:
2025-04-12 05:25:25 +08:00
parent 3021f466f0
commit 771a9d1f28
27 changed files with 1212 additions and 306 deletions

View File

@@ -5,7 +5,7 @@ on:
- main
pull_request:
jobs:
mockery:
xgotext:
runs-on: ubuntu-latest
steps:
- name: Checkout code

View File

@@ -76,30 +76,30 @@ func initCli() (*app.Cli, error) {
cli := route.NewCli(cliService)
command := bootstrap.NewCli(locale, cli)
gormigrate := bootstrap.NewMigrate(db)
benchmarkApp := benchmark.NewApp()
benchmarkApp := benchmark.NewApp(locale)
dockerApp := docker.NewApp()
fail2banApp := fail2ban.NewApp(websiteRepo)
fail2banApp := fail2ban.NewApp(locale, websiteRepo)
frpApp := frp.NewApp()
giteaApp := gitea.NewApp()
memcachedApp := memcached.NewApp()
memcachedApp := memcached.NewApp(locale)
minioApp := minio.NewApp()
mysqlApp := mysql.NewApp(settingRepo)
nginxApp := nginx.NewApp()
php74App := php74.NewApp(taskRepo)
php80App := php80.NewApp(taskRepo)
php81App := php81.NewApp(taskRepo)
php82App := php82.NewApp(taskRepo)
php83App := php83.NewApp(taskRepo)
php84App := php84.NewApp(taskRepo)
phpmyadminApp := phpmyadmin.NewApp()
mysqlApp := mysql.NewApp(locale, settingRepo)
nginxApp := nginx.NewApp(locale)
php74App := php74.NewApp(locale, taskRepo)
php80App := php80.NewApp(locale, taskRepo)
php81App := php81.NewApp(locale, taskRepo)
php82App := php82.NewApp(locale, taskRepo)
php83App := php83.NewApp(locale, taskRepo)
php84App := php84.NewApp(locale, taskRepo)
phpmyadminApp := phpmyadmin.NewApp(locale)
podmanApp := podman.NewApp()
postgresqlApp := postgresql.NewApp()
pureftpdApp := pureftpd.NewApp()
redisApp := redis.NewApp()
rsyncApp := rsync.NewApp()
s3fsApp := s3fs.NewApp(settingRepo)
supervisorApp := supervisor.NewApp()
toolboxApp := toolbox.NewApp()
postgresqlApp := postgresql.NewApp(locale)
pureftpdApp := pureftpd.NewApp(locale)
redisApp := redis.NewApp(locale)
rsyncApp := rsync.NewApp(locale)
s3fsApp := s3fs.NewApp(locale, settingRepo)
supervisorApp := supervisor.NewApp(locale)
toolboxApp := toolbox.NewApp(locale)
loader := bootstrap.NewLoader(benchmarkApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, minioApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp)
appCli := app.NewCli(command, gormigrate, loader)
return appCli, nil

View File

@@ -111,30 +111,34 @@ func initWeb() (*app.Web, error) {
monitorService := service.NewMonitorService(settingRepo, monitorRepo)
settingService := service.NewSettingService(settingRepo)
systemctlService := service.NewSystemctlService()
benchmarkApp := benchmark.NewApp()
locale, err := bootstrap.NewT(koanf)
if err != nil {
return nil, err
}
benchmarkApp := benchmark.NewApp(locale)
dockerApp := docker.NewApp()
fail2banApp := fail2ban.NewApp(websiteRepo)
fail2banApp := fail2ban.NewApp(locale, websiteRepo)
frpApp := frp.NewApp()
giteaApp := gitea.NewApp()
memcachedApp := memcached.NewApp()
memcachedApp := memcached.NewApp(locale)
minioApp := minio.NewApp()
mysqlApp := mysql.NewApp(settingRepo)
nginxApp := nginx.NewApp()
php74App := php74.NewApp(taskRepo)
php80App := php80.NewApp(taskRepo)
php81App := php81.NewApp(taskRepo)
php82App := php82.NewApp(taskRepo)
php83App := php83.NewApp(taskRepo)
php84App := php84.NewApp(taskRepo)
phpmyadminApp := phpmyadmin.NewApp()
mysqlApp := mysql.NewApp(locale, settingRepo)
nginxApp := nginx.NewApp(locale)
php74App := php74.NewApp(locale, taskRepo)
php80App := php80.NewApp(locale, taskRepo)
php81App := php81.NewApp(locale, taskRepo)
php82App := php82.NewApp(locale, taskRepo)
php83App := php83.NewApp(locale, taskRepo)
php84App := php84.NewApp(locale, taskRepo)
phpmyadminApp := phpmyadmin.NewApp(locale)
podmanApp := podman.NewApp()
postgresqlApp := postgresql.NewApp()
pureftpdApp := pureftpd.NewApp()
redisApp := redis.NewApp()
rsyncApp := rsync.NewApp()
s3fsApp := s3fs.NewApp(settingRepo)
supervisorApp := supervisor.NewApp()
toolboxApp := toolbox.NewApp()
postgresqlApp := postgresql.NewApp(locale)
pureftpdApp := pureftpd.NewApp(locale)
redisApp := redis.NewApp(locale)
rsyncApp := rsync.NewApp(locale)
s3fsApp := s3fs.NewApp(locale, settingRepo)
supervisorApp := supervisor.NewApp(locale)
toolboxApp := toolbox.NewApp(locale)
loader := bootstrap.NewLoader(benchmarkApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, minioApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp)
http := route.NewHttp(userService, dashboardService, taskService, websiteService, databaseService, databaseServerService, databaseUserService, backupService, certService, certDNSService, certAccountService, appService, cronService, processService, safeService, firewallService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, monitorService, settingService, systemctlService, loader)
wsService := service.NewWsService(koanf, sshRepo)

5
go.sum
View File

@@ -57,7 +57,6 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
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.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
@@ -203,8 +202,6 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -264,8 +261,6 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -22,15 +22,19 @@ import (
"time"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/tnb-labs/panel/internal/service"
)
type App struct {
t *gotext.Locale
}
func NewApp() *App {
return &App{}
func NewApp(t *gotext.Locale) *App {
return &App{
t: t,
}
}
func (s *App) Route(r chi.Router) {
@@ -74,7 +78,7 @@ func (s *App) Test(w http.ResponseWriter, r *http.Request) {
result := s.memoryTestTask()
service.Success(w, result)
default:
service.Error(w, http.StatusUnprocessableEntity, "未知测试类型")
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("unknown test type"))
}
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-rat/chix"
"github.com/go-rat/utils/str"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"github.com/tnb-labs/panel/internal/app"
@@ -19,11 +20,13 @@ import (
)
type App struct {
t *gotext.Locale
websiteRepo biz.WebsiteRepo
}
func NewApp(website biz.WebsiteRepo) *App {
func NewApp(t *gotext.Locale, website biz.WebsiteRepo) *App {
return &App{
t: t,
websiteRepo: website,
}
}
@@ -47,12 +50,8 @@ func (s *App) List(w http.ResponseWriter, r *http.Request) {
}
jailList := regexp.MustCompile(`\[(.*?)]`).FindAllStringSubmatch(raw, -1)
if len(jailList) == 0 {
service.Error(w, http.StatusUnprocessableEntity, "Fail2ban 规则为空")
return
}
var jails []Jail
jails := make([]Jail, 0)
for i, jail := range jailList {
if i == 0 {
continue
@@ -107,7 +106,7 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
return
}
if (strings.Contains(raw, "["+jailName+"]") && jailType == "service") || (strings.Contains(raw, "["+jailWebsiteName+"]"+"-cc") && jailType == "website" && jailWebsiteMode == "cc") || (strings.Contains(raw, "["+jailWebsiteName+"]"+"-path") && jailType == "website" && jailWebsiteMode == "path") {
service.Error(w, http.StatusUnprocessableEntity, "规则已存在")
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("rule already exists"))
return
}
@@ -115,7 +114,7 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
case "website":
website, err := s.websiteRepo.GetByName(jailWebsiteName)
if err != nil {
service.Error(w, http.StatusUnprocessableEntity, "获取网站配置失败:%v", err)
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
var ports string
@@ -140,7 +139,7 @@ logpath = ` + app.Root + `/wwwlogs/` + website.Name + `.log
`
raw += rule
if err = io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
@@ -159,7 +158,7 @@ ignoreregex =
`
}
if err = io.Write("/etc/fail2ban/filter.d/haozi-"+jailWebsiteName+"-"+jailWebsiteMode+".conf", filter, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
@@ -178,11 +177,11 @@ ignoreregex =
filter = "pure-ftpd"
port, err = shell.Execf(`cat %s/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`, app.Root)
default:
service.Error(w, http.StatusUnprocessableEntity, "未知服务")
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("unknown service"))
return
}
if len(port) == 0 || err != nil {
service.Error(w, http.StatusUnprocessableEntity, "获取服务端口失败,请检查是否安装")
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("get service port failed, please check if it is installed"))
return
}
@@ -199,13 +198,13 @@ bantime = ` + jailBanTime + `
`
raw += rule
if err := io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
}
if _, err = shell.Execf("fail2ban-client reload"); err != nil {
service.Error(w, http.StatusInternalServerError, "重载配置失败")
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
@@ -226,7 +225,7 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
return
}
if !strings.Contains(raw, "["+req.Name+"]") {
service.Error(w, http.StatusUnprocessableEntity, "规则不存在")
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("rule not found"))
return
}
@@ -234,12 +233,12 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
raw = strings.ReplaceAll(raw, "\n# "+req.Name+"-START"+rule+"# "+req.Name+"-END", "")
raw = strings.TrimSpace(raw)
if err := io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if _, err := shell.Execf("fail2ban-client reload"); err != nil {
service.Error(w, http.StatusInternalServerError, "重载配置失败")
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
@@ -256,17 +255,17 @@ func (s *App) BanList(w http.ResponseWriter, r *http.Request) {
currentlyBan, err := shell.Execf(`fail2ban-client status %s | grep "Currently banned" | awk '{print $4}'`, req.Name)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取封禁列表失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get current banned list"))
return
}
totalBan, err := shell.Execf(`fail2ban-client status %s | grep "Total banned" | awk '{print $4}'`, req.Name)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取封禁列表失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get total banned list"))
return
}
bannedIp, err := shell.Execf(`fail2ban-client status %s | grep "Banned IP list" | awk -F ":" '{print $2}'`, req.Name)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取封禁列表失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get banned ip list"))
return
}
bannedIpList := strings.Split(bannedIp, " ")
@@ -300,7 +299,7 @@ func (s *App) Unban(w http.ResponseWriter, r *http.Request) {
}
if _, err = shell.Execf("fail2ban-client set %s unbanip %s", req.Name, req.IP); err != nil {
service.Error(w, http.StatusInternalServerError, "解封失败")
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
@@ -325,17 +324,17 @@ func (s *App) SetWhiteList(w http.ResponseWriter, r *http.Request) {
if reg.MatchString(raw) {
raw = reg.ReplaceAllString(raw, "ignoreip = "+req.IP+"\n")
} else {
service.Error(w, http.StatusInternalServerError, "解析Fail2ban规则失败Fail2ban可能已损坏")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to parse the ignoreip of fail2ban"))
return
}
if err = io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if _, err = shell.Execf("fail2ban-client reload"); err != nil {
service.Error(w, http.StatusInternalServerError, "重载配置失败")
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
service.Success(w, nil)
@@ -353,7 +352,7 @@ func (s *App) GetWhiteList(w http.ResponseWriter, r *http.Request) {
ignoreIp := reg.FindStringSubmatch(raw)[1]
service.Success(w, ignoreIp)
} else {
service.Error(w, http.StatusInternalServerError, "解析Fail2ban规则失败Fail2ban可能已损坏")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to parse the ignoreip of fail2ban"))
return
}
}

View File

@@ -7,6 +7,7 @@ import (
"regexp"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/tnb-labs/panel/internal/service"
"github.com/tnb-labs/panel/pkg/io"
@@ -14,10 +15,14 @@ import (
"github.com/tnb-labs/panel/pkg/types"
)
type App struct{}
type App struct {
t *gotext.Locale
}
func NewApp() *App {
return &App{}
func NewApp(t *gotext.Locale) *App {
return &App{
t: t,
}
}
func (s *App) Route(r chi.Router) {
@@ -29,7 +34,7 @@ func (s *App) Route(r chi.Router) {
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
status, err := systemctl.Status("memcached")
if err != nil {
service.Error(w, http.StatusInternalServerError, "failed to get Memcached status: %v", err)
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get Memcached status: %v"), err)
return
}
if !status {
@@ -48,7 +53,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
_, err = conn.Write([]byte("stats\nquit\n"))
if err != nil {
service.Error(w, http.StatusInternalServerError, "failed to write to Memcached: %v", err)
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to write to Memcached: %v"), err)
return
}
@@ -69,7 +74,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
}
if err = scanner.Err(); err != nil {
service.Error(w, http.StatusInternalServerError, "failed to read from Memcached: %v", err)
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to read from Memcached: %v"), err)
return
}

View File

@@ -7,6 +7,7 @@ import (
"regexp"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"github.com/tnb-labs/panel/internal/app"
@@ -21,11 +22,13 @@ import (
)
type App struct {
t *gotext.Locale
settingRepo biz.SettingRepo
}
func NewApp(setting biz.SettingRepo) *App {
func NewApp(t *gotext.Locale, setting biz.SettingRepo) *App {
return &App{
t: t,
settingRepo: setting,
}
}
@@ -45,7 +48,7 @@ func (s *App) Route(r chi.Router) {
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
config, err := io.Read(app.Root + "/server/mysql/conf/my.cnf")
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取配置失败")
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
@@ -61,12 +64,12 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
}
if err = io.Write(app.Root+"/server/mysql/conf/my.cnf", req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "写入配置失败:%v", err)
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Restart("mysqld"); err != nil {
service.Error(w, http.StatusInternalServerError, "重启失败:%v", err)
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to restart MySQL: %v"), err)
return
}
@@ -77,12 +80,12 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
rootPassword, err := s.settingRepo.Get(biz.SettingKeyMySQLRootPassword)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取root密码失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to load MySQL root password: %v", err))
return
}
if len(rootPassword) == 0 {
service.Error(w, http.StatusUnprocessableEntity, "root密码为空")
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("MySQL root password is empty"))
return
}
@@ -93,12 +96,12 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
}
if err = os.Setenv("MYSQL_PWD", rootPassword); err != nil {
service.Error(w, http.StatusInternalServerError, "设置环境变量失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set MYSQL_PWD env: %v", err))
return
}
raw, err := shell.Execf(`mysqladmin -u root extended-status`)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取负载失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get MySQL status: %v", err))
return
}
_ = os.Unsetenv("MYSQL_PWD")
@@ -108,24 +111,24 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
regex string
name string
}{
{`Uptime\s+\|\s+(\d+)\s+\|`, "运行时间"},
{`Queries\s+\|\s+(\d+)\s+\|`, "总查询次数"},
{`Connections\s+\|\s+(\d+)\s+\|`, "总连接次数"},
{`Com_commit\s+\|\s+(\d+)\s+\|`, "每秒事务"},
{`Com_rollback\s+\|\s+(\d+)\s+\|`, "每秒回滚"},
{`Bytes_sent\s+\|\s+(\d+)\s+\|`, "发送"},
{`Bytes_received\s+\|\s+(\d+)\s+\|`, "接收"},
{`Threads_connected\s+\|\s+(\d+)\s+\|`, "活动连接数"},
{`Max_used_connections\s+\|\s+(\d+)\s+\|`, "峰值连接数"},
{`Key_read_requests\s+\|\s+(\d+)\s+\|`, "索引命中率"},
{`Innodb_buffer_pool_reads\s+\|\s+(\d+)\s+\|`, "Innodb索引命中率"},
{`Created_tmp_disk_tables\s+\|\s+(\d+)\s+\|`, "创建临时表到硬盘"},
{`Open_tables\s+\|\s+(\d+)\s+\|`, "已打开的表"},
{`Select_full_join\s+\|\s+(\d+)\s+\|`, "没有使用索引的量"},
{`Select_full_range_join\s+\|\s+(\d+)\s+\|`, "没有索引的JOIN量"},
{`Select_range_check\s+\|\s+(\d+)\s+\|`, "没有索引的子查询量"},
{`Sort_merge_passes\s+\|\s+(\d+)\s+\|`, "排序后的合并次数"},
{`Table_locks_waited\s+\|\s+(\d+)\s+\|`, "锁表次数"},
{`Uptime\s+\|\s+(\d+)\s+\|`, s.t.Get("Uptime")},
{`Queries\s+\|\s+(\d+)\s+\|`, s.t.Get("Total Queries")},
{`Connections\s+\|\s+(\d+)\s+\|`, s.t.Get("Total Connections")},
{`Com_commit\s+\|\s+(\d+)\s+\|`, s.t.Get("Transactions per Second")},
{`Com_rollback\s+\|\s+(\d+)\s+\|`, s.t.Get("Rollbacks per Second")},
{`Bytes_sent\s+\|\s+(\d+)\s+\|`, s.t.Get("Bytes Sent")},
{`Bytes_received\s+\|\s+(\d+)\s+\|`, s.t.Get("Bytes Received")},
{`Threads_connected\s+\|\s+(\d+)\s+\|`, s.t.Get("Active Connections")},
{`Max_used_connections\s+\|\s+(\d+)\s+\|`, s.t.Get("Peak Connections")},
{`Key_read_requests\s+\|\s+(\d+)\s+\|`, s.t.Get("Index Hit Rate")},
{`Innodb_buffer_pool_reads\s+\|\s+(\d+)\s+\|`, s.t.Get("Innodb Index Hit Rate")},
{`Created_tmp_disk_tables\s+\|\s+(\d+)\s+\|`, s.t.Get("Temporary Tables Created on Disk")},
{`Open_tables\s+\|\s+(\d+)\s+\|`, s.t.Get("Open Tables")},
{`Select_full_join\s+\|\s+(\d+)\s+\|`, s.t.Get("Full Joins without Index")},
{`Select_full_range_join\s+\|\s+(\d+)\s+\|`, s.t.Get("Full Range Joins without Index")},
{`Select_range_check\s+\|\s+(\d+)\s+\|`, s.t.Get("Subqueries without Index")},
{`Sort_merge_passes\s+\|\s+(\d+)\s+\|`, s.t.Get("Sort Merge Passes")},
{`Table_locks_waited\s+\|\s+(\d+)\s+\|`, s.t.Get("Table Locks Waited")},
}
for _, expression := range expressions {
@@ -133,7 +136,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
matches := re.FindStringSubmatch(raw)
if len(matches) > 1 {
d := map[string]string{"name": expression.name, "value": matches[1]}
if expression.name == "发送" || expression.name == "接收" {
if expression.name == s.t.Get("Bytes Sent") || expression.name == s.t.Get("Bytes Received") {
d["value"] = tools.FormatBytes(cast.ToFloat64(matches[1]))
}
@@ -182,11 +185,7 @@ func (s *App) ClearSlowLog(w http.ResponseWriter, r *http.Request) {
func (s *App) GetRootPassword(w http.ResponseWriter, r *http.Request) {
rootPassword, err := s.settingRepo.Get(biz.SettingKeyMySQLRootPassword)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取root密码失败")
return
}
if len(rootPassword) == 0 {
service.Error(w, http.StatusUnprocessableEntity, "root密码为空")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to load MySQL root password: %v", err))
return
}
@@ -219,7 +218,7 @@ func (s *App) SetRootPassword(w http.ResponseWriter, r *http.Request) {
}
}
if err = s.settingRepo.Set(biz.SettingKeyMySQLRootPassword, req.Password); err != nil {
service.Error(w, http.StatusInternalServerError, "设置保存失败:%v", err)
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-resty/resty/v2"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"github.com/tnb-labs/panel/internal/app"
@@ -20,11 +21,13 @@ import (
)
type App struct {
// Dependent services
t *gotext.Locale
}
func NewApp() *App {
return &App{}
func NewApp(t *gotext.Locale) *App {
return &App{
t: t,
}
}
func (s *App) Route(r chi.Router) {
@@ -38,7 +41,7 @@ func (s *App) Route(r chi.Router) {
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
config, err := io.Read(fmt.Sprintf("%s/server/nginx/conf/nginx.conf", app.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取配置失败")
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
@@ -53,13 +56,13 @@ func (s *App) SaveConfig(w http.ResponseWriter, r *http.Request) {
}
if err = io.Write(fmt.Sprintf("%s/server/nginx/conf/nginx.conf", app.Root), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "保存配置失败")
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Reload("nginx"); err != nil {
_, err = shell.Execf("nginx -t")
service.Error(w, http.StatusInternalServerError, "重载服务失败: %v", err)
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v"), err)
return
}
@@ -84,6 +87,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
resp, err := client.R().Get("http://127.0.0.1/nginx_status")
if err != nil || !resp.IsSuccess() {
service.Success(w, []types.NV{})
return
}
raw := resp.String()
@@ -91,29 +95,29 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
workers, err := shell.Execf("ps aux | grep nginx | grep 'worker process' | wc -l")
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取负载失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get nginx workers: %v"), err)
return
}
data = append(data, types.NV{
Name: "工作进程",
Name: s.t.Get("Workers"),
Value: workers,
})
out, err := shell.Execf("ps aux | grep nginx | grep 'worker process' | awk '{memsum+=$6};END {print memsum}'")
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取负载失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get nginx workers: %v"), err)
return
}
mem := tools.FormatBytes(cast.ToFloat64(out))
data = append(data, types.NV{
Name: "内存占用",
Name: s.t.Get("Memory"),
Value: mem,
})
match := regexp.MustCompile(`Active connections:\s+(\d+)`).FindStringSubmatch(raw)
if len(match) == 2 {
data = append(data, types.NV{
Name: "活跃连接数",
Name: s.t.Get("Active connections"),
Value: match[1],
})
}
@@ -121,15 +125,15 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
match = regexp.MustCompile(`server accepts handled requests\s+(\d+)\s+(\d+)\s+(\d+)`).FindStringSubmatch(raw)
if len(match) == 4 {
data = append(data, types.NV{
Name: "总连接次数",
Name: s.t.Get("Total connections"),
Value: match[1],
})
data = append(data, types.NV{
Name: "总握手次数",
Name: s.t.Get("Total handshakes"),
Value: match[2],
})
data = append(data, types.NV{
Name: "总请求次数",
Name: s.t.Get("Total requests"),
Value: match[3],
})
}
@@ -137,15 +141,15 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
match = regexp.MustCompile(`Reading:\s+(\d+)\s+Writing:\s+(\d+)\s+Waiting:\s+(\d+)`).FindStringSubmatch(raw)
if len(match) == 4 {
data = append(data, types.NV{
Name: "请求数",
Name: s.t.Get("Reading"),
Value: match[1],
})
data = append(data, types.NV{
Name: "响应数",
Name: s.t.Get("Writing"),
Value: match[2],
})
data = append(data, types.NV{
Name: "驻留进程",
Name: s.t.Get("Waiting"),
Value: match[3],
})
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-resty/resty/v2"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"github.com/tnb-labs/panel/internal/app"
@@ -22,11 +23,13 @@ import (
type App struct {
version uint
t *gotext.Locale
taskRepo biz.TaskRepo
}
func NewApp(task biz.TaskRepo) *App {
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
return &App{
t: t,
taskRepo: task,
}
}
@@ -120,8 +123,36 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
return
}
dataKeys := []string{"应用池", "工作模式", "启动时间", "接受连接", "监听队列", "最大监听队列", "监听队列长度", "空闲进程数量", "活动进程数量", "总进程数量", "最大活跃进程数量", "达到进程上限次数", "慢请求"}
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"}
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 {
@@ -194,7 +225,7 @@ func (s *App) InstallExtension(w http.ResponseWriter, r *http.Request) {
}
if !s.checkExtension(req.Slug) {
service.Error(w, http.StatusUnprocessableEntity, "扩展不存在")
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("extension %s does not exist", req.Slug))
return
}
@@ -205,7 +236,7 @@ func (s *App) InstallExtension(w http.ResponseWriter, r *http.Request) {
}
task := new(biz.Task)
task.Name = fmt.Sprintf("安装PHP-%d扩展 %s", s.version, req.Slug)
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"
@@ -225,7 +256,7 @@ func (s *App) UninstallExtension(w http.ResponseWriter, r *http.Request) {
}
if !s.checkExtension(req.Slug) {
service.Error(w, http.StatusUnprocessableEntity, "扩展不存在")
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("extension %s does not exist", req.Slug))
return
}
@@ -236,7 +267,7 @@ func (s *App) UninstallExtension(w http.ResponseWriter, r *http.Request) {
}
task := new(biz.Task)
task.Name = fmt.Sprintf("卸载PHP-%d扩展 %s", s.version, req.Slug)
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"
@@ -253,198 +284,197 @@ func (s *App) getExtensions() []Extension {
{
Name: "fileinfo",
Slug: "fileinfo",
Description: "Fileinfo 是一个用于识别文件类型的库",
Description: s.t.Get("Fileinfo is a library used to identify file types"),
},
{
Name: "OPcache",
Slug: "Zend OPcache",
Description: "OPcache 将 PHP 脚本预编译的字节码存储到共享内存中来提升 PHP 的性能",
Description: s.t.Get("OPcache stores precompiled PHP script bytecode in shared memory to improve PHP performance"),
},
{
Name: "igbinary",
Slug: "igbinary",
Description: "Igbinary 是一个用于序列化和反序列化数据的库",
Description: s.t.Get("Igbinary is a library for serializing and deserializing data"),
},
{
Name: "Redis",
Slug: "redis",
Description: "PhpRedis 连接并操作 Redis 数据库上的数据(需先安装上面 igbinary 拓展)",
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: "Memcached 是一个用于连接 Memcached 服务器的驱动程序",
Description: s.t.Get("Memcached is a driver for connecting to Memcached servers"),
},
{
Name: "ImageMagick",
Slug: "imagick",
Description: "ImageMagick 是一个免费的创建、编辑、合成图片的软件",
Description: s.t.Get("ImageMagick is free software for creating, editing, and composing images"),
},
{
Name: "exif",
Slug: "exif",
Description: "exif 是一个用于读取和写入图像元数据的库",
Description: s.t.Get("Exif is a library for reading and writing image metadata"),
},
{
Name: "pgsql",
Slug: "pgsql",
Description: "pgsql 是一个用于连接 PostgreSQL 的驱动程序(需先安装 PostgreSQL",
Description: s.t.Get("pgsql is a driver for connecting to PostgreSQL (requires PostgreSQL installed)"),
},
{
Name: "pdo_pgsql",
Slug: "pdo_pgsql",
Description: "pdo_pgsql 是一个用于连接 PostgreSQL 的 PDO 驱动程序(需先安装 PostgreSQL",
Description: s.t.Get("pdo_pgsql is a PDO driver for connecting to PostgreSQL (requires PostgreSQL installed)"),
},
{
Name: "sqlsrv",
Slug: "sqlsrv",
Description: "sqlsrv 是一个用于连接 SQL Server 的驱动程序",
Description: s.t.Get("sqlsrv is a driver for connecting to SQL Server"),
},
{
Name: "pdo_sqlsrv",
Slug: "pdo_sqlsrv",
Description: "pdo_sqlsrv 是一个用于连接 SQL Server 的 PDO 驱动程序",
Description: s.t.Get("pdo_sqlsrv is a PDO driver for connecting to SQL Server"),
},
{
Name: "imap",
Slug: "imap",
Description: "IMAP 扩展允许 PHP 读取、搜索、删除、下载和管理邮件",
Description: s.t.Get("IMAP extension allows PHP to read, search, delete, download, and manage emails"),
},
{
Name: "zip",
Slug: "zip",
Description: "Zip 是一个用于处理 ZIP 文件的库",
Description: s.t.Get("Zip is a library for handling ZIP files"),
},
{
Name: "bz2",
Slug: "bz2",
Description: "Bzip2 是一个用于压缩和解压缩文件的库",
Description: s.t.Get("Bzip2 is a library for compressing and decompressing files"),
},
{
Name: "ssh2",
Slug: "ssh2",
Description: "SSH2 是一个用于连接 SSH 服务器的库",
Description: s.t.Get("SSH2 is a library for connecting to SSH servers"),
},
{
Name: "event",
Slug: "event",
Description: "Event 是一个用于处理事件的库",
Description: s.t.Get("Event is a library for handling events"),
},
{
Name: "readline",
Slug: "readline",
Description: "Readline 是一个处理文本的库",
Description: s.t.Get("Readline is a library for processing text"),
},
{
Name: "snmp",
Slug: "snmp",
Description: "SNMP 是一种用于网络管理的协议",
Description: s.t.Get("SNMP is a protocol for network management"),
},
{
Name: "ldap",
Slug: "ldap",
Description: "LDAP 是一种用于访问目录服务的协议",
Description: s.t.Get("LDAP is a protocol for accessing directory services"),
},
{
Name: "enchant",
Slug: "enchant",
Description: "Enchant 是一个拼写检查库",
Description: s.t.Get("Enchant is a spell-checking library"),
},
{
Name: "pspell",
Slug: "pspell",
Description: "Pspell 是一个拼写检查库",
Description: s.t.Get("Pspell is a spell-checking library"),
},
{
Name: "calendar",
Slug: "calendar",
Description: "Calendar 是一个用于处理日期的库",
Description: s.t.Get("Calendar is a library for handling dates"),
},
{
Name: "gmp",
Slug: "gmp",
Description: "GMP 是一个用于处理大整数的库",
Description: s.t.Get("GMP is a library for handling large integers"),
},
{
Name: "xlswriter",
Slug: "xlswriter",
Description: "XLSWriter 是一个高性能读写 Excel 文件的库",
Description: s.t.Get("XLSWriter is a high-performance library for reading and writing Excel files"),
},
{
Name: "xsl",
Slug: "xsl",
Description: "XSL 是一个用于处理 XML 文档的库",
Description: s.t.Get("XSL is a library for processing XML documents"),
},
{
Name: "intl",
Slug: "intl",
Description: "Intl 是一个用于处理国际化和本地化的库",
Description: s.t.Get("Intl is a library for handling internationalization and localization"),
},
{
Name: "gettext",
Slug: "gettext",
Description: "Gettext 是一个用于处理多语言的库",
Description: s.t.Get("Gettext is a library for handling multilingual support"),
},
{
Name: "grpc",
Slug: "grpc",
Description: "gRPC 是一个高性能、开源和通用的 RPC 框架",
Description: s.t.Get("gRPC is a high-performance, open-source, and general-purpose RPC framework"),
},
{
Name: "protobuf",
Slug: "protobuf",
Description: "protobuf 是一个用于序列化和反序列化数据的库",
Description: s.t.Get("protobuf is a library for serializing and deserializing data"),
},
{
Name: "rdkafka",
Slug: "rdkafka",
Description: "rdkafka 是一个用于连接 Apache Kafka 的库",
Description: s.t.Get("rdkafka is a library for connecting to Apache Kafka"),
},
{
Name: "xhprof",
Slug: "xhprof",
Description: "xhprof 是一个用于性能分析的库",
Description: s.t.Get("xhprof is a library for performance profiling"),
},
{
Name: "xdebug",
Slug: "xdebug",
Description: "xdebug 是一个用于调试和分析 PHP 代码的库",
Description: s.t.Get("xdebug is a library for debugging and profiling PHP code"),
},
{
Name: "yaml",
Slug: "yaml",
Description: "yaml 是一个用于处理 YAML 的库",
Description: s.t.Get("yaml is a library for handling YAML"),
},
{
Name: "zstd",
Slug: "zstd",
Description: "zstd 是一个用于压缩和解压缩文件的库",
Description: s.t.Get("zstd is a library for compressing and decompressing files"),
},
{
Name: "sysvmsg",
Slug: "sysvmsg",
Description: "Sysvmsg 是一个用于处理 System V 消息队列的库",
Description: s.t.Get("Sysvmsg is a library for handling System V message queues"),
},
{
Name: "sysvsem",
Slug: "sysvsem",
Description: "Sysvsem 是一个用于处理 System V 信号量的库",
Description: s.t.Get("Sysvsem is a library for handling System V semaphores"),
},
{
Name: "sysvshm",
Slug: "sysvshm",
Description: "Sysvshm 是一个用于处理 System V 共享内存的库",
Description: s.t.Get("Sysvshm is a library for handling System V shared memory"),
},
{
Name: "ionCube",
Slug: "ionCube Loader",
Description: "ionCube 是一个专业级的 PHP 加密解密工具(需在 OPcache 之后安装)",
Description: s.t.Get("ionCube is a professional-grade PHP encryption and decryption tool (must be installed after OPcache)"),
},
{
Name: "Swoole",
Slug: "swoole",
Description: "Swoole 是一个用于构建高性能的异步并发服务器的 PHP 扩展",
Description: s.t.Get("Swoole is a PHP extension for building high-performance asynchronous concurrent servers"),
},
}
@@ -453,7 +483,7 @@ func (s *App) getExtensions() []Extension {
extensions = append(extensions, Extension{
Name: "Swow",
Slug: "Swow",
Description: "Swow 是一个用于构建高性能的异步并发服务器的 PHP 扩展",
Description: s.t.Get("Swow is a PHP extension for building high-performance asynchronous concurrent servers"),
})
}
// PHP 8.4 移除了 pspell 和 imap 并且不再建议使用

View File

@@ -2,6 +2,7 @@ package php74
import (
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/tnb-labs/panel/internal/apps/php"
"github.com/tnb-labs/panel/internal/biz"
@@ -11,9 +12,9 @@ type App struct {
php *php.App
}
func NewApp(task biz.TaskRepo) *App {
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
return &App{
php: php.NewApp(task),
php: php.NewApp(t, task),
}
}

View File

@@ -2,6 +2,7 @@ package php80
import (
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/tnb-labs/panel/internal/apps/php"
"github.com/tnb-labs/panel/internal/biz"
@@ -11,9 +12,9 @@ type App struct {
php *php.App
}
func NewApp(task biz.TaskRepo) *App {
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
return &App{
php: php.NewApp(task),
php: php.NewApp(t, task),
}
}

View File

@@ -2,6 +2,7 @@ package php81
import (
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/tnb-labs/panel/internal/apps/php"
"github.com/tnb-labs/panel/internal/biz"
@@ -11,9 +12,9 @@ type App struct {
php *php.App
}
func NewApp(task biz.TaskRepo) *App {
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
return &App{
php: php.NewApp(task),
php: php.NewApp(t, task),
}
}

View File

@@ -2,6 +2,7 @@ package php82
import (
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/tnb-labs/panel/internal/apps/php"
"github.com/tnb-labs/panel/internal/biz"
@@ -11,9 +12,9 @@ type App struct {
php *php.App
}
func NewApp(task biz.TaskRepo) *App {
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
return &App{
php: php.NewApp(task),
php: php.NewApp(t, task),
}
}

View File

@@ -2,6 +2,7 @@ package php83
import (
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/tnb-labs/panel/internal/apps/php"
"github.com/tnb-labs/panel/internal/biz"
@@ -11,9 +12,9 @@ type App struct {
php *php.App
}
func NewApp(task biz.TaskRepo) *App {
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
return &App{
php: php.NewApp(task),
php: php.NewApp(t, task),
}
}

View File

@@ -2,6 +2,7 @@ package php84
import (
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/tnb-labs/panel/internal/apps/php"
"github.com/tnb-labs/panel/internal/biz"
@@ -11,9 +12,9 @@ type App struct {
php *php.App
}
func NewApp(task biz.TaskRepo) *App {
func NewApp(t *gotext.Locale, task biz.TaskRepo) *App {
return &App{
php: php.NewApp(task),
php: php.NewApp(t, task),
}
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-rat/chix"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"github.com/tnb-labs/panel/internal/app"
@@ -19,10 +20,14 @@ import (
"github.com/tnb-labs/panel/pkg/systemctl"
)
type App struct{}
type App struct {
t *gotext.Locale
}
func NewApp() *App {
return &App{}
func NewApp(t *gotext.Locale) *App {
return &App{
t: t,
}
}
func (s *App) Route(r chi.Router) {
@@ -35,7 +40,7 @@ func (s *App) Route(r chi.Router) {
func (s *App) Info(w http.ResponseWriter, r *http.Request) {
files, err := os.ReadDir(fmt.Sprintf("%s/server/phpmyadmin", app.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, "找不到 phpMyAdmin 目录")
service.Error(w, http.StatusInternalServerError, s.t.Get("phpMyAdmin directory not found"))
return
}
@@ -46,7 +51,7 @@ func (s *App) Info(w http.ResponseWriter, r *http.Request) {
}
}
if len(phpmyadmin) == 0 {
service.Error(w, http.StatusInternalServerError, "找不到 phpMyAdmin 目录")
service.Error(w, http.StatusInternalServerError, s.t.Get("phpMyAdmin directory not found"))
return
}
@@ -57,7 +62,7 @@ func (s *App) Info(w http.ResponseWriter, r *http.Request) {
}
match := regexp.MustCompile(`listen\s+(\d+);`).FindStringSubmatch(conf)
if len(match) == 0 {
service.Error(w, http.StatusInternalServerError, "找不到 phpMyAdmin 端口")
service.Error(w, http.StatusInternalServerError, s.t.Get("phpMyAdmin port not found"))
return
}
@@ -81,7 +86,7 @@ func (s *App) UpdatePort(w http.ResponseWriter, r *http.Request) {
}
conf = regexp.MustCompile(`listen\s+(\d+);`).ReplaceAllString(conf, "listen "+cast.ToString(req.Port)+";")
if err = io.Write(fmt.Sprintf("%s/server/vhost/phpmyadmin.conf", app.Root), conf, 0644); err != nil {
service.ErrorSystem(w)
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
@@ -100,7 +105,7 @@ func (s *App) UpdatePort(w http.ResponseWriter, r *http.Request) {
if err = systemctl.Reload("nginx"); err != nil {
_, err = shell.Execf("nginx -t")
service.Error(w, http.StatusInternalServerError, "重载OpenResty失败%v", err)
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v", err))
return
}
@@ -131,7 +136,7 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
if err = systemctl.Reload("nginx"); err != nil {
_, err = shell.Execf("nginx -t")
service.Error(w, http.StatusInternalServerError, "重载OpenResty失败%v", err)
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v", err))
return
}

View File

@@ -6,6 +6,7 @@ import (
"time"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/tnb-labs/panel/internal/app"
"github.com/tnb-labs/panel/internal/service"
@@ -15,10 +16,14 @@ import (
"github.com/tnb-labs/panel/pkg/types"
)
type App struct{}
type App struct {
t *gotext.Locale
}
func NewApp() *App {
return &App{}
func NewApp(t *gotext.Locale) *App {
return &App{
t: t,
}
}
func (s *App) Route(r chi.Router) {
@@ -36,7 +41,7 @@ func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
// 获取配置
config, err := io.Read(fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL配置失败")
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
@@ -52,12 +57,12 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
}
if err = io.Write(fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "写入PostgreSQL配置失败")
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Reload("postgresql"); err != nil {
service.Error(w, http.StatusInternalServerError, "重载服务失败: %v", err)
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload PostgreSQL: %v", err))
return
}
@@ -69,7 +74,7 @@ func (s *App) GetUserConfig(w http.ResponseWriter, r *http.Request) {
// 获取配置
config, err := io.Read(fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root))
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL配置失败")
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
@@ -85,12 +90,12 @@ func (s *App) UpdateUserConfig(w http.ResponseWriter, r *http.Request) {
}
if err = io.Write(fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root), req.Config, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "写入PostgreSQL配置失败")
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = systemctl.Reload("postgresql"); err != nil {
service.Error(w, http.StatusInternalServerError, "重载服务失败: %v", err)
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload PostgreSQL: %v", err))
return
}
@@ -107,36 +112,36 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
start, err := shell.Execf(`echo "select pg_postmaster_start_time();" | su - postgres -c "psql" | sed -n 3p | cut -d'.' -f1`)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL启动时间失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL start time: %v", err))
return
}
pid, err := shell.Execf(`echo "select pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL进程PID失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL backend pid: %v", err))
return
}
process, err := shell.Execf(`ps aux | grep postgres | grep -v grep | wc -l`)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL进程数失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL process: %v", err))
return
}
connections, err := shell.Execf(`echo "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL连接数失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL connections: %v", err))
return
}
storage, err := shell.Execf(`echo "select pg_size_pretty(pg_database_size('postgres'));" | su - postgres -c "psql" | sed -n 3p`)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL空间占用失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL database size: %v", err))
return
}
data := []types.NV{
{Name: "启动时间", Value: start},
{Name: "进程 PID", Value: pid},
{Name: "进程数", Value: process},
{Name: "总连接数", Value: connections},
{Name: "空间占用", Value: storage},
{Name: s.t.Get("Start Time"), Value: start},
{Name: s.t.Get("Process PID"), Value: pid},
{Name: s.t.Get("Process Count"), Value: process},
{Name: s.t.Get("Total Connections"), Value: connections},
{Name: s.t.Get("Storage Usage"), Value: storage},
}
service.Success(w, data)

View File

@@ -7,6 +7,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-rat/chix"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"github.com/tnb-labs/panel/internal/app"
@@ -17,10 +18,14 @@ import (
"github.com/tnb-labs/panel/pkg/systemctl"
)
type App struct{}
type App struct {
t *gotext.Locale
}
func NewApp() *App {
return &App{}
func NewApp(t *gotext.Locale) *App {
return &App{
t: t,
}
}
func (s *App) Route(r chi.Router) {
@@ -76,16 +81,16 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
req.Path = "/" + req.Path
}
if !io.Exists(req.Path) {
service.Error(w, http.StatusUnprocessableEntity, "目录不存在")
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("directory %s does not exist", req.Path))
return
}
if err = io.Chmod(req.Path, 0755); err != nil {
service.Error(w, http.StatusUnprocessableEntity, "修改目录权限失败")
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if err = io.Chown(req.Path, "www", "www"); err != nil {
service.Error(w, http.StatusUnprocessableEntity, "修改目录权限失败")
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
return
}
if _, err = shell.Execf(`yes '%s' | pure-pw useradd '%s' -u www -g www -d '%s'`, req.Password, req.Username, req.Path); err != nil {
@@ -144,7 +149,7 @@ func (s *App) ChangePassword(w http.ResponseWriter, r *http.Request) {
func (s *App) GetPort(w http.ResponseWriter, r *http.Request) {
port, err := shell.Execf(`cat %s/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`, app.Root)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取PureFtpd端口失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get port: %v", err))
return
}

View File

@@ -7,6 +7,7 @@ import (
"strings"
"github.com/go-chi/chi/v5"
"github.com/leonelquinteros/gotext"
"github.com/tnb-labs/panel/internal/app"
"github.com/tnb-labs/panel/internal/service"
@@ -16,10 +17,14 @@ import (
"github.com/tnb-labs/panel/pkg/types"
)
type App struct{}
type App struct {
t *gotext.Locale
}
func NewApp() *App {
return &App{}
func NewApp(t *gotext.Locale) *App {
return &App{
t: t,
}
}
func (s *App) Route(r chi.Router) {
@@ -31,7 +36,7 @@ func (s *App) Route(r chi.Router) {
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
status, err := systemctl.Status("redis")
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取 Redis 状态失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get redis status: %v", err))
return
}
if !status {
@@ -54,7 +59,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
raw, err := shell.Execf("redis-cli%s info", withPassword)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取 Redis 负载失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get redis info: %v", err))
return
}
@@ -69,19 +74,19 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
}
data := []types.NV{
{Name: "TCP 端口", Value: dataRaw["tcp_port"]},
{Name: "已运行天数", Value: dataRaw["uptime_in_days"]},
{Name: "连接的客户端数", Value: dataRaw["connected_clients"]},
{Name: "已分配的内存总量", Value: dataRaw["used_memory_human"]},
{Name: "占用内存总量", Value: dataRaw["used_memory_rss_human"]},
{Name: "占用内存峰值", Value: dataRaw["used_memory_peak_human"]},
{Name: "内存碎片比率", Value: dataRaw["mem_fragmentation_ratio"]},
{Name: "运行以来连接过的客户端的总数", Value: dataRaw["total_connections_received"]},
{Name: "运行以来执行过的命令的总数", Value: dataRaw["total_commands_processed"]},
{Name: "每秒执行的命令数", Value: dataRaw["instantaneous_ops_per_sec"]},
{Name: "查找数据库键成功次数", Value: dataRaw["keyspace_hits"]},
{Name: "查找数据库键失败次数", Value: dataRaw["keyspace_misses"]},
{Name: "最近一次 fork() 操作耗费的毫秒数", Value: dataRaw["latest_fork_usec"]},
{Name: s.t.Get("TCP Port"), Value: dataRaw["tcp_port"]},
{Name: s.t.Get("Uptime in Days"), Value: dataRaw["uptime_in_days"]},
{Name: s.t.Get("Connected Clients"), Value: dataRaw["connected_clients"]},
{Name: s.t.Get("Total Allocated Memory"), Value: dataRaw["used_memory_human"]},
{Name: s.t.Get("Total Memory Usage"), Value: dataRaw["used_memory_rss_human"]},
{Name: s.t.Get("Peak Memory Usage"), Value: dataRaw["used_memory_peak_human"]},
{Name: s.t.Get("Memory Fragmentation Ratio"), Value: dataRaw["mem_fragmentation_ratio"]},
{Name: s.t.Get("Total Connections Received"), Value: dataRaw["total_connections_received"]},
{Name: s.t.Get("Total Commands Processed"), Value: dataRaw["total_commands_processed"]},
{Name: s.t.Get("Commands Per Second"), Value: dataRaw["instantaneous_ops_per_sec"]},
{Name: s.t.Get("Keyspace Hits"), Value: dataRaw["keyspace_hits"]},
{Name: s.t.Get("Keyspace Misses"), Value: dataRaw["keyspace_misses"]},
{Name: s.t.Get("Latest Fork Time (ms)"), Value: dataRaw["latest_fork_usec"]},
}
service.Success(w, data)

View File

@@ -9,6 +9,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-rat/chix"
"github.com/go-rat/utils/str"
"github.com/leonelquinteros/gotext"
"github.com/tnb-labs/panel/internal/service"
"github.com/tnb-labs/panel/pkg/io"
@@ -16,10 +17,14 @@ import (
"github.com/tnb-labs/panel/pkg/systemctl"
)
type App struct{}
type App struct {
t *gotext.Locale
}
func NewApp() *App {
return &App{}
func NewApp(t *gotext.Locale) *App {
return &App{
t: t,
}
}
func (s *App) Route(r chi.Router) {
@@ -74,7 +79,7 @@ func (s *App) List(w http.ResponseWriter, r *http.Request) {
currentModule.AuthUser = value
currentModule.Secret, err = shell.Execf(`grep -E '^%s:.*$' /etc/rsyncd.secrets | awk -F ':' '{print $2}'`, currentModule.AuthUser)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取模块%s的密钥失败", currentModule.AuthUser)
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get the secret key for module %s", currentModule.AuthUser))
return
}
case "hosts allow":
@@ -109,7 +114,7 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
return
}
if strings.Contains(config, "["+req.Name+"]") {
service.Error(w, http.StatusUnprocessableEntity, "模块%s已存在", req.Name)
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("module %s already exists", req.Name))
return
}
@@ -154,7 +159,7 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
return
}
if !strings.Contains(config, "["+req.Name+"]") {
service.Error(w, http.StatusUnprocessableEntity, "模块%s不存在", req.Name)
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("module %s does not exist", req.Name))
return
}
@@ -196,7 +201,7 @@ func (s *App) Update(w http.ResponseWriter, r *http.Request) {
return
}
if !strings.Contains(config, "["+req.Name+"]") {
service.Error(w, http.StatusUnprocessableEntity, "模块%s不存在", req.Name)
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("module %s does not exist", req.Name))
return
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-rat/chix"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"github.com/tnb-labs/panel/internal/biz"
@@ -18,11 +19,13 @@ import (
)
type App struct {
t *gotext.Locale
settingRepo biz.SettingRepo
}
func NewApp(setting biz.SettingRepo) *App {
func NewApp(t *gotext.Locale, setting biz.SettingRepo) *App {
return &App{
t: t,
settingRepo: setting,
}
}
@@ -38,12 +41,12 @@ func (s *App) List(w http.ResponseWriter, r *http.Request) {
var s3fsList []Mount
list, err := s.settingRepo.Get("s3fs", "[]")
if err != nil {
service.Error(w, http.StatusInternalServerError, "failed to get s3fs list")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err))
return
}
if err = json.Unmarshal([]byte(list), &s3fsList); err != nil {
service.Error(w, http.StatusInternalServerError, "failed to get s3fs list")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err))
return
}
@@ -65,36 +68,36 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
// 检查下地域节点中是否包含bucket如果包含了肯定是错误的
if strings.Contains(req.URL, req.Bucket) {
service.Error(w, http.StatusUnprocessableEntity, "endpoint should not contain bucket")
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("endpoint should not contain bucket"))
return
}
// 检查挂载目录是否存在且为空
if !io.Exists(req.Path) {
if err = os.MkdirAll(req.Path, 0755); err != nil {
service.Error(w, http.StatusUnprocessableEntity, "failed to create mount path: %v", err)
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("failed to create mount path: %v", err))
return
}
}
if !io.Empty(req.Path) {
service.Error(w, http.StatusUnprocessableEntity, "mount path is not empty")
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("mount path is not empty"))
return
}
var s3fsList []Mount
list, err := s.settingRepo.Get("s3fs", "[]")
if err != nil {
service.Error(w, http.StatusInternalServerError, "failed to get s3fs list")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err))
return
}
if err = json.Unmarshal([]byte(list), &s3fsList); err != nil {
service.Error(w, http.StatusInternalServerError, "failed to get s3fs list")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err))
return
}
for _, s := range s3fsList {
if s.Path == req.Path {
service.Error(w, http.StatusUnprocessableEntity, "mount path already exists")
for _, item := range s3fsList {
if item.Path == req.Path {
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("mount path already exists"))
return
}
}
@@ -102,7 +105,7 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
id := time.Now().UnixMicro()
password := req.Ak + ":" + req.Sk
if err = io.Write("/etc/passwd-s3fs-"+cast.ToString(id), password, 0600); err != nil {
service.Error(w, http.StatusInternalServerError, "failed to create passwd file: %v", err)
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to create passwd file: %v", err))
return
}
if _, err = shell.Execf(`echo 's3fs#%s %s fuse _netdev,allow_other,nonempty,url=%s,passwd_file=/etc/passwd-s3fs-%s 0 0' >> /etc/fstab`, req.Bucket, req.Path, req.URL, cast.ToString(id)); err != nil {
@@ -111,12 +114,12 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
}
if _, err = shell.Execf("mount -a"); err != nil {
_, _ = shell.Execf(`sed -i 's@^s3fs#%s\s%s.*$@@g' /etc/fstab`, req.Bucket, req.Path)
service.Error(w, http.StatusInternalServerError, "invalid /etc/fstab: %v", err)
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if _, err = shell.Execf(`df -h | grep '%s'`, req.Path); err != nil {
_, _ = shell.Execf(`sed -i 's@^s3fs#%s\s%s.*$@@g' /etc/fstab`, req.Bucket, req.Path)
service.Error(w, http.StatusInternalServerError, "mount failed: %v", err)
service.Error(w, http.StatusInternalServerError, s.t.Get("mount failed: %v", err))
return
}
@@ -128,12 +131,12 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
})
encoded, err := json.Marshal(s3fsList)
if err != nil {
service.Error(w, http.StatusInternalServerError, "failed to add s3fs mount")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to add s3fs mount: %v", err))
return
}
if err = s.settingRepo.Set("s3fs", string(encoded)); err != nil {
service.Error(w, http.StatusInternalServerError, "failed to add s3fs mount")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to add s3fs mount: %v", err))
return
}
@@ -151,11 +154,11 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
var s3fsList []Mount
list, err := s.settingRepo.Get("s3fs", "[]")
if err != nil {
service.Error(w, http.StatusInternalServerError, "failed to get s3fs list")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err))
return
}
if err = json.Unmarshal([]byte(list), &s3fsList); err != nil {
service.Error(w, http.StatusInternalServerError, "failed to get s3fs list")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err))
return
}
@@ -167,7 +170,7 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
}
}
if mount.ID == 0 {
service.Error(w, http.StatusUnprocessableEntity, "mount not found")
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("mount not found"))
return
}
@@ -184,7 +187,7 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
return
}
if _, err = shell.Execf("mount -a"); err != nil {
service.Error(w, http.StatusInternalServerError, "invalid /etc/fstab: %v", err)
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
if err = io.Remove("/etc/passwd-s3fs-" + cast.ToString(mount.ID)); err != nil {
@@ -200,11 +203,11 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
}
encoded, err := json.Marshal(newS3fsList)
if err != nil {
service.Error(w, http.StatusInternalServerError, "failed to delete s3fs mount")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to delete s3fs mount: %v", err))
return
}
if err = s.settingRepo.Set("s3fs", string(encoded)); err != nil {
service.Error(w, http.StatusInternalServerError, "failed to delete s3fs mount")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to delete s3fs mount: %v", err))
return
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-rat/chix"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"github.com/tnb-labs/panel/internal/service"
@@ -16,10 +17,11 @@ import (
)
type App struct {
t *gotext.Locale
name string
}
func NewApp() *App {
func NewApp(t *gotext.Locale) *App {
var name string
if os.IsRHEL() {
name = "supervisord"
@@ -28,6 +30,7 @@ func NewApp() *App {
}
return &App{
t: t,
name: name,
}
}
@@ -102,7 +105,7 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
}
if err = systemctl.Restart(s.name); err != nil {
service.Error(w, http.StatusInternalServerError, "重启 %s 服务失败: %v", s.name, err)
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to restart %s: %v", s.name, err))
return
}
@@ -156,8 +159,8 @@ func (s *App) StartProcess(w http.ResponseWriter, r *http.Request) {
return
}
if _, err = shell.Execf(`supervisorctl start %s`, req.Process); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
if out, err := shell.Execf(`supervisorctl start %s`, req.Process); err != nil {
service.Error(w, http.StatusInternalServerError, "%v %s", err, out)
return
}
@@ -172,8 +175,8 @@ func (s *App) StopProcess(w http.ResponseWriter, r *http.Request) {
return
}
if _, err = shell.Execf(`supervisorctl stop %s`, req.Process); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
if out, err := shell.Execf(`supervisorctl stop %s`, req.Process); err != nil {
service.Error(w, http.StatusInternalServerError, "%v %s", err, out)
return
}
@@ -188,8 +191,8 @@ func (s *App) RestartProcess(w http.ResponseWriter, r *http.Request) {
return
}
if _, err = shell.Execf(`supervisorctl restart %s`, req.Process); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
if out, err := shell.Execf(`supervisorctl restart %s`, req.Process); err != nil {
service.Error(w, http.StatusInternalServerError, "%v %s", err, out)
return
}
@@ -212,7 +215,7 @@ func (s *App) ProcessLog(w http.ResponseWriter, r *http.Request) {
}
if err != nil {
service.Error(w, http.StatusInternalServerError, "无法从进程 %s 的配置文件中获取日志路径", req.Process)
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err))
return
}
@@ -235,7 +238,7 @@ func (s *App) ClearProcessLog(w http.ResponseWriter, r *http.Request) {
}
if err != nil {
service.Error(w, http.StatusInternalServerError, "无法从进程 %s 的配置文件中获取日志路径", req.Process)
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err))
return
}
@@ -343,8 +346,8 @@ func (s *App) DeleteProcess(w http.ResponseWriter, r *http.Request) {
return
}
if _, err = shell.Execf(`supervisorctl stop '%s'`, req.Process); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
if out, err := shell.Execf(`supervisorctl stop '%s'`, req.Process); err != nil {
service.Error(w, http.StatusInternalServerError, "%v %s", err, out)
return
}
@@ -352,7 +355,7 @@ func (s *App) DeleteProcess(w http.ResponseWriter, r *http.Request) {
if os.IsRHEL() {
logPath, err = shell.Execf(`cat '/etc/supervisord.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, req.Process)
if err != nil {
service.Error(w, http.StatusInternalServerError, "无法从进程 %s 的配置文件中获取日志路径", req.Process)
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err))
return
}
if err = io.Remove(`/etc/supervisord.d/` + req.Process + `.conf`); err != nil {
@@ -362,7 +365,7 @@ func (s *App) DeleteProcess(w http.ResponseWriter, r *http.Request) {
} else {
logPath, err = shell.Execf(`cat '/etc/supervisor/conf.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, req.Process)
if err != nil {
service.Error(w, http.StatusInternalServerError, "无法从进程 %s 的配置文件中获取日志路径", req.Process)
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err))
return
}
if err = io.Remove(`/etc/supervisor/conf.d/` + req.Process + `.conf`); err != nil {

View File

@@ -8,6 +8,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-rat/chix"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"github.com/tnb-labs/panel/internal/app"
@@ -19,10 +20,14 @@ import (
"github.com/tnb-labs/panel/pkg/types"
)
type App struct{}
type App struct {
t *gotext.Locale
}
func NewApp() *App {
return &App{}
func NewApp(t *gotext.Locale) *App {
return &App{
t: t,
}
}
func (s *App) Route(r chi.Router) {
@@ -48,13 +53,9 @@ func (s *App) GetDNS(w http.ResponseWriter, r *http.Request) {
service.Error(w, http.StatusInternalServerError, "%v", err)
return
}
match := regexp.MustCompile(`nameserver\s+(\S+)`).FindAllStringSubmatch(raw, -1)
if len(match) == 0 {
service.Error(w, http.StatusInternalServerError, "找不到 DNS 信息")
return
}
var dns []string
match := regexp.MustCompile(`nameserver\s+(\S+)`).FindAllStringSubmatch(raw, -1)
dns := make([]string, 0)
for _, m := range match {
dns = append(dns, m[1])
}
@@ -75,7 +76,7 @@ func (s *App) UpdateDNS(w http.ResponseWriter, r *http.Request) {
dns += "nameserver " + req.DNS2 + "\n"
if err := io.Write("/etc/resolv.conf", dns, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "写入 DNS 信息失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to update DNS: %v", err))
return
}
@@ -89,7 +90,7 @@ func (s *App) GetSWAP(w http.ResponseWriter, r *http.Request) {
if io.Exists(filepath.Join(app.Root, "swap")) {
file, err := io.FileInfo(filepath.Join(app.Root, "swap"))
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取 SWAP 信息失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get SWAP: %v", err))
return
}
@@ -102,7 +103,7 @@ func (s *App) GetSWAP(w http.ResponseWriter, r *http.Request) {
raw, err := shell.Execf("free | grep Swap")
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取 SWAP 信息失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get SWAP: %v", err))
return
}
@@ -147,11 +148,11 @@ func (s *App) UpdateSWAP(w http.ResponseWriter, r *http.Request) {
var free string
free, err = shell.Execf("df -k %s | awk '{print $4}' | tail -n 1", app.Root)
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取硬盘空间失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get disk space: %v", err))
return
}
if cast.ToInt64(free)*1024 < req.Size*1024*1024 {
service.Error(w, http.StatusInternalServerError, "硬盘空间不足,当前剩余%s", tools.FormatBytes(cast.ToFloat64(free)))
service.Error(w, http.StatusInternalServerError, s.t.Get("disk space is insufficient, current free %s", tools.FormatBytes(cast.ToFloat64(free))))
return
}
@@ -171,7 +172,7 @@ func (s *App) UpdateSWAP(w http.ResponseWriter, r *http.Request) {
return
}
if err = io.Chmod(filepath.Join(app.Root, "swap"), 0600); err != nil {
service.Error(w, http.StatusInternalServerError, "设置 SWAP 权限失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set SWAP permission: %v", err))
return
}
}
@@ -192,19 +193,18 @@ func (s *App) UpdateSWAP(w http.ResponseWriter, r *http.Request) {
func (s *App) GetTimezone(w http.ResponseWriter, r *http.Request) {
raw, err := shell.Execf("timedatectl | grep zone")
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取时区信息失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get timezone: %v", err))
return
}
match := regexp.MustCompile(`zone:\s+(\S+)`).FindStringSubmatch(raw)
if len(match) == 0 {
service.Error(w, http.StatusInternalServerError, "找不到时区信息")
return
match = append(match, "")
}
zonesRaw, err := shell.Execf("timedatectl list-timezones")
if err != nil {
service.Error(w, http.StatusInternalServerError, "获取时区列表失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get available timezones: %v", err))
return
}
zones := strings.Split(zonesRaw, "\n")
@@ -289,7 +289,7 @@ func (s *App) UpdateHostname(w http.ResponseWriter, r *http.Request) {
if _, err = shell.Execf("hostnamectl set-hostname '%s'", req.Hostname); err != nil {
// 直接写 /etc/hostname
if err = io.Write("/etc/hostname", req.Hostname, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "写入主机名失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set hostname: %v", err))
return
}
}
@@ -312,7 +312,7 @@ func (s *App) UpdateHosts(w http.ResponseWriter, r *http.Request) {
}
if err = io.Write("/etc/hosts", req.Hosts, 0644); err != nil {
service.Error(w, http.StatusInternalServerError, "写入 hosts 信息失败")
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set hosts: %v", err))
return
}
@@ -329,7 +329,7 @@ func (s *App) UpdateRootPassword(w http.ResponseWriter, r *http.Request) {
req.Password = strings.ReplaceAll(req.Password, `'`, `\'`)
if _, err = shell.Execf(`yes '%s' | passwd root`, req.Password); err != nil {
service.Error(w, http.StatusInternalServerError, "%v", err)
service.Error(w, http.StatusInternalServerError, "%v", s.t.Get("failed to set root password: %v", err))
return
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/knadh/koanf/v2"
"github.com/leonelquinteros/gotext"
"github.com/tnb-labs/panel/pkg/embed"
)

View File

@@ -11,6 +11,40 @@ msgstr ""
msgid "AUTHOR"
msgstr ""
#: internal/apps/php/app.go:130
msgid "Accepted Connections"
msgstr ""
#: internal/apps/mysql/app.go:121
msgid "Active Connections"
msgstr ""
#: internal/apps/php/app.go:135
msgid "Active Processes"
msgstr ""
#: internal/apps/nginx/app.go:120
msgid "Active connections"
msgstr ""
#: internal/apps/php/app.go:127
msgid "Application Pool"
msgstr ""
#: internal/apps/mysql/app.go:120
#: internal/apps/mysql/app.go:139
msgid "Bytes Received"
msgstr ""
#: internal/apps/mysql/app.go:119
#: internal/apps/mysql/app.go:139
msgid "Bytes Sent"
msgstr ""
#: internal/apps/php/app.go:352
msgid "Bzip2 is a library for compressing and decompressing files"
msgstr ""
#: internal/bootstrap/cli.go:24
msgid "CATEGORY"
msgstr ""
@@ -24,19 +58,123 @@ msgstr ""
msgid "COPYRIGHT"
msgstr ""
#: internal/apps/php/app.go:392
msgid "Calendar is a library for handling dates"
msgstr ""
#: internal/bootstrap/cli.go:17
#: internal/bootstrap/cli.go:25
msgid "DESCRIPTION"
msgstr ""
#: internal/apps/php/app.go:382
msgid "Enchant is a spell-checking library"
msgstr ""
#: internal/apps/php/app.go:362
msgid "Event is a library for handling events"
msgstr ""
#: internal/apps/php/app.go:317
msgid "Exif is a library for reading and writing image metadata"
msgstr ""
#: internal/apps/php/app.go:287
msgid "Fileinfo is a library used to identify file types"
msgstr ""
#: internal/bootstrap/cli.go:34
msgid "Forumhttps://bbs.haozi.net"
msgstr ""
#: internal/apps/mysql/app.go:127
msgid "Full Joins without Index"
msgstr ""
#: internal/apps/mysql/app.go:128
msgid "Full Range Joins without Index"
msgstr ""
#: internal/bootstrap/cli.go:20
msgid "GLOBAL OPTIONS"
msgstr ""
#: internal/apps/php/app.go:397
msgid "GMP is a library for handling large integers"
msgstr ""
#: internal/apps/php/app.go:417
msgid "Gettext is a library for handling multilingual support"
msgstr ""
#: internal/apps/php/app.go:342
msgid "IMAP extension allows PHP to read, search, delete, download, and manage emails"
msgstr ""
#: internal/apps/php/app.go:134
msgid "Idle Processes"
msgstr ""
#: internal/apps/php/app.go:297
msgid "Igbinary is a library for serializing and deserializing data"
msgstr ""
#: internal/apps/php/app.go:312
msgid "ImageMagick is free software for creating, editing, and composing images"
msgstr ""
#: internal/apps/mysql/app.go:123
msgid "Index Hit Rate"
msgstr ""
#: internal/apps/mysql/app.go:124
msgid "Innodb Index Hit Rate"
msgstr ""
#: internal/apps/php/app.go:239
msgid "Install PHP-%d %s extension"
msgstr ""
#: internal/apps/php/app.go:412
msgid "Intl is a library for handling internationalization and localization"
msgstr ""
#: internal/apps/php/app.go:377
msgid "LDAP is a protocol for accessing directory services"
msgstr ""
#: internal/apps/php/app.go:131
msgid "Listen Queue"
msgstr ""
#: internal/apps/php/app.go:133
msgid "Listen Queue Length"
msgstr ""
#: internal/apps/php/app.go:137
msgid "Max Active Processes"
msgstr ""
#: internal/apps/php/app.go:138
msgid "Max Children Reached"
msgstr ""
#: internal/apps/php/app.go:132
msgid "Max Listen Queue"
msgstr ""
#: internal/apps/php/app.go:307
msgid "Memcached is a driver for connecting to Memcached servers"
msgstr ""
#: internal/apps/nginx/app.go:113
msgid "Memory"
msgstr ""
#: internal/apps/mysql/app.go:88
msgid "MySQL root password is empty"
msgstr ""
#: internal/bootstrap/cli.go:14
#: internal/bootstrap/cli.go:22
#: internal/bootstrap/cli.go:27
@@ -48,6 +186,30 @@ msgstr ""
msgid "OPTIONS"
msgstr ""
#: internal/apps/php/app.go:292
msgid "OPcache stores precompiled PHP script bytecode in shared memory to improve PHP performance"
msgstr ""
#: internal/apps/mysql/app.go:126
msgid "Open Tables"
msgstr ""
#: internal/apps/mysql/app.go:122
msgid "Peak Connections"
msgstr ""
#: internal/apps/php/app.go:302
msgid "PhpRedis connects to and operates on data in Redis databases (requires the igbinary extension installed above)"
msgstr ""
#: internal/apps/php/app.go:128
msgid "Process Manager"
msgstr ""
#: internal/apps/php/app.go:387
msgid "Pspell is a spell-checking library"
msgstr ""
#: internal/bootstrap/cli.go:35
msgid "QQ Group12370907"
msgstr ""
@@ -56,6 +218,98 @@ msgstr ""
msgid "Rat Panel CLI Tool"
msgstr ""
#: internal/apps/nginx/app.go:144
msgid "Reading"
msgstr ""
#: internal/apps/php/app.go:367
msgid "Readline is a library for processing text"
msgstr ""
#: internal/apps/mysql/app.go:118
msgid "Rollbacks per Second"
msgstr ""
#: internal/apps/php/app.go:372
msgid "SNMP is a protocol for network management"
msgstr ""
#: internal/apps/php/app.go:357
msgid "SSH2 is a library for connecting to SSH servers"
msgstr ""
#: internal/apps/php/app.go:139
msgid "Slow Requests"
msgstr ""
#: internal/apps/mysql/app.go:130
msgid "Sort Merge Passes"
msgstr ""
#: internal/apps/php/app.go:129
msgid "Start Time"
msgstr ""
#: internal/apps/mysql/app.go:129
msgid "Subqueries without Index"
msgstr ""
#: internal/apps/php/app.go:477
msgid "Swoole is a PHP extension for building high-performance asynchronous concurrent servers"
msgstr ""
#: internal/apps/php/app.go:486
msgid "Swow is a PHP extension for building high-performance asynchronous concurrent servers"
msgstr ""
#: internal/apps/php/app.go:457
msgid "Sysvmsg is a library for handling System V message queues"
msgstr ""
#: internal/apps/php/app.go:462
msgid "Sysvsem is a library for handling System V semaphores"
msgstr ""
#: internal/apps/php/app.go:467
msgid "Sysvshm is a library for handling System V shared memory"
msgstr ""
#: internal/apps/mysql/app.go:131
msgid "Table Locks Waited"
msgstr ""
#: internal/apps/mysql/app.go:125
msgid "Temporary Tables Created on Disk"
msgstr ""
#: internal/apps/mysql/app.go:116
msgid "Total Connections"
msgstr ""
#: internal/apps/php/app.go:136
msgid "Total Processes"
msgstr ""
#: internal/apps/mysql/app.go:115
msgid "Total Queries"
msgstr ""
#: internal/apps/nginx/app.go:128
msgid "Total connections"
msgstr ""
#: internal/apps/nginx/app.go:132
msgid "Total handshakes"
msgstr ""
#: internal/apps/nginx/app.go:136
msgid "Total requests"
msgstr ""
#: internal/apps/mysql/app.go:117
msgid "Transactions per Second"
msgstr ""
#: internal/bootstrap/cli.go:15
#: internal/bootstrap/cli.go:23
#: internal/bootstrap/cli.go:28
@@ -63,10 +317,170 @@ msgstr ""
msgid "USAGE"
msgstr ""
#: internal/apps/php/app.go:270
msgid "Uninstall PHP-%d %s extension"
msgstr ""
#: internal/apps/mysql/app.go:114
msgid "Uptime"
msgstr ""
#: internal/bootstrap/cli.go:16
msgid "VERSION"
msgstr ""
#: internal/apps/nginx/app.go:152
msgid "Waiting"
msgstr ""
#: internal/bootstrap/cli.go:33
msgid "Websitehttps://panel.haozi.net"
msgstr ""
#: internal/apps/nginx/app.go:102
msgid "Workers"
msgstr ""
#: internal/apps/nginx/app.go:148
msgid "Writing"
msgstr ""
#: internal/apps/php/app.go:402
msgid "XLSWriter is a high-performance library for reading and writing Excel files"
msgstr ""
#: internal/apps/php/app.go:407
msgid "XSL is a library for processing XML documents"
msgstr ""
#: internal/apps/php/app.go:347
msgid "Zip is a library for handling ZIP files"
msgstr ""
#: internal/apps/php/app.go:228
#: internal/apps/php/app.go:259
msgid "extension %s does not exist"
msgstr ""
#: internal/apps/memcached/app.go:37
msgid "failed to get Memcached status: %v"
msgstr ""
#: internal/apps/mysql/app.go:104
msgid "failed to get MySQL status: %v"
msgstr ""
#: internal/apps/fail2ban/app.go:268
msgid "failed to get banned ip list"
msgstr ""
#: internal/apps/fail2ban/app.go:258
msgid "failed to get current banned list"
msgstr ""
#: internal/apps/nginx/app.go:108
#: internal/apps/nginx/app.go:98
msgid "failed to get nginx workers: %v"
msgstr ""
#: internal/apps/fail2ban/app.go:263
msgid "failed to get total banned list"
msgstr ""
#: internal/apps/mysql/app.go:188
#: internal/apps/mysql/app.go:83
msgid "failed to load MySQL root password: %v"
msgstr ""
#: internal/apps/fail2ban/app.go:327
#: internal/apps/fail2ban/app.go:355
msgid "failed to parse the ignoreip of fail2ban"
msgstr ""
#: internal/apps/memcached/app.go:77
msgid "failed to read from Memcached: %v"
msgstr ""
#: internal/apps/nginx/app.go:65
msgid "failed to reload nginx: %v"
msgstr ""
#: internal/apps/mysql/app.go:72
msgid "failed to restart MySQL: %v"
msgstr ""
#: internal/apps/mysql/app.go:99
msgid "failed to set MYSQL_PWD env: %v"
msgstr ""
#: internal/apps/memcached/app.go:56
msgid "failed to write to Memcached: %v"
msgstr ""
#: internal/apps/php/app.go:422
msgid "gRPC is a high-performance, open-source, and general-purpose RPC framework"
msgstr ""
#: internal/apps/fail2ban/app.go:184
msgid "get service port failed, please check if it is installed"
msgstr ""
#: internal/apps/php/app.go:472
msgid "ionCube is a professional-grade PHP encryption and decryption tool (must be installed after OPcache)"
msgstr ""
#: internal/apps/php/app.go:327
msgid "pdo_pgsql is a PDO driver for connecting to PostgreSQL (requires PostgreSQL installed)"
msgstr ""
#: internal/apps/php/app.go:337
msgid "pdo_sqlsrv is a PDO driver for connecting to SQL Server"
msgstr ""
#: internal/apps/php/app.go:322
msgid "pgsql is a driver for connecting to PostgreSQL (requires PostgreSQL installed)"
msgstr ""
#: internal/apps/php/app.go:427
msgid "protobuf is a library for serializing and deserializing data"
msgstr ""
#: internal/apps/php/app.go:432
msgid "rdkafka is a library for connecting to Apache Kafka"
msgstr ""
#: internal/apps/fail2ban/app.go:109
msgid "rule already exists"
msgstr ""
#: internal/apps/fail2ban/app.go:228
msgid "rule not found"
msgstr ""
#: internal/apps/php/app.go:332
msgid "sqlsrv is a driver for connecting to SQL Server"
msgstr ""
#: internal/apps/fail2ban/app.go:180
msgid "unknown service"
msgstr ""
#: internal/apps/benchmark/app.go:81
msgid "unknown test type"
msgstr ""
#: internal/apps/php/app.go:442
msgid "xdebug is a library for debugging and profiling PHP code"
msgstr ""
#: internal/apps/php/app.go:437
msgid "xhprof is a library for performance profiling"
msgstr ""
#: internal/apps/php/app.go:447
msgid "yaml is a library for handling YAML"
msgstr ""
#: internal/apps/php/app.go:452
msgid "zstd is a library for compressing and decompressing files"
msgstr ""

View File

@@ -11,6 +11,40 @@ msgstr ""
msgid "AUTHOR"
msgstr ""
#: internal/apps/php/app.go:130
msgid "Accepted Connections"
msgstr ""
#: internal/apps/mysql/app.go:121
msgid "Active Connections"
msgstr ""
#: internal/apps/php/app.go:135
msgid "Active Processes"
msgstr ""
#: internal/apps/nginx/app.go:120
msgid "Active connections"
msgstr ""
#: internal/apps/php/app.go:127
msgid "Application Pool"
msgstr ""
#: internal/apps/mysql/app.go:120
#: internal/apps/mysql/app.go:139
msgid "Bytes Received"
msgstr ""
#: internal/apps/mysql/app.go:119
#: internal/apps/mysql/app.go:139
msgid "Bytes Sent"
msgstr ""
#: internal/apps/php/app.go:352
msgid "Bzip2 is a library for compressing and decompressing files"
msgstr ""
#: internal/bootstrap/cli.go:24
msgid "CATEGORY"
msgstr ""
@@ -24,19 +58,123 @@ msgstr ""
msgid "COPYRIGHT"
msgstr ""
#: internal/apps/php/app.go:392
msgid "Calendar is a library for handling dates"
msgstr ""
#: internal/bootstrap/cli.go:17
#: internal/bootstrap/cli.go:25
msgid "DESCRIPTION"
msgstr ""
#: internal/apps/php/app.go:382
msgid "Enchant is a spell-checking library"
msgstr ""
#: internal/apps/php/app.go:362
msgid "Event is a library for handling events"
msgstr ""
#: internal/apps/php/app.go:317
msgid "Exif is a library for reading and writing image metadata"
msgstr ""
#: internal/apps/php/app.go:287
msgid "Fileinfo is a library used to identify file types"
msgstr ""
#: internal/bootstrap/cli.go:34
msgid "Forumhttps://bbs.haozi.net"
msgstr ""
#: internal/apps/mysql/app.go:127
msgid "Full Joins without Index"
msgstr ""
#: internal/apps/mysql/app.go:128
msgid "Full Range Joins without Index"
msgstr ""
#: internal/bootstrap/cli.go:20
msgid "GLOBAL OPTIONS"
msgstr ""
#: internal/apps/php/app.go:397
msgid "GMP is a library for handling large integers"
msgstr ""
#: internal/apps/php/app.go:417
msgid "Gettext is a library for handling multilingual support"
msgstr ""
#: internal/apps/php/app.go:342
msgid "IMAP extension allows PHP to read, search, delete, download, and manage emails"
msgstr ""
#: internal/apps/php/app.go:134
msgid "Idle Processes"
msgstr ""
#: internal/apps/php/app.go:297
msgid "Igbinary is a library for serializing and deserializing data"
msgstr ""
#: internal/apps/php/app.go:312
msgid "ImageMagick is free software for creating, editing, and composing images"
msgstr ""
#: internal/apps/mysql/app.go:123
msgid "Index Hit Rate"
msgstr ""
#: internal/apps/mysql/app.go:124
msgid "Innodb Index Hit Rate"
msgstr ""
#: internal/apps/php/app.go:239
msgid "Install PHP-%d %s extension"
msgstr ""
#: internal/apps/php/app.go:412
msgid "Intl is a library for handling internationalization and localization"
msgstr ""
#: internal/apps/php/app.go:377
msgid "LDAP is a protocol for accessing directory services"
msgstr ""
#: internal/apps/php/app.go:131
msgid "Listen Queue"
msgstr ""
#: internal/apps/php/app.go:133
msgid "Listen Queue Length"
msgstr ""
#: internal/apps/php/app.go:137
msgid "Max Active Processes"
msgstr ""
#: internal/apps/php/app.go:138
msgid "Max Children Reached"
msgstr ""
#: internal/apps/php/app.go:132
msgid "Max Listen Queue"
msgstr ""
#: internal/apps/php/app.go:307
msgid "Memcached is a driver for connecting to Memcached servers"
msgstr ""
#: internal/apps/nginx/app.go:113
msgid "Memory"
msgstr ""
#: internal/apps/mysql/app.go:88
msgid "MySQL root password is empty"
msgstr ""
#: internal/bootstrap/cli.go:14
#: internal/bootstrap/cli.go:22
#: internal/bootstrap/cli.go:27
@@ -48,6 +186,30 @@ msgstr ""
msgid "OPTIONS"
msgstr ""
#: internal/apps/php/app.go:292
msgid "OPcache stores precompiled PHP script bytecode in shared memory to improve PHP performance"
msgstr ""
#: internal/apps/mysql/app.go:126
msgid "Open Tables"
msgstr ""
#: internal/apps/mysql/app.go:122
msgid "Peak Connections"
msgstr ""
#: internal/apps/php/app.go:302
msgid "PhpRedis connects to and operates on data in Redis databases (requires the igbinary extension installed above)"
msgstr ""
#: internal/apps/php/app.go:128
msgid "Process Manager"
msgstr ""
#: internal/apps/php/app.go:387
msgid "Pspell is a spell-checking library"
msgstr ""
#: internal/bootstrap/cli.go:35
msgid "QQ Group12370907"
msgstr ""
@@ -56,6 +218,98 @@ msgstr ""
msgid "Rat Panel CLI Tool"
msgstr ""
#: internal/apps/nginx/app.go:144
msgid "Reading"
msgstr ""
#: internal/apps/php/app.go:367
msgid "Readline is a library for processing text"
msgstr ""
#: internal/apps/mysql/app.go:118
msgid "Rollbacks per Second"
msgstr ""
#: internal/apps/php/app.go:372
msgid "SNMP is a protocol for network management"
msgstr ""
#: internal/apps/php/app.go:357
msgid "SSH2 is a library for connecting to SSH servers"
msgstr ""
#: internal/apps/php/app.go:139
msgid "Slow Requests"
msgstr ""
#: internal/apps/mysql/app.go:130
msgid "Sort Merge Passes"
msgstr ""
#: internal/apps/php/app.go:129
msgid "Start Time"
msgstr ""
#: internal/apps/mysql/app.go:129
msgid "Subqueries without Index"
msgstr ""
#: internal/apps/php/app.go:477
msgid "Swoole is a PHP extension for building high-performance asynchronous concurrent servers"
msgstr ""
#: internal/apps/php/app.go:486
msgid "Swow is a PHP extension for building high-performance asynchronous concurrent servers"
msgstr ""
#: internal/apps/php/app.go:457
msgid "Sysvmsg is a library for handling System V message queues"
msgstr ""
#: internal/apps/php/app.go:462
msgid "Sysvsem is a library for handling System V semaphores"
msgstr ""
#: internal/apps/php/app.go:467
msgid "Sysvshm is a library for handling System V shared memory"
msgstr ""
#: internal/apps/mysql/app.go:131
msgid "Table Locks Waited"
msgstr ""
#: internal/apps/mysql/app.go:125
msgid "Temporary Tables Created on Disk"
msgstr ""
#: internal/apps/mysql/app.go:116
msgid "Total Connections"
msgstr ""
#: internal/apps/php/app.go:136
msgid "Total Processes"
msgstr ""
#: internal/apps/mysql/app.go:115
msgid "Total Queries"
msgstr ""
#: internal/apps/nginx/app.go:128
msgid "Total connections"
msgstr ""
#: internal/apps/nginx/app.go:132
msgid "Total handshakes"
msgstr ""
#: internal/apps/nginx/app.go:136
msgid "Total requests"
msgstr ""
#: internal/apps/mysql/app.go:117
msgid "Transactions per Second"
msgstr ""
#: internal/bootstrap/cli.go:15
#: internal/bootstrap/cli.go:23
#: internal/bootstrap/cli.go:28
@@ -63,10 +317,170 @@ msgstr ""
msgid "USAGE"
msgstr ""
#: internal/apps/php/app.go:270
msgid "Uninstall PHP-%d %s extension"
msgstr ""
#: internal/apps/mysql/app.go:114
msgid "Uptime"
msgstr ""
#: internal/bootstrap/cli.go:16
msgid "VERSION"
msgstr ""
#: internal/apps/nginx/app.go:152
msgid "Waiting"
msgstr ""
#: internal/bootstrap/cli.go:33
msgid "Websitehttps://panel.haozi.net"
msgstr ""
#: internal/apps/nginx/app.go:102
msgid "Workers"
msgstr ""
#: internal/apps/nginx/app.go:148
msgid "Writing"
msgstr ""
#: internal/apps/php/app.go:402
msgid "XLSWriter is a high-performance library for reading and writing Excel files"
msgstr ""
#: internal/apps/php/app.go:407
msgid "XSL is a library for processing XML documents"
msgstr ""
#: internal/apps/php/app.go:347
msgid "Zip is a library for handling ZIP files"
msgstr ""
#: internal/apps/php/app.go:228
#: internal/apps/php/app.go:259
msgid "extension %s does not exist"
msgstr ""
#: internal/apps/memcached/app.go:37
msgid "failed to get Memcached status: %v"
msgstr ""
#: internal/apps/mysql/app.go:104
msgid "failed to get MySQL status: %v"
msgstr ""
#: internal/apps/fail2ban/app.go:268
msgid "failed to get banned ip list"
msgstr ""
#: internal/apps/fail2ban/app.go:258
msgid "failed to get current banned list"
msgstr ""
#: internal/apps/nginx/app.go:108
#: internal/apps/nginx/app.go:98
msgid "failed to get nginx workers: %v"
msgstr ""
#: internal/apps/fail2ban/app.go:263
msgid "failed to get total banned list"
msgstr ""
#: internal/apps/mysql/app.go:188
#: internal/apps/mysql/app.go:83
msgid "failed to load MySQL root password: %v"
msgstr ""
#: internal/apps/fail2ban/app.go:327
#: internal/apps/fail2ban/app.go:355
msgid "failed to parse the ignoreip of fail2ban"
msgstr ""
#: internal/apps/memcached/app.go:77
msgid "failed to read from Memcached: %v"
msgstr ""
#: internal/apps/nginx/app.go:65
msgid "failed to reload nginx: %v"
msgstr ""
#: internal/apps/mysql/app.go:72
msgid "failed to restart MySQL: %v"
msgstr ""
#: internal/apps/mysql/app.go:99
msgid "failed to set MYSQL_PWD env: %v"
msgstr ""
#: internal/apps/memcached/app.go:56
msgid "failed to write to Memcached: %v"
msgstr ""
#: internal/apps/php/app.go:422
msgid "gRPC is a high-performance, open-source, and general-purpose RPC framework"
msgstr ""
#: internal/apps/fail2ban/app.go:184
msgid "get service port failed, please check if it is installed"
msgstr ""
#: internal/apps/php/app.go:472
msgid "ionCube is a professional-grade PHP encryption and decryption tool (must be installed after OPcache)"
msgstr ""
#: internal/apps/php/app.go:327
msgid "pdo_pgsql is a PDO driver for connecting to PostgreSQL (requires PostgreSQL installed)"
msgstr ""
#: internal/apps/php/app.go:337
msgid "pdo_sqlsrv is a PDO driver for connecting to SQL Server"
msgstr ""
#: internal/apps/php/app.go:322
msgid "pgsql is a driver for connecting to PostgreSQL (requires PostgreSQL installed)"
msgstr ""
#: internal/apps/php/app.go:427
msgid "protobuf is a library for serializing and deserializing data"
msgstr ""
#: internal/apps/php/app.go:432
msgid "rdkafka is a library for connecting to Apache Kafka"
msgstr ""
#: internal/apps/fail2ban/app.go:109
msgid "rule already exists"
msgstr ""
#: internal/apps/fail2ban/app.go:228
msgid "rule not found"
msgstr ""
#: internal/apps/php/app.go:332
msgid "sqlsrv is a driver for connecting to SQL Server"
msgstr ""
#: internal/apps/fail2ban/app.go:180
msgid "unknown service"
msgstr ""
#: internal/apps/benchmark/app.go:81
msgid "unknown test type"
msgstr ""
#: internal/apps/php/app.go:442
msgid "xdebug is a library for debugging and profiling PHP code"
msgstr ""
#: internal/apps/php/app.go:437
msgid "xhprof is a library for performance profiling"
msgstr ""
#: internal/apps/php/app.go:447
msgid "yaml is a library for handling YAML"
msgstr ""
#: internal/apps/php/app.go:452
msgid "zstd is a library for compressing and decompressing files"
msgstr ""