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
+15 -12
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
+15 -13
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
+1 -1
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
+10 -19
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"))
}
+5 -2
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 {
+10 -6
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)
}
+5 -2
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
+1 -6
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
}
+9 -7
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
}
+8 -4
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{
+8 -4
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 {
+6 -3
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
+1 -1
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 {
+5 -2
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
}
+8 -5
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
}
+24 -21
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),
},
})
+33 -29
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 {
+25 -22
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
}
+16 -13
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)
+15 -10
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
}
+5 -2
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
}
+7 -4
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)))
}
}()