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({