feat: Improve the license binding logic (#8457)

This commit is contained in:
ssongliu
2025-04-22 10:23:07 +00:00
committed by GitHub
parent 3b6bb9bd22
commit 176a78ced6
23 changed files with 279 additions and 40 deletions
+2
View File
@@ -63,6 +63,8 @@ ErrFreeNodeLimit: "Community version node limit reached, please go to www.lxware
ErrNodeBound: "This license is bound to another node, please check and retry!"
ErrNodeBoundDelete: "This license is bound and does not support delete operations. Please check and try again!"
ErrNodeBoundLimit: "The current free node has reached its limit, please check and try again!"
ErrLicenseFree: "Free nodes can only be used when the license is properly bound to a node. Please verify and try again!"
ErrLicenseUnbind: "Community Edition nodes detected for this license. Please unbind in [Panel Settings - License] and try again!"
ErrNoSuchNode: "Node information not found, please check and retry!"
ErrNodeUnbind: "This node is not within the license binding range, please check and retry!"
ErrNodeBind: "This node is already bound to a license, please check and retry!"
+2
View File
@@ -64,6 +64,8 @@ ErrFreeNodeLimit: "コミュニティ版のノード数が無料上限に達し
ErrNodeBound: "このライセンスは他のノードにバインドされています、確認して再試行してください!"
ErrNodeBoundDelete: "このライセンスはバインドされており、削除操作はサポートされていません。確認して再試行してください!"
ErrNodeBoundLimit: "現在の無料ノードは上限に達しました。確認して再試行してください!"
ErrLicenseFree: "ライセンスがノードに正常にバインドされている場合のみ、その無料ノードを使用できます。確認して再試行してください!"
ErrLicenseUnbind: "このライセンスにコミュニティ版ノードが存在します。[パネル設定 - ライセンス]でバインド解除してから再試行してください!"
ErrNoSuchNode: "そのノード情報が見つかりませんでした、確認して再試行してください!"
ErrNodeUnbind: "そのノードはライセンスのバインド範囲内ではありません、確認して再試行してください!"
ErrNodeBind: "そのノードはライセンスにバインドされています、確認して再試行してください!"
+2
View File
@@ -63,6 +63,8 @@ ErrFreeNodeLimit: "커뮤니티 에디션 노드 수가 무료 한도에 도달
ErrNodeBound: "이 라이선스는 다른 노드에 바인딩되어 있습니다. 다시 확인하고 시도해 주세요!"
ErrNodeBoundDelete: "이 라이센스는 바인딩되어 있으며 삭제 작업을 지원하지 않습니다. 확인 후 다시 시도해 주세요!"
ErrNodeBoundLimit: "현재 무료 노드가 한도에 도달했습니다. 확인 후 다시 시도해 주세요!"
ErrLicenseFree: "라이선스가 노드에 정상적으로 바인딩된 경우에만 무료 노드를 사용할 수 있습니다. 확인 후 다시 시도하세요!"
ErrLicenseUnbind: "이 라이선스에 커뮤니티 에디션 노드가 존재합니다. [패널 설정 - 라이선스]에서 바인딩 해제 후 다시 시도하세요!"
ErrNoSuchNode: "노드 정보를 찾을 수 없습니다. 다시 확인하고 시도해 주세요!"
ErrNodeUnbind: "이 노드가 라이선스 바인딩 범위에 있지 않음을 감지하였습니다. 다시 확인하고 시도해 주세요!"
ErrNodeBind: "이 노드가 이미 라이선스에 바인딩되어 있음을 감지하였습니다. 다시 확인하고 시도해 주세요!"
+2
View File
@@ -63,6 +63,8 @@ ErrFreeNodeLimit: "Jumlah nod versi komuniti telah mencapai had percuma, sila la
ErrNodeBound: "Lesen ini telah diikat dengan nod lain, sila semak dan cuba lagi!"
ErrNodeBoundDelete: "Lisensi ini telah diikat dan tidak menyokong operasi penghapusan. Sila semak dan cuba lagi!"
ErrNodeBoundLimit: "Nod percuma semasa telah mencapai had, sila semak dan cuba lagi!"
ErrLicenseFree: "Nod percuma hanya boleh digunakan apabila lesen terikat dengan betul pada nod. Sila semak dan cuba lagi!"
ErrLicenseUnbind: "Nod Edisi Komuniti dikesan untuk lesen ini. Sila lepaskan ikatan dalam [Tetapan Panel - Lesen] dan cuba lagi!"
ErrNoSuchNode: "Maklumat nod tidak ditemui, sila semak dan cuba lagi!"
ErrNodeUnbind: "Nod di luar skop lesen dikesan, sila semak dan cuba lagi!"
ErrNodeBind: "Nod ini telah diikat dengan lesen, sila semak dan cuba lagi!"
+2
View File
@@ -63,6 +63,8 @@ ErrFreeNodeLimit: "O número de nós da versão comunitária atingiu o limite gr
ErrNodeBound: "Esta licença já está vinculada a outro nó, por favor verifique e tente novamente!"
ErrNodeBoundDelete: "Esta licença está vinculada e não suporta operações de exclusão. Por favor, verifique e tente novamente!"
ErrNodeBoundLimit: "O nó gratuito atual atingiu seu limite, por favor verifique e tente novamente!"
ErrLicenseFree: "Nós gratuitos só podem ser usados quando a licença está devidamente vinculada a um nó. Por favor verifique e tente novamente!"
ErrLicenseUnbind: "Nós da Edição Comunitária detectados para esta licença. Por favor desvincule em [Configurações do Painel - Licença] e tente novamente!"
ErrNoSuchNode: "As informações deste nó não foram encontradas, por favor verifique e tente novamente!"
ErrNodeUnbind: "Nó fora do escopo da licença detectado, por favor verifique e tente novamente!"
ErrNodeBind: "Este nó já está vinculado a uma licença, por favor verifique e tente novamente!"
+2
View File
@@ -63,6 +63,8 @@ ErrFreeNodeLimit: "Количество узлов в версии сообще
ErrNodeBound: "Эта лицензия уже связана с другим узлом, проверьте и повторите попытку!"
ErrNodeBoundDelete: "Эта лицензия привязана и не поддерживает операции удаления. Пожалуйста, проверьте и попробуйте снова!"
ErrNodeBoundLimit: "Текущий бесплатный узел достиг предела, пожалуйста, проверьте и попробуйте снова!"
ErrLicenseFree: "Бесплатные узлы доступны только при правильной привязке лицензии к узлу. Проверьте и повторите попытку!"
ErrLicenseUnbind: "Обнаружены узлы Community Edition для этой лицензии. Отвяжите в [Настройки панели - Лицензия] и повторите попытку!"
ErrNoSuchNode: "Информация об узле не найдена, проверьте и повторите попытку!"
ErrNodeUnbind: "Обнаружен узел вне области действия лицензии, проверьте и повторите попытку!"
ErrNodeBind: "Этот узел уже связан с лицензией, проверьте и повторите попытку!"
+2
View File
@@ -63,6 +63,8 @@ ErrFreeNodeLimit: "社區版節點數量已達免費上限,請前往 www.lxwar
ErrNodeBound: "該許可證已綁定到其他節點,請檢查後重試!"
ErrNodeBoundDelete: "該許可證已被綁定,不支持刪除操作,請檢查後重試!"
ErrNodeBoundLimit: "當前免費節點已達上限,請檢查後重試!"
ErrLicenseFree: "僅當許可證正常綁定到節點後,才能使用其免費節點,請檢查後重試!"
ErrLicenseUnbind: "檢測到該許可證存在社區版節點,請在 [面板設定 - 許可證] 中解除綁定後重試!"
ErrNoSuchNode: "未能找到該節點信息,請檢查後重試!"
ErrNodeUnbind: "檢測到該節點未在許可證綁定範圍內,請檢查後重試!"
ErrNodeBind: "檢測到該節點已綁定許可證,請檢查後重試!"
+2
View File
@@ -63,6 +63,8 @@ ErrFreeNodeLimit: "社区版节点数量已达到免费上限,请前往 www.lx
ErrNodeBound: "该许可证已绑定到其他节点,请检查后重试!"
ErrNodeBoundDelete: "该许可证已被绑定,不支持删除操作,请检查后重试!"
ErrNodeBoundLimit: "当前免费节点已经达到上限,请检查后重试!"
ErrLicenseFree: "仅当许可证正常绑定到节点后,才能使用其免费节点,请检查后重试!"
ErrLicenseUnbind: "检测到该许可证存在社区版节点,请在 [ 面板设置 - 许可证 ] 中解绑后重试!"
ErrNoSuchNode: "未能找到该节点信息,请检查后重试!"
ErrNodeUnbind: "检测到该节点未在许可证绑定范围内,请检查后重试!"
ErrNodeBind: "检测到该节点已绑定许可证,请检查后重试!"
+9
View File
@@ -216,6 +216,13 @@ export namespace Setting {
smsUsed: number;
smsTotal: number;
}
export interface LicenseOptions {
id: number;
licenseName: string;
totalFreeCount: number;
availableXpackCount: number;
availableFreeCount: number;
}
export interface LicenseStatus {
productPro: string;
status: string;
@@ -227,5 +234,7 @@ export namespace Setting {
addr: string;
status: string;
version: string;
isXpack: boolean;
isBound: boolean;
}
}
+4 -1
View File
@@ -33,8 +33,11 @@ export const bindLicense = (id: number, nodeID: number) => {
export const unbindLicense = (id: number, force: boolean) => {
return http.post(`/core/licenses/unbind`, { id: id, force: force }, TimeoutEnum.T_60S);
};
export const changeBind = (id: number, nodeIDs: Array<number>) => {
return http.post(`/core/licenses/bind/free`, { licenseID: id, nodeIDs: nodeIDs }, TimeoutEnum.T_60S);
};
export const loadLicenseOptions = () => {
return http.get(`/core/licenses/options`);
return http.get<Array<Setting.LicenseOptions>>(`/core/licenses/options`);
};
export const listNodeOptions = () => {
return http.get<Array<Setting.NodeItem>>(`/core/nodes/list`);
+11
View File
@@ -301,6 +301,7 @@ const message = {
uninstalling: 'Uninstalling',
lost: 'Lost Contact',
bound: 'Bound',
unbind: 'Unbound',
exceptional: 'Exceptional',
free: 'Free',
enable: 'Enabled',
@@ -1880,6 +1881,10 @@ const message = {
oss: 'Open Source Software',
pro: 'Pro',
trial: 'Trial',
add: 'Add Community Edition',
licenseAlert:
'Community Edition nodes can only be added when the license is properly bound to a node. Only nodes properly bound to the license support switching.',
licenseUnbindHelper: 'Community Edition nodes detected for this license. Please unbind and try again!',
subscription: 'Subscription',
perpetual: 'Perpetual',
versionConstraint: '{0} Version Buyout',
@@ -3242,6 +3247,11 @@ const message = {
master: 'Main Node',
node: 'Node',
addr: 'Address',
nodeUnhealthy: 'Node status abnormal',
nodeUnhealthyHelper: 'Abnormal node status detected. Please check in [Node Management] and try again!',
nodeUnbind: 'Node not bound to license',
nodeUnbindHelper:
'Detected that this node is not bound to a license. Please bind it in [Panel Settings - License] menu and try again!',
memTotal: 'Total Memory',
nodeManagement: 'Node Management',
addNode: 'Add Node',
@@ -3276,6 +3286,7 @@ const message = {
'When the installation directory is empty, it will be installed in the /opt directory by default',
nodePort: 'Node Port',
offline: 'Offline mode',
freeCount: 'Free quota [{0}]',
offlineHelper: 'Used when the node is in an offline environment',
},
customApp: {
+12
View File
@@ -293,6 +293,7 @@ const message = {
uninstalling: 'アンインストール中',
lost: '接続失効',
bound: 'バインド済み',
unbind: '未バインド',
exceptional: '異常',
free: '空き',
enable: '有効',
@@ -1787,6 +1788,11 @@ const message = {
oss: '無料',
pro: '専門',
trial: '体験',
add: 'コミュニティ版を追加',
licenseAlert:
'ライセンスがノードに正常にバインドされている場合のみコミュニティ版ノードを追加できますライセンスに正常にバインドされているノードのみ切り替えがサポートされます',
licenseUnbindHelper:
'このライセンスにコミュニティ版ノードが存在しますバインドを解除してから再試行してください',
subscription: 'サブスクリプション',
perpetual: '永久ライセンス',
versionConstraint: '{0} バージョン買い取り',
@@ -3100,6 +3106,11 @@ const message = {
master: '主ノード',
node: 'ノード',
addr: 'アドレス',
nodeUnhealthy: 'ノード状態異常',
nodeUnhealthyHelper: 'ノード状態異常を検出しました[ノード管理]で確認してから再試行してください',
nodeUnbind: 'ノードがライセンスにバインドされていません',
nodeUnbindHelper:
'このノードがライセンスにバインドされていないことを検出しました[パネル設定 - ライセンス]メニューでバインドしてから再試行してください',
memTotal: '総メモリ',
nodeManagement: 'ノード管理',
addNode: 'ノードを追加',
@@ -3134,6 +3145,7 @@ const message = {
baseDirHelper: 'インストールディレクトリが空の場合デフォルトで/optディレクトリにインストールされます',
nodePort: 'ノードポート',
offline: 'オフラインモード',
freeCount: '無料枠 [{0}]',
offlineHelper: 'ノードがオフライン環境にある場合に使用',
},
customApp: {
+11
View File
@@ -295,6 +295,7 @@ const message = {
uninstalling: '제거 ',
lost: '연결 끊김',
bound: '바인딩됨',
unbind: '미바인드',
exceptional: '예외',
free: '여유',
enable: '활성화됨',
@@ -1760,6 +1761,10 @@ const message = {
oss: '오픈 소스 소프트웨어',
pro: 'Pro',
trial: '체험판',
add: '커뮤니티 에디션 추가',
licenseAlert:
'라이선스가 노드에 정상적으로 바인딩된 경우에만 커뮤니티 에디션 노드를 추가할 수 있습니다. 라이선스에 정상적으로 바인딩된 노드만 전환이 지원됩니다.',
licenseUnbindHelper: '이 라이선스에 커뮤니티 에디션 노드가 존재합니다. 바인딩 해제 후 다시 시도하세요!',
subscription: '구독',
perpetual: '영구 라이선스',
versionConstraint: '{0} 버전 일시불 구매',
@@ -3049,6 +3054,11 @@ const message = {
master: ' 노드',
node: '노드',
addr: '주소',
nodeUnhealthy: '노드 상태 이상',
nodeUnhealthyHelper: '노드 상태 이상이 감지되었습니다. [노드 관리]에서 확인 다시 시도하세요!',
nodeUnbind: '노드가 라이선스에 바인딩되지 않음',
nodeUnbindHelper:
' 노드가 라이선스에 바인딩되지 않은 것으로 감지되었습니다. [패널 설정 - 라이선스] 메뉴에서 바인딩 다시 시도하세요!',
memTotal: ' 메모리',
nodeManagement: '노드 관리',
addNode: '노드 추가',
@@ -3082,6 +3092,7 @@ const message = {
baseDirHelper: '설치 디렉토리가 비어 있으면 기본적으로 /opt 디렉토리에 설치됩니다',
nodePort: '노드 포트',
offline: '오프라인 모드',
freeCount: '무료 할당량 [{0}]',
offlineHelper: '노드가 오프라인 환경일 사용',
},
customApp: {
+11
View File
@@ -301,6 +301,7 @@ const message = {
uninstalling: 'Menyahpasang',
lost: 'Hilang',
bound: 'Terikat',
unbind: 'Tidak terikat',
exceptional: 'Luar biasa',
free: 'Bebas',
enable: 'Aktif',
@@ -1845,6 +1846,10 @@ const message = {
oss: 'Perisian Sumber Terbuka',
pro: 'Pro',
trial: 'Percubaan',
add: 'Tambah Edisi Komuniti',
licenseAlert:
'Nod Edisi Komuniti hanya boleh ditambah apabila lesen terikat dengan betul pada nod. Hanya nod yang terikat dengan betul pada lesen menyokong penukaran.',
licenseUnbindHelper: 'Nod Edisi Komuniti dikesan untuk lesen ini. Sila lepaskan ikatan dan cuba lagi!',
subscription: 'Langganan',
perpetual: 'Lesen Perpetual',
versionConstraint: '{0} Pembelian versi',
@@ -3170,6 +3175,11 @@ const message = {
master: 'Nod Utama',
node: 'Nod',
addr: 'Alamat',
nodeUnhealthy: 'Status nod tidak normal',
nodeUnhealthyHelper: 'Status nod tidak normal dikesan. Sila semak dalam [Pengurusan Nod] dan cuba lagi!',
nodeUnbind: 'Nod tidak terikat pada lesen',
nodeUnbindHelper:
'Terdeteksi nod ini tidak terikat pada lesen. Sila ikat dalam menu [Tetapan Panel - Lesen] dan cuba lagi!',
memTotal: 'Jumlah Memori',
nodeManagement: 'Pengurusan Nod',
addNode: 'Tambah Nod',
@@ -3205,6 +3215,7 @@ const message = {
baseDirHelper: 'Apabila direktori pemasangan kosong, secara lalai akan dipasang di direktori /opt',
nodePort: 'Port Nod',
offline: 'Mod luar talian',
freeCount: 'Had percuma [{0}]',
offlineHelper: 'Digunakan apabila nod berada dalam persekitaran luar talian',
},
customApp: {
+13
View File
@@ -299,6 +299,7 @@ const message = {
uninstalling: 'Desinstalando',
lost: 'Perdido',
bound: 'Vinculado',
unbind: 'Desvinculado',
exceptional: 'Excepcional',
free: 'Livre',
enable: 'Habilitado',
@@ -1831,6 +1832,11 @@ const message = {
oss: 'Open Source Software',
pro: 'Pro',
trial: 'Teste',
add: 'Adicionar Edição Comunitária',
licenseAlert:
'Nós da Edição Comunitária podem ser adicionados quando a licença está devidamente vinculada a um . Apenas nós devidamente vinculados à licença suportam troca.',
licenseUnbindHelper:
'Nós da Edição Comunitária detectados para esta licença. Por favor, desvincule e tente novamente!',
subscription: 'Assinatura',
perpetual: 'Licença Perpétua',
versionConstraint: '{0} Compra do versão',
@@ -3174,6 +3180,12 @@ const message = {
master: ' Principal',
node: '',
addr: 'Endereço',
nodeUnhealthy: 'Estado do anormal',
nodeUnhealthyHelper:
'Estado anormal do detectado. Por favor verifique em [Gestão de Nós] e tente novamente!',
nodeUnbind: ' não vinculado à licença',
nodeUnbindHelper:
'Detectamos que este não está vinculado a uma licença. Por favor vincule no menu [Configurações do Painel - Licença] e tente novamente!',
memTotal: 'Memória Total',
nodeManagement: 'Gerenciamento de ',
addNode: 'Adicionar ',
@@ -3208,6 +3220,7 @@ const message = {
baseDirHelper: 'Quando o diretório de instalação está vazio, será instalado por padrão no diretório /opt',
nodePort: 'Porta do ',
offline: 'Modo offline',
freeCount: 'Cota gratuita [{0}]',
offlineHelper: 'Usado quando o está em ambiente offline',
},
customApp: {
+13
View File
@@ -296,6 +296,7 @@ const message = {
uninstalling: 'Удаление',
lost: 'Потеряно',
bound: 'Привязано',
unbind: 'Не привязано',
exceptional: 'Исключение',
free: 'Свободно',
enable: 'Включено',
@@ -1830,6 +1831,11 @@ const message = {
oss: 'Open Source Software',
pro: 'Pro',
trial: 'Пробная версия',
add: 'Добавить Community Edition',
licenseAlert:
'Узлы Community Edition можно добавлять только при правильной привязке лицензии к узлу. Переключение поддерживается только для узлов, правильно привязанных к лицензии.',
licenseUnbindHelper:
'Обнаружены узлы Community Edition для этой лицензии. Отвяжите лицензию и повторите попытку!',
subscription: 'Подписка',
perpetual: 'Пожизненная лицензия',
versionConstraint: '{0} Выкуп версии',
@@ -3162,6 +3168,12 @@ const message = {
master: 'Главный узел',
node: 'Узел',
addr: 'Адрес',
nodeUnhealthy: 'Некорректное состояние узла',
nodeUnhealthyHelper:
'Обнаружено некорректное состояние узла. Проверьте в [Управление узлами] и повторите попытку!',
nodeUnbind: 'Узел не привязан к лицензии',
nodeUnbindHelper:
'Обнаружено, что узел не привязан к лицензии. Привяжите в меню [Настройки панели - Лицензия] и повторите попытку!',
memTotal: 'Общая память',
nodeManagement: 'Управление узлом',
addNode: 'Добавить узел',
@@ -3196,6 +3208,7 @@ const message = {
baseDirHelper: 'Если каталог установки пуст, по умолчанию он будет установлен в каталоге /opt',
nodePort: 'Порт узла',
offline: 'Автономный режим',
freeCount: 'Бесплатная квота [{0}]',
offlineHelper: 'Используется, когда узел находится в автономной среде',
},
customApp: {
+9
View File
@@ -295,6 +295,7 @@ const message = {
uninstalling: '卸載中',
lost: '已失聯',
bound: '已綁定',
unbind: '未綁定',
exceptional: '異常',
free: '空閒',
enable: '已啟用',
@@ -1747,6 +1748,9 @@ const message = {
oss: '社區版',
pro: '專業版',
trial: '試用',
add: '添加社區版',
licenseAlert: '僅當許可證正常綁定到節點時該許可證才能添加社區版節點只有正常綁定到許可證的節點支援切換',
licenseUnbindHelper: '檢測到該許可證存在社區版節點請解除綁定後重試',
subscription: '訂閱',
perpetual: '永久授權',
versionConstraint: '{0} 版本買斷',
@@ -3007,6 +3011,10 @@ const message = {
master: '主節點',
node: '節點',
addr: '地址',
nodeUnhealthy: '節點狀態異常',
nodeUnhealthyHelper: '檢測到該節點狀態異常請在 [節點管理] 中檢查後重試',
nodeUnbind: '節點未綁定許可證',
nodeUnbindHelper: '檢測到該節點未綁定許可證請在 [面板設定 - 許可證] 選單中綁定後重試',
memTotal: '記憶體總計',
nodeManagement: '節點管理',
addNode: '新增節點',
@@ -3039,6 +3047,7 @@ const message = {
baseDirHelper: '安裝目錄為空時預設安裝於 /opt 目錄下',
nodePort: '節點端口',
offline: '離線模式',
freeCount: '免費額度 [{0}]',
offlineHelper: '當節點處於離線環境時使用',
},
customApp: {
+9
View File
@@ -293,6 +293,7 @@ const message = {
uninstalling: '卸载中',
lost: '已失联',
bound: '已绑定',
unbind: '未绑定',
exceptional: '异常',
free: '空闲',
enable: '已启用',
@@ -1739,6 +1740,9 @@ const message = {
oss: '社区版',
pro: '专业版',
trial: '试用',
add: '添加社区版',
licenseAlert: '仅当许可证正常绑定到节点时该许可证才能添加社区版节点只有正常绑定到许可证的节点支持切换',
licenseUnbindHelper: '检测到该许可证存在社区版节点请解绑后重试',
subscription: '订阅',
perpetual: '永久授权',
versionConstraint: '{0} 版本买断',
@@ -2989,6 +2993,10 @@ const message = {
master: '主节点',
node: '节点',
addr: '地址',
nodeUnhealthy: '节点状态异常',
nodeUnhealthyHelper: '检测到该节点状态异常请在 [节点管理] 中检查后重试',
nodeUnbind: '节点未绑定许可证',
nodeUnbindHelper: '检测到该节点未绑定许可证请在 [ 面板设置 - 许可证 ] 菜单中绑定后重试',
memTotal: '内存总计',
nodeManagement: '节点管理',
addNode: '添加节点',
@@ -3021,6 +3029,7 @@ const message = {
baseDirHelper: '安装目录为空时默认安装在 /opt 目录下',
nodePort: '节点端口',
offline: '离线模式',
freeCount: '免费额度 [{0}] ',
offlineHelper: '节点为离线环境时使用',
},
customApp: {
@@ -41,9 +41,18 @@
:key="item.name"
>
{{ item.name }}
<el-icon class="icon-status" v-if="item.status !== 'Healthy'" type="danger">
<Warning />
</el-icon>
<el-tooltip
:content="item.isBound ? $t('xpack.node.nodeUnhealthy') : $t('xpack.node.nodeUnbind')"
placement="right"
>
<el-icon
class="icon-status"
v-if="item.status !== 'Healthy' || !item.isBound"
type="danger"
>
<Warning />
</el-icon>
</el-tooltip>
</div>
</div>
<el-divider class="divider" />
@@ -120,16 +129,21 @@ const changeNode = (command: string) => {
}
for (const item of nodes.value) {
if (item.name == command) {
if (props.version == item.version) {
globalStore.currentNode = command || 'local';
globalStore.isOffline = item.isOffline;
location.reload();
if (!item.isBound) {
MsgError(i18n.global.t('xpack.node.nodeUnbindHelper'));
return;
}
if (item.version !== props.version) {
if (item.status !== 'Healthy') {
MsgError(i18n.global.t('xpack.node.nodeUnhealthyHelper'));
return;
}
if (props.version != item.version) {
MsgError(i18n.global.t('setting.versionNotSame'));
return;
}
globalStore.currentNode = command || 'local';
globalStore.isOffline = item.isOffline;
location.reload();
}
}
};
@@ -0,0 +1,96 @@
<template>
<DrawerPro v-model="drawerVisible" :header="$t('setting.license')" :resource="licenseName" @close="handleClose">
<el-form label-position="top" :model="form" @submit.prevent v-loading="loading">
<el-form-item :label="$t('license.add')" prop="nodeID">
<el-select filterable multiple v-model="form.nodeIDs" class="w-full">
<div v-for="item in unboundOptions" :key="item.id">
<el-option :label="item.name" :value="item.id" />
</div>
</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()">
{{ $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 { changeBind, listNodeOptions } from '@/api/modules/setting';
import { GlobalStore } from '@/store';
import { Setting } from '@/api/interface/setting';
const globalStore = GlobalStore();
interface DialogProps {
licenseID: number;
licenseName: string;
freeNodes: Array<Setting.NodeItem>;
}
const drawerVisible = ref();
const loading = ref();
const licenseName = ref();
const unboundOptions = ref([]);
const form = reactive({
licenseID: null,
nodeIDs: [],
});
const acceptParams = (params: DialogProps): void => {
licenseName.value = params.licenseName;
form.licenseID = params.licenseID;
form.nodeIDs = [];
for (const item of params.freeNodes) {
if (!item.isXpack) {
form.nodeIDs.push(item.id);
}
}
unboundOptions.value = [];
loadNodes();
drawerVisible.value = true;
};
const onBind = async () => {
loading.value = true;
await changeBind(form.licenseID, form.nodeIDs)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
globalStore.isProductPro = false;
window.location.reload();
})
.catch(() => {
loading.value = false;
});
};
const loadNodes = async () => {
await listNodeOptions().then((res) => {
let nodeOptions = res.data || [];
for (const item of nodeOptions) {
if (!item.isBound) {
unboundOptions.value.push(item);
continue;
}
for (const item2 of form.nodeIDs) {
if (item.id === item2) {
unboundOptions.value.push(item);
}
}
}
});
};
const handleClose = () => {
drawerVisible.value = false;
};
defineExpose({
acceptParams,
});
</script>
+41 -31
View File
@@ -6,6 +6,9 @@
{{ $t('commons.button.add') }}
</el-button>
</template>
<template #prompt>
<el-alert type="info" :title="$t('license.licenseAlert')" :closable="false" />
</template>
<template #rightToolBar>
<TableRefresh @search="search()" />
<TableSetting title="backup-account-refresh" @search="search()" />
@@ -36,32 +39,21 @@
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column :label="$t('setting.bindNode')">
<el-table-column :label="$t('setting.bindNode')" :min-width="120">
<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"
>
<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>
</template>
</el-popover>
<span v-else link type="primary">({{ row.bindCount }} / {{ row.freeCount }})</span>
<div v-if="row.status !== 'Free'">
<div>
{{ $t('license.pro') }}:
{{ row.bindNode === '127.0.0.1' ? $t('xpack.node.master') : row.bindNode }}
</div>
<div>
{{ $t('license.oss') }}:
<el-button class="bind-button" @click="onBindFree(row)" link type="primary">
{{ row.bindCount }} / {{ row.freeCount }}
</el-button>
</div>
</div>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column
@@ -82,7 +74,8 @@
</LayoutContent>
<LicenseImport ref="licenseRef" />
<LicenseBind ref="bindRef" />
<BindFree ref="bindFreeRef" />
<BindXpack ref="bindXpackRef" />
<OpDialog ref="opRef" @search="search" />
<OpDialog ref="opRef2" @search="search" @submit="submitUnbind">
<template #content>
@@ -103,17 +96,19 @@
import { ref, reactive, onMounted } from 'vue';
import { deleteLicense, searchLicense, syncLicense, unbindLicense } from '@/api/modules/setting';
import LicenseImport from '@/components/license-import/index.vue';
import LicenseBind from '@/views/setting/license/bind/index.vue';
import BindFree from '@/views/setting/license/bind/free.vue';
import BindXpack from '@/views/setting/license/bind/xpack.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';
import { initFavicon } from '@/utils/xpack';
const globalStore = GlobalStore();
const loading = ref();
const licenseRef = ref();
const bindRef = ref();
const bindFreeRef = ref();
const bindXpackRef = ref();
const opRef = ref();
const opRef2 = ref();
const forceUnbind = ref();
@@ -152,7 +147,19 @@ const onSync = async (row: any) => {
});
};
const onBindFree = async (row: any) => {
bindFreeRef.value.acceptParams({
licenseID: row.id,
licenseName: row.licenseName,
freeNodes: row.freeNodes || [],
});
};
const onUnbind = async (row: any) => {
if (row.freeNodes && (row.freeNodes.length > 1 || !row.freeNodes[0].isXpack)) {
MsgError(i18n.global.t('license.licenseUnbindHelper'));
return;
}
unbindRow.value = row;
opRef2.value.acceptParams({
title: i18n.global.t('commons.button.unbind'),
@@ -242,7 +249,7 @@ const buttons = [
return row.status !== 'Free';
},
click: (row: any) => {
bindRef.value.acceptParams({ licenseID: row.id, licenseName: row.licenseName });
bindXpackRef.value.acceptParams({ licenseID: row.id, licenseName: row.licenseName });
},
},
{
@@ -256,9 +263,6 @@ const buttons = [
},
{
label: i18n.global.t('commons.button.edit'),
disabled: (row: any) => {
return row.status === 'Free';
},
click: (row: any) => {
licenseRef.value.acceptParams({ oldLicense: row.licenseName });
},
@@ -293,3 +297,9 @@ onMounted(() => {
search();
});
</script>
<style lang="scss" scoped>
.bind-button {
margin-top: -2px;
}
</style>
@@ -374,8 +374,10 @@ const onReconnect = async (item: any) => {
ctx.refs[`t-${item.index}`][0].acceptParams({
endpoint: '/api/v2/core/hosts/terminal',
args: `id=${item.wsID}`,
initCmd: initCmd.value,
error: res.data ? '' : 'Failed to set up the connection. Please check the host information',
});
initCmd.value = '';
});
syncTerminal();
};