From a8170a499a90e926bab62e8031a96c0c6ed6fef4 Mon Sep 17 00:00:00 2001 From: ssongliu <73214554+ssongliu@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:57:38 +0800 Subject: [PATCH] feat: Add support for multiple host license (#7296) --- agent/app/api/v2/health.go | 2 +- agent/app/service/backup.go | 4 +- agent/configs/system.go | 9 +- agent/constant/errs.go | 10 +- agent/i18n/lang/en.yaml | 10 - agent/i18n/lang/zh-Hant.yaml | 10 - agent/i18n/lang/zh.yaml | 10 - agent/init/hook/hook.go | 4 +- agent/server/server.go | 17 +- agent/utils/xpack/xpack.go | 1 - core/app/repo/common.go | 52 +-- core/app/service/app_launcher.go | 7 +- core/app/service/auth.go | 31 +- core/app/service/backup.go | 27 +- core/app/service/command.go | 23 +- core/app/service/entry.go | 1 - core/app/service/group.go | 15 +- core/app/service/host.go | 17 +- core/app/service/logs.go | 9 +- core/app/service/setting.go | 11 +- core/app/service/task.go | 7 +- core/app/service/upgrade.go | 7 +- core/buserr/errors.go | 12 + core/constant/errs.go | 19 + core/constant/status.go | 5 + core/i18n/lang/en.yaml | 17 + core/i18n/lang/zh-Hant.yaml | 17 + core/i18n/lang/zh.yaml | 18 +- core/init/db/db.go | 82 +--- core/init/hook/hook.go | 15 +- core/middleware/bind_domain.go | 3 +- core/middleware/helper.go | 3 +- core/middleware/ip_limit.go | 3 +- core/middleware/loading.go | 3 +- core/middleware/password_expired.go | 5 +- core/middleware/proxy.go | 49 ++- core/middleware/session.go | 5 +- core/utils/common/common.go | 23 ++ core/utils/common/sqlite.go | 86 +++++ core/utils/http/new.go | 2 +- core/utils/jwt/jwt.go | 3 +- core/utils/xpack/xpack.go | 4 +- frontend/src/api/interface/setting.ts | 5 + frontend/src/api/modules/setting.ts | 33 +- .../src/components/system-upgrade/index.vue | 6 +- frontend/src/global/use-theme.ts | 2 +- frontend/src/lang/modules/en.ts | 34 +- frontend/src/lang/modules/tw.ts | 26 +- frontend/src/lang/modules/zh.ts | 24 +- .../src/layout/components/Sidebar/index.vue | 48 ++- frontend/src/layout/index.vue | 3 +- frontend/src/store/interface/index.ts | 1 + frontend/src/store/modules/global.ts | 5 +- frontend/src/utils/xpack.ts | 33 +- .../src/views/setting/license/bind/index.vue | 97 +++++ .../views/setting/license/delete/index.vue | 74 ++++ frontend/src/views/setting/license/index.vue | 351 ++++++++++-------- frontend/src/views/setting/panel/index.vue | 8 +- .../src/views/setting/panel/proxy/index.vue | 6 +- 59 files changed, 870 insertions(+), 544 deletions(-) create mode 100644 core/utils/common/sqlite.go create mode 100644 frontend/src/views/setting/license/bind/index.vue create mode 100644 frontend/src/views/setting/license/delete/index.vue diff --git a/agent/app/api/v2/health.go b/agent/app/api/v2/health.go index 57cf547bc..4dbfd6ece 100644 --- a/agent/app/api/v2/health.go +++ b/agent/app/api/v2/health.go @@ -9,7 +9,7 @@ import ( ) func (b *BaseApi) CheckHealth(c *gin.Context) { - _, err := xpack.RequestToMaster("/api/v2/agent/health", http.MethodGet, nil) + _, err := xpack.RequestToMaster("/api/v2/agent/xpack/health", http.MethodGet, nil) if err != nil { helper.InternalServer(c, err) return diff --git a/agent/app/service/backup.go b/agent/app/service/backup.go index 161241dc5..8ce775b60 100644 --- a/agent/app/service/backup.go +++ b/agent/app/service/backup.go @@ -293,7 +293,7 @@ func NewBackupClientWithID(id uint) (*model.BackupAccount, cloud_storage.CloudSt if err != nil { return nil, nil, err } - data, err := xpack.RequestToMaster("/api/v2/agent/backup", http.MethodPost, bytes.NewReader(bodyItem)) + data, err := xpack.RequestToMaster("/api/v2/agent/xpack/backup", http.MethodPost, bytes.NewReader(bodyItem)) if err != nil { return nil, nil, err } @@ -358,7 +358,7 @@ func NewBackupClientMap(ids []string) (map[string]backupClientHelper, error) { if err != nil { return nil, err } - data, err := xpack.RequestToMaster("/api/v2/agent/backup/list", http.MethodPost, bytes.NewReader(bodyItem)) + data, err := xpack.RequestToMaster("/api/v2/agent/xpack/backup/list", http.MethodPost, bytes.NewReader(bodyItem)) if err != nil { return nil, err } diff --git a/agent/configs/system.go b/agent/configs/system.go index bbdb1b694..413be7ee2 100644 --- a/agent/configs/system.go +++ b/agent/configs/system.go @@ -3,10 +3,11 @@ package configs type System struct { Mode string `mapstructure:"mode"` - Version string `mapstructure:"version"` - BaseDir string `mapstructure:"base_dir"` - MasterAddr string `mapstructure:"master_addr"` - EncryptKey string `mapstructure:"encrypt_key"` + Version string `mapstructure:"version"` + BaseDir string `mapstructure:"base_dir"` + CurrentNode string `mapstructure:"base_dir"` + MasterAddr string `mapstructure:"master_addr"` + EncryptKey string `mapstructure:"encrypt_key"` DbFile string `mapstructure:"db_agent_file"` DbPath string `mapstructure:"db_path"` diff --git a/agent/constant/errs.go b/agent/constant/errs.go index f7c7a0281..93291f061 100644 --- a/agent/constant/errs.go +++ b/agent/constant/errs.go @@ -30,7 +30,7 @@ var ( ErrDemoEnvironment = "ErrDemoEnvironment" ErrCmdIllegal = "ErrCmdIllegal" ErrXpackNotFound = "ErrXpackNotFound" - ErrXpackNotActive = "ErrXpackNotActive" + ErrXpackExceptional = "ErrXpackExceptional" ErrXpackOutOfDate = "ErrXpackOutOfDate" ) @@ -134,11 +134,3 @@ var ( var ( ErrNotExistUser = "ErrNotExistUser" ) - -// license -var ( - ErrLicense = "ErrLicense" - ErrLicenseCheck = "ErrLicenseCheck" - ErrLicenseSave = "ErrLicenseSave" - ErrLicenseSync = "ErrLicenseSync" -) diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index e0d67eeae..517882e8f 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -240,16 +240,6 @@ cc: 'Access Frequency Limit' defaultUrlBlack: 'URL Rules' sqlInject: 'SQL Injection' - -#license -ErrLicense: "License format error, please check and try again!" -ErrLicenseCheck: "License verification failed, please check and try again!" -ErrLicenseSave: "Failed to save license information, error {{ .err }}, please try again!" -ErrLicenseSync: "Failed to sync license information, no license information detected in the database!" -ErrXpackNotFound: "This section is a professional edition feature, please import the license first in Panel Settings-License interface" -ErrXpackNotActive: "This section is a professional edition feature, please synchronize the license status first in Panel Settings-License interface" -ErrXpackOutOfDate: "The current license has expired, please re-import the license in Panel Settings-License interface" - #task TaskStart: "{{.name}} Start [START]" TaskEnd: "{{.name}} End [COMPLETED]" diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index d1b83a9b9..28418c23d 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -241,16 +241,6 @@ cc: '訪問頻率限制' defaultUrlBlack: 'URL 規則' sqlInject: 'SQL 注入' - -#license -ErrLicense: "許可證格式錯誤,請檢查後重試!" -ErrLicenseCheck: "許可證校驗失敗,請檢查後重試!" -ErrLicenseSave: "許可證信息保存失敗,錯誤 {{ .err }}, 請重試!" -ErrLicenseSync: "許可證信息同步失敗,資料庫中未檢測到許可證信息!" -ErrXpackNotFound: "該部分為專業版功能,請先在 面板設置-許可證 界面導入許可證" -ErrXpackNotActive: "該部分為專業版功能,請先在 面板設置-許可證 界面同步許可證狀態" -ErrXpackOutOfDate: "當前許可證已過期,請重新在 面板設置-許可證 界面導入許可證" - #task TaskStart: "{{.name}} 開始 [START]" TaskEnd: "{{.name}} 結束 [COMPLETED]" diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index 5d8407862..820f793bc 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -239,16 +239,6 @@ cc: '访问频率限制' defaultUrlBlack: 'URL 规则' sqlInject: 'SQL 注入' - -#license -ErrLicense: "许可证格式错误,请检查后重试!" -ErrLicenseCheck: "许可证校验失败,请检查后重试!" -ErrLicenseSave: "许可证信息保存失败,错误 {{ .err }},请重试!" -ErrLicenseSync: "许可证信息同步失败,数据库中未检测到许可证信息!" -ErrXpackNotFound: "该部分为专业版功能,请先在 面板设置-许可证 界面导入许可证" -ErrXpackNotActive: "该部分为专业版功能,请先在 面板设置-许可证 界面同步许可证状态" -ErrXpackOutOfDate: "当前许可证已过期,请重新在 面板设置-许可证 界面导入许可证" - #task TaskStart: "{{.name}} 任务开始 [START]" TaskEnd: "{{.name}} 任务结束 [COMPLETED]" diff --git a/agent/init/hook/hook.go b/agent/init/hook/hook.go index 0ffff3fe0..334319afc 100644 --- a/agent/init/hook/hook.go +++ b/agent/init/hook/hook.go @@ -33,9 +33,9 @@ func initGlobalData() { global.CONF.System.BaseDir, _ = settingRepo.GetValueByKey("BaseDir") global.CONF.System.Version, _ = settingRepo.GetValueByKey("SystemVersion") global.CONF.System.EncryptKey, _ = settingRepo.GetValueByKey("EncryptKey") - currentNode, _ := settingRepo.GetValueByKey("CurrentNode") + global.CONF.System.CurrentNode, _ = settingRepo.GetValueByKey("CurrentNode") - global.IsMaster = currentNode == "127.0.0.1" || len(currentNode) == 0 + global.IsMaster = global.CONF.System.CurrentNode == "127.0.0.1" || len(global.CONF.System.CurrentNode) == 0 if global.IsMaster { global.CoreDB = common.LoadDBConnByPath(path.Join(global.CONF.System.DbPath, "core.db"), "core") } else { diff --git a/agent/server/server.go b/agent/server/server.go index 8fb13a5c1..a657bb026 100644 --- a/agent/server/server.go +++ b/agent/server/server.go @@ -3,6 +3,10 @@ package server import ( "crypto/tls" "fmt" + "net" + "net/http" + "os" + "github.com/1Panel-dev/1Panel/agent/app/repo" "github.com/1Panel-dev/1Panel/agent/cron" "github.com/1Panel-dev/1Panel/agent/global" @@ -18,12 +22,10 @@ import ( "github.com/1Panel-dev/1Panel/agent/init/validator" "github.com/1Panel-dev/1Panel/agent/init/viper" "github.com/1Panel-dev/1Panel/agent/utils/encrypt" - "net" - "net/http" - "os" + + _ "net/http/pprof" "github.com/gin-gonic/gin" - _ "net/http/pprof" ) func Start() { @@ -47,12 +49,13 @@ func Start() { } go func() { - http.ListenAndServe("0.0.0.0:6060", nil) + _ = http.ListenAndServe("0.0.0.0:6060", nil) }() if global.IsMaster { - _ = os.Remove("/tmp/agent.sock") - listener, err := net.Listen("unix", "/tmp/agent.sock") + _ = os.Remove("/etc/1panel/agent.sock") + _ = os.Mkdir("/etc/1panel", 0755) + listener, err := net.Listen("unix", "/etc/1panel/agent.sock") if err != nil { panic(err) } diff --git a/agent/utils/xpack/xpack.go b/agent/utils/xpack/xpack.go index 5cd78aef7..b53453247 100644 --- a/agent/utils/xpack/xpack.go +++ b/agent/utils/xpack/xpack.go @@ -49,7 +49,6 @@ func LoadNodeInfo() (bool, model.NodeInfo, error) { var info model.NodeInfo info.BaseDir = loadParams("BASE_DIR") info.Version = loadParams("ORIGINAL_VERSION") - info.CurrentNode = "127.0.0.1" info.EncryptKey = common.RandStr(16) return false, info, nil } diff --git a/core/app/repo/common.go b/core/app/repo/common.go index cad791ff9..4483aab93 100644 --- a/core/app/repo/common.go +++ b/core/app/repo/common.go @@ -8,78 +8,66 @@ import ( "gorm.io/gorm" ) -type ICommonRepo interface { - WithByID(id uint) global.DBOption - WithByGroupID(id uint) global.DBOption - - WithByName(name string) global.DBOption - WithByType(ty string) global.DBOption - WithByKey(key string) global.DBOption - WithOrderBy(orderStr string) global.DBOption - WithByStatus(status string) global.DBOption - WithByGroupBelong(group string) global.DBOption - - WithByIDs(ids []uint) global.DBOption - - WithOrderRuleBy(orderBy, order string) global.DBOption -} - -type CommonRepo struct{} - -func NewICommonRepo() ICommonRepo { - return &CommonRepo{} -} - -func (c *CommonRepo) WithByID(id uint) global.DBOption { +func WithByID(id uint) global.DBOption { return func(g *gorm.DB) *gorm.DB { return g.Where("id = ?", id) } } -func (c *CommonRepo) WithByGroupID(id uint) global.DBOption { +func WithByGroupID(id uint) global.DBOption { return func(g *gorm.DB) *gorm.DB { return g.Where("group_id = ?", id) } } -func (c *CommonRepo) WithByIDs(ids []uint) global.DBOption { +func WithByIDs(ids []uint) global.DBOption { return func(g *gorm.DB) *gorm.DB { return g.Where("id in (?)", ids) } } -func (c *CommonRepo) WithByName(name string) global.DBOption { +func WithByName(name string) global.DBOption { return func(g *gorm.DB) *gorm.DB { return g.Where("`name` = ?", name) } } +func WithoutByName(name string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("`name` != ?", name) + } +} -func (c *CommonRepo) WithByType(ty string) global.DBOption { +func WithByType(ty string) global.DBOption { return func(g *gorm.DB) *gorm.DB { return g.Where("`type` = ?", ty) } } -func (c *CommonRepo) WithByKey(key string) global.DBOption { +func WithByAddr(addr string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("addr = ?", addr) + } +} +func WithByKey(key string) global.DBOption { return func(g *gorm.DB) *gorm.DB { return g.Where("key = ?", key) } } -func (c *CommonRepo) WithByStatus(status string) global.DBOption { +func WithByStatus(status string) global.DBOption { return func(g *gorm.DB) *gorm.DB { return g.Where("status = ?", status) } } -func (c *CommonRepo) WithByGroupBelong(group string) global.DBOption { +func WithByGroupBelong(group string) global.DBOption { return func(g *gorm.DB) *gorm.DB { return g.Where("group_belong = ?", group) } } -func (c *CommonRepo) WithOrderBy(orderStr string) global.DBOption { +func WithOrderBy(orderStr string) global.DBOption { return func(g *gorm.DB) *gorm.DB { return g.Order(orderStr) } } -func (c *CommonRepo) WithOrderRuleBy(orderBy, order string) global.DBOption { +func WithOrderRuleBy(orderBy, order string) global.DBOption { switch order { case constant.OrderDesc: order = "desc" diff --git a/core/app/service/app_launcher.go b/core/app/service/app_launcher.go index b6da7a8ae..3f0a85b32 100644 --- a/core/app/service/app_launcher.go +++ b/core/app/service/app_launcher.go @@ -2,6 +2,7 @@ package service import ( "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/repo" "github.com/1Panel-dev/1Panel/core/constant" ) @@ -17,7 +18,7 @@ func NewIAppLauncher() IAppLauncher { } func (u *LauncherService) Search() ([]string, error) { - launchers, err := launcherRepo.List(commonRepo.WithOrderBy("created_at")) + launchers, err := launcherRepo.List(repo.WithOrderBy("created_at")) if err != nil { return nil, err } @@ -29,7 +30,7 @@ func (u *LauncherService) Search() ([]string, error) { } func (u *LauncherService) ChangeShow(req dto.SettingUpdate) error { - launcher, _ := launcherRepo.Get(commonRepo.WithByKey(req.Key)) + launcher, _ := launcherRepo.Get(repo.WithByKey(req.Key)) if req.Value == constant.StatusEnable { if launcher.ID != 0 { return nil @@ -41,5 +42,5 @@ func (u *LauncherService) ChangeShow(req dto.SettingUpdate) error { return nil } - return launcherRepo.Delete(commonRepo.WithByKey(req.Key)) + return launcherRepo.Delete(repo.WithByKey(req.Key)) } diff --git a/core/app/service/auth.go b/core/app/service/auth.go index 94e3b6519..4401f36e8 100644 --- a/core/app/service/auth.go +++ b/core/app/service/auth.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/repo" "github.com/1Panel-dev/1Panel/core/buserr" "github.com/1Panel-dev/1Panel/core/constant" "github.com/1Panel-dev/1Panel/core/global" @@ -31,11 +32,11 @@ func NewIAuthService() IAuthService { } func (u *AuthService) Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, error) { - nameSetting, err := settingRepo.Get(commonRepo.WithByKey("UserName")) + nameSetting, err := settingRepo.Get(repo.WithByKey("UserName")) if err != nil { return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error()) } - passwordSetting, err := settingRepo.Get(commonRepo.WithByKey("Password")) + passwordSetting, err := settingRepo.Get(repo.WithByKey("Password")) if err != nil { return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error()) } @@ -46,14 +47,14 @@ func (u *AuthService) Login(c *gin.Context, info dto.Login, entrance string) (*d if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name { return nil, constant.ErrAuth } - entranceSetting, err := settingRepo.Get(commonRepo.WithByKey("SecurityEntrance")) + entranceSetting, err := settingRepo.Get(repo.WithByKey("SecurityEntrance")) if err != nil { return nil, err } if len(entranceSetting.Value) != 0 && entranceSetting.Value != entrance { return nil, buserr.New(constant.ErrEntrance) } - mfa, err := settingRepo.Get(commonRepo.WithByKey("MFAStatus")) + mfa, err := settingRepo.Get(repo.WithByKey("MFAStatus")) if err != nil { return nil, err } @@ -67,11 +68,11 @@ func (u *AuthService) Login(c *gin.Context, info dto.Login, entrance string) (*d } func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin, entrance string) (*dto.UserLoginInfo, error) { - nameSetting, err := settingRepo.Get(commonRepo.WithByKey("UserName")) + nameSetting, err := settingRepo.Get(repo.WithByKey("UserName")) if err != nil { return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error()) } - passwordSetting, err := settingRepo.Get(commonRepo.WithByKey("Password")) + passwordSetting, err := settingRepo.Get(repo.WithByKey("Password")) if err != nil { return nil, errors.WithMessage(constant.ErrRecordNotFound, err.Error()) } @@ -82,18 +83,18 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin, entrance strin if !hmac.Equal([]byte(info.Password), []byte(pass)) || nameSetting.Value != info.Name { return nil, constant.ErrAuth } - entranceSetting, err := settingRepo.Get(commonRepo.WithByKey("SecurityEntrance")) + entranceSetting, err := settingRepo.Get(repo.WithByKey("SecurityEntrance")) if err != nil { return nil, err } if len(entranceSetting.Value) != 0 && entranceSetting.Value != entrance { return nil, buserr.New(constant.ErrEntrance) } - mfaSecret, err := settingRepo.Get(commonRepo.WithByKey("MFASecret")) + mfaSecret, err := settingRepo.Get(repo.WithByKey("MFASecret")) if err != nil { return nil, err } - mfaInterval, err := settingRepo.Get(commonRepo.WithByKey("MFAInterval")) + mfaInterval, err := settingRepo.Get(repo.WithByKey("MFAInterval")) if err != nil { return nil, err } @@ -106,11 +107,11 @@ func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin, entrance strin } func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (*dto.UserLoginInfo, error) { - setting, err := settingRepo.Get(commonRepo.WithByKey("SessionTimeout")) + setting, err := settingRepo.Get(repo.WithByKey("SessionTimeout")) if err != nil { return nil, err } - httpsSetting, err := settingRepo.Get(commonRepo.WithByKey("SSL")) + httpsSetting, err := settingRepo.Get(repo.WithByKey("SSL")) if err != nil { return nil, err } @@ -147,7 +148,7 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) ( } func (u *AuthService) LogOut(c *gin.Context) error { - httpsSetting, err := settingRepo.Get(commonRepo.WithByKey("SSL")) + httpsSetting, err := settingRepo.Get(repo.WithByKey("SSL")) if err != nil { return err } @@ -163,7 +164,7 @@ func (u *AuthService) LogOut(c *gin.Context) error { } func (u *AuthService) VerifyCode(code string) (bool, error) { - setting, err := settingRepo.Get(commonRepo.WithByKey("SecurityEntrance")) + setting, err := settingRepo.Get(repo.WithByKey("SecurityEntrance")) if err != nil { return false, err } @@ -171,7 +172,7 @@ func (u *AuthService) VerifyCode(code string) (bool, error) { } func (u *AuthService) CheckIsSafety(code string) (string, error) { - status, err := settingRepo.Get(commonRepo.WithByKey("SecurityEntrance")) + status, err := settingRepo.Get(repo.WithByKey("SecurityEntrance")) if err != nil { return "", err } @@ -185,7 +186,7 @@ func (u *AuthService) CheckIsSafety(code string) (string, error) { } func (u *AuthService) GetResponsePage() (string, error) { - pageCode, err := settingRepo.Get(commonRepo.WithByKey("NoAuthSetting")) + pageCode, err := settingRepo.Get(repo.WithByKey("NoAuthSetting")) if err != nil { return "", err } diff --git a/core/app/service/backup.go b/core/app/service/backup.go index ef95f5a06..60982f646 100644 --- a/core/app/service/backup.go +++ b/core/app/service/backup.go @@ -13,6 +13,7 @@ import ( "github.com/1Panel-dev/1Panel/core/app/dto" "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/app/repo" "github.com/1Panel-dev/1Panel/core/buserr" "github.com/1Panel-dev/1Panel/core/constant" "github.com/1Panel-dev/1Panel/core/global" @@ -51,7 +52,7 @@ func NewIBackupService() IBackupService { func (u *BackupService) Get(req dto.OperateByID) (dto.BackupInfo, error) { var data dto.BackupInfo - account, err := backupRepo.Get(commonRepo.WithByID(req.ID)) + account, err := backupRepo.Get(repo.WithByID(req.ID)) if err != nil { return data, err } @@ -70,7 +71,7 @@ func (u *BackupService) Get(req dto.OperateByID) (dto.BackupInfo, error) { } func (u *BackupService) List(req dto.OperateByIDs) ([]dto.BackupInfo, error) { - accounts, err := backupRepo.List(commonRepo.WithByIDs(req.IDs), commonRepo.WithOrderBy("created_at desc")) + accounts, err := backupRepo.List(repo.WithByIDs(req.IDs), repo.WithOrderBy("created_at desc")) if err != nil { return nil, err } @@ -94,7 +95,7 @@ func (u *BackupService) List(req dto.OperateByIDs) ([]dto.BackupInfo, error) { } func (u *BackupService) GetLocalDir() (string, error) { - account, err := backupRepo.Get(commonRepo.WithByType(constant.Local)) + account, err := backupRepo.Get(repo.WithByType(constant.Local)) if err != nil { return "", err } @@ -106,7 +107,7 @@ func (u *BackupService) GetLocalDir() (string, error) { } func (u *BackupService) LoadBackupOptions() ([]dto.BackupOption, error) { - accounts, err := backupRepo.List(commonRepo.WithOrderBy("created_at desc")) + accounts, err := backupRepo.List(repo.WithOrderBy("created_at desc")) if err != nil { return nil, err } @@ -125,9 +126,9 @@ func (u *BackupService) SearchWithPage(req dto.SearchPageWithType) (int64, inter count, accounts, err := backupRepo.Page( req.Page, req.PageSize, - commonRepo.WithByType(req.Type), - commonRepo.WithByName(req.Info), - commonRepo.WithOrderBy("created_at desc"), + repo.WithByType(req.Type), + repo.WithByName(req.Info), + repo.WithOrderBy("created_at desc"), ) if err != nil { return 0, nil, err @@ -181,7 +182,7 @@ func (u *BackupService) LoadBackupClientInfo(clientType string) (dto.BackupClien } else { data.RedirectUri = constant.OneDriveRedirectURI } - clientID, err := settingRepo.Get(commonRepo.WithByKey(clientIDKey)) + clientID, err := settingRepo.Get(repo.WithByKey(clientIDKey)) if err != nil { return data, err } @@ -190,7 +191,7 @@ func (u *BackupService) LoadBackupClientInfo(clientType string) (dto.BackupClien return data, err } data.ClientID = string(idItem) - clientSecret, err := settingRepo.Get(commonRepo.WithByKey(clientIDSc)) + clientSecret, err := settingRepo.Get(repo.WithByKey(clientIDSc)) if err != nil { return data, err } @@ -204,7 +205,7 @@ func (u *BackupService) LoadBackupClientInfo(clientType string) (dto.BackupClien } func (u *BackupService) Create(req dto.BackupOperate) error { - backup, _ := backupRepo.Get(commonRepo.WithByName(req.Name)) + backup, _ := backupRepo.Get(repo.WithByName(req.Name)) if backup.ID != 0 { return constant.ErrRecordExist } @@ -279,7 +280,7 @@ func (u *BackupService) GetBuckets(req dto.ForBuckets) ([]interface{}, error) { } func (u *BackupService) Delete(id uint) error { - backup, _ := backupRepo.Get(commonRepo.WithByID(id)) + backup, _ := backupRepo.Get(repo.WithByID(id)) if backup.ID == 0 { return constant.ErrRecordNotFound } @@ -295,11 +296,11 @@ func (u *BackupService) Delete(id uint) error { return buserr.New(constant.ErrBackupInUsed) } - return backupRepo.Delete(commonRepo.WithByID(id)) + return backupRepo.Delete(repo.WithByID(id)) } func (u *BackupService) Update(req dto.BackupOperate) error { - backup, _ := backupRepo.Get(commonRepo.WithByID(req.ID)) + backup, _ := backupRepo.Get(repo.WithByID(req.ID)) if backup.ID == 0 { return constant.ErrRecordNotFound } diff --git a/core/app/service/command.go b/core/app/service/command.go index bb5da1f98..e465fce1d 100644 --- a/core/app/service/command.go +++ b/core/app/service/command.go @@ -2,6 +2,7 @@ package service import ( "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/repo" "github.com/1Panel-dev/1Panel/core/constant" "github.com/1Panel-dev/1Panel/core/global" "github.com/jinzhu/copier" @@ -24,7 +25,7 @@ func NewICommandService() ICommandService { } func (u *CommandService) List(req dto.OperateByType) ([]dto.CommandInfo, error) { - commands, err := commandRepo.List(commonRepo.WithOrderBy("name"), commonRepo.WithByType(req.Type)) + commands, err := commandRepo.List(repo.WithOrderBy("name"), repo.WithByType(req.Type)) if err != nil { return nil, constant.ErrRecordNotFound } @@ -40,11 +41,11 @@ func (u *CommandService) List(req dto.OperateByType) ([]dto.CommandInfo, error) } func (u *CommandService) SearchForTree(req dto.OperateByType) ([]dto.CommandTree, error) { - cmdList, err := commandRepo.List(commonRepo.WithOrderBy("name"), commonRepo.WithByType(req.Type)) + cmdList, err := commandRepo.List(repo.WithOrderBy("name"), repo.WithByType(req.Type)) if err != nil { return nil, err } - groups, err := groupRepo.GetList(commonRepo.WithByType(req.Type)) + groups, err := groupRepo.GetList(repo.WithByType(req.Type)) if err != nil { return nil, err } @@ -67,20 +68,20 @@ func (u *CommandService) SearchForTree(req dto.OperateByType) ([]dto.CommandTree func (u *CommandService) SearchWithPage(req dto.SearchCommandWithPage) (int64, interface{}, error) { options := []global.DBOption{ - commonRepo.WithOrderRuleBy(req.OrderBy, req.Order), - commonRepo.WithByType(req.Type), + repo.WithOrderRuleBy(req.OrderBy, req.Order), + repo.WithByType(req.Type), } if len(req.Info) != 0 { options = append(options, commandRepo.WithByInfo(req.Info)) } if req.GroupID != 0 { - options = append(options, commonRepo.WithByGroupID(req.GroupID)) + options = append(options, repo.WithByGroupID(req.GroupID)) } total, commands, err := commandRepo.Page(req.Page, req.PageSize, options...) if err != nil { return 0, nil, err } - groups, _ := groupRepo.GetList(commonRepo.WithByType(req.Type)) + groups, _ := groupRepo.GetList(repo.WithByType(req.Type)) var dtoCommands []dto.CommandInfo for _, command := range commands { var item dto.CommandInfo @@ -99,7 +100,7 @@ func (u *CommandService) SearchWithPage(req dto.SearchCommandWithPage) (int64, i } func (u *CommandService) Create(commandDto dto.CommandOperate) error { - command, _ := commandRepo.Get(commonRepo.WithByName(commandDto.Name)) + command, _ := commandRepo.Get(repo.WithByName(commandDto.Name)) if command.ID != 0 { return constant.ErrRecordExist } @@ -114,13 +115,13 @@ func (u *CommandService) Create(commandDto dto.CommandOperate) error { func (u *CommandService) Delete(ids []uint) error { if len(ids) == 1 { - command, _ := commandRepo.Get(commonRepo.WithByID(ids[0])) + command, _ := commandRepo.Get(repo.WithByID(ids[0])) if command.ID == 0 { return constant.ErrRecordNotFound } - return commandRepo.Delete(commonRepo.WithByID(ids[0])) + return commandRepo.Delete(repo.WithByID(ids[0])) } - return commandRepo.Delete(commonRepo.WithByIDs(ids)) + return commandRepo.Delete(repo.WithByIDs(ids)) } func (u *CommandService) Update(id uint, upMap map[string]interface{}) error { diff --git a/core/app/service/entry.go b/core/app/service/entry.go index 9c77249b5..b3479e4c6 100644 --- a/core/app/service/entry.go +++ b/core/app/service/entry.go @@ -5,7 +5,6 @@ import "github.com/1Panel-dev/1Panel/core/app/repo" var ( hostRepo = repo.NewIHostRepo() commandRepo = repo.NewICommandRepo() - commonRepo = repo.NewICommonRepo() settingRepo = repo.NewISettingRepo() backupRepo = repo.NewIBackupRepo() logRepo = repo.NewILogRepo() diff --git a/core/app/service/group.go b/core/app/service/group.go index ed98d582d..0ab212e8c 100644 --- a/core/app/service/group.go +++ b/core/app/service/group.go @@ -7,6 +7,7 @@ import ( "github.com/1Panel-dev/1Panel/core/app/dto" "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/app/repo" "github.com/1Panel-dev/1Panel/core/buserr" "github.com/1Panel-dev/1Panel/core/constant" "github.com/1Panel-dev/1Panel/core/global" @@ -31,11 +32,11 @@ func NewIGroupService() IGroupService { func (u *GroupService) List(req dto.OperateByType) ([]dto.GroupInfo, error) { options := []global.DBOption{ - commonRepo.WithOrderBy("is_default desc"), - commonRepo.WithOrderBy("created_at desc"), + repo.WithOrderBy("is_default desc"), + repo.WithOrderBy("created_at desc"), } if len(req.Type) != 0 { - options = append(options, commonRepo.WithByType(req.Type)) + options = append(options, repo.WithByType(req.Type)) } var ( groups []model.Group @@ -57,7 +58,7 @@ func (u *GroupService) List(req dto.OperateByType) ([]dto.GroupInfo, error) { } func (u *GroupService) Create(req dto.GroupCreate) error { - group, _ := groupRepo.Get(commonRepo.WithByName(req.Name), commonRepo.WithByType(req.Type)) + group, _ := groupRepo.Get(repo.WithByName(req.Name), repo.WithByType(req.Type)) if group.ID != 0 { return constant.ErrRecordExist } @@ -71,14 +72,14 @@ func (u *GroupService) Create(req dto.GroupCreate) error { } func (u *GroupService) Delete(id uint) error { - group, _ := groupRepo.Get(commonRepo.WithByID(id)) + group, _ := groupRepo.Get(repo.WithByID(id)) if group.ID == 0 { return constant.ErrRecordNotFound } if group.IsDefault { return buserr.New(constant.ErrGroupIsDefault) } - defaultGroup, err := groupRepo.Get(commonRepo.WithByType(group.Type), groupRepo.WithByDefault(true)) + defaultGroup, err := groupRepo.Get(repo.WithByType(group.Type), groupRepo.WithByDefault(true)) if err != nil { return err } @@ -103,7 +104,7 @@ func (u *GroupService) Delete(id uint) error { if err != nil { return err } - return groupRepo.Delete(commonRepo.WithByID(id)) + return groupRepo.Delete(repo.WithByID(id)) } func (u *GroupService) Update(req dto.GroupUpdate) error { diff --git a/core/app/service/host.go b/core/app/service/host.go index b28ad4226..3f99cdcfb 100644 --- a/core/app/service/host.go +++ b/core/app/service/host.go @@ -6,6 +6,7 @@ import ( "github.com/1Panel-dev/1Panel/core/app/dto" "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/app/repo" "github.com/1Panel-dev/1Panel/core/constant" "github.com/1Panel-dev/1Panel/core/global" "github.com/1Panel-dev/1Panel/core/utils/encrypt" @@ -84,7 +85,7 @@ func (u *HostService) TestLocalConn(id uint) bool { return false } } else { - host, err = hostRepo.Get(commonRepo.WithByID(id)) + host, err = hostRepo.Get(repo.WithByID(id)) if err != nil { return false } @@ -124,7 +125,7 @@ func (u *HostService) TestLocalConn(id uint) bool { } func (u *HostService) GetHostInfo(id uint) (*model.Host, error) { - host, err := hostRepo.Get(commonRepo.WithByID(id)) + host, err := hostRepo.Get(repo.WithByID(id)) if err != nil { return nil, constant.ErrRecordNotFound } @@ -156,7 +157,7 @@ func (u *HostService) SearchWithPage(req dto.SearchHostWithPage) (int64, interfa options = append(options, hostRepo.WithByInfo(req.Info)) } if req.GroupID != 0 { - options = append(options, commonRepo.WithByGroupID(req.GroupID)) + options = append(options, repo.WithByGroupID(req.GroupID)) } total, hosts, err := hostRepo.Page(req.Page, req.PageSize, options...) if err != nil { @@ -168,7 +169,7 @@ func (u *HostService) SearchWithPage(req dto.SearchHostWithPage) (int64, interfa if err := copier.Copy(&item, &host); err != nil { return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) } - group, _ := groupRepo.Get(commonRepo.WithByID(host.GroupID)) + group, _ := groupRepo.Get(repo.WithByID(host.GroupID)) item.GroupBelong = group.Name if !item.RememberPassword { item.Password = "" @@ -204,7 +205,7 @@ func (u *HostService) SearchForTree(search dto.SearchForTree) ([]dto.HostTree, e if err != nil { return nil, err } - groups, err := groupRepo.GetList(commonRepo.WithByType("host")) + groups, err := groupRepo.GetList(repo.WithByType("host")) if err != nil { return nil, err } @@ -257,7 +258,7 @@ func (u *HostService) Create(req dto.HostOperate) (*dto.HostInfo, error) { return nil, errors.WithMessage(constant.ErrStructTransform, err.Error()) } if req.GroupID == 0 { - group, err := groupRepo.Get(commonRepo.WithByType("host"), groupRepo.WithByDefault(true)) + group, err := groupRepo.Get(repo.WithByType("host"), groupRepo.WithByDefault(true)) if err != nil { return nil, errors.New("get default group failed") } @@ -307,7 +308,7 @@ func (u *HostService) Create(req dto.HostOperate) (*dto.HostInfo, error) { } func (u *HostService) Delete(ids []uint) error { - hosts, _ := hostRepo.GetList(commonRepo.WithByIDs(ids)) + hosts, _ := hostRepo.GetList(repo.WithByIDs(ids)) for _, host := range hosts { if host.ID == 0 { return constant.ErrRecordNotFound @@ -316,7 +317,7 @@ func (u *HostService) Delete(ids []uint) error { return errors.New("the local connection information cannot be deleted!") } } - return hostRepo.Delete(commonRepo.WithByIDs(ids)) + return hostRepo.Delete(repo.WithByIDs(ids)) } func (u *HostService) Update(id uint, upMap map[string]interface{}) error { diff --git a/core/app/service/logs.go b/core/app/service/logs.go index 2d8046417..f6ec9820e 100644 --- a/core/app/service/logs.go +++ b/core/app/service/logs.go @@ -10,6 +10,7 @@ import ( "github.com/1Panel-dev/1Panel/core/app/dto" "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/app/repo" "github.com/1Panel-dev/1Panel/core/constant" "github.com/1Panel-dev/1Panel/core/global" "github.com/1Panel-dev/1Panel/core/utils/cmd" @@ -75,13 +76,13 @@ func (u *LogService) ListSystemLogFile() ([]string, error) { func (u *LogService) PageLoginLog(req dto.SearchLgLogWithPage) (int64, interface{}, error) { options := []global.DBOption{ - commonRepo.WithOrderBy("created_at desc"), + repo.WithOrderBy("created_at desc"), } if len(req.IP) != 0 { options = append(options, logRepo.WithByIP(req.IP)) } if len(req.Status) != 0 { - options = append(options, commonRepo.WithByStatus(req.Status)) + options = append(options, repo.WithByStatus(req.Status)) } total, ops, err := logRepo.PageLoginLog( req.Page, @@ -105,14 +106,14 @@ func (u *LogService) CreateOperationLog(operation *model.OperationLog) error { func (u *LogService) PageOperationLog(req dto.SearchOpLogWithPage) (int64, interface{}, error) { options := []global.DBOption{ - commonRepo.WithOrderBy("created_at desc"), + repo.WithOrderBy("created_at desc"), logRepo.WithByLikeOperation(req.Operation), } if len(req.Source) != 0 { options = append(options, logRepo.WithBySource(req.Source)) } if len(req.Status) != 0 { - options = append(options, commonRepo.WithByStatus(req.Status)) + options = append(options, repo.WithByStatus(req.Status)) } total, ops, err := logRepo.PageOperationLog( diff --git a/core/app/service/setting.go b/core/app/service/setting.go index 33ff5da55..b74133826 100644 --- a/core/app/service/setting.go +++ b/core/app/service/setting.go @@ -14,6 +14,7 @@ import ( "time" "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/repo" "github.com/1Panel-dev/1Panel/core/buserr" "github.com/1Panel-dev/1Panel/core/constant" "github.com/1Panel-dev/1Panel/core/global" @@ -74,7 +75,7 @@ func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) { func (u *SettingService) Update(key, value string) error { switch key { case "AppStoreLastModified": - exist, _ := settingRepo.Get(commonRepo.WithByKey("AppStoreLastModified")) + exist, _ := settingRepo.Get(repo.WithByKey("AppStoreLastModified")) if exist.ID == 0 { _ = settingRepo.Create("AppStoreLastModified", value) return nil @@ -270,14 +271,14 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error { } func (u *SettingService) LoadFromCert() (*dto.SSLInfo, error) { - ssl, err := settingRepo.Get(commonRepo.WithByKey("SSL")) + ssl, err := settingRepo.Get(repo.WithByKey("SSL")) if err != nil { return nil, err } if ssl.Value == "disable" { return &dto.SSLInfo{}, nil } - sslType, err := settingRepo.Get(commonRepo.WithByKey("SSLType")) + sslType, err := settingRepo.Get(repo.WithByKey("SSLType")) if err != nil { return nil, err } @@ -311,7 +312,7 @@ func (u *SettingService) LoadFromCert() (*dto.SSLInfo, error) { } func (u *SettingService) HandlePasswordExpired(c *gin.Context, old, new string) error { - setting, err := settingRepo.Get(commonRepo.WithByKey("Password")) + setting, err := settingRepo.Get(repo.WithByKey("Password")) if err != nil { return err } @@ -328,7 +329,7 @@ func (u *SettingService) HandlePasswordExpired(c *gin.Context, old, new string) return err } - expiredSetting, err := settingRepo.Get(commonRepo.WithByKey("ExpirationDays")) + expiredSetting, err := settingRepo.Get(repo.WithByKey("ExpirationDays")) if err != nil { return err } diff --git a/core/app/service/task.go b/core/app/service/task.go index d703c90b3..e411ecf89 100644 --- a/core/app/service/task.go +++ b/core/app/service/task.go @@ -2,6 +2,7 @@ package service import ( "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/repo" "github.com/1Panel-dev/1Panel/core/global" ) @@ -17,13 +18,13 @@ func NewITaskService() ITaskLogService { func (u *TaskLogService) Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, error) { opts := []global.DBOption{ - commonRepo.WithOrderBy("created_at desc"), + repo.WithOrderBy("created_at desc"), } if req.Status != "" { - opts = append(opts, commonRepo.WithByStatus(req.Status)) + opts = append(opts, repo.WithByStatus(req.Status)) } if req.Type != "" { - opts = append(opts, commonRepo.WithByType(req.Type)) + opts = append(opts, repo.WithByType(req.Type)) } total, tasks, err := taskRepo.Page( diff --git a/core/app/service/upgrade.go b/core/app/service/upgrade.go index 534f1b5e5..1542e59ee 100644 --- a/core/app/service/upgrade.go +++ b/core/app/service/upgrade.go @@ -12,6 +12,7 @@ import ( "github.com/1Panel-dev/1Panel/core/app/dto" "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/app/repo" "github.com/1Panel-dev/1Panel/core/constant" "github.com/1Panel-dev/1Panel/core/global" "github.com/1Panel-dev/1Panel/core/utils/cmd" @@ -35,11 +36,11 @@ func NewIUpgradeService() IUpgradeService { func (u *UpgradeService) SearchUpgrade() (*dto.UpgradeInfo, error) { var upgrade dto.UpgradeInfo - currentVersion, err := settingRepo.Get(commonRepo.WithByKey("SystemVersion")) + currentVersion, err := settingRepo.Get(repo.WithByKey("SystemVersion")) if err != nil { return nil, err } - DeveloperMode, err := settingRepo.Get(commonRepo.WithByKey("DeveloperMode")) + DeveloperMode, err := settingRepo.Get(repo.WithByKey("DeveloperMode")) if err != nil { return nil, err } @@ -167,7 +168,7 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error { } func (u *UpgradeService) Rollback(req dto.OperateByID) error { - log, _ := upgradeLogRepo.Get(commonRepo.WithByID(req.ID)) + log, _ := upgradeLogRepo.Get(repo.WithByID(req.ID)) if log.ID == 0 { return constant.ErrRecordNotFound } diff --git a/core/buserr/errors.go b/core/buserr/errors.go index a71020dd0..d4805eedc 100644 --- a/core/buserr/errors.go +++ b/core/buserr/errors.go @@ -38,6 +38,18 @@ func New(Key string) BusinessError { } } +func WithErr(Key string, err error) BusinessError { + paramMap := map[string]interface{}{} + if err != nil { + paramMap["err"] = err + } + return BusinessError{ + Msg: Key, + Map: paramMap, + Err: err, + } +} + func WithDetail(Key string, detail interface{}, err error) BusinessError { return BusinessError{ Msg: Key, diff --git a/core/constant/errs.go b/core/constant/errs.go index 0c776c204..ac34d7713 100644 --- a/core/constant/errs.go +++ b/core/constant/errs.go @@ -50,6 +50,14 @@ var ( ErrEntrance = "ErrEntrance" ErrProxy = "ErrProxy" ErrLocalDelete = "ErrLocalDelete" + + ErrXpackNotFound = "ErrXpackNotFound" + ErrXpackExceptional = "ErrXpackExceptional" + ErrXpackLost = "ErrXpackLost" + ErrXpackTimeout = "ErrXpackTimeout" + ErrXpackOutOfDate = "ErrXpackOutOfDate" + ErrNoSuchNode = "ErrNoSuchNode" + ErrNodeUnbind = "ErrNodeUnbind" ) // backup @@ -58,3 +66,14 @@ var ( ErrBackupLocalDelete = "ErrBackupLocalDelete" ErrMasterAddr = "ErrMasterAddr" ) + +var ( + ErrLicense = "ErrLicense" + ErrLicenseCheck = "ErrLicenseCheck" + ErrLicenseSave = "ErrLicenseSave" + ErrLicenseSync = "ErrLicenseSync" + ErrUnbindMaster = "ErrUnbindMaster" + ErrFreeNodeLimit = "ErrFreeNodeLimit" + ErrNodeBound = "ErrNodeBound" + ErrNodeBind = "ErrNodeBind" +) diff --git a/core/constant/status.go b/core/constant/status.go index 3df82969e..69ae3eade 100644 --- a/core/constant/status.go +++ b/core/constant/status.go @@ -14,4 +14,9 @@ const ( StatusUnhealthy = "unhealthy" StatusUpgrading = "upgrading" StatusRunning = "running" + StatusFree = "free" + StatusBound = "bound" + StatusExceptional = "exceptional" + StatusRetrying = "retrying" + StatusLost = "lost" ) diff --git a/core/i18n/lang/en.yaml b/core/i18n/lang/en.yaml index 3aa83105a..f93717601 100644 --- a/core/i18n/lang/en.yaml +++ b/core/i18n/lang/en.yaml @@ -30,6 +30,23 @@ ErrBackupInUsed: "The backup account is currently in use in a scheduled task and ErrBackupCheck: "Backup account test connection failed {{.err}}" ErrBackupLocalDelete: "Deleting local server backup accounts is not currently supported." +#license +ErrLicense: "License format error, please check and try again!" +ErrLicenseCheck: "License verification failed, please check and try again!" +ErrLicenseSave: "Failed to save license information, error {{ .err }}, please try again!" +ErrLicenseSync: "Failed to sync license information, no license information detected in the database!" +ErrXpackNotFound: "This section is a professional edition feature, please import the license first in Panel Settings-License interface" +ErrXpackExceptional: "This section is a professional edition feature, please synchronize the license status first in Panel Settings-License interface" +ErrXpackOutOfDate: "The current license has expired, please re-import the license in Panel Settings-License interface" +ErrXpackLost: "The license has reached the maximum number of retry attempts. Please go to the [Settings] [License] page and manually click the sync button to ensure that the professional version features are functioning properly." +ErrXpackTimeout: "Request timed out, the network connection may be unstable, please try again later!" +ErrUnbindMaster: "Detected that there are nodes in the node management, unable to unbind the current license. Please remove them first and try again!" +ErrFreeNodeLimit: "The number of nodes for the community edition has reached the free limit. Please visit www.lxware.cn/1panel to purchase and try again!" +ErrNodeBound: "This license is already bound to another node. Please check and try again!" +ErrNoSuchNode: "Failed to find the node information. Please check and try again!" +ErrNodeUnbind: "Detected that this node is no longer within the license binding range. Please check and try again!" +ErrNodeBind: "The node is already bound to a license. Please check and try again!" + #task TaskStart: "{{.name}} Task Start [START]" TaskEnd: "{{.name}} Task End [COMPLETED]" diff --git a/core/i18n/lang/zh-Hant.yaml b/core/i18n/lang/zh-Hant.yaml index 85e257fb1..ef61db72b 100644 --- a/core/i18n/lang/zh-Hant.yaml +++ b/core/i18n/lang/zh-Hant.yaml @@ -30,6 +30,23 @@ ErrBackupInUsed: "該備份帳號已在計劃任務中使用,無法刪除" ErrBackupCheck: "備份帳號測試連接失敗 {{.err}}" ErrBackupLocalDelete: "暫不支持刪除本地伺服器備份帳號" +#license +ErrLicense: "許可證格式錯誤,請檢查後重試!" +ErrLicenseCheck: "許可證校驗失敗,請檢查後重試!" +ErrLicenseSave: "許可證信息保存失敗,錯誤 {{ .err }}, 請重試!" +ErrLicenseSync: "許可證信息同步失敗,資料庫中未檢測到許可證信息!" +ErrXpackNotFound: "該部分為專業版功能,請先在 面板設置-許可證 界面導入許可證" +ErrXpackExceptional: "該部分為專業版功能,請先在 面板設置-許可證 界面同步許可證狀態" +ErrXpackOutOfDate: "當前許可證已過期,請重新在 面板設置-許可證 界面導入許可證" +ErrXpackLost: "許可證已達到最大重試次數,請進入【面板設定】【許可證】頁面手動點擊同步按鈕,以確保專業版功能正常使用" +ErrXpackTimeout: "請求超時,網絡連接可能不穩定,請稍後再試!" +ErrUnbindMaster: "檢測到節點管理內存在節點,無法解綁當前許可證,請先移除後重試!" +ErrFreeNodeLimit: "社區版節點數量已達到免費上限,請前往 www.lxware.cn/1panel 購買後重試!" +ErrNodeBound: "該許可證已綁定到其他節點,請檢查後重試!" +ErrNoSuchNode: "未能找到該節點信息,請檢查後重試!" +ErrNodeUnbind: "檢測到該節點未在許可證綁定範圍內,請檢查後重試!" +ErrNodeBind: "檢測到該節點已綁定許可證,請檢查後重試!" + #task TaskStart: "{{.name}} 任務開始 [START]" TaskEnd: "{{.name}} 任務結束 [COMPLETED]" diff --git a/core/i18n/lang/zh.yaml b/core/i18n/lang/zh.yaml index fc66b1b28..6fe3641f5 100644 --- a/core/i18n/lang/zh.yaml +++ b/core/i18n/lang/zh.yaml @@ -32,7 +32,23 @@ ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除" ErrBackupCheck: "备份账号测试连接失败 {{ .err}}" ErrBackupLocalDelete: "暂不支持删除本地服务器备份账号" -#task +#license +ErrLicense: "许可证格式错误,请检查后重试!" +ErrLicenseCheck: "许可证校验失败,请检查后重试!" +ErrLicenseSave: "许可证信息保存失败,错误 {{ .err }},请重试!" +ErrLicenseSync: "许可证信息同步失败,数据库中未检测到许可证信息!" +ErrXpackNotFound: "该部分为专业版功能,请先在 面板设置-许可证 界面导入许可证" +ErrXpackExceptional: "该部分为专业版功能,请先在 面板设置-许可证 界面同步许可证状态" +ErrXpackOutOfDate: "当前许可证已过期,请重新在 面板设置-许可证 界面导入许可证" +ErrXpackLost: "许可证已达到最大重试次数,请进入【面板设置】【许可证】页面手动点击同步按钮,以确保专业版功能正常使用" +ErrXpackTimeout: "请求超时,网络连接可能不稳定,请稍后再试!" +ErrUnbindMaster: "检测到节点管理内存在节点,无法解绑当前许可证,请先移除后重试!" +ErrFreeNodeLimit: "社区版节点数量已达到免费上限,请前往 www.lxware.cn/1panel 购买后重试!" +ErrNodeBound: "该许可证已绑定到其他节点,请检查后重试!" +ErrNoSuchNode: "未能找到该节点信息,请检查后重试!" +ErrNodeUnbind: "检测到该节点未在许可证绑定范围内,请检查后重试!" +ErrNodeBind: "检测到该节点已绑定许可证,请检查后重试!" + #task TaskStart: "{{.name}} 任务开始 [START]" TaskEnd: "{{.name}} 任务结束 [COMPLETED]" diff --git a/core/init/db/db.go b/core/init/db/db.go index 2432fa18a..1e218539b 100644 --- a/core/init/db/db.go +++ b/core/init/db/db.go @@ -1,89 +1,13 @@ package db import ( - "fmt" - "log" - "os" "path" - "time" "github.com/1Panel-dev/1Panel/core/global" - "github.com/glebarez/sqlite" - "gorm.io/gorm" - "gorm.io/gorm/logger" + "github.com/1Panel-dev/1Panel/core/utils/common" ) func Init() { - initDB() - initTaskDB() -} - -func initDB() { - dbPath := path.Join(global.CONF.System.BaseDir, "1panel/db") - if _, err := os.Stat(dbPath); err != nil { - if err := os.MkdirAll(dbPath, os.ModePerm); err != nil { - panic(fmt.Errorf("init db dir failed, err: %v", err)) - } - } - fullPath := path.Join(dbPath, global.CONF.System.DbCoreFile) - if _, err := os.Stat(fullPath); err != nil { - f, err := os.Create(fullPath) - if err != nil { - panic(fmt.Errorf("init db file failed, err: %v", err)) - } - _ = f.Close() - } - - db, err := NewDBWithPath(fullPath) - if err != nil { - panic(err) - } - - global.DB = db - global.LOG.Info("init db successfully") -} - -func initTaskDB() { - fullPath := path.Join(global.CONF.System.BaseDir, "1panel/db/task.db") - if _, err := os.Stat(fullPath); err != nil { - f, err := os.Create(fullPath) - if err != nil { - panic(fmt.Errorf("init task db file failed, err: %v", err)) - } - _ = f.Close() - } - - db, err := NewDBWithPath(fullPath) - if err != nil { - panic(err) - } - - global.TaskDB = db - global.LOG.Info("init task db successfully") -} - -func NewDBWithPath(dbPath string) (*gorm.DB, error) { - db, _ := gorm.Open(sqlite.Open(dbPath), &gorm.Config{ - DisableForeignKeyConstraintWhenMigrating: true, - Logger: getLogger(), - }) - sqlDB, dbError := db.DB() - if dbError != nil { - return nil, dbError - } - sqlDB.SetConnMaxIdleTime(10) - sqlDB.SetMaxOpenConns(100) - sqlDB.SetConnMaxLifetime(time.Hour) - return db, nil -} -func getLogger() logger.Interface { - return logger.New( - log.New(os.Stdout, "\r\n", log.LstdFlags), - logger.Config{ - SlowThreshold: time.Second, - LogLevel: logger.Silent, - IgnoreRecordNotFoundError: true, - Colorful: false, - }, - ) + global.DB = common.LoadDBConnByPath(path.Join(global.CONF.System.BaseDir, "1panel/db/core.db"), "core") + global.TaskDB = common.LoadDBConnByPath(path.Join(global.CONF.System.BaseDir, "1panel/db/task.db"), "task") } diff --git a/core/init/hook/hook.go b/core/init/hook/hook.go index a360c7830..69b1e2f41 100644 --- a/core/init/hook/hook.go +++ b/core/init/hook/hook.go @@ -14,39 +14,38 @@ import ( func Init() { settingRepo := repo.NewISettingRepo() - commonRepo := repo.NewICommonRepo() - masterSetting, err := settingRepo.Get(commonRepo.WithByKey("MasterAddr")) + masterSetting, err := settingRepo.Get(repo.WithByKey("MasterAddr")) if err != nil { global.LOG.Errorf("load master addr from setting failed, err: %v", err) } global.CONF.System.MasterAddr = masterSetting.Value - portSetting, err := settingRepo.Get(commonRepo.WithByKey("ServerPort")) + portSetting, err := settingRepo.Get(repo.WithByKey("ServerPort")) if err != nil { global.LOG.Errorf("load service port from setting failed, err: %v", err) } global.CONF.System.Port = portSetting.Value - ipv6Setting, err := settingRepo.Get(commonRepo.WithByKey("Ipv6")) + ipv6Setting, err := settingRepo.Get(repo.WithByKey("Ipv6")) if err != nil { global.LOG.Errorf("load ipv6 status from setting failed, err: %v", err) } global.CONF.System.Ipv6 = ipv6Setting.Value - bindAddressSetting, err := settingRepo.Get(commonRepo.WithByKey("BindAddress")) + bindAddressSetting, err := settingRepo.Get(repo.WithByKey("BindAddress")) if err != nil { global.LOG.Errorf("load bind address from setting failed, err: %v", err) } global.CONF.System.BindAddress = bindAddressSetting.Value - sslSetting, err := settingRepo.Get(commonRepo.WithByKey("SSL")) + sslSetting, err := settingRepo.Get(repo.WithByKey("SSL")) if err != nil { global.LOG.Errorf("load service ssl from setting failed, err: %v", err) } global.CONF.System.SSL = sslSetting.Value - versionSetting, err := settingRepo.Get(commonRepo.WithByKey("SystemVersion")) + versionSetting, err := settingRepo.Get(repo.WithByKey("SystemVersion")) if err != nil { global.LOG.Errorf("load version from setting failed, err: %v", err) } global.CONF.System.Version = versionSetting.Value - if _, err := settingRepo.Get(commonRepo.WithByKey("SystemStatus")); err != nil { + if _, err := settingRepo.Get(repo.WithByKey("SystemStatus")); err != nil { _ = settingRepo.Create("SystemStatus", "Free") } if err := settingRepo.Update("SystemStatus", "Free"); err != nil { diff --git a/core/middleware/bind_domain.go b/core/middleware/bind_domain.go index 9edbef1f8..050f0a826 100644 --- a/core/middleware/bind_domain.go +++ b/core/middleware/bind_domain.go @@ -13,8 +13,7 @@ import ( func BindDomain() gin.HandlerFunc { return func(c *gin.Context) { settingRepo := repo.NewISettingRepo() - commonRepo := repo.NewICommonRepo() - status, err := settingRepo.Get(commonRepo.WithByKey("BindDomain")) + status, err := settingRepo.Get(repo.WithByKey("BindDomain")) if err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) return diff --git a/core/middleware/helper.go b/core/middleware/helper.go index 3faee0f3c..e34d32c8e 100644 --- a/core/middleware/helper.go +++ b/core/middleware/helper.go @@ -8,8 +8,7 @@ import ( func LoadErrCode(errInfo string) int { settingRepo := repo.NewISettingRepo() - commonRepo := repo.NewICommonRepo() - codeVal, err := settingRepo.Get(commonRepo.WithByKey("NoAuthSetting")) + codeVal, err := settingRepo.Get(repo.WithByKey("NoAuthSetting")) if err != nil { return 500 } diff --git a/core/middleware/ip_limit.go b/core/middleware/ip_limit.go index 22dbec0f3..27fbff887 100644 --- a/core/middleware/ip_limit.go +++ b/core/middleware/ip_limit.go @@ -15,8 +15,7 @@ import ( func WhiteAllow() gin.HandlerFunc { return func(c *gin.Context) { settingRepo := repo.NewISettingRepo() - commonRepo := repo.NewICommonRepo() - status, err := settingRepo.Get(commonRepo.WithByKey("AllowIPs")) + status, err := settingRepo.Get(repo.WithByKey("AllowIPs")) if err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) return diff --git a/core/middleware/loading.go b/core/middleware/loading.go index dbdfdd1df..df177d111 100644 --- a/core/middleware/loading.go +++ b/core/middleware/loading.go @@ -10,8 +10,7 @@ import ( func GlobalLoading() gin.HandlerFunc { return func(c *gin.Context) { settingRepo := repo.NewISettingRepo() - commonRepo := repo.NewICommonRepo() - status, err := settingRepo.Get(commonRepo.WithByKey("SystemStatus")) + status, err := settingRepo.Get(repo.WithByKey("SystemStatus")) if err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) return diff --git a/core/middleware/password_expired.go b/core/middleware/password_expired.go index 35cd8d193..9794e7996 100644 --- a/core/middleware/password_expired.go +++ b/core/middleware/password_expired.go @@ -21,8 +21,7 @@ func PasswordExpired() gin.HandlerFunc { return } settingRepo := repo.NewISettingRepo() - commonRepo := repo.NewICommonRepo() - setting, err := settingRepo.Get(commonRepo.WithByKey("ExpirationDays")) + setting, err := settingRepo.Get(repo.WithByKey("ExpirationDays")) if err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypePasswordExpired, err) return @@ -33,7 +32,7 @@ func PasswordExpired() gin.HandlerFunc { return } - extime, err := settingRepo.Get(commonRepo.WithByKey("ExpirationTime")) + extime, err := settingRepo.Get(repo.WithByKey("ExpirationTime")) if err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypePasswordExpired, err) return diff --git a/core/middleware/proxy.go b/core/middleware/proxy.go index 909cfd72e..22bc38da9 100644 --- a/core/middleware/proxy.go +++ b/core/middleware/proxy.go @@ -16,44 +16,43 @@ import ( func Proxy() gin.HandlerFunc { return func(c *gin.Context) { - if strings.HasPrefix(c.Request.URL.Path, "/api/v2/core") || strings.HasPrefix(c.Request.URL.Path, "/1panel/swagger") { + if strings.HasPrefix(c.Request.URL.Path, "/1panel/swagger") || !strings.HasPrefix(c.Request.URL.Path, "/api/v2") { c.Next() return } - if !strings.HasPrefix(c.Request.URL.Path, "/api/v2") { + if strings.HasPrefix(c.Request.URL.Path, "/api/v2/core") && !strings.HasPrefix(c.Request.URL.Path, "/api/v2/core/xpack") { c.Next() return } + currentNode := c.Request.Header.Get("CurrentNode") - if len(currentNode) != 0 && currentNode != "127.0.0.1" { - if err := xpack.Proxy(c, currentNode); err != nil { + if !strings.HasPrefix(c.Request.URL.Path, "/api/v2/core") && (currentNode == "local" || len(currentNode) == 0) { + sockPath := "/etc/1panel/agent.sock" + if _, err := os.Stat(sockPath); err != nil { helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrProxy, err) return } + dialUnix := func() (conn net.Conn, err error) { + return net.Dial("unix", sockPath) + } + transport := &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialUnix() + }, + } + proxy := &httputil.ReverseProxy{ + Director: func(req *http.Request) { + req.URL.Scheme = "http" + req.URL.Host = "unix" + }, + Transport: transport, + } + proxy.ServeHTTP(c.Writer, c.Request) c.Abort() return } - sockPath := "/tmp/agent.sock" - if _, err := os.Stat(sockPath); err != nil { - helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrProxy, err) - return - } - dialUnix := func() (conn net.Conn, err error) { - return net.Dial("unix", sockPath) - } - transport := &http.Transport{ - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return dialUnix() - }, - } - proxy := &httputil.ReverseProxy{ - Director: func(req *http.Request) { - req.URL.Scheme = "http" - req.URL.Host = "unix" - }, - Transport: transport, - } - proxy.ServeHTTP(c.Writer, c.Request) + xpack.Proxy(c, currentNode) c.Abort() + return } } diff --git a/core/middleware/session.go b/core/middleware/session.go index 4c56e4337..7ee602ba7 100644 --- a/core/middleware/session.go +++ b/core/middleware/session.go @@ -27,14 +27,13 @@ func SessionAuth() gin.HandlerFunc { return } settingRepo := repo.NewISettingRepo() - commonRepo := repo.NewICommonRepo() - setting, err := settingRepo.Get(commonRepo.WithByKey("SessionTimeout")) + setting, err := settingRepo.Get(repo.WithByKey("SessionTimeout")) if err != nil { global.LOG.Errorf("create operation record failed, err: %v", err) return } lifeTime, _ := strconv.Atoi(setting.Value) - httpsSetting, err := settingRepo.Get(commonRepo.WithByKey("SSL")) + httpsSetting, err := settingRepo.Get(repo.WithByKey("SSL")) if err != nil { global.LOG.Errorf("create operation record failed, err: %v", err) return diff --git a/core/utils/common/common.go b/core/utils/common/common.go index 0c4290763..43026a0e6 100644 --- a/core/utils/common/common.go +++ b/core/utils/common/common.go @@ -4,10 +4,13 @@ import ( "fmt" mathRand "math/rand" "net" + "os" + "path" "strconv" "strings" "time" + "github.com/1Panel-dev/1Panel/core/global" "github.com/1Panel-dev/1Panel/core/utils/cmd" ) @@ -120,3 +123,23 @@ func LoadArch() (string, error) { } return "", fmt.Errorf("unsupported such arch: %s", std) } + +func Clean(str []byte) { + for i := 0; i < len(str); i++ { + str[i] = 0 + } +} + +func CreateDirWhenNotExist(isDir bool, pathItem string) (string, error) { + checkPath := pathItem + if !isDir { + checkPath = path.Dir(pathItem) + } + if _, err := os.Stat(checkPath); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(checkPath, os.ModePerm); err != nil { + global.LOG.Errorf("mkdir %s failed, err: %v", checkPath, err) + return pathItem, err + } + } + return pathItem, nil +} diff --git a/core/utils/common/sqlite.go b/core/utils/common/sqlite.go new file mode 100644 index 000000000..283306980 --- /dev/null +++ b/core/utils/common/sqlite.go @@ -0,0 +1,86 @@ +package common + +import ( + "fmt" + "log" + "os" + "path" + "time" + + "github.com/glebarez/sqlite" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +func LoadDBConnByPath(fullPath, dbName string) *gorm.DB { + if _, err := CreateDirWhenNotExist(true, path.Dir(fullPath)); err != nil { + panic(fmt.Errorf("init db dir failed, err: %v", err)) + } + if _, err := os.Stat(fullPath); err != nil { + f, err := os.Create(fullPath) + if err != nil { + panic(fmt.Errorf("init %s db file failed, err: %v", dbName, err)) + } + _ = f.Close() + } + + db, err := GetDBWithPath(fullPath) + if err != nil { + panic(err) + } + return db +} + +func LoadDBConnByPathWithErr(fullPath, dbName string) (*gorm.DB, error) { + if _, err := CreateDirWhenNotExist(true, path.Dir(fullPath)); err != nil { + return nil, fmt.Errorf("init db dir failed, err: %v", err) + } + if _, err := os.Stat(fullPath); err != nil { + f, err := os.Create(fullPath) + if err != nil { + return nil, fmt.Errorf("init %s db file failed, err: %v", dbName, err) + } + _ = f.Close() + } + + db, err := GetDBWithPath(fullPath) + if err != nil { + return nil, fmt.Errorf("init %s db failed, err: %v", dbName, err) + } + return db, nil +} + +func CloseDB(db *gorm.DB) { + sqlDB, err := db.DB() + if err != nil { + return + } + _ = sqlDB.Close() +} + +func GetDBWithPath(dbPath string) (*gorm.DB, error) { + db, _ := gorm.Open(sqlite.Open(dbPath), &gorm.Config{ + DisableForeignKeyConstraintWhenMigrating: true, + Logger: newLogger(), + }) + sqlDB, dbError := db.DB() + if dbError != nil { + return nil, dbError + } + sqlDB.SetConnMaxIdleTime(10) + sqlDB.SetMaxOpenConns(100) + sqlDB.SetConnMaxLifetime(time.Hour) + return db, nil +} + +func newLogger() logger.Interface { + return logger.New( + log.New(os.Stdout, "\r\n", log.LstdFlags), + logger.Config{ + SlowThreshold: time.Second, + LogLevel: logger.Silent, + IgnoreRecordNotFoundError: true, + Colorful: false, + }, + ) +} diff --git a/core/utils/http/new.go b/core/utils/http/new.go index 1db6ecd18..163a7c14c 100644 --- a/core/utils/http/new.go +++ b/core/utils/http/new.go @@ -14,7 +14,7 @@ import ( ) func NewLocalClient(reqUrl, reqMethod string, body io.Reader) (interface{}, error) { - sockPath := "/tmp/agent.sock" + sockPath := "/etc/1panel/agent.sock" if _, err := os.Stat(sockPath); err != nil { return nil, fmt.Errorf("no such agent.sock find in localhost, err: %v", err) } diff --git a/core/utils/jwt/jwt.go b/core/utils/jwt/jwt.go index b70c541dc..5c2480d86 100644 --- a/core/utils/jwt/jwt.go +++ b/core/utils/jwt/jwt.go @@ -27,8 +27,7 @@ type BaseClaims struct { func NewJWT() *JWT { settingRepo := repo.NewISettingRepo() - commonRepo := repo.NewICommonRepo() - jwtSign, _ := settingRepo.Get(commonRepo.WithByKey("JWTSigningKey")) + jwtSign, _ := settingRepo.Get(repo.WithByKey("JWTSigningKey")) return &JWT{ []byte(jwtSign.Value), } diff --git a/core/utils/xpack/xpack.go b/core/utils/xpack/xpack.go index dfb5f4a0b..b29434909 100644 --- a/core/utils/xpack/xpack.go +++ b/core/utils/xpack/xpack.go @@ -6,8 +6,8 @@ import ( "github.com/gin-gonic/gin" ) -func Proxy(c *gin.Context, currentNode string) error { - return nil +func Proxy(c *gin.Context, currentNode string) { + return } func UpdateGroup(name string, group, newGroup uint) error { diff --git a/frontend/src/api/interface/setting.ts b/frontend/src/api/interface/setting.ts index b8a4aae98..0a2a116b5 100644 --- a/frontend/src/api/interface/setting.ts +++ b/frontend/src/api/interface/setting.ts @@ -224,4 +224,9 @@ export namespace Setting { productPro: string; status: string; } + export interface NodeItem { + id: number; + addr: string; + status: string; + } } diff --git a/frontend/src/api/modules/setting.ts b/frontend/src/api/modules/setting.ts index e8c7f1732..89a29cce9 100644 --- a/frontend/src/api/modules/setting.ts +++ b/frontend/src/api/modules/setting.ts @@ -1,24 +1,39 @@ import http from '@/api'; import { deepCopy } from '@/utils/util'; import { Base64 } from 'js-base64'; -import { ResPage, SearchWithPage, DescriptionUpdate } from '../interface'; +import { ResPage, SearchWithPage, DescriptionUpdate, ReqPage } from '../interface'; import { Setting } from '../interface/setting'; // license export const UploadFileData = (params: FormData) => { - return http.upload('/xpack/licenses/upload', params); + return http.upload('/core/licenses/upload', params); }; -export const getLicense = () => { - return http.get<Setting.License>(`/xpack/licenses/get`); +export const SearchLicense = (params: ReqPage) => { + return http.post<ResPage<Setting.License>>('/core/licenses/search', params); +}; +export const DeleteLicense = (id: number, force: boolean) => { + return http.post('/core/licenses/del', { id: id, force: force }); }; export const getLicenseStatus = () => { - return http.get<Setting.LicenseStatus>(`/xpack/licenses/get/status`); + return http.get<Setting.LicenseStatus>(`/core/licenses/status`); }; -export const syncLicense = () => { - return http.post(`/xpack/licenses/sync`); +export const getMasterLicenseStatus = () => { + return http.get<Setting.LicenseStatus>(`/core/licenses/master/status`); }; -export const unbindLicense = () => { - return http.post(`/xpack/licenses/unbind`); +export const syncLicense = (id: number) => { + return http.post(`/core/licenses/sync`, { id: id }); +}; +export const bindLicense = (id: number, nodeID: number) => { + return http.post(`/core/licenses/bind`, { nodeID: nodeID, licenseID: id }); +}; +export const unbindLicense = (id: number) => { + return http.post(`/core/licenses/unbind`, { id: id }); +}; +export const loadLicenseOptions = () => { + return http.get(`/core/licenses/options`); +}; +export const listNodeOptions = () => { + return http.get<Array<Setting.NodeItem>>(`/core/nodes/list`); }; // agent diff --git a/frontend/src/components/system-upgrade/index.vue b/frontend/src/components/system-upgrade/index.vue index 30b788d25..cf2461a32 100644 --- a/frontend/src/components/system-upgrade/index.vue +++ b/frontend/src/components/system-upgrade/index.vue @@ -15,7 +15,7 @@ <el-divider direction="vertical" /> </span> <el-button type="primary" link @click="toHalo"> - <span class="font-normal">{{ isProductPro ? $t('license.pro') : $t('license.community') }}</span> + <span class="font-normal">{{ isMasterProductPro ? $t('license.pro') : $t('license.community') }}</span> </el-button> <span class="version">{{ version }}</span> <el-badge is-dot style="margin-top: -3px" v-if="version !== 'Waiting' && globalStore.hasNewVersion"> @@ -49,7 +49,7 @@ const globalStore = GlobalStore(); const upgradeRef = ref(); const version = ref<string>(''); -const isProductPro = ref(); +const isMasterProductPro = ref(); const loading = ref(false); const upgradeInfo = ref(); const upgradeVersion = ref(); @@ -112,7 +112,7 @@ const onLoadUpgradeInfo = async () => { }; onMounted(() => { - isProductPro.value = globalStore.isProductPro; + isMasterProductPro.value = globalStore.isMasterProductPro; search(); }); </script> diff --git a/frontend/src/global/use-theme.ts b/frontend/src/global/use-theme.ts index 5038d1014..9f97edd02 100644 --- a/frontend/src/global/use-theme.ts +++ b/frontend/src/global/use-theme.ts @@ -3,7 +3,7 @@ import { GlobalStore } from '@/store'; export const useTheme = () => { const globalStore = GlobalStore(); const switchTheme = () => { - if (globalStore.themeConfig.isGold && globalStore.isProductPro) { + if (globalStore.themeConfig.isGold && globalStore.isMasterProductPro) { const body = document.documentElement as HTMLElement; body.setAttribute('class', 'dark-gold'); return; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 12ef3c03f..8042ced1d 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -72,6 +72,8 @@ const message = { createNewFolder: 'Create new folder', createNewFile: 'Create new file', helpDoc: 'Help Document', + bind: 'Bind', + unbind: 'Unbind', }, search: { timeStart: 'Time start', @@ -1731,30 +1733,34 @@ const message = { community: 'Community Edition: ', community2: 'Community Edition', pro: 'Professional Edition: ', - trial: 'Trial Edition', - office: 'Official Edition', + xpack: 'Professional Edition', + trial: 'Trial Version', + forceDelete: 'Force delete, will ignore errors during the deletion process and eventually remove metadata', + deleteHelper: 'Deleting the license may cause child nodes to be unable to switch, please proceed with caution!', + office: 'Official Version', trialInfo: 'Version', authorizationId: 'Subscription Authorization ID', authorizedUser: 'Authorized User', - expiresAt: 'Expiry Date', + expiresAt: 'Expiration Date', productName: 'Product Name', productStatus: 'Product Status', - Lost01: 'Lost * 1', - Lost02: 'Lost * 2', - Lost03: 'Lost', - Enable: 'Enabled', - Disable: 'Disabled', + lost: 'Lost Contact', + bound: 'Bound', + exceptional: 'Exceptional', + free: 'Free', lostHelper: - 'The License needs to be periodically synchronized for availability. Please ensure normal external network access. After three losses of connection, the License binding will be released.', + 'The license has reached the maximum number of retry attempts. Please manually click the sync button to ensure the professional version functions properly.', + exceptionalHelper: + 'License synchronization verification is abnormal. Please manually click the sync button to ensure the professional version functions properly.', quickUpdate: 'Quick Update', import: 'Import', - power: 'Authorize', - unbind: 'Unbind License', - unbindHelper: 'All Pro related Settings will be cleared after unbinding. Do you want to continue? ', + power: 'Authorization', + unbind: 'Unbind', + unbindHelper: 'Unbinding will clear all professional edition settings for this node. Do you wish to continue?', + unbindMasterHelper: + 'There are currently other nodes besides the master node. The {0} operation is not supported. Please delete the nodes in the node management and try again.', importLicense: 'Import License', importHelper: 'Please click or drag the license file here', - technicalAdvice: 'Technical Advice', - advice: 'Consultation', indefinitePeriod: 'Indefinite Period', levelUpPro: 'Upgrade to Professional Edition', licenseSync: 'License Sync', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index efafaf19b..1c9679d3f 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -71,6 +71,8 @@ const message = { createNewFolder: '新建資料夾', createNewFile: '新建檔案', helpDoc: '幫助文档', + bind: '綁定', + unbind: '解除綁定', }, search: { timeStart: '開始時間', @@ -1612,7 +1614,10 @@ const message = { community: '社區版:', community2: '社區版', pro: '專業版:', + xpack: '專業版', trial: '試用版', + forceDelete: '強制刪除,會忽略刪除過程中產生的錯誤並最終刪除元數據', + deleteHelper: '刪除許可證可能導致子節點無法切換,請謹慎操作!', office: '正式版', trialInfo: '版本', authorizationId: '訂閱授權 ID', @@ -1620,22 +1625,21 @@ const message = { expiresAt: '到期時間', productName: '產品名稱', productStatus: '產品狀態', - Lost01: '失聯 * 1', - Lost02: '失聯 * 2', - Lost03: '已失聯', - Enable: '已啟用', - Disable: '未啟用', - lostHelper: '許可證需要定時同步是否可用,請保證正常外網訪問,失聯三次後將解除許可證綁定', + lost: '已失聯', + bound: '已綁定', + exceptional: '異常', + free: '空閒', + lostHelper: '許可證已達到最大重試次數,請手動點擊同步按鈕,以確保專業版功能正常使用。', + exceptionalHelper: '許可證同步驗證異常,請手動點擊同步按鈕,以確保專業版功能正常使用。', quickUpdate: '快速更新', import: '導入', - power: '授 權', + power: '授權', unbind: '解除綁定', - unbindHelper: '解除綁定後將清除所有專業版相關設置,是否繼續?', + unbindHelper: '解除綁定後將清除該節點所有專業版相關設置,是否繼續?', + unbindMasterHelper: '當前已存在除主節點外其他節點,不支持 {0} 操作,請在節點管理中刪除節點後重試', importLicense: '導入許可證', importHelper: '請點擊或拖動許可文件到此處', - technicalAdvice: '技術諮詢', - advice: '諮詢', - indefinitePeriod: '無限期', + indefinitePeriod: '無期限', levelUpPro: '升級專業版', licenseSync: '許可證同步', knowMorePro: '了解更多', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index ad81ae32b..6c3e96ad2 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -71,6 +71,8 @@ const message = { createNewFolder: '新建文件夹', createNewFile: '新建文件', helpDoc: '帮助文档', + bind: '绑定', + unbind: '解绑', }, search: { timeStart: '开始时间', @@ -1597,6 +1599,8 @@ const message = { currentVersion: '当前运行版本:', license: '许可证', + bind: '绑定', + bindNode: '绑定节点', advancedMenuHide: '高级功能菜单隐藏', showMainAdvancedMenu: '如果只保留 1 个菜单,则侧边栏只会显示高级功能主菜单', showAll: '全部显示', @@ -1611,7 +1615,10 @@ const message = { community: '社区版:', community2: '社区版', pro: '专业版:', + xpack: '专业版', trial: '试用版', + forceDelete: '强制删除,会忽略删除过程中产生的错误并最终删除元数据', + deleteHelper: '删除许可证可能导致子节点无法切换,请谨慎操作!', office: '正式版', trialInfo: '版本', authorizationId: '订阅授权 ID', @@ -1619,21 +1626,20 @@ const message = { expiresAt: '到期时间', productName: '产品名称', productStatus: '产品状态', - Lost01: '失联 * 1', - Lost02: '失联 * 2', - Lost03: '已失联', - Enable: '已激活', - Disable: '未激活', - lostHelper: '许可证需要定时同步是否可用,请保证正常外网访问,失联三次后将解除许可证绑定', + lost: '已失联', + bound: '已绑定', + exceptional: '异常', + free: '空闲', + lostHelper: '许可证已达到最大重试次数,请手动点击同步按钮,以确保专业版功能正常使用。', + exceptionalHelper: '许可证同步验证异常,请手动点击同步按钮,以确保专业版功能正常使用。', quickUpdate: '快速更新', import: '导入', power: '授 权', unbind: '解除绑定', - unbindHelper: '解除绑定后将清除所有专业版相关设置,是否继续?', + unbindHelper: '解除绑定后将清除该节点所有专业版相关设置,是否继续?', + unbindMasterHelper: '当前已存在除主节点外其他节点,不支持 {0} 操作,请在节点管理中删除节点后重试', importLicense: '导入许可证', importHelper: '请点击或拖动许可文件到此处', - technicalAdvice: '技术咨询', - advice: '咨询', indefinitePeriod: '无限期', levelUpPro: '升级专业版', licenseSync: '许可证同步', diff --git a/frontend/src/layout/components/Sidebar/index.vue b/frontend/src/layout/components/Sidebar/index.vue index 3bddd3a83..c27118b58 100644 --- a/frontend/src/layout/components/Sidebar/index.vue +++ b/frontend/src/layout/components/Sidebar/index.vue @@ -8,13 +8,16 @@ > <Logo :isCollapse="isCollapse" /> - <span v-if="nodes.length !== 1" class="el-dropdown-link"> - {{ globalStore.currentNode || '127.0.0.1' }} + <span v-if="nodes.length !== 0" class="el-dropdown-link"> + {{ loadCurrentName() }} </span> - <el-dropdown v-if="nodes.length !== 1" placement="right-start" @command="changeNode"> + <el-dropdown v-if="nodes.length !== 0" placement="right-start" @command="changeNode"> <el-icon class="ico"><Switch /></el-icon> <template #dropdown> <el-dropdown-menu> + <el-dropdown-item command="local"> + {{ $t('terminal.local') }} + </el-dropdown-item> <el-dropdown-item v-for="item in nodes" :key="item.name" :command="item.name"> {{ item.name }} </el-dropdown-item> @@ -60,7 +63,7 @@ import { ElMessageBox } from 'element-plus'; import { GlobalStore, MenuStore } from '@/store'; import { MsgSuccess } from '@/utils/message'; import { isString } from '@vueuse/core'; -import { getSettingInfo } from '@/api/modules/setting'; +import { getSettingInfo, listNodeOptions } from '@/api/modules/setting'; const route = useRoute(); const menuStore = MenuStore(); @@ -83,6 +86,13 @@ let routerMenus = computed((): RouteRecordRaw[] => { return menuStore.menuList.filter((route) => route.meta && !route.meta.hideInSidebar); }); +const loadCurrentName = () => { + if (globalStore.currentNode) { + return globalStore.currentNode === 'local' ? i18n.global.t('terminal.local') : globalStore.currentNode; + } + return i18n.global.t('terminal.local'); +}; + const screenWidth = ref(0); interface Node { @@ -127,25 +137,23 @@ const systemLogOut = async () => { }; const loadNodes = async () => { - let listXNodes; - const xpackModules = import.meta.glob('../../../xpack/api/modules/node.ts', { eager: true }); - if (xpackModules['../../../xpack/api/modules/node.ts']) { - listXNodes = xpackModules['../../../xpack/api/modules/node.ts']['listNodes'] || {}; - const res = await listXNodes(); - if (!res) { + await listNodeOptions() + .then((res) => { + if (!res) { + nodes.value = []; + return; + } + nodes.value = res.data; + if (nodes.value.length === 0) { + globalStore.currentNode = 'local'; + } + }) + .catch(() => { nodes.value = []; - return; - } - nodes.value = res.data; - if (nodes.value.length === 1) { - globalStore.currentNode = nodes.value[0].name; - } - return; - } - nodes.value = []; + }); }; const changeNode = (command: string) => { - globalStore.currentNode = command || '127.0.0.1'; + globalStore.currentNode = command || 'local'; location.reload(); }; diff --git a/frontend/src/layout/index.vue b/frontend/src/layout/index.vue index 699577a96..3313a12d8 100644 --- a/frontend/src/layout/index.vue +++ b/frontend/src/layout/index.vue @@ -22,7 +22,7 @@ import { GlobalStore, MenuStore, TabsStore } from '@/store'; import { DeviceType } from '@/enums/app'; import { getSystemAvailable } from '@/api/modules/setting'; import { useRoute, useRouter } from 'vue-router'; -import { loadProductProFromDB } from '@/utils/xpack'; +import { loadMasterProductProFromDB, loadProductProFromDB } from '@/utils/xpack'; import { useTheme } from '@/global/use-theme'; const { switchTheme } = useTheme(); useResize(); @@ -100,6 +100,7 @@ onMounted(() => { loadStatus(); loadProductProFromDB(); + loadMasterProductProFromDB(); const mqList = window.matchMedia('(prefers-color-scheme: dark)'); if (mqList.addEventListener) { diff --git a/frontend/src/store/interface/index.ts b/frontend/src/store/interface/index.ts index 47c945d0f..ae0f9ea5d 100644 --- a/frontend/src/store/interface/index.ts +++ b/frontend/src/store/interface/index.ts @@ -35,6 +35,7 @@ export interface GlobalState { isProductPro: boolean; productProExpires: number; + isMasterProductPro: boolean; errStatus: string; diff --git a/frontend/src/store/modules/global.ts b/frontend/src/store/modules/global.ts index efded6287..de1d90815 100644 --- a/frontend/src/store/modules/global.ts +++ b/frontend/src/store/modules/global.ts @@ -39,17 +39,18 @@ const GlobalStore = defineStore({ isProductPro: false, productProExpires: 0, + isMasterProductPro: false, errStatus: '', - currentNode: '127.0.0.1', + currentNode: 'local', }), getters: { isDarkTheme: (state) => state.themeConfig.theme === 'dark' || state.themeConfig.isGold || (state.themeConfig.theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches), - isDarkGoldTheme: (state) => state.themeConfig.isGold && state.isProductPro, + isDarkGoldTheme: (state) => state.themeConfig.isGold && state.isMasterProductPro, }, actions: { setOpenMenuTabs(openMenuTabs: boolean) { diff --git a/frontend/src/utils/xpack.ts b/frontend/src/utils/xpack.ts index 3ea217ae4..10a320aa9 100644 --- a/frontend/src/utils/xpack.ts +++ b/frontend/src/utils/xpack.ts @@ -1,4 +1,4 @@ -import { getLicenseStatus, getSettingInfo } from '@/api/modules/setting'; +import { getLicenseStatus, getMasterLicenseStatus, getSettingInfo } from '@/api/modules/setting'; import { useTheme } from '@/global/use-theme'; import { GlobalStore } from '@/store'; const globalStore = GlobalStore(); @@ -53,36 +53,39 @@ const loadDataFromDB = async () => { export async function loadProductProFromDB() { const res = await getLicenseStatus(); if (!res || !res.data) { - resetXSetting(); globalStore.isProductPro = false; } else { - globalStore.isProductPro = - res.data.status === 'Enable' || res.data.status === 'Lost01' || res.data.status === 'Lost02'; + globalStore.isProductPro = res.data.status === 'bound'; if (globalStore.isProductPro) { globalStore.productProExpires = Number(res.data.productPro); } } - switchTheme(); - initFavicon(); loadDataFromDB(); } +export async function loadMasterProductProFromDB() { + const res = await getMasterLicenseStatus(); + if (!res || !res.data) { + globalStore.isMasterProductPro = false; + } else { + globalStore.isMasterProductPro = res.data.status === 'bound'; + } + switchTheme(); + initFavicon(); +} + export async function getXpackSettingForTheme() { - const res = await getLicenseStatus(); + const res = await getMasterLicenseStatus(); if (!res.data) { - globalStore.isProductPro = false; + globalStore.isMasterProductPro = false; resetXSetting(); switchTheme(); initFavicon(); return; } - globalStore.isProductPro = - res.data.status === 'Enable' || res.data.status === 'Lost01' || res.data.status === 'Lost02'; - if (globalStore.isProductPro) { - globalStore.productProExpires = Number(res.data.productPro); - } - if (!globalStore.isProductPro) { - globalStore.isProductPro = false; + globalStore.isMasterProductPro = res.data.status === 'bound'; + if (!globalStore.isMasterProductPro) { + globalStore.isMasterProductPro = false; resetXSetting(); switchTheme(); initFavicon(); diff --git a/frontend/src/views/setting/license/bind/index.vue b/frontend/src/views/setting/license/bind/index.vue new file mode 100644 index 000000000..d432f0e34 --- /dev/null +++ b/frontend/src/views/setting/license/bind/index.vue @@ -0,0 +1,97 @@ +<template> + <DrawerPro + v-model="drawerVisible" + :header="$t('commons.button.bind')" + :resource="licenseName" + :back="handleClose" + size="small" + > + <el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading"> + <el-form-item :label="$t('setting.bindNode')" prop="nodeID" :rules="Rules.requiredSelect"> + <el-select filterable v-model="form.nodeID" style="width: 100%"> + <el-option v-for="item in freeNodes" :key="item.id" :label="item.name" :value="item.id" /> + </el-select> + </el-form-item> + </el-form> + <template #footer> + <el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button> + <el-button :disabled="loading" type="primary" @click="onBind(formRef)"> + {{ $t('commons.button.confirm') }} + </el-button> + </template> + </DrawerPro> +</template> +<script lang="ts" setup> +import { reactive, ref } from 'vue'; +import i18n from '@/lang'; +import { MsgSuccess } from '@/utils/message'; +import { bindLicense, listNodeOptions } from '@/api/modules/setting'; +import { FormInstance } from 'element-plus'; +import { GlobalStore } from '@/store'; +import { Rules } from '@/global/form-rules'; +const globalStore = GlobalStore(); + +interface DialogProps { + licenseName: string; + licenseID: number; +} +const drawerVisible = ref(); +const loading = ref(); +const licenseName = ref(); +const freeNodes = ref([]); + +const form = reactive({ + nodeID: null, + licenseID: null, +}); + +const formRef = ref<FormInstance>(); + +const acceptParams = (params: DialogProps): void => { + licenseName.value = params.licenseName; + form.licenseID = params.licenseID; + loadNodes(); + drawerVisible.value = true; +}; + +const onBind = async (formEl: FormInstance | undefined) => { + if (!formEl) return; + formEl.validate(async (valid) => { + if (!valid) return; + loading.value = true; + await bindLicense(form.licenseID, form.nodeID) + .then(() => { + loading.value = false; + MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); + globalStore.isProductPro = false; + globalStore.themeConfig.isGold = false; + window.location.reload(); + }) + .catch(() => { + loading.value = false; + }); + }); +}; + +const loadNodes = async () => { + if (!globalStore.isMasterProductPro) { + freeNodes.value = [{ id: 0, name: i18n.global.t('terminal.local') }]; + return; + } + await listNodeOptions() + .then((res) => { + freeNodes.value = res.data || []; + }) + .catch(() => { + freeNodes.value = []; + }); +}; + +const handleClose = () => { + drawerVisible.value = false; +}; + +defineExpose({ + acceptParams, +}); +</script> diff --git a/frontend/src/views/setting/license/delete/index.vue b/frontend/src/views/setting/license/delete/index.vue new file mode 100644 index 000000000..8f7f623ff --- /dev/null +++ b/frontend/src/views/setting/license/delete/index.vue @@ -0,0 +1,74 @@ +<template> + <el-dialog v-model="dialogVisible" :title="$t('commons.button.delete')" width="30%" :close-on-click-modal="false"> + <el-form ref="deleteRef" v-loading="loading" @submit.prevent> + <el-form-item> + <el-alert :title="$t('license.deleteHelper')" :closable="false" type="warning" /> + </el-form-item> + <el-form-item> + <el-checkbox v-model="form.forceDelete" :label="$t('database.unBindForce')" /> + <span class="input-help"> + {{ $t('license.forceDelete') }} + </span> + </el-form-item> + </el-form> + <template #footer> + <span class="dialog-footer"> + <el-button @click="dialogVisible = false" :disabled="loading"> + {{ $t('commons.button.cancel') }} + </el-button> + <el-button type="primary" @click="submit" :disabled="loading"> + {{ $t('commons.button.confirm') }} + </el-button> + </span> + </template> + </el-dialog> +</template> +<script lang="ts" setup> +import { FormInstance } from 'element-plus'; +import { ref } from 'vue'; +import i18n from '@/lang'; +import { MsgSuccess } from '@/utils/message'; +import { DeleteLicense } from '@/api/modules/setting'; + +let form = reactive({ + id: 0, + licenseName: '', + forceDelete: false, +}); +let dialogVisible = ref(false); +let loading = ref(false); + +const deleteRef = ref<FormInstance>(); + +interface DialogProps { + id: number; + name: string; + database: string; +} +const emit = defineEmits<{ (e: 'search'): void }>(); + +const acceptParams = async (prop: DialogProps) => { + form.id = prop.id; + form.licenseName = prop.name; + form.forceDelete = false; + dialogVisible.value = true; +}; + +const submit = async () => { + loading.value = true; + DeleteLicense(form.id, form.forceDelete) + .then(() => { + loading.value = false; + emit('search'); + MsgSuccess(i18n.global.t('commons.msg.deleteSuccess')); + dialogVisible.value = false; + }) + .catch(() => { + loading.value = false; + }); +}; + +defineExpose({ + acceptParams, +}); +</script> diff --git a/frontend/src/views/setting/license/index.vue b/frontend/src/views/setting/license/index.vue index 31b23eb59..dda9dd46c 100644 --- a/frontend/src/views/setting/license/index.vue +++ b/frontend/src/views/setting/license/index.vue @@ -1,144 +1,133 @@ <template> <div> - <LayoutContent v-loading="loading" :title="$t('setting.license')" :divider="true"> + <LayoutContent v-loading="loading" :title="$t('setting.license')"> + <template #leftToolBar> + <el-button type="primary" @click="toUpload()"> + {{ $t('commons.button.add') }} + </el-button> + </template> <template #main> - <el-row :gutter="20" class="mt-5; mb-10"> - <el-col :xs="24" :sm="24" :md="15" :lg="15" :xl="15"> - <div class="descriptions" v-if="hasLicense"> - <el-descriptions :column="1" direction="horizontal" size="large" border> - <el-descriptions-item :label="$t('license.authorizationId')"> - {{ license.licenseName || '-' }} - <el-button - type="primary" - class="ml-3" - plain - @click="onSync" - size="small" - v-if="showSync()" + <ComplexTable :pagination-config="paginationConfig" @sort-change="search" @search="search" :data="data"> + <el-table-column + :label="$t('license.authorizationId')" + :min-width="80" + prop="licenseName" + show-overflow-tooltip + /> + <el-table-column :label="$t('license.authorizedUser')" prop="assigneeName" show-overflow-tooltip> + <template #default="{ row }"> + {{ row.assigneeName || '-' }} + </template> + </el-table-column> + <el-table-column :label="$t('license.expiresAt')" prop="expiresAt" show-overflow-tooltip> + <template #default="{ row }"> + {{ row.expiresAt || '-' }} + </template> + </el-table-column> + <el-table-column :label="$t('commons.table.status')" prop="status" show-overflow-tooltip> + <template #default="{ row }"> + <div v-if="row.status"> + <el-tooltip + v-if="row.status === 'exceptional'" + :content="$t('license.exceptionalHelper')" + > + <el-tag type="danger"> + {{ $t('license.' + row.status) }} + </el-tag> + </el-tooltip> + <el-tooltip v-if="row.status === 'lost'" :content="$t('license.lostHelper')"> + <el-tag type="info"> + {{ $t('license.' + row.status) }} + </el-tag> + </el-tooltip> + <el-tag v-if="row.status !== 'exceptional' && row.status !== 'lost'"> + {{ $t('license.' + row.status) }} + </el-tag> + </div> + <span v-else>-</span> + </template> + </el-table-column> + <el-table-column :label="$t('setting.bindNode')"> + <template #default="{ row }"> + <span v-if="row.freeCount !== 0 && (row.status === 'free' || row.status === 'exceptional')"> + - + </span> + <div v-else> + <span v-if="row.freeCount === 0">{{ row.bindNode || '-' }}</span> + <div v-else> + <el-popover + placement="bottom" + :width="120" + trigger="hover" + v-if="row.freeNodes && row.freeNodes.length != 0" > - {{ $t('commons.button.sync') }} - </el-button> - <el-button type="primary" class="ml-3" plain @click="onUnBind()" size="small"> - {{ $t('license.unbind') }} - </el-button> - </el-descriptions-item> - <el-descriptions-item :label="$t('license.authorizedUser')"> - {{ license.assigneeName || '-' }} - </el-descriptions-item> - <el-descriptions-item :label="$t('license.productName')"> - {{ license.productName || '-' }} - </el-descriptions-item> - <el-descriptions-item :label="$t('license.trialInfo')"> - {{ license.trial ? $t('license.trial') : $t('license.office') }} - </el-descriptions-item> - <el-descriptions-item :label="$t('license.expiresAt')"> - {{ license.expiresAt || '-' }} - </el-descriptions-item> - <el-descriptions-item :label="$t('license.productStatus')"> - <div v-if="license.status"> - <el-tooltip - v-if="license.status.indexOf('Lost') !== -1" - :content="$t('license.lostHelper')" - > - <el-tag type="info"> - {{ $t('license.' + license.status) }} - </el-tag> - </el-tooltip> - <el-tag v-else>{{ $t('license.' + license.status) }}</el-tag> - </div> - <span v-else>-</span> - </el-descriptions-item> - <el-descriptions-item class="descriptions" :label="$t('commons.table.message')"> - {{ license.message }} - </el-descriptions-item> - </el-descriptions> - </div> - - <CardWithHeader :header="$t('home.overview')" height="160px" v-if="!hasLicense"> - <template #body> - <div class="h-app-card"> - <el-row> - <el-col :span="6"> - <span>{{ $t('setting.license') }}</span> - </el-col> - <el-col :span="6"> - <span>{{ $t('license.community2') }}</span> - </el-col> - </el-row> - </div> - </template> - </CardWithHeader> - </el-col> - - <el-col :xs="24" :sm="24" :md="9" :lg="9" :xl="9"> - <CardWithHeader :header="$t('license.quickUpdate')" height="160px"> - <template #body> - <div class="h-app-card"> - <el-row> - <el-col :span="15"> - <div class="h-app-content">{{ $t('license.importLicense') }}:</div> - </el-col> - <el-col :span="5"> - <el-button type="primary" plain round size="small" @click="toUpload"> - {{ $t('license.import') }} + <div v-for="(item, index) of row.freeNodes" :key="index"> + <el-tag>{{ item.name }}</el-tag> + </div> + <template #reference> + <el-button link type="primary"> + ({{ row.bindCount }} / {{ row.freeCount }}) </el-button> - </el-col> - </el-row> + </template> + </el-popover> + <span v-else link type="primary">({{ row.bindCount }} / {{ row.freeCount }})</span> </div> - <div class="h-app-card"> - <el-row> - <el-col :span="15"> - <div class="h-app-content">{{ $t('license.technicalAdvice') }}:</div> - </el-col> - <el-col :span="5"> - <el-button type="primary" plain round size="small" @click="toHalo()"> - {{ $t('license.advice') }} - </el-button> - </el-col> - </el-row> - </div> - </template> - </CardWithHeader> - </el-col> - </el-row> + </div> + </template> + </el-table-column> + <el-table-column + prop="createdAt" + :label="$t('commons.table.date')" + :formatter="dateFormat" + show-overflow-tooltip + /> + <fu-table-operations + width="300px" + :buttons="buttons" + :ellipsis="10" + :label="$t('commons.table.operate')" + fix + /> + </ComplexTable> </template> </LayoutContent> <LicenseImport ref="licenseRef" /> + <LicenseBind ref="bindRef" /> + <LicenseDelete ref="delRef" @search="search" /> </div> </template> <script setup lang="ts"> import { ref, reactive, onMounted } from 'vue'; -import { getLicense, syncLicense, unbindLicense } from '@/api/modules/setting'; -import CardWithHeader from '@/components/card-with-header/index.vue'; +import { SearchLicense, syncLicense, unbindLicense } from '@/api/modules/setting'; import LicenseImport from '@/components/license-import/index.vue'; +import LicenseDelete from '@/views/setting/license/delete/index.vue'; +import LicenseBind from '@/views/setting/license/bind/index.vue'; +import { dateFormat } from '@/utils/util'; import i18n from '@/lang'; -import { MsgSuccess } from '@/utils/message'; +import { MsgError, MsgSuccess } from '@/utils/message'; import { GlobalStore } from '@/store'; + +const globalStore = GlobalStore(); const loading = ref(); const licenseRef = ref(); -const globalStore = GlobalStore(); -const hasLicense = ref(); +const delRef = ref(); +const bindRef = ref(); -const license = reactive({ - licenseName: '', - trial: true, - expiresAt: '', - assigneeName: '', - productName: '', - - status: '', - message: '', +const data = ref(); +const paginationConfig = reactive({ + cacheSizeKey: 'backup-page-size', + currentPage: 1, + pageSize: 10, + total: 0, + type: '', + name: '', }); -const toHalo = () => { - window.open('https://www.lxware.cn/1panel' + '', '_blank', 'noopener,noreferrer'); -}; - -const onSync = async () => { +const onSync = async (row: any) => { loading.value = true; - await syncLicense() + await syncLicense(row.id) .then(() => { loading.value = false; MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); @@ -149,20 +138,24 @@ const onSync = async () => { }); }; -const onUnBind = async () => { +const onUnbind = async (row: any) => { ElMessageBox.confirm(i18n.global.t('license.unbindHelper'), i18n.global.t('license.unbind'), { confirmButtonText: i18n.global.t('commons.button.confirm'), cancelButtonText: i18n.global.t('commons.button.cancel'), type: 'info', }).then(async () => { loading.value = true; - await unbindLicense() + await unbindLicense(row.id) .then(() => { loading.value = false; - globalStore.isProductPro = false; - globalStore.themeConfig.isGold = false; MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); - window.location.reload(); + if (row.freeCount !== 0) { + globalStore.isProductPro = false; + globalStore.themeConfig.isGold = false; + window.location.reload(); + return; + } + search(); }) .catch(() => { loading.value = false; @@ -170,6 +163,29 @@ const onUnBind = async () => { }); }; +const search = async () => { + loading.value = true; + let params = { + page: paginationConfig.currentPage, + pageSize: paginationConfig.pageSize, + }; + await SearchLicense(params) + .then((res) => { + loading.value = false; + data.value = res.data.items || []; + for (const item of data.value) { + item.productName = 'product-1panel-pro'; + item.expiresAt = + item.productPro === '0' + ? i18n.global.t('license.indefinitePeriod') + : timestampToDate(Number(item.productPro)); + } + }) + .catch(() => { + loading.value = false; + }); +}; + const timestampToDate = (timestamp: number) => { const date = new Date(timestamp * 1000); const y = date.getFullYear(); @@ -186,47 +202,64 @@ const timestampToDate = (timestamp: number) => { return `${y}-${m}-${d} ${h}:${minute}:${second}`; }; -const search = async () => { - loading.value = true; - await getLicense() - .then((res) => { - loading.value = false; - license.status = res.data.status; - globalStore.isProductPro = - res.data.status === 'Enable' || res.data.status === 'Lost01' || res.data.status === 'Lost02'; - if (res.data.status === '') { - hasLicense.value = false; - return; - } - hasLicense.value = true; - if (globalStore.isProductPro) { - globalStore.productProExpires = Number(res.data.productPro); - } - license.licenseName = res.data.licenseName; - license.message = res.data.message; - license.assigneeName = res.data.assigneeName; - license.trial = res.data.trial; - if (res.data.productPro) { - license.productName = 'product-1panel-pro'; - license.expiresAt = - res.data.productPro === '0' - ? i18n.global.t('license.indefinitePeriod') - : timestampToDate(Number(res.data.productPro)); - } - }) - .catch(() => { - loading.value = false; - }); -}; - -const showSync = () => { - return license.status.indexOf('Lost') !== -1 || license.status === 'Disable'; -}; - const toUpload = () => { licenseRef.value.acceptParams(); }; +const buttons = [ + { + label: i18n.global.t('commons.button.bind'), + disabled: (row: any) => { + return row.status !== 'free'; + }, + click: (row: any) => { + bindRef.value.acceptParams({ licenseID: row.id, licenseName: row.licenseName }); + }, + }, + { + label: i18n.global.t('commons.button.unbind'), + disabled: (row: any) => { + return row.status === 'free'; + }, + click: (row: any) => { + if (row.freeCount != 0) { + if (row.freeNodes) { + MsgError(i18n.global.t('license.unbindMasterHelper', [i18n.global.t('commons.button.unbind')])); + return; + } + for (const item of data.value) { + if (item.bindNode && item.freeCount == 0) { + MsgError(i18n.global.t('license.unbindMasterHelper', [i18n.global.t('commons.button.unbind')])); + return; + } + } + } + onUnbind(row); + }, + }, + { + label: i18n.global.t('commons.button.sync'), + disabled: (row: any) => { + return row.status.indexOf('Lost') !== -1 || row.status === 'Disable'; + }, + click: (row: any) => { + onSync(row); + }, + }, + { + label: i18n.global.t('commons.button.delete'), + click: (row: any) => { + for (const item of data.value) { + if (item.bindNode && row.freeCount != 0) { + MsgError(i18n.global.t('license.unbindMasterHelper', [i18n.global.t('commons.button.delete')])); + return; + } + } + delRef.value.acceptParams({ id: row.id, name: row.licenseName }); + }, + }, +]; + onMounted(() => { search(); }); diff --git a/frontend/src/views/setting/panel/index.vue b/frontend/src/views/setting/panel/index.vue index 0287403ba..559e61497 100644 --- a/frontend/src/views/setting/panel/index.vue +++ b/frontend/src/views/setting/panel/index.vue @@ -28,7 +28,7 @@ <el-form-item :label="$t('setting.theme')" prop="theme"> <el-radio-group @change="onSave('Theme', form.theme)" v-model="form.theme"> - <el-radio-button v-if="isProductPro" value="dark-gold"> + <el-radio-button v-if="isMasterProductPro" value="dark-gold"> <span>{{ $t('setting.darkGold') }}</span> </el-radio-button> <el-radio-button value="light"> @@ -159,7 +159,7 @@ const loading = ref(false); const i18n = useI18n(); const globalStore = GlobalStore(); -const { isProductPro } = storeToRefs(globalStore); +const { isMasterProductPro } = storeToRefs(globalStore); const { switchTheme } = useTheme(); @@ -230,7 +230,7 @@ const search = async () => { const checkedTitles = getCheckedTitles(json); form.proHideMenus = checkedTitles.toString(); - if (isProductPro.value) { + if (isMasterProductPro.value) { const xpackRes = await getXpackSetting(); if (xpackRes) { form.theme = xpackRes.data.theme === 'dark-gold' ? 'dark-gold' : res.data.theme; @@ -305,7 +305,7 @@ const onSave = async (key: string, val: any) => { globalStore.themeConfig.theme = val; } switchTheme(); - if (globalStore.isProductPro) { + if (globalStore.isMasterProductPro) { updateXpackSettingByKey('Theme', val === 'dark-gold' ? 'dark-gold' : ''); if (val === 'dark-gold') { MsgSuccess(i18n.t('commons.msg.operationSuccess')); diff --git a/frontend/src/views/setting/panel/proxy/index.vue b/frontend/src/views/setting/panel/proxy/index.vue index 6e868ee6b..69d73e856 100644 --- a/frontend/src/views/setting/panel/proxy/index.vue +++ b/frontend/src/views/setting/panel/proxy/index.vue @@ -5,8 +5,8 @@ <template #default> {{ $t('setting.proxyHelper') }} <ul class="-ml-5"> - <li v-if="isProductPro">{{ $t('setting.proxyHelper1') }}</li> - <li v-if="isProductPro">{{ $t('setting.proxyHelper2') }}</li> + <li v-if="isMasterProductPro">{{ $t('setting.proxyHelper1') }}</li> + <li v-if="isMasterProductPro">{{ $t('setting.proxyHelper2') }}</li> <li>{{ $t('setting.proxyHelper3') }}</li> </ul> </template> @@ -69,7 +69,7 @@ import { storeToRefs } from 'pinia'; const globalStore = GlobalStore(); const emit = defineEmits<{ (e: 'search'): void }>(); -const { isProductPro } = storeToRefs(globalStore); +const { isMasterProductPro } = storeToRefs(globalStore); const formRef = ref<FormInstance>(); const rules = reactive({