diff --git a/backend/buserr/errors.go b/backend/buserr/errors.go index 82d42e8b4..ad3fe1152 100644 --- a/backend/buserr/errors.go +++ b/backend/buserr/errors.go @@ -47,10 +47,14 @@ func WithDetail(Key string, detail interface{}, err error) BusinessError { } func WithErr(Key string, err error) BusinessError { + paramMap := map[string]interface{}{} + if err != nil { + paramMap["err"] = err + } return BusinessError{ - Msg: Key, - Detail: "", - Err: err, + Msg: Key, + Map: paramMap, + Err: err, } } diff --git a/backend/constant/errs.go b/backend/constant/errs.go index fc1d701c7..1cafd9158 100644 --- a/backend/constant/errs.go +++ b/backend/constant/errs.go @@ -43,6 +43,9 @@ var ( ErrNameIsExist = "ErrNameIsExist" ErrDemoEnvironment = "ErrDemoEnvironment" ErrCmdIllegal = "ErrCmdIllegal" + ErrXpackNotFound = "ErrXpackNotFound" + ErrXpackNotActive = "ErrXpackNotActive" + ErrXpackOutOfDate = "ErrXpackOutOfDate" ) // app @@ -150,3 +153,12 @@ var ( var ( ErrNotExistUser = "ErrNotExistUser" ) + +// license +var ( + ErrLicense = "ErrLicense" + ErrLicenseCheckInLocal = "ErrLicenseCheckInLocal" + ErrLicenseCheckInRemote = "ErrLicenseCheckInRemote" + ErrLicenseSave = "ErrLicenseSave" + ErrLicenseSync = "ErrLicenseSync" +) diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index 4c24f45f9..0d3e003ab 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -171,4 +171,14 @@ ErrBanAction: "Setting failed, the current {{ .name }} service is unavailable, p ErrScope: "Modification of this configuration is not supported" ErrStateChange: "State modification failed" ErrRuleExist: "Rule is Exist" -ErrRuleNotExist: "Rule is not Exist" \ No newline at end of file +ErrRuleNotExist: "Rule is not Exist" + +#license +ErrLicense: "License format error, please re-import!" +ErrLicenseCheckInLocal: "Local license check failed, error {{ .err }}, please try again!" +ErrLicenseCheckInRemote: "Remote license check failed, error {{ .err }}, please try again!" +ErrLicenseSave: "Failed to save license information, error {{ .err }}, please try again!" +ErrLicenseSync: "Failed to synchronize license information, error {{ .err }}, please try again!" +ErrXpackNotFound: "This section is an Xpack feature, please first import the License in Panel Settings-License interface" +ErrXpackNotActive: "This section is an Xpack feature, please first synchronize the License status in Panel Settings-License interface" +ErrXpackOutOfDate: "The current License has expired, please re-import the License in Panel Settings-License interface" diff --git a/backend/i18n/lang/zh-Hant.yaml b/backend/i18n/lang/zh-Hant.yaml index 45c98a445..63f3d0e8b 100644 --- a/backend/i18n/lang/zh-Hant.yaml +++ b/backend/i18n/lang/zh-Hant.yaml @@ -173,3 +173,13 @@ ErrScope: "不支援修改此配置" ErrStateChange: "狀態修改失敗" ErrRuleExist: "規則名稱已存在" ErrRuleNotExist: "規則不存在" + +#license +ErrLicense: "License 格式錯誤,請重新匯入!" +ErrLicenseCheckInLocal: "License 本地校驗失敗,錯誤 {{ .err }},請重試!" +ErrLicenseCheckInRemote: "License 遠程校驗失敗,錯誤 {{ .err }},請重試!" +ErrLicenseSave: "License 資訊保存失敗,錯誤 {{ .err }},請重試!" +ErrLicenseSync: "License 資訊同步失敗,錯誤 {{ .err }},請重試!" +ErrXpackNotFound: "該部分為 Xpack 功能,請先在 面板設置-許可證 界面匯入 License" +ErrXpackNotActive: "該部分為 Xpack 功能,請先在 面板設置-許可證 界面同步 License 狀態" +ErrXpackOutOfDate: "當前 License 已過期,請重新在 面板設置-許可證 界面匯入 License" diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index d489bba4f..59024ccda 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -171,4 +171,15 @@ ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后 ErrScope: "不支持修改此配置" ErrStateChange: "状态修改失败" ErrRuleExist: "规则名称已存在" -ErrRuleNotExist: "规则不存在" \ No newline at end of file +ErrRuleNotExist: "规则不存在" + +#license +ErrLicense: "License 格式错误,请重新导入!" +ErrLicenseCheckInLocal: "License 本地校验失败,错误 {{ .err }},请重试!" +ErrLicenseCheckInRemote: "License 远程校验失败,错误 {{ .err }},请重试!" +ErrLicenseSave: "License 信息保存失败,错误 {{ .err }},请重试!" +ErrLicenseSync: "License 信息同步失败,错误 {{ .err }},请重试!" +ErrXpackNotFound: "该部分为 Xpack 功能,请先在 面板设置-许可证 界面导入 License" +ErrXpackNotActive: "该部分为 Xpack 功能,请先在 面板设置-许可证 界面同步 License 状态" +ErrXpackOutOfDate: "当前 License 已过期,请重新在 面板设置-许可证 界面导入 License" + diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 6150bd589..d19c2ec9a 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -73,6 +73,11 @@ class RequestHttp { }); return Promise.reject(data); } + if (data.code == ResultEnum.ERRXPACK) { + globalStore.isProductPro = false; + window.location.reload(); + return Promise.reject(data); + } if (data.code == ResultEnum.ERRGLOBALLOADDING) { globalStore.setGlobalLoading(true); globalStore.setLoadingText(data.message); diff --git a/frontend/src/api/interface/setting.ts b/frontend/src/api/interface/setting.ts index f64f9f62c..4ef59512c 100644 --- a/frontend/src/api/interface/setting.ts +++ b/frontend/src/api/interface/setting.ts @@ -140,4 +140,12 @@ export namespace Setting { latestVersion: string; releaseNote: string; } + + export interface License { + licenseName: string; + assigneeName: string; + productPro: string; + trial: boolean; + status: string; + } } diff --git a/frontend/src/api/modules/setting.ts b/frontend/src/api/modules/setting.ts index 5ca748d93..08991c13d 100644 --- a/frontend/src/api/modules/setting.ts +++ b/frontend/src/api/modules/setting.ts @@ -6,6 +6,18 @@ import { Backup } from '../interface/backup'; import { Setting } from '../interface/setting'; import { TimeoutEnum } from '@/enums/http-enum'; +export const UploadFileData = (params: FormData) => { + return http.upload('/licenses/upload', params); +}; + +export const getLicense = () => { + return http.get(`/licenses/get`); +}; + +export const syncLicense = () => { + return http.post(`/licenses/sync`); +}; + export const getSettingInfo = () => { return http.post(`/settings/search`); }; diff --git a/frontend/src/components/system-upgrade/index.vue b/frontend/src/components/system-upgrade/index.vue index 7cddc9344..70e84b725 100644 --- a/frontend/src/components/system-upgrade/index.vue +++ b/frontend/src/components/system-upgrade/index.vue @@ -14,7 +14,6 @@ - {{ $t('setting.currentVersion') + version }} ({{ $t('setting.hasNewVersion') }}) diff --git a/frontend/src/enums/http-enum.ts b/frontend/src/enums/http-enum.ts index 68105ef25..e81dc11d4 100644 --- a/frontend/src/enums/http-enum.ts +++ b/frontend/src/enums/http-enum.ts @@ -9,6 +9,7 @@ export enum ResultEnum { ERRGLOBALLOADDING = 407, ERRIP = 408, ERRDOMAIN = 409, + ERRXPACK = 410, TIMEOUT = 20000, TYPE = 'success', } diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index ed3dcad38..c5fa77f23 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1449,6 +1449,34 @@ const message = { forum: 'Forum Help', doc2: 'User Manual', currentVersion: 'Version', + + license: 'License', + }, + license: { + community: 'Community Edition', + pro: 'Professional Edition', + trial: 'Trial Version', + office: 'Official Version', + authorizationId: 'Subscription Authorization ID', + authorizedUser: 'Authorized User', + expiresAt: 'Expiration Time', + productName: 'Product Name', + productStatus: 'Product Status', + lost01: 'Lost * 1', + lost02: 'Lost * 2', + Enable: 'Enabled', + Disable: 'Disabled', + lostHelper: + 'The license needs to be regularly synchronized for availability. Please ensure normal access to the external network. After three losses of contact, the license binding will be released.', + quickUpdate: 'Quick Update', + import: 'Import', + importLicense: 'Import License', + importHelper: 'Click or drag the License file here', + technicalAdvice: 'Technical Advice', + advice: 'Consultation', + indefinitePeriod: 'Indefinite Period', + levelUpPro: 'Upgrade to Professional Edition', + knowMorePro: 'Learn More about Professional Edition', }, clean: { scan: 'Start Scanning', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 73f8b43d1..952d38250 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -1350,6 +1350,32 @@ const message = { forum: '論壇求助', doc2: '使用手冊', currentVersion: '當前運行版本:', + + license: '許可證', + }, + license: { + community: '社區版', + pro: '專業版', + trial: '試用版', + office: '正式版本', + authorizationId: '訂閱授權 ID', + authorizedUser: '被授權方', + productName: '產品名稱', + productStatus: '產品狀態', + lost01: '失聯 * 1', + lost02: '失聯 * 2', + Enable: '已啟用', + Disable: '未啟用', + lostHelper: 'License 需要定時同步是否可用,請確保正常外網訪問,失聯三次後將解除 License 綁定', + quickUpdate: '快速更新', + import: '導入', + importLicense: '導入 License', + importHelper: '點擊或將 License 文件拖拽到此處', + technicalAdvice: '技術諮詢', + advice: '諮詢', + indefinitePeriod: '無限期', + levelUpPro: '升級專業版', + knowMorePro: '了解更多專業版信息', }, clean: { scan: '開始掃描', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 0100e3b4b..ec8acd6c5 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1351,6 +1351,36 @@ const message = { forum: '论坛求助', doc2: '使用手册', currentVersion: '当前运行版本:', + + license: '许可证', + }, + license: { + community: '社区版', + pro: '专业版', + trial: '实验版', + office: '正式版', + authorizationId: '订阅授权 ID', + authorizedUser: '被授权方', + expiresAt: '到期时间', + productName: '产品名称', + productStatus: '产品状态', + lost01: '失联 * 1', + lost02: '失联 * 2', + Enable: '已激活', + Disable: '未激活', + lostHelper: 'License 需要定时同步是否可用,请保证正常外网访问,失联三次后将解除 License 绑定', + quickUpdate: '快速更新', + import: '导入', + importLicense: '导入 License', + importHelper: '点击或将 License 文件拖拽到此处', + technicalAdvice: '技术咨询', + advice: '咨询', + indefinitePeriod: '无限期', + levelUpPro: '升级专业版', + knowMorePro: '了解更多专业版信息', + noLicense: '该部分为 Xpack 功能,请先在 面板设置-许可证 界面导入 License', + goImport: '去导入', + closeAlert: '当前页面可在面板设置中关闭显示', }, clean: { scan: '开始扫描', diff --git a/frontend/src/layout/index.vue b/frontend/src/layout/index.vue index 4f3ed8572..36e8e4188 100644 --- a/frontend/src/layout/index.vue +++ b/frontend/src/layout/index.vue @@ -23,7 +23,7 @@ import { MenuStore } from '@/store/modules/menu'; import { DeviceType } from '@/enums/app'; import { useI18n } from 'vue-i18n'; import { useTheme } from '@/hooks/use-theme'; -import { getSettingInfo, getSystemAvailable } from '@/api/modules/setting'; +import { getLicense, getSettingInfo, getSystemAvailable } from '@/api/modules/setting'; useResize(); const menuStore = MenuStore(); @@ -73,6 +73,11 @@ const loadDataFromDB = async () => { switchDark(); }; +const loadProductProFromDB = async () => { + const res = await getLicense(); + globalStore.isProductPro = res.data.status === 'Enable'; +}; + const updateDarkMode = async (event: MediaQueryListEvent) => { const res = await getSettingInfo(); if (res.data.theme !== 'auto') { @@ -110,6 +115,7 @@ onBeforeUnmount(() => { onMounted(() => { loadStatus(); loadDataFromDB(); + loadProductProFromDB(); const mqList = window.matchMedia('(prefers-color-scheme: dark)'); if (mqList.addEventListener) { diff --git a/frontend/src/routers/modules/setting.ts b/frontend/src/routers/modules/setting.ts index e278b22bd..9effd0993 100644 --- a/frontend/src/routers/modules/setting.ts +++ b/frontend/src/routers/modules/setting.ts @@ -37,6 +37,16 @@ const settingRouter = { activeMenu: 'Setting', }, }, + { + path: 'license', + name: 'License', + component: () => import('@/views/setting/license/index.vue'), + hidden: true, + meta: { + requiresAuth: true, + activeMenu: 'Setting', + }, + }, { path: 'about', name: 'About', diff --git a/frontend/src/store/index.ts b/frontend/src/store/index.ts index c6f68c4b5..9f802e74e 100644 --- a/frontend/src/store/index.ts +++ b/frontend/src/store/index.ts @@ -36,6 +36,8 @@ export const GlobalStore = defineStore({ currentDB: '', showEntranceWarn: true, defaultNetwork: 'all', + + isProductPro: false, }), getters: {}, actions: { diff --git a/frontend/src/store/interface/index.ts b/frontend/src/store/interface/index.ts index 5469fb89c..db9abb2d8 100644 --- a/frontend/src/store/interface/index.ts +++ b/frontend/src/store/interface/index.ts @@ -31,6 +31,8 @@ export interface GlobalState { currentDB: string; showEntranceWarn: boolean; defaultNetwork: string; + + isProductPro: boolean; } export interface MenuState { diff --git a/frontend/src/views/home/index.vue b/frontend/src/views/home/index.vue index 3d6297a93..30e108464 100644 --- a/frontend/src/views/home/index.vue +++ b/frontend/src/views/home/index.vue @@ -7,7 +7,41 @@ path: '/', }, ]" - /> + > + + + + +
+
+ +
+

{{ $t('setting.description') }}

+ + {{ $t('license.importLicense') }} + +
+ + {{ $t('license.knowMorePro') }} + +
+
+
+ + + @@ -233,8 +269,9 @@ import { dateFormatForSecond, computeSize } from '@/utils/util'; import { useRouter } from 'vue-router'; import { loadBaseInfo, loadCurrentInfo } from '@/api/modules/dashboard'; import { getIOOptions, getNetworkOptions } from '@/api/modules/monitor'; -import { getSettingInfo, loadUpgradeInfo } from '@/api/modules/setting'; +import { getLicense, getSettingInfo, loadUpgradeInfo } from '@/api/modules/setting'; import { GlobalStore } from '@/store'; +import Upload from '@/views/setting/license/upload/index.vue'; const router = useRouter(); const globalStore = GlobalStore(); @@ -258,6 +295,23 @@ const timeNetDatas = ref>([]); const ioOptions = ref(); const netOptions = ref(); + +const dialogFormVisible = ref(false); + +const uploadRef = ref(); +const loading = ref(); +const show = ref(null); + +const license = reactive({ + licenseName: '', + trial: true, + expiresAt: '', + assigneeName: '', + productName: '', + + status: '', +}); + const searchInfo = reactive({ ioOption: 'all', netOption: 'all', @@ -538,7 +592,30 @@ const onBlur = () => { isActive.value = false; }; +const search = async () => { + loading.value = true; + await getLicense() + .then((res) => { + loading.value = false; + license.status = res.data.status; + show.value = license.status !== 'Enable'; + }) + .catch(() => { + show.value = true; + loading.value = false; + }); +}; + +const toHalo = () => { + window.open('https://halo.test.lxware.cn/', '_blank', 'noopener,noreferrer'); +}; + +const toUpload = () => { + uploadRef.value.acceptParams(); +}; + onMounted(() => { + search(); window.addEventListener('focus', onFocus); window.addEventListener('blur', onBlur); loadSafeStatus(); @@ -607,4 +684,22 @@ onBeforeUnmount(() => { margin-bottom: 10px; } } + +.version { + font-size: 14px; + color: #858585; + text-decoration: none; + letter-spacing: 0.5px; +} + +.system-link { + margin-left: 15px; + + .svg-icon { + font-size: 7px; + } + span { + line-height: 20px; + } +} diff --git a/frontend/src/views/setting/index.vue b/frontend/src/views/setting/index.vue index e05e46858..587d8bf39 100644 --- a/frontend/src/views/setting/index.vue +++ b/frontend/src/views/setting/index.vue @@ -27,6 +27,10 @@ const buttons = [ label: i18n.global.t('setting.snapshot'), path: '/settings/snapshot', }, + { + label: i18n.global.t('setting.license'), + path: '/settings/license', + }, { label: i18n.global.t('setting.about'), path: '/settings/about', diff --git a/frontend/src/views/setting/license/index.vue b/frontend/src/views/setting/license/index.vue new file mode 100644 index 000000000..135d4360b --- /dev/null +++ b/frontend/src/views/setting/license/index.vue @@ -0,0 +1,209 @@ + + + + + diff --git a/frontend/src/views/setting/license/upload/index.vue b/frontend/src/views/setting/license/upload/index.vue new file mode 100644 index 000000000..a11a185e8 --- /dev/null +++ b/frontend/src/views/setting/license/upload/index.vue @@ -0,0 +1,107 @@ + + +