UNPKG

node-red-contrib-ewelink-cube

Version:

Node-RED integration with eWeLink Cube

576 lines (541 loc) 21.4 kB
<link rel="stylesheet" href="resources/node-red-contrib-ewelink-cube/css/element.css" /> <link rel="stylesheet" href="resources/node-red-contrib-ewelink-cube/css/index.css" /> <script src="resources/node-red-contrib-ewelink-cube/js/vue2.min.js"></script> <script src="resources/node-red-contrib-ewelink-cube/js/element.js"></script> <script src="resources/node-red-contrib-ewelink-cube/js/config.js"></script> <script src="resources/node-red-contrib-ewelink-cube/js/tools.js"></script> <script src="resources/node-red-contrib-ewelink-cube/js/capabilitiesTransform.js"></script> <script src="resources/node-red-contrib-ewelink-cube/js/stateTransform.js"></script> <script type="text/html" data-template-name="api-server"> <div class="form-row"> <label for="node-config-input-name"> <i class="fa fa-tag"></i> <span data-i18n="api-server.label.name"></span> </label> <input type="text" id="node-config-input-name"> </div> <div class="form-row" style="position: relative;"> <span class="require">*</span> <label for="node-config-input-ip"> <i class="fa fa-server"></i> <span data-i18n="api-server.label.ip"></span> </label> <div style="display: inline-flex; width: 70%;position:relative"> <input type="text" id="node-config-input-ip" style="width: 100%; margin-right: 10px;" data-i18n="[placeholder]api-server.message.place_input"> <!-- hidden input, if node-config-input-ip is a domain name, then use node-config-input-ipaddr --> <input type="text" id="node-config-input-ipaddr" style="display: none;"> <ul class="ip-search-ul"></ul> <div class="ip-arrow"><i class="triangle"></i></div> <a class="red-ui-button" id="node-config-input-search-btn"> <i class="fa fa-search"></i> </a> </div> <div id="error-hint-input-ip" style="font-size: 12px; color: red; padding-left: 104px;"></div> </div> <div class="form-row"> <label> <i class="fa fa-id-badge"></i> <span data-i18n="api-server.label.token"></span> </label> <button id="node-config-input-get-token-btn" type="button" class="red-ui-button" style="width: 70%;background-color: #ccc;color: #ffff!important;" disabled> <!-- Spin icon --> <!-- <i class="fa fa-search spinner"></i> --> <span class="btn-text" data-i18n="api-server.message.get_token"></span> <span class="count-down" style="margin-left: 8px;"></span> </button> <!-- Store token --> <input type="text" id="node-config-input-token" style="width: 0; height: 0; display: none;"> <!-- Store token IP address --> <input type="text" id="node-config-input-token-ipaddr" style="display: none;"> <div id="error-hint-get-token" style="font-size: 12px; color: red; padding-left: 104px;"></div> </div> <div class="illustrate"> <p class="ip-address"><strong data-i18n="ip-illustrate"></strong></p> <p class="ip-address" data-i18n="api-server.label.confirm_local_network"></p> <p class="ip-address" data-i18n="api-server.label.input_ip_port"></p> <p class="ip-address" data-i18n="api-server.label.nspro_input_ip_port"></p> </div> </script> <script type="text/javascript"> //ul show or hidden let ihostIpListVisible = false; let outerNode = null; let ihostList = null; const API_PREFIX = 'ewelink-cube-api-v1'; ;(function () { const DOM_ID_SEARCH_BTN = '#node-config-input-search-btn'; const DOM_ID_SEARCH_BTN_ICON = `${DOM_ID_SEARCH_BTN} i`; const DOM_ID_GET_TOKEN_BTN = '#node-config-input-get-token-btn'; const DOM_ID_GET_TOKEN_BTN_ICON = `${DOM_ID_GET_TOKEN_BTN} i`; const DOM_ID_GET_TOKEN_BTN_TEXT = `${DOM_ID_GET_TOKEN_BTN} .btn-text`; const DOM_ID_GET_TOKEN_COUNT_DOWN = `${DOM_ID_GET_TOKEN_BTN} .count-down`; const DOM_ID_INPUT_NAME = '#node-config-input-name'; const DOM_ID_INPUT_IP = '#node-config-input-ip'; const DOM_ID_INPUT_IPADDR = '#node-config-input-ipaddr'; const DOM_ID_INPUT_TOKEN = '#node-config-input-token'; const DOM_ID_INPUT_TOKEN_IPADDR = '#node-config-input-token-ipaddr'; const DOM_ID_ERROR_HINT_INPUT_IP = '#error-hint-input-ip'; const DOM_ID_ERROR_HINT_GET_TOKEN = '#error-hint-get-token'; const DOM_IP_SEARCH_LIST = '.ip-search-ul'; const DOM_IP_ARROW = '.ip-arrow'; const DOM_TRIANGLE = '.triangle'; // Count down timer let countDownTimerId = null; /** * Set search button icon spin state. */ function setSearchBtnIconSpin(val) { if (val) { $(DOM_ID_SEARCH_BTN_ICON).addClass('spinner'); } else { $(DOM_ID_SEARCH_BTN_ICON).removeClass('spinner'); } } /** * Set get token button spin state. */ function setGetTokenBtnSpin(val) { if (val) { disableGetTokenBtn(); $(DOM_ID_GET_TOKEN_BTN_ICON).css('display', 'inline-block'); // $(DOM_ID_GET_TOKEN_BTN_TEXT).css('display', 'none'); } else { $(DOM_ID_GET_TOKEN_BTN_ICON).css('display', 'none'); // $(DOM_ID_GET_TOKEN_BTN_TEXT).css('display', 'inline-block'); } } /** * Enable get token button. */ function enableGetTokenBtn() { $(DOM_ID_GET_TOKEN_BTN).prop('disabled', false); $(DOM_ID_GET_TOKEN_BTN).css('background-color','#1890ff'); } /** * Disable get token button. */ function disableGetTokenBtn() { $(DOM_ID_GET_TOKEN_BTN).prop('disabled', true); $(DOM_ID_GET_TOKEN_BTN).css('background-color','#ccc'); } /** * Test token. */ function testToken(ip, token) { $.ajax({ type: 'POST', url: `${API_PREFIX}/test-token`, contentType: 'application/json; charset=utf-8', data: JSON.stringify({ ip, token }), success(res) { if (res.error !== 0) { // wrong token enableGetTokenBtn(); }else{ $(DOM_ID_GET_TOKEN_BTN_TEXT).text(outerNode._('api-server.message.has_token')); } }, error(err) { console.error(err); enableGetTokenBtn(); } }); } /** init query mdns */ function queryMdns(){ $.ajax({ type: 'POST', url: `${API_PREFIX}/query/mdns`, contentType: 'application/json; charset=utf-8', data: {}, success(res) { console.log('init query mdns',res); }, error(err) {} }); } /** * Test valid hostname * * testValidHost('192.168.3.12') => true * testValidHost('1.1.1.1.1.1') => false * testValidHost('ihost.local') => true * testValidHost('ihost-xxxxx.local') => true */ function testValidHost(input) { const host = input.toLowerCase(); let validIpRegex; if(input.indexOf(':')!== -1){ validIpRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4}))$/; }else{ validIpRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; } const validIhostName = host.startsWith('ihost') && host.endsWith('.local'); if (!validIpRegex.test(host) && !validIhostName && host !== 'ihost' && host !== 'nspanelpro' && host !== 'ewelinksmartpanel') { return false; } else { return true; } } /** * Search button click event handler. */ function searchBtnOnClickHandler() { const node = this; const ip = $(DOM_ID_INPUT_IP).val().trim(); const token = $(DOM_ID_INPUT_TOKEN).val(); if (!ip) { $(DOM_ID_ERROR_HINT_INPUT_IP).text(node._('api-server.message.please_input_ip')); return; } else { if (!testValidHost(ip)) { $(DOM_ID_ERROR_HINT_INPUT_IP).text(node._('api-server.message.invalid_ip')); return; } else { setSearchBtnIconSpin(true); $(DOM_ID_ERROR_HINT_INPUT_IP).text(''); $.ajax({ type: 'POST', url: `${API_PREFIX}/get-bridge-info`, contentType: 'application/json; charset=utf-8', data: JSON.stringify({ ip }), success(res) { if (typeof res !== 'object') { console.log('not object'); } else if (res.error !== 0) { $(DOM_ID_ERROR_HINT_INPUT_IP).text(node._('api-server.message.request_failed')); } else { RED.notify(node._('api-server.message.connect_success'), { type: 'success '}); if (token) { testToken(ip, token); } else { enableGetTokenBtn(); $(DOM_ID_GET_TOKEN_BTN_TEXT).text(node._('api-server.message.get_token')); } } setSearchBtnIconSpin(false); }, error(err) { console.error(err); $(DOM_ID_ERROR_HINT_INPUT_IP).text(node._('api-server.message.request_failed')); setSearchBtnIconSpin(false); enableGetTokenBtn(); } }); } } } /** * Whether get token button exist. */ function getTokenBtnExist() { return $(DOM_ID_GET_TOKEN_BTN).length !== 0; } /** * Format count. */ function formatCount(count) { const min = Math.floor(count / 60); const sec = count % 60; return `(${min}min${sec}s)`; } /** * Set count down timer. */ function setCountDown() { let count = 300; // Count 1 time. $(DOM_ID_GET_TOKEN_COUNT_DOWN).text(`${formatCount(--count)}`); countDownTimerId = setInterval(() => { if (count > 0 && getTokenBtnExist()) { $(DOM_ID_GET_TOKEN_COUNT_DOWN).text(`${formatCount(--count)}`); } else { count = 300; $(DOM_ID_GET_TOKEN_COUNT_DOWN).text(''); clearInterval(countDownTimerId); } }, 1000); } /** * Unset count down timer. */ function unsetCountDown() { $(DOM_ID_GET_TOKEN_COUNT_DOWN).text(''); clearInterval(countDownTimerId); } /** * Get token button click event handler. */ function getTokenBtnOnClickHandler() { const node = this; let ip = $('#node-config-input-ip').val().trim(); // let ip = $('#node-config-input-ip1').val().trim(); if (!ip) { $(DOM_ID_ERROR_HINT_INPUT_IP).text(node._('api-server.message.please_input_ip')); return; } if( ihostList !== null && ihostList.length>0){ for(const item of ihostList){ if(item.name === ip){ ip = item.ip; } } } console.log('get token 的 IP',ip); $(DOM_ID_ERROR_HINT_INPUT_IP).text(''); setGetTokenBtnSpin(true); setCountDown(); $.ajax({ type: 'POST', url: `${API_PREFIX}/get-bridge-token`, contentType: 'application/json; charset=utf-8', data: JSON.stringify({ ip }), success(res) { console.log(res); if (res.error === 0) { $(DOM_ID_ERROR_HINT_GET_TOKEN).text(''); $(DOM_ID_INPUT_TOKEN).val(res.data.token); $(DOM_ID_INPUT_TOKEN_IPADDR).val(ip); $(DOM_ID_GET_TOKEN_BTN_TEXT).text(node._('api-server.message.has_token')); RED.notify(node._('api-server.message.get_token_success'), { type: 'success '}); } else { $(DOM_ID_ERROR_HINT_GET_TOKEN).text(node._('api-server.message.request_failed')); } setGetTokenBtnSpin(false); unsetCountDown(); }, error(err) { console.log(err); setGetTokenBtnSpin(false); unsetCountDown(); } }) } /** * show or hidden ip list */ // function focusIp(){ // ihostIpListVisible = true; // $(DOM_IP_SEARCH_LIST).css('display','block'); // $(DOM_TRIANGLE).css('rotate','180deg'); // } // function blurIp(){ // setTimeout(()=>{ // ihostIpListVisible = false; // $(DOM_IP_SEARCH_LIST).css('display','none'); // $(DOM_TRIANGLE).css('rotate','0deg'); // },300); // } function showIpList(){ ihostIpListVisible = !ihostIpListVisible; if(ihostIpListVisible){ renderIpListOption(); $(DOM_IP_SEARCH_LIST).css('display','block'); $(DOM_TRIANGLE).css('rotate','180deg'); }else{ $(DOM_IP_SEARCH_LIST).css('display','none'); $(DOM_TRIANGLE).css('rotate','0deg'); } } function renderIpListOption(){ $(DOM_IP_SEARCH_LIST).empty(); let templateLi = ''; $.ajax({ type: 'POST', url: `${API_PREFIX}/get-local-ip-list`, contentType: 'application/json; charset=utf-8', data: {}, success(res) { if (res.error === 0) { ihostList = res.data.ihostList; console.log('ihostlist----------->',ihostList); for(const item of res.data.ihostList){ // Only display domain name starts with ihost- and ends with .local /* TODO: release this code after next version if (item.name.startsWith('ihost-') && item.name.endsWith('.local')) { templateLi+=`<li class="ip-search-li" value="${item.ip}" onClick="selectIp(this)">${item.name}</li>`; } */ templateLi+=`<li class="ip-search-li" value="${item.ip}" onClick="selectIp(this)">${item.name}</li>`; } $(DOM_IP_SEARCH_LIST).append(templateLi); } }, error(err) {} }); } function ipChangeHandler(e){ disableGetTokenBtn(); $(DOM_ID_GET_TOKEN_BTN_TEXT).text(outerNode._('api-server.message.get_token')); } // Emit all error logs. RED.comms.subscribe('EVENT_NODE_RED_ERROR', (topic, payload) => { RED.notify(payload.msg, { type: 'error' }); }); RED.nodes.registerType('api-server', { category: 'config', defaults: { name: { value: '', }, ip: { value: '', required: true }, ipaddr: { value: '', }, token: { value: '', required: true }, tokenIpaddr: { value: '' } }, label() { return this.name || this.ip || 'Token'; }, oneditprepare() { const node = this; outerNode = this; // Set default name if input field is empty. if ($(DOM_ID_INPUT_NAME).val() === '') { $(DOM_ID_INPUT_NAME).val('Token'); } queryMdns(); // Hide get token button icon. $(DOM_ID_GET_TOKEN_BTN_ICON).css('display', 'none'); // Set search button click event. $(DOM_ID_SEARCH_BTN).on('click', searchBtnOnClickHandler.bind(this)); // Set get token button click event. $(DOM_ID_GET_TOKEN_BTN).on('click', getTokenBtnOnClickHandler.bind(this)); // Set get token button text. const token = $(DOM_ID_INPUT_TOKEN).val(); if (token) { $(DOM_ID_INPUT_TOKEN_IPADDR).val(node.ipaddr); setTimeout(() => { $(DOM_ID_GET_TOKEN_BTN_TEXT).text(node._('api-server.message.has_token')); }, 0); } $(DOM_ID_INPUT_IP).on('change', ipChangeHandler); $(DOM_IP_ARROW).on('click',showIpList); }, oneditsave() { // Cache api server node data when user clicks `save` button. const id = this.id; const name = $(DOM_ID_INPUT_NAME).val().trim(); let ip = $(DOM_ID_INPUT_IP).val().trim(); if( ihostList !== null && ihostList.length > 0){ //localName change ip for(const item of ihostList){ if(item.name === ip){ $(DOM_ID_INPUT_IPADDR).val(item.ip); ip = item.ip; } } } const token = $(DOM_ID_INPUT_TOKEN).val().trim(); $.ajax({ type: 'POST', url: `${API_PREFIX}/cache/add-api-server-node`, contentType: 'application/json; charset=utf-8', data: JSON.stringify({ id, name, ip, token }) }); }, oneditdelete() { // Remove api server node data from cache when user clicks `delete` button. const id = this.id; $.ajax({ type: 'POST', url: `${API_PREFIX}/cache/remove-api-server-node`, contentType: 'application/json; charset=utf-8', data: JSON.stringify({ id }) }); } }); })(); function selectIp(that){ // $('#node-config-input-ip').val($(that).attr("value")); $('#node-config-input-ip').val($(that).text()); ihostIpListVisible = false; $('.triangle').css('rotate','0deg'); $('.ip-search-ul').css('display','none'); if ($(that).attr('value') !== $('#node-config-input-token-ipaddr').val()) { $('#node-config-input-get-token-btn').prop('disabled', false); $('#node-config-input-get-token-btn').css('background-color','#1890ff'); $('#node-config-input-get-token-btn .btn-text').text(outerNode._('api-server.message.get_token')); } else { $('#node-config-input-get-token-btn').prop('disabled', true); $('#node-config-input-get-token-btn').css('background-color','#cccccc'); $('#node-config-input-get-token-btn .btn-text').text(outerNode._('api-server.message.has_token')); } } </script> <style> .require{ position:absolute; left: -8px; top: 10px; color: red; font-size: 20px; } .ip-search-ul{ position: absolute; top:36px; left:-26px; width:87%; min-height:20px; max-height:200px; background-color: #FFFFFF; border-radius: 10px; display: none; border:1px solid #ccc; z-index: 99; cursor: pointer; overflow: auto; overflow-x: hidden; } .ip-search-li{ height:20px; font-size: 15px; padding:4px 10px 4px 6px; list-style: none; } .ip-search-li:hover{ background-color: #1967d2; color: #FFFFFF; } .ip-arrow{ position:absolute; right:55px; top:8px; display: flex; justify-content: center; align-items: center; width:18px; height: 18px; border-radius: 4px; border: 1px solid #ccc; cursor: pointer; } .triangle { width: 0; height: 0; border-top: 5px solid #ccc; border-right: 5px solid transparent; border-left: 5px solid transparent; } .illustrate{ width: 450px; margin-top: 40px; } .ip-address{ padding-left: 15px; } </style>