2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 11:27:17 +08:00

feat: 添加翻译

This commit is contained in:
2025-04-12 18:41:08 +08:00
parent 3f90402164
commit 2d45e84b66
24 changed files with 267 additions and 223 deletions

View File

@@ -61,18 +61,18 @@ func initCli() (*app.Cli, error) {
}
cacheRepo := data.NewCacheRepo(db)
queue := bootstrap.NewQueue()
taskRepo := data.NewTaskRepo(db, logger, queue)
appRepo := data.NewAppRepo(db, cacheRepo, taskRepo)
userRepo := data.NewUserRepo(db)
settingRepo := data.NewSettingRepo(db, koanf, taskRepo)
databaseServerRepo := data.NewDatabaseServerRepo(db, logger)
databaseUserRepo := data.NewDatabaseUserRepo(db, databaseServerRepo)
databaseRepo := data.NewDatabaseRepo(db, databaseServerRepo, databaseUserRepo)
certRepo := data.NewCertRepo(db, logger)
certAccountRepo := data.NewCertAccountRepo(db, userRepo, logger)
taskRepo := data.NewTaskRepo(locale, db, logger, queue)
appRepo := data.NewAppRepo(locale, db, cacheRepo, taskRepo)
userRepo := data.NewUserRepo(locale, db)
settingRepo := data.NewSettingRepo(locale, db, koanf, taskRepo)
databaseServerRepo := data.NewDatabaseServerRepo(locale, db, logger)
databaseUserRepo := data.NewDatabaseUserRepo(locale, db, databaseServerRepo)
databaseRepo := data.NewDatabaseRepo(locale, db, databaseServerRepo, databaseUserRepo)
certRepo := data.NewCertRepo(locale, db, logger)
certAccountRepo := data.NewCertAccountRepo(locale, db, userRepo, logger)
websiteRepo := data.NewWebsiteRepo(db, cacheRepo, databaseRepo, databaseServerRepo, databaseUserRepo, certRepo, certAccountRepo)
backupRepo := data.NewBackupRepo(db, settingRepo, websiteRepo)
cliService := service.NewCliService(koanf, db, appRepo, cacheRepo, userRepo, settingRepo, backupRepo, websiteRepo, databaseServerRepo)
backupRepo := data.NewBackupRepo(locale, db, settingRepo, websiteRepo)
cliService := service.NewCliService(locale, koanf, db, appRepo, cacheRepo, userRepo, settingRepo, backupRepo, websiteRepo, databaseServerRepo)
cli := route.NewCli(cliService)
command := bootstrap.NewCli(locale, cli)
gormigrate := bootstrap.NewMigrate(db)

View File

@@ -61,40 +61,44 @@ func initWeb() (*app.Web, error) {
if err != nil {
return nil, err
}
locale, err := bootstrap.NewT(koanf)
if err != nil {
return nil, err
}
cacheRepo := data.NewCacheRepo(db)
queue := bootstrap.NewQueue()
taskRepo := data.NewTaskRepo(db, logger, queue)
appRepo := data.NewAppRepo(db, cacheRepo, taskRepo)
taskRepo := data.NewTaskRepo(locale, db, logger, queue)
appRepo := data.NewAppRepo(locale, db, cacheRepo, taskRepo)
middlewares := middleware.NewMiddlewares(koanf, logger, manager, appRepo)
userRepo := data.NewUserRepo(db)
userService := service.NewUserService(koanf, manager, userRepo)
databaseServerRepo := data.NewDatabaseServerRepo(db, logger)
databaseUserRepo := data.NewDatabaseUserRepo(db, databaseServerRepo)
databaseRepo := data.NewDatabaseRepo(db, databaseServerRepo, databaseUserRepo)
certRepo := data.NewCertRepo(db, logger)
certAccountRepo := data.NewCertAccountRepo(db, userRepo, logger)
userRepo := data.NewUserRepo(locale, db)
userService := service.NewUserService(locale, koanf, manager, userRepo)
databaseServerRepo := data.NewDatabaseServerRepo(locale, db, logger)
databaseUserRepo := data.NewDatabaseUserRepo(locale, db, databaseServerRepo)
databaseRepo := data.NewDatabaseRepo(locale, db, databaseServerRepo, databaseUserRepo)
certRepo := data.NewCertRepo(locale, db, logger)
certAccountRepo := data.NewCertAccountRepo(locale, db, userRepo, logger)
websiteRepo := data.NewWebsiteRepo(db, cacheRepo, databaseRepo, databaseServerRepo, databaseUserRepo, certRepo, certAccountRepo)
settingRepo := data.NewSettingRepo(db, koanf, taskRepo)
cronRepo := data.NewCronRepo(db)
backupRepo := data.NewBackupRepo(db, settingRepo, websiteRepo)
dashboardService := service.NewDashboardService(koanf, taskRepo, websiteRepo, appRepo, settingRepo, cronRepo, backupRepo)
settingRepo := data.NewSettingRepo(locale, db, koanf, taskRepo)
cronRepo := data.NewCronRepo(locale, db)
backupRepo := data.NewBackupRepo(locale, db, settingRepo, websiteRepo)
dashboardService := service.NewDashboardService(locale, koanf, taskRepo, websiteRepo, appRepo, settingRepo, cronRepo, backupRepo)
taskService := service.NewTaskService(taskRepo)
websiteService := service.NewWebsiteService(websiteRepo, settingRepo)
databaseService := service.NewDatabaseService(databaseRepo)
databaseServerService := service.NewDatabaseServerService(databaseServerRepo)
databaseUserService := service.NewDatabaseUserService(databaseUserRepo)
backupService := service.NewBackupService(backupRepo)
certService := service.NewCertService(certRepo)
backupService := service.NewBackupService(locale, backupRepo)
certService := service.NewCertService(locale, certRepo)
certDNSRepo := data.NewCertDNSRepo(db)
certDNSService := service.NewCertDNSService(certDNSRepo)
certAccountService := service.NewCertAccountService(certAccountRepo)
appService := service.NewAppService(appRepo, cacheRepo, settingRepo)
appService := service.NewAppService(locale, appRepo, cacheRepo, settingRepo)
cronService := service.NewCronService(cronRepo)
processService := service.NewProcessService()
safeRepo := data.NewSafeRepo()
safeService := service.NewSafeService(safeRepo)
firewallService := service.NewFirewallService()
sshRepo := data.NewSSHRepo(db)
sshRepo := data.NewSSHRepo(locale, db)
sshService := service.NewSSHService(sshRepo)
containerRepo := data.NewContainerRepo()
containerService := service.NewContainerService(containerRepo)
@@ -106,15 +110,11 @@ func initWeb() (*app.Web, error) {
containerImageService := service.NewContainerImageService(containerImageRepo)
containerVolumeRepo := data.NewContainerVolumeRepo()
containerVolumeService := service.NewContainerVolumeService(containerVolumeRepo)
fileService := service.NewFileService(taskRepo)
fileService := service.NewFileService(locale, taskRepo)
monitorRepo := data.NewMonitorRepo(db, settingRepo)
monitorService := service.NewMonitorService(settingRepo, monitorRepo)
settingService := service.NewSettingService(settingRepo)
systemctlService := service.NewSystemctlService()
locale, err := bootstrap.NewT(koanf)
if err != nil {
return nil, err
}
systemctlService := service.NewSystemctlService(locale)
benchmarkApp := benchmark.NewApp(locale)
dockerApp := docker.NewApp()
fail2banApp := fail2ban.NewApp(locale, websiteRepo)
@@ -141,7 +141,7 @@ func initWeb() (*app.Web, error) {
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)
wsService := service.NewWsService(locale, koanf, sshRepo)
ws := route.NewWs(wsService)
mux, err := bootstrap.NewRouter(middlewares, http, ws)
if err != nil {

View File

@@ -10,6 +10,7 @@ import (
"strings"
"time"
"github.com/leonelquinteros/gotext"
"gorm.io/gorm"
"github.com/tnb-labs/panel/internal/app"
@@ -24,13 +25,15 @@ import (
)
type certRepo struct {
t *gotext.Locale
db *gorm.DB
log *slog.Logger
client *acme.Client
}
func NewCertRepo(db *gorm.DB, log *slog.Logger) biz.CertRepo {
func NewCertRepo(t *gotext.Locale, db *gorm.DB, log *slog.Logger) biz.CertRepo {
return &certRepo{
t: t,
db: db,
log: log,
}
@@ -86,10 +89,10 @@ func (r *certRepo) GetByWebsite(WebsiteID uint) (*biz.Cert, error) {
func (r *certRepo) Upload(req *request.CertUpload) (*biz.Cert, error) {
info, err := pkgcert.ParseCert(req.Cert)
if err != nil {
return nil, fmt.Errorf("failed to parse certificate: %v", err)
return nil, errors.New(r.t.Get("failed to parse certificate: %v", err))
}
if _, err = pkgcert.ParseKey(req.Key); err != nil {
return nil, fmt.Errorf("failed to parse private key: %v", err)
return nil, errors.New(r.t.Get("failed to parse private key: %v", err))
}
cert := &biz.Cert{
@@ -126,7 +129,7 @@ func (r *certRepo) Update(req *request.CertUpdate) error {
req.Domains = info.DNSNames
}
if req.Type == "upload" && req.AutoRenew {
return errors.New("upload certificate cannot be set to auto renew")
return errors.New(r.t.Get("upload certificate cannot be set to auto renew"))
}
return r.db.Model(&biz.Cert{}).Where("id = ?", req.ID).Select("*").Updates(&biz.Cert{
@@ -162,11 +165,11 @@ func (r *certRepo) ObtainAuto(id uint) (*acme.Certificate, error) {
client.UseDns(cert.DNS.Type, cert.DNS.Data)
} else {
if cert.Website == nil {
return nil, errors.New("this certificate is not associated with a website and cannot be signed. You can try to sign it manually")
return nil, errors.New(r.t.Get("this certificate is not associated with a website and cannot be signed. You can try to sign it manually"))
} else {
for _, domain := range cert.Domains {
if strings.Contains(domain, "*") {
return nil, errors.New("wildcard domains cannot use HTTP verification")
return nil, errors.New(r.t.Get("wildcard domains cannot use HTTP verification"))
}
}
conf := fmt.Sprintf("%s/server/vhost/acme/%s.conf", app.Root, cert.Website.Name)
@@ -204,7 +207,7 @@ func (r *certRepo) ObtainManual(id uint) (*acme.Certificate, error) {
}
if r.client == nil {
return nil, errors.New("please retry the manual obtain operation")
return nil, errors.New(r.t.Get("please retry the manual obtain operation"))
}
ssl, err := r.client.ObtainCertificateManual()
@@ -270,18 +273,18 @@ func (r *certRepo) Renew(id uint) (*acme.Certificate, error) {
}
if cert.CertURL == "" {
return nil, errors.New("this certificate has not been signed successfully and cannot be renewed")
return nil, errors.New(r.t.Get("this certificate has not been signed successfully and cannot be renewed"))
}
if cert.DNS != nil {
client.UseDns(cert.DNS.Type, cert.DNS.Data)
} else {
if cert.Website == nil {
return nil, errors.New("this certificate is not associated with a website and cannot be signed. You can try to sign it manually")
return nil, errors.New(r.t.Get("this certificate is not associated with a website and cannot be signed. You can try to sign it manually"))
} else {
for _, domain := range cert.Domains {
if strings.Contains(domain, "*") {
return nil, errors.New("wildcard domains cannot use HTTP verification")
return nil, errors.New(r.t.Get("wildcard domains cannot use HTTP verification"))
}
}
conf := fmt.Sprintf("%s/server/vhost/acme/%s.conf", app.Root, cert.Website.Name)
@@ -345,7 +348,7 @@ func (r *certRepo) Deploy(ID, WebsiteID uint) error {
}
if cert.Cert == "" || cert.Key == "" {
return errors.New("this certificate has not been signed successfully and cannot be deployed")
return errors.New(r.t.Get("this certificate has not been signed successfully and cannot be deployed"))
}
website := new(biz.Website)
@@ -400,7 +403,7 @@ func (r *certRepo) runScript(cert *biz.Cert) error {
func (r *certRepo) getClient(cert *biz.Cert) (*acme.Client, error) {
if cert.Account == nil {
return nil, errors.New("this certificate is not associated with an ACME account and cannot be signed")
return nil, errors.New(r.t.Get("this certificate is not associated with an ACME account and cannot be signed"))
}
var ca string

View File

@@ -3,11 +3,11 @@ package data
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
"github.com/go-resty/resty/v2"
"github.com/leonelquinteros/gotext"
"gorm.io/gorm"
"github.com/tnb-labs/panel/internal/biz"
@@ -17,13 +17,15 @@ import (
)
type certAccountRepo struct {
t *gotext.Locale
db *gorm.DB
log *slog.Logger
user biz.UserRepo
}
func NewCertAccountRepo(db *gorm.DB, user biz.UserRepo, log *slog.Logger) biz.CertAccountRepo {
func NewCertAccountRepo(t *gotext.Locale, db *gorm.DB, user biz.UserRepo, log *slog.Logger) biz.CertAccountRepo {
return &certAccountRepo{
t: t,
db: db,
log: log,
user: user,
@@ -31,7 +33,7 @@ func NewCertAccountRepo(db *gorm.DB, user biz.UserRepo, log *slog.Logger) biz.Ce
}
func (r certAccountRepo) List(page, limit uint) ([]*biz.CertAccount, int64, error) {
var accounts []*biz.CertAccount
accounts := make([]*biz.CertAccount, 0)
var total int64
err := r.db.Model(&biz.CertAccount{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&accounts).Error
return accounts, total, err
@@ -99,16 +101,16 @@ func (r certAccountRepo) Create(req *request.CertAccountCreate) (*biz.CertAccoun
case "sslcom":
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CASSLcom, &acme.EAB{KeyID: account.Kid, MACKey: account.HmacEncoded}, acme.KeyType(account.KeyType), r.log)
default:
return nil, errors.New("unsupported CA")
return nil, errors.New(r.t.Get("unsupported CA"))
}
if err != nil {
return nil, fmt.Errorf("failed to register account: %v", err)
return nil, errors.New(r.t.Get("failed to register account: %v", err))
}
privateKey, err := cert.EncodeKey(client.Account.PrivateKey)
if err != nil {
return nil, errors.New("failed to get private key")
return nil, errors.New(r.t.Get("failed to get private key"))
}
account.PrivateKey = string(privateKey)
@@ -158,16 +160,16 @@ func (r certAccountRepo) Update(req *request.CertAccountUpdate) error {
case "sslcom":
client, err = acme.NewRegisterAccount(context.Background(), account.Email, acme.CASSLcom, &acme.EAB{KeyID: account.Kid, MACKey: account.HmacEncoded}, acme.KeyType(account.KeyType), r.log)
default:
return errors.New("unsupported CA")
return errors.New(r.t.Get("unsupported CA"))
}
if err != nil {
return errors.New("failed to register account")
return errors.New(r.t.Get("failed to register account: %v", err))
}
privateKey, err := cert.EncodeKey(client.Account.PrivateKey)
if err != nil {
return errors.New("failed to get private key")
return errors.New(r.t.Get("failed to get private key: %v", err))
}
account.PrivateKey = string(privateKey)
@@ -193,11 +195,11 @@ func (r certAccountRepo) getGoogleEAB() (*acme.EAB, error) {
resp, err := client.R().SetResult(&data{}).Get("https://gts.rat.dev/eab")
if err != nil || !resp.IsSuccess() {
return &acme.EAB{}, fmt.Errorf("failed to get Google EAB: %v", err)
return &acme.EAB{}, errors.New(r.t.Get("failed to get Google EAB: %v", err))
}
eab := resp.Result().(*data)
if eab.Message != "success" {
return &acme.EAB{}, fmt.Errorf("failed to get Google EAB: %s", eab.Message)
return &acme.EAB{}, errors.New(r.t.Get("failed to get Google EAB: %s", eab.Message))
}
return &acme.EAB{KeyID: eab.Data.KeyId, MACKey: eab.Data.MacKey}, nil
@@ -218,11 +220,11 @@ func (r certAccountRepo) getZeroSSLEAB(email string) (*acme.EAB, error) {
"email": email,
}).SetResult(&data{}).Post("https://api.zerossl.com/acme/eab-credentials-email")
if err != nil || !resp.IsSuccess() {
return &acme.EAB{}, fmt.Errorf("failed to get ZeroSSL EAB: %v", err)
return &acme.EAB{}, errors.New(r.t.Get("failed to get ZeroSSL EAB: %v", err))
}
eab := resp.Result().(*data)
if !eab.Success {
return &acme.EAB{}, fmt.Errorf("failed to get ZeroSSL EAB")
return &acme.EAB{}, errors.New(r.t.Get("failed to get ZeroSSL EAB"))
}
return &acme.EAB{KeyID: eab.EabKid, MACKey: eab.EabHmacKey}, nil

View File

@@ -18,7 +18,7 @@ func NewCertDNSRepo(db *gorm.DB) biz.CertDNSRepo {
}
func (r certDNSRepo) List(page, limit uint) ([]*biz.CertDNS, int64, error) {
var certDNS []*biz.CertDNS
certDNS := make([]*biz.CertDNS, 0)
var total int64
err := r.db.Model(&biz.CertDNS{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&certDNS).Error
return certDNS, total, err

View File

@@ -8,6 +8,7 @@ import (
"time"
"github.com/go-rat/utils/str"
"github.com/leonelquinteros/gotext"
"gorm.io/gorm"
"github.com/tnb-labs/panel/internal/app"
@@ -20,11 +21,13 @@ import (
)
type cronRepo struct {
t *gotext.Locale
db *gorm.DB
}
func NewCronRepo(db *gorm.DB) biz.CronRepo {
func NewCronRepo(t *gotext.Locale, db *gorm.DB) biz.CronRepo {
return &cronRepo{
t: t,
db: db,
}
}
@@ -39,7 +42,7 @@ func (r *cronRepo) Count() (int64, error) {
}
func (r *cronRepo) List(page, limit uint) ([]*biz.Cron, int64, error) {
var cron []*biz.Cron
cron := make([]*biz.Cron, 0)
var total int64
err := r.db.Model(&biz.Cron{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&cron).Error
return cron, total, err
@@ -61,8 +64,6 @@ func (r *cronRepo) Create(req *request.CronCreate) error {
script = fmt.Sprintf(`#!/bin/bash
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
# 耗子面板 - 网站备份脚本
panel-cli backup website -n '%s' -p '%s'
panel-cli backup clear -t website -f '%s' -s '%d' -p '%s'
systemctl reload nginx
@@ -72,8 +73,6 @@ systemctl reload nginx
script = fmt.Sprintf(`#!/bin/bash
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
# 耗子面板 - 数据库备份脚本
panel-cli backup database -t '%s' -n '%s' -p '%s'
panel-cli backup clear -t '%s' -f '%s' -s '%d' -p '%s'
`, req.BackupType, req.Target, req.BackupPath, req.BackupType, req.Target, req.Save, req.BackupPath)
@@ -83,9 +82,6 @@ panel-cli backup clear -t '%s' -f '%s' -s '%d' -p '%s'
script = fmt.Sprintf(`#!/bin/bash
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
# 耗子面板 - 日志切割脚本
# 执行切割
panel-cli cutoff website -n '%s' -p '%s'
panel-cli cutoff clear -t website -f '%s' -s '%d' -p '%s'
`, req.Target, req.BackupPath, req.Target, req.Save, req.BackupPath)
@@ -97,18 +93,17 @@ panel-cli cutoff clear -t website -f '%s' -s '%d' -p '%s'
shellDir := fmt.Sprintf("%s/server/cron/", app.Root)
shellLogDir := fmt.Sprintf("%s/server/cron/logs/", app.Root)
if !io.Exists(shellDir) {
return errors.New("计划任务目录不存在")
return errors.New(r.t.Get("cron directory %s not exists", shellDir))
}
if !io.Exists(shellLogDir) {
return errors.New("计划任务日志目录不存在")
return errors.New(r.t.Get("cron log directory %s not exists", shellLogDir))
}
shellFile := strconv.Itoa(int(time.Now().Unix())) + str.Random(16)
if err := io.Write(filepath.Join(shellDir, shellFile+".sh"), script, 0700); err != nil {
return errors.New(err.Error())
}
if out, err := shell.Execf("dos2unix %s%s.sh", shellDir, shellFile); err != nil {
return errors.New(out)
}
// 编码转换
_, _ = shell.Execf("dos2unix %s%s.sh", shellDir, shellFile)
cron := new(biz.Cron)
cron.Name = req.Name
@@ -134,10 +129,6 @@ func (r *cronRepo) Update(req *request.CronUpdate) error {
return err
}
if !cron.Status {
return errors.New("计划任务已禁用")
}
cron.Time = req.Time
cron.Name = req.Name
if err = r.db.Save(cron).Error; err != nil {
@@ -227,5 +218,5 @@ func (r *cronRepo) restartCron() error {
return systemctl.Restart("cron")
}
return errors.New("不支持的系统")
return errors.New(r.t.Get("unsupported system"))
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"slices"
"github.com/leonelquinteros/gotext"
"gorm.io/gorm"
"github.com/tnb-labs/panel/internal/biz"
@@ -13,13 +14,15 @@ import (
)
type databaseRepo struct {
t *gotext.Locale
db *gorm.DB
server biz.DatabaseServerRepo
user biz.DatabaseUserRepo
}
func NewDatabaseRepo(db *gorm.DB, server biz.DatabaseServerRepo, user biz.DatabaseUserRepo) biz.DatabaseRepo {
func NewDatabaseRepo(t *gotext.Locale, db *gorm.DB, server biz.DatabaseServerRepo, user biz.DatabaseUserRepo) biz.DatabaseRepo {
return &databaseRepo{
t: t,
db: db,
server: server,
user: user,
@@ -179,7 +182,7 @@ func (r databaseRepo) Comment(req *request.DatabaseComment) error {
switch server.Type {
case biz.DatabaseTypeMysql:
return errors.New("mysql not support database comment")
return errors.New(r.t.Get("mysql not support database comment"))
case biz.DatabaseTypePostgresql:
postgres, err := db.NewPostgres(server.Username, server.Password, server.Host, server.Port)
if err != nil {

View File

@@ -1,10 +1,12 @@
package data
import (
"errors"
"fmt"
"log/slog"
"slices"
"github.com/leonelquinteros/gotext"
"gorm.io/gorm"
"github.com/tnb-labs/panel/internal/biz"
@@ -13,12 +15,14 @@ import (
)
type databaseServerRepo struct {
t *gotext.Locale
db *gorm.DB
log *slog.Logger
}
func NewDatabaseServerRepo(db *gorm.DB, log *slog.Logger) biz.DatabaseServerRepo {
func NewDatabaseServerRepo(t *gotext.Locale, db *gorm.DB, log *slog.Logger) biz.DatabaseServerRepo {
return &databaseServerRepo{
t: t,
db: db,
log: log,
}
@@ -34,7 +38,7 @@ func (r databaseServerRepo) Count() (int64, error) {
}
func (r databaseServerRepo) List(page, limit uint) ([]*biz.DatabaseServer, int64, error) {
var databaseServer []*biz.DatabaseServer
databaseServer := make([]*biz.DatabaseServer, 0)
var total int64
err := r.db.Model(&biz.DatabaseServer{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&databaseServer).Error
@@ -79,7 +83,7 @@ func (r databaseServerRepo) Create(req *request.DatabaseServerCreate) error {
}
if !r.checkServer(databaseServer) {
return fmt.Errorf("check server connection failed")
return errors.New(r.t.Get("check server connection failed"))
}
return r.db.Create(databaseServer).Error
@@ -99,7 +103,7 @@ func (r databaseServerRepo) Update(req *request.DatabaseServerUpdate) error {
server.Remark = req.Remark
if !r.checkServer(server) {
return fmt.Errorf("check server connection failed")
return errors.New(r.t.Get("check server connection failed"))
}
return r.db.Save(server).Error
@@ -154,7 +158,7 @@ func (r databaseServerRepo) Sync(id uint) error {
ServerID: id,
Username: user.User,
Host: user.Host,
Remark: fmt.Sprintf("sync from server %s", server.Name),
Remark: r.t.Get("sync from server %s", server.Name),
}
if err = r.db.Create(newUser).Error; err != nil {
r.log.Warn("sync database user failed", slog.Any("err", err))
@@ -180,7 +184,7 @@ func (r databaseServerRepo) Sync(id uint) error {
newUser := &biz.DatabaseUser{
ServerID: id,
Username: user.Role,
Remark: fmt.Sprintf("sync from server %s", server.Name),
Remark: r.t.Get("sync from server %s", server.Name),
}
r.db.Create(newUser)
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"slices"
"github.com/leonelquinteros/gotext"
"gorm.io/gorm"
"github.com/tnb-labs/panel/internal/biz"
@@ -12,12 +13,14 @@ import (
)
type databaseUserRepo struct {
t *gotext.Locale
db *gorm.DB
server biz.DatabaseServerRepo
}
func NewDatabaseUserRepo(db *gorm.DB, server biz.DatabaseServerRepo) biz.DatabaseUserRepo {
func NewDatabaseUserRepo(t *gotext.Locale, db *gorm.DB, server biz.DatabaseServerRepo) biz.DatabaseUserRepo {
return &databaseUserRepo{
t: t,
db: db,
server: server,
}
@@ -33,7 +36,7 @@ func (r databaseUserRepo) Count() (int64, error) {
}
func (r databaseUserRepo) List(page, limit uint) ([]*biz.DatabaseUser, int64, error) {
var user []*biz.DatabaseUser
user := make([]*biz.DatabaseUser, 0)
var total int64
err := r.db.Model(&biz.DatabaseUser{}).Preload("Server").Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&user).Error

View File

@@ -1,7 +1,6 @@
package data
import (
"errors"
"time"
"github.com/spf13/cast"
@@ -56,14 +55,10 @@ func (r monitorRepo) Clear() error {
}
func (r monitorRepo) List(start, end time.Time) ([]*biz.Monitor, error) {
var monitors []*biz.Monitor
monitors := make([]*biz.Monitor, 0)
if err := r.db.Where("created_at BETWEEN ? AND ?", start, end).Find(&monitors).Error; err != nil {
return nil, err
}
if len(monitors) == 0 {
return nil, errors.New("没有找到数据")
}
return monitors, nil
}

View File

@@ -3,11 +3,11 @@ package data
import (
"context"
"errors"
"fmt"
"path/filepath"
"github.com/go-rat/utils/hash"
"github.com/knadh/koanf/v2"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"gopkg.in/yaml.v3"
"gorm.io/gorm"
@@ -23,13 +23,15 @@ import (
)
type settingRepo struct {
t *gotext.Locale
db *gorm.DB
conf *koanf.Koanf
task biz.TaskRepo
}
func NewSettingRepo(db *gorm.DB, conf *koanf.Koanf, task biz.TaskRepo) biz.SettingRepo {
func NewSettingRepo(t *gotext.Locale, db *gorm.DB, conf *koanf.Koanf, task biz.TaskRepo) biz.SettingRepo {
return &settingRepo{
t: t,
db: db,
conf: conf,
task: task,
@@ -186,15 +188,15 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P
oldKey, _ := io.Read(filepath.Join(app.Root, "panel/storage/cert.key"))
if oldCert != setting.Cert || oldKey != setting.Key {
if r.task.HasRunningTask() {
return false, errors.New("后台任务正在运行,禁止修改部分设置,请稍后再试")
return false, errors.New(r.t.Get("background task is running, modifying some settings is prohibited, please try again later"))
}
restartFlag = true
}
if _, err := cert.ParseCert(setting.Cert); err != nil {
return false, fmt.Errorf("failed to parse certificate: %w", err)
return false, errors.New(r.t.Get("failed to parse certificate: %w", err))
}
if _, err := cert.ParseKey(setting.Key); err != nil {
return false, fmt.Errorf("failed to parse private key: %w", err)
return false, errors.New(r.t.Get("failed to parse private key: %w", err))
}
if err := io.Write(filepath.Join(app.Root, "panel/storage/cert.pem"), setting.Cert, 0644); err != nil {
return false, err
@@ -215,7 +217,7 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P
if setting.Port != config.HTTP.Port {
if os.TCPPortInUse(setting.Port) {
return false, errors.New("端口已被占用")
return false, errors.New(r.t.Get("port is already in use"))
}
}
@@ -243,7 +245,7 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P
}
if raw != string(encoded) {
if r.task.HasRunningTask() {
return false, errors.New("后台任务正在运行,禁止修改部分设置,请稍后再试")
return false, errors.New(r.t.Get("background task is running, modifying some settings is prohibited, please try again later"))
}
restartFlag = true
}

View File

@@ -1,8 +1,10 @@
package data
import (
"errors"
"fmt"
"github.com/leonelquinteros/gotext"
"gorm.io/gorm"
"github.com/tnb-labs/panel/internal/biz"
@@ -11,17 +13,19 @@ import (
)
type sshRepo struct {
t *gotext.Locale
db *gorm.DB
}
func NewSSHRepo(db *gorm.DB) biz.SSHRepo {
func NewSSHRepo(t *gotext.Locale, db *gorm.DB) biz.SSHRepo {
return &sshRepo{
t: t,
db: db,
}
}
func (r *sshRepo) List(page, limit uint) ([]*biz.SSH, int64, error) {
var ssh []*biz.SSH
ssh := make([]*biz.SSH, 0)
var total int64
err := r.db.Model(&biz.SSH{}).Omit("Hosts").Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&ssh).Error
return ssh, total, err
@@ -46,7 +50,7 @@ func (r *sshRepo) Create(req *request.SSHCreate) error {
}
_, err := pkgssh.NewSSHClient(conf)
if err != nil {
return fmt.Errorf("failed to check ssh connection: %v", err)
return errors.New(r.t.Get("failed to check ssh connection: %v", err))
}
ssh := &biz.SSH{
@@ -70,7 +74,7 @@ func (r *sshRepo) Update(req *request.SSHUpdate) error {
}
_, err := pkgssh.NewSSHClient(conf)
if err != nil {
return fmt.Errorf("failed to check ssh connection: %v", err)
return errors.New(r.t.Get("failed to check ssh connection: %v", err))
}
ssh := &biz.SSH{

View File

@@ -1,9 +1,10 @@
package data
import (
"fmt"
"errors"
"log/slog"
"github.com/leonelquinteros/gotext"
"gorm.io/gorm"
"github.com/tnb-labs/panel/internal/biz"
@@ -12,13 +13,15 @@ import (
)
type taskRepo struct {
t *gotext.Locale
db *gorm.DB
log *slog.Logger
queue *queue.Queue
}
func NewTaskRepo(db *gorm.DB, log *slog.Logger, queue *queue.Queue) biz.TaskRepo {
func NewTaskRepo(t *gotext.Locale, db *gorm.DB, log *slog.Logger, queue *queue.Queue) biz.TaskRepo {
return &taskRepo{
t: t,
db: db,
log: log,
queue: queue,
@@ -32,7 +35,7 @@ func (r *taskRepo) HasRunningTask() bool {
}
func (r *taskRepo) List(page, limit uint) ([]*biz.Task, int64, error) {
var tasks []*biz.Task
tasks := make([]*biz.Task, 0)
var total int64
err := r.db.Model(&biz.Task{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&tasks).Error
return tasks, total, err
@@ -53,12 +56,13 @@ func (r *taskRepo) UpdateStatus(id uint, status biz.TaskStatus) error {
}
func (r *taskRepo) Push(task *biz.Task) error {
// 防止有人喜欢酒吧点炒饭
var count int64
if err := r.db.Model(&biz.Task{}).Where("shell = ? and (status = ? or status = ?)", task.Shell, biz.TaskStatusWaiting, biz.TaskStatusRunning).Count(&count).Error; err != nil {
return err
}
if count > 0 {
return fmt.Errorf("duplicate submission, please wait for the previous task to end")
return errors.New(r.t.Get("duplicate submission, please wait for the previous task to end"))
}
if err := r.db.Create(task).Error; err != nil {

View File

@@ -4,18 +4,21 @@ import (
"errors"
"github.com/go-rat/utils/hash"
"github.com/leonelquinteros/gotext"
"gorm.io/gorm"
"github.com/tnb-labs/panel/internal/biz"
)
type userRepo struct {
t *gotext.Locale
db *gorm.DB
hasher hash.Hasher
}
func NewUserRepo(db *gorm.DB) biz.UserRepo {
func NewUserRepo(t *gotext.Locale, db *gorm.DB) biz.UserRepo {
return &userRepo{
t: t,
db: db,
hasher: hash.NewArgon2id(),
}
@@ -42,14 +45,14 @@ func (r *userRepo) CheckPassword(username, password string) (*biz.User, error) {
user := new(biz.User)
if err := r.db.Where("username = ?", username).First(user).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("用户名或密码错误")
return nil, errors.New(r.t.Get("username or password error"))
} else {
return nil, err
}
}
if !r.hasher.Check(password, user.Password) {
return nil, errors.New("用户名或密码错误")
return nil, errors.New(r.t.Get("username or password error"))
}
return user, nil

View File

@@ -208,7 +208,7 @@ func (r *websiteRepo) GetByName(name string) (*types.WebsiteSetting, error) {
}
func (r *websiteRepo) List(page, limit uint) ([]*biz.Website, int64, error) {
var websites []*biz.Website
websites := make([]*biz.Website, 0)
var total int64
if err := r.db.Model(&biz.Website{}).Count(&total).Error; err != nil {

View File

@@ -4,6 +4,7 @@ import (
"net/http"
"github.com/go-rat/chix"
"github.com/leonelquinteros/gotext"
"github.com/tnb-labs/panel/internal/biz"
"github.com/tnb-labs/panel/internal/http/request"
@@ -11,13 +12,15 @@ import (
)
type AppService struct {
t *gotext.Locale
appRepo biz.AppRepo
cacheRepo biz.CacheRepo
settingRepo biz.SettingRepo
}
func NewAppService(app biz.AppRepo, cache biz.CacheRepo, setting biz.SettingRepo) *AppService {
func NewAppService(t *gotext.Locale, app biz.AppRepo, cache biz.CacheRepo, setting biz.SettingRepo) *AppService {
return &AppService{
t: t,
appRepo: app,
cacheRepo: cache,
settingRepo: setting,
@@ -167,7 +170,7 @@ func (s *AppService) IsInstalled(w http.ResponseWriter, r *http.Request) {
func (s *AppService) UpdateCache(w http.ResponseWriter, r *http.Request) {
if offline, _ := s.settingRepo.GetBool(biz.SettingKeyOfflineMode); offline {
Error(w, http.StatusForbidden, "离线模式下无法更新应用列表缓存")
Error(w, http.StatusForbidden, s.t.Get("Unable to update app list cache in offline mode"))
return
}

View File

@@ -8,6 +8,7 @@ import (
"slices"
"github.com/go-rat/chix"
"github.com/leonelquinteros/gotext"
"github.com/tnb-labs/panel/internal/biz"
"github.com/tnb-labs/panel/internal/http/request"
@@ -15,11 +16,13 @@ import (
)
type BackupService struct {
t *gotext.Locale
backupRepo biz.BackupRepo
}
func NewBackupService(backup biz.BackupRepo) *BackupService {
func NewBackupService(t *gotext.Locale, backup biz.BackupRepo) *BackupService {
return &BackupService{
t: t,
backupRepo: backup,
}
}
@@ -71,7 +74,7 @@ func (s *BackupService) Upload(w http.ResponseWriter, r *http.Request) {
// 只允许上传 .sql .zip .tar .gz .tgz .bz2 .xz .7z
if !slices.Contains([]string{".sql", ".zip", ".tar", ".gz", ".tgz", ".bz2", ".xz", ".7z"}, filepath.Ext(req.File.Filename)) {
Error(w, http.StatusForbidden, "unsupported file type")
Error(w, http.StatusForbidden, s.t.Get("unsupported file type"))
return
}
@@ -81,19 +84,19 @@ func (s *BackupService) Upload(w http.ResponseWriter, r *http.Request) {
return
}
if io.Exists(filepath.Join(path, req.File.Filename)) {
Error(w, http.StatusForbidden, "target backup %s already exists", path)
Error(w, http.StatusForbidden, s.t.Get("target backup %s already exists", path))
return
}
src, _ := req.File.Open()
out, err := os.OpenFile(filepath.Join(path, req.File.Filename), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
if err != nil {
Error(w, http.StatusInternalServerError, "open file error: %v", err)
Error(w, http.StatusInternalServerError, "%v", err)
return
}
if _, err = stdio.Copy(out, src); err != nil {
Error(w, http.StatusInternalServerError, "write file error: %v", err)
Error(w, http.StatusInternalServerError, "%v", err)
return
}

View File

@@ -4,6 +4,7 @@ import (
"net/http"
"github.com/go-rat/chix"
"github.com/leonelquinteros/gotext"
"github.com/tnb-labs/panel/internal/biz"
"github.com/tnb-labs/panel/internal/http/request"
@@ -12,21 +13,19 @@ import (
)
type CertService struct {
t *gotext.Locale
certRepo biz.CertRepo
}
func NewCertService(cert biz.CertRepo) *CertService {
func NewCertService(t *gotext.Locale, cert biz.CertRepo) *CertService {
return &CertService{
t: t,
certRepo: cert,
}
}
func (s *CertService) CAProviders(w http.ResponseWriter, r *http.Request) {
Success(w, []types.LV{
{
Label: "GoogleCN推荐",
Value: "googlecn",
},
{
Label: "Let's Encrypt",
Value: "letsencrypt",
@@ -39,6 +38,10 @@ func (s *CertService) CAProviders(w http.ResponseWriter, r *http.Request) {
Label: "SSL.com",
Value: "sslcom",
},
{
Label: "GoogleCN",
Value: "googlecn",
},
{
Label: "Google",
Value: "google",
@@ -54,67 +57,67 @@ func (s *CertService) CAProviders(w http.ResponseWriter, r *http.Request) {
func (s *CertService) DNSProviders(w http.ResponseWriter, r *http.Request) {
Success(w, []types.LV{
{
Label: "阿里云",
Label: s.t.Get("Aliyun"),
Value: string(acme.AliYun),
},
{
Label: "腾讯云",
Label: s.t.Get("Tencent Cloud"),
Value: string(acme.Tencent),
},
{
Label: "华为云",
Label: s.t.Get("Huawei Cloud"),
Value: string(acme.Huawei),
},
{
Label: "西部数码",
Label: s.t.Get("West.cn"),
Value: string(acme.Westcn),
},
{
Label: "CloudFlare",
Label: s.t.Get("CloudFlare"),
Value: string(acme.CloudFlare),
},
{
Label: "Godaddy",
Label: s.t.Get("Godaddy"),
Value: string(acme.Godaddy),
},
{
Label: "Gcore",
Label: s.t.Get("Gcore"),
Value: string(acme.Gcore),
},
{
Label: "Porkbun",
Label: s.t.Get("Porkbun"),
Value: string(acme.Porkbun),
},
{
Label: "Namecheap",
Label: s.t.Get("Namecheap"),
Value: string(acme.Namecheap),
},
{
Label: "NameSilo",
Label: s.t.Get("NameSilo"),
Value: string(acme.NameSilo),
},
{
Label: "Name.com",
Label: s.t.Get("Name.com"),
Value: string(acme.Namecom),
},
{
Label: "ClouDNS",
Label: s.t.Get("ClouDNS"),
Value: string(acme.ClouDNS),
},
{
Label: "Duck DNS",
Label: s.t.Get("Duck DNS"),
Value: string(acme.DuckDNS),
},
{
Label: "Hetzner",
Label: s.t.Get("Hetzner"),
Value: string(acme.Hetzner),
},
{
Label: "Linode",
Label: s.t.Get("Linode"),
Value: string(acme.Linode),
},
{
Label: "Vercel",
Label: s.t.Get("Vercel"),
Value: string(acme.Vercel),
},
})

View File

@@ -11,6 +11,7 @@ import (
"github.com/go-rat/utils/hash"
"github.com/go-rat/utils/str"
"github.com/knadh/koanf/v2"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"github.com/urfave/cli/v3"
"gopkg.in/yaml.v3"
@@ -32,6 +33,7 @@ import (
type CliService struct {
hr string
t *gotext.Locale
api *api.API
conf *koanf.Koanf
db *gorm.DB
@@ -45,10 +47,11 @@ type CliService struct {
hash hash.Hasher
}
func NewCliService(conf *koanf.Koanf, db *gorm.DB, appRepo biz.AppRepo, cache biz.CacheRepo, user biz.UserRepo, setting biz.SettingRepo, backup biz.BackupRepo, website biz.WebsiteRepo, databaseServer biz.DatabaseServerRepo) *CliService {
func NewCliService(t *gotext.Locale, conf *koanf.Koanf, db *gorm.DB, appRepo biz.AppRepo, cache biz.CacheRepo, user biz.UserRepo, setting biz.SettingRepo, backup biz.BackupRepo, website biz.WebsiteRepo, databaseServer biz.DatabaseServerRepo) *CliService {
return &CliService{
hr: `+----------------------------------------------------`,
api: api.NewAPI(app.Version),
t: t,
conf: conf,
db: db,
appRepo: appRepo,
@@ -67,7 +70,7 @@ func (s *CliService) Restart(ctx context.Context, cmd *cli.Command) error {
return err
}
fmt.Println("面板服务已重启")
fmt.Println(s.t.Get("Panel service restarted"))
return nil
}
@@ -76,7 +79,7 @@ func (s *CliService) Stop(ctx context.Context, cmd *cli.Command) error {
return err
}
fmt.Println("面板服务已停止")
fmt.Println(s.t.Get("Panel service stopped"))
return nil
}
@@ -85,19 +88,19 @@ func (s *CliService) Start(ctx context.Context, cmd *cli.Command) error {
return err
}
fmt.Println("面板服务已启动")
fmt.Println(s.t.Get("Panel service started"))
return nil
}
func (s *CliService) Update(ctx context.Context, cmd *cli.Command) error {
panel, err := s.api.LatestVersion()
if err != nil {
return fmt.Errorf("获取最新版本失败:%v", err)
return errors.New(s.t.Get("Failed to get latest version: %v", err))
}
download := collect.First(panel.Downloads)
if download == nil {
return fmt.Errorf("下载地址为空")
return errors.New(s.t.Get("Download URL is empty"))
}
return s.backupRepo.UpdatePanel(panel.Version, download.URL, download.Checksum)
@@ -105,13 +108,14 @@ func (s *CliService) Update(ctx context.Context, cmd *cli.Command) error {
func (s *CliService) Sync(ctx context.Context, cmd *cli.Command) error {
if err := s.cacheRepo.UpdateApps(); err != nil {
return fmt.Errorf("同步应用数据失败:%v", err)
return errors.New(s.t.Get("Sync app data failed: %v", err))
}
if err := s.cacheRepo.UpdateRewrites(); err != nil {
return fmt.Errorf("同步伪静态规则失败:%v", err)
return errors.New(s.t.Get("Sync rewrite rules failed: %v", err))
}
fmt.Println("数据同步成功")
fmt.Println(s.t.Get("Sync data successfully"))
return nil
}
@@ -122,13 +126,13 @@ func (s *CliService) Fix(ctx context.Context, cmd *cli.Command) error {
func (s *CliService) Info(ctx context.Context, cmd *cli.Command) error {
user := new(biz.User)
if err := s.db.Where("id", 1).First(user).Error; err != nil {
return fmt.Errorf("获取管理员信息失败:%v", err)
return errors.New(s.t.Get("Failed to get user info: %v", err))
}
password := str.Random(16)
hashed, err := s.hash.Make(password)
if err != nil {
return fmt.Errorf("密码生成失败:%v", err)
return errors.New(s.t.Get("Failed to generate password: %v", err))
}
user.Username = str.Random(8)
user.Password = hashed
@@ -137,7 +141,7 @@ func (s *CliService) Info(ctx context.Context, cmd *cli.Command) error {
}
if err = s.db.Save(user).Error; err != nil {
return fmt.Errorf("管理员信息保存失败:%v", err)
return errors.New(s.t.Get("Failed to save user info: %v", err))
}
protocol := "http"
@@ -147,39 +151,39 @@ func (s *CliService) Info(ctx context.Context, cmd *cli.Command) error {
port := s.conf.String("http.port")
if port == "" {
return fmt.Errorf("端口获取失败")
return errors.New(s.t.Get("Failed to get port"))
}
entrance := s.conf.String("http.entrance")
if entrance == "" {
return fmt.Errorf("入口获取失败")
return errors.New(s.t.Get("Failed to get entrance"))
}
fmt.Printf("用户名: %s\n", user.Username)
fmt.Printf("密码: %s\n", password)
fmt.Printf("端口: %s\n", port)
fmt.Printf("入口: %s\n", entrance)
fmt.Println(s.t.Get("Username: %s", user.Username))
fmt.Println(s.t.Get("Password: %s", password))
fmt.Println(s.t.Get("Port: %s", port))
fmt.Println(s.t.Get("Entrance: %s", entrance))
lv4, err := tools.GetLocalIPv4()
if err == nil {
fmt.Printf("本地IPv4地址: %s://%s:%s%s\n", protocol, lv4, port, entrance)
fmt.Println(s.t.Get("Local IPv4: %s://%s:%s%s", protocol, lv4, port, entrance))
}
lv6, err := tools.GetLocalIPv6()
if err == nil {
fmt.Printf("本地IPv6地址: %s://[%s]:%s%s\n", protocol, lv6, port, entrance)
fmt.Println(s.t.Get("Local IPv6: %s://[%s]:%s%s", protocol, lv6, port, entrance))
}
rv4, err := tools.GetPublicIPv4()
if err == nil {
fmt.Printf("公网IPv4地址: %s://%s:%s%s\n", protocol, rv4, port, entrance)
fmt.Println(s.t.Get("Public IPv4: %s://%s:%s%s", protocol, rv4, port, entrance))
}
rv6, err := tools.GetPublicIPv6()
if err == nil {
fmt.Printf("公网IPv6地址: %s://[%s]:%s%s\n", protocol, rv6, port, entrance)
fmt.Println(s.t.Get("Public IPv6: %s://[%s]:%s%s", protocol, rv6, port, entrance))
}
fmt.Println("请根据自身网络情况自行选择合适的地址访问面板")
fmt.Printf("如无法访问,请检查服务器运营商安全组和防火墙是否放行%s端口\n", port)
fmt.Println("若仍无法访问,可尝试运行 panel-cli https off 关闭面板HTTPS")
fmt.Println("警告关闭面板HTTPS后面板安全性将大大降低请谨慎操作")
fmt.Println(s.t.Get("Please choose the appropriate address to access the panel based on your network situation"))
fmt.Println(s.t.Get("If you cannot access, please check whether the server's security group and firewall allow port %s", port))
fmt.Println(s.t.Get("If you still cannot access, try running panel-cli https off to turn off panel HTTPS"))
fmt.Println(s.t.Get("Warning: After turning off panel HTTPS, the security of the panel will be greatly reduced, please operate with caution"))
return nil
}
@@ -187,11 +191,11 @@ func (s *CliService) Info(ctx context.Context, cmd *cli.Command) error {
func (s *CliService) UserList(ctx context.Context, cmd *cli.Command) error {
users := make([]biz.User, 0)
if err := s.db.Find(&users).Error; err != nil {
return fmt.Errorf("获取用户列表失败:%v", err)
return errors.New(s.t.Get("Failed to get user list: %v", err))
}
for _, user := range users {
fmt.Printf("ID: %d, 用户名: %s, 邮箱: %s, 创建日期: %s\n", user.ID, user.Username, user.Email, user.CreatedAt.Format(time.DateTime))
fmt.Println(s.t.Get("ID: %d, Username: %s, Email: %s, Created At: %s", user.ID, user.Username, user.Email, user.CreatedAt.Format(time.DateTime)))
}
return nil
@@ -202,10 +206,10 @@ func (s *CliService) UserName(ctx context.Context, cmd *cli.Command) error {
oldUsername := cmd.Args().Get(0)
newUsername := cmd.Args().Get(1)
if oldUsername == "" {
return fmt.Errorf("旧用户名不能为空")
return errors.New(s.t.Get("Old username cannot be empty"))
}
if newUsername == "" {
return fmt.Errorf("新用户名不能为空")
return errors.New(s.t.Get("New username cannot be empty"))
}
if err := s.db.Where("username", oldUsername).First(user).Error; err != nil {

View File

@@ -11,6 +11,7 @@ import (
"github.com/go-rat/utils/collect"
"github.com/hashicorp/go-version"
"github.com/knadh/koanf/v2"
"github.com/leonelquinteros/gotext"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/host"
"github.com/spf13/cast"
@@ -26,6 +27,7 @@ import (
)
type DashboardService struct {
t *gotext.Locale
api *api.API
conf *koanf.Koanf
taskRepo biz.TaskRepo
@@ -36,8 +38,9 @@ type DashboardService struct {
backupRepo biz.BackupRepo
}
func NewDashboardService(conf *koanf.Koanf, task biz.TaskRepo, website biz.WebsiteRepo, appRepo biz.AppRepo, setting biz.SettingRepo, cron biz.CronRepo, backupRepo biz.BackupRepo) *DashboardService {
func NewDashboardService(t *gotext.Locale, conf *koanf.Koanf, task biz.TaskRepo, website biz.WebsiteRepo, appRepo biz.AppRepo, setting biz.SettingRepo, cron biz.CronRepo, backupRepo biz.BackupRepo) *DashboardService {
return &DashboardService{
t: t,
api: api.NewAPI(app.Version),
conf: conf,
taskRepo: task,
@@ -52,7 +55,7 @@ func NewDashboardService(conf *koanf.Koanf, task biz.TaskRepo, website biz.Websi
func (s *DashboardService) Panel(w http.ResponseWriter, r *http.Request) {
name, _ := s.settingRepo.Get(biz.SettingKeyName)
if name == "" {
name = "耗子面板"
name = s.t.Get("Rat Panel")
}
Success(w, chix.M{
@@ -64,7 +67,7 @@ func (s *DashboardService) Panel(w http.ResponseWriter, r *http.Request) {
func (s *DashboardService) HomeApps(w http.ResponseWriter, r *http.Request) {
apps, err := s.appRepo.GetHomeShow()
if err != nil {
Error(w, http.StatusInternalServerError, "获取首页应用失败: %v", err)
Error(w, http.StatusInternalServerError, s.t.Get("failed to get home apps: %v", err))
return
}
@@ -84,7 +87,7 @@ func (s *DashboardService) Current(w http.ResponseWriter, r *http.Request) {
func (s *DashboardService) SystemInfo(w http.ResponseWriter, r *http.Request) {
hostInfo, err := host.Info()
if err != nil {
Error(w, http.StatusInternalServerError, "获取系统信息失败")
Error(w, http.StatusInternalServerError, s.t.Get("failed to get system info: %v", err))
return
}
@@ -130,7 +133,7 @@ func (s *DashboardService) SystemInfo(w http.ResponseWriter, r *http.Request) {
func (s *DashboardService) CountInfo(w http.ResponseWriter, r *http.Request) {
websiteCount, err := s.websiteRepo.Count()
if err != nil {
Error(w, http.StatusInternalServerError, "获取网站数量失败")
Error(w, http.StatusInternalServerError, s.t.Get("failed to get the total number of websites: %v", err))
return
}
@@ -194,8 +197,8 @@ func (s *DashboardService) InstalledDbAndPhp(w http.ResponseWriter, r *http.Requ
var phpData []types.LVInt
var dbData []types.LV
phpData = append(phpData, types.LVInt{Value: 0, Label: "不使用"})
dbData = append(dbData, types.LV{Value: "0", Label: "不使用"})
phpData = append(phpData, types.LVInt{Value: 0, Label: s.t.Get("Not used")})
dbData = append(dbData, types.LV{Value: "0", Label: s.t.Get("Not used")})
for _, p := range php {
// 过滤 phpmyadmin
match := regexp.MustCompile(`php(\d+)`).FindStringSubmatch(p.Slug)
@@ -222,25 +225,25 @@ func (s *DashboardService) InstalledDbAndPhp(w http.ResponseWriter, r *http.Requ
func (s *DashboardService) CheckUpdate(w http.ResponseWriter, r *http.Request) {
if offline, _ := s.settingRepo.GetBool(biz.SettingKeyOfflineMode); offline {
Error(w, http.StatusForbidden, "离线模式下无法检查更新")
Error(w, http.StatusForbidden, s.t.Get("unable to check for updates in offline mode"))
return
}
current := app.Version
latest, err := s.api.LatestVersion()
if err != nil {
Error(w, http.StatusInternalServerError, "获取最新版本失败")
Error(w, http.StatusInternalServerError, s.t.Get("failed to get the latest version: %v", err))
return
}
v1, err := version.NewVersion(current)
if err != nil {
Error(w, http.StatusInternalServerError, "版本号解析失败")
Error(w, http.StatusInternalServerError, s.t.Get("failed to parse version: %v", err))
return
}
v2, err := version.NewVersion(latest.Version)
if err != nil {
Error(w, http.StatusInternalServerError, "版本号解析失败")
Error(w, http.StatusInternalServerError, s.t.Get("failed to parse version: %v", err))
return
}
if v1.GreaterThanOrEqual(v2) {
@@ -257,35 +260,35 @@ func (s *DashboardService) CheckUpdate(w http.ResponseWriter, r *http.Request) {
func (s *DashboardService) UpdateInfo(w http.ResponseWriter, r *http.Request) {
if offline, _ := s.settingRepo.GetBool(biz.SettingKeyOfflineMode); offline {
Error(w, http.StatusForbidden, "离线模式下无法检查更新")
Error(w, http.StatusForbidden, s.t.Get("unable to check for updates in offline mode"))
return
}
current := app.Version
latest, err := s.api.LatestVersion()
if err != nil {
Error(w, http.StatusInternalServerError, "获取最新版本失败")
Error(w, http.StatusInternalServerError, s.t.Get("failed to get the latest version: %v", err))
return
}
v1, err := version.NewVersion(current)
if err != nil {
Error(w, http.StatusInternalServerError, "版本号解析失败")
Error(w, http.StatusInternalServerError, s.t.Get("failed to parse version: %v", err))
return
}
v2, err := version.NewVersion(latest.Version)
if err != nil {
Error(w, http.StatusInternalServerError, "版本号解析失败")
Error(w, http.StatusInternalServerError, s.t.Get("failed to parse version: %v", err))
return
}
if v1.GreaterThanOrEqual(v2) {
Error(w, http.StatusInternalServerError, "当前版本已是最新版本")
Error(w, http.StatusInternalServerError, s.t.Get("the current version is the latest version"))
return
}
versions, err := s.api.IntermediateVersions()
if err != nil {
Error(w, http.StatusInternalServerError, "获取更新信息失败:%v", err)
Error(w, http.StatusInternalServerError, s.t.Get("failed to get the update information: %v", err))
return
}
@@ -294,24 +297,24 @@ func (s *DashboardService) UpdateInfo(w http.ResponseWriter, r *http.Request) {
func (s *DashboardService) Update(w http.ResponseWriter, r *http.Request) {
if offline, _ := s.settingRepo.GetBool(biz.SettingKeyOfflineMode); offline {
Error(w, http.StatusForbidden, "离线模式下无法更新")
Error(w, http.StatusForbidden, s.t.Get("unable to update in offline mode"))
return
}
if s.taskRepo.HasRunningTask() {
Error(w, http.StatusInternalServerError, "后台任务正在运行,禁止更新,请稍后再试")
Error(w, http.StatusInternalServerError, s.t.Get("background task is running, updating is prohibited, please try again later"))
return
}
panel, err := s.api.LatestVersion()
if err != nil {
Error(w, http.StatusInternalServerError, "获取最新版本失败:%v", err)
Error(w, http.StatusInternalServerError, s.t.Get("failed to get the latest version: %v", err))
return
}
download := collect.First(panel.Downloads)
if download == nil {
Error(w, http.StatusInternalServerError, "获取下载链接失败")
Error(w, http.StatusInternalServerError, s.t.Get("failed to get the latest version download link"))
return
}
ver, url, checksum := panel.Version, download.URL, download.Checksum
@@ -330,7 +333,7 @@ func (s *DashboardService) Update(w http.ResponseWriter, r *http.Request) {
func (s *DashboardService) Restart(w http.ResponseWriter, r *http.Request) {
if s.taskRepo.HasRunningTask() {
Error(w, http.StatusInternalServerError, "后台任务正在运行,禁止重启,请稍后再试")
Error(w, http.StatusInternalServerError, s.t.Get("background task is running, restart is prohibited, please try again later"))
return
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/go-rat/chix"
"github.com/go-rat/utils/file"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"github.com/tnb-labs/panel/internal/app"
@@ -29,11 +30,13 @@ import (
)
type FileService struct {
t *gotext.Locale
taskRepo biz.TaskRepo
}
func NewFileService(task biz.TaskRepo) *FileService {
func NewFileService(t *gotext.Locale, task biz.TaskRepo) *FileService {
return &FileService{
t: t,
taskRepo: task,
}
}
@@ -74,11 +77,11 @@ func (s *FileService) Content(w http.ResponseWriter, r *http.Request) {
return
}
if fileInfo.IsDir() {
Error(w, http.StatusInternalServerError, "target is a directory")
Error(w, http.StatusInternalServerError, s.t.Get("target is a directory"))
return
}
if fileInfo.Size() > 10*1024*1024 {
Error(w, http.StatusInternalServerError, "file is too large, please download it")
Error(w, http.StatusInternalServerError, s.t.Get("file is too large, please download it to view"))
return
}
@@ -129,7 +132,7 @@ func (s *FileService) Delete(w http.ResponseWriter, r *http.Request) {
banned := []string{"/", app.Root, filepath.Join(app.Root, "server"), filepath.Join(app.Root, "panel")}
if slices.Contains(banned, req.Path) {
Error(w, http.StatusForbidden, "please don't do this")
Error(w, http.StatusForbidden, s.t.Get("please don't do this"))
return
}
@@ -150,17 +153,17 @@ func (s *FileService) Upload(w http.ResponseWriter, r *http.Request) {
path := r.FormValue("path")
_, handler, err := r.FormFile("file")
if err != nil {
Error(w, http.StatusInternalServerError, "upload file error: %v", err)
Error(w, http.StatusInternalServerError, s.t.Get("upload file error: %v", err))
return
}
if io.Exists(path) {
Error(w, http.StatusForbidden, "target path %s already exists", path)
Error(w, http.StatusForbidden, s.t.Get("target path %s already exists", path))
return
}
if !io.Exists(filepath.Dir(path)) {
if err = stdos.MkdirAll(filepath.Dir(path), 0755); err != nil {
Error(w, http.StatusInternalServerError, "create directory error: %v", err)
Error(w, http.StatusInternalServerError, s.t.Get("create directory error: %v", err))
return
}
}
@@ -168,12 +171,12 @@ func (s *FileService) Upload(w http.ResponseWriter, r *http.Request) {
src, _ := handler.Open()
out, err := stdos.OpenFile(path, stdos.O_CREATE|stdos.O_RDWR|stdos.O_TRUNC, 0644)
if err != nil {
Error(w, http.StatusInternalServerError, "open file error: %v", err)
Error(w, http.StatusInternalServerError, s.t.Get("open file error: %v", err))
return
}
if _, err = stdio.Copy(out, src); err != nil {
Error(w, http.StatusInternalServerError, "write file error: %v", err)
Error(w, http.StatusInternalServerError, s.t.Get("write file error: %v", err))
return
}
@@ -216,7 +219,7 @@ func (s *FileService) Move(w http.ResponseWriter, r *http.Request) {
}
if io.IsDir(item.Source) && strings.HasPrefix(item.Target, item.Source) {
Error(w, http.StatusForbidden, "you can't do this, it will be broken")
Error(w, http.StatusForbidden, s.t.Get("you can't do this, it will be broken"))
return
}
@@ -245,7 +248,7 @@ func (s *FileService) Copy(w http.ResponseWriter, r *http.Request) {
}
if io.IsDir(item.Source) && strings.HasPrefix(item.Target, item.Source) {
Error(w, http.StatusForbidden, "you can't do this, it will be broken")
Error(w, http.StatusForbidden, s.t.Get("you can't do this, it will be broken"))
return
}
@@ -271,7 +274,7 @@ func (s *FileService) Download(w http.ResponseWriter, r *http.Request) {
return
}
if info.IsDir() {
Error(w, http.StatusInternalServerError, "can't download a directory")
Error(w, http.StatusInternalServerError, s.t.Get("can't download a directory"))
return
}
@@ -289,7 +292,7 @@ func (s *FileService) RemoteDownload(w http.ResponseWriter, r *http.Request) {
timestamp := time.Now().Format("20060102150405")
task := new(biz.Task)
task.Name = "下载远程文件"
task.Name = s.t.Get("Download remote file %v", filepath.Base(req.Path))
task.Status = biz.TaskStatusWaiting
task.Shell = fmt.Sprintf(`wget -o /tmp/remote-download-%s.log -O '%s' '%s' && chmod 0755 '%s' && chown www:www '%s'`, timestamp, req.Path, req.URL, req.Path, req.Path)
task.Log = fmt.Sprintf("/tmp/remote-download-%s.log", timestamp)

View File

@@ -3,15 +3,20 @@ package service
import (
"net/http"
"github.com/leonelquinteros/gotext"
"github.com/tnb-labs/panel/internal/http/request"
"github.com/tnb-labs/panel/pkg/systemctl"
)
type SystemctlService struct {
t *gotext.Locale
}
func NewSystemctlService() *SystemctlService {
return &SystemctlService{}
func NewSystemctlService(t *gotext.Locale) *SystemctlService {
return &SystemctlService{
t: t,
}
}
func (s *SystemctlService) Status(w http.ResponseWriter, r *http.Request) {
@@ -23,7 +28,7 @@ func (s *SystemctlService) Status(w http.ResponseWriter, r *http.Request) {
status, err := systemctl.Status(req.Service)
if err != nil {
Error(w, http.StatusInternalServerError, "获取 %s 服务运行状态失败: %v", req.Service, err)
Error(w, http.StatusInternalServerError, s.t.Get("failed to get %s service running status: %v", req.Service, err))
return
}
@@ -39,7 +44,7 @@ func (s *SystemctlService) IsEnabled(w http.ResponseWriter, r *http.Request) {
enabled, err := systemctl.IsEnabled(req.Service)
if err != nil {
Error(w, http.StatusInternalServerError, "获取 %s 服务启用状态失败: %v", req.Service, err)
Error(w, http.StatusInternalServerError, s.t.Get("failed to get %s service enable status: %v", req.Service, err))
return
}
@@ -54,7 +59,7 @@ func (s *SystemctlService) Enable(w http.ResponseWriter, r *http.Request) {
}
if err = systemctl.Enable(req.Service); err != nil {
Error(w, http.StatusInternalServerError, "启用 %s 服务失败: %v", req.Service, err)
Error(w, http.StatusInternalServerError, s.t.Get("failed to enable %s service: %v", req.Service, err))
return
}
@@ -69,7 +74,7 @@ func (s *SystemctlService) Disable(w http.ResponseWriter, r *http.Request) {
}
if err = systemctl.Disable(req.Service); err != nil {
Error(w, http.StatusInternalServerError, "禁用 %s 服务失败: %v", req.Service, err)
Error(w, http.StatusInternalServerError, s.t.Get("failed to disable %s service: %v", req.Service, err))
return
}
@@ -84,7 +89,7 @@ func (s *SystemctlService) Restart(w http.ResponseWriter, r *http.Request) {
}
if err = systemctl.Restart(req.Service); err != nil {
Error(w, http.StatusInternalServerError, "重启 %s 服务失败: %v", req.Service, err)
Error(w, http.StatusInternalServerError, s.t.Get("failed to restart %s service: %v", req.Service, err))
return
}
@@ -99,7 +104,7 @@ func (s *SystemctlService) Reload(w http.ResponseWriter, r *http.Request) {
}
if err = systemctl.Reload(req.Service); err != nil {
Error(w, http.StatusInternalServerError, "重载 %s 服务失败: %v", req.Service, err)
Error(w, http.StatusInternalServerError, s.t.Get("failed to reload %s service: %v", req.Service, err))
return
}
@@ -114,7 +119,7 @@ func (s *SystemctlService) Start(w http.ResponseWriter, r *http.Request) {
}
if err = systemctl.Start(req.Service); err != nil {
Error(w, http.StatusInternalServerError, "启动 %s 服务失败: %v", req.Service, err)
Error(w, http.StatusInternalServerError, s.t.Get("failed to start %s service: %v", req.Service, err))
return
}
@@ -129,7 +134,7 @@ func (s *SystemctlService) Stop(w http.ResponseWriter, r *http.Request) {
}
if err = systemctl.Stop(req.Service); err != nil {
Error(w, http.StatusInternalServerError, "停止 %s 服务失败: %v", req.Service, err)
Error(w, http.StatusInternalServerError, s.t.Get("failed to stop %s service: %v", req.Service, err))
return
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/go-rat/chix"
"github.com/go-rat/sessions"
"github.com/knadh/koanf/v2"
"github.com/leonelquinteros/gotext"
"github.com/spf13/cast"
"github.com/tnb-labs/panel/internal/biz"
@@ -20,14 +21,16 @@ import (
)
type UserService struct {
t *gotext.Locale
conf *koanf.Koanf
session *sessions.Manager
userRepo biz.UserRepo
}
func NewUserService(conf *koanf.Koanf, session *sessions.Manager, user biz.UserRepo) *UserService {
func NewUserService(t *gotext.Locale, conf *koanf.Koanf, session *sessions.Manager, user biz.UserRepo) *UserService {
gob.Register(rsa.PrivateKey{}) // 必须注册 rsa.PrivateKey 类型否则无法反序列化 session 中的 key
return &UserService{
t: t,
conf: conf,
session: session,
userRepo: user,
@@ -72,7 +75,7 @@ func (s *UserService) Login(w http.ResponseWriter, r *http.Request) {
key, ok := sess.Get("key").(rsa.PrivateKey)
if !ok {
Error(w, http.StatusForbidden, "invalid key, please refresh the page")
Error(w, http.StatusForbidden, s.t.Get("invalid key, please refresh the page"))
return
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/gorilla/websocket"
"github.com/knadh/koanf/v2"
"github.com/leonelquinteros/gotext"
stdssh "golang.org/x/crypto/ssh"
"github.com/tnb-labs/panel/internal/biz"
@@ -17,12 +18,14 @@ import (
)
type WsService struct {
t *gotext.Locale
conf *koanf.Koanf
sshRepo biz.SSHRepo
}
func NewWsService(conf *koanf.Koanf, ssh biz.SSHRepo) *WsService {
func NewWsService(t *gotext.Locale, conf *koanf.Koanf, ssh biz.SSHRepo) *WsService {
return &WsService{
t: t,
conf: conf,
sshRepo: ssh,
}
@@ -97,14 +100,14 @@ func (s *WsService) Exec(w http.ResponseWriter, r *http.Request) {
// 第一条消息是命令
_, cmd, err := ws.ReadMessage()
if err != nil {
_ = ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "failed to read command"))
_ = ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, s.t.Get("failed to read command: %v", err)))
return
}
ctx, cancel := context.WithCancel(context.Background())
out, err := shell.ExecfWithPipe(ctx, string(cmd))
if err != nil {
_ = ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "failed to run command"))
_ = ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, s.t.Get("failed to run command: %v", err)))
cancel()
return
}
@@ -116,7 +119,7 @@ func (s *WsService) Exec(w http.ResponseWriter, r *http.Request) {
_ = ws.WriteMessage(websocket.TextMessage, []byte(line))
}
if err = scanner.Err(); err != nil {
_ = ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "failed to read command output"))
_ = ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, s.t.Get("failed to read command output: %v", err)))
}
}()