@gregoriusrippenstein/node-red-contrib-nodedev
Version:
Support the development of Node-RED nodes within Node-RED.
484 lines (394 loc) • 19.4 kB
HTML
<script type="text/javascript">
(function() {
var globalYourConfigNode = null;
function doGenerateOTP(cfgnode, cb) {
ensureYourConfigNodeExists();
$.ajax({
url: "NodeDevOtpGenerator",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({
cfgnode: cfgnode
}),
success: function (resp) {
$('#node-input-nodedev-sidebar-otp').val(resp.data.otp)
cb(resp)
},
error: function (jqXHR, textStatus, errorThrown) {
RED.notify("Generating failed: " + textStatus, {
type: "error",
id: "NodeFactorySidebar",
timeout: 2000
});
console.log(textStatus,errorThrown )
}
});
}
function doSubmission(pkgname,pkgversion) {
ensureYourConfigNodeExists();
$.ajax({
url: "NodeFactorySidebarCfg",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({
pkgname: $("#node-input-nodefactorysidebar-package-name").val().trim(),
pkgversion: $("#node-input-nodefactorysidebar-package-version").val().trim(),
pkgregistry: $("#node-input-nodefactorysidebar-package-registry").val().trim()
}),
success: function (resp) {
RED.notify("Submission triggered", {
type: "warning",
id: "NodeFactorySidebar",
timeout: 2000
});
},
error: function (jqXHR, textStatus, errorThrown) {
RED.notify("Submission failed: " + textStatus, {
type: "error",
id: "NodeFactorySidebar",
timeout: 2000
});
console.log(textStatus,errorThrown )
}
});
}
function ensureYourConfigNodeExists() {
// This function makes sure there is 1 instance of your config node is available, and that the globalYourConfigNode variable refers to it.
// Explained in the next step of this tutorial... --> https://discourse.nodered.org/t/tutorial-create-a-sidebar-plugin-and-persist-the-data-in-a-config-node/82020
// If we had found it previously, check if it has been deleted by the user behind our back
if (globalYourConfigNode !== null) {
var configNode = RED.nodes.node(globalYourConfigNode.id);
if (configNode === null) { globalYourConfigNode = null; }
}
// If not found previously, let's go find it
if (globalYourConfigNode === null) {
var configNodes = [];
RED.nodes.eachConfig(function(configNode) {
if (configNode.type === 'NodeFactorySidebarCfg') {
configNodes.push(configNode);
}
});
// Make sure we only have 1 config node
while (configNodes.length > 1) {
var configNode = configNodes.pop();
RED.nodes.remove(configNode.id);
RED.nodes.dirty(true);
}
// When we found a config node, let's use that one
if (configNodes.length === 1) { globalYourConfigNode = configNodes[0]; }
}
// When it doesn't exist yet, create it if required
if (globalYourConfigNode === null) {
// Remark: since this config node is dynamically created (and only used in this sidebar which isn't another node), the config
// node is in fact "unused". But since we don't want it to appear "unused" in the "config nodes" panel, we need to set hasUsers
// to false (see https://github.com/node-red/node-red/blob/master/CHANGELOG.md#0161-maintenance-release).
// The hasUsers needs also to be specified in the RED.nodes.registerType statement!
let typ = RED.nodes.getType("NodeFactorySidebarCfg");
globalYourConfigNode = {
id: RED.nodes.id(), // on the server side, this is called RED.util.generateId()
_def: typ,
type: "NodeFactorySidebarCfg",
hasUsers: false,
users: [],
name: "NodeFactorySidebar",
label: function() { return this.name || "NodeFactorySidebar"},
/* values and data defined by this config node */
pkgname: typ.defaults.pkgname.value,
pkgversion: typ.defaults.pkgversion.value,
otptype: typ.defaults.otptype.value,
algorithm: typ.defaults.algorithm.value,
secret: typ.defaults.secret.value,
secretType: typ.defaults.secretType.value,
issuer: typ.defaults.issuer.value,
otplabel: typ.defaults.otplabel.value,
digits: typ.defaults.digits.value,
counter: typ.defaults.counter.value,
period: typ.defaults.period.value
}
// Add the new config node to the collection of Node-RED nodes
RED.nodes.add(globalYourConfigNode);
// Make sure the "Deploy" button becomes active
RED.nodes.dirty(true);
}
}
// Add your plugin as a new tabsheet in the right sidebar AFTER the flow editor is completely started
var initialiseConfigNodeOnce = () => {
RED.events.off('runtime-state', initialiseConfigNodeOnce);
// The html content of the sidebar has been specified below as a data-template, from where it can be loaded:
var content = $($('script[type="text/x-red"][data-template-name="NodeFactorySidebar"]').i18n().html());
// Add a "Your sidebar" tabsheet to the right sidebar panel, in which this sidebar panel can be displayed
// --> more details: https://nodered.org/docs/api/ui/sidebar/
RED.sidebar.addTab({
id: "NodeFactorySidebar",
label: "Node Dev", // short name for the tab
name: "Node Dev", // long name for the menu
content: content,
closeable: true,
// disableOnEdit: true,
enableOnEdit: true,
iconClass: "fa fa-industry" // your fontawesome icon
});
var tabs = RED.tabs.create({
id: 'func-nodedev-pkgimporter-tabs',
onchange: function(tab) {
$('#func-nodedev-pkgimporter-tabs-content').children().hide();
$('#' + tab.id).show();
}
});
tabs.addTab({
id: 'func-nodedev-pkgimporter-tab-pkgimport',
iconClass: 'fa fa-industry',
label: 'PkgImporter'
});
tabs.addTab({
id: 'func-nodedev-pkgimporter-tab-otpgenerator',
iconClass: 'fa fa-user-secret',
label: 'OTP Generator'
});
tabs.addTab({
id: 'func-nodedev-pkgimporter-tab-options',
iconClass: 'fa fa-cog',
label: 'Options'
});
ensureYourConfigNodeExists();
/*
************ Package importer handlers
*/
var doSomething = (e) => {
if (e) { e.preventDefault(); }
doSubmission()
}
$('#node-input-nodefactorysidebar-generate-btn').on('click', doSomething );
$("#node-input-nodefactorysidebar-package-version").on("change", function() {
ensureYourConfigNodeExists();
let data = $(this).val();
if (globalYourConfigNode.pkgversion != data) {
globalYourConfigNode.pkgversion = data.trim();
RED.nodes.dirty(true);
}
})
$("#node-input-nodefactorysidebar-package-version").val(globalYourConfigNode.pkgversion);
$("#node-input-nodefactorysidebar-package-name").on("change", function() {
ensureYourConfigNodeExists();
let data = $(this).val();
if (globalYourConfigNode.pkgname != data) {
globalYourConfigNode.pkgname = data.trim();
RED.nodes.dirty(true);
}
})
$("#node-input-nodefactorysidebar-package-name").val(globalYourConfigNode.pkgname);
$("#node-input-nodefactorysidebar-package-registry").on("change", function() {
ensureYourConfigNodeExists();
let data = $(this).val();
if (globalYourConfigNode.pkgregistry != data) {
globalYourConfigNode.pkgregistry = data.trim();
RED.nodes.dirty(true);
}
})
$("#node-input-nodefactorysidebar-package-registry").val(globalYourConfigNode.pkgregistry);
/*
************* OTP generator handlers
*/
$("#node-input-nodedev-sidebar-otp-secret").typedInput({
types:["env", "msg", "global", "cred"],
typeField: "#node-input-nodedev-sidebar-otp-secretType"
});
$("#node-input-nodedev-sidebar-otp-secret").typedInput( 'value', globalYourConfigNode.secret || globalYourConfigNode._def.defaults.secret.value );
$("#node-input-nodedev-sidebar-otp-secret").typedInput( 'type', globalYourConfigNode.secretType || globalYourConfigNode._def.defaults.secretType.value);
$("#node-input-nodedev-sidebar-otp-secret").on('change', () => {
ensureYourConfigNodeExists();
let val = $("#node-input-nodedev-sidebar-otp-secret").typedInput('value');
let typ = $("#node-input-nodedev-sidebar-otp-secret").typedInput('type');
if ( val != globalYourConfigNode.secret ) {
globalYourConfigNode.secret = val;
RED.nodes.dirty(true);
}
if ( typ != globalYourConfigNode.secretType ) {
globalYourConfigNode.secretType = typ;
RED.nodes.dirty(true);
}
})
$('#node-input-nodedev-sidebar-otp-otptype').val(globalYourConfigNode.otptype || globalYourConfigNode._def.defaults.otptype.value)
$('#node-input-nodedev-sidebar-otp-algorithm').val(globalYourConfigNode.algorithm || globalYourConfigNode._def.defaults.algorithm.value)
$('#node-input-nodedev-sidebar-otp-issuer').val(globalYourConfigNode.issuer || globalYourConfigNode._def.defaults.issuer.value)
$('#node-input-nodedev-sidebar-otp-label').val(globalYourConfigNode.otplabel || globalYourConfigNode._def.defaults.otplabel.value)
$('#node-input-nodedev-sidebar-otp-digits').val(globalYourConfigNode.digits || globalYourConfigNode._def.defaults.digits.value)
$('#node-input-nodedev-sidebar-otp-period').val(globalYourConfigNode.period || globalYourConfigNode._def.defaults.period.value)
$('#node-input-nodedev-sidebar-otp-counter').val(globalYourConfigNode.counter || globalYourConfigNode._def.defaults.counter.value)
$('#node-input-nodedev-sidebar-otp-otptype').on('change', () => {
ensureYourConfigNodeExists();
let val = $('#node-input-nodedev-sidebar-otp-otptype').val()
if ( val != globalYourConfigNode.otptype) {
globalYourConfigNode.otptype = val
RED.nodes.dirty(true);
}
})
$('#node-input-nodedev-sidebar-otp-algorithm').on('change', () => {
ensureYourConfigNodeExists();
let val = $('#node-input-nodedev-sidebar-otp-algorithm').val()
if ( val != globalYourConfigNode.algorithm) {
globalYourConfigNode.algorithm = val
RED.nodes.dirty(true);
}
})
$('#node-input-nodedev-sidebar-otp-issuer').on('change', () => {
ensureYourConfigNodeExists();
let val = $('#node-input-nodedev-sidebar-otp-issuer').val()
if ( val != globalYourConfigNode.issuer) {
globalYourConfigNode.issuer = val
RED.nodes.dirty(true);
}
})
$('#node-input-nodedev-sidebar-otp-label').on('change', () => {
ensureYourConfigNodeExists();
let val = $('#node-input-nodedev-sidebar-otp-label').val()
if ( val != globalYourConfigNode.otplabel) {
globalYourConfigNode.otplabel = val
RED.nodes.dirty(true);
}
})
$('#node-input-nodedev-sidebar-otp-digits').on('change', () => {
ensureYourConfigNodeExists();
let val = $('#node-input-nodedev-sidebar-otp-digits').val()
if ( val != globalYourConfigNode.digits) {
globalYourConfigNode.digits = val
RED.nodes.dirty(true);
}
})
$('#node-input-nodedev-sidebar-otp-period').on('change', () => {
ensureYourConfigNodeExists();
let val = $('#node-input-nodedev-sidebar-otp-period').val()
if ( val != globalYourConfigNode.period) {
globalYourConfigNode.period = val
RED.nodes.dirty(true);
}
})
$('#node-input-nodedev-sidebar-otp-counter').on('change', () => {
ensureYourConfigNodeExists();
let val = $('#node-input-nodedev-sidebar-otp-counter').val()
if ( val != globalYourConfigNode.counter) {
globalYourConfigNode.counter = val
RED.nodes.dirty(true);
}
})
$('#nodedev-sidebar-otp-generate-btn').on('click', e => {
if ( e) { e.preventDefault()}
ensureYourConfigNodeExists();
doGenerateOTP(globalYourConfigNode, (resp) => {
$('#node-input-nodedev-sidebar-otp').val(resp.data.otp)
})
})
};
RED.events.on('runtime-state', initialiseConfigNodeOnce);
})();
</script>
<!-- The html for the right sidebar plugin screen -->
<script type="text/x-red" data-template-name="NodeFactorySidebar">
<div class="form-row func-nodedev-pkgimporter-tabs-row">
<ul style="min-width: 600px; margin-bottom: 20px;" id="func-nodedev-pkgimporter-tabs"></ul>
</div>
<div id="func-nodedev-pkgimporter-tabs-content" style="min-height: calc(100% - 95px);">
<div id="func-nodedev-pkgimporter-tab-pkgimport" style="display:none">
<div class="form-row" style="margin-left: 10px; margin-top: 30px;">
Import <a href="https://npmjs.com" target=_blank>npmJs.com</a> packages as a flow. packages
are imported as a collection of PkgFile nodes. This can help to learn from other
peoples experiences and code.
</div>
<div class="form-row" style="margin-left: 10px;">
Importing GitHub repos is also supported. For this, the package name should be either <code>https://github.com/<user>/<repo></code> or <code>git@github.com:<user>/<repo></code>, the version is assumed to be branch or tag name and the Registry option is ignored.
</div>
<div class="form-row" style="margin-left: 10px; margin-top: 30px;">
Enter package details here:
</div>
<div class="form-row" style="margin-left: 10px;">
<label for="node-input-nodefactorysidebar-package-name">
<i class="fa fa-tag"></i>
Name
</label>
<input type="text" id="node-input-nodefactorysidebar-package-name"
placeholder="node-red-dashboard">
</div>
<div class="form-row" style="margin-left: 10px;">
<label for="node-input-nodefactorysidebar-package-version">
<i class="fa fa-tag"></i>
Version
</label>
<input type="text" id="node-input-nodefactorysidebar-package-version"
placeholder="3.5.0">
</div>
<div class="form-row" style="margin-left: 10px;">
<label for="node-input-nodefactorysidebar-package-registry">
<i class="fa fa-tag"></i>
Registry
</label>
<input type="text" id="node-input-nodefactorysidebar-package-registry"
placeholder="https://registry.npmjs.org">
</div>
<div class="form-row">
<div style="position: relative; height: 100%; margin: 15px;">
<button id="node-input-nodefactorysidebar-generate-btn"
class="red-ui-button"
style="width: 100%; margin-top: 30px">Import Package</button>
</div>
</div>
</div>
<div id="func-nodedev-pkgimporter-tab-otpgenerator" style="display:none">
<div class="form-row" style="margin-left: 10px;">
<label for="node-input-nodedev-sidebar-otp-otptype"><i class="fa fa-tag"></i> Type</label>
<select id="node-input-nodedev-sidebar-otp-otptype">
<option value="totp">Time-Based (TOTP)</option>
<option value="hotp">HMAC-based (HOTP)</option>
</select>
</div>
<div class="form-row" style="margin-left: 10px;">
<label for="node-input-nodedev-sidebar-otp-secret">
<i class="fa fa-key"></i>
Secret
</label>
<input type="text" id="node-input-nodedev-sidebar-otp-secret">
<input type="hidden" id="node-input-nodedev-sidebar-otp-secretType">
</div>
<div class="form-row" style="margin-left: 10px;">
<label for="node-input-nodedev-sidebar-otp-algorithm"><i class="fa fa-tag"></i> Algorithm</label>
<select id="node-input-nodedev-sidebar-otp-algorithm">
<option value="SHA1">SHA1</option>
<option value="SHA256">SHA256</option>
<option value="SHA512">SHA512</option>
</select>
</div>
<div class="form-row" style="margin-left: 10px;">
<label for="node-input-nodedev-sidebar-otp-issuer"><i class="fa fa-tag"></i> Issuer</label>
<input type="text" id="node-input-nodedev-sidebar-otp-issuer" placeholder="Issuer">
</div>
<div class="form-row" style="margin-left: 10px;">
<label for="node-input-nodedev-sidebar-otp-label"><i class="fa fa-tag"></i> Label</label>
<input type="text" id="node-input-nodedev-sidebar-otp-label" placeholder="Label">
</div>
<div class="form-row" style="margin-left: 10px;">
<label for="node-input-nodedev-sidebar-otp-digits"><i class="fa fa-tag"></i> Digits</label>
<input type="text" id="node-input-nodedev-sidebar-otp-digits" placeholder="Digits">
</div>
<div class="form-row" style="margin-left: 10px;">
<label for="node-input-nodedev-sidebar-otp-period"><i class="fa fa-tag"></i> Period</label>
<input type="text" id="node-input-nodedev-sidebar-otp-period" placeholder="Period">
</div>
<div class="form-row" style="margin-left: 10px;">
<label for="node-input-nodedev-sidebar-otp-counter"><i class="fa fa-tag"></i> Counter</label>
<input type="text" id="node-input-nodedev-sidebar-otp-counter" placeholder="Counter">
</div>
<div class="form-row" style="margin-left: 10px;">
<button id="nodedev-sidebar-otp-generate-btn" class="button">Generate</button>
<input type="text" id="node-input-nodedev-sidebar-otp" placeholder="OTP...">
</div>
</div>
<div id="func-nodedev-pkgimporter-tab-options" style="display:none">
<div class="form-row" style="margin-left: 10px;">
Drag & Drop
</div>
<div class="form-row" style="margin-left: 10px;">
<input style="width: 10%;" type="checkbox" id="nodedev-drag-drop-generate-pkgfile-nodes"/>
<label for="nodedev-drag-drop-generate-pkgfile-nodes" style="width: 40%;"><i class="fa fa-code" ></i> Create PkgFile nodes on drop?</label>
</div>
</div>
</div>
</script>