node-red-contrib-google-smarthome
Version:
Lets you control Node-Red via Google Assistant or the Google Home App
475 lines (430 loc) • 23.4 kB
HTML
<!--
node-red-contrib-google-smarthome
Copyright (C) 2024 Michael Jacobsen and others.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<script type="text/x-red" data-template-name="googlesmarthome-client">
<style>
ol#node-config-input-googlesmarthome-emails-container .red-ui-typedInput-container {
flex:1;
}
</style>
<div class="form-tips" style="margin-bottom: 1em">
For setup instructions, <a href="https://github.com/mikejac/node-red-contrib-google-smarthome/blob/master/docs/setup_instructions.md" target="_blank">look here </a>.
</div>
<div class="form-row">
<label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="googlesmarthome.label.name"></span></label>
<input type="text" id="node-config-input-name" data-i18n="[placeholder]googlesmarthome.placeholder.name">
</div>
<div class="form-row">
<label style="width:auto" for="node-config-input-enabledebug"><i class="fa fa-arrow-right"></i> <span data-i18n="googlesmarthome.label.enabledebug"></span></label>
<input type="checkbox" checked id="node-config-input-enabledebug" style="display:inline-block; width:auto; vertical-align:top;">
</div>
<div class="form-row">
<label for="node-config-input-default_lang"><i class="fa fa-tag"></i> <span data-i18n="googlesmarthome.label.default_lang"></span></label>
<select id="node-config-input-default_lang" >
<option value='da' data-i18n='googlesmarthome.label.da'></option>
<option value='nl' data-i18n='googlesmarthome.label.nl'></option>
<option value='en' data-i18n='googlesmarthome.label.en'></option>
<option value='fr' data-i18n='googlesmarthome.label.fr'></option>
<option value='de' data-i18n='googlesmarthome.label.de'></option>
<option value='hi' data-i18n='googlesmarthome.label.hi'></option>
<option value='id' data-i18n='googlesmarthome.label.id'></option>
<option value='it' data-i18n='googlesmarthome.label.it'></option>
<option value='ja' data-i18n='googlesmarthome.label.ja'></option>
<option value='ko' data-i18n='googlesmarthome.label.ko'></option>
<option value='no' data-i18n='googlesmarthome.label.no'></option>
<option value='pt-BR' data-i18n='googlesmarthome.label.pt-BR'></option>
<option value='es' data-i18n='googlesmarthome.label.es'></option>
<option value='sv' data-i18n='googlesmarthome.label.sv'></option>
<option value='th' data-i18n='googlesmarthome.label.th'></option>
</select>
</div>
<fieldset>
<legend>Local Authentication</legend>
<div class="form-row">
<label style="width:auto" for="node-config-input-usegooglelogin"><i class="fa fa-arrow-right"></i> <span data-i18n="googlesmarthome.label.usegooglelogin"></span></label>
<input type="checkbox" id="node-config-input-usegooglelogin" style="display:inline-block; width:auto; vertical-align:top;">
</div>
<div class="form-row hidden" id="usegooglelogin" style="background: #fbfbfb">
<div class="form-row">
<label for="node-config-input-loginclientid"><i class="fa fa-user"></i> <span data-i18n="googlesmarthome.label.loginclientid"></span></label>
<input type="text" id="node-config-input-loginclientid">
</div>
<div class="form-row">
<div class="form-row" style="margin-bottom:0;">
<label><i class="fa fa-list"></i> <span data-i18n="googlesmarthome.label.emails"></span></label>
</div>
<div class="form-row node-config-input-googlesmarthome-emails-container-row">
<ol id="node-config-input-googlesmarthome-emails-container"></ol>
</div>
</div>
</div>
<div class="form-row" id="useloginpwd" style="background: #fbfbfb">
<div class="form-row">
<label for="node-config-input-username"><i class="fa fa-user"></i> <span data-i18n="googlesmarthome.label.username"></span></label>
<input type="text" id="node-config-input-username">
</div>
<div class="form-row">
<label for="node-config-input-password"><i class="fa fa-lock"></i> <span data-i18n="googlesmarthome.label.password"></span></label>
<input type="password" id="node-config-input-password">
</div>
</div>
</fieldset>
<fieldset>
<legend>Actions on Google Project Settings</legend>
<div class="form-row">
<label for="node-config-input-clientid"><i class="fa fa-user"></i> <span data-i18n="googlesmarthome.label.clientid"></span></label>
<input type="text" id="node-config-input-clientid">
</div>
<div class="form-row">
<label for="node-config-input-clientsecret"><i class="fa fa-lock"></i> <span data-i18n="googlesmarthome.label.clientsecret"></span></label>
<input type="password" id="node-config-input-clientsecret">
</div>
</fieldset>
<fieldset>
<legend>Google HomeGraph Settings</legend>
<div class="form-row">
<label for="node-config-input-jwtkey"><i class="fa fa-folder"></i> <span data-i18n="googlesmarthome.label.jwtkey"></span></label>
<input type="text" id="node-config-input-jwtkey">
</div>
</fieldset>
<fieldset>
<legend>Web Server Settings</legend>
<div class="form-row">
<label for="node-config-input-port"><i class="fa fa-globe"></i> <span data-i18n="googlesmarthome.label.port"></span></label>
<input type="text" id="node-config-input-port" data-i18n="[placeholder]googlesmarthome.placeholder.port">
</div>
<div class="form-row">
<label for="node-config-input-httppath"><i class="fa fa-folder"></i> <span data-i18n="googlesmarthome.label.httppath"></span></label>
<input type="text" id="node-config-input-httppath">
</div>
<div class="form-row hidden" id="connectioninfo" style="background: #fbfbfb">
<div class="form-row">
<label style="width:auto" for="node-config-input-usehttpnoderoot"><i class="fa fa-arrow-right"></i> <span data-i18n="googlesmarthome.label.usehttpnoderoot"></span></label>
<input type="checkbox" id="node-config-input-usehttpnoderoot" style="display:inline-block; width:auto; vertical-align:top;">
</div>
<div class="form-row">
<label style="width:auto" for="node-config-input-ssloffload"><i class="fa fa-arrow-right"></i> <span data-i18n="googlesmarthome.label.ssloffload"></span></label>
<input type="checkbox" id="node-config-input-ssloffload" style="display:inline-block; width:auto; vertical-align:top;">
</div>
<div class="form-row hidden" id="ssloffloadkeys" style="background: #fbfbfb">
<div class="form-row">
<label for="node-config-input-publickey"><i class="fa fa-folder"></i> <span data-i18n="googlesmarthome.label.publickey"></span></label>
<input type="text" id="node-config-input-publickey">
</div>
<div class="form-row">
<label for="node-config-input-privatekey"><i class="fa fa-folder"></i> <span data-i18n="googlesmarthome.label.privatekey"></span></label>
<input type="text" id="node-config-input-privatekey">
</div>
</div>
</div>
</fieldset>
<fieldset>
<legend>Local Fulfillment</legend>
<div class="form-row">
<label for="node-config-input-local_scan_type"><i class="fa fa-tag"></i> <span data-i18n="googlesmarthome.label.local_scan_type"></span></label>
<select id="node-config-input-local_scan_type" >
<option value='' data-i18n='googlesmarthome.label.disabled'></option>
<option value='MDNS' data-i18n='googlesmarthome.label.MDNS'></option>
<option value='UDP' data-i18n='googlesmarthome.label.UDP'></option>
</select>
</div>
<div class="form-row hidden" id="local_fulfillment" style="background: #fbfbfb">
<div class="form-row" id="local_scan_port">
<label for="node-config-input-local_scan_port"><i class="fa fa-globe"></i> <span data-i18n="googlesmarthome.label.local_scan_port"></span></label>
<input type="text" id="node-config-input-local_scan_port" data-i18n="[placeholder]googlesmarthome.placeholder.local_scan_port">
</div>
<div class="form-row">
<label for="node-config-input-localport"><i class="fa fa-globe"></i> <span data-i18n="googlesmarthome.label.localport"></span></label>
<input type="text" id="node-config-input-localport" data-i18n="[placeholder]googlesmarthome.placeholder.localport">
</div>
</div>
</fieldset>
<fieldset>
<legend data-i18n="googlesmarthome.label.advanced_settings"></legend>
<div class="form-row">
<label for="node-config-input-accesstokenduration"><i class="fa fa-globe"></i> <span data-i18n="googlesmarthome.label.accesstokenduration"></span></label>
<input type="text" id="node-config-input-accesstokenduration" data-i18n="[placeholder]googlesmarthome.placeholder.accesstokenduration">
</div>
<div class="form-row">
<label for="node-config-input-reportinterval"><i class="fa fa-globe"></i> <span data-i18n="googlesmarthome.label.reportinterval"></span></label>
<input type="text" id="node-config-input-reportinterval" data-i18n="[placeholder]googlesmarthome.placeholder.reportinterval">
</div>
<div class="form-row">
<label for="node-config-input-request_sync_delay"><i class="fa fa-globe"></i> <span data-i18n="googlesmarthome.label.request_sync_delay"></span></label>
<input type="text" id="node-config-input-request_sync_delay" data-i18n="[placeholder]googlesmarthome.placeholder.request_sync_delay">
</div>
<div class="form-row">
<label for="node-config-input-set_state_delay"><i class="fa fa-globe"></i> <span data-i18n="googlesmarthome.label.set_state_delay"></span></label>
<input type="text" id="node-config-input-set_state_delay" data-i18n="[placeholder]googlesmarthome.placeholder.set_state_delay">
</div>
</fieldset>
</script>
<script type="text/javascript">
(function () {
function isNonNegativeInteger(v) {
const n = parseInt(v);
const f = parseFloat(v);
return !isNaN(v) && Number.isInteger(f) && n >= 0;
}
function isOptionalNonNegativeInteger(v) {
return v.trim().length == 0 || isNonNegativeInteger(v);
}
RED.nodes.registerType('googlesmarthome-client', {
category: 'config',
defaults: {
name: {
value: ""
},
enabledebug: {
value: false
},
default_lang: {
value: "en", required: true,
},
usegooglelogin: {
value: false
},
/*
loginclientid: {
value:"", required:false,
validate: function(v) {
let usegoogleloginField = $("#node-config-input-usegooglelogin");
let usegooglelogin = usegoogleloginField.length > 0 ? usegoogleloginField.prop('checked') : this.usegooglelogin;
return usegooglelogin ? v.trim().length > 0 : true;
}
},
emails: {
value:[], required:false,
validate: function(v) {
let usegoogleloginField = $("#node-config-input-usegooglelogin");
let usegooglelogin = usegoogleloginField.length > 0 ? usegoogleloginField.prop('checked') : this.usegooglelogin;
return usegooglelogin ? v.trim().length > 0 : true;
}
},
username: {
value:"", required:false,
validate: function(v) {
let usegoogleloginField = $("#node-config-input-usegooglelogin");
let usegooglelogin = usegoogleloginField.length > 0 ? usegoogleloginField.prop('checked') : this.usegooglelogin;
return !usegooglelogin ? v.trim().length > 0 : true;
}
},
password: {
value:"", required:false,
validate: function(v) {
let usegoogleloginField = $("#node-config-input-usegooglelogin");
let usegooglelogin = usegoogleloginField.length > 0 ? usegoogleloginField.prop('checked') : this.usegooglelogin;
return !usegooglelogin ? v.trim().length > 0 : true;
}
},
*/
usehttpnoderoot: {
value: false
},
port: {
value: 3001, required: false, validate: RED.validators.number(true)
},
httppath: {
value: "", required: false, validate: RED.validators.regex(/^[a-zA-Z0-9_\-\/]*$/),
},
ssloffload: {
value: false
},
/*
publickey: {
value:"", required:false,
validate: function(v) {
let ssloffloadField = $("#node-config-input-ssloffload");
let ssloffload = ssloffloadField.length > 0 ? ssloffloadField.prop('checked') : this.ssloffload;
let portField = $("#node-config-input-port");
let port = portField.length > 0 ? portField.val() : this.port;
port = isNaN(port) ? 0 : +port;
return ssloffload || port <= 0 ? true : v.trim().length > 0;
}
},
privatekey: {
value:"", required:false,
validate: function(v) {
let ssloffloadField = $("#node-config-input-ssloffload");
let ssloffload = ssloffloadField.length > 0 ? ssloffloadField.prop('checked') : this.ssloffload;
let portField = $("#node-config-input-port");
let port = portField.length > 0 ? portField.val() : this.port;
port = isNaN(port) ? 0 : +port;
return ssloffload || port <= 0 ? true : v.trim().length > 0;
}
},
*/
local_scan_type: {
value: "", required: false
},
local_scan_port: {
required: false, validate: RED.validators.number(true)
},
localport: {
required: false, validate: RED.validators.number(true)
},
/*
jwtkey: {
value:"", required:true
},
clientid: {
value:"", required:true
},
clientsecret: {
value:"", required:true
}
*/
accesstokenduration: {
value: 60, required: true, validate: RED.validators.number(true)
},
reportinterval: {
value: 60, required: true, validate: RED.validators.number(true)
},
request_sync_delay: {
value: '', required: false, validate: isOptionalNonNegativeInteger
},
set_state_delay: {
value: '', required: false, validate: isOptionalNonNegativeInteger
}
},
credentials: {
loginclientid: { type: "text" },
emails: { type: "text" },
username: { type: "text" },
password: { type: "password" },
publickey: { type: "text" },
privatekey: { type: "text" },
jwtkey: { type: "text" },
clientid: { type: "text" },
clientsecret: { type: "password" },
},
color: "#3FADB5",
icon: "google-smarthome.png",
label: function () {
return this.name || "googlesmarthome-client";
},
labelStyle: function () {
return this.name ? "node_label_italic" : "";
},
oneditprepare: function () {
// emails
$('#node-config-input-googlesmarthome-emails-container').css('min-height', '150px').css('min-width', '250px').editableList({
addItem: function (container, i, opt) {
let emails = opt;
if (!Object.prototype.hasOwnProperty.call(emails, 'name')) {
emails = { name: "" };
}
container.css({
overflow: 'hidden',
whiteSpace: 'nowrap'
});
let fragment = document.createDocumentFragment();
let row1 = $('<div/>', { style: "display:flex;" }).appendTo(fragment);
let propertyName = $('<input/>', { class: "node-config-input-googlesmarthome-emails-property-name", type: "text", style: "width: 100%" })
.appendTo(row1);
propertyName.val(emails.name);
container[0].appendChild(fragment);
},
removable: true,
sortable: true
});
let emails = this.credentials.emails;
if (typeof emails === 'string') {
emails = emails.split(";");
}
if (!emails) {
emails = [];
}
for (let i = 0; i < emails.length; i++) {
let email = emails[i];
$("#node-config-input-googlesmarthome-emails-container").editableList('addItem', { name: email });
}
// Use Google login on / off
let useGoogleLogin = function () {
let usegooglelogin = $("#node-config-input-usegooglelogin").prop('checked');
if (usegooglelogin == false) {
$("#usegooglelogin").hide();
$("#useloginpwd").show();
} else {
$("#usegooglelogin").show();
$("#useloginpwd").hide();
}
};
useGoogleLogin();
$("#node-config-input-usegooglelogin").change(useGoogleLogin);
// Use external SSL offload on / off
let sslOffLoadKeys = function () {
let ssloffload = $("#node-config-input-ssloffload").prop('checked');
if (ssloffload == false) {
$("#ssloffloadkeys").show();
} else {
$("#ssloffloadkeys").hide();
}
};
sslOffLoadKeys();
$("#node-config-input-ssloffload").change(sslOffLoadKeys);
// Use same NODE-Red port
let useNODERedPort = function () {
let input_port = $('#node-config-input-port').val();
input_port = isNaN(input_port) ? 0 : +input_port;
if ((input_port <= 0)) { // || (RED.settings.uiPort === node)) {
$('#connectioninfo').hide();
} else {
$('#connectioninfo').show();
}
};
useNODERedPort();
$('#node-config-input-port').change(useNODERedPort);
// Local FulFillment
let localFulFillment = function () {
let scan_type = $('#node-config-input-local_scan_type').val();
if (scan_type === 'MDNS') {
$('#local_fulfillment').show();
$('#local_scan_port').hide();
} else if (scan_type === 'UDP') {
$('#local_fulfillment').show();
$('#local_scan_port').show();
} else {
$('#local_fulfillment').hide();
}
};
localFulFillment();
$('#node-config-input-local_scan_type').change(localFulFillment);
},
oneditsave: function () {
let me = this;
// emails
let emails = $("#node-config-input-googlesmarthome-emails-container").editableList('items');
me.credentials.emails = [];
emails.each(function (i) {
let email = $(this);
let name = email.find(".node-config-input-googlesmarthome-emails-property-name").val();
me.credentials.emails.push(name);
});
},
oneditresize: function (size) {
// emails
let rows = $("#dialog-form>div:not(node-config-input-googlesmarthome-emails-container-row)");
let height = size.height;
for (let i = 0; i < rows.length; i++) {
height -= $(rows[i]).outerHeight(true);
}
let editorRow = $("#dialog-form>div.node-config-input-googlesmarthome-emails-container-row");
height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")));
height += 16;
$("#node-config-input-googlesmarthome-emails-container").editableList('height', height);
}
});
})();
</script>