diff --git a/internal/biz/user.go b/internal/biz/user.go index ff0c2b28..7c42df8e 100644 --- a/internal/biz/user.go +++ b/internal/biz/user.go @@ -1,6 +1,7 @@ package biz import ( + "image" "time" "gorm.io/gorm" @@ -18,8 +19,13 @@ type User struct { } type UserRepo interface { - Create(username, password string) (*User, error) - CheckPassword(username, password string) (*User, error) + List(page, limit uint) ([]*User, int64, error) Get(id uint) (*User, error) - Save(user *User) error + Create(username, password string) (*User, error) + UpdatePassword(id uint, password string) error + Delete(id uint) error + CheckPassword(username, password string) (*User, error) + IsTwoFA(username string) (bool, error) + GenerateTwoFA(id uint) (image.Image, string, string, error) + UpdateTwoFA(id uint, code, secret string) error } diff --git a/internal/data/setting.go b/internal/data/setting.go index 45bc6cfa..2db9b1c1 100644 --- a/internal/data/setting.go +++ b/internal/data/setting.go @@ -10,8 +10,6 @@ import ( "github.com/go-rat/utils/hash" "github.com/knadh/koanf/v2" "github.com/leonelquinteros/gotext" - "github.com/pquerna/otp" - "github.com/pquerna/otp/totp" "github.com/spf13/cast" "gopkg.in/yaml.v3" "gorm.io/gorm" @@ -382,19 +380,3 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P return restartFlag, nil } - -// GetTwoFA 生成两步验证密钥 -// TODO: 即将废弃 -func (r *settingRepo) GetTwoFA() (*otp.Key, error) { - key, err := totp.Generate(totp.GenerateOpts{ - Issuer: "RatPanel", - AccountName: "admin", - SecretSize: 32, - Algorithm: otp.AlgorithmSHA256, - }) - if err != nil { - return nil, err - } - - return key, nil -} diff --git a/internal/data/user.go b/internal/data/user.go index a0aaa73d..9aa62e0f 100644 --- a/internal/data/user.go +++ b/internal/data/user.go @@ -2,9 +2,13 @@ package data import ( "errors" + "image" "github.com/go-rat/utils/hash" "github.com/leonelquinteros/gotext" + "github.com/pquerna/otp" + "github.com/pquerna/otp/totp" + "github.com/spf13/cast" "gorm.io/gorm" "github.com/tnb-labs/panel/internal/biz" @@ -24,6 +28,22 @@ func NewUserRepo(t *gotext.Locale, db *gorm.DB) biz.UserRepo { } } +func (r *userRepo) List(page, limit uint) ([]*biz.User, int64, error) { + users := make([]*biz.User, 0) + var total int64 + err := r.db.Model(&biz.User{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&users).Error + return users, total, err +} + +func (r *userRepo) Get(id uint) (*biz.User, error) { + user := new(biz.User) + if err := r.db.First(user, id).Error; err != nil { + return nil, err + } + + return user, nil +} + func (r *userRepo) Create(username, password string) (*biz.User, error) { value, err := r.hasher.Make(password) if err != nil { @@ -41,6 +61,34 @@ func (r *userRepo) Create(username, password string) (*biz.User, error) { return user, nil } +func (r *userRepo) UpdatePassword(id uint, password string) error { + value, err := r.hasher.Make(password) + if err != nil { + return err + } + + user, err := r.Get(id) + if err != nil { + return err + } + + user.Password = value + return r.db.Save(user).Error +} + +func (r *userRepo) Delete(id uint) error { + if id == 1 { + return errors.New(r.t.Get("please don't do this")) + } + + user := new(biz.User) + if err := r.db.First(user, id).Error; err != nil { + return err + } + + return r.db.Delete(user).Error +} + 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 { @@ -58,15 +106,66 @@ func (r *userRepo) CheckPassword(username, password string) (*biz.User, error) { return user, nil } -func (r *userRepo) Get(id uint) (*biz.User, error) { +func (r *userRepo) IsTwoFA(username string) (bool, error) { user := new(biz.User) - if err := r.db.First(user, id).Error; err != nil { - return nil, err + if err := r.db.Where("username = ?", username).First(user).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return false, errors.New(r.t.Get("username or password error")) + } else { + return false, err + } } - return user, nil + return user.TwoFA != "", nil } -func (r *userRepo) Save(user *biz.User) error { +func (r *userRepo) GenerateTwoFA(id uint) (image.Image, string, string, error) { + key, err := totp.Generate(totp.GenerateOpts{ + Issuer: "RatPanel", + AccountName: cast.ToString(id), + SecretSize: 32, + Algorithm: otp.AlgorithmSHA256, + }) + if err != nil { + return nil, "", "", err + } + + img, err := key.Image(200, 200) + if err != nil { + return nil, "", "", err + } + + return img, key.URL(), key.Secret(), nil +} + +func (r *userRepo) UpdateTwoFA(id uint, code, secret string) error { + user, err := r.Get(id) + if err != nil { + return err + } + + // 保存前先验证一次,防止错误开启 + if !totp.Validate(code, secret) { + return errors.New(r.t.Get("invalid 2fa code")) + } + + user.TwoFA = secret return r.db.Save(user).Error } + +func (r *userRepo) CheckTwoFA(id uint, code string) (bool, error) { + user, err := r.Get(id) + if err != nil { + return false, err + } + + if user.TwoFA == "" { + return true, nil // 未开启2FA,无需验证 + } + + if !totp.Validate(code, user.TwoFA) { + return false, errors.New(r.t.Get("invalid 2fa code")) + } + + return true, nil +} diff --git a/internal/http/request/user.go b/internal/http/request/user.go index 59dbf7af..5301c406 100644 --- a/internal/http/request/user.go +++ b/internal/http/request/user.go @@ -1,7 +1,37 @@ package request type UserLogin struct { - Username string `json:"username" form:"username" validate:"required"` - Password string `json:"password" form:"password" validate:"required"` - SafeLogin bool `json:"safe_login" form:"safe_login"` + Username string `json:"username" validate:"required"` + Password string `json:"password" validate:"required"` + SafeLogin bool `json:"safe_login"` + PassCode string `json:"pass_code"` +} + +type UserIsTwoFA struct { + Username string `uri:"username" validate:"required"` +} + +type UserCreate struct { + Username string `json:"username" validate:"required|notExists:users,username"` + Password string `json:"password" validate:"required|password"` +} + +type UserUpdatePassword struct { + ID uint `json:"id" validate:"required|exists:users,id"` + Password string `json:"password" validate:"required|password"` +} + +type UserUpdateEmail struct { + ID uint `json:"id" validate:"required|exists:users,id"` + TwoFA string `json:"two_fa" validate:"required"` +} + +type UserUpdateTwoFA struct { + ID uint `json:"id" validate:"required|exists:users,id"` + TwoFA string `json:"two_fa" validate:"required"` + Code string `json:"code" validate:"required"` +} + +type UserDelete struct { + ID uint `json:"id" validate:"required|exists:users,id"` } diff --git a/internal/service/user.go b/internal/service/user.go index d3ed2c1c..26d52c9c 100644 --- a/internal/service/user.go +++ b/internal/service/user.go @@ -13,6 +13,7 @@ import ( "github.com/go-rat/sessions" "github.com/knadh/koanf/v2" "github.com/leonelquinteros/gotext" + "github.com/pquerna/otp/totp" "github.com/spf13/cast" "github.com/tnb-labs/panel/internal/biz" @@ -87,6 +88,13 @@ func (s *UserService) Login(w http.ResponseWriter, r *http.Request) { return } + if user.TwoFA != "" { + if !totp.Validate(req.PassCode, user.TwoFA) { + Error(w, http.StatusForbidden, s.t.Get("invalid 2fa code")) + return + } + } + // 安全登录下,将当前客户端与会话绑定 // 安全登录只在未启用面板 HTTPS 时生效 ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr)) @@ -128,6 +136,17 @@ func (s *UserService) IsLogin(w http.ResponseWriter, r *http.Request) { Success(w, sess.Has("user_id")) } +func (s *UserService) IsTwoFA(w http.ResponseWriter, r *http.Request) { + req, err := Bind[request.UserIsTwoFA](r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, "%v", err) + return + } + + twoFA, _ := s.userRepo.IsTwoFA(req.Username) + Success(w, twoFA) +} + func (s *UserService) Info(w http.ResponseWriter, r *http.Request) { userID := cast.ToUint(r.Context().Value("user_id")) if userID == 0 { @@ -148,3 +167,53 @@ func (s *UserService) Info(w http.ResponseWriter, r *http.Request) { "email": user.Email, }) } + +func (s *UserService) List(w http.ResponseWriter, r *http.Request) { + req, err := Bind[request.Paginate](r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, "%v", err) + return + } + + users, total, err := s.userRepo.List(req.Page, req.Limit) + if err != nil { + Error(w, http.StatusInternalServerError, "%v", err) + return + } + + Success(w, chix.M{ + "total": total, + "items": users, + }) +} + +func (s *UserService) Create(w http.ResponseWriter, r *http.Request) { + req, err := Bind[request.UserCreate](r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, "%v", err) + return + } + + user, err := s.userRepo.Create(req.Username, req.Password) + if err != nil { + Error(w, http.StatusInternalServerError, "%v", err) + return + } + + Success(w, user) +} + +func (s *UserService) UpdatePassword(w http.ResponseWriter, r *http.Request) { + req, err := Bind[request.UserUpdatePassword](r) + if err != nil { + Error(w, http.StatusUnprocessableEntity, "%v", err) + return + } + + if err = s.userRepo.UpdatePassword(req.ID, req.Password); err != nil { + Error(w, http.StatusInternalServerError, "%v", err) + return + } + + Success(w, nil) +} diff --git a/mocks/biz/SettingRepo.go b/mocks/biz/SettingRepo.go index 8c71783b..1ced4c34 100644 --- a/mocks/biz/SettingRepo.go +++ b/mocks/biz/SettingRepo.go @@ -9,6 +9,8 @@ import ( mock "github.com/stretchr/testify/mock" + otp "github.com/pquerna/otp" + request "github.com/tnb-labs/panel/internal/http/request" ) @@ -71,6 +73,118 @@ func (_c *SettingRepo_Delete_Call) RunAndReturn(run func(biz.SettingKey) error) return _c } +// GenerateAPIKey provides a mock function with no fields +func (_m *SettingRepo) GenerateAPIKey() (string, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GenerateAPIKey") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func() (string, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SettingRepo_GenerateAPIKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GenerateAPIKey' +type SettingRepo_GenerateAPIKey_Call struct { + *mock.Call +} + +// GenerateAPIKey is a helper method to define mock.On call +func (_e *SettingRepo_Expecter) GenerateAPIKey() *SettingRepo_GenerateAPIKey_Call { + return &SettingRepo_GenerateAPIKey_Call{Call: _e.mock.On("GenerateAPIKey")} +} + +func (_c *SettingRepo_GenerateAPIKey_Call) Run(run func()) *SettingRepo_GenerateAPIKey_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *SettingRepo_GenerateAPIKey_Call) Return(_a0 string, _a1 error) *SettingRepo_GenerateAPIKey_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *SettingRepo_GenerateAPIKey_Call) RunAndReturn(run func() (string, error)) *SettingRepo_GenerateAPIKey_Call { + _c.Call.Return(run) + return _c +} + +// GenerateTwoFAKey provides a mock function with no fields +func (_m *SettingRepo) GenerateTwoFAKey() (*otp.Key, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GenerateTwoFAKey") + } + + var r0 *otp.Key + var r1 error + if rf, ok := ret.Get(0).(func() (*otp.Key, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() *otp.Key); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*otp.Key) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SettingRepo_GenerateTwoFAKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GenerateTwoFAKey' +type SettingRepo_GenerateTwoFAKey_Call struct { + *mock.Call +} + +// GenerateTwoFAKey is a helper method to define mock.On call +func (_e *SettingRepo_Expecter) GenerateTwoFAKey() *SettingRepo_GenerateTwoFAKey_Call { + return &SettingRepo_GenerateTwoFAKey_Call{Call: _e.mock.On("GenerateTwoFAKey")} +} + +func (_c *SettingRepo_GenerateTwoFAKey_Call) Run(run func()) *SettingRepo_GenerateTwoFAKey_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *SettingRepo_GenerateTwoFAKey_Call) Return(_a0 *otp.Key, _a1 error) *SettingRepo_GenerateTwoFAKey_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *SettingRepo_GenerateTwoFAKey_Call) RunAndReturn(run func() (*otp.Key, error)) *SettingRepo_GenerateTwoFAKey_Call { + _c.Call.Return(run) + return _c +} + // Get provides a mock function with given fields: key, defaultValue func (_m *SettingRepo) Get(key biz.SettingKey, defaultValue ...string) (string, error) { _va := make([]interface{}, len(defaultValue)) diff --git a/mocks/biz/UserTokenRepo.go b/mocks/biz/UserTokenRepo.go deleted file mode 100644 index 275a7e53..00000000 --- a/mocks/biz/UserTokenRepo.go +++ /dev/null @@ -1,198 +0,0 @@ -// Code generated by mockery. DO NOT EDIT. - -package biz - -import ( - mock "github.com/stretchr/testify/mock" - biz "github.com/tnb-labs/panel/internal/biz" -) - -// UserTokenRepo is an autogenerated mock type for the UserTokenRepo type -type UserTokenRepo struct { - mock.Mock -} - -type UserTokenRepo_Expecter struct { - mock *mock.Mock -} - -func (_m *UserTokenRepo) EXPECT() *UserTokenRepo_Expecter { - return &UserTokenRepo_Expecter{mock: &_m.Mock} -} - -// Create provides a mock function with given fields: userID, ips -func (_m *UserTokenRepo) Create(userID uint, ips []string) (*biz.UserToken, error) { - ret := _m.Called(userID, ips) - - if len(ret) == 0 { - panic("no return value specified for Create") - } - - var r0 *biz.UserToken - var r1 error - if rf, ok := ret.Get(0).(func(uint, []string) (*biz.UserToken, error)); ok { - return rf(userID, ips) - } - if rf, ok := ret.Get(0).(func(uint, []string) *biz.UserToken); ok { - r0 = rf(userID, ips) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*biz.UserToken) - } - } - - if rf, ok := ret.Get(1).(func(uint, []string) error); ok { - r1 = rf(userID, ips) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UserTokenRepo_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' -type UserTokenRepo_Create_Call struct { - *mock.Call -} - -// Create is a helper method to define mock.On call -// - userID uint -// - ips []string -func (_e *UserTokenRepo_Expecter) Create(userID interface{}, ips interface{}) *UserTokenRepo_Create_Call { - return &UserTokenRepo_Create_Call{Call: _e.mock.On("Create", userID, ips)} -} - -func (_c *UserTokenRepo_Create_Call) Run(run func(userID uint, ips []string)) *UserTokenRepo_Create_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(uint), args[1].([]string)) - }) - return _c -} - -func (_c *UserTokenRepo_Create_Call) Return(_a0 *biz.UserToken, _a1 error) *UserTokenRepo_Create_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *UserTokenRepo_Create_Call) RunAndReturn(run func(uint, []string) (*biz.UserToken, error)) *UserTokenRepo_Create_Call { - _c.Call.Return(run) - return _c -} - -// Get provides a mock function with given fields: id -func (_m *UserTokenRepo) Get(id uint) (*biz.UserToken, error) { - ret := _m.Called(id) - - if len(ret) == 0 { - panic("no return value specified for Get") - } - - var r0 *biz.UserToken - var r1 error - if rf, ok := ret.Get(0).(func(uint) (*biz.UserToken, error)); ok { - return rf(id) - } - if rf, ok := ret.Get(0).(func(uint) *biz.UserToken); ok { - r0 = rf(id) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*biz.UserToken) - } - } - - if rf, ok := ret.Get(1).(func(uint) error); ok { - r1 = rf(id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// UserTokenRepo_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' -type UserTokenRepo_Get_Call struct { - *mock.Call -} - -// Get is a helper method to define mock.On call -// - id uint -func (_e *UserTokenRepo_Expecter) Get(id interface{}) *UserTokenRepo_Get_Call { - return &UserTokenRepo_Get_Call{Call: _e.mock.On("Get", id)} -} - -func (_c *UserTokenRepo_Get_Call) Run(run func(id uint)) *UserTokenRepo_Get_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(uint)) - }) - return _c -} - -func (_c *UserTokenRepo_Get_Call) Return(_a0 *biz.UserToken, _a1 error) *UserTokenRepo_Get_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *UserTokenRepo_Get_Call) RunAndReturn(run func(uint) (*biz.UserToken, error)) *UserTokenRepo_Get_Call { - _c.Call.Return(run) - return _c -} - -// Save provides a mock function with given fields: user -func (_m *UserTokenRepo) Save(user *biz.UserToken) error { - ret := _m.Called(user) - - if len(ret) == 0 { - panic("no return value specified for Save") - } - - var r0 error - if rf, ok := ret.Get(0).(func(*biz.UserToken) error); ok { - r0 = rf(user) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// UserTokenRepo_Save_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Save' -type UserTokenRepo_Save_Call struct { - *mock.Call -} - -// Save is a helper method to define mock.On call -// - user *biz.UserToken -func (_e *UserTokenRepo_Expecter) Save(user interface{}) *UserTokenRepo_Save_Call { - return &UserTokenRepo_Save_Call{Call: _e.mock.On("Save", user)} -} - -func (_c *UserTokenRepo_Save_Call) Run(run func(user *biz.UserToken)) *UserTokenRepo_Save_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*biz.UserToken)) - }) - return _c -} - -func (_c *UserTokenRepo_Save_Call) Return(_a0 error) *UserTokenRepo_Save_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *UserTokenRepo_Save_Call) RunAndReturn(run func(*biz.UserToken) error) *UserTokenRepo_Save_Call { - _c.Call.Return(run) - return _c -} - -// NewUserTokenRepo creates a new instance of UserTokenRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewUserTokenRepo(t interface { - mock.TestingT - Cleanup(func()) -}) *UserTokenRepo { - mock := &UserTokenRepo{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/embed/locales/backend.pot b/pkg/embed/locales/backend.pot index f5d59073..ce0cdb54 100644 --- a/pkg/embed/locales/backend.pot +++ b/pkg/embed/locales/backend.pot @@ -495,10 +495,10 @@ msgstr "" msgid "Index Hit Rate" msgstr "" -#: internal/service/cli.go:831 -#: internal/service/cli.go:836 -#: internal/service/cli.go:841 +#: internal/service/cli.go:840 #: internal/service/cli.go:845 +#: internal/service/cli.go:850 +#: internal/service/cli.go:854 msgid "Initialization failed: %v" msgstr "" @@ -1196,10 +1196,6 @@ msgstr "" msgid "Zip is a library for handling ZIP files" msgstr "" -#: internal/http/middleware/must_login.go:94 -msgid "api signature expired" -msgstr "" - #: internal/data/app.go:159 msgid "app %s already installed" msgstr "" @@ -1210,7 +1206,6 @@ msgstr "" #: internal/data/app.go:210 #: internal/data/app.go:265 -#: internal/http/middleware/must_install.go:48 msgid "app %s not installed" msgstr "" @@ -1225,12 +1220,8 @@ msgstr "" msgid "app %s requires panel version %s, current version %s" msgstr "" -#: internal/http/middleware/must_install.go:29 -msgid "app not found" -msgstr "" - -#: internal/data/setting.go:314 -#: internal/data/setting.go:375 +#: internal/data/setting.go:354 +#: internal/data/setting.go:412 msgid "background task is running, modifying some settings is prohibited, please try again later" msgstr "" @@ -1261,10 +1252,6 @@ msgstr "" msgid "check server connection failed" msgstr "" -#: internal/http/middleware/must_login.go:122 -msgid "client ip/ua changed, please login again" -msgstr "" - #: internal/data/backup.go:564 msgid "could not find .sql backup file" msgstr "" @@ -1522,12 +1509,12 @@ msgid "failed to load MySQL root password: %v" msgstr "" #: internal/data/cert.go:92 -#: internal/data/setting.go:319 +#: internal/data/setting.go:359 msgid "failed to parse certificate: %v" msgstr "" #: internal/data/cert.go:95 -#: internal/data/setting.go:322 +#: internal/data/setting.go:362 msgid "failed to parse private key: %v" msgstr "" @@ -1639,34 +1626,10 @@ msgstr "" msgid "get service port failed, please check if it is installed" msgstr "" -#: internal/http/middleware/entrance.go:101 -msgid "invalid access entrance" -msgstr "" - -#: internal/http/middleware/must_login.go:84 -msgid "invalid api signature" -msgstr "" - #: internal/service/user.go:78 msgid "invalid key, please refresh the page" msgstr "" -#: internal/http/middleware/entrance.go:50 -msgid "invalid request domain: %s" -msgstr "" - -#: internal/http/middleware/entrance.go:63 -msgid "invalid request ip: %s" -msgstr "" - -#: internal/http/middleware/entrance.go:72 -msgid "invalid request user agent: %s" -msgstr "" - -#: internal/http/middleware/must_login.go:136 -msgid "invalid user id, please login again" -msgstr "" - #: internal/apps/php/app.go:473 msgid "ionCube is a professional-grade PHP encryption and decryption tool (must be installed after OPcache)" msgstr "" @@ -1712,22 +1675,6 @@ msgstr "" msgid "open file error: %v" msgstr "" -#: internal/http/middleware/status.go:38 -msgid "panel is closed" -msgstr "" - -#: internal/http/middleware/status.go:30 -msgid "panel is maintaining, please refresh later" -msgstr "" - -#: internal/http/middleware/status.go:22 -msgid "panel is upgrading, please refresh later" -msgstr "" - -#: internal/http/middleware/status.go:46 -msgid "panel run error, please check or contact support" -msgstr "" - #: internal/apps/php/app.go:328 msgid "pdo_pgsql is a PDO driver for connecting to PostgreSQL (requires PostgreSQL installed)" msgstr "" @@ -1759,7 +1706,7 @@ msgstr "" msgid "please retry the manual obtain operation" msgstr "" -#: internal/data/setting.go:343 +#: internal/data/setting.go:383 msgid "port is already in use" msgstr "" @@ -1792,10 +1739,6 @@ msgstr "" msgid "runtime directory does not exist" msgstr "" -#: internal/http/middleware/must_login.go:107 -msgid "session expired, please login again" -msgstr "" - #: internal/apps/php/app.go:333 msgid "sqlsrv is a driver for connecting to SQL Server" msgstr "" diff --git a/web/src/locales/frontend.pot b/web/src/locales/frontend.pot index 98c75d4f..a33bd31b 100644 --- a/web/src/locales/frontend.pot +++ b/web/src/locales/frontend.pot @@ -64,7 +64,8 @@ msgstr "" #: src/views/apps/toolbox/IndexView.vue:66 #: src/views/apps/toolbox/IndexView.vue:72 #: src/views/apps/toolbox/IndexView.vue:81 -#: src/views/setting/IndexView.vue:46 +#: src/views/setting/SettingBase.vue:39 +#: src/views/setting/SettingHttps.vue:27 #: src/views/website/EditView.vue:115 msgid "Saved successfully" msgstr "" @@ -369,7 +370,7 @@ msgstr "" #: src/views/cert/CertView.vue:497 #: src/views/cert/CertView.vue:573 #: src/views/cert/UploadCertModal.vue:38 -#: src/views/setting/SettingSafe.vue:54 +#: src/views/setting/SettingHttps.vue:45 #: src/views/website/EditView.vue:355 msgid "Certificate" msgstr "" @@ -911,7 +912,8 @@ msgstr "" #: src/views/apps/toolbox/IndexView.vue:109 #: src/views/file/EditModal.vue:31 #: src/views/file/ListTable.vue:723 -#: src/views/setting/IndexView.vue:63 +#: src/views/setting/SettingBase.vue:97 +#: src/views/setting/SettingHttps.vue:62 #: src/views/website/EditView.vue:215 msgid "Save" msgstr "" @@ -1609,7 +1611,7 @@ msgstr "" #: src/views/database/UpdateServerModal.vue:86 #: src/views/database/UserList.vue:40 #: src/views/login/IndexView.vue:115 -#: src/views/setting/SettingBase.vue:49 +#: src/views/setting/SettingBase.vue:67 #: src/views/ssh/CreateModal.vue:83 #: src/views/ssh/UpdateModal.vue:89 msgid "Username" @@ -1675,7 +1677,7 @@ msgstr "" #: src/views/database/UpdateUserModal.vue:49 #: src/views/database/UserList.vue:50 #: src/views/login/IndexView.vue:123 -#: src/views/setting/SettingBase.vue:52 +#: src/views/setting/SettingBase.vue:70 #: src/views/ssh/CreateModal.vue:77 #: src/views/ssh/CreateModal.vue:86 #: src/views/ssh/UpdateModal.vue:83 @@ -2290,7 +2292,7 @@ msgstr "" #: src/views/cert/CertView.vue:509 #: src/views/cert/CertView.vue:585 #: src/views/cert/UploadCertModal.vue:46 -#: src/views/setting/SettingSafe.vue:61 +#: src/views/setting/SettingHttps.vue:52 #: src/views/ssh/CreateModal.vue:78 #: src/views/ssh/CreateModal.vue:89 #: src/views/ssh/UpdateModal.vue:84 @@ -3478,7 +3480,7 @@ msgstr "" #: src/views/database/UpdateServerModal.vue:76 #: src/views/firewall/ForwardView.vue:32 #: src/views/firewall/RuleView.vue:49 -#: src/views/setting/SettingBase.vue:58 +#: src/views/setting/SettingBase.vue:76 #: src/views/ssh/CreateModal.vue:68 #: src/views/ssh/UpdateModal.vue:74 #: src/views/website/IndexView.vue:420 @@ -4126,115 +4128,78 @@ msgstr "" msgid "Time Selection" msgstr "" -#: src/views/setting/IndexView.vue:49 -msgid "Panel is restarting, page will refresh in 3 seconds" -msgstr "" - -#: src/views/setting/IndexView.vue:67 +#: src/views/setting/IndexView.vue:17 msgid "Basic" msgstr "" -#: src/views/setting/IndexView.vue:70 -msgid "Safe" -msgstr "" - -#: src/views/setting/SettingBase.vue:20 -msgid "Stable" -msgstr "" - -#: src/views/setting/SettingBase.vue:24 -msgid "Beta" -msgstr "" - -#: src/views/setting/SettingBase.vue:34 -msgid "Modifying panel port/entrance requires corresponding changes in the browser address bar to access the panel!" -msgstr "" - -#: src/views/setting/SettingBase.vue:40 -#: src/views/setting/SettingBase.vue:41 -msgid "Panel Name" -msgstr "" - -#: src/views/setting/SettingBase.vue:43 -msgid "Language" -msgstr "" - -#: src/views/setting/SettingBase.vue:46 -msgid "Update Channel" -msgstr "" - -#: src/views/setting/SettingBase.vue:50 -#: src/views/setting/SettingBase.vue:53 -#: src/views/setting/SettingSafe.vue:26 -msgid "admin" +#: src/views/setting/SettingBase.vue:42 +msgid "Panel is restarting, page will refresh in 3 seconds" msgstr "" #: src/views/setting/SettingBase.vue:55 -msgid "Certificate Default Email" -msgstr "" - -#: src/views/setting/SettingBase.vue:56 -msgid "admin@yourdomain.com" -msgstr "" - -#: src/views/setting/SettingBase.vue:59 -msgid "8888" +msgid "Modifying panel port/entrance requires corresponding changes in the browser address bar to access the panel!" msgstr "" #: src/views/setting/SettingBase.vue:61 -msgid "Default Website Directory" -msgstr "" - #: src/views/setting/SettingBase.vue:62 -msgid "/www/wwwroot" +msgid "Panel Name" msgstr "" #: src/views/setting/SettingBase.vue:64 -msgid "Default Backup Directory" +msgid "Language" msgstr "" -#: src/views/setting/SettingBase.vue:65 -msgid "/www/backup" +#: src/views/setting/SettingBase.vue:68 +#: src/views/setting/SettingBase.vue:71 +#: src/views/setting/SettingBase.vue:80 +msgid "admin" msgstr "" -#: src/views/setting/SettingSafe.vue:12 -msgid "Login Timeout" +#: src/views/setting/SettingBase.vue:73 +msgid "Certificate Default Email" msgstr "" -#: src/views/setting/SettingSafe.vue:15 -msgid "120" +#: src/views/setting/SettingBase.vue:74 +msgid "admin@yourdomain.com" msgstr "" -#: src/views/setting/SettingSafe.vue:21 -#: src/views/website/ProxyBuilderModal.vue:188 -msgid "minutes" +#: src/views/setting/SettingBase.vue:77 +msgid "8888" msgstr "" -#: src/views/setting/SettingSafe.vue:25 +#: src/views/setting/SettingBase.vue:79 msgid "Access Entrance" msgstr "" -#: src/views/setting/SettingSafe.vue:28 -msgid "Bind Domain" -msgstr "" - -#: src/views/setting/SettingSafe.vue:35 -msgid "Bind IP" -msgstr "" - -#: src/views/setting/SettingSafe.vue:38 -msgid "Bind UA" -msgstr "" - -#: src/views/setting/SettingSafe.vue:45 +#: src/views/setting/SettingBase.vue:82 msgid "Offline Mode" msgstr "" -#: src/views/setting/SettingSafe.vue:48 +#: src/views/setting/SettingBase.vue:85 msgid "Auto Update" msgstr "" -#: src/views/setting/SettingSafe.vue:51 +#: src/views/setting/SettingBase.vue:88 +msgid "Default Website Directory" +msgstr "" + +#: src/views/setting/SettingBase.vue:89 +msgid "/www/wwwroot" +msgstr "" + +#: src/views/setting/SettingBase.vue:91 +msgid "Default Backup Directory" +msgstr "" + +#: src/views/setting/SettingBase.vue:92 +msgid "/www/backup" +msgstr "" + +#: src/views/setting/SettingHttps.vue:36 +msgid "Incorrect certificates may cause the panel to be inaccessible. Please proceed with caution!" +msgstr "" + +#: src/views/setting/SettingHttps.vue:42 msgid "Panel HTTPS" msgstr "" @@ -4752,6 +4717,10 @@ msgstr "" msgid "Cache time (minutes)" msgstr "" +#: src/views/website/ProxyBuilderModal.vue:188 +msgid "minutes" +msgstr "" + #: src/views/website/ProxyBuilderModal.vue:191 msgid "Content Replacement" msgstr ""