mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Compare commits
158 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
129ca0e39f | ||
|
|
906703db5f | ||
|
|
0cd4a2b4ec | ||
|
|
aef8aaa0bd | ||
|
|
428fbb8622 | ||
|
|
b9f03e7d80 | ||
|
|
db686388b9 | ||
|
|
626cba4002 | ||
|
|
37d4a6b9e2 | ||
|
|
12c4561aba | ||
|
|
fed49e3718 | ||
|
|
3af37d3984 | ||
|
|
0f49a11228 | ||
|
|
27d3e165b0 | ||
|
|
e941c22f6c | ||
|
|
7281e4deb6 | ||
|
|
f2191e94b3 | ||
|
|
349ebfe4db | ||
|
|
708365c4ac | ||
|
|
0e9ea0aff1 | ||
|
|
63ba05a193 | ||
|
|
4b702815cf | ||
|
|
55e66ebcac | ||
|
|
dcd8b3699c | ||
|
|
0e2d13172a | ||
|
|
2e2556fdad | ||
|
|
859a7538e1 | ||
|
|
0d1543ee8a | ||
|
|
d3a98dd355 | ||
|
|
ad10125303 | ||
|
|
b89e866d39 | ||
|
|
afbaf1cfe0 | ||
|
|
b3be8b30e7 | ||
|
|
8bfab8f73d | ||
|
|
c6ad2c9ad2 | ||
|
|
3b44d9972e | ||
|
|
af736c98f2 | ||
|
|
f1377fa217 | ||
|
|
2ba146b9ff | ||
|
|
2361607aa3 | ||
|
|
86ffc80098 | ||
|
|
7f6915eb59 | ||
|
|
d69bcad028 | ||
|
|
4cb45e2712 | ||
|
|
b7a0ad703a | ||
|
|
7610b9a975 | ||
|
|
7d95f621df | ||
|
|
bba210e112 | ||
|
|
3a97e20bde | ||
|
|
4fe7ea00b0 | ||
|
|
3ec8ecd4de | ||
|
|
401e65e852 | ||
|
|
e7c5b691a0 | ||
|
|
9f3ea8da67 | ||
|
|
4d84d624b1 | ||
|
|
633a6a0ee6 | ||
|
|
c7bcd3f438 | ||
|
|
d3a29a6f16 | ||
|
|
827711ca89 | ||
|
|
76e98f74fa | ||
|
|
fb09f4b22d | ||
|
|
bb06585748 | ||
|
|
c76ba1dcc7 | ||
|
|
a115301b04 | ||
|
|
72917117a9 | ||
|
|
6567739236 | ||
|
|
4aa6b47c0e | ||
|
|
03558b012c | ||
|
|
3288efdad6 | ||
|
|
3902a343f3 | ||
|
|
882b7d0391 | ||
|
|
81f082825d | ||
|
|
392fd6fed3 | ||
|
|
51afed4041 | ||
|
|
17e3b71d9c | ||
|
|
6e75089f3a | ||
|
|
6dc640b129 | ||
|
|
27cbaac343 | ||
|
|
fa4006619e | ||
|
|
cb8fe8462a | ||
|
|
abd51a5511 | ||
|
|
a0cc1e6b0c | ||
|
|
50399c6bfa | ||
|
|
de48c1be44 | ||
|
|
0786ec4b66 | ||
|
|
db319e0ebc | ||
|
|
4fc568856a | ||
|
|
4c6771669b | ||
|
|
9bca2a91c9 | ||
|
|
66eaaf5a48 | ||
|
|
9837f0e2e1 | ||
|
|
6b8ffb4c68 | ||
|
|
f35dd34da9 | ||
|
|
ed19e4fa08 | ||
|
|
661e1a4f90 | ||
|
|
5826de76ca | ||
|
|
05888740e5 | ||
|
|
41f3b0c333 | ||
|
|
70f3e72a20 | ||
|
|
e873afd40b | ||
|
|
2777c2937a | ||
|
|
798903e4cc | ||
|
|
58622ba18f | ||
|
|
c368dcd5b7 | ||
|
|
0b4c652ce7 | ||
|
|
dbaacc411a | ||
|
|
1850185d1e | ||
|
|
2e9d445d36 | ||
|
|
aed89d82fb | ||
|
|
231adac6d8 | ||
|
|
587c4e5915 | ||
|
|
55f1cbf18f | ||
|
|
38168a545b | ||
|
|
43c6df49d7 | ||
|
|
f1c59faf72 | ||
|
|
5f7019325c | ||
|
|
fe4dae8518 | ||
|
|
1f848b205b | ||
|
|
742c470d81 | ||
|
|
5ead3342cc | ||
|
|
b95dc2ecce | ||
|
|
4d0950215f | ||
|
|
da0ce9fe0d | ||
|
|
ca62e720b5 | ||
|
|
c4b1795396 | ||
|
|
fd2e47ed73 | ||
|
|
d5f2255a68 | ||
|
|
05b58e9263 | ||
|
|
4a91c27e4b | ||
|
|
3a03d46d8d | ||
|
|
f03aff7006 | ||
|
|
043b8a3105 | ||
|
|
1dd9984521 | ||
|
|
d2be7f8c8f | ||
|
|
88dc202db2 | ||
|
|
083d54b008 | ||
|
|
87d77efa57 | ||
|
|
35c4a41d7b | ||
|
|
1ca3ca07d5 | ||
|
|
d673846e3d | ||
|
|
f62b7afede | ||
|
|
e65770a53a | ||
|
|
a92a741932 | ||
|
|
45f67191ba | ||
|
|
93f5da325b | ||
|
|
8fb955e182 | ||
|
|
9f5e6a4b37 | ||
|
|
f43738446e | ||
|
|
923a46d304 | ||
|
|
b9b5eaccae | ||
|
|
cda11491c2 | ||
|
|
98c539f662 | ||
|
|
9fb958b302 | ||
|
|
8e25e76439 | ||
|
|
62694da7e6 | ||
|
|
86064651af | ||
|
|
65daaeb617 | ||
|
|
08b39f50b3 |
18
Gruntfile.js
18
Gruntfile.js
@@ -124,14 +124,15 @@ module.exports = function(grunt) {
|
||||
"editor/js/ui/library.js",
|
||||
"editor/js/ui/notifications.js",
|
||||
"editor/js/ui/subflow.js",
|
||||
"editor/js/ui/touch/radialMenu.js"
|
||||
"editor/js/ui/touch/radialMenu.js",
|
||||
"editor/js/ui/typedInput.js"
|
||||
],
|
||||
dest: "public/red/red.js"
|
||||
},
|
||||
vendor: {
|
||||
files: {
|
||||
"public/vendor/vendor.js": [
|
||||
"editor/vendor/jquery/js/jquery-1.11.1.min.js",
|
||||
"editor/vendor/jquery/js/jquery-1.11.3.min.js",
|
||||
"editor/vendor/bootstrap/js/bootstrap.min.js",
|
||||
"editor/vendor/jquery/js/jquery-ui-1.10.3.custom.min.js",
|
||||
"editor/vendor/jquery/js/jquery.ui.touch-punch.min.js",
|
||||
@@ -174,8 +175,8 @@ module.exports = function(grunt) {
|
||||
messages: {
|
||||
src: [
|
||||
'nodes/core/locales/en-US/messages.json',
|
||||
'locales/en-US/editor.json',
|
||||
'locales/en-US/runtime.json'
|
||||
'red/api/locales/en-US/editor.json',
|
||||
'red/runtime/locales/en-US/runtime.json'
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -223,8 +224,8 @@ module.exports = function(grunt) {
|
||||
json: {
|
||||
files: [
|
||||
'nodes/core/locales/en-US/messages.json',
|
||||
'locales/en-US/editor.json',
|
||||
'locales/en-US/runtime.json'
|
||||
'red/api/locales/en-US/editor.json',
|
||||
'red/runtime/locales/en-US/runtime.json'
|
||||
],
|
||||
tasks: ['jsonlint:messages']
|
||||
}
|
||||
@@ -238,7 +239,7 @@ module.exports = function(grunt) {
|
||||
args: nodemonArgs,
|
||||
ext: 'js,html,json',
|
||||
watch: [
|
||||
'red','nodes','locales'
|
||||
'red','nodes'
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -302,8 +303,7 @@ module.exports = function(grunt) {
|
||||
'red/**',
|
||||
'public/**',
|
||||
'editor/templates/**',
|
||||
'bin/**',
|
||||
'locales/**'
|
||||
'bin/**'
|
||||
],
|
||||
dest: path.resolve('<%= paths.dist %>/node-red-<%= pkg.version %>')
|
||||
}]
|
||||
|
||||
@@ -66,4 +66,4 @@ For more open-source projects from IBM, head over [here](http://ibm.github.io).
|
||||
|
||||
## Copyright and license
|
||||
|
||||
Copyright 2013, 2015 IBM Corp. under [the Apache 2.0 license](LICENSE).
|
||||
Copyright 2013, 2016 IBM Corp. under [the Apache 2.0 license](LICENSE).
|
||||
|
||||
BIN
editor/images/typedInput/09.png
Normal file
BIN
editor/images/typedInput/09.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 638 B |
BIN
editor/images/typedInput/az.png
Normal file
BIN
editor/images/typedInput/az.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 546 B |
BIN
editor/images/typedInput/bool.png
Normal file
BIN
editor/images/typedInput/bool.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 646 B |
BIN
editor/images/typedInput/json.png
Normal file
BIN
editor/images/typedInput/json.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 588 B |
BIN
editor/images/typedInput/re.png
Normal file
BIN
editor/images/typedInput/re.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 502 B |
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,6 +23,9 @@ RED.history = (function() {
|
||||
undo_history[i].dirty = true;
|
||||
}
|
||||
},
|
||||
list: function() {
|
||||
return undo_history
|
||||
},
|
||||
depth: function() {
|
||||
return undo_history.length;
|
||||
},
|
||||
@@ -80,6 +83,12 @@ RED.history = (function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ev.removedLinks) {
|
||||
for (i=0;i<ev.removedLinks.length;i++) {
|
||||
RED.nodes.addLink(ev.removedLinks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (ev.t == "delete") {
|
||||
if (ev.workspaces) {
|
||||
for (i=0;i<ev.workspaces.length;i++) {
|
||||
@@ -168,6 +177,17 @@ RED.history = (function() {
|
||||
n.n.y = n.oy;
|
||||
n.n.dirty = true;
|
||||
}
|
||||
// A move could have caused a link splice
|
||||
if (ev.links) {
|
||||
for (i=0;i<ev.links.length;i++) {
|
||||
RED.nodes.removeLink(ev.links[i]);
|
||||
}
|
||||
}
|
||||
if (ev.removedLinks) {
|
||||
for (i=0;i<ev.removedLinks.length;i++) {
|
||||
RED.nodes.addLink(ev.removedLinks[i]);
|
||||
}
|
||||
}
|
||||
} else if (ev.t == "edit") {
|
||||
for (i in ev.changes) {
|
||||
if (ev.changes.hasOwnProperty(i)) {
|
||||
@@ -265,7 +285,11 @@ RED.history = (function() {
|
||||
RED.view.redraw(true);
|
||||
RED.palette.refresh();
|
||||
RED.workspaces.refresh();
|
||||
RED.sidebar.config.refresh();
|
||||
}
|
||||
},
|
||||
peek: function() {
|
||||
return undo_history[undo_history.length-1];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -103,8 +103,10 @@ var RED = (function() {
|
||||
var id = m.id;
|
||||
RED.nodes.addNodeSet(m);
|
||||
addedTypes = addedTypes.concat(m.types);
|
||||
$.get('nodes/'+id, function(data) {
|
||||
$("body").append(data);
|
||||
RED.i18n.loadCatalog(id, function() {
|
||||
$.get('nodes/'+id, function(data) {
|
||||
$("body").append(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
if (addedTypes.length) {
|
||||
@@ -142,6 +144,8 @@ var RED = (function() {
|
||||
RED.notify(RED._("palette.event.nodeDisabled", {count:msg.types.length})+typeList,"success");
|
||||
}
|
||||
}
|
||||
// Refresh flow library to ensure any examples are updated
|
||||
RED.library.loadFlowLibrary();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -156,11 +160,13 @@ var RED = (function() {
|
||||
function loadEditor() {
|
||||
RED.menu.init({id:"btn-sidemenu",
|
||||
options: [
|
||||
{id:"menu-item-sidebar-menu",label:RED._("menu.label.sidebar.sidebar"),options:[
|
||||
{id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true},
|
||||
null
|
||||
{id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[
|
||||
{id:"menu-item-view-show-grid",label:RED._("menu.label.view.showGrid"),toggle:true,onselect:RED.view.toggleShowGrid},
|
||||
{id:"menu-item-view-snap-grid",label:RED._("menu.label.view.snapGrid"),toggle:true,onselect:RED.view.toggleSnapGrid},
|
||||
{id:"menu-item-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:toggleStatus, selected: true},
|
||||
null,
|
||||
{id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true}
|
||||
]},
|
||||
{id:"menu-item-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:toggleStatus, selected: true},
|
||||
null,
|
||||
{id:"menu-item-import",label:RED._("menu.label.import"),options:[
|
||||
{id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:RED.clipboard.import},
|
||||
@@ -171,23 +177,24 @@ var RED = (function() {
|
||||
{id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:RED.library.export}
|
||||
]},
|
||||
null,
|
||||
{id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [
|
||||
{id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:RED.subflow.createSubflow},
|
||||
{id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:RED.subflow.convertToSubflow},
|
||||
]},
|
||||
null,
|
||||
{id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:function(){}},
|
||||
{id:"menu-item-workspace",label:RED._("menu.label.flows"),options:[
|
||||
{id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:RED.workspaces.add},
|
||||
{id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:RED.workspaces.edit},
|
||||
{id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:RED.workspaces.remove},
|
||||
null
|
||||
]},
|
||||
{id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [
|
||||
{id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:RED.subflow.createSubflow},
|
||||
{id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:RED.subflow.convertToSubflow},
|
||||
]},
|
||||
null,
|
||||
{id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),onselect:RED.keyboard.showHelp},
|
||||
{id:"menu-item-help",
|
||||
label: RED.settings.theme("menu.menu-item-help.label","Node-RED Website"),
|
||||
href: RED.settings.theme("menu.menu-item-help.url","http://nodered.org/docs")
|
||||
}
|
||||
},
|
||||
{id:"menu-item-node-red-version", label:"v"+RED.settings.version}
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
|
||||
RED.settings = (function () {
|
||||
|
||||
|
||||
var loadedSettings = {};
|
||||
|
||||
|
||||
var hasLocalStorage = function () {
|
||||
try {
|
||||
return 'localStorage' in window && window['localStorage'] !== null;
|
||||
@@ -74,7 +74,7 @@ RED.settings = (function () {
|
||||
RED.settings.set("auth-tokens",{access_token: accessToken});
|
||||
window.location.search = "";
|
||||
}
|
||||
|
||||
|
||||
$.ajaxSetup({
|
||||
beforeSend: function(jqXHR,settings) {
|
||||
// Only attach auth header for requests to relative paths
|
||||
@@ -89,7 +89,7 @@ RED.settings = (function () {
|
||||
|
||||
load(done);
|
||||
}
|
||||
|
||||
|
||||
var load = function(done) {
|
||||
$.ajax({
|
||||
headers: {
|
||||
@@ -141,7 +141,7 @@ RED.settings = (function () {
|
||||
set: set,
|
||||
get: get,
|
||||
remove: remove,
|
||||
|
||||
|
||||
theme: theme
|
||||
}
|
||||
})
|
||||
|
||||
@@ -22,7 +22,7 @@ RED.clipboard = (function() {
|
||||
var exportNodesDialog;
|
||||
var importNodesDialog;
|
||||
|
||||
function setupDialogs(){
|
||||
function setupDialogs() {
|
||||
dialog = $('<div id="clipboard-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
|
||||
.appendTo("body")
|
||||
.dialog({
|
||||
@@ -61,7 +61,7 @@ RED.clipboard = (function() {
|
||||
close: function(e) {
|
||||
RED.keyboard.enable();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
dialogContainer = dialog.children(".dialog-form");
|
||||
|
||||
@@ -83,9 +83,11 @@ RED.clipboard = (function() {
|
||||
function validateImport() {
|
||||
var importInput = $("#clipboard-import");
|
||||
var v = importInput.val();
|
||||
v = v.substring(v.indexOf('['),v.lastIndexOf(']')+1);
|
||||
try {
|
||||
JSON.parse(v);
|
||||
importInput.removeClass("input-error");
|
||||
importInput.val(v);
|
||||
$("#clipboard-dialog-ok").button("enable");
|
||||
} catch(err) {
|
||||
if (v !== "") {
|
||||
@@ -153,8 +155,6 @@ RED.clipboard = (function() {
|
||||
RED.keyboard.add(/* e */ 69,{ctrl:true},function(){exportNodes();d3.event.preventDefault();});
|
||||
RED.keyboard.add(/* i */ 73,{ctrl:true},function(){importNodes();d3.event.preventDefault();});
|
||||
|
||||
|
||||
|
||||
$('#chart').on("dragenter",function(event) {
|
||||
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
|
||||
$("#dropTarget").css({display:'table'});
|
||||
@@ -173,18 +173,13 @@ RED.clipboard = (function() {
|
||||
.on("drop",function(event) {
|
||||
var data = event.originalEvent.dataTransfer.getData("text/plain");
|
||||
hideDropTarget();
|
||||
data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1);
|
||||
RED.view.importNodes(data);
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
|
||||
},
|
||||
import: importNodes,
|
||||
export: exportNodes
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
})();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2015 IBM Corp.
|
||||
* Copyright 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -208,13 +208,13 @@ RED.deploy = (function() {
|
||||
.html("<li>"+invalidNodes.map(function(A) { return (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")"}).join("</li><li>")+"</li>");
|
||||
|
||||
} else if (hasUnusedConfig && !ignoreDeployWarnings.unusedConfig) {
|
||||
showWarning = true;
|
||||
$( "#node-dialog-confirm-deploy-type" ).val("unusedConfig");
|
||||
$( "#node-dialog-confirm-deploy-unused" ).show();
|
||||
|
||||
unusedConfigNodes.sort(sortNodeInfo);
|
||||
$( "#node-dialog-confirm-deploy-unused-list" )
|
||||
.html("<li>"+unusedConfigNodes.map(function(A) { return (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")"}).join("</li><li>")+"</li>");
|
||||
// showWarning = true;
|
||||
// $( "#node-dialog-confirm-deploy-type" ).val("unusedConfig");
|
||||
// $( "#node-dialog-confirm-deploy-unused" ).show();
|
||||
//
|
||||
// unusedConfigNodes.sort(sortNodeInfo);
|
||||
// $( "#node-dialog-confirm-deploy-unused-list" )
|
||||
// .html("<li>"+unusedConfigNodes.map(function(A) { return (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")"}).join("</li><li>")+"</li>");
|
||||
}
|
||||
if (showWarning) {
|
||||
$( "#node-dialog-confirm-deploy-hide" ).prop("checked",false);
|
||||
@@ -241,7 +241,13 @@ RED.deploy = (function() {
|
||||
"Node-RED-Deployment-Type":deploymentType
|
||||
}
|
||||
}).done(function(data,textStatus,xhr) {
|
||||
RED.notify(RED._("deploy.successfulDeploy"),"success");
|
||||
if (hasUnusedConfig) {
|
||||
RED.notify(
|
||||
'<p>'+RED._("deploy.successfulDeploy")+'</p>'+
|
||||
'<p>'+RED._("deploy.unusedConfigNodes")+' <a href="#" onclick="RED.sidebar.config.show(true); return false;">'+RED._("deploy.unusedConfigNodesLink")+'</a></p>',"success",false,6000);
|
||||
} else {
|
||||
RED.notify(RED._("deploy.successfulDeploy"),"success");
|
||||
}
|
||||
RED.nodes.eachNode(function(node) {
|
||||
if (node.changed) {
|
||||
node.dirty = true;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -116,6 +116,9 @@ RED.editor = (function() {
|
||||
*/
|
||||
function validateNodeProperty(node,definition,property,value) {
|
||||
var valid = true;
|
||||
if (/^\$\([a-zA-Z_][a-zA-Z0-9_]*\)$/.test(value)) {
|
||||
return true;
|
||||
}
|
||||
if ("required" in definition[property] && definition[property].required) {
|
||||
valid = value !== "";
|
||||
}
|
||||
@@ -126,8 +129,8 @@ RED.editor = (function() {
|
||||
if (!value || value == "_ADD_") {
|
||||
valid = definition[property].hasOwnProperty("required") && !definition[property].required;
|
||||
} else {
|
||||
var v = RED.nodes.node(value).valid;
|
||||
valid = (v==null || v);
|
||||
var configNode = RED.nodes.node(value);
|
||||
valid = (configNode !== null && (configNode.valid == null || configNode.valid));
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
@@ -150,9 +153,9 @@ RED.editor = (function() {
|
||||
node.ports.pop();
|
||||
}
|
||||
RED.nodes.eachLink(function(l) {
|
||||
if (l.source === node && l.sourcePort >= node.outputs) {
|
||||
removedLinks.push(l);
|
||||
}
|
||||
if (l.source === node && l.sourcePort >= node.outputs) {
|
||||
removedLinks.push(l);
|
||||
}
|
||||
});
|
||||
} else if (node.outputs > node.ports.length) {
|
||||
while (node.outputs > node.ports.length) {
|
||||
@@ -305,7 +308,7 @@ RED.editor = (function() {
|
||||
}
|
||||
editing_node.dirty = true;
|
||||
validateNode(editing_node);
|
||||
RED.view.redraw();
|
||||
RED.view.redraw(true);
|
||||
}
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
@@ -348,6 +351,10 @@ RED.editor = (function() {
|
||||
resize: function(e,ui) {
|
||||
if (editing_node) {
|
||||
$(this).dialog('option',"sizeCache-"+editing_node.type,ui.size);
|
||||
if (editing_node._def.oneditresize) {
|
||||
var form = $("#dialog-form");
|
||||
editing_node._def.oneditresize.call(editing_node,{width:form.width(),height:form.height()});
|
||||
}
|
||||
}
|
||||
},
|
||||
open: function(e) {
|
||||
@@ -364,6 +371,12 @@ RED.editor = (function() {
|
||||
$(this).dialog('option','width',size.width);
|
||||
$(this).dialog('option','height',size.height);
|
||||
}
|
||||
if (editing_node._def.oneditresize) {
|
||||
setTimeout(function() {
|
||||
var form = $("#dialog-form");
|
||||
editing_node._def.oneditresize.call(editing_node,{width:form.width(),height:form.height()});
|
||||
},0);
|
||||
}
|
||||
}
|
||||
},
|
||||
close: function(e) {
|
||||
@@ -385,6 +398,12 @@ RED.editor = (function() {
|
||||
}
|
||||
editing_node = null;
|
||||
}
|
||||
}).parent().on('keydown', function(evt) {
|
||||
if (evt.keyCode === $.ui.keyCode.ESCAPE && (evt.metaKey || evt.ctrlKey)) {
|
||||
$("#node-dialog-cancel").click();
|
||||
} else if (evt.keyCode === $.ui.keyCode.ENTER && (evt.metaKey || evt.ctrlKey)) {
|
||||
$("#node-dialog-ok").click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -935,20 +954,20 @@ RED.editor = (function() {
|
||||
RED.nodes.add(editing_config_node);
|
||||
}
|
||||
|
||||
updateConfigNodeSelect(configProperty,configType,editing_config_node.id);
|
||||
|
||||
if (configTypeDef.credentials) {
|
||||
updateNodeCredentials(editing_config_node,configTypeDef.credentials,"node-config-input");
|
||||
}
|
||||
if (configTypeDef.oneditsave) {
|
||||
configTypeDef.oneditsave.call(editing_config_node);
|
||||
}
|
||||
if (configTypeDef.credentials) {
|
||||
updateNodeCredentials(editing_config_node,configTypeDef.credentials,"node-config-input");
|
||||
}
|
||||
validateNode(editing_config_node);
|
||||
for (var i=0;i<editing_config_node.users.length;i++) {
|
||||
var user = editing_config_node.users[i];
|
||||
validateNode(user);
|
||||
}
|
||||
|
||||
updateConfigNodeSelect(configProperty,configType,editing_config_node.id);
|
||||
|
||||
RED.nodes.dirty(true);
|
||||
RED.view.redraw(true);
|
||||
$(this).dialog("close");
|
||||
@@ -1008,6 +1027,12 @@ RED.editor = (function() {
|
||||
cancel: '.ui-dialog-content, .ui-dialog-titlebar-close, #node-config-dialog-scope-container'
|
||||
});
|
||||
}
|
||||
}).parent().on('keydown', function(evt) {
|
||||
if (evt.keyCode === $.ui.keyCode.ESCAPE && (evt.metaKey || evt.ctrlKey)) {
|
||||
$("#node-config-dialog-cancel").click();
|
||||
} else if (evt.keyCode === $.ui.keyCode.ENTER && (evt.metaKey || evt.ctrlKey)) {
|
||||
$("#node-config-dialog-ok").click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1079,7 +1104,7 @@ RED.editor = (function() {
|
||||
RED.history.push(historyEvent);
|
||||
}
|
||||
editing_node.dirty = true;
|
||||
RED.view.redraw();
|
||||
RED.view.redraw(true);
|
||||
}
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
@@ -1129,6 +1154,12 @@ RED.editor = (function() {
|
||||
$(".node-text-editor").css("height",height+"px");
|
||||
subflowEditor.resize();
|
||||
}
|
||||
}).parent().on('keydown', function(evt) {
|
||||
if (evt.keyCode === $.ui.keyCode.ESCAPE && (evt.metaKey || evt.ctrlKey)) {
|
||||
$("#subflow-dialog-cancel").click();
|
||||
} else if (evt.keyCode === $.ui.keyCode.ENTER && (evt.metaKey || evt.ctrlKey)) {
|
||||
$("#subflow-dialog-ok").click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,7 +27,9 @@ RED.library = (function() {
|
||||
var li;
|
||||
var a;
|
||||
var ul = document.createElement("ul");
|
||||
ul.id = "menu-item-import-library-submenu";
|
||||
if (root === "") {
|
||||
ul.id = "menu-item-import-library-submenu";
|
||||
}
|
||||
ul.className = "dropdown-menu";
|
||||
if (data.d) {
|
||||
for (i in data.d) {
|
||||
@@ -36,7 +38,8 @@ RED.library = (function() {
|
||||
li.className = "dropdown-submenu pull-left";
|
||||
a = document.createElement("a");
|
||||
a.href="#";
|
||||
a.innerHTML = i;
|
||||
var label = i.replace(/^node-red-contrib-/,"").replace(/^node-red-node-/,"").replace(/-/," ").replace(/_/," ");
|
||||
a.innerHTML = label;
|
||||
li.appendChild(a);
|
||||
li.appendChild(buildMenu(data.d[i],root+(root!==""?"/":"")+i));
|
||||
ul.appendChild(li);
|
||||
@@ -53,7 +56,7 @@ RED.library = (function() {
|
||||
a.flowName = root+(root!==""?"/":"")+data.f[i];
|
||||
a.onclick = function() {
|
||||
$.get('library/flows/'+this.flowName, function(data) {
|
||||
RED.view.importNodes(data);
|
||||
RED.view.importNodes(data);
|
||||
});
|
||||
};
|
||||
li.appendChild(a);
|
||||
@@ -63,7 +66,17 @@ RED.library = (function() {
|
||||
}
|
||||
return ul;
|
||||
};
|
||||
var examples;
|
||||
if (data.d && data.d._examples_) {
|
||||
examples = data.d._examples_;
|
||||
delete data.d._examples_;
|
||||
}
|
||||
var menu = buildMenu(data,"");
|
||||
$("#menu-item-import-examples").remove();
|
||||
if (examples) {
|
||||
RED.menu.addItem("menu-item-import",{id:"menu-item-import-examples",label:RED._("menu.label.examples"),options:[]})
|
||||
$("#menu-item-import-examples-submenu").replaceWith(buildMenu(examples,"_examples_"));
|
||||
}
|
||||
//TODO: need an api in RED.menu for this
|
||||
$("#menu-item-import-library-submenu").replaceWith(menu);
|
||||
});
|
||||
@@ -117,7 +130,7 @@ RED.library = (function() {
|
||||
$(".active",bc).removeClass("active");
|
||||
bc.append(bcli);
|
||||
$.getJSON("library/"+options.url+root+dirName,function(data) {
|
||||
$("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data));
|
||||
$("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data));
|
||||
});
|
||||
}
|
||||
})();
|
||||
@@ -125,20 +138,20 @@ RED.library = (function() {
|
||||
ul.appendChild(li);
|
||||
} else {
|
||||
// file
|
||||
li = buildFileListItem(v);
|
||||
li.innerHTML = v.name;
|
||||
li.onclick = (function() {
|
||||
var item = v;
|
||||
return function(e) {
|
||||
$(".list-selected",ul).removeClass("list-selected");
|
||||
$(this).addClass("list-selected");
|
||||
$.get("library/"+options.url+root+item.fn, function(data) {
|
||||
selectedLibraryItem = item;
|
||||
libraryEditor.setValue(data,-1);
|
||||
});
|
||||
}
|
||||
})();
|
||||
ul.appendChild(li);
|
||||
li = buildFileListItem(v);
|
||||
li.innerHTML = v.name;
|
||||
li.onclick = (function() {
|
||||
var item = v;
|
||||
return function(e) {
|
||||
$(".list-selected",ul).removeClass("list-selected");
|
||||
$(this).addClass("list-selected");
|
||||
$.get("library/"+options.url+root+item.fn, function(data) {
|
||||
selectedLibraryItem = item;
|
||||
libraryEditor.setValue(data,-1);
|
||||
});
|
||||
}
|
||||
})();
|
||||
ul.appendChild(li);
|
||||
}
|
||||
}
|
||||
return ul;
|
||||
@@ -428,8 +441,8 @@ RED.library = (function() {
|
||||
data: $("#node-input-library-filename").attr('nodes'),
|
||||
contentType: "application/json; charset=utf-8"
|
||||
}).done(function() {
|
||||
RED.library.loadFlowLibrary();
|
||||
RED.notify(RED._("library.savedNodes"),"success");
|
||||
RED.library.loadFlowLibrary();
|
||||
RED.notify(RED._("library.savedNodes"),"success");
|
||||
}).fail(function(xhr,textStatus,err) {
|
||||
RED.notify(RED._("library.saveFailed",{message:xhr.responseText}),"error");
|
||||
});
|
||||
@@ -452,7 +465,7 @@ RED.library = (function() {
|
||||
close: function(e) {
|
||||
RED.keyboard.enable();
|
||||
}
|
||||
});
|
||||
});
|
||||
exportToLibraryDialog.children(".dialog-form").append($(
|
||||
'<div class="form-row">'+
|
||||
'<label for="node-input-library-filename" data-i18n="[append]editor:library.filename"><i class="fa fa-file"></i> </label>'+
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -49,6 +49,13 @@ RED.notify = (function() {
|
||||
};
|
||||
})();
|
||||
if (!fixed) {
|
||||
$(n).click((function() {
|
||||
var nn = n;
|
||||
return function() {
|
||||
nn.close();
|
||||
window.clearTimeout(nn.timeoutid);
|
||||
};
|
||||
})());
|
||||
n.timeoutid = window.setTimeout(n.close,timeout||3000);
|
||||
}
|
||||
currentNotifications.push(n);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -66,7 +66,7 @@ RED.palette = (function() {
|
||||
var lineHeight = 20;
|
||||
var portHeight = 10;
|
||||
|
||||
var words = label.split(" ");
|
||||
var words = label.split(/[ -]/);
|
||||
|
||||
var displayLines = [];
|
||||
|
||||
@@ -134,22 +134,27 @@ RED.palette = (function() {
|
||||
d.id = "palette_node_"+nodeTypeId;
|
||||
d.type = nt;
|
||||
|
||||
var label;
|
||||
|
||||
if (typeof def.paletteLabel === "undefined") {
|
||||
label = /^(.*?)([ -]in|[ -]out)?$/.exec(nt)[1];
|
||||
} else {
|
||||
label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||"";
|
||||
var label = /^(.*?)([ -]in|[ -]out)?$/.exec(nt)[1];
|
||||
if (typeof def.paletteLabel !== "undefined") {
|
||||
try {
|
||||
label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||"";
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+nt+".paletteLabel",err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$('<div/>',{class:"palette_label"+(def.align=="right"?" palette_label_right":"")}).appendTo(d);
|
||||
|
||||
d.className="palette_node";
|
||||
|
||||
|
||||
if (def.icon) {
|
||||
var icon_url = (typeof def.icon === "function" ? def.icon.call({}) : def.icon);
|
||||
var icon_url = "arrow-in.png";
|
||||
try {
|
||||
icon_url = (typeof def.icon === "function" ? def.icon.call({}) : def.icon);
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+nt+".icon",err);
|
||||
}
|
||||
var iconContainer = $('<div/>',{class:"palette_icon_container"+(def.align=="right"?" palette_icon_container_right":"")}).appendTo(d);
|
||||
$('<div/>',{class:"palette_icon",style:"background-image: url(icons/"+icon_url+")"}).appendTo(iconContainer);
|
||||
}
|
||||
@@ -210,12 +215,83 @@ RED.palette = (function() {
|
||||
var help = '<div class="node-help">'+helpText+"</div>";
|
||||
RED.sidebar.info.set(help);
|
||||
});
|
||||
var chart = $("#chart");
|
||||
var chartOffset = chart.offset();
|
||||
var chartSVG = $("#chart>svg").get(0);
|
||||
var activeSpliceLink;
|
||||
var mouseX;
|
||||
var mouseY;
|
||||
var spliceTimer;
|
||||
$(d).draggable({
|
||||
helper: 'clone',
|
||||
appendTo: 'body',
|
||||
revert: true,
|
||||
revertDuration: 50,
|
||||
start: function() {RED.view.focus();}
|
||||
start: function() {RED.view.focus();},
|
||||
stop: function() { d3.select('.link_splice').classed('link_splice',false); if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null;}},
|
||||
drag: function(e,ui) {
|
||||
// TODO: this is the margin-left of palette node. Hard coding
|
||||
// it here makes me sad
|
||||
ui.position.left += 17.5;
|
||||
if (def.inputs > 0 && def.outputs > 0) {
|
||||
mouseX = e.clientX - chartOffset.left+chart.scrollLeft();
|
||||
mouseY = e.clientY-chartOffset.top +chart.scrollTop();
|
||||
|
||||
if (!spliceTimer) {
|
||||
spliceTimer = setTimeout(function() {
|
||||
var nodes = [];
|
||||
var bestDistance = Infinity;
|
||||
var bestLink = null;
|
||||
if (chartSVG.getIntersectionList) {
|
||||
var svgRect = chartSVG.createSVGRect();
|
||||
svgRect.x = mouseX;
|
||||
svgRect.y = mouseY;
|
||||
svgRect.width = 1;
|
||||
svgRect.height = 1;
|
||||
nodes = chartSVG.getIntersectionList(svgRect,chartSVG);
|
||||
mouseX /= RED.view.scale();
|
||||
mouseY /= RED.view.scale();
|
||||
} else {
|
||||
// Firefox doesn't do getIntersectionList and that
|
||||
// makes us sad
|
||||
mouseX /= RED.view.scale();
|
||||
mouseY /= RED.view.scale();
|
||||
nodes = RED.view.getLinksAtPoint(mouseX,mouseY);
|
||||
}
|
||||
for (var i=0;i<nodes.length;i++) {
|
||||
if (d3.select(nodes[i]).classed('link_background')) {
|
||||
var length = nodes[i].getTotalLength();
|
||||
for (var j=0;j<length;j+=10) {
|
||||
var p = nodes[i].getPointAtLength(j);
|
||||
var d2 = ((p.x-mouseX)*(p.x-mouseX))+((p.y-mouseY)*(p.y-mouseY));
|
||||
if (d2 < 200 && d2 < bestDistance) {
|
||||
bestDistance = d2;
|
||||
bestLink = nodes[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (activeSpliceLink && activeSpliceLink !== bestLink) {
|
||||
d3.select(activeSpliceLink.parentNode).classed('link_splice',false);
|
||||
}
|
||||
if (bestLink) {
|
||||
d3.select(bestLink.parentNode).classed('link_splice',true)
|
||||
} else {
|
||||
d3.select('.link_splice').classed('link_splice',false);
|
||||
}
|
||||
if (activeSpliceLink !== bestLink) {
|
||||
if (bestLink) {
|
||||
$(ui.helper).data('splice',d3.select(bestLink).data()[0]);
|
||||
} else {
|
||||
$(ui.helper).removeData('splice');
|
||||
}
|
||||
}
|
||||
activeSpliceLink = bestLink;
|
||||
spliceTimer = null;
|
||||
},200);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var nodeInfo = null;
|
||||
@@ -292,7 +368,7 @@ RED.palette = (function() {
|
||||
}
|
||||
|
||||
var re = new RegExp(val,'i');
|
||||
$(".palette_node").each(function(i,el) {
|
||||
$("#palette-container .palette_node").each(function(i,el) {
|
||||
var currentLabel = $(el).find(".palette_label").text();
|
||||
if (val === "" || re.test(el.id) || re.test(currentLabel)) {
|
||||
$(this).show();
|
||||
|
||||
@@ -20,10 +20,14 @@ RED.sidebar = (function() {
|
||||
id:"sidebar-tabs",
|
||||
onchange:function(tab) {
|
||||
$("#sidebar-content").children().hide();
|
||||
$("#sidebar-footer").children().hide();
|
||||
if (tab.onchange) {
|
||||
tab.onchange.call(tab);
|
||||
}
|
||||
$(tab.content).show();
|
||||
if (tab.toolbar) {
|
||||
$(tab.toolbar).show();
|
||||
}
|
||||
},
|
||||
onremove: function(tab) {
|
||||
$(tab.content).hide();
|
||||
@@ -58,10 +62,15 @@ RED.sidebar = (function() {
|
||||
|
||||
$("#sidebar-content").append(options.content);
|
||||
$(options.content).hide();
|
||||
if (options.toolbar) {
|
||||
$("#sidebar-footer").append(options.toolbar);
|
||||
$(options.toolbar).hide();
|
||||
}
|
||||
$(options.content).hide();
|
||||
var id = options.id;
|
||||
|
||||
RED.menu.addItem("menu-item-sidebar-menu",{
|
||||
id:"menu-item-sidebar-menu-"+options.id,
|
||||
RED.menu.addItem("menu-item-view-menu",{
|
||||
id:"menu-item-view-menu-"+options.id,
|
||||
label:options.name,
|
||||
onselect:function() {
|
||||
showSidebar(options.id);
|
||||
@@ -80,7 +89,7 @@ RED.sidebar = (function() {
|
||||
sidebar_tabs.removeTab(id);
|
||||
$(knownTabs[id].content).remove();
|
||||
delete knownTabs[id];
|
||||
RED.menu.removeItem("menu-item-sidebar-menu-"+id);
|
||||
RED.menu.removeItem("menu-item-view-menu-"+id);
|
||||
}
|
||||
|
||||
var sidebarSeparator = {};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,24 +19,115 @@ RED.sidebar.config = (function() {
|
||||
var content = document.createElement("div");
|
||||
content.className = "sidebar-node-config"
|
||||
|
||||
$('<div class="palette-category">'+
|
||||
'<div class="workspace-config-node-tray-header palette-header"><i class="fa fa-angle-down expanded"></i><span data-i18n="sidebar.config.local"></span></div>'+
|
||||
'<ul id="workspace-config-node-tray-locals" class="palette-content config-node-list"></ul>'+
|
||||
'</div>'+
|
||||
'<div class="palette-category">'+
|
||||
'<div class="workspace-config-node-tray-header palette-header"><i class="fa fa-angle-down expanded"></i><span data-i18n="sidebar.config.global"></span></div>'+
|
||||
'<ul id="workspace-config-node-tray-globals" class="palette-content config-node-list"></ul>'+
|
||||
'</div>').appendTo(content);
|
||||
$('<div class="button-group sidebar-header">'+
|
||||
'<a class="sidebar-header-button-toggle selected" id="workspace-config-node-filter-all" href="#"><span data-i18n="sidebar.config.filterAll"></span></a>'+
|
||||
'<a class="sidebar-header-button-toggle" id="workspace-config-node-filter-unused" href="#"><span data-i18n="sidebar.config.filterUnused"></span></a> '+
|
||||
'</div>'
|
||||
).appendTo(content);
|
||||
|
||||
|
||||
var toolbar = $('<div>'+
|
||||
'<a class="sidebar-footer-button" id="workspace-config-node-collapse-all" href="#"><i class="fa fa-angle-double-up"></i></a> '+
|
||||
'<a class="sidebar-footer-button" id="workspace-config-node-expand-all" href="#"><i class="fa fa-angle-double-down"></i></a>'+
|
||||
'</div>');
|
||||
|
||||
var globalCategories = $("<div>").appendTo(content);
|
||||
var flowCategories = $("<div>").appendTo(content);
|
||||
var subflowCategories = $("<div>").appendTo(content);
|
||||
|
||||
var showUnusedOnly = false;
|
||||
|
||||
var categories = {};
|
||||
|
||||
function getOrCreateCategory(name,parent,label) {
|
||||
name = name.replace(/\./i,"-");
|
||||
if (!categories[name]) {
|
||||
var container = $('<div class="palette-category workspace-config-node-category" id="workspace-config-node-category-'+name+'"></div>').appendTo(parent);
|
||||
var header = $('<div class="workspace-config-node-tray-header palette-header"><i class="fa fa-angle-down expanded"></i></div>').appendTo(container);
|
||||
if (label) {
|
||||
$('<span class="config-node-label"/>').text(label).appendTo(header);
|
||||
} else {
|
||||
$('<span class="config-node-label" data-i18n="sidebar.config.'+name+'">').appendTo(header);
|
||||
}
|
||||
$('<span class="config-node-filter-info"></span>').appendTo(header);
|
||||
category = $('<ul class="palette-content config-node-list"></ul>').appendTo(container);
|
||||
container.i18n();
|
||||
var icon = header.find("i");
|
||||
var result = {
|
||||
label: label,
|
||||
list: category,
|
||||
size: function() {
|
||||
return result.list.find("li:not(.config_node_none)").length
|
||||
},
|
||||
open: function(snap) {
|
||||
if (!icon.hasClass("expanded")) {
|
||||
icon.addClass("expanded");
|
||||
if (snap) {
|
||||
result.list.show();
|
||||
} else {
|
||||
result.list.slideDown();
|
||||
}
|
||||
}
|
||||
},
|
||||
close: function(snap) {
|
||||
if (icon.hasClass("expanded")) {
|
||||
icon.removeClass("expanded");
|
||||
if (snap) {
|
||||
result.list.hide();
|
||||
} else {
|
||||
result.list.slideUp();
|
||||
}
|
||||
}
|
||||
},
|
||||
isOpen: function() {
|
||||
return icon.hasClass("expanded");
|
||||
}
|
||||
};
|
||||
|
||||
header.on('click', function(e) {
|
||||
if (result.isOpen()) {
|
||||
result.close();
|
||||
} else {
|
||||
result.open();
|
||||
}
|
||||
});
|
||||
categories[name] = result;
|
||||
} else {
|
||||
if (categories[name].label !== label) {
|
||||
categories[name].list.parent().find('.config-node-label').text(label);
|
||||
categories[name].label = label;
|
||||
}
|
||||
}
|
||||
return categories[name];
|
||||
}
|
||||
|
||||
function createConfigNodeList(id,nodes) {
|
||||
var category = getOrCreateCategory(id.replace(/\./i,"-"))
|
||||
var list = category.list;
|
||||
|
||||
function createConfigNodeList(nodes,list) {
|
||||
nodes.sort(function(A,B) {
|
||||
if (A.type < B.type) { return -1;}
|
||||
if (A.type > B.type) { return 1;}
|
||||
return 0;
|
||||
});
|
||||
if (showUnusedOnly) {
|
||||
var hiddenCount = nodes.length;
|
||||
nodes = nodes.filter(function(n) {
|
||||
return n.users.length === 0;
|
||||
})
|
||||
hiddenCount = hiddenCount - nodes.length;
|
||||
if (hiddenCount > 0) {
|
||||
list.parent().find('.config-node-filter-info').text(RED._('sidebar.config.filtered',{count:hiddenCount})).show();
|
||||
} else {
|
||||
list.parent().find('.config-node-filter-info').hide();
|
||||
}
|
||||
} else {
|
||||
list.parent().find('.config-node-filter-info').hide();
|
||||
}
|
||||
list.empty();
|
||||
if (nodes.length === 0) {
|
||||
$('<li class="config_node_none" data-i18n="sidebar.config.none">NONE</li>').i18n().appendTo(list);
|
||||
category.close(true);
|
||||
} else {
|
||||
var currentType = "";
|
||||
nodes.forEach(function(node) {
|
||||
@@ -86,23 +177,46 @@ RED.sidebar.config = (function() {
|
||||
RED.view.redraw();
|
||||
});
|
||||
});
|
||||
category.open(true);
|
||||
}
|
||||
}
|
||||
|
||||
function refreshConfigNodeList() {
|
||||
var validList = {"global":true};
|
||||
|
||||
var localConfigNodes = [];
|
||||
getOrCreateCategory("global",globalCategories);
|
||||
|
||||
RED.nodes.eachWorkspace(function(ws) {
|
||||
validList[ws.id.replace(/\./g,"-")] = true;
|
||||
getOrCreateCategory(ws.id,flowCategories,ws.label);
|
||||
})
|
||||
RED.nodes.eachSubflow(function(sf) {
|
||||
validList[sf.id.replace(/\./g,"-")] = true;
|
||||
getOrCreateCategory(sf.id,subflowCategories,sf.name);
|
||||
})
|
||||
$(".workspace-config-node-category").each(function() {
|
||||
var id = $(this).attr('id').substring("workspace-config-node-category-".length);
|
||||
if (!validList[id]) {
|
||||
$(this).remove();
|
||||
delete categories[id];
|
||||
}
|
||||
})
|
||||
var globalConfigNodes = [];
|
||||
|
||||
var configList = {};
|
||||
RED.nodes.eachConfig(function(cn) {
|
||||
if (cn.z == RED.workspaces.active()) {
|
||||
localConfigNodes.push(cn);
|
||||
if (cn.z) {//} == RED.workspaces.active()) {
|
||||
configList[cn.z.replace(/\./g,"-")] = configList[cn.z.replace(/\./g,"-")]||[];
|
||||
configList[cn.z.replace(/\./g,"-")].push(cn);
|
||||
} else if (!cn.z) {
|
||||
globalConfigNodes.push(cn);
|
||||
}
|
||||
});
|
||||
createConfigNodeList(localConfigNodes,$("#workspace-config-node-tray-locals"));
|
||||
createConfigNodeList(globalConfigNodes,$("#workspace-config-node-tray-globals"));
|
||||
for (var id in validList) {
|
||||
if (validList.hasOwnProperty(id)) {
|
||||
createConfigNodeList(id,configList[id]||[]);
|
||||
}
|
||||
}
|
||||
createConfigNodeList('global',globalConfigNodes);
|
||||
}
|
||||
|
||||
function init() {
|
||||
@@ -111,25 +225,63 @@ RED.sidebar.config = (function() {
|
||||
label: RED._("sidebar.config.label"),
|
||||
name: RED._("sidebar.config.name"),
|
||||
content: content,
|
||||
toolbar: toolbar,
|
||||
closeable: true,
|
||||
visible: false,
|
||||
onchange: function() { refreshConfigNodeList(); }
|
||||
});
|
||||
|
||||
$(".workspace-config-node-tray-header").on('click', function(e) {
|
||||
var icon = $(this).find("i");
|
||||
if (icon.hasClass("expanded")) {
|
||||
icon.removeClass("expanded");
|
||||
$(this).next().slideUp();
|
||||
} else {
|
||||
icon.addClass("expanded");
|
||||
$(this).next().slideDown();
|
||||
}
|
||||
RED.menu.setAction('menu-item-config-nodes',function() {
|
||||
RED.sidebar.show('config');
|
||||
})
|
||||
|
||||
$("#workspace-config-node-collapse-all").on("click", function(e) {
|
||||
e.preventDefault();
|
||||
for (var cat in categories) {
|
||||
if (categories.hasOwnProperty(cat)) {
|
||||
categories[cat].close();
|
||||
}
|
||||
}
|
||||
});
|
||||
$("#workspace-config-node-expand-all").on("click", function(e) {
|
||||
e.preventDefault();
|
||||
for (var cat in categories) {
|
||||
if (categories.hasOwnProperty(cat)) {
|
||||
if (categories[cat].size() > 0) {
|
||||
categories[cat].open();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
$('#workspace-config-node-filter-all').on("click",function(e) {
|
||||
e.preventDefault();
|
||||
if (showUnusedOnly) {
|
||||
$(this).addClass('selected');
|
||||
$('#workspace-config-node-filter-unused').removeClass('selected');
|
||||
showUnusedOnly = !showUnusedOnly;
|
||||
refreshConfigNodeList();
|
||||
}
|
||||
});
|
||||
$('#workspace-config-node-filter-unused').on("click",function(e) {
|
||||
e.preventDefault();
|
||||
if (!showUnusedOnly) {
|
||||
$(this).addClass('selected');
|
||||
$('#workspace-config-node-filter-all').removeClass('selected');
|
||||
showUnusedOnly = !showUnusedOnly;
|
||||
refreshConfigNodeList();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
function show() {
|
||||
function show(unused) {
|
||||
if (unused !== undefined) {
|
||||
if (unused) {
|
||||
$('#workspace-config-node-filter-unused').click();
|
||||
} else {
|
||||
$('#workspace-config-node-filter-all').click();
|
||||
}
|
||||
}
|
||||
refreshConfigNodeList();
|
||||
RED.sidebar.show("config");
|
||||
}
|
||||
|
||||
306
editor/js/ui/typedInput.js
Normal file
306
editor/js/ui/typedInput.js
Normal file
@@ -0,0 +1,306 @@
|
||||
/**
|
||||
* Copyright 2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
(function($) {
|
||||
var allOptions = {
|
||||
msg: {value:"msg",label:"msg.",validate:/^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]+)*/i},
|
||||
flow: {value:"flow",label:"flow.",validate:/^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]+)*/i},
|
||||
global: {value:"global",label:"global.",validate:/^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]+)*/i},
|
||||
str: {value:"str",label:"string",icon:"red/images/typedInput/az.png"},
|
||||
num: {value:"num",label:"number",icon:"red/images/typedInput/09.png",validate:/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/},
|
||||
bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.png",options:["true","false"]},
|
||||
json: {value:"json",label:"JSON",icon:"red/images/typedInput/json.png", validate: function(v) { try{JSON.parse(v);return true;}catch(e){return false;}}},
|
||||
re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.png"}
|
||||
};
|
||||
var nlsd = false;
|
||||
|
||||
$.widget( "nodered.typedInput", {
|
||||
_create: function() {
|
||||
if (!nlsd && RED && RED._) {
|
||||
for (var i in allOptions) {
|
||||
if (allOptions.hasOwnProperty(i)) {
|
||||
allOptions[i].label = RED._("typedInput.type."+i,{defaultValue:allOptions[i].label});
|
||||
}
|
||||
}
|
||||
}
|
||||
nlsd = true;
|
||||
var that = this;
|
||||
|
||||
this.disarmClick = false;
|
||||
this.element.addClass('red-ui-typedInput');
|
||||
this.uiWidth = this.element.width();
|
||||
this.uiSelect = this.element
|
||||
.wrap( "<div>" )
|
||||
.parent();
|
||||
|
||||
["Right","Left"].forEach(function(d) {
|
||||
var m = that.element.css("margin"+d);
|
||||
that.uiSelect.css("margin"+d,m);
|
||||
that.element.css("margin"+d,0);
|
||||
});
|
||||
this.uiSelect.addClass("red-ui-typedInput-container");
|
||||
|
||||
this.options.types = this.options.types||Object.keys(allOptions);
|
||||
|
||||
var hasSubOptions = false;
|
||||
this.typeMap = {};
|
||||
this.types = this.options.types.map(function(opt) {
|
||||
var result;
|
||||
if (typeof opt === 'string') {
|
||||
result = allOptions[opt];
|
||||
} else {
|
||||
result = opt;
|
||||
}
|
||||
that.typeMap[result.value] = result;
|
||||
if (result.options) {
|
||||
hasSubOptions = true;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
if (this.options.typeField) {
|
||||
this.typeField = $(this.options.typeField).hide();
|
||||
var t = this.typeField.val();
|
||||
if (t && this.typeMap[t]) {
|
||||
this.options.default = t;
|
||||
}
|
||||
} else {
|
||||
this.typeField = $("<input>",{type:'hidden'}).appendTo(this.uiSelect);
|
||||
}
|
||||
|
||||
this.selectTrigger = $('<a href="#"><i class="fa fa-sort-desc"></i></a>').prependTo(this.uiSelect);
|
||||
this.selectLabel = $('<span></span>').appendTo(this.selectTrigger);
|
||||
|
||||
this.element.on('focus', function() {
|
||||
that.uiSelect.addClass('red-ui-typedInput-focus');
|
||||
});
|
||||
this.element.on('blur', function() {
|
||||
that.uiSelect.removeClass('red-ui-typedInput-focus');
|
||||
});
|
||||
this.element.on('change', function() {
|
||||
that.validate();
|
||||
})
|
||||
|
||||
this.selectTrigger.click(function(event) {
|
||||
event.preventDefault();
|
||||
that._showMenu(that.menu,that.selectTrigger);
|
||||
});
|
||||
|
||||
|
||||
if (hasSubOptions) {
|
||||
// explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline'
|
||||
this.optionSelectTrigger = $('<a href="#" class="red-ui-typedInput-option-trigger" style="display:inline-block"><i class="fa fa-sort-desc"></i></a>').appendTo(this.uiSelect);
|
||||
this.optionSelectLabel = $('<span></span>').prependTo(this.optionSelectTrigger);
|
||||
this.optionSelectTrigger.click(function(event) {
|
||||
event.preventDefault();
|
||||
if (that.optionMenu) {
|
||||
that.optionMenu.css({
|
||||
minWidth:that.optionSelectLabel.width()
|
||||
});
|
||||
|
||||
that._showMenu(that.optionMenu,that.optionSelectLabel)
|
||||
}
|
||||
});
|
||||
}
|
||||
this.menu = this._createMenu(this.types, function(v) { that.type(v) });
|
||||
this.type(this.options.default||this.types[0].value);
|
||||
},
|
||||
_hideMenu: function(menu) {
|
||||
$(document).off("mousedown.close-property-select");
|
||||
menu.hide();
|
||||
this.element.focus();
|
||||
},
|
||||
_createMenu: function(opts,callback) {
|
||||
var that = this;
|
||||
var menu = $("<div>").addClass("red-ui-typedInput-options");
|
||||
opts.forEach(function(opt) {
|
||||
if (typeof opt === 'string') {
|
||||
opt = {value:opt,label:opt};
|
||||
}
|
||||
var op = $('<a href="#">').attr("value",opt.value).appendTo(menu);
|
||||
if (opt.label) {
|
||||
op.text(opt.label);
|
||||
}
|
||||
if (opt.icon) {
|
||||
$('<img>',{src:opt.icon,style:"margin-right: 4px; height: 18px;"}).prependTo(op);
|
||||
} else {
|
||||
op.css({paddingLeft: "18px"});
|
||||
}
|
||||
|
||||
op.click(function(event) {
|
||||
event.preventDefault();
|
||||
callback(opt.value);
|
||||
that._hideMenu(menu);
|
||||
});
|
||||
});
|
||||
menu.css({
|
||||
display: "none",
|
||||
});
|
||||
menu.appendTo(document.body);
|
||||
return menu;
|
||||
|
||||
},
|
||||
_showMenu: function(menu,relativeTo) {
|
||||
if (this.disarmClick) {
|
||||
this.disarmClick = false;
|
||||
return
|
||||
}
|
||||
var that = this;
|
||||
var pos = relativeTo.offset();
|
||||
var height = relativeTo.height();
|
||||
menu.css({
|
||||
top: (height+pos.top-3)+"px",
|
||||
left: (2+pos.left)+"px",
|
||||
});
|
||||
menu.slideDown(100);
|
||||
this._delay(function() {
|
||||
that.uiSelect.addClass('red-ui-typedInput-focus');
|
||||
$(document).on("mousedown.close-property-select", function(event) {
|
||||
if(!$(event.target).closest(menu).length) {
|
||||
that._hideMenu(menu);
|
||||
}
|
||||
if ($(event.target).closest(relativeTo).length) {
|
||||
that.disarmClick = true;
|
||||
event.preventDefault();
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
_getLabelWidth: function(label) {
|
||||
var labelWidth = label.width();
|
||||
if (labelWidth === 0) {
|
||||
var newTrigger = label.clone();
|
||||
newTrigger.css({
|
||||
position:"absolute",
|
||||
top:0,
|
||||
left:-1000
|
||||
}).appendTo(document.body);
|
||||
labelWidth = newTrigger.width()+4;
|
||||
newTrigger.remove();
|
||||
}
|
||||
return labelWidth;
|
||||
},
|
||||
_resize: function() {
|
||||
|
||||
if (this.typeMap[this.propertyType] && this.typeMap[this.propertyType].hasValue === false) {
|
||||
this.selectTrigger.width(this.uiWidth+5);
|
||||
} else {
|
||||
this.selectTrigger.width('auto');
|
||||
var labelWidth = this._getLabelWidth(this.selectTrigger);
|
||||
|
||||
var newWidth = this.uiWidth-labelWidth+4;
|
||||
this.element.width(newWidth);
|
||||
|
||||
if (this.optionSelectTrigger) {
|
||||
var triggerWidth = this._getLabelWidth(this.optionSelectTrigger);
|
||||
labelWidth = this._getLabelWidth(this.optionSelectLabel)-4;
|
||||
this.optionSelectLabel.width(labelWidth+(newWidth-triggerWidth));
|
||||
}
|
||||
}
|
||||
},
|
||||
_destroy: function() {
|
||||
this.menu.remove();
|
||||
},
|
||||
width: function(desiredWidth) {
|
||||
this.uiWidth = desiredWidth;
|
||||
this._resize();
|
||||
},
|
||||
value: function(value) {
|
||||
if (!arguments.length) {
|
||||
return this.element.val();
|
||||
} else {
|
||||
if (this.typeMap[this.propertyType].options) {
|
||||
if (this.typeMap[this.propertyType].options.indexOf(value) === -1) {
|
||||
value = "";
|
||||
}
|
||||
this.optionSelectLabel.text(value);
|
||||
}
|
||||
this.element.val(value);
|
||||
this.element.trigger('change');
|
||||
}
|
||||
},
|
||||
type: function(type) {
|
||||
if (!arguments.length) {
|
||||
return this.propertyType;
|
||||
} else {
|
||||
var opt = this.typeMap[type];
|
||||
if (opt && this.propertyType !== type) {
|
||||
this.propertyType = type;
|
||||
this.typeField.val(type);
|
||||
this.selectLabel.empty();
|
||||
if (opt.icon) {
|
||||
$('<img>',{src:opt.icon,style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel);
|
||||
} else {
|
||||
this.selectLabel.text(opt.label);
|
||||
}
|
||||
if (opt.options) {
|
||||
if (this.optionSelectTrigger) {
|
||||
this.optionSelectTrigger.show();
|
||||
this.element.hide();
|
||||
var that = this;
|
||||
this.optionMenu = this._createMenu(opt.options,function(v){
|
||||
that.optionSelectLabel.text(v);
|
||||
that.value(v);
|
||||
});
|
||||
var currentVal = this.element.val();
|
||||
if (opt.options.indexOf(currentVal) !== -1) {
|
||||
this.optionSelectLabel.text(currentVal);
|
||||
} else {
|
||||
this.value(opt.options[0]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.optionMenu) {
|
||||
this.optionMenu.remove();
|
||||
this.optionMenu = null;
|
||||
}
|
||||
if (this.optionSelectTrigger) {
|
||||
this.optionSelectTrigger.hide();
|
||||
}
|
||||
if (opt.hasValue === false) {
|
||||
this.element.val("");
|
||||
this.element.hide();
|
||||
} else {
|
||||
this.element.show();
|
||||
}
|
||||
this.element.trigger('change');
|
||||
}
|
||||
this._resize();
|
||||
}
|
||||
}
|
||||
},
|
||||
validate: function() {
|
||||
var result;
|
||||
var value = this.value();
|
||||
var type = this.type();
|
||||
if (this.typeMap[type] && this.typeMap[type].validate) {
|
||||
var val = this.typeMap[type].validate;
|
||||
if (typeof val === 'function') {
|
||||
result = val(value);
|
||||
} else {
|
||||
result = val.test(value);
|
||||
}
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
if (result) {
|
||||
this.uiSelect.removeClass('input-error');
|
||||
} else {
|
||||
this.uiSelect.addClass('input-error');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
||||
})(jQuery);
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,6 +31,12 @@ RED.view = (function() {
|
||||
|
||||
var workspaceScrollPositions = {};
|
||||
|
||||
var gridSize = 20;
|
||||
var snapGrid = false;
|
||||
|
||||
var activeSpliceLink;
|
||||
var spliceActive = false;
|
||||
var spliceTimer;
|
||||
|
||||
var activeSubflow = null;
|
||||
var activeNodes = [];
|
||||
@@ -196,40 +202,54 @@ RED.view = (function() {
|
||||
.attr('height', space_height)
|
||||
.attr('fill','#fff');
|
||||
|
||||
//var gridScale = d3.scale.linear().range([0,2000]).domain([0,2000]);
|
||||
//var grid = vis.append('g');
|
||||
//
|
||||
//grid.selectAll("line.horizontal").data(gridScale.ticks(100)).enter()
|
||||
// .append("line")
|
||||
// .attr(
|
||||
// {
|
||||
// "class":"horizontal",
|
||||
// "x1" : 0,
|
||||
// "x2" : 2000,
|
||||
// "y1" : function(d){ return gridScale(d);},
|
||||
// "y2" : function(d){ return gridScale(d);},
|
||||
// "fill" : "none",
|
||||
// "shape-rendering" : "crispEdges",
|
||||
// "stroke" : "#eee",
|
||||
// "stroke-width" : "1px"
|
||||
// });
|
||||
//grid.selectAll("line.vertical").data(gridScale.ticks(100)).enter()
|
||||
// .append("line")
|
||||
// .attr(
|
||||
// {
|
||||
// "class":"vertical",
|
||||
// "y1" : 0,
|
||||
// "y2" : 2000,
|
||||
// "x1" : function(d){ return gridScale(d);},
|
||||
// "x2" : function(d){ return gridScale(d);},
|
||||
// "fill" : "none",
|
||||
// "shape-rendering" : "crispEdges",
|
||||
// "stroke" : "#eee",
|
||||
// "stroke-width" : "1px"
|
||||
// });
|
||||
var gridScale = d3.scale.linear().range([0,space_width]).domain([0,space_width]);
|
||||
var grid = vis.append('g');
|
||||
|
||||
grid.selectAll("line.horizontal").data(gridScale.ticks(space_width/gridSize)).enter()
|
||||
.append("line")
|
||||
.attr(
|
||||
{
|
||||
"class":"horizontal",
|
||||
"x1" : 0,
|
||||
"x2" : space_width,
|
||||
"y1" : function(d){ return gridScale(d);},
|
||||
"y2" : function(d){ return gridScale(d);},
|
||||
"fill" : "none",
|
||||
"shape-rendering" : "crispEdges",
|
||||
"stroke" : "#eee",
|
||||
"stroke-width" : "1px"
|
||||
});
|
||||
grid.selectAll("line.vertical").data(gridScale.ticks(space_width/gridSize)).enter()
|
||||
.append("line")
|
||||
.attr(
|
||||
{
|
||||
"class":"vertical",
|
||||
"y1" : 0,
|
||||
"y2" : space_width,
|
||||
"x1" : function(d){ return gridScale(d);},
|
||||
"x2" : function(d){ return gridScale(d);},
|
||||
"fill" : "none",
|
||||
"shape-rendering" : "crispEdges",
|
||||
"stroke" : "#eee",
|
||||
"stroke-width" : "1px"
|
||||
});
|
||||
grid.style("visibility","hidden");
|
||||
|
||||
var drag_line = vis.append("svg:path").attr("class", "drag_line");
|
||||
var dragGroup = vis.append('g');
|
||||
var drag_lines = [];
|
||||
|
||||
function showDragLines(nodes) {
|
||||
for (var i=0;i<nodes.length;i++) {
|
||||
var node = nodes[i];
|
||||
node.el = dragGroup.append("svg:path").attr("class", "drag_line");
|
||||
drag_lines.push(node);
|
||||
}
|
||||
}
|
||||
function hideDragLines() {
|
||||
while(drag_lines.length) {
|
||||
(drag_lines.pop()).el.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function updateActiveNodes() {
|
||||
var activeWorkspace = RED.workspaces.active();
|
||||
@@ -300,7 +320,6 @@ RED.view = (function() {
|
||||
drop: function( event, ui ) {
|
||||
d3.event = event;
|
||||
var selected_tool = ui.draggable[0].type;
|
||||
|
||||
var m = /^subflow:(.+)$/.exec(selected_tool);
|
||||
|
||||
if (activeSubflow && m) {
|
||||
@@ -313,16 +332,9 @@ RED.view = (function() {
|
||||
RED.notify(RED._("notification.error",{message: RED._("notification.errors.cannotAddCircularReference")}),"error");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var mousePos = d3.touches(this)[0]||d3.mouse(this);
|
||||
mousePos[1] += this.scrollTop;
|
||||
mousePos[0] += this.scrollLeft;
|
||||
mousePos[1] /= scaleFactor;
|
||||
mousePos[0] /= scaleFactor;
|
||||
|
||||
var nn = { id:(1+Math.random()*4294967295).toString(16),x: mousePos[0],y:mousePos[1],w:node_width,z:RED.workspaces.active()};
|
||||
var nn = { id:(1+Math.random()*4294967295).toString(16),z:RED.workspaces.active()};
|
||||
|
||||
nn.type = selected_tool;
|
||||
nn._def = RED.nodes.getType(nn.type);
|
||||
@@ -338,7 +350,11 @@ RED.view = (function() {
|
||||
}
|
||||
|
||||
if (nn._def.onadd) {
|
||||
nn._def.onadd.call(nn);
|
||||
try {
|
||||
nn._def.onadd.call(nn);
|
||||
} catch(err) {
|
||||
console.log("onadd:",err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var subflow = RED.nodes.subflow(m[1]);
|
||||
@@ -347,7 +363,10 @@ RED.view = (function() {
|
||||
}
|
||||
|
||||
nn.changed = true;
|
||||
|
||||
nn.w = node_width;
|
||||
nn.h = Math.max(node_height,(nn.outputs||0) * 15);
|
||||
|
||||
var historyEvent = {
|
||||
t:'add',
|
||||
nodes:[nn.id],
|
||||
@@ -364,6 +383,41 @@ RED.view = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0));
|
||||
var mousePos = d3.touches(this)[0]||d3.mouse(this);
|
||||
|
||||
mousePos[1] += this.scrollTop + ((nn.h/2)-helperOffset[1]);
|
||||
mousePos[0] += this.scrollLeft + ((nn.w/2)-helperOffset[0]);
|
||||
mousePos[1] /= scaleFactor;
|
||||
mousePos[0] /= scaleFactor;
|
||||
|
||||
if (snapGrid) {
|
||||
mousePos[0] = gridSize*(Math.ceil(mousePos[0]/gridSize));
|
||||
mousePos[1] = gridSize*(Math.ceil(mousePos[1]/gridSize));
|
||||
}
|
||||
nn.x = mousePos[0];
|
||||
nn.y = mousePos[1];
|
||||
|
||||
var spliceLink = $(ui.helper).data('splice');
|
||||
if (spliceLink) {
|
||||
// TODO: DRY - droppable/nodeMouseDown/canvasMouseUp
|
||||
RED.nodes.removeLink(spliceLink);
|
||||
var link1 = {
|
||||
source:spliceLink.source,
|
||||
sourcePort:spliceLink.sourcePort,
|
||||
target: nn
|
||||
};
|
||||
var link2 = {
|
||||
source:nn,
|
||||
sourcePort:0,
|
||||
target: spliceLink.target
|
||||
};
|
||||
RED.nodes.addLink(link1);
|
||||
RED.nodes.addLink(link2);
|
||||
historyEvent.links = [link1,link2];
|
||||
historyEvent.removedLinks = [spliceLink];
|
||||
}
|
||||
|
||||
RED.history.push(historyEvent);
|
||||
RED.nodes.add(nn);
|
||||
RED.editor.validateNode(nn);
|
||||
@@ -420,6 +474,8 @@ RED.view = (function() {
|
||||
}
|
||||
|
||||
function canvasMouseMove() {
|
||||
var i;
|
||||
var node;
|
||||
mouse_position = d3.touches(this)[0]||d3.mouse(this);
|
||||
// Prevent touch scrolling...
|
||||
//if (d3.touches(this)[0]) {
|
||||
@@ -466,36 +522,73 @@ RED.view = (function() {
|
||||
var mousePos;
|
||||
if (mouse_mode == RED.state.JOINING) {
|
||||
// update drag line
|
||||
drag_line.attr("class", "drag_line");
|
||||
mousePos = mouse_position;
|
||||
var numOutputs = (mousedown_port_type === 0)?(mousedown_node.outputs || 1):1;
|
||||
var sourcePort = mousedown_port_index;
|
||||
var portY = -((numOutputs-1)/2)*13 +13*sourcePort;
|
||||
|
||||
var sc = (mousedown_port_type === 0)?1:-1;
|
||||
|
||||
var dy = mousePos[1]-(mousedown_node.y+portY);
|
||||
var dx = mousePos[0]-(mousedown_node.x+sc*mousedown_node.w/2);
|
||||
var delta = Math.sqrt(dy*dy+dx*dx);
|
||||
var scale = lineCurveScale;
|
||||
var scaleY = 0;
|
||||
|
||||
if (delta < node_width) {
|
||||
scale = 0.75-0.75*((node_width-delta)/node_width);
|
||||
}
|
||||
if (dx*sc < 0) {
|
||||
scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width));
|
||||
if (Math.abs(dy) < 3*node_height) {
|
||||
scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ;
|
||||
if (drag_lines.length === 0) {
|
||||
if (d3.event.shiftKey) {
|
||||
// Get all the wires we need to detach.
|
||||
var links = [];
|
||||
var filter;
|
||||
if (mousedown_port_type === 0) {
|
||||
filter = {
|
||||
source:mousedown_node,
|
||||
sourcePort: mousedown_port_index
|
||||
}
|
||||
} else {
|
||||
filter = {
|
||||
target: mousedown_node
|
||||
}
|
||||
}
|
||||
var existingLinks = RED.nodes.filterLinks(filter);
|
||||
for (i=0;i<existingLinks.length;i++) {
|
||||
var link = existingLinks[i];
|
||||
RED.nodes.removeLink(link);
|
||||
links.push({
|
||||
link:link,
|
||||
node: (mousedown_port_type===0)?link.target:link.source,
|
||||
port: (mousedown_port_type===0)?0:link.sourcePort,
|
||||
portType: (mousedown_port_type===0)?1:0
|
||||
})
|
||||
}
|
||||
showDragLines(links);
|
||||
mouse_mode = 0;
|
||||
updateActiveNodes();
|
||||
redraw();
|
||||
mouse_mode = RED.state.JOINING;
|
||||
} else {
|
||||
showDragLines([{node:mousedown_node,port:mousedown_port_index,portType:mousedown_port_type}]);
|
||||
}
|
||||
}
|
||||
mousePos = mouse_position;
|
||||
for (i=0;i<drag_lines.length;i++) {
|
||||
var drag_line = drag_lines[i];
|
||||
var numOutputs = (drag_line.portType === 0)?(drag_line.node.outputs || 1):1;
|
||||
var sourcePort = drag_line.port;
|
||||
var portY = -((numOutputs-1)/2)*13 +13*sourcePort;
|
||||
|
||||
drag_line.attr("d",
|
||||
"M "+(mousedown_node.x+sc*mousedown_node.w/2)+" "+(mousedown_node.y+portY)+
|
||||
" C "+(mousedown_node.x+sc*(mousedown_node.w/2+node_width*scale))+" "+(mousedown_node.y+portY+scaleY*node_height)+" "+
|
||||
(mousePos[0]-sc*(scale)*node_width)+" "+(mousePos[1]-scaleY*node_height)+" "+
|
||||
mousePos[0]+" "+mousePos[1]
|
||||
);
|
||||
var sc = (drag_line.portType === 0)?1:-1;
|
||||
|
||||
var dy = mousePos[1]-(drag_line.node.y+portY);
|
||||
var dx = mousePos[0]-(drag_line.node.x+sc*drag_line.node.w/2);
|
||||
var delta = Math.sqrt(dy*dy+dx*dx);
|
||||
var scale = lineCurveScale;
|
||||
var scaleY = 0;
|
||||
|
||||
if (delta < node_width) {
|
||||
scale = 0.75-0.75*((node_width-delta)/node_width);
|
||||
}
|
||||
if (dx*sc < 0) {
|
||||
scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width));
|
||||
if (Math.abs(dy) < 3*node_height) {
|
||||
scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ;
|
||||
}
|
||||
}
|
||||
|
||||
drag_line.el.attr("d",
|
||||
"M "+(drag_line.node.x+sc*drag_line.node.w/2)+" "+(drag_line.node.y+portY)+
|
||||
" C "+(drag_line.node.x+sc*(drag_line.node.w/2+node_width*scale))+" "+(drag_line.node.y+portY+scaleY*node_height)+" "+
|
||||
(mousePos[0]-sc*(scale)*node_width)+" "+(mousePos[1]-scaleY*node_height)+" "+
|
||||
mousePos[0]+" "+mousePos[1]
|
||||
);
|
||||
}
|
||||
d3.event.preventDefault();
|
||||
} else if (mouse_mode == RED.state.MOVING) {
|
||||
mousePos = d3.mouse(document.body);
|
||||
@@ -506,11 +599,17 @@ RED.view = (function() {
|
||||
if (d > 3) {
|
||||
mouse_mode = RED.state.MOVING_ACTIVE;
|
||||
clickElapsed = 0;
|
||||
spliceActive = false;
|
||||
if (moving_set.length === 1) {
|
||||
node = moving_set[0];
|
||||
spliceActive = node.n._def.inputs > 0 &&
|
||||
node.n._def.outputs > 0 &&
|
||||
RED.nodes.filterLinks({ source: node.n }).length === 0 &&
|
||||
RED.nodes.filterLinks({ target: node.n }).length === 0;
|
||||
}
|
||||
}
|
||||
} else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING) {
|
||||
mousePos = mouse_position;
|
||||
var node;
|
||||
var i;
|
||||
var minX = 0;
|
||||
var minY = 0;
|
||||
for (var n = 0; n<moving_set.length; n++) {
|
||||
@@ -532,11 +631,11 @@ RED.view = (function() {
|
||||
node.n.y -= minY;
|
||||
}
|
||||
}
|
||||
if (d3.event.shiftKey && moving_set.length > 0) {
|
||||
var gridOffset = [0,0];
|
||||
if (snapGrid != d3.event.shiftKey && moving_set.length > 0) {
|
||||
var gridOffset = [0,0];
|
||||
node = moving_set[0];
|
||||
gridOffset[0] = node.n.x-(20*Math.floor((node.n.x-node.n.w/2)/20)+node.n.w/2);
|
||||
gridOffset[1] = node.n.y-(20*Math.floor(node.n.y/20));
|
||||
gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
|
||||
gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize));
|
||||
if (gridOffset[0] !== 0 || gridOffset[1] !== 0) {
|
||||
for (i = 0; i<moving_set.length; i++) {
|
||||
node = moving_set[i];
|
||||
@@ -548,6 +647,57 @@ RED.view = (function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING) && moving_set.length === 1) {
|
||||
node = moving_set[0];
|
||||
if (spliceActive) {
|
||||
if (!spliceTimer) {
|
||||
spliceTimer = setTimeout(function() {
|
||||
var nodes = [];
|
||||
var bestDistance = Infinity;
|
||||
var bestLink = null;
|
||||
var mouseX = mousePos[0];
|
||||
var mouseY = mousePos[1];
|
||||
if (outer[0][0].getIntersectionList) {
|
||||
var svgRect = outer[0][0].createSVGRect();
|
||||
svgRect.x = mouseX;
|
||||
svgRect.y = mouseY;
|
||||
svgRect.width = 1;
|
||||
svgRect.height = 1;
|
||||
nodes = outer[0][0].getIntersectionList(svgRect, outer[0][0]);
|
||||
} else {
|
||||
// Firefox doesn't do getIntersectionList and that
|
||||
// makes us sad
|
||||
nodes = RED.view.getLinksAtPoint(mouseX,mouseY);
|
||||
}
|
||||
for (var i=0;i<nodes.length;i++) {
|
||||
if (d3.select(nodes[i]).classed('link_background')) {
|
||||
var length = nodes[i].getTotalLength();
|
||||
for (var j=0;j<length;j+=10) {
|
||||
var p = nodes[i].getPointAtLength(j);
|
||||
var d2 = ((p.x-mouseX)*(p.x-mouseX))+((p.y-mouseY)*(p.y-mouseY));
|
||||
if (d2 < 200 && d2 < bestDistance) {
|
||||
bestDistance = d2;
|
||||
bestLink = nodes[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (activeSpliceLink && activeSpliceLink !== bestLink) {
|
||||
d3.select(activeSpliceLink.parentNode).classed('link_splice',false);
|
||||
}
|
||||
if (bestLink) {
|
||||
d3.select(bestLink.parentNode).classed('link_splice',true)
|
||||
} else {
|
||||
d3.select('.link_splice').classed('link_splice',false);
|
||||
}
|
||||
activeSpliceLink = bestLink;
|
||||
spliceTimer = null;
|
||||
},100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
if (mouse_mode !== 0) {
|
||||
redraw();
|
||||
@@ -555,8 +705,22 @@ RED.view = (function() {
|
||||
}
|
||||
|
||||
function canvasMouseUp() {
|
||||
var i;
|
||||
var historyEvent;
|
||||
if (mousedown_node && mouse_mode == RED.state.JOINING) {
|
||||
drag_line.attr("class", "drag_line_hidden");
|
||||
var removedLinks = [];
|
||||
for (i=0;i<drag_lines.length;i++) {
|
||||
if (drag_lines[i].link) {
|
||||
removedLinks.push(drag_lines[i].link)
|
||||
}
|
||||
}
|
||||
historyEvent = {
|
||||
t:'delete',
|
||||
links: removedLinks,
|
||||
dirty:RED.nodes.dirty()
|
||||
};
|
||||
RED.history.push(historyEvent);
|
||||
hideDragLines();
|
||||
}
|
||||
if (lasso) {
|
||||
var x = parseInt(lasso.attr("x"));
|
||||
@@ -594,7 +758,7 @@ RED.view = (function() {
|
||||
updateSelection();
|
||||
lasso.remove();
|
||||
lasso = null;
|
||||
} else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey ) {
|
||||
} else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey&& !d3.event.metaKey ) {
|
||||
clearSelection();
|
||||
updateSelection();
|
||||
}
|
||||
@@ -604,11 +768,33 @@ RED.view = (function() {
|
||||
for (var j=0;j<moving_set.length;j++) {
|
||||
ns.push({n:moving_set[j].n,ox:moving_set[j].ox,oy:moving_set[j].oy});
|
||||
}
|
||||
RED.history.push({t:'move',nodes:ns,dirty:RED.nodes.dirty()});
|
||||
historyEvent = {t:'move',nodes:ns,dirty:RED.nodes.dirty()};
|
||||
if (activeSpliceLink) {
|
||||
// TODO: DRY - droppable/nodeMouseDown/canvasMouseUp
|
||||
var spliceLink = d3.select(activeSpliceLink).data()[0];
|
||||
RED.nodes.removeLink(spliceLink);
|
||||
var link1 = {
|
||||
source:spliceLink.source,
|
||||
sourcePort:spliceLink.sourcePort,
|
||||
target: moving_set[0].n
|
||||
};
|
||||
var link2 = {
|
||||
source:moving_set[0].n,
|
||||
sourcePort:0,
|
||||
target: spliceLink.target
|
||||
};
|
||||
RED.nodes.addLink(link1);
|
||||
RED.nodes.addLink(link2);
|
||||
historyEvent.links = [link1,link2];
|
||||
historyEvent.removedLinks = [spliceLink];
|
||||
updateActiveNodes();
|
||||
}
|
||||
RED.nodes.dirty(true);
|
||||
RED.history.push(historyEvent);
|
||||
}
|
||||
}
|
||||
if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE) {
|
||||
for (var i=0;i<moving_set.length;i++) {
|
||||
for (i=0;i<moving_set.length;i++) {
|
||||
delete moving_set[i].ox;
|
||||
delete moving_set[i].oy;
|
||||
}
|
||||
@@ -724,6 +910,7 @@ RED.view = (function() {
|
||||
delete moving_set[i].oy;
|
||||
}
|
||||
RED.history.push({t:'move',nodes:ns,dirty:RED.nodes.dirty()});
|
||||
RED.nodes.dirty(true);
|
||||
}
|
||||
function moveSelection(dx,dy) {
|
||||
var minX = 0;
|
||||
@@ -876,6 +1063,13 @@ RED.view = (function() {
|
||||
mousedown_link = null;
|
||||
mouse_mode = 0;
|
||||
mousedown_port_type = 0;
|
||||
activeSpliceLink = null;
|
||||
spliceActive = false;
|
||||
d3.select('.link_splice').classed('link_splice',false);
|
||||
if (spliceTimer) {
|
||||
clearTimeout(spliceTimer);
|
||||
spliceTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function portMouseDown(d,portType,portIndex) {
|
||||
@@ -892,46 +1086,59 @@ RED.view = (function() {
|
||||
}
|
||||
|
||||
function portMouseUp(d,portType,portIndex) {
|
||||
var i;
|
||||
document.body.style.cursor = "";
|
||||
if (mouse_mode == RED.state.JOINING && mousedown_node) {
|
||||
if (mouse_mode == RED.state.JOINING && drag_lines.length > 0) {
|
||||
if (typeof TouchEvent != "undefined" && d3.event instanceof TouchEvent) {
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.z == RED.workspaces.active()) {
|
||||
var hw = n.w/2;
|
||||
var hh = n.h/2;
|
||||
if (n.x-hw<mouse_position[0] && n.x+hw> mouse_position[0] &&
|
||||
n.y-hh<mouse_position[1] && n.y+hh>mouse_position[1]) {
|
||||
mouseup_node = n;
|
||||
portType = mouseup_node.inputs>0?1:0;
|
||||
portIndex = 0;
|
||||
}
|
||||
if (n.z == RED.workspaces.active()) {
|
||||
var hw = n.w/2;
|
||||
var hh = n.h/2;
|
||||
if (n.x-hw<mouse_position[0] && n.x+hw> mouse_position[0] &&
|
||||
n.y-hh<mouse_position[1] && n.y+hh>mouse_position[1]) {
|
||||
mouseup_node = n;
|
||||
portType = mouseup_node.inputs>0?1:0;
|
||||
portIndex = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mouseup_node = d;
|
||||
}
|
||||
if (portType == mousedown_port_type || mouseup_node === mousedown_node) {
|
||||
drag_line.attr("class", "drag_line_hidden");
|
||||
resetMouseVars();
|
||||
return;
|
||||
var addedLinks = [];
|
||||
var removedLinks = [];
|
||||
|
||||
for (i=0;i<drag_lines.length;i++) {
|
||||
if (drag_lines[i].link) {
|
||||
removedLinks.push(drag_lines[i].link)
|
||||
}
|
||||
}
|
||||
var src,dst,src_port;
|
||||
if (mousedown_port_type === 0) {
|
||||
src = mousedown_node;
|
||||
src_port = mousedown_port_index;
|
||||
dst = mouseup_node;
|
||||
} else if (mousedown_port_type == 1) {
|
||||
src = mouseup_node;
|
||||
dst = mousedown_node;
|
||||
src_port = portIndex;
|
||||
for (i=0;i<drag_lines.length;i++) {
|
||||
if (portType != drag_lines[i].portType && mouseup_node !== drag_lines[i].node) {
|
||||
var drag_line = drag_lines[i];
|
||||
var src,dst,src_port;
|
||||
if (drag_line.portType === 0) {
|
||||
src = drag_line.node;
|
||||
src_port = drag_line.port;
|
||||
dst = mouseup_node;
|
||||
} else if (drag_line.portType == 1) {
|
||||
src = mouseup_node;
|
||||
dst = drag_line.node;
|
||||
src_port = portIndex;
|
||||
}
|
||||
var existingLink = RED.nodes.filterLinks({source:src,target:dst,sourcePort: src_port}).length !== 0;
|
||||
if (!existingLink) {
|
||||
var link = {source: src, sourcePort:src_port, target: dst};
|
||||
RED.nodes.addLink(link);
|
||||
addedLinks.push(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
var existingLink = RED.nodes.filterLinks({source:src,target:dst,sourcePort: src_port}).length !== 0;
|
||||
if (!existingLink) {
|
||||
var link = {source: src, sourcePort:src_port, target: dst};
|
||||
RED.nodes.addLink(link);
|
||||
if (addedLinks.length > 0 || removedLinks.length > 0) {
|
||||
var historyEvent = {
|
||||
t:'add',
|
||||
links:[link],
|
||||
links:addedLinks,
|
||||
removedLinks: removedLinks,
|
||||
dirty:RED.nodes.dirty()
|
||||
};
|
||||
if (activeSubflow) {
|
||||
@@ -947,8 +1154,9 @@ RED.view = (function() {
|
||||
RED.history.push(historyEvent);
|
||||
updateActiveNodes();
|
||||
RED.nodes.dirty(true);
|
||||
} else {
|
||||
}
|
||||
resetMouseVars();
|
||||
hideDragLines();
|
||||
selected_link = null;
|
||||
redraw();
|
||||
}
|
||||
@@ -977,6 +1185,29 @@ RED.view = (function() {
|
||||
//RED.touch.radialMenu.show(d3.select(this),pos);
|
||||
if (mouse_mode == RED.state.IMPORT_DRAGGING) {
|
||||
RED.keyboard.remove(/* ESCAPE */ 27);
|
||||
|
||||
if (activeSpliceLink) {
|
||||
// TODO: DRY - droppable/nodeMouseDown/canvasMouseUp
|
||||
var spliceLink = d3.select(activeSpliceLink).data()[0];
|
||||
RED.nodes.removeLink(spliceLink);
|
||||
var link1 = {
|
||||
source:spliceLink.source,
|
||||
sourcePort:spliceLink.sourcePort,
|
||||
target: moving_set[0].n
|
||||
};
|
||||
var link2 = {
|
||||
source:moving_set[0].n,
|
||||
sourcePort:0,
|
||||
target: spliceLink.target
|
||||
};
|
||||
RED.nodes.addLink(link1);
|
||||
RED.nodes.addLink(link2);
|
||||
var historyEvent = RED.history.peek();
|
||||
historyEvent.links = [link1,link2];
|
||||
historyEvent.removedLinks = [spliceLink];
|
||||
updateActiveNodes();
|
||||
}
|
||||
|
||||
updateSelection();
|
||||
RED.nodes.dirty(true);
|
||||
redraw();
|
||||
@@ -994,10 +1225,10 @@ RED.view = (function() {
|
||||
|
||||
var i;
|
||||
|
||||
if (d.selected && d3.event.ctrlKey) {
|
||||
d.selected = false;
|
||||
if (d.selected && (d3.event.ctrlKey||d3.event.metaKey)) {
|
||||
mousedown_node.selected = false;
|
||||
for (i=0;i<moving_set.length;i+=1) {
|
||||
if (moving_set[i].n === d) {
|
||||
if (moving_set[i].n === mousedown_node) {
|
||||
moving_set.splice(i,1);
|
||||
break;
|
||||
}
|
||||
@@ -1012,7 +1243,7 @@ RED.view = (function() {
|
||||
moving_set.push({n:cnodes[n]});
|
||||
}
|
||||
} else if (!d.selected) {
|
||||
if (!d3.event.ctrlKey) {
|
||||
if (!d3.event.ctrlKey && !d3.event.metaKey) {
|
||||
clearSelection();
|
||||
}
|
||||
mousedown_node.selected = true;
|
||||
@@ -1049,7 +1280,11 @@ RED.view = (function() {
|
||||
d.dirty = true;
|
||||
}
|
||||
if (d._def.button.onclick) {
|
||||
d._def.button.onclick.call(d);
|
||||
try {
|
||||
d._def.button.onclick.call(d);
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+d.type+".onclick",err);
|
||||
}
|
||||
}
|
||||
if (d.dirty) {
|
||||
redraw();
|
||||
@@ -1123,7 +1358,7 @@ RED.view = (function() {
|
||||
.on("touchstart", function(d,i){portMouseDown(d,1,0);} )
|
||||
.on("mouseup", function(d,i){portMouseUp(d,1,0);})
|
||||
.on("touchend",function(d,i){portMouseUp(d,1,0);} )
|
||||
.on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type !== 0 ));})
|
||||
.on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || (drag_lines.length > 0 && drag_lines[0].portType !== 1)));})
|
||||
.on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);});
|
||||
|
||||
outGroup.append("svg:text").attr('class','port_label').attr('x',20).attr('y',8).style("font-size","10px").text("output");
|
||||
@@ -1166,7 +1401,7 @@ RED.view = (function() {
|
||||
.on("touchstart", function(d,i){portMouseDown(d,0,i);} )
|
||||
.on("mouseup", function(d,i){portMouseUp(d,0,i);})
|
||||
.on("touchend",function(d,i){portMouseUp(d,0,i);} )
|
||||
.on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type !== 0 ));})
|
||||
.on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || (drag_lines.length > 0 && drag_lines[0].portType !== 0) ));})
|
||||
.on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);});
|
||||
inGroup.append("svg:text").attr('class','port_label').attr('x',18).attr('y',20).style("font-size","10px").text("input");
|
||||
|
||||
@@ -1204,8 +1439,13 @@ RED.view = (function() {
|
||||
var node = d3.select(this);
|
||||
node.attr("id",d.id);
|
||||
var l = d._def.label;
|
||||
l = (typeof l === "function" ? l.call(d) : l)||"";
|
||||
d.w = Math.max(node_width,calculateTextWidth(l, "node_label", 50)+(d._def.inputs>0?7:0) );
|
||||
try {
|
||||
l = (typeof l === "function" ? l.call(d) : l)||"";
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+d.type+".label",err);
|
||||
l = d.type;
|
||||
}
|
||||
d.w = Math.max(node_width,gridSize*(Math.ceil((calculateTextWidth(l, "node_label", 50)+(d._def.inputs>0?7:0))/gridSize)) );
|
||||
d.h = Math.max(node_height,(d.outputs||0) * 15);
|
||||
|
||||
if (d._def.badge) {
|
||||
@@ -1387,9 +1627,16 @@ RED.view = (function() {
|
||||
//if (d.x < -50) deleteSelection(); // Delete nodes if dragged back to palette
|
||||
if (d.resize) {
|
||||
var l = d._def.label;
|
||||
l = (typeof l === "function" ? l.call(d) : l)||"";
|
||||
d.w = Math.max(node_width,calculateTextWidth(l, "node_label", 50)+(d._def.inputs>0?7:0) );
|
||||
try {
|
||||
l = (typeof l === "function" ? l.call(d) : l)||"";
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+d.type+".label",err);
|
||||
l = d.type;
|
||||
}
|
||||
var ow = d.w;
|
||||
d.w = Math.max(node_width,gridSize*(Math.ceil((calculateTextWidth(l, "node_label", 50)+(d._def.inputs>0?7:0))/gridSize)) );
|
||||
d.h = Math.max(node_height,(d.outputs||0) * 15);
|
||||
d.x += (d.w-ow)/2;
|
||||
d.resize = false;
|
||||
}
|
||||
var thisNode = d3.select(this);
|
||||
@@ -1423,7 +1670,7 @@ RED.view = (function() {
|
||||
.on("touchstart",function(d){portMouseDown(d,1,0);})
|
||||
.on("mouseup",function(d){portMouseUp(d,1,0);} )
|
||||
.on("touchend",function(d){portMouseUp(d,1,0);} )
|
||||
.on("mouseover",function(d) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type != 1 ));})
|
||||
.on("mouseover",function(d) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || (drag_lines.length > 0 && drag_lines[0].portType !== 1) ));})
|
||||
.on("mouseout",function(d) { var port = d3.select(this); port.classed("port_hovered",false);})
|
||||
}
|
||||
|
||||
@@ -1438,7 +1685,7 @@ RED.view = (function() {
|
||||
.on("touchstart",(function(){var node = d; return function(d,i){portMouseDown(node,0,i);}})() )
|
||||
.on("mouseup",(function(){var node = d; return function(d,i){portMouseUp(node,0,i);}})() )
|
||||
.on("touchend",(function(){var node = d; return function(d,i){portMouseUp(node,0,i);}})() )
|
||||
.on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type !== 0 ));})
|
||||
.on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || (drag_lines.length > 0 && drag_lines[0].portType !== 0) ));})
|
||||
.on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);});
|
||||
|
||||
d._ports.exit().remove();
|
||||
@@ -1453,20 +1700,33 @@ RED.view = (function() {
|
||||
});
|
||||
}
|
||||
thisNode.selectAll('text.node_label').text(function(d,i){
|
||||
var l = "";
|
||||
if (d._def.label) {
|
||||
if (typeof d._def.label == "function") {
|
||||
return d._def.label.call(d);
|
||||
} else {
|
||||
return d._def.label;
|
||||
l = d._def.label;
|
||||
try {
|
||||
l = (typeof l === "function" ? l.call(d) : l)||"";
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+d.type+".label",err);
|
||||
l = d.type;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
return l;
|
||||
})
|
||||
.attr('y', function(d){return (d.h/2)-1;})
|
||||
.attr('class',function(d){
|
||||
var s = "";
|
||||
if (d._def.labelStyle) {
|
||||
s = d._def.labelStyle;
|
||||
try {
|
||||
s = (typeof s === "function" ? s.call(d) : s)||"";
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+d.type+".labelStyle",err);
|
||||
s = "";
|
||||
}
|
||||
s = " "+s;
|
||||
}
|
||||
return 'node_label'+
|
||||
(d._def.align?' node_label_'+d._def.align:'')+
|
||||
(d._def.labelStyle?' '+(typeof d._def.labelStyle == "function" ? d._def.labelStyle.call(d):d._def.labelStyle):'') ;
|
||||
(d._def.align?' node_label_'+d._def.align:'')+s;
|
||||
});
|
||||
|
||||
if (d._def.icon) {
|
||||
@@ -1474,7 +1734,12 @@ RED.view = (function() {
|
||||
var current_url = icon.attr("xlink:href");
|
||||
var icon_url;
|
||||
if (typeof d._def.icon == "function") {
|
||||
icon_url = d._def.icon.call(d);
|
||||
try {
|
||||
icon_url = d._def.icon.call(d);
|
||||
} catch(err) {
|
||||
console.log("icon",err);
|
||||
icon_url = "arrow-in.png";
|
||||
}
|
||||
} else {
|
||||
icon_url = d._def.icon;
|
||||
}
|
||||
@@ -1538,7 +1803,12 @@ RED.view = (function() {
|
||||
thisNode.selectAll('text.node_badge_label').text(function(d,i) {
|
||||
if (d._def.badge) {
|
||||
if (typeof d._def.badge == "function") {
|
||||
return d._def.badge.call(d);
|
||||
try {
|
||||
return d._def.badge.call(d);
|
||||
} catch(err) {
|
||||
console.log("Definition error: "+d.type+".badge",err);
|
||||
return "";
|
||||
}
|
||||
} else {
|
||||
return d._def.badge;
|
||||
}
|
||||
@@ -1618,7 +1888,7 @@ RED.view = (function() {
|
||||
touchStartTime = null;
|
||||
showTouchMenu(obj,pos);
|
||||
},touchLongPressTimeout);
|
||||
});
|
||||
})
|
||||
l.append("svg:path").attr("class","link_outline link_path");
|
||||
l.append("svg:path").attr("class","link_line link_path")
|
||||
.classed("link_subflow", function(d) { return activeSubflow && (d.source.type === "subflow" || d.target.type === "subflow") });
|
||||
@@ -1746,6 +2016,12 @@ RED.view = (function() {
|
||||
}
|
||||
if (!touchImport) {
|
||||
mouse_mode = RED.state.IMPORT_DRAGGING;
|
||||
spliceActive = false;
|
||||
if (new_ms.length === 1) {
|
||||
node = new_ms[0];
|
||||
spliceActive = node.n._def.inputs > 0 &&
|
||||
node.n._def.outputs > 0;
|
||||
}
|
||||
}
|
||||
|
||||
RED.keyboard.add(/* ESCAPE */ 27,function(){
|
||||
@@ -1844,6 +2120,31 @@ RED.view = (function() {
|
||||
selection.link = selected_link;
|
||||
}
|
||||
return selection;
|
||||
},
|
||||
toggleShowGrid: function(state) {
|
||||
if (state) {
|
||||
grid.style("visibility","visible");
|
||||
} else {
|
||||
grid.style("visibility","hidden");
|
||||
}
|
||||
},
|
||||
toggleSnapGrid: function(state) {
|
||||
snapGrid = state;
|
||||
redraw();
|
||||
},
|
||||
scale: function() {
|
||||
return scaleFactor;
|
||||
},
|
||||
getLinksAtPoint: function(x,y) {
|
||||
var result = [];
|
||||
var links = outer.selectAll(".link_background")[0];
|
||||
for (var i=0;i<links.length;i++) {
|
||||
var bb = links[i].getBBox();
|
||||
if (x >= bb.x && y >= bb.y && x <= bb.x+bb.width && y <= bb.y+bb.height) {
|
||||
result.push(links[i])
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -28,7 +28,6 @@ RED.workspaces = (function() {
|
||||
var tabId = RED.nodes.id();
|
||||
do {
|
||||
workspaceIndex += 1;
|
||||
//TODO: nls of Sheet
|
||||
} while($("#workspace-tabs a[title='"+RED._('workspace.defaultName',{number:workspaceIndex})+"']").size() !== 0);
|
||||
|
||||
ws = {type:"tab",id:tabId,label:RED._('workspace.defaultName',{number:workspaceIndex})};
|
||||
@@ -55,6 +54,7 @@ RED.workspaces = (function() {
|
||||
historyEvent.workspaces = [ws];
|
||||
RED.history.push(historyEvent);
|
||||
RED.nodes.dirty(true);
|
||||
RED.sidebar.config.refresh();
|
||||
} else {
|
||||
$( "#node-dialog-delete-workspace" ).dialog('option','workspace',ws);
|
||||
$( "#node-dialog-delete-workspace-content" ).text(RED._("workspace.delete",{label:ws.label}));
|
||||
@@ -141,8 +141,8 @@ RED.workspaces = (function() {
|
||||
if (workspace.label != label) {
|
||||
workspace_tabs.renameTab(workspace.id,label);
|
||||
RED.nodes.dirty(true);
|
||||
RED.sidebar.config.refresh();
|
||||
$("#menu-item-workspace-menu-"+workspace.id.replace(".","-")).text(label);
|
||||
// TODO: update entry in menu
|
||||
}
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
|
||||
@@ -43,3 +43,8 @@ $workspace-button-color-disabled: #ccc;
|
||||
$workspace-button-color-focus: #999;
|
||||
$workspace-button-color-hover: #666;
|
||||
$workspace-button-color-active: #666;
|
||||
$workspace-button-color-selected: #AAA;
|
||||
|
||||
$typedInput-button-background: #efefef;
|
||||
$typedInput-button-background-hover: #ddd;
|
||||
$typedInput-button-background-active: #e3e3e3;
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
|
||||
.drag_line {
|
||||
stroke: $node-selected-color;
|
||||
stroke-width: 4;
|
||||
stroke-width: 3;
|
||||
fill: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -225,6 +225,9 @@
|
||||
cursor: crosshair;
|
||||
fill: none;
|
||||
}
|
||||
.link_splice > .link_line {
|
||||
stroke-dasharray: 15,8;
|
||||
}
|
||||
|
||||
g.link_selected path.link_line {
|
||||
stroke: $node-selected-color;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2015 IBM Corp.
|
||||
* Copyright 2015, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,9 @@
|
||||
font-size: 14px !important;
|
||||
font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif !important;
|
||||
}
|
||||
.ui-widget input {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* jQuery Theme overrides */
|
||||
.ui-tabs .ui-tabs-panel {
|
||||
@@ -73,3 +76,23 @@
|
||||
.ui-dialog .ui-dialog-buttonpane {
|
||||
padding: .3em 1em .5em 1em;
|
||||
}
|
||||
|
||||
.ui-spinner {
|
||||
border-radius: 4px;
|
||||
padding: 0;
|
||||
border: 1px solid $form-input-border-color;
|
||||
}
|
||||
.ui-spinner input {
|
||||
margin: 0 17px 0 0;
|
||||
padding: 6px;
|
||||
border: none;
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
|
||||
@mixin workspace-button {
|
||||
@include disable-selection;
|
||||
color: $workspace-button-color;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
color: $workspace-button-color;
|
||||
background: $workspace-button-background;
|
||||
border: 1px solid $secondary-border-color;
|
||||
text-align: center;
|
||||
@@ -57,7 +57,28 @@
|
||||
background: $workspace-button-background-active;
|
||||
text-decoration: none;
|
||||
}
|
||||
&.selected:not(.disabled) {
|
||||
color: $workspace-button-color-selected;
|
||||
background: $workspace-button-background-active;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.button-group &:not(:first-child) {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
@mixin workspace-button-toggle {
|
||||
@include workspace-button;
|
||||
color: $workspace-button-color-selected;
|
||||
background: $workspace-button-background-active;
|
||||
|
||||
&.selected:not(.disabled) {
|
||||
color: $workspace-button-color;
|
||||
background: $workspace-button-background;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@mixin component-footer {
|
||||
border-top: 1px solid $primary-border-color;
|
||||
background: #f3f3f3;
|
||||
|
||||
@@ -33,6 +33,12 @@
|
||||
border: 1px solid #325C80;
|
||||
border-left-width: 16px;
|
||||
}
|
||||
.notification a {
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-success {
|
||||
border-color: #4B8400;
|
||||
|
||||
@@ -114,11 +114,16 @@
|
||||
}
|
||||
|
||||
.palette-header {
|
||||
position: relative;
|
||||
background: $palette-header-background;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
padding: 9px;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
}
|
||||
.palette-header i {
|
||||
margin: 3px 10px 3px 3px;
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
right: 0;
|
||||
bottom: 25px;
|
||||
left: 0px;
|
||||
padding-top: 3px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -63,6 +62,33 @@
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
color: #666;
|
||||
text-align: right;
|
||||
padding: 8px 10px;
|
||||
background: #f3f3f3;
|
||||
border-bottom: 1px solid $secondary-border-color;
|
||||
}
|
||||
|
||||
#sidebar-footer {
|
||||
@include component-footer;
|
||||
}
|
||||
|
||||
.sidebar-footer-button {
|
||||
@include component-footer-button;
|
||||
}
|
||||
.sidebar-header-button {
|
||||
@include workspace-button;
|
||||
font-size: 13px;
|
||||
line-height: 13px;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
.sidebar-header-button-toggle {
|
||||
@include workspace-button-toggle;
|
||||
font-size: 13px;
|
||||
line-height: 13px;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
.sidebar-header-button:not(:first-child) {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
@import "popover";
|
||||
@import "flow";
|
||||
|
||||
@import "typedInput";
|
||||
|
||||
@import "dragdrop";
|
||||
|
||||
@import "keyboard";
|
||||
|
||||
@@ -47,23 +47,35 @@
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
}
|
||||
.config_node_type {
|
||||
color: #999;
|
||||
text-align: right;
|
||||
padding-right: 3px;
|
||||
&:not(:first-child) {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
.config_node_none {
|
||||
color: #ddd;
|
||||
text-align:right;
|
||||
padding-right: 3px;
|
||||
}
|
||||
.config_node_unused {
|
||||
border-color: #aaa;
|
||||
background: #f9f9f9;
|
||||
border-style: dashed;
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
.config_node_type {
|
||||
color: #999;
|
||||
text-align: right;
|
||||
padding-right: 3px;
|
||||
&:not(:first-child) {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
.config_node_none {
|
||||
color: #ddd;
|
||||
text-align:right;
|
||||
padding-right: 3px;
|
||||
}
|
||||
.config_node_unused {
|
||||
border-color: #aaa;
|
||||
background: #f9f9f9;
|
||||
border-style: dashed;
|
||||
color: #aaa;
|
||||
}
|
||||
.config-node-filter-info {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right:0;
|
||||
height: 38px;
|
||||
line-height: 38px;
|
||||
padding: 0 8px;
|
||||
background: $palette-header-background;
|
||||
font-size: 0.8em;
|
||||
color: #999;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
122
editor/sass/typedInput.scss
Normal file
122
editor/sass/typedInput.scss
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Copyright 2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
.red-ui-typedInput-container {
|
||||
border: 1px solid $form-input-border-color;
|
||||
border-radius: 4px;
|
||||
height: 34px;
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
box-sizing: border-box;
|
||||
overflow:hidden;
|
||||
|
||||
input {
|
||||
padding: 0 0 0 1px;
|
||||
margin:0;
|
||||
height: 32px;
|
||||
border:none;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
box-shadow: none;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&.red-ui-typedInput-focus:not(.input-error) {
|
||||
border-color: $form-input-focus-color !important;
|
||||
}
|
||||
|
||||
a {
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
padding: 0 1px 0 5px;
|
||||
display:inline-block;
|
||||
background: $typedInput-button-background;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
vertical-align: middle;
|
||||
color: #555;
|
||||
i {
|
||||
position:relative;
|
||||
top:-3px;
|
||||
margin-right:4px;
|
||||
margin-top: 1px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
padding: 0 1px 0 5px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
background: $typedInput-button-background-hover;
|
||||
}
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
&:active {
|
||||
background: $typedInput-button-background-active;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
a.red-ui-typedInput-option-trigger {
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
padding: 0 5px 0 0;
|
||||
|
||||
i {
|
||||
margin-right: 0;
|
||||
margin-left: 4px;
|
||||
}
|
||||
span {
|
||||
background:#fff;
|
||||
padding: 0 5px 0 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.red-ui-typedInput-options {
|
||||
@include component-shadow;
|
||||
position: absolute;
|
||||
border: 1px solid $primary-border-color;
|
||||
background: #fff;
|
||||
z-index: 2000;
|
||||
a {
|
||||
padding: 6px 18px 6px 6px;
|
||||
display: block;
|
||||
border-bottom: 1px solid $secondary-border-color;
|
||||
color: #333;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
background: $typedInput-button-background-hover;
|
||||
}
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
&:active {
|
||||
background: $typedInput-button-background-active;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@
|
||||
</head>
|
||||
<body spellcheck="false">
|
||||
<div id="header">
|
||||
<span class="logo">{{#header.url}}<a href="{{.}}">{{/header.url}}{{#header.image}}<img src="{{.}}">{{/header.image}} <span>{{ header.title }}</span>{{#header.url}}</a>{{/header.url}}</span>
|
||||
<span class="logo">{{#header.url}}<a href="{{.}}">{{/header.url}}{{#header.image}}<img src="{{.}}" title="{{version}}">{{/header.image}} <span>{{ header.title }}</span>{{#header.url}}</a>{{/header.url}}</span>
|
||||
<ul class="header-toolbar hide">
|
||||
<li><a id="btn-sidemenu" class="button" data-toggle="dropdown" href="#"><i class="fa fa-bars"></i></a></li>
|
||||
<ul>
|
||||
@@ -104,9 +104,6 @@
|
||||
<div id="node-dialog-confirm-deploy-unknown" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.unknown;[append]deploy.confirm.confirm">
|
||||
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-unknown-list"></ul>
|
||||
</div>
|
||||
<div id="node-dialog-confirm-deploy-unused" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.unusedConfig;[append]deploy.confirm.confirm">
|
||||
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-unused-list"></ul>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
4
editor/vendor/jquery/js/jquery-1.11.1.min.js
vendored
4
editor/vendor/jquery/js/jquery-1.11.1.min.js
vendored
File diff suppressed because one or more lines are too long
5
editor/vendor/jquery/js/jquery-1.11.3.min.js
vendored
Normal file
5
editor/vendor/jquery/js/jquery-1.11.3.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -52,8 +52,8 @@
|
||||
<!-- node in the palette. -->
|
||||
<p>Simple sample input node. Just sends a single message when it starts up.
|
||||
This is not very useful.</p>
|
||||
<p>Outputs an object called <b>msg</b> containing <b>msg.topic</b> and
|
||||
<b>msg.payload</b>. msg.payload is a String.</p>
|
||||
<p>Outputs an object called <code>msg</code> containing <code>msg.topic</code> and
|
||||
<code>msg.payload</code>. msg.payload is a String.</p>
|
||||
</script>
|
||||
|
||||
<!-- Finally, the node type is registered along with all of its properties -->
|
||||
|
||||
@@ -22,10 +22,11 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="sentiment">
|
||||
<p>Analyses the <b>msg.payload</b> and adds a <b>msg.sentiment</b> object that contains the resulting AFINN-111 sentiment score as <b>msg.sentiment.score</b>.</p>
|
||||
<p>Analyses the <code>msg.payload</code> and adds a <code>msg.sentiment</code> object
|
||||
that contains the resulting AFINN-111 sentiment score as <code>msg.sentiment.score</code>.</p>
|
||||
<p>A score greater than zero is positive and less than zero is negative.</p>
|
||||
<p>The score typically ranges from -5 to +5, but can go higher and lower.</p>
|
||||
<p>An object of word score overrides can be supplied as <b>msg.overrides</b>.</p>
|
||||
<p>An object of word score overrides can be supplied as <code>msg.overrides</code>.</p>
|
||||
<p>See <a href="https://github.com/thisandagain/sentiment/blob/master/README.md" target="_new">the Sentiment docs here</a>.</p>
|
||||
</script>
|
||||
|
||||
|
||||
@@ -15,18 +15,10 @@
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="inject">
|
||||
<div class="form-row node-input-payload">
|
||||
<label for="node-input-payloadType"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
|
||||
<select id="node-input-payloadType" style="width:73%">
|
||||
<option value="date" data-i18n="inject.timestamp"></option>
|
||||
<option value="string" data-i18n="inject.string"></option>
|
||||
<option value="none" data-i18n="inject.blank"></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-row" id="node-input-row-payload">
|
||||
<label for="node-input-payload"></label>
|
||||
<input type="text" id="node-input-payload" style="width:70%">
|
||||
<div class="form-row">
|
||||
<label for="node-input-payload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
|
||||
<input type="text" id="node-input-payload" style="width:300px">
|
||||
<input type="hidden" id="node-input-payloadType">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
@@ -150,13 +142,7 @@
|
||||
.inject-time-times {
|
||||
width: 90px;
|
||||
}
|
||||
.inject-time-row > .ui-spinner {
|
||||
height: 28px;
|
||||
margin: 3px 0;
|
||||
border-color: rgb(204, 204, 204);
|
||||
}
|
||||
#inject-time-time {
|
||||
margin-top: 3px;
|
||||
width: 75px;
|
||||
}
|
||||
.inject-time-count {
|
||||
@@ -165,12 +151,15 @@
|
||||
</style>
|
||||
<script type="text/x-red" data-help-name="inject">
|
||||
<p>Pressing the button on the left side of the node allows a message on a topic
|
||||
to be injected into the flow. This is mainly for test purposes.</p>
|
||||
to be injected into the flow.</p>
|
||||
<p>The payload defaults to the current time in millisecs since 1970, but can
|
||||
also be set to a String or left blank.</p>
|
||||
also be set to various other javascript types.</p>
|
||||
<p>The repeat function allows the payload to be sent on the required schedule.</p>
|
||||
<p>The <i>Fire once at start</i> option actually waits a short interval before firing
|
||||
<p>The <i>Inject once at start</i> option actually waits a short interval before firing
|
||||
to give other nodes a chance to instantiate properly.</p>
|
||||
<p>The <i>Flow</i> and <i>Global</i> options allow one to inject a flow or global
|
||||
context value.
|
||||
</p>
|
||||
<p><b>Note: </b>"Interval between times" and "at a specific time" uses cron.
|
||||
This means that 20 minutes will be at the next hour, 20 minutes past and
|
||||
40 minutes past - not in 20 minutes time. If you want every 20 minutes
|
||||
@@ -186,7 +175,22 @@
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
topic: {value:""},
|
||||
payload: {value:""},
|
||||
payload: {value:"", validate:function(v) {
|
||||
var ptype = $("#node-input-payloadType").val() || this.payloadType;
|
||||
if (ptype === 'json') {
|
||||
try {
|
||||
JSON.parse(v);
|
||||
return true;
|
||||
} catch(err) {
|
||||
return false;
|
||||
}
|
||||
} else if (ptype === 'flow' || ptype === 'global' ) {
|
||||
return /^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]+)*/i.test(v);
|
||||
} else if (ptype === 'num') {
|
||||
return /^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v);
|
||||
}
|
||||
return true;
|
||||
}},
|
||||
payloadType: {value:"date"},
|
||||
repeat: {value:""},
|
||||
crontab: {value:""},
|
||||
@@ -196,23 +200,51 @@
|
||||
outputs:1,
|
||||
icon: "inject.png",
|
||||
label: function() {
|
||||
if (this.payloadType === "string") {
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
} else if (this.payloadType === "string" ||
|
||||
this.payloadType === "str" ||
|
||||
this.payloadType === "num" ||
|
||||
this.payloadType === "bool" ||
|
||||
this.payloadType === "json") {
|
||||
if ((this.topic !== "") && ((this.topic.length + this.payload.length) <= 32)) {
|
||||
return this.name||this.topic + ":" + this.payload;
|
||||
}
|
||||
else if (this.payload.length < 24) {
|
||||
return this.name||this.payload;
|
||||
return this.topic + ":" + this.payload;
|
||||
} else if (this.payload.length > 0 && this.payload.length < 24) {
|
||||
return this.payload;
|
||||
} else {
|
||||
return this._("inject.inject");
|
||||
}
|
||||
} else if (this.payloadType === 'date') {
|
||||
return this._("inject.timestamp")
|
||||
} else if (this.payloadType === 'flow' && this.payload.length < 19) {
|
||||
return 'flow.'+this.payload;
|
||||
} else if (this.payloadType === 'global' && this.payload.length < 17) {
|
||||
return 'global.'+this.payload;
|
||||
} else {
|
||||
return this._("inject.inject");
|
||||
}
|
||||
if ((this.topic.length < 24) && (this.topic.length > 0)) {
|
||||
return this.name||this.topic;
|
||||
}
|
||||
else { return this.name||(this.payloadType==="date"?this._("inject.timestamp"):null)||this._("inject.inject"); }
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if (this.payloadType == null) {
|
||||
if (this.payload == "") {
|
||||
this.payloadType = "date";
|
||||
} else {
|
||||
this.payloadType = "str";
|
||||
}
|
||||
} else if (this.payloadType === 'string' || this.payloadType === 'none') {
|
||||
this.payloadType = "str";
|
||||
}
|
||||
$("#node-input-payloadType").val(this.payloadType);
|
||||
|
||||
$("#node-input-payload").typedInput({
|
||||
default: 'str',
|
||||
typeField: $("#node-input-payloadType"),
|
||||
types:['flow','global','str','num','bool','json',{value:"date",label:this._("inject.timestamp"),hasValue:false}]
|
||||
});
|
||||
|
||||
$("#inject-time-type-select").change(function() {
|
||||
$("#node-input-crontab").val('');
|
||||
var id = $("#inject-time-type-select option:selected").val();
|
||||
@@ -373,24 +405,8 @@
|
||||
$("#inject-time-type-select option").filter(function() {return $(this).val() == repeattype;}).attr('selected',true);
|
||||
$("#inject-time-row-"+repeattype).show();
|
||||
|
||||
if (this.payloadType == null) {
|
||||
if (this.payload == "") {
|
||||
this.payloadType = "date";
|
||||
} else {
|
||||
this.payloadType = "string";
|
||||
}
|
||||
}
|
||||
$("#node-input-payload").typedInput('type',this.payloadType);
|
||||
|
||||
$("#node-input-payloadType").change(function() {
|
||||
var id = $("#node-input-payloadType option:selected").val();
|
||||
if (id === "string") {
|
||||
$("#node-input-row-payload").show();
|
||||
} else {
|
||||
$("#node-input-row-payload").hide();
|
||||
}
|
||||
});
|
||||
$("#node-input-payloadType").val(this.payloadType);
|
||||
$("#node-input-payloadType").change();
|
||||
$("#inject-time-type-select").change();
|
||||
$("#inject-time-interval-time-start").change();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -50,16 +50,22 @@ module.exports = function(RED) {
|
||||
}
|
||||
|
||||
this.on("input",function(msg) {
|
||||
var msg = {topic:this.topic};
|
||||
if ( (this.payloadType == null && this.payload === "") || this.payloadType === "date") {
|
||||
msg.payload = Date.now();
|
||||
} else if (this.payloadType == null || this.payloadType === "string") {
|
||||
msg.payload = this.payload;
|
||||
} else {
|
||||
msg.payload = "";
|
||||
try {
|
||||
msg = {topic:this.topic};
|
||||
if ( (this.payloadType == null && this.payload === "") || this.payloadType === "date") {
|
||||
msg.payload = Date.now();
|
||||
} else if (this.payloadType == null) {
|
||||
msg.payload = this.payload;
|
||||
} else if (this.payloadType == 'none') {
|
||||
msg.payload = "";
|
||||
} else {
|
||||
msg.payload = RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg);
|
||||
}
|
||||
this.send(msg);
|
||||
msg = null;
|
||||
} catch(err) {
|
||||
this.error(err,msg);
|
||||
}
|
||||
this.send(msg);
|
||||
msg = null;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2015 IBM Corp.
|
||||
Copyright 2015, 2016 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -22,7 +22,7 @@
|
||||
<option value="target" data-i18n="catch.scope.selected"></options>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row node-input-target-row" style="display: none;">
|
||||
<div class="form-row node-input-target-row" style="display: none;min-height: 100px;">
|
||||
<div id="node-input-catch-target-container-div" style="position: relative; box-sizing: border-box; border-radius: 2px; height: 180px; border: 1px solid #ccc;overflow:hidden; ">
|
||||
<div style="box-sizing: border-box; line-height: 20px; font-size: 0.8em; border-bottom: 1px solid #ddd; height: 20px;">
|
||||
<input type="checkbox" data-i18n="[title]catch.label.selectAll" id="node-input-target-node-checkbox-all" style="width: 30px; margin: 0 2px 1px 2px;">
|
||||
@@ -106,7 +106,7 @@
|
||||
outputs:1,
|
||||
icon: "alert.png",
|
||||
label: function() {
|
||||
return this.name||this.scope?this._("catch.catchNodes",{number:this.scope.length}):this._("catch.catch");
|
||||
return this.name||(this.scope?this._("catch.catchNodes",{number:this.scope.length}):this._("catch.catch"));
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
@@ -114,6 +114,17 @@
|
||||
oneditprepare: function() {
|
||||
var nodeList = $("#node-input-catch-target-container");
|
||||
var node = this;
|
||||
this.resize = function() {
|
||||
var rows = $("#dialog-form>div:not(.node-input-target-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-target-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$("#node-input-catch-target-container-div").css("height",height+"px");
|
||||
}
|
||||
|
||||
function createNodeList() {
|
||||
var scope = node.scope || [];
|
||||
nodeList.empty();
|
||||
@@ -238,6 +249,7 @@
|
||||
} else {
|
||||
$(".node-input-target-row").hide();
|
||||
}
|
||||
node.resize();
|
||||
});
|
||||
if (this.scope == null) {
|
||||
$("#node-input-scope-select").val("all");
|
||||
@@ -245,31 +257,6 @@
|
||||
$("#node-input-scope-select").val("target");
|
||||
}
|
||||
$("#node-input-scope-select").change();
|
||||
|
||||
function dialogResize() {
|
||||
var rows = $("#dialog-form>div:not(.node-input-target-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-target-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$("#node-input-catch-target-container-div").css("height",height+"px");
|
||||
};
|
||||
|
||||
$( "#dialog" ).on("dialogresize", dialogResize);
|
||||
$( "#dialog" ).one("dialogopen", function(ev) {
|
||||
var size = $( "#dialog" ).dialog('option','sizeCache-catch');
|
||||
if (size) {
|
||||
$("#dialog").dialog('option','width',size.width);
|
||||
$("#dialog").dialog('option','height',size.height);
|
||||
dialogResize();
|
||||
}
|
||||
});
|
||||
$( "#dialog" ).one("dialogclose", function(ev,ui) {
|
||||
$( "#dialog" ).off("dialogresize",dialogResize);
|
||||
});
|
||||
|
||||
},
|
||||
oneditsave: function() {
|
||||
var scope = $("#node-input-scope-select").children("option:selected").val();
|
||||
@@ -283,8 +270,10 @@
|
||||
node.scope.push($(this).data('node-id'));
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
this.resize();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2015 IBM Corp.
|
||||
Copyright 2015, 2016 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -22,7 +22,7 @@
|
||||
<option value="target" data-i18n="status.scope.selected"></options>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row node-input-target-row" style="display: none;">
|
||||
<div class="form-row node-input-target-row" style="display: none; min-height: 100px;">
|
||||
<div id="node-input-status-target-container-div" style="position: relative; box-sizing: border-box; border-radius: 2px; height: 180px; border: 1px solid #ccc;overflow:hidden; ">
|
||||
<div style="box-sizing: border-box; line-height: 20px; font-size: 0.8em; border-bottom: 1px solid #ddd; height: 20px;">
|
||||
<input type="checkbox" data-i18n="[title]status.label.selectAll" id="node-input-target-node-checkbox-all" style="width: 30px; margin: 0 2px 1px 2px;">
|
||||
@@ -97,7 +97,7 @@
|
||||
outputs:1,
|
||||
icon: "alert.png",
|
||||
label: function() {
|
||||
return this.name||this.scope?this._("status.statusNodes",{number:this.scope.length}):this._("status.status");
|
||||
return this.name||(this.scope?this._("status.statusNodes",{number:this.scope.length}):this._("status.status"));
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
@@ -105,6 +105,17 @@
|
||||
oneditprepare: function() {
|
||||
var nodeList = $("#node-input-status-target-container");
|
||||
var node = this;
|
||||
this.resize = function() {
|
||||
var rows = $("#dialog-form>div:not(.node-input-target-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-target-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$("#node-input-status-target-container-div").css("height",height+"px");
|
||||
}
|
||||
|
||||
function createNodeList() {
|
||||
var scope = node.scope || [];
|
||||
nodeList.empty();
|
||||
@@ -219,8 +230,6 @@
|
||||
$(".node-input-target-node-checkbox").prop('checked',this.checked);
|
||||
})
|
||||
|
||||
|
||||
|
||||
$("#node-input-scope-select").change(function(e) {
|
||||
var scope = $(this).children("option:selected").val();
|
||||
if (scope === "target") {
|
||||
@@ -229,6 +238,7 @@
|
||||
} else {
|
||||
$(".node-input-target-row").hide();
|
||||
}
|
||||
node.resize();
|
||||
});
|
||||
if (this.scope == null) {
|
||||
$("#node-input-scope-select").val("all");
|
||||
@@ -236,31 +246,6 @@
|
||||
$("#node-input-scope-select").val("target");
|
||||
}
|
||||
$("#node-input-scope-select").change();
|
||||
|
||||
function dialogResize() {
|
||||
var rows = $("#dialog-form>div:not(.node-input-target-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-target-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$("#node-input-status-target-container-div").css("height",height+"px");
|
||||
};
|
||||
|
||||
$( "#dialog" ).on("dialogresize", dialogResize);
|
||||
$( "#dialog" ).one("dialogopen", function(ev) {
|
||||
var size = $( "#dialog" ).dialog('option','sizeCache-status');
|
||||
if (size) {
|
||||
$("#dialog").dialog('option','width',size.width);
|
||||
$("#dialog").dialog('option','height',size.height);
|
||||
dialogResize();
|
||||
}
|
||||
});
|
||||
$( "#dialog" ).one("dialogclose", function(ev,ui) {
|
||||
$( "#dialog" ).off("dialogresize",dialogResize);
|
||||
});
|
||||
|
||||
},
|
||||
oneditsave: function() {
|
||||
var scope = $("#node-input-scope-select").children("option:selected").val();
|
||||
@@ -276,6 +261,9 @@
|
||||
})
|
||||
|
||||
}
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
this.resize();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -39,13 +39,14 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="debug">
|
||||
<p>The Debug node can be connected to the output of any node. It can be used to display the output of any message property in the debug tab of the sidebar. The default is to display <b>msg.payload</b>.</p>
|
||||
<p>Each message will also display the timestamp, <b>msg.topic</b> and the property chosen to output.</p>
|
||||
<p>The Debug node can be connected to the output of any node. It can be used to display the output of any message
|
||||
property in the debug tab of the sidebar. The default is to display <code>msg.payload</code>.</p>
|
||||
<p>Each message will also display the timestamp, <code>msg.topic</code> and the type of property chosen to output.</p>
|
||||
<p>The sidebar can be accessed under the options drop-down in the top right corner.</p>
|
||||
<p>The button to the right of the node will toggle its output on and off so you can de-clutter the debug window.</p>
|
||||
<p>If the payload is an object or buffer it will be stringified first for display and indicate that by saying "(Object)" or "(Buffer)".</p>
|
||||
<p>Selecting any particular message will highlight (in red) the debug node that reported it. This is useful if you wire up multiple debug nodes.</p>
|
||||
<p>Optionally can show the complete <b>msg</b> object.</p>
|
||||
<p>Optionally can show the complete <code>msg</code> object, and send messages to the console log.</p>
|
||||
<p>In addition any calls to node.warn or node.error will appear here.</p>
|
||||
</script>
|
||||
|
||||
@@ -127,17 +128,10 @@
|
||||
}
|
||||
},
|
||||
onpaletteadd: function() {
|
||||
var content = document.createElement("div");
|
||||
$(content).css({"position":"relative","height":"100%"});
|
||||
var toolbar = document.createElement("div");
|
||||
toolbar.id = "debug-toolbar";
|
||||
content.appendChild(toolbar);
|
||||
var content = $("<div>").css({"position":"relative","height":"100%"});
|
||||
var toolbar = $('<div class="sidebar-header"><a id="debug-tab-clear" title="clear log" class="button" href="#"><i class="fa fa-trash"></i></a></div>').appendTo(content);
|
||||
|
||||
toolbar.innerHTML = '<div class="pull-right"><a id="debug-tab-clear" title="clear log" class="button" href="#"><i class="fa fa-trash"></i></a></div> ';
|
||||
|
||||
var messages = document.createElement("div");
|
||||
messages.id = "debug-content";
|
||||
content.appendChild(messages);
|
||||
var messages = $('<div id="debug-content"/>').appendTo(content);
|
||||
|
||||
RED.sidebar.addTab({
|
||||
id: "debug",
|
||||
@@ -221,9 +215,9 @@
|
||||
'</span>';
|
||||
}
|
||||
msg.innerHTML += '<span class="debug-message-payload">'+ payload+ '</span>';
|
||||
var atBottom = (sbc.scrollHeight-messages.offsetHeight-sbc.scrollTop) < 5;
|
||||
var atBottom = (sbc.scrollHeight-messages.height()-sbc.scrollTop) < 5;
|
||||
messageCount++;
|
||||
$(messages).append(msg);
|
||||
messages.append(msg);
|
||||
|
||||
if (messageCount > 200) {
|
||||
$("#debug-content .debug-message:first").remove();
|
||||
@@ -257,17 +251,12 @@
|
||||
<style>
|
||||
#debug-content {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
top: 43px;
|
||||
bottom: 0px;
|
||||
left:0px;
|
||||
right: 0px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
#debug-toolbar {
|
||||
padding: 3px 10px;
|
||||
height: 24px;
|
||||
background: #f3f3f3;
|
||||
}
|
||||
.debug-message {
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
@@ -124,7 +124,6 @@ module.exports = function(RED) {
|
||||
if (msg.msg.length > debuglength) {
|
||||
msg.msg = msg.msg.substr(0,debuglength) +" ....";
|
||||
}
|
||||
|
||||
RED.comms.publish("debug",msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<p>Provides 3 outputs... stdout, stderr, and return code.</p>
|
||||
<p>By default uses exec() which calls the command, then gets a callback on completion, returning the complete result in one message, along with any errors.</p>
|
||||
<p>Optionally can use spawn() instead, which returns the output from stdout and stderr as the command runs (usually one line at a time). On completion it then returns a return code (on the 3rd output).</p>
|
||||
<p>The optional append gets added to the command after the <b>msg.payload</b> - so you can do things like pipe the result to another command.</p>
|
||||
<p>The optional append gets added to the command after the <code>msg.payload</code> - so you can do things like pipe the result to another command.</p>
|
||||
<p>Parameters with spaces should be enclosed in quotes - <i>"This is a single parameter"</i></p>
|
||||
<p>If stdout is binary a <i>buffer</i> is returned - otherwise returns a <i>string</i>.</p>
|
||||
<p>The blue status icon will be visible while the node is active.</p>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2013, 2015 IBM Corp.
|
||||
Copyright 2013, 2016 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -29,7 +29,7 @@
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-outputs"><i class="fa fa-random"></i> <span data-i18n="function.label.outputs"></span></label>
|
||||
<input id="node-input-outputs" style="width: 60px; height: 1.7em;" value="1">
|
||||
<input id="node-input-outputs" style="width: 60px;" value="1">
|
||||
</div>
|
||||
<div class="form-tips"><span data-i18n="function.tip"></span></div>
|
||||
</script>
|
||||
@@ -88,31 +88,6 @@
|
||||
min:1
|
||||
});
|
||||
|
||||
function functionDialogResize() {
|
||||
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-text-editor-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$(".node-text-editor").css("height",height+"px");
|
||||
that.editor.resize();
|
||||
}
|
||||
$( "#dialog" ).on("dialogresize", functionDialogResize);
|
||||
$( "#dialog" ).one("dialogopen", function(ev) {
|
||||
var size = $( "#dialog" ).dialog('option','sizeCache-function');
|
||||
if (size) {
|
||||
$("#dialog").dialog('option','width',size.width);
|
||||
$("#dialog").dialog('option','height',size.height);
|
||||
functionDialogResize();
|
||||
}
|
||||
});
|
||||
$( "#dialog" ).one("dialogclose", function(ev,ui) {
|
||||
var height = $( "#dialog" ).dialog('option','height');
|
||||
$( "#dialog" ).off("dialogresize",functionDialogResize);
|
||||
});
|
||||
|
||||
this.editor = RED.editor.createEditor({
|
||||
id: 'node-input-func-editor',
|
||||
mode: 'ace/mode/javascript',
|
||||
@@ -141,6 +116,17 @@
|
||||
}
|
||||
$("#node-input-func").val(this.editor.getValue());
|
||||
delete this.editor;
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-text-editor-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$(".node-text-editor").css("height",height+"px");
|
||||
this.editor.resize();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -90,7 +90,34 @@ module.exports = function(RED) {
|
||||
}
|
||||
},
|
||||
context: {
|
||||
global:RED.settings.functionGlobalContext || {}
|
||||
set: function() {
|
||||
node.context().set.apply(node,arguments);
|
||||
},
|
||||
get: function() {
|
||||
return node.context().get.apply(node,arguments);
|
||||
},
|
||||
get global() {
|
||||
return node.context().global;
|
||||
},
|
||||
get flow() {
|
||||
return node.context().flow;
|
||||
}
|
||||
},
|
||||
flow: {
|
||||
set: function() {
|
||||
node.context().flow.set.apply(node,arguments);
|
||||
},
|
||||
get: function() {
|
||||
return node.context().flow.get.apply(node,arguments);
|
||||
}
|
||||
},
|
||||
global: {
|
||||
set: function() {
|
||||
node.context().global.set.apply(node,arguments);
|
||||
},
|
||||
get: function() {
|
||||
return node.context().global.get.apply(node,arguments);
|
||||
}
|
||||
},
|
||||
setTimeout: function () {
|
||||
var func = arguments[0];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2013, 2015 IBM Corp.
|
||||
Copyright 2013, 2016 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -19,29 +19,41 @@
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom: 0px;">
|
||||
<div class="form-row">
|
||||
<label for="node-input-field"><i class="fa fa-edit"></i> <span data-i18n="template.label.property"></span></label>
|
||||
<input type="text" id="node-input-field" placeholder="payload" style="width:250px;">
|
||||
<input type="hidden" id="node-input-fieldType">
|
||||
</div>
|
||||
<div class="form-row" style="position: relative; margin-bottom: 0px;">
|
||||
<label for="node-input-template"><i class="fa fa-file-code-o"></i> <span data-i18n="template.label.template"></span></label>
|
||||
<input type="hidden" id="node-input-template" autofocus="autofocus">
|
||||
<select id="node-input-format" style=" font-size: 0.8em; margin-bottom: 3px; width:110px; float:right;">
|
||||
<option value="handlebars">mustache</option>
|
||||
<option value="html">HTML</option>
|
||||
<option value="json">JSON</option>
|
||||
<option value="markdown">Markdown</option>
|
||||
<option value="text">none</option>
|
||||
</select>
|
||||
<div style="position: absolute; right:0;display:inline-block; text-align: right; font-size: 0.8em;">
|
||||
<span data-i18n="template.label.format"></span>:
|
||||
<select id="node-input-format" style="width:110px; font-size: 10px !important; height: 24px; padding:0;">
|
||||
<option value="handlebars">mustache</option>
|
||||
<option value="html">HTML</option>
|
||||
<option value="json">JSON</option>
|
||||
<option value="markdown">Markdown</option>
|
||||
<option value="text">none</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row node-text-editor-row">
|
||||
<div style="height: 250px;" class="node-text-editor" id="node-input-template-editor" ></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-field"><i class="fa fa-edit"></i> <span data-i18n="template.label.property"></span></label>
|
||||
msg.<input type="text" id="node-input-field" placeholder="payload" style="width:170px;">
|
||||
<label for="node-input-syntax"><i class="fa fa-code"></i> <span data-i18n="template.label.syntax"></span></label>
|
||||
<select id="node-input-syntax" style="width:180px;">
|
||||
<option value="mustache" data-i18n="template.label.mustache"></option>
|
||||
<option value="plain" data-i18n="template.label.plain"></option>
|
||||
</select>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="template">
|
||||
<p>Creates a new message based on the provided template.</p>
|
||||
<p>This uses the <i><a href="http://mustache.github.io/mustache.5.html" target="_new">mustache</a></i> format.</p>
|
||||
<p>Sets a property based on the provided template.</p>
|
||||
<p>By default this uses the <i><a href="http://mustache.github.io/mustache.5.html" target="_new">mustache</a></i>
|
||||
format, but this can be switched off if required.</p>
|
||||
<p>For example, when a template of:
|
||||
<pre>Hello {{name}}. Today is {{date}}</pre>
|
||||
<p>receives a message containing:
|
||||
@@ -50,8 +62,10 @@
|
||||
date: "Monday"
|
||||
payload: ...
|
||||
}</pre>
|
||||
<p>The resulting payload will be:
|
||||
<p>The resulting property will be:
|
||||
<pre>Hello Fred. Today is Monday</pre>
|
||||
<p>By default, mustache will escape any HTML entities in the values it substitutes.
|
||||
To prevent this, use <code>{{{triple}}}</code> braces.
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
@@ -61,7 +75,9 @@
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
field: {value:"payload"},
|
||||
fieldType: {value:"msg"},
|
||||
format: {value:"handlebars"},
|
||||
syntax: {value:"mustache"},
|
||||
template: {value:"This is the payload: {{payload}} !"},
|
||||
},
|
||||
inputs:1,
|
||||
@@ -72,30 +88,19 @@
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var that = this;
|
||||
function templateDialogResize() {
|
||||
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-text-editor-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$(".node-text-editor").css("height",height+"px");
|
||||
that.editor.resize();
|
||||
};
|
||||
$( "#dialog" ).on("dialogresize", templateDialogResize);
|
||||
$( "#dialog" ).one("dialogopen", function(ev) {
|
||||
var size = $( "#dialog" ).dialog('option','sizeCache-template');
|
||||
if (size) {
|
||||
$("#dialog").dialog('option','width',size.width);
|
||||
$("#dialog").dialog('option','height',size.height);
|
||||
templateDialogResize();
|
||||
}
|
||||
});
|
||||
$( "#dialog" ).one("dialogclose", function(ev,ui) {
|
||||
var height = $( "#dialog" ).dialog('option','height');
|
||||
$( "#dialog" ).off("dialogresize",templateDialogResize);
|
||||
if (!this.fieldType) {
|
||||
this.fieldType = 'msg';
|
||||
}
|
||||
if (!this.syntax) {
|
||||
this.syntax = 'mustache';
|
||||
$("#node-input-syntax").val(this.syntax);
|
||||
}
|
||||
$("#node-input-field").typedInput({
|
||||
default: 'msg',
|
||||
types: ['msg','flow','global'],
|
||||
typeField: $("#node-input-fieldType")
|
||||
});
|
||||
|
||||
this.editor = RED.editor.createEditor({
|
||||
id: 'node-input-template-editor',
|
||||
mode: 'ace/mode/html',
|
||||
@@ -108,7 +113,7 @@
|
||||
fields:['name','outputs']
|
||||
});
|
||||
this.editor.focus();
|
||||
|
||||
|
||||
$("#node-input-format").change(function() {
|
||||
var mod = "ace/mode/"+$("#node-input-format").val();
|
||||
that.editor.getSession().setMode({
|
||||
@@ -120,6 +125,17 @@
|
||||
oneditsave: function() {
|
||||
$("#node-input-template").val(this.editor.getValue())
|
||||
delete this.editor;
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-text-editor-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$(".node-text-editor").css("height",height+"px");
|
||||
this.editor.resize();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,33 +23,26 @@ module.exports = function(RED) {
|
||||
this.name = n.name;
|
||||
this.field = n.field || "payload";
|
||||
this.template = n.template;
|
||||
this.syntax = n.syntax || "mustache";
|
||||
this.fieldType = n.fieldType || "msg";
|
||||
|
||||
var node = this;
|
||||
|
||||
var b = node.field.split(".");
|
||||
var i = 0;
|
||||
var m = null;
|
||||
var rec = function(obj) {
|
||||
i += 1;
|
||||
if ((i < b.length) && (typeof obj[b[i-1]] === "object")) {
|
||||
rec(obj[b[i-1]]); // not there yet - carry on digging
|
||||
}
|
||||
else {
|
||||
if (i === b.length) { // we've finished so assign the value
|
||||
obj[b[i-1]] = mustache.render(node.template,m);
|
||||
node.send(m);
|
||||
}
|
||||
else {
|
||||
obj[b[i-1]] = {}; // needs to be a new object so create it
|
||||
rec(obj[b[i-1]]); // and carry on digging
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.on("input", function(msg) {
|
||||
try {
|
||||
m = msg;
|
||||
i = 0;
|
||||
rec(msg);
|
||||
var value;
|
||||
if (node.syntax === "mustache") {
|
||||
value = mustache.render(node.template,msg);
|
||||
} else {
|
||||
value = node.template;
|
||||
}
|
||||
if (node.fieldType === 'msg') {
|
||||
RED.util.setMessageProperty(msg,node.field,value);
|
||||
} else if (node.fieldType === 'flow') {
|
||||
node.context().flow.set(node.field,value);
|
||||
} else if (node.fieldType === 'global') {
|
||||
node.context().global.set(node.field,value);
|
||||
}
|
||||
node.send(msg);
|
||||
} catch(err) {
|
||||
node.error(err.message);
|
||||
}
|
||||
|
||||
@@ -14,21 +14,21 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!-- First, the content of the edit dialog is defined. -->
|
||||
<script type="text/x-red" data-template-name="delay">
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-pauseType"><i class="fa fa-tasks"></i> <span data-i18n="delay.action"></span></label>
|
||||
<select id="node-input-pauseType" style="width:270px !important">
|
||||
<option value="delay" data-i18n="delay.delaymsg"></option>
|
||||
<option value="random" data-i18n="delay.ramdomdelay"></option>
|
||||
<option value="rate" data-i18n="delay.limitrate"></option>
|
||||
<option value="queue" data-i18n="delay.fairqueue"></option>
|
||||
<option value="delay" data-i18n="delay.delaymsg"></option>
|
||||
<option value="random" data-i18n="delay.randomdelay"></option>
|
||||
<option value="rate" data-i18n="delay.limitrate"></option>
|
||||
<option value="queue" data-i18n="delay.fairqueue"></option>
|
||||
<option value="timed" data-i18n="delay.timedqueue"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="delay-details" class="form-row">
|
||||
<label for="node-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="delay.for"></span></label>
|
||||
<input type="text" id="node-input-timeout" placeholder="Time" style="direction:rtl; width:50px !important">
|
||||
<input type="text" id="node-input-timeout" placeholder="Time" style="text-align:end; width:50px !important">
|
||||
<select id="node-input-timeoutUnits" style="width:200px !important">
|
||||
<option value="milliseconds" data-i18n="delay.milisecs"></option>
|
||||
<option value="seconds" data-i18n="delay.secs"></option>
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
<div id="rate-details" class="form-row">
|
||||
<label for="node-input-rate"><i class="fa fa-clock-o"></i> <span data-i18n="delay.rate"></span></label>
|
||||
<input type="text" id="node-input-rate" placeholder="1" style="direction:rtl; width:30px !important">
|
||||
<input type="text" id="node-input-rate" placeholder="1" style="text-align:end; width:30px !important">
|
||||
<label for="node-input-rateUnits"><span data-i18n="delay.msgper"></span></label>
|
||||
<select id="node-input-rateUnits" style="width:140px !important">
|
||||
<option value="second" data-i18n="delay.sec"></option>
|
||||
@@ -55,9 +55,9 @@
|
||||
|
||||
<div id="random-details" class="form-row">
|
||||
<label for="node-input-randomFirst"><i class="fa fa-clock-o"></i> <span data-i18n="delay.between"></span></label>
|
||||
<input type="text" id="node-input-randomFirst" placeholder="" style="directon:rtl; width:30px !important">
|
||||
<label for="node-input-randomLast" style="width:20px"> & </label>
|
||||
<input type="text" id="node-input-randomLast" placeholder="" style="directon:rtl; width:30px !important">
|
||||
<input type="text" id="node-input-randomFirst" placeholder="" style="text-align:end; width:30px !important">
|
||||
&
|
||||
<input type="text" id="node-input-randomLast" placeholder="" style="text-align:end; width:30px !important">
|
||||
<select id="node-input-randomUnits" style="width:140px !important">
|
||||
<option value="milliseconds" data-i18n="delay.milisecs"></option>
|
||||
<option value="seconds" data-i18n="delay.secs"></option>
|
||||
@@ -74,25 +74,26 @@
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Next, some simple help text is provided for the node. -->
|
||||
<script type="text/x-red" data-help-name="delay">
|
||||
<p>Introduces a delay into a flow or rate limits messages.</p>
|
||||
<p>The default delay is 5 seconds and rate limit of 1 msg/second, but both can be configured.</p>
|
||||
<p>When set to rate limit messages, they are spread across the configured time period. It can
|
||||
also be set to discard any intermediate messages that arrive.</p>
|
||||
<p>The "topic based fair queue" adds messages to a release queue tagged by their <b>msg.topic</b> property.
|
||||
<p>The "topic based fair queue" adds messages to a release queue tagged by their <code>msg.topic</code> property.
|
||||
At each "tick", derived from the rate, the next "topic" is released.
|
||||
Any messages arriving on the same topic before release replace those in that position in the queue.
|
||||
So each "topic" gets a turn - but the most recent value is always the one sent.</p>
|
||||
<p>The "timed release queue" adds messages to an array based on their <code>msg.topic</code> property.
|
||||
At each "tick", all the latest messages are released. Any new messages arriving before release will
|
||||
replace those of the same topic.</p>
|
||||
</script>
|
||||
|
||||
<!-- Finally, the node type is registered along with all of its properties -->
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('delay',{
|
||||
category: 'function', // the palette category
|
||||
category: 'function',
|
||||
color:"#E6E0F8",
|
||||
defaults: { // defines the editable properties of the node
|
||||
name: {value:""}, // along with default values.
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
pauseType: {value:"delay", required:true},
|
||||
timeout: {value:"5", required:true, validate:RED.validators.number()},
|
||||
timeoutUnits: {value:"seconds"},
|
||||
@@ -103,10 +104,10 @@
|
||||
randomUnits: {value: "seconds"},
|
||||
drop: {value:false}
|
||||
},
|
||||
inputs:1, // set the number of inputs - only 0 or 1
|
||||
outputs:1, // set the number of outputs - 0 to n
|
||||
icon: "timer.png", // set the icon (held in public/icons)
|
||||
label: function() { // sets the default label contents
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "timer.png",
|
||||
label: function() {
|
||||
if (this.pauseType == "delay") {
|
||||
var units = this.timeoutUnits ? this.timeoutUnits.charAt(0) : "s";
|
||||
if (this.timeoutUnits == "milliseconds") { units = "ms"; }
|
||||
@@ -117,12 +118,15 @@
|
||||
} else if (this.pauseType == "random") {
|
||||
return this.name || this._("delay.label.random");
|
||||
}
|
||||
else if (this.pauseType == "timed") {
|
||||
return this.name || this.rate+" "+this._("delay.label.timed")+" "+this.rateUnits;
|
||||
}
|
||||
else {
|
||||
var units = this.rateUnits ? this.rateUnits.charAt(0) : "s";
|
||||
return this.name || this._("delay.label.queue")+" "+this.rate+" msg/"+units;
|
||||
}
|
||||
},
|
||||
labelStyle: function() { // sets the class to apply to the label
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
@@ -152,6 +156,11 @@
|
||||
$("#rate-details").show();
|
||||
$("#random-details").hide();
|
||||
$("#node-input-dr").hide();
|
||||
} else if (this.pauseType == "timed") {
|
||||
$("#delay-details").hide();
|
||||
$("#rate-details").show();
|
||||
$("#random-details").hide();
|
||||
$("#node-input-dr").hide();
|
||||
}
|
||||
|
||||
if (!this.timeoutUnits) {
|
||||
@@ -187,6 +196,11 @@
|
||||
$("#rate-details").show();
|
||||
$("#random-details").hide();
|
||||
$("#node-input-dr").hide();
|
||||
} else if (this.value == "timed") {
|
||||
$("#delay-details").hide();
|
||||
$("#rate-details").show();
|
||||
$("#random-details").hide();
|
||||
$("#node-input-dr").hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -147,10 +147,17 @@ module.exports = function(RED) {
|
||||
node.status({});
|
||||
});
|
||||
|
||||
} else if (this.pauseType === "queue") {
|
||||
} else if ((this.pauseType === "queue") || (this.pauseType === "timed")) {
|
||||
this.intervalID = setInterval(function() {
|
||||
if (node.buffer.length > 0) {
|
||||
node.send(node.buffer.shift()); // send the first on the queue
|
||||
if (this.pauseType === "queue") {
|
||||
if (node.buffer.length > 0) {
|
||||
node.send(node.buffer.shift()); // send the first on the queue
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (node.buffer.length > 0) { // send the whole queue
|
||||
node.send(node.buffer.shift());
|
||||
}
|
||||
}
|
||||
node.status({text:node.buffer.length});
|
||||
},node.rate);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<span data-i18n="trigger.send"></span>
|
||||
<select id="node-input-op1type" style="width:200px !important">
|
||||
<option value="val" data-i18n="trigger.output.string"></option>
|
||||
<option value="num" data-i18n="trigger.output.number"></option>
|
||||
<option value="pay" data-i18n="trigger.output.existing"></option>
|
||||
<option value="nul" data-i18n="trigger.output.nothing"></option>
|
||||
</select>
|
||||
@@ -31,7 +32,7 @@
|
||||
<option value="wait" data-i18n="trigger.wait-for"></option>
|
||||
</select>
|
||||
<span class="node-type-wait">
|
||||
<input type="text" id="node-input-duration" style="text-align:right; width:70px !important">
|
||||
<input type="text" id="node-input-duration" style="text-align:end; width:70px !important">
|
||||
<select id="node-input-units" style="width:140px !important">
|
||||
<option value="ms" data-i18n="trigger.duration.ms"></option>
|
||||
<option value="s" data-i18n="trigger.duration.s"></option>
|
||||
@@ -44,35 +45,41 @@
|
||||
<span data-i18n="trigger.then-send"></span>
|
||||
<select id="node-input-op2type" style="width:200px !important">
|
||||
<option value="val" data-i18n="trigger.output.string"></option>
|
||||
<option value="num" data-i18n="trigger.output.number"></option>
|
||||
<option value="pay" data-i18n="trigger.output.existing"></option>
|
||||
<option value="nul" data-i18n="trigger.output.nothing"></option>
|
||||
</select>
|
||||
<input style="width: 145px !important" type="text" id="node-input-op2">
|
||||
</div>
|
||||
<div class="form-row node-type-wait">
|
||||
<input type="checkbox" id="node-input-extend" style="margin-left: 5px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;" for="node-input-extend" data-i18n="trigger.extend"></label>
|
||||
<input type="checkbox" id="node-input-extend" style="margin-left: 0px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;" for="node-input-extend" data-i18n="trigger.extend"></label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<span data-i18n="trigger.label.reset"></span><input type="text" id="node-input-reset" style="width:240px" data-i18n="[placeholder]trigger.label.resetprompt">
|
||||
</div>
|
||||
<br/>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></input>
|
||||
</div>
|
||||
<div class="form-tips" data-i18n="[html]trigger.tip"></div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="trigger">
|
||||
<p>Creates two messages on the output separated by a timeout whenever ANY <b>msg</b> arrives on the input.</p>
|
||||
<p>Creates two messages on the output separated by a timeout whenever <i>any</i> <code>msg</code> arrives on the input.</p>
|
||||
<p>For example, this can be used to toggle a Raspberry PI GPIO pin on and off.</p>
|
||||
<p>The two output states can be specified as can the duration of the timer.
|
||||
Either output can be set to a value, or templated from the inbound
|
||||
<b>msg</b> using mustache syntax. <pre>The payload is {{payload}}</pre></p>
|
||||
<p>If the payload is an object then setting the output to <i>existing payload</i> will pass the complete payload object through.</p>
|
||||
<code>msg</code> using mustache syntax. <pre>The payload is {{payload}}</pre></p>
|
||||
<p>If the <code>msg.payload</code> is an object then setting the output to
|
||||
<i>existing payload</i> will pass the complete payload object through.</p>
|
||||
<p>Optionally the timer can be extended by being retriggered... or not.</p>
|
||||
<p>By setting the first output to <i>nothing</i>, and selecting extend timer - a watchdog timer can be created.
|
||||
No output will happen as long as repeated inputs occur within the timeout period.</p>
|
||||
<p>Setting the timer to 0 creates an "infinite" timeout - the first output will happen but the second
|
||||
never will, and neither can the first be retriggered - so a true one shot.</p>
|
||||
<p>If a <b>msg.reset</b> property is present any timeout currently in progress
|
||||
<p>If a <code>msg.reset</code> property is present, or the <code>msg.payload</code>
|
||||
matches the optional reset value, any timeout currently in progress
|
||||
will be cleared and the second output will not happen.</p>
|
||||
<p>The blue status icon will be visible while the node is active.</p>
|
||||
</script>
|
||||
@@ -88,7 +95,8 @@
|
||||
op2type: {value:"val"},
|
||||
duration: {value:"250",required:true,validate:RED.validators.number()},
|
||||
extend: {value:"false"},
|
||||
units: {value: "ms"},
|
||||
units: {value:"ms"},
|
||||
reset: {value:""},
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
@@ -116,14 +124,14 @@
|
||||
}
|
||||
});
|
||||
$("#node-input-op1type").change(function() {
|
||||
if ($(this).val() == "val") {
|
||||
if (($(this).val() == "val")||($(this).val() == "num")) {
|
||||
$("#node-input-op1").show();
|
||||
} else {
|
||||
$("#node-input-op1").hide();
|
||||
}
|
||||
});
|
||||
$("#node-input-op2type").change(function() {
|
||||
if ($(this).val() == "val") {
|
||||
if (($(this).val() == "val")||($(this).val() == "num")) {
|
||||
$("#node-input-op2").show();
|
||||
} else {
|
||||
$("#node-input-op2").hide();
|
||||
|
||||
@@ -25,6 +25,7 @@ module.exports = function(RED) {
|
||||
this.op2type = n.op2type || "val";
|
||||
this.extend = n.extend || "false";
|
||||
this.units = n.units || "ms";
|
||||
this.reset = n.reset || '';
|
||||
this.duration = n.duration || 250;
|
||||
if (this.duration <= 0) { this.duration = 0; }
|
||||
else {
|
||||
@@ -34,24 +35,24 @@ module.exports = function(RED) {
|
||||
}
|
||||
this.op1Templated = this.op1.indexOf("{{") != -1;
|
||||
this.op2Templated = this.op2.indexOf("{{") != -1;
|
||||
if (!isNaN(this.op1)) { this.op1 = Number(this.op1); }
|
||||
if (!isNaN(this.op2)) { this.op2 = Number(this.op2); }
|
||||
if ((this.op1type === "num") && (!isNaN(this.op1))) { this.op1 = Number(this.op1); }
|
||||
if ((this.op2type === "num") && (!isNaN(this.op2))) { this.op2 = Number(this.op2); }
|
||||
if (this.op1 == "true") { this.op1 = true; }
|
||||
if (this.op2 == "true") { this.op2 = true; }
|
||||
if (this.op1 == "false") { this.op1 = false; }
|
||||
if (this.op2 == "false") { this.op2 = false; }
|
||||
if (this.op1 == "null") { this.op1 = null; }
|
||||
if (this.op2 == "null") { this.op2 = null; }
|
||||
try { this.op1 = JSON.parse(this.op1); }
|
||||
catch(e) { this.op1 = this.op1; }
|
||||
try { this.op2 = JSON.parse(this.op2); }
|
||||
catch(e) { this.op2 = this.op2; }
|
||||
//try { this.op1 = JSON.parse(this.op1); }
|
||||
//catch(e) { this.op1 = this.op1; }
|
||||
//try { this.op2 = JSON.parse(this.op2); }
|
||||
//catch(e) { this.op2 = this.op2; }
|
||||
|
||||
var node = this;
|
||||
var tout = null;
|
||||
var m2;
|
||||
this.on("input", function(msg) {
|
||||
if (msg.hasOwnProperty("reset")) {
|
||||
if (msg.hasOwnProperty("reset") || ((node.reset !== '')&&(msg.payload == node.reset)) ) {
|
||||
clearTimeout(tout);
|
||||
tout = null;
|
||||
node.status({});
|
||||
@@ -90,8 +91,8 @@ module.exports = function(RED) {
|
||||
this.on("close", function() {
|
||||
if (tout) {
|
||||
clearTimeout(tout);
|
||||
node.status({});
|
||||
}
|
||||
node.status({});
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("trigger",TriggerNode);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2013, 2015 IBM Corp.
|
||||
Copyright 2013, 2016 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -55,33 +55,6 @@
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var that = this;
|
||||
$( "#node-input-outputs" ).spinner({
|
||||
min:1
|
||||
});
|
||||
function functionDialogResize() {
|
||||
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-text-editor-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$(".node-text-editor").css("height",height+"px");
|
||||
that.editor.resize();
|
||||
};
|
||||
$( "#dialog" ).on("dialogresize", functionDialogResize);
|
||||
$( "#dialog" ).one("dialogopen", function(ev) {
|
||||
var size = $( "#dialog" ).dialog('option','sizeCache-function');
|
||||
if (size) {
|
||||
$("#dialog").dialog('option','width',size.width);
|
||||
$("#dialog").dialog('option','height',size.height);
|
||||
functionDialogResize();
|
||||
}
|
||||
});
|
||||
$( "#dialog" ).one("dialogclose", function(ev,ui) {
|
||||
var height = $( "#dialog" ).dialog('option','height');
|
||||
$( "#dialog" ).off("dialogresize",functionDialogResize);
|
||||
});
|
||||
this.editor = RED.editor.createEditor({
|
||||
id: 'node-input-info-editor',
|
||||
mode: 'ace/mode/markdown',
|
||||
@@ -92,6 +65,17 @@
|
||||
oneditsave: function() {
|
||||
$("#node-input-info").val(this.editor.getValue());
|
||||
delete this.editor;
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-text-editor-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$(".node-text-editor").css("height",height+"px");
|
||||
this.editor.resize();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -41,16 +41,18 @@
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-intype"><i class="fa fa-level-up"></i> <span data-i18n="rpi-gpio.label.resistor"></span></label>
|
||||
<select type="text" id="node-input-intype" style="width: 150px;">
|
||||
<select type="text" id="node-input-intype" style="width:100px;">
|
||||
<option value="tri" data-i18n="rpi-gpio.resistor.none"></option>
|
||||
<option value="up" data-i18n="rpi-gpio.resistor.pullup"></option>
|
||||
<option value="down" data-i18n="rpi-gpio.resistor.pulldown"></option>
|
||||
</select>
|
||||
<span data-i18n="rpi-gpio.label.debounce"></span>
|
||||
<input type="text" id="node-input-debounce" style="width:47px; text-align:right"/> mS
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-read" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-read" style="width: 70%;"><span data-i18n="rpi-gpio.label.readinitial"></span></label>
|
||||
<label for="node-input-read" style="width:70%;"><span data-i18n="rpi-gpio.label.readinitial"></span></label>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="form-row">
|
||||
@@ -62,10 +64,11 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="rpi-gpio in">
|
||||
<p>Raspberry Pi input node. Generates a <b>msg.payload</b> with either a 0 or 1 depending on the state of the input pin. Requires the gpio command to work.</p>
|
||||
<p>Raspberry Pi input node. Generates a <code>msg.payload</code> with either a
|
||||
0 or 1 depending on the state of the input pin.</p>
|
||||
<p>You may also enable the input pullup resistor or the pulldown resistor.</p>
|
||||
<p>The <b>msg.topic</b> is set to <i>pi/{the pin number}</i></p>
|
||||
<p>Requires the RPi.GPIO python library version 0.5.8 (or better) in order to work.</p>
|
||||
<p>The <code>msg.topic</code> is set to <i>pi/{the pin number}</i></p>
|
||||
<p>Requires the RPi.GPIO python library version 0.5.10 (or better) in order to work.</p>
|
||||
<p><b>Note:</b> we are using the actual physical pin numbers on connector P1 as they are easier to locate.</p>
|
||||
</script>
|
||||
|
||||
@@ -77,8 +80,9 @@
|
||||
color:"#c6dbef",
|
||||
defaults: {
|
||||
name: { value:"" },
|
||||
pin: { value:"",required:true,validate:RED.validators.number() },
|
||||
intype: { value: "in" },
|
||||
pin: { value:"tri",required:true,validate:RED.validators.number() },
|
||||
intype: { value:"in" },
|
||||
debounce: { value:"25" },
|
||||
read: { value:false }
|
||||
},
|
||||
inputs:0,
|
||||
@@ -104,7 +108,7 @@
|
||||
var alreadyset = this._("rpi-gpio.alreadyset");
|
||||
$.getJSON('rpi-gpio/'+this.id,function(data) {
|
||||
$('#pitype').text(data.type);
|
||||
if ((data.type === "Model B+") || (data.type === "Model A+")) {
|
||||
if ((data.type !== "Model B") && (data.type !== "Model A")) {
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",27).text("27 - SDA0 - BCM0"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",28).text("28 - SCL0 - BCM1"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",29).text("29 - GPIO21 - BCM5"));
|
||||
@@ -200,11 +204,14 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="rpi-gpio out">
|
||||
<p>Raspberry Pi output node. Expects a <b>msg.payload</b> with either a 0 or 1 (or true or false). Requires the gpio command to work.</p>
|
||||
<p>Will set the selected physical pin high or low depending on the value passed in.</p>
|
||||
<p>Raspberry Pi output node. Can be used in Digital, PWM or Servo modes.
|
||||
<p>Digital mode expects a <code>msg.payload</code> with either a 0 or 1 (or true or false),
|
||||
and will set the selected physical pin high or low depending on the value passed in.</p>
|
||||
<p>The initial value of the pin at deploy time can also be set to 0 or 1.</p>
|
||||
<p>When using PWM mode - expects an input value of a number 0 - 100.</p>
|
||||
<p>Requires the RPi.GPIO python library version 0.5.8 (or better) in order to work.</p>
|
||||
<p>When using PWM mode - expects an input value of a number 0 - 100. It can be floating point.</p>
|
||||
<p>PWM mode can be used to drive a servo using input values between 10 and 20 only.
|
||||
The GPIO2 pin is best for this as it uses hardware to do the PWM.</p>
|
||||
<p>Requires the RPi.GPIO python library version 0.5.10 (or better) in order to work.</p>
|
||||
<p><b>Note:</b> we are using the actual physical pin numbers on connector P1 as they are easier to locate.</p>
|
||||
</script>
|
||||
|
||||
@@ -247,7 +254,7 @@
|
||||
if (!$("#node-input-out").val()) { $("#node-input-out").val("out"); }
|
||||
$.getJSON('rpi-gpio/'+this.id,function(data) {
|
||||
$('#pitype').text(data.type);
|
||||
if ((data.type === "Model B+") || (data.type === "Model A+")) {
|
||||
if ((data.type !== "Model B") && (data.type !== "Model A")) {
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",27).text("27 - SDA0 - BCM0"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",28).text("28 - SCL0 - BCM1"));
|
||||
$('#node-input-pin').append($("<option></option>").attr("value",29).text("29 - GPIO21 - BCM5"));
|
||||
@@ -334,12 +341,12 @@
|
||||
|
||||
<script type="text/x-red" data-help-name="rpi-mouse">
|
||||
<p>Raspberry Pi mouse button node. Requires a USB mouse.</p>
|
||||
<p>Generates a <b>msg.payload</b> with
|
||||
<p>Generates a <code>msg.payload</code> with
|
||||
either a 1 or 0 when the selected mouse button is pressed and released</p>
|
||||
<p>Also sets <b>msg.button</b> to the code value
|
||||
<p>Also sets <code>msg.button</code> to the code value
|
||||
<ul><li>1 = left</li><li>2 = right</li><li>4 = middle</li></ul>
|
||||
so you can work out which button or combination was pressed.</p>
|
||||
<p>And sets <b>msg.topic</b> to <i>pi/mouse</i>.</p>
|
||||
<p>And sets <code>msg.topic</code> to <i>pi/mouse</i>.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
@@ -366,3 +373,37 @@
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="rpi-keyboard">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="rpi-keyboard">
|
||||
<p>Raspberry Pi keyboard handling node. Requires a USB keyboard.</p>
|
||||
<p>Generates a <code>msg.payload</code> with a keycode, and <code>msg.action</code> set to
|
||||
<i>up, down</i> or <i>repeat</i> whenever a key is pressed or released.</p>
|
||||
<p>It also sets <code>msg.topic</code> to <i>pi/key</i>.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('rpi-keyboard',{
|
||||
category: 'Raspberry Pi',
|
||||
label: 'Raspberry Pi',
|
||||
color:"#c6dbef",
|
||||
defaults: {
|
||||
name: { value:"" }
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "rpi.png",
|
||||
label: function() {
|
||||
return this.name||this._("rpi-gpio.label.pikeyboard");;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013,2015 IBM Corp.
|
||||
* Copyright 2013,2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,21 +18,25 @@ module.exports = function(RED) {
|
||||
"use strict";
|
||||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
var fs = require('fs');
|
||||
var fs = require('fs');
|
||||
|
||||
var gpioCommand = __dirname+'/nrgpio.py';
|
||||
var gpioCommand = __dirname+'/nrgpio';
|
||||
|
||||
if (!fs.existsSync("/dev/ttyAMA0")) { // unlikely if not on a Pi
|
||||
try {
|
||||
fs.statSync("/dev/ttyAMA0"); // unlikely if not on a Pi
|
||||
} catch(err) {
|
||||
//RED.log.info(RED._("rpi-gpio.errors.ignorenode"));
|
||||
throw "Info : "+RED._("rpi-gpio.errors.ignorenode");
|
||||
}
|
||||
|
||||
if (!fs.existsSync("/usr/share/doc/python-rpi.gpio")) {
|
||||
try {
|
||||
fs.statSync("/usr/share/doc/python-rpi.gpio");
|
||||
} catch(err) {
|
||||
RED.log.warn(RED._("rpi-gpio.errors.libnotfound"));
|
||||
throw "Warning : "+RED._("rpi-gpio.errors.libnotfound");
|
||||
}
|
||||
|
||||
if ( !(1 & parseInt ((fs.statSync(gpioCommand).mode & parseInt ("777", 8)).toString (8)[0]) )) {
|
||||
if ( !(1 & parseInt((fs.statSync(gpioCommand).mode & parseInt("777", 8)).toString(8)[0]) )) {
|
||||
RED.log.error(RED._("rpi-gpio.errors.needtobeexecutable",{command:gpioCommand}));
|
||||
throw "Error : "+RED._("rpi-gpio.errors.mustbeexecutable");
|
||||
}
|
||||
@@ -49,6 +53,7 @@ module.exports = function(RED) {
|
||||
this.pin = n.pin;
|
||||
this.intype = n.intype;
|
||||
this.read = n.read || false;
|
||||
this.debounce = Number(n.debounce || 25);
|
||||
if (this.read) { this.buttonState = -2; }
|
||||
var node = this;
|
||||
if (!pinsInUse.hasOwnProperty(this.pin)) {
|
||||
@@ -61,18 +66,14 @@ module.exports = function(RED) {
|
||||
}
|
||||
|
||||
if (node.pin !== undefined) {
|
||||
if (node.intype === "tri") {
|
||||
node.child = spawn(gpioCommand, ["in",node.pin]);
|
||||
} else {
|
||||
node.child = spawn(gpioCommand, ["in",node.pin,node.intype]);
|
||||
}
|
||||
node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]);
|
||||
node.running = true;
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
|
||||
|
||||
node.child.stdout.on('data', function (data) {
|
||||
data = data.toString().trim();
|
||||
if (data.length > 0) {
|
||||
if (node.buttonState !== -1) {
|
||||
if (node.running && node.buttonState !== -1) {
|
||||
node.send({ topic:"pi/"+node.pin, payload:Number(data) });
|
||||
}
|
||||
node.buttonState = data;
|
||||
@@ -86,8 +87,8 @@ module.exports = function(RED) {
|
||||
});
|
||||
|
||||
node.child.on('close', function (code) {
|
||||
node.child = null;
|
||||
node.running = false;
|
||||
node.child = null;
|
||||
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
|
||||
if (node.done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
@@ -120,7 +121,6 @@ module.exports = function(RED) {
|
||||
}
|
||||
RED.nodes.registerType("rpi-gpio in",GPIOInNode);
|
||||
|
||||
|
||||
function GPIOOutNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.pin = n.pin;
|
||||
@@ -160,7 +160,7 @@ module.exports = function(RED) {
|
||||
if (node.pin !== undefined) {
|
||||
if (node.set && (node.out === "out")) {
|
||||
node.child = spawn(gpioCommand, [node.out,node.pin,node.level]);
|
||||
} else {
|
||||
} else {
|
||||
node.child = spawn(gpioCommand, [node.out,node.pin]);
|
||||
}
|
||||
node.running = true;
|
||||
@@ -210,20 +210,6 @@ module.exports = function(RED) {
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
var pitype = { type:"" };
|
||||
exec(gpioCommand+" rev 0", function(err,stdout,stderr) {
|
||||
if (err) {
|
||||
RED.log.info(RED._("rpi-gpio.errors.version"));
|
||||
}
|
||||
else {
|
||||
if (stdout.trim() == "0") { pitype = { type:"Compute" }; }
|
||||
else if (stdout.trim() == "1") { pitype = { type:"A/B v1" }; }
|
||||
else if (stdout.trim() == "2") { pitype = { type:"A/B v2" }; }
|
||||
else if (stdout.trim() == "3") { pitype = { type:"Model B+" }; }
|
||||
else { RED.log.info(RED._("rpi-gpio.errors.sawpitype"),stdout.trim()); }
|
||||
}
|
||||
});
|
||||
RED.nodes.registerType("rpi-gpio out",GPIOOutNode);
|
||||
|
||||
function PiMouseNode(n) {
|
||||
@@ -231,7 +217,7 @@ module.exports = function(RED) {
|
||||
this.butt = n.butt || 7;
|
||||
var node = this;
|
||||
|
||||
node.child = spawn(gpioCommand, ["mouse",node.butt]);
|
||||
node.child = spawn(gpioCommand+".py", ["mouse",node.butt]);
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
|
||||
|
||||
node.child.stdout.on('data', function (data) {
|
||||
@@ -273,6 +259,70 @@ module.exports = function(RED) {
|
||||
}
|
||||
RED.nodes.registerType("rpi-mouse",PiMouseNode);
|
||||
|
||||
function PiKeyboardNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
|
||||
node.child = spawn(gpioCommand+".py", ["kbd","0"]);
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.ok"});
|
||||
|
||||
node.child.stdout.on('data', function (data) {
|
||||
var b = data.toString().trim().split(",");
|
||||
var act = "up";
|
||||
if (b[1] === "1") { act = "down"; }
|
||||
if (b[1] === "2") { act = "repeat"; }
|
||||
node.send({ topic:"pi/key", payload:Number(b[0]), action:act });
|
||||
});
|
||||
|
||||
node.child.stderr.on('data', function (data) {
|
||||
if (RED.settings.verbose) { node.log("err: "+data+" :"); }
|
||||
});
|
||||
|
||||
node.child.on('close', function (code) {
|
||||
node.running = false;
|
||||
node.child = null;
|
||||
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
|
||||
if (node.done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
node.done();
|
||||
}
|
||||
else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
|
||||
});
|
||||
|
||||
node.child.on('error', function (err) {
|
||||
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
|
||||
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
|
||||
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
|
||||
});
|
||||
|
||||
node.on("close", function(done) {
|
||||
node.status({});
|
||||
if (node.child != null) {
|
||||
node.done = done;
|
||||
node.child.kill('SIGINT');
|
||||
node.child = null;
|
||||
}
|
||||
else { done(); }
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("rpi-keyboard",PiKeyboardNode);
|
||||
|
||||
var pitype = { type:"" };
|
||||
exec(gpioCommand+" info", function(err,stdout,stderr) {
|
||||
if (err) {
|
||||
RED.log.info(RED._("rpi-gpio.errors.version"));
|
||||
}
|
||||
else {
|
||||
try {
|
||||
var info = JSON.parse( stdout.trim().replace(/\'/g,"\"") );
|
||||
pitype.type = info["TYPE"];
|
||||
}
|
||||
catch(e) {
|
||||
RED.log.info(RED._("rpi-gpio.errors.sawpitype"),stdout.trim());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
RED.httpAdmin.get('/rpi-gpio/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) {
|
||||
res.json(pitype);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright 2014 IBM Corp.
|
||||
# Copyright 2014,2016 IBM Corp.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -15,9 +15,13 @@
|
||||
|
||||
# Import library functions we need
|
||||
import RPi.GPIO as GPIO
|
||||
import struct
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
from time import sleep
|
||||
|
||||
bounce = 20 # bounce time in mS to apply
|
||||
bounce = 25;
|
||||
|
||||
if sys.version_info >= (3,0):
|
||||
print("Sorry - currently only configured to work with python 2.x")
|
||||
@@ -92,18 +96,18 @@ if len(sys.argv) > 2:
|
||||
|
||||
elif cmd == "in":
|
||||
#print "Initialised pin "+str(pin)+" to IN"
|
||||
bounce = int(sys.argv[4])
|
||||
def handle_callback(chan):
|
||||
sleep(bounce/1000)
|
||||
print GPIO.input(chan)
|
||||
|
||||
if len(sys.argv) == 4:
|
||||
if sys.argv[3].lower() == "up":
|
||||
GPIO.setup(pin,GPIO.IN,GPIO.PUD_UP)
|
||||
elif sys.argv[3].lower() == "down":
|
||||
GPIO.setup(pin,GPIO.IN,GPIO.PUD_DOWN)
|
||||
else:
|
||||
GPIO.setup(pin,GPIO.IN)
|
||||
if sys.argv[3].lower() == "up":
|
||||
GPIO.setup(pin,GPIO.IN,GPIO.PUD_UP)
|
||||
elif sys.argv[3].lower() == "down":
|
||||
GPIO.setup(pin,GPIO.IN,GPIO.PUD_DOWN)
|
||||
else:
|
||||
GPIO.setup(pin,GPIO.IN)
|
||||
|
||||
print GPIO.input(pin)
|
||||
GPIO.add_event_detect(pin, GPIO.BOTH, callback=handle_callback, bouncetime=bounce)
|
||||
|
||||
@@ -166,12 +170,6 @@ if len(sys.argv) > 2:
|
||||
except:
|
||||
data = 0
|
||||
|
||||
elif cmd == "rev":
|
||||
print GPIO.RPI_REVISION
|
||||
|
||||
elif cmd == "ver":
|
||||
print GPIO.VERSION
|
||||
|
||||
elif cmd == "mouse": # catch mice button events
|
||||
file = open( "/dev/input/mice", "rb" )
|
||||
oldbutt = 0
|
||||
@@ -193,15 +191,40 @@ if len(sys.argv) > 2:
|
||||
file.close()
|
||||
sys.exit(0)
|
||||
|
||||
elif cmd == "kbd": # catch keyboard button events
|
||||
try:
|
||||
while not os.path.isdir("/dev/input/by-path"):
|
||||
time.sleep(10)
|
||||
infile = subprocess.check_output("ls /dev/input/by-path/ | grep -m 1 'kbd'", shell=True).strip()
|
||||
infile_path = "/dev/input/by-path/" + infile
|
||||
EVENT_SIZE = struct.calcsize('llHHI')
|
||||
file = open(infile_path, "rb")
|
||||
event = file.read(EVENT_SIZE)
|
||||
while event:
|
||||
(tv_sec, tv_usec, type, code, value) = struct.unpack('llHHI', event)
|
||||
#if type != 0 or code != 0 or value != 0:
|
||||
if type == 1:
|
||||
# type,code,value
|
||||
print("%u,%u" % (code, value))
|
||||
event = file.read(EVENT_SIZE)
|
||||
print "0,0"
|
||||
file.close()
|
||||
sys.exit(0)
|
||||
except:
|
||||
file.close()
|
||||
sys.exit(0)
|
||||
|
||||
elif len(sys.argv) > 1:
|
||||
cmd = sys.argv[1].lower()
|
||||
if cmd == "rev":
|
||||
print GPIO.RPI_REVISION
|
||||
elif cmd == "ver":
|
||||
print GPIO.VERSION
|
||||
elif cmd == "info":
|
||||
print GPIO.RPI_INFO
|
||||
else:
|
||||
print "Bad parameters - in|out|pwm|buzz|byte|borg|mouse|ver {pin} {value|up|down}"
|
||||
print " only ver (gpio version) and rev (board revision) accept no pin parameter."
|
||||
print "Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}"
|
||||
print " only ver (gpio version) and info (board information) accept no pin parameter."
|
||||
|
||||
else:
|
||||
print "Bad parameters - in|out|pwm|buzz|byte|borg|mouse|ver {pin} {value|up|down}"
|
||||
print "Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2013,2015 IBM Corp.
|
||||
Copyright 2013, 2016 IBM Corp.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
@@ -27,9 +27,15 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="mqtt in">
|
||||
<p>MQTT input node. Connects to a broker and subscribes to the specified topic. The topic may contain MQTT wildcards.</p>
|
||||
<p>Outputs an object called <b>msg</b> containing <b>msg.topic, msg.payload, msg.qos</b> and <b>msg.retain</b>.</p>
|
||||
<p><b>msg.payload</b> is usually a string, but can also be a binary buffer.</p>
|
||||
<p>Connects to a broker and subscribes to the specified topic.</p>
|
||||
<p>Outputs a message with the properties:</p>
|
||||
<ul>
|
||||
<li><code>msg.topic</code></li>
|
||||
<li><code>msg.payload</code></li>
|
||||
<li><code>msg.qos</code></li>
|
||||
<li><code>msg.retain</code></li>
|
||||
</ul>
|
||||
<p><code>msg.payload</code> will be a String, unless it is detected as a binary buffer.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
@@ -37,7 +43,7 @@
|
||||
category: 'input',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
topic: {value:"",required:true},
|
||||
topic: {value:"",required:true,validate: RED.validators.regex(/^(#$|(\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/)},
|
||||
broker: {type:"mqtt-broker", required:true}
|
||||
},
|
||||
color:"#d8bfd8",
|
||||
@@ -84,9 +90,14 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="mqtt out">
|
||||
<p>Connects to a MQTT broker and publishes <b>msg.payload</b> either to the <b>msg.topic</b> or to the topic specified in the edit window. The value in the edit window has precedence.</p>
|
||||
<p>Likewise QoS and/or retain values in the edit panel will overwrite any <b>msg.qos</b> and <b>msg.retain</b> properties. If nothing is set they default to <i>0</i> and <i>false</i> respectively.</p>
|
||||
<p>If <b>msg.payload</b> contains an object it will be stringified before being sent.</p>
|
||||
<p>Connects to a MQTT broker and publishes messages.</p>
|
||||
<p>The topic used can be configured in the node or, if left blank, can be set
|
||||
by <code>msg.topic</code>.</p>
|
||||
<p>Likewise the QoS and retain values can be configured in the node or, if left
|
||||
blank, set by <code>msg.qos</code> and <code>msg.retain</code> respectively.
|
||||
By default, messages are published at QoS 0 with the retain flag set to false.</p>
|
||||
<p>If <code>msg.payload</code> contains an object it will be converted to JSON
|
||||
before being sent.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
@@ -226,7 +237,7 @@
|
||||
usetls: {value: false},
|
||||
verifyservercert: { value: false},
|
||||
compatmode: { value: true},
|
||||
keepalive: {value:15,validate:RED.validators.number()},
|
||||
keepalive: {value:60,validate:RED.validators.number()},
|
||||
cleansession: {value: true},
|
||||
willTopic: {value:""},
|
||||
willQos: {value:"0"},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013,2015 IBM Corp.
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -45,6 +45,7 @@ module.exports = function(RED) {
|
||||
this.brokerurl = "";
|
||||
this.connected = false;
|
||||
this.connecting = false;
|
||||
this.closing = false;
|
||||
this.options = {};
|
||||
this.queue = [];
|
||||
this.subscriptions = {};
|
||||
@@ -75,7 +76,7 @@ module.exports = function(RED) {
|
||||
this.verifyservercert = false;
|
||||
}
|
||||
if (typeof this.keepalive === 'undefined'){
|
||||
this.keepalive = 15;
|
||||
this.keepalive = 60;
|
||||
} else if (typeof this.keepalive === 'string') {
|
||||
this.keepalive = Number(this.keepalive);
|
||||
}
|
||||
@@ -136,11 +137,17 @@ module.exports = function(RED) {
|
||||
}
|
||||
};
|
||||
|
||||
this.deregister = function(mqttNode){
|
||||
this.deregister = function(mqttNode,done){
|
||||
delete node.users[mqttNode.id];
|
||||
if (Object.keys(node.users).length === 0) {
|
||||
node.client.end();
|
||||
if (node.closing) {
|
||||
return done();
|
||||
}
|
||||
if (Object.keys(node.users).length === 0) {
|
||||
if (node.client) {
|
||||
return node.client.end(done);
|
||||
}
|
||||
}
|
||||
done();
|
||||
};
|
||||
|
||||
this.connect = function () {
|
||||
@@ -177,15 +184,14 @@ module.exports = function(RED) {
|
||||
if (node.birthMessage) {
|
||||
node.publish(node.birthMessage);
|
||||
}
|
||||
|
||||
// Send any queued messages
|
||||
while(node.queue.length) {
|
||||
var msg = node.queue.shift();
|
||||
//console.log(msg);
|
||||
node.publish(msg);
|
||||
}
|
||||
});
|
||||
|
||||
node.client.on("reconnect", function() {
|
||||
for (var id in node.users) {
|
||||
if (node.users.hasOwnProperty(id)) {
|
||||
node.users[id].status({fill:"yellow",shape:"ring",text:"common.status.connecting"});
|
||||
}
|
||||
}
|
||||
})
|
||||
// Register disconnect handlers
|
||||
node.client.on('close', function () {
|
||||
if (node.connected) {
|
||||
@@ -265,17 +271,13 @@ module.exports = function(RED) {
|
||||
retain: msg.retain || false
|
||||
};
|
||||
node.client.publish(msg.topic, msg.payload, options, function (err){return});
|
||||
} else {
|
||||
if (!node.connecting) {
|
||||
node.connect();
|
||||
}
|
||||
node.queue.push(msg);
|
||||
}
|
||||
};
|
||||
|
||||
this.on('close', function(done) {
|
||||
this.closing = true;
|
||||
if (this.connected) {
|
||||
this.client.on('close', function() {
|
||||
this.client.once('close', function() {
|
||||
done();
|
||||
});
|
||||
this.client.end();
|
||||
@@ -298,10 +300,14 @@ module.exports = function(RED) {
|
||||
this.topic = n.topic;
|
||||
this.broker = n.broker;
|
||||
this.brokerConn = RED.nodes.getNode(this.broker);
|
||||
if (!/^(#$|(\+|[^+#]*)(\/(\+|[^+#]*))*(\/(\+|#|[^+#]*))?$)/.test(this.topic)) {
|
||||
return this.warn(RED._("mqtt.errors.invalid-topic"));
|
||||
}
|
||||
var node = this;
|
||||
if (this.brokerConn) {
|
||||
this.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
|
||||
if (this.topic) {
|
||||
node.brokerConn.register(this);
|
||||
this.brokerConn.subscribe(this.topic,2,function(topic,payload,packet) {
|
||||
if (isUtf8(payload)) { payload = payload.toString(); }
|
||||
var msg = {topic:topic,payload:payload, qos: packet.qos, retain: packet.retain};
|
||||
@@ -313,15 +319,14 @@ module.exports = function(RED) {
|
||||
if (this.brokerConn.connected) {
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||
}
|
||||
node.brokerConn.register(this);
|
||||
}
|
||||
else {
|
||||
this.error(RED._("mqtt.errors.not-defined"));
|
||||
}
|
||||
this.on('close', function() {
|
||||
this.on('close', function(done) {
|
||||
if (node.brokerConn) {
|
||||
node.brokerConn.unsubscribe(node.topic,node.id);
|
||||
node.brokerConn.deregister(node);
|
||||
node.brokerConn.deregister(node,done);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -365,8 +370,8 @@ module.exports = function(RED) {
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||
}
|
||||
node.brokerConn.register(node);
|
||||
this.on('close', function() {
|
||||
node.brokerConn.deregister(node);
|
||||
this.on('close', function(done) {
|
||||
node.brokerConn.deregister(node,done);
|
||||
});
|
||||
} else {
|
||||
this.error(RED._("mqtt.errors.missing-config"));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013,2015 IBM Corp.
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -63,6 +63,8 @@ module.exports = function(RED) {
|
||||
var corsSetup = false;
|
||||
|
||||
function createRequestWrapper(node,req) {
|
||||
// This misses a bunch of properties (eg headers). Before we use this function
|
||||
// need to ensure it captures everything documented by Express and HTTP modules.
|
||||
var wrapper = {
|
||||
_req: req
|
||||
};
|
||||
@@ -98,7 +100,7 @@ module.exports = function(RED) {
|
||||
wrapper[f] = function() {
|
||||
node.warn(RED._("httpin.errors.deprecated-call",{method:"msg.req."+f}));
|
||||
var result = req[f].apply(req,arguments);
|
||||
if (result === res) {
|
||||
if (result === req) {
|
||||
return wrapper;
|
||||
} else {
|
||||
return result;
|
||||
@@ -154,6 +156,13 @@ module.exports = function(RED) {
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
var corsHandler = function(req,res,next) { next(); }
|
||||
|
||||
if (RED.settings.httpNodeCors) {
|
||||
corsHandler = cors(RED.settings.httpNodeCors);
|
||||
RED.httpNode.options("*",corsHandler);
|
||||
}
|
||||
|
||||
function HTTPIn(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
if (RED.settings.httpNodeRoot !== false) {
|
||||
@@ -185,14 +194,6 @@ module.exports = function(RED) {
|
||||
}
|
||||
};
|
||||
|
||||
var corsHandler = function(req,res,next) { next(); }
|
||||
|
||||
if (RED.settings.httpNodeCors && !corsSetup) {
|
||||
corsHandler = cors(RED.settings.httpNodeCors);
|
||||
RED.httpNode.options("*",corsHandler);
|
||||
corsSetup = true;
|
||||
}
|
||||
|
||||
var httpMiddleware = function(req,res,next) { next(); }
|
||||
|
||||
if (RED.settings.httpNodeMiddleware) {
|
||||
|
||||
@@ -28,6 +28,8 @@ module.exports = function(RED) {
|
||||
var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
|
||||
var nodeMethod = n.method || "GET";
|
||||
this.ret = n.ret || "txt";
|
||||
if (RED.settings.httpRequestTimeout) { this.reqTimeout = parseInt(RED.settings.httpRequestTimeout) || 120000; }
|
||||
else { this.reqTimeout = 120000; }
|
||||
var node = this;
|
||||
|
||||
var prox, noprox;
|
||||
@@ -162,6 +164,13 @@ module.exports = function(RED) {
|
||||
node.status({});
|
||||
});
|
||||
});
|
||||
req.setTimeout(node.reqTimeout, function() {
|
||||
node.error(RED._("common.notification.errors.no-response"),msg);
|
||||
setTimeout(function() {
|
||||
node.status({fill:"red",shape:"ring",text:"common.notification.errors.no-response"});
|
||||
},10);
|
||||
req.abort();
|
||||
});
|
||||
req.on('error',function(err) {
|
||||
msg.payload = err.toString() + " : " + url;
|
||||
msg.statusCode = err.code;
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
<script type="text/x-red" data-help-name="websocket in">
|
||||
<p>WebSocket input node.</p>
|
||||
<p>By default, the data received from the WebSocket will be in <b>msg.payload</b>.
|
||||
<p>By default, the data received from the WebSocket will be in <code>msg.payload</code>.
|
||||
The socket can be configured to expect a properly formed JSON string, in which
|
||||
case it will parse the JSON and send on the resulting object as the entire message.</p>
|
||||
</script>
|
||||
@@ -48,7 +48,7 @@
|
||||
(function() {
|
||||
|
||||
function ws_oneditprepare() {
|
||||
$("#websocket-client-row").hide();
|
||||
$("#websocket-client-row").hide();
|
||||
$("#node-input-mode").change(function(){
|
||||
if( $("#node-input-mode").val() === 'client') {
|
||||
$("#websocket-server-row").hide();
|
||||
@@ -59,7 +59,7 @@
|
||||
$("#websocket-client-row").hide();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if(this.client) {
|
||||
$("#node-input-mode").val('client').change();
|
||||
}
|
||||
@@ -67,24 +67,24 @@
|
||||
$("#node-input-mode").val('server').change();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ws_oneditsave() {
|
||||
if($("#node-input-mode").val() === 'client') {
|
||||
$("#node-input-server").append('<option value="">Dummy</option>');
|
||||
$("#node-input-server").val('');
|
||||
$("#node-input-server").val('');
|
||||
}
|
||||
else {
|
||||
$("#node-input-client").append('<option value="">Dummy</option>');
|
||||
$("#node-input-client").val('');
|
||||
$("#node-input-client").val('');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ws_label() {
|
||||
var nodeid = (this.client)?this.client:this.server;
|
||||
var wsNode = RED.nodes.node(nodeid);
|
||||
return this.name||(wsNode?"[ws] "+wsNode.label():"websocket");
|
||||
}
|
||||
|
||||
|
||||
function ws_validateserver() {
|
||||
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
|
||||
return true;
|
||||
@@ -93,7 +93,7 @@
|
||||
return RED.nodes.node(this.server) != null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ws_validateclient() {
|
||||
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
|
||||
return RED.nodes.node(this.client) != null;
|
||||
@@ -121,7 +121,7 @@
|
||||
oneditsave: ws_oneditsave,
|
||||
oneditprepare: ws_oneditprepare
|
||||
});
|
||||
|
||||
|
||||
RED.nodes.registerType('websocket out',{
|
||||
category: 'output',
|
||||
defaults: {
|
||||
@@ -141,7 +141,7 @@
|
||||
oneditsave: ws_oneditsave,
|
||||
oneditprepare: ws_oneditprepare
|
||||
});
|
||||
|
||||
|
||||
RED.nodes.registerType('websocket-listener',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
@@ -152,7 +152,7 @@
|
||||
outputs:0,
|
||||
label: function() {
|
||||
var root = RED.settings.httpNodeRoot;
|
||||
if (root.slice(-1) != "/") {
|
||||
if (root.slice(-1) != "/") {
|
||||
root = root+"/";
|
||||
}
|
||||
if (this.path.charAt(0) == "/") {
|
||||
@@ -164,7 +164,7 @@
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var root = RED.settings.httpNodeRoot;
|
||||
if (root.slice(-1) == "/") {
|
||||
if (root.slice(-1) == "/") {
|
||||
root = root.slice(0,-1);
|
||||
}
|
||||
if (root == "") {
|
||||
@@ -188,7 +188,7 @@
|
||||
return this.path;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -217,15 +217,15 @@
|
||||
|
||||
<script type="text/x-red" data-help-name="websocket out">
|
||||
<p>WebSocket out node.</p>
|
||||
<p>By default, <b>msg.payload</b> will be sent over the WebSocket. The socket
|
||||
can be configured to encode the entire message object as a JSON string and send that
|
||||
<p>By default, <code>msg.payload</code> will be sent over the WebSocket. The socket
|
||||
can be configured to encode the entire <code>msg</code> object as a JSON string and send that
|
||||
over the WebSocket.</p>
|
||||
|
||||
<p>If the message arriving at this node started at a WebSocket In node, the message
|
||||
will be sent back to the client that triggered the flow. Otherwise, the message
|
||||
will be broadcast to all connected clients.</p>
|
||||
<p>If you want to broadcast a message that started at a WebSocket In node, you
|
||||
should delete the <b>msg._session</b> property within the flow</p>.
|
||||
should delete the <code>msg._session</code> property within the flow.</p>
|
||||
</script>
|
||||
|
||||
<!-- WebSocket Server configuration node -->
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013,2015 IBM Corp.
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -82,7 +82,15 @@ module.exports = function(RED) {
|
||||
RED.server.addListener('newListener',storeListener);
|
||||
|
||||
// Create a WebSocket Server
|
||||
node.server = new ws.Server({server:RED.server,path:path});
|
||||
node.server = new ws.Server({
|
||||
server:RED.server,
|
||||
path:path,
|
||||
// Disable the deflate option due to this issue
|
||||
// https://github.com/websockets/ws/pull/632
|
||||
// that is fixed in the 1.x release of the ws module
|
||||
// that we cannot currently pickup as it drops node 0.10 support
|
||||
perMessageDeflate: false
|
||||
});
|
||||
|
||||
// Workaround https://github.com/einaros/ws/pull/253
|
||||
// Stop listening for new listener events
|
||||
|
||||
@@ -31,11 +31,11 @@
|
||||
<p>You can enter a list of comma separated directories and/or files. You will
|
||||
need to put quotes "..." around any that have spaces in.</p>
|
||||
<p>On Windows you must use double back-slashes \\ in any directory names.</p>
|
||||
<p>The full filename of the file that actually changed is put into <b>msg.payload</b>,
|
||||
while a stringified version of the watch list is returned in <b>msg.topic</b>.</p>
|
||||
<p><b>msg.file</b> contains just the short filename of the file that changed.
|
||||
<b>msg.type</b> has the type of thing changed, usually <i>file</i> or <i>directory</i>,
|
||||
while <b>msg.size</b> holds the file size in bytes.</p>
|
||||
<p>The full filename of the file that actually changed is put into <code>msg.payload</code>,
|
||||
while a stringified version of the watch list is returned in <code>msg.topic</code>.</p>
|
||||
<p><code>msg.file</code> contains just the short filename of the file that changed.
|
||||
<code>msg.type</code> has the type of thing changed, usually <i>file</i> or <i>directory</i>,
|
||||
while <code>msg.size</code> holds the file size in bytes.</p>
|
||||
<p>Of course in Linux, <i>everything</i> is a file and thus can be watched...</p>
|
||||
<p><b>Note: </b>The directory or file must exist in order to be watched. If the file
|
||||
or directory gets deleted it may no longer be monitored even if it gets re-created.</p>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="tcp in">
|
||||
<p>Provides a choice of tcp inputs. Can either connect to a remote tcp port,
|
||||
<p>Provides a choice of TCP inputs. Can either connect to a remote TCP port,
|
||||
or accept incoming connections.</p>
|
||||
</script>
|
||||
|
||||
@@ -147,12 +147,14 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="tcp out">
|
||||
<p>Provides a choice of tcp outputs. Can either connect to a remote tcp port,
|
||||
<p>Provides a choice of TCP outputs. Can either connect to a remote TCP port,
|
||||
accept incoming connections, or reply to messages received from a TCP In node.</p>
|
||||
<p>Only <b>msg.payload</b> is sent.</p>
|
||||
<p>If <b>msg.payload</b> is a string containing a Base64 encoding of binary
|
||||
<p>Only <code>msg.payload</code> is sent.</p>
|
||||
<p>If <code>msg.payload</code> is a string containing a Base64 encoding of binary
|
||||
data, the Base64 decoding option will cause it to be converted back to binary
|
||||
before being sent.</p>
|
||||
<p>If <code>msg._session</code> is not present the payload is
|
||||
sent to <b>all</b> connected clients.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
@@ -247,12 +249,12 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="tcp request">
|
||||
<p>A simple TCP request node - sends the <b>msg.payload</b> to a server tcp port and expects a response.</p>
|
||||
<p>Connects, sends the "request", reads the "response". It can either count a number of
|
||||
<p>A simple TCP request node - sends the <code>msg.payload</code> to a server tcp port and expects a response.</p>
|
||||
<p>Connects, sends the "request", and reads the "response". It can either count a number of
|
||||
returned characters into a fixed buffer, match a specified character before returning,
|
||||
wait a fixed timeout from first reply and then return, or just sit and wait for data.</p>
|
||||
<p>The response will be output in <b>msg.payload</b> as a buffer, so you may want to .toString() it.</p>
|
||||
<p>If you leave tcp host or port blank they must be set by using the <b>msg.host</b> and <b>msg.port</b> properties.</p>
|
||||
<p>The response will be output in <code>msg.payload</code> as a buffer, so you may want to .toString() it.</p>
|
||||
<p>If you leave tcp host or port blank they must be set by using the <code>msg.host</code> and <code>msg.port</code> properties.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
@@ -276,7 +276,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
if (node.doend === true) {
|
||||
end = true;
|
||||
if (client) { client.end(); }
|
||||
if (client) { node.status({}); client.destroy(); }
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -303,6 +303,17 @@ module.exports = function(RED) {
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (var i in connectionPool) {
|
||||
if (Buffer.isBuffer(msg.payload)) {
|
||||
connectionPool[i].write(msg.payload);
|
||||
} else if (typeof msg.payload === "string" && node.base64) {
|
||||
connectionPool[i].write(new Buffer(msg.payload,'base64'));
|
||||
} else {
|
||||
connectionPool[i].write(new Buffer(""+msg.payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var connectedSockets = [];
|
||||
@@ -438,7 +449,7 @@ module.exports = function(RED) {
|
||||
msg.payload = new Buffer(i+1);
|
||||
buf.copy(msg.payload,0,0,i+1);
|
||||
node.send(msg);
|
||||
//if (client) { client.end(); }
|
||||
if (client) { node.status({}); client.destroy(); }
|
||||
}, node.splitc);
|
||||
i = 0;
|
||||
buf[0] = data[j];
|
||||
@@ -452,7 +463,7 @@ module.exports = function(RED) {
|
||||
msg.payload = new Buffer(i);
|
||||
buf.copy(msg.payload,0,0,i);
|
||||
node.send(msg);
|
||||
//if (client) { client.end(); }
|
||||
if (client) { node.status({}); client.destroy(); }
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
@@ -464,7 +475,7 @@ module.exports = function(RED) {
|
||||
msg.payload = new Buffer(i);
|
||||
buf.copy(msg.payload,0,0,i);
|
||||
node.send(msg);
|
||||
//if (client) { client.end(); }
|
||||
if (client) { node.status({}); client.destroy(); }
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
Copyright 2013,2016 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -29,7 +29,7 @@
|
||||
</div>
|
||||
<div class="form-row node-input-iface">
|
||||
<label for="node-input-iface"><i class="fa fa-random"></i> <span data-i18n="udp.label.interface"></span></label>
|
||||
<input type="text" id="node-input-iface" data-i18n="[placeholder]udp.label.interface">
|
||||
<input type="text" id="node-input-iface" data-i18n="[placeholder]udp.label.interfaceprompt">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-port"><i class="fa fa-sign-in"></i> <span data-i18n="udp.label.onport"></span></label>
|
||||
@@ -52,12 +52,15 @@
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-tips"><span data-i18n="udp.tip.in"></span></div>
|
||||
<div class="form-tips" id="udpporttip"><span data-i18n="[html]udp.tip.port"></span></div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="udp in">
|
||||
<p>A udp input node, that produces a <b>msg.payload</b> containing a <i>BUFFER</i>, string, or base64 encoded string. Supports multicast.</p>
|
||||
<p>It also provides <b>msg.ip</b> and <b>msg.port</b> to the ip address and port from which the message was received.</p>
|
||||
<p>On some systems you may need to be root to use ports below 1024 and/or broadcast.</p>
|
||||
<p>A UDP input node, that produces a <code>msg.payload</code> containing a
|
||||
Buffer, string, or base64 encoded string. Supports multicast.</p>
|
||||
<p>It also provides <code>msg.ip</code> and <code>msg.port</code> set to the
|
||||
ip address and port from which the message was received.</p>
|
||||
<p><b>Note</b>: On some systems you may need to be root to use ports below 1024 and/or broadcast.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
@@ -98,6 +101,21 @@
|
||||
}
|
||||
});
|
||||
$("#node-input-multicast").change();
|
||||
|
||||
var porttip = this._("udp.tip.port");
|
||||
var alreadyused = this._("udp.errors.alreadyused");
|
||||
var portsInUse = {};
|
||||
$.getJSON('udp-ports/'+this.id,function(data) {
|
||||
portsInUse = data || {};
|
||||
$('#udpporttip').html(porttip + Object.keys(data||{}));
|
||||
});
|
||||
$("#node-input-port").change(function() {
|
||||
var portnew = $("#node-input-port").val();
|
||||
if (portsInUse.hasOwnProperty($("#node-input-port").val())) {
|
||||
console.log($("#node-input-port").val());
|
||||
RED.notify(alreadyused+" "+$("#node-input-port").val(),"warn");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -147,10 +165,10 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="udp out">
|
||||
<p>This node sends <b>msg.payload</b> to the designated udp host and port. Supports multicast.</p>
|
||||
<p>You may also use <b>msg.ip</b> and <b>msg.port</b> to set the destination values.<br/><b>Note</b>: the statically configured values have precedence.</p>
|
||||
<p>This node sends <code>msg.payload</code> to the designated UDP host and port. Supports multicast.</p>
|
||||
<p>You may also use <code>msg.ip</code> and <code>msg.port</code> to set the destination values, but the statically configured values have precedence.</p>
|
||||
<p>If you select broadcast either set the address to the local broadcast ip address, or maybe try 255.255.255.255, which is the global broadcast address.</p>
|
||||
<p>On some systems you may need to be root to use ports below 1024 and/or broadcast.</p>
|
||||
<p><b>Note</b>: On some systems you may need to be root to use ports below 1024 and/or broadcast.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
* Copyright 2013,2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var dgram = require('dgram');
|
||||
var udpInputPortsInUse = {};
|
||||
|
||||
// The Input Node
|
||||
function UDPin(n) {
|
||||
@@ -28,6 +29,12 @@ module.exports = function(RED) {
|
||||
this.multicast = n.multicast;
|
||||
this.ipv = n.ipv || "udp4";
|
||||
var node = this;
|
||||
if (!udpInputPortsInUse.hasOwnProperty(this.port)) {
|
||||
udpInputPortsInUse[this.port] = n.id;
|
||||
}
|
||||
else {
|
||||
node.warn(RED._("udp.errors.alreadyused",node.port));
|
||||
}
|
||||
|
||||
var opts = {type:node.ipv, reuseAddr:true};
|
||||
if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; }
|
||||
@@ -76,6 +83,10 @@ module.exports = function(RED) {
|
||||
});
|
||||
|
||||
node.on("close", function() {
|
||||
console.log("ID=",node.id);
|
||||
if (udpInputPortsInUse[node.port] === node.id) {
|
||||
delete udpInputPortsInUse[node.port];
|
||||
}
|
||||
try {
|
||||
server.close();
|
||||
node.log(RED._("udp.status.listener-stopped"));
|
||||
@@ -86,9 +97,11 @@ module.exports = function(RED) {
|
||||
|
||||
server.bind(node.port,node.iface);
|
||||
}
|
||||
RED.httpAdmin.get('/udp-ports/:id', RED.auth.needsPermission('udp-in.read'), function(req,res) {
|
||||
res.json(udpInputPortsInUse);
|
||||
});
|
||||
RED.nodes.registerType("udp in",UDPin);
|
||||
|
||||
|
||||
// The Output Node
|
||||
function UDPout(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
var util = require("util");
|
||||
var mqtt = require("mqtt");
|
||||
var events = require("events");
|
||||
|
||||
util.log("[warn] nodes/core/io/lib/mqtt.js is deprecated and will be removed in a future release of Node-RED. Please report this usage to the Node-RED mailing list.");
|
||||
|
||||
//var inspect = require("util").inspect;
|
||||
|
||||
//var Client = module.exports.Client = function(
|
||||
|
||||
@@ -17,6 +17,8 @@ var util = require("util");
|
||||
var mqtt = require("./mqtt");
|
||||
var settings = require(process.env.NODE_RED_HOME+"/red/red").settings;
|
||||
|
||||
util.log("[warn] nodes/core/io/lib/mqttConnectionPool.js is deprecated and will be removed in a future release of Node-RED. Please report this usage to the Node-RED mailing list.");
|
||||
|
||||
var connections = {};
|
||||
|
||||
function matchTopic(ts,t) {
|
||||
@@ -60,7 +62,7 @@ module.exports = {
|
||||
subscribe: function(topic,qos,callback,ref) {
|
||||
ref = ref||0;
|
||||
subscriptions[topic] = subscriptions[topic]||{};
|
||||
|
||||
|
||||
var sub = {
|
||||
topic:topic,
|
||||
qos:qos,
|
||||
@@ -104,7 +106,7 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
disconnect: function(ref) {
|
||||
|
||||
|
||||
this._instances -= 1;
|
||||
if (this._instances == 0) {
|
||||
client.disconnect();
|
||||
|
||||
@@ -37,8 +37,6 @@
|
||||
"repeat": "Repeat"
|
||||
},
|
||||
"timestamp": "timestamp",
|
||||
"string": "string",
|
||||
"blank": "blank",
|
||||
"none": "none",
|
||||
"interval": "interval",
|
||||
"interval-time": "interval between times",
|
||||
@@ -47,6 +45,7 @@
|
||||
"minutes": "minutes",
|
||||
"hours": "hours",
|
||||
"between": "between",
|
||||
"previous": "previous value",
|
||||
"at": "at",
|
||||
"and": "and",
|
||||
"every": "every",
|
||||
@@ -138,7 +137,11 @@
|
||||
"template": {
|
||||
"label": {
|
||||
"template": "Template",
|
||||
"property": "Property"
|
||||
"property": "Set property",
|
||||
"format": "Syntax Highlight",
|
||||
"syntax": "Format",
|
||||
"mustache": "Mustache template",
|
||||
"plain": "Plain text"
|
||||
},
|
||||
"templatevalue": "This is the payload: {{payload}} !"
|
||||
},
|
||||
@@ -146,9 +149,10 @@
|
||||
"action": "Action",
|
||||
"for": "For",
|
||||
"delaymsg": "Delay message",
|
||||
"ramdomdelay": "Random delay",
|
||||
"randomdelay": "Random delay",
|
||||
"limitrate": "Limit rate to",
|
||||
"fairqueue": "Topic based fair queue",
|
||||
"timedqueue": "Timed release queue",
|
||||
"milisecs": "Miliseconds",
|
||||
"secs": "Seconds",
|
||||
"sec": "Second",
|
||||
@@ -165,8 +169,9 @@
|
||||
"label": {
|
||||
"delay": "delay",
|
||||
"limit": "limit",
|
||||
"random": "ramdom",
|
||||
"queue": "queue"
|
||||
"random": "random",
|
||||
"queue": "queue",
|
||||
"timed": "releases per"
|
||||
},
|
||||
"error": {
|
||||
"buffer": "buffer exceeded 1000 messages"
|
||||
@@ -177,8 +182,9 @@
|
||||
"then": "then",
|
||||
"then-send": "then send",
|
||||
"output": {
|
||||
"string": "the string payload",
|
||||
"existing": "the existing message",
|
||||
"string": "the string",
|
||||
"number": "the number",
|
||||
"existing": "the existing msg.payload",
|
||||
"nothing": "nothing"
|
||||
},
|
||||
"wait-reset": "wait to be reset",
|
||||
@@ -189,11 +195,13 @@
|
||||
"m": "Minutes",
|
||||
"h": "Hours"
|
||||
},
|
||||
"extend": "extend delay if new message arrives",
|
||||
"tip": "The node can be reset by sending a message with the <b>msg.reset</b> property set",
|
||||
"extend": " extend delay if new message arrives",
|
||||
"tip": "The node can also be reset by sending a message with the <code>msg.reset</code> property set to any value.",
|
||||
"label": {
|
||||
"trigger": "trigger",
|
||||
"trigger-block": "trigger & block"
|
||||
"trigger-block": "trigger & block",
|
||||
"reset": "and reset if msg.payload == ",
|
||||
"resetprompt": "(optional reset value)"
|
||||
}
|
||||
},
|
||||
"comment": {
|
||||
@@ -371,7 +379,8 @@
|
||||
"using": "using",
|
||||
"output": "Output",
|
||||
"group": "Group",
|
||||
"interface": "Interface",
|
||||
"interface": "Local IP",
|
||||
"interfaceprompt": "(optional) local ip address to bind to",
|
||||
"send": "Send a",
|
||||
"toport": "to port",
|
||||
"address": "Address",
|
||||
@@ -398,7 +407,8 @@
|
||||
},
|
||||
"tip": {
|
||||
"in": "Tip: Make sure your firewall will allow the data in.",
|
||||
"out": "Tip: leave address and port blank if you want to set using <b>msg.ip</b> and <b>msg.port</b>."
|
||||
"out": "Tip: leave address and port blank if you want to set using <code>msg.ip</code> and <code>msg.port</code>.",
|
||||
"port": "Ports already in use: "
|
||||
},
|
||||
"status": {
|
||||
"listener-at": "udp listener at __host__:__port__",
|
||||
@@ -417,7 +427,8 @@
|
||||
"interface": "Must be ip address of the required interface",
|
||||
"ip-notset": "udp: ip address not set",
|
||||
"port-notset": "udp: port not set",
|
||||
"port-invalid": "udp: port number not valid"
|
||||
"port-invalid": "udp: port number not valid",
|
||||
"alreadyused": "udp: port already in use"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
@@ -459,7 +470,8 @@
|
||||
"replace": "Replace with"
|
||||
},
|
||||
"errors": {
|
||||
"invalid-from": "Invalid 'from' property: __error__"
|
||||
"invalid-from": "Invalid 'from' property: __error__",
|
||||
"invalid-json": "Invalid 'to' JSON property"
|
||||
}
|
||||
},
|
||||
"range": {
|
||||
@@ -531,7 +543,8 @@
|
||||
},
|
||||
"output": {
|
||||
"html": "the html content of the elements",
|
||||
"text": "only the text content of the elements"
|
||||
"text": "only the text content of the elements",
|
||||
"attr": "an object of any attributes of the elements"
|
||||
},
|
||||
"format": {
|
||||
"single": "as a single message containing an array",
|
||||
@@ -565,8 +578,10 @@
|
||||
"readinitial": "Read initial state of pin on deploy/restart?",
|
||||
"type": "Type",
|
||||
"initpin": "Initialise pin state?",
|
||||
"debounce": "Debounce",
|
||||
"button": "Button",
|
||||
"pimouse": "Pi Mouse",
|
||||
"pikeyboard": "Pi Keyboard",
|
||||
"left": "Left",
|
||||
"right": "Right",
|
||||
"middle": "Middle"
|
||||
@@ -624,8 +639,13 @@
|
||||
"tail": {
|
||||
"label": {
|
||||
"filename": "Filename",
|
||||
"type": "File type",
|
||||
"splitlines": "Split lines on \\n?"
|
||||
},
|
||||
"action": {
|
||||
"text": "Text - returns String",
|
||||
"binary": "Binary - returns Buffer"
|
||||
},
|
||||
"errors": {
|
||||
"windowsnotsupport": "Not currently supported on Windows."
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<span data-i18n="switch.label.property"></span> msg.<input type="text" id="node-input-property" style="width: 200px;"/>
|
||||
<span data-i18n="switch.label.property"></span> <input type="text" id="node-input-property" style="width: 300px;"/>
|
||||
</div>
|
||||
<div class="form-row node-input-rule-container-row" style="margin-bottom: 0px;">
|
||||
<div id="node-input-rule-container-div" style="box-sizing: border-box; border-radius: 5px; height: 310px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
|
||||
@@ -39,11 +39,11 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="switch">
|
||||
<p>A simple function node to route messages based on its properties.</p>
|
||||
<p>A node to route messages based on property values.</p>
|
||||
<p>When a message arrives, the selected property is evaluated against each
|
||||
of the defined rules. The message is then sent to the output of <i>all</i>
|
||||
rules that pass.</p>
|
||||
<p>Note: the <i>otherwise</i> rule applies as a "not any of" the rules preceding it.</p>
|
||||
<p><b>Note</b>: the <i>otherwise</i> rule applies as a "not any of" the rules preceding it.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
@@ -53,6 +53,7 @@
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
property: {value:"payload", required:true},
|
||||
propertyType: { value:"msg" },
|
||||
rules: {value:[{t:"eq", v:""}]},
|
||||
checkall: {value:"true", required:true},
|
||||
outputs: {value:1}
|
||||
@@ -64,7 +65,10 @@
|
||||
return this.name||"switch";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var node = this;
|
||||
var previousValueType = {value:"prev",label:this._("inject.previous"),hasValue:false};
|
||||
|
||||
$("#node-input-property").typedInput({default:this.propertyType||'msg',types:['msg','flow','global']});
|
||||
var operators = [
|
||||
{v:"eq",t:"=="},
|
||||
{v:"neq",t:"!="},
|
||||
@@ -85,7 +89,7 @@
|
||||
var andLabel = this._("switch.and");
|
||||
var caseLabel = this._("switch.ignorecase");
|
||||
|
||||
function resizeRule(rule,width) {
|
||||
this.resizeRule = function(rule,newWidth) {
|
||||
var selectField = rule.find("select");
|
||||
var type = selectField.children("option:selected").val();
|
||||
var valueField = rule.find(".node-input-rule-value");
|
||||
@@ -102,14 +106,14 @@
|
||||
selectField.width(selectWidth);
|
||||
|
||||
if (type === "btwn") {
|
||||
var labelWidth = rule.find(".node-input-tule-btwn-label").width();
|
||||
btwnField1.width((width-256-labelWidth)/2);
|
||||
btwnField2.width((width-256-labelWidth)/2);
|
||||
var labelWidth = rule.find(".node-input-rule-btwn-label").width();
|
||||
btwnField1.typedInput("width",(newWidth-selectWidth-120));
|
||||
btwnField2.typedInput("width",(newWidth-selectWidth-120));
|
||||
} else {
|
||||
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
|
||||
// valueField.hide();
|
||||
} else {
|
||||
valueField.width(width-selectWidth-120);
|
||||
valueField.typedInput("width",(newWidth-selectWidth-120));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,6 +122,8 @@
|
||||
var container = $('<li/>',{style:"background: #fff; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"});
|
||||
var row = $('<div/>').appendTo(container);
|
||||
var row2 = $('<div/>',{style:"padding-top: 5px; padding-left: 175px;"}).appendTo(container);
|
||||
var row3 = $('<div/>',{style:"padding-top: 5px; padding-left: 120px;"}).appendTo(container);
|
||||
|
||||
$('<i style="color: #eee; cursor: move;" class="node-input-rule-handle fa fa-bars"></i>').appendTo(row);
|
||||
|
||||
var selectField = $('<select/>',{style:"width:120px; margin-left: 5px; text-align: center;"}).appendTo(row);
|
||||
@@ -125,11 +131,10 @@
|
||||
selectField.append($("<option></option>").val(operators[d].v).text(operators[d].t));
|
||||
}
|
||||
|
||||
var valueField = $('<input/>',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px; width: 145px;"}).appendTo(row);
|
||||
var btwnField = $('<span/>').appendTo(row);
|
||||
var btwnValueField = $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"margin-left: 5px; width: 50px;"}).appendTo(btwnField);
|
||||
var btwnAndLabel = $('<span/>',{class:"node-input-tule-btwn-label"}).text(" "+andLabel+" ").appendTo(btwnField);
|
||||
var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"width: 50px;margin-left:2px;"}).appendTo(btwnField);
|
||||
var valueField = $('<input/>',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px; width: 145px;"}).appendTo(row).typedInput({default:'str',types:['msg','flow','global','str','num',previousValueType]});
|
||||
var btwnValueField = $('<input/>',{class:"node-input-rule-btwn-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['msg','flow','global','str','num',previousValueType]});
|
||||
var btwnAndLabel = $('<span/>',{class:"node-input-rule-btwn-label"}).text(" "+andLabel+" ").appendTo(row3);
|
||||
var btwnValue2Field = $('<input/>',{class:"node-input-rule-btwn-value2",type:"text",style:"margin-left:2px;"}).appendTo(row3).typedInput({default:'num',types:['msg','flow','global','str','num',previousValueType]});
|
||||
|
||||
var finalspan = $('<span/>',{style:"float: right;margin-right: 10px;"}).appendTo(row);
|
||||
finalspan.append(' → <span class="node-input-rule-index">'+i+'</span> ');
|
||||
@@ -141,24 +146,28 @@
|
||||
$('<label/>',{for:"node-input-rule-case-"+i,style:"margin-left: 3px;"}).text(caseLabel).appendTo(row2);
|
||||
selectField.change(function() {
|
||||
var width = $("#node-input-rule-container").width();
|
||||
resizeRule(container,width);
|
||||
var type = selectField.children("option:selected").val();
|
||||
|
||||
node.resizeRule(container,width);
|
||||
if (type === "btwn") {
|
||||
valueField.hide();
|
||||
btwnField.show();
|
||||
valueField.parent().hide();
|
||||
btwnValueField.parent().show();
|
||||
} else {
|
||||
btwnField.hide();
|
||||
btwnValueField.parent().hide();
|
||||
if (type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else") {
|
||||
valueField.hide();
|
||||
valueField.parent().hide();
|
||||
} else {
|
||||
valueField.show();
|
||||
valueField.parent().show();
|
||||
}
|
||||
}
|
||||
if (type === "regex") {
|
||||
row2.show();
|
||||
row3.hide();
|
||||
} else if (type === "btwn"){
|
||||
row2.hide();
|
||||
row3.show();
|
||||
} else {
|
||||
row2.hide();
|
||||
row3.hide();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -177,10 +186,13 @@
|
||||
|
||||
selectField.find("option").filter(function() {return $(this).val() == rule.t;}).attr('selected',true);
|
||||
if (rule.t == "btwn") {
|
||||
btwnValueField.val(rule.v);
|
||||
btwnValue2Field.val(rule.v2);
|
||||
btwnValueField.typedInput('value',rule.v);
|
||||
btwnValueField.typedInput('type',rule.vt||'num');
|
||||
btwnValue2Field.typedInput('value',rule.v2);
|
||||
btwnValue2Field.typedInput('type',rule.v2t||'num');
|
||||
} else if (typeof rule.v != "undefined") {
|
||||
valueField.val(rule.v);
|
||||
valueField.typedInput('value',rule.v);
|
||||
valueField.typedInput('type',rule.vt||'str');
|
||||
}
|
||||
if (rule.case) {
|
||||
caseSensitive.prop('checked',true);
|
||||
@@ -200,23 +212,6 @@
|
||||
generateRule(i+1,rule);
|
||||
}
|
||||
|
||||
function switchDialogResize() {
|
||||
var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-rule-container-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$("#node-input-rule-container-div").css("height",height+"px");
|
||||
|
||||
var rules = $("#node-input-rule-container").children();
|
||||
var newWidth = $("#node-input-rule-container").width();
|
||||
rules.each(function(i) {
|
||||
resizeRule($(this),newWidth);
|
||||
})
|
||||
};
|
||||
|
||||
$( "#node-input-rule-container" ).sortable({
|
||||
axis: "y",
|
||||
update: function( event, ui ) {
|
||||
@@ -229,21 +224,6 @@
|
||||
cursor: "move"
|
||||
});
|
||||
$( "#node-input-rule-container .node-input-rule-handle" ).disableSelection();
|
||||
|
||||
$( "#dialog" ).on("dialogresize", switchDialogResize);
|
||||
$( "#dialog" ).one("dialogopen", function(ev) {
|
||||
var size = $( "#dialog" ).dialog('option','sizeCache-switch');
|
||||
if (size) {
|
||||
$("#dialog").dialog('option','width',size.width);
|
||||
$("#dialog").dialog('option','height',size.height);
|
||||
switchDialogResize();
|
||||
} else {
|
||||
setTimeout(switchDialogResize,10);
|
||||
}
|
||||
});
|
||||
$( "#dialog" ).one("dialogclose", function(ev,ui) {
|
||||
$( "#dialog" ).off("dialogresize",switchDialogResize);
|
||||
});
|
||||
},
|
||||
oneditsave: function() {
|
||||
var rules = $("#node-input-rule-container").children();
|
||||
@@ -256,10 +236,13 @@
|
||||
var r = {t:type};
|
||||
if (!(type === "true" || type === "false" || type === "null" || type === "nnull" || type === "else")) {
|
||||
if (type === "btwn") {
|
||||
r.v = rule.find(".node-input-rule-btwn-value").val();
|
||||
r.v2 = rule.find(".node-input-rule-btwn-value2").val();
|
||||
r.v = rule.find(".node-input-rule-btwn-value").typedInput('value');
|
||||
r.vt = rule.find(".node-input-rule-btwn-value").typedInput('type');
|
||||
r.v2 = rule.find(".node-input-rule-btwn-value2").typedInput('value');
|
||||
r.v2t = rule.find(".node-input-rule-btwn-value2").typedInput('type');
|
||||
} else {
|
||||
r.v = rule.find(".node-input-rule-value").val();
|
||||
r.v = rule.find(".node-input-rule-value").typedInput('value');
|
||||
r.vt = rule.find(".node-input-rule-value").typedInput('type');
|
||||
}
|
||||
if (type === "regex") {
|
||||
r.case = rule.find(".node-input-rule-case").prop("checked");
|
||||
@@ -267,7 +250,25 @@
|
||||
}
|
||||
node.rules.push(r);
|
||||
});
|
||||
node.outputs = node.rules.length;
|
||||
this.outputs = node.rules.length;
|
||||
this.propertyType = $("#node-input-property").typedInput('type');
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
|
||||
var height = size.height;
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-rule-container-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$("#node-input-rule-container-div").css("height",height+"px");
|
||||
|
||||
var rules = $("#node-input-rule-container").children();
|
||||
var newWidth = $("#node-input-rule-container").width();
|
||||
var node = this;
|
||||
rules.each(function(i) {
|
||||
node.resizeRule($(this),newWidth);
|
||||
})
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -37,30 +37,55 @@ module.exports = function(RED) {
|
||||
RED.nodes.createNode(this, n);
|
||||
this.rules = n.rules || [];
|
||||
this.property = n.property;
|
||||
this.propertyType = n.propertyType || "msg";
|
||||
this.checkall = n.checkall || "true";
|
||||
var propertyParts = (n.property || "payload").split(".");
|
||||
this.previousValue = null;
|
||||
var node = this;
|
||||
|
||||
for (var i=0; i<this.rules.length; i+=1) {
|
||||
var rule = this.rules[i];
|
||||
if (!isNaN(Number(rule.v))) {
|
||||
rule.v = Number(rule.v);
|
||||
rule.v2 = Number(rule.v2);
|
||||
if (!rule.vt) {
|
||||
rule.vt = 'str';
|
||||
}
|
||||
if (rule.vt === 'str' || rule.vt === 'num') {
|
||||
if (!isNaN(Number(rule.v))) {
|
||||
rule.v = Number(rule.v);
|
||||
}
|
||||
}
|
||||
if (typeof rule.v2 !== 'undefined') {
|
||||
if (!rule.v2t) {
|
||||
rule.v2t = 'str';
|
||||
}
|
||||
if (rule.v2t === 'str' || rule.v2t === 'num') {
|
||||
if (!isNaN(Number(rule.v2))) {
|
||||
rule.v2 = Number(rule.v2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.on('input', function (msg) {
|
||||
var onward = [];
|
||||
try {
|
||||
var prop = propertyParts.reduce(function (obj, i) {
|
||||
return obj[i]
|
||||
}, msg);
|
||||
var prop = RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg);
|
||||
var elseflag = true;
|
||||
for (var i=0; i<node.rules.length; i+=1) {
|
||||
var rule = node.rules[i];
|
||||
var test = prop;
|
||||
var v1,v2;
|
||||
if (rule.vt === 'prev') {
|
||||
v1 = node.previousValue;
|
||||
} else {
|
||||
v1 = RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg);
|
||||
}
|
||||
v2 = rule.v2;
|
||||
if (rule.v2t === 'prev') {
|
||||
v2 = node.previousValue;
|
||||
} else if (typeof v2 !== 'undefined') {
|
||||
v2 = RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg);
|
||||
}
|
||||
node.previousValue = prop;
|
||||
if (rule.t == "else") { test = elseflag; elseflag = true; }
|
||||
if (operators[rule.t](test,rule.v, rule.v2, rule.case)) {
|
||||
if (operators[rule.t](test,v1,v2,rule.case)) {
|
||||
onward.push(msg);
|
||||
elseflag = false;
|
||||
if (node.checkall == "false") { break; }
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<script type="text/x-red" data-template-name="change">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
<input type="text" id="node-input-name" style="width: 370px;" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom:0;">
|
||||
<label><i class="fa fa-list"></i> <span data-i18n="change.label.rules"></span></label>
|
||||
@@ -33,12 +33,12 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="change">
|
||||
<p>Set, change or delete properties of a message.</p>
|
||||
<p>The node can specify multiple rules that will be applied to the message in turn.</p>
|
||||
<p>Set, change or delete properties of a message, flow context or global context.</p>
|
||||
<p>The node can specify multiple rules that will be applied in turn.</p>
|
||||
<p>The available operations are:</p>
|
||||
<ul>
|
||||
<li><b>Set</b> - set a property. The <b>to</b> property can either be a string value, or reference
|
||||
another message property by name, for example: <code>msg.topic</code>.</li>
|
||||
<li><b>Set</b> - set a property. The value can be a variety of different types, or
|
||||
can be taken from an existing message or context property.</li>
|
||||
<li><b>Change</b> - search & replace parts of the property. If regular expressions
|
||||
are enabled, the <b>replace with</b> property can include capture groups, for example <code>$1</code></li>
|
||||
<li><b>Delete</b> - delete a property.</li>
|
||||
@@ -51,7 +51,7 @@
|
||||
category: 'function',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
rules:{value:[{t:"set",p:"payload",to:""}]},
|
||||
rules:{value:[{t:"set",p:"payload",pt:"msg",to:"",tot:"str"}]},
|
||||
// legacy
|
||||
action: {value:""},
|
||||
property: {value:""},
|
||||
@@ -77,11 +77,11 @@
|
||||
} else {
|
||||
if (this.rules.length == 1) {
|
||||
if (this.rules[0].t === "set") {
|
||||
return this._("change.label.set",{property:"msg."+this.rules[0].p});
|
||||
return this._("change.label.set",{property:(this.rules[0].pt||"msg")+"."+this.rules[0].p});
|
||||
} else if (this.rules[0].t === "change") {
|
||||
return this._("change.label.change",{property:"msg."+this.rules[0].p});
|
||||
return this._("change.label.change",{property:(this.rules[0].pt||"msg")+"."+this.rules[0].p});
|
||||
} else {
|
||||
return this._("change.label.delete",{property:"msg."+this.rules[0].p});
|
||||
return this._("change.label.delete",{property:(this.rules[0].pt||"msg")+"."+this.rules[0].p});
|
||||
}
|
||||
} else {
|
||||
return this._("change.label.changeCount",{count:this.rules.length});
|
||||
@@ -99,14 +99,22 @@
|
||||
var search = this._("change.action.search");
|
||||
var replace = this._("change.action.replace");
|
||||
var regex = this._("change.label.regex");
|
||||
if (this.reg === null) { $("#node-input-reg").prop('checked', true); }
|
||||
$("#node-input-action").change();
|
||||
|
||||
function resizeRule(rule,width) {
|
||||
rule.find('input[type="text"]').width(width-220);
|
||||
}
|
||||
|
||||
function generateRule(rule) {
|
||||
|
||||
if (rule.t === "change" && rule.re) {
|
||||
rule.fromt = 're';
|
||||
delete rule.re;
|
||||
}
|
||||
if (rule.t === "set" && !rule.tot) {
|
||||
if (rule.to.indexOf("msg.") === 0 && !rule.tot) {
|
||||
rule.to = rule.to.substring(4);
|
||||
rule.tot = "msg";
|
||||
} else {
|
||||
rule.tot = "str";
|
||||
}
|
||||
}
|
||||
|
||||
var container = $('<li/>',{style:"background: #fff; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"});
|
||||
|
||||
var row1 = $('<div/>').appendTo(container);
|
||||
@@ -114,41 +122,46 @@
|
||||
var row2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(container);
|
||||
var row3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(container);
|
||||
|
||||
var selectField = $('<select/>',{class:"node-input-rule-type",style:"width: 100px"}).appendTo(row1);
|
||||
var selectField = $('<select/>',{class:"node-input-rule-type",style:"width: 110px; margin-right: 10px;"}).appendTo(row1);
|
||||
var selectOptions = [{v:"set",l:set},{v:"change",l:change},{v:"delete",l:del}];
|
||||
for (var i=0;i<3;i++) {
|
||||
selectField.append($("<option></option>").val(selectOptions[i].v).text(selectOptions[i].l));
|
||||
}
|
||||
|
||||
$('<div/>',{style:"display:inline-block; width: 50px; text-align: right;"}).text("msg.").appendTo(row1);
|
||||
var propertyName = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-name",type:"text"}).appendTo(row1);
|
||||
|
||||
|
||||
var finalspan = $('<span/>',{style:"float: right; margin-right: 10px;"}).appendTo(row1);
|
||||
var deleteButton = $('<a/>',{href:"#",class:"editor-button editor-button-small", style:"margin-top: 7px; margin-left: 5px;"}).appendTo(finalspan);
|
||||
var propertyName = $('<input/>',{style:"width: 250px",class:"node-input-rule-property-name",type:"text"})
|
||||
.appendTo(row1)
|
||||
.typedInput({types:['msg','flow','global']});
|
||||
var finalspan = $('<span/>',{style:"float: right; margin-right: 10px;"})
|
||||
.appendTo(row1);
|
||||
var deleteButton = $('<a/>',{href:"#",class:"editor-button editor-button-small", style:"margin-top: 7px; margin-left: 5px;"})
|
||||
.appendTo(finalspan);
|
||||
$('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
|
||||
|
||||
|
||||
$('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text(to).appendTo(row2);
|
||||
var propertyValue = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-value",type:"text"}).appendTo(row2);
|
||||
|
||||
$('<div/>',{style:"display: inline-block;text-align:right; width:120px;padding-right: 10px; box-sizing: border-box;"})
|
||||
.text(to)
|
||||
.appendTo(row2);
|
||||
var propertyValue = $('<input/>',{style:"width: 250px",class:"node-input-rule-property-value",type:"text"})
|
||||
.appendTo(row2)
|
||||
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json']});
|
||||
var row3_1 = $('<div/>').appendTo(row3);
|
||||
$('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text(search).appendTo(row3_1);
|
||||
var fromValue = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-search-value",type:"text"}).appendTo(row3_1);
|
||||
$('<div/>',{style:"display: inline-block;text-align:right; width:120px;padding-right: 10px; box-sizing: border-box;"})
|
||||
.text(search)
|
||||
.appendTo(row3_1);
|
||||
var fromValue = $('<input/>',{style:"width: 250px",class:"node-input-rule-property-search-value",type:"text"})
|
||||
.appendTo(row3_1)
|
||||
.typedInput({default:'str',types:['msg','flow','global','str','re','num','bool']});
|
||||
|
||||
var row3_2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(row3);
|
||||
$('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text(replace).appendTo(row3_2);
|
||||
var toValue = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-replace-value",type:"text"}).appendTo(row3_2);
|
||||
|
||||
var row3_3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(row3);
|
||||
var id = "node-input-rule-property-regex-"+Math.floor(Math.random()*10000);
|
||||
var useRegExp = $('<input/>',{id:id,class:"node-input-rule-property-re",type:"checkbox", style:"margin-left: 150px; margin-right: 10px; display: inline-block; width: auto; vertical-align: top;"}).appendTo(row3_3);
|
||||
$('<label/>',{for:id,style:"width: auto;"}).text(regex).appendTo(row3_3);
|
||||
|
||||
$('<div/>',{style:"display: inline-block;text-align:right; width:120px;padding-right: 10px; box-sizing: border-box;"})
|
||||
.text(replace)
|
||||
.appendTo(row3_2);
|
||||
var toValue = $('<input/>',{style:"width: 250px",class:"node-input-rule-property-replace-value",type:"text"})
|
||||
.appendTo(row3_2)
|
||||
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json']});
|
||||
|
||||
selectField.change(function() {
|
||||
var width = $("#node-input-rule-container").width();
|
||||
resizeRule(container,width);
|
||||
var type = $(this).val();
|
||||
if (type == "set") {
|
||||
row2.show();
|
||||
@@ -169,11 +182,14 @@
|
||||
});
|
||||
|
||||
selectField.find("option").filter(function() {return $(this).val() == rule.t;}).attr('selected',true);
|
||||
propertyName.val(rule.p);
|
||||
propertyValue.val(rule.to);
|
||||
fromValue.val(rule.from);
|
||||
toValue.val(rule.to);
|
||||
useRegExp.prop('checked', rule.re);
|
||||
propertyName.typedInput('value',rule.p);
|
||||
propertyName.typedInput('type',rule.pt)
|
||||
propertyValue.typedInput('value',rule.to);
|
||||
propertyValue.typedInput('type',rule.tot)
|
||||
fromValue.typedInput('value',rule.from);
|
||||
fromValue.typedInput('type',rule.fromt)
|
||||
toValue.typedInput('value',rule.to);
|
||||
toValue.typedInput('type',rule.tot)
|
||||
selectField.change();
|
||||
|
||||
$("#node-input-rule-container").append(container);
|
||||
@@ -185,7 +201,8 @@
|
||||
if (!this.rules) {
|
||||
var rule = {
|
||||
t:(this.action=="replace"?"set":this.action),
|
||||
p:this.property
|
||||
p:this.property,
|
||||
pt:"msg"
|
||||
}
|
||||
|
||||
if (rule.t === "set") {
|
||||
@@ -208,37 +225,6 @@
|
||||
for (var i=0;i<this.rules.length;i++) {
|
||||
generateRule(this.rules[i]);
|
||||
}
|
||||
|
||||
function changeDialogResize() {
|
||||
var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
|
||||
var height = $("#dialog-form").height();
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-rule-container-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$("#node-input-rule-container-div").css("height",height+"px");
|
||||
|
||||
var rules = $("#node-input-rule-container").children();
|
||||
var newWidth = $("#node-input-rule-container").width();
|
||||
rules.each(function(i) {
|
||||
resizeRule($(this),newWidth);
|
||||
})
|
||||
};
|
||||
$( "#dialog" ).on("dialogresize", changeDialogResize);
|
||||
$( "#dialog" ).one("dialogopen", function(ev) {
|
||||
var size = $( "#dialog" ).dialog('option','sizeCache-change');
|
||||
if (size) {
|
||||
$("#dialog").dialog('option','width',size.width);
|
||||
$("#dialog").dialog('option','height',size.height);
|
||||
changeDialogResize();
|
||||
} else {
|
||||
setTimeout(changeDialogResize,10);
|
||||
}
|
||||
});
|
||||
$( "#dialog" ).one("dialogclose", function(ev,ui) {
|
||||
$( "#dialog" ).off("dialogresize",changeDialogResize);
|
||||
});
|
||||
},
|
||||
oneditsave: function() {
|
||||
var rules = $("#node-input-rule-container").children();
|
||||
@@ -250,17 +236,37 @@
|
||||
var type = rule.find(".node-input-rule-type option:selected").val();
|
||||
var r = {
|
||||
t:type,
|
||||
p:rule.find(".node-input-rule-property-name").val()
|
||||
p:rule.find(".node-input-rule-property-name").typedInput('value'),
|
||||
pt:rule.find(".node-input-rule-property-name").typedInput('type')
|
||||
};
|
||||
if (type === "set") {
|
||||
r.to = rule.find(".node-input-rule-property-value").val();
|
||||
r.to = rule.find(".node-input-rule-property-value").typedInput('value');
|
||||
r.tot = rule.find(".node-input-rule-property-value").typedInput('type');
|
||||
} else if (type === "change") {
|
||||
r.from = rule.find(".node-input-rule-property-search-value").val();
|
||||
r.to = rule.find(".node-input-rule-property-replace-value").val();
|
||||
r.re = rule.find(".node-input-rule-property-re").prop('checked');
|
||||
r.from = rule.find(".node-input-rule-property-search-value").typedInput('value');
|
||||
r.fromt = rule.find(".node-input-rule-property-search-value").typedInput('type');
|
||||
r.to = rule.find(".node-input-rule-property-replace-value").typedInput('value');
|
||||
r.tot = rule.find(".node-input-rule-property-replace-value").typedInput('type');
|
||||
}
|
||||
node.rules.push(r);
|
||||
});
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
var rows = $("#dialog-form>div:not(.node-input-rule-container-row)");
|
||||
var height = size.height;
|
||||
for (var i=0;i<rows.size();i++) {
|
||||
height -= $(rows[i]).outerHeight(true);
|
||||
}
|
||||
var editorRow = $("#dialog-form>div.node-input-rule-container-row");
|
||||
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
|
||||
$("#node-input-rule-container-div").css("height",height+"px");
|
||||
|
||||
var rules = $("#node-input-rule-container").children();
|
||||
var newWidth = $("#node-input-rule-container").width();
|
||||
rules.each(function(i) {
|
||||
$(this).find('.red-ui-typedInput').typedInput("width",newWidth-180);
|
||||
})
|
||||
$("#node-input-name").width(newWidth-130);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -19,15 +19,15 @@ module.exports = function(RED) {
|
||||
|
||||
function ChangeNode(n) {
|
||||
RED.nodes.createNode(this, n);
|
||||
|
||||
|
||||
this.rules = n.rules;
|
||||
|
||||
|
||||
if (!this.rules) {
|
||||
var rule = {
|
||||
t:(n.action=="replace"?"set":n.action),
|
||||
p:n.property||""
|
||||
}
|
||||
|
||||
|
||||
if (rule.t === "set") {
|
||||
rule.to = n.to||"";
|
||||
} else if (rule.t === "change") {
|
||||
@@ -37,80 +37,185 @@ module.exports = function(RED) {
|
||||
}
|
||||
this.rules = [rule];
|
||||
}
|
||||
|
||||
|
||||
this.actions = [];
|
||||
|
||||
var valid = true;
|
||||
|
||||
|
||||
for (var i=0;i<this.rules.length;i++) {
|
||||
var rule = this.rules[i];
|
||||
if (rule.t === "change") {
|
||||
if (rule.re === false) {
|
||||
rule.from = rule.from.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
// Migrate to type-aware rules
|
||||
if (!rule.pt) {
|
||||
rule.pt = "msg";
|
||||
}
|
||||
if (rule.t === "change" && rule.re) {
|
||||
rule.fromt = 're';
|
||||
delete rule.re;
|
||||
}
|
||||
if (rule.t === "set" && !rule.tot) {
|
||||
if (rule.to.indexOf("msg.") === 0 && !rule.tot) {
|
||||
rule.to = rule.to.substring(4);
|
||||
rule.tot = "msg";
|
||||
}
|
||||
}
|
||||
if (!rule.tot) {
|
||||
rule.tot = "str";
|
||||
}
|
||||
if (!rule.fromt) {
|
||||
rule.fromt = "str";
|
||||
}
|
||||
if (rule.t === "change" && rule.fromt !== 'msg' && rule.fromt !== 'flow' && rule.fromt !== 'global') {
|
||||
rule.fromRE = rule.from;
|
||||
if (rule.fromt !== 're') {
|
||||
rule.fromRE = rule.fromRE.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
}
|
||||
try {
|
||||
rule.from = new RegExp(rule.from, "g");
|
||||
rule.fromRE = new RegExp(rule.fromRE, "g");
|
||||
} catch (e) {
|
||||
valid = false;
|
||||
this.error(RED._("change.errors.invalid-from",{error:e.message}));
|
||||
}
|
||||
}
|
||||
if (rule.tot === 'num') {
|
||||
rule.to = Number(rule.to);
|
||||
} else if (rule.tot === 'json') {
|
||||
try {
|
||||
rule.to = JSON.parse(rule.to);
|
||||
} catch(e2) {
|
||||
valid = false;
|
||||
this.error(RED._("change.errors.invalid-json"));
|
||||
}
|
||||
} else if (rule.tot === 'bool') {
|
||||
rule.to = /^true$/i.test(rule.to);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function applyRule(msg,rule) {
|
||||
var propertyParts;
|
||||
var depth = 0;
|
||||
|
||||
propertyParts = rule.p.split(".");
|
||||
try {
|
||||
propertyParts.reduce(function(obj, i) {
|
||||
var to = rule.to;
|
||||
// Set msg from property to another msg property
|
||||
if (rule.t === "set" && rule.to.indexOf("msg.") === 0) {
|
||||
var parts = to.substring(4);
|
||||
var msgPropParts = parts.split(".");
|
||||
try {
|
||||
msgPropParts.reduce(function(ob, j) {
|
||||
to = (typeof ob[j] !== "undefined" ? ob[j] : undefined);
|
||||
return to;
|
||||
}, msg);
|
||||
} catch (err) {}
|
||||
}
|
||||
var property = rule.p;
|
||||
var value = rule.to;
|
||||
var current;
|
||||
var fromValue;
|
||||
var fromType;
|
||||
var fromRE;
|
||||
if (rule.tot === "msg") {
|
||||
value = RED.util.getMessageProperty(msg,rule.to);
|
||||
} else if (rule.tot === 'flow') {
|
||||
value = node.context().flow.get(rule.to);
|
||||
} else if (rule.tot === 'global') {
|
||||
value = node.context().global.get(rule.to);
|
||||
}
|
||||
|
||||
if (++depth === propertyParts.length) {
|
||||
if (rule.t === "change") {
|
||||
if (typeof obj[i] === "string") {
|
||||
obj[i] = obj[i].replace(rule.from, rule.to);
|
||||
if (rule.t === 'change') {
|
||||
if (rule.fromt === 'msg' || rule.fromt === 'flow' || rule.fromt === 'global') {
|
||||
if (rule.fromt === "msg") {
|
||||
fromValue = RED.util.getMessageProperty(msg,rule.from);
|
||||
} else if (rule.tot === 'flow') {
|
||||
fromValue = node.context().flow.get(rule.from);
|
||||
} else if (rule.tot === 'global') {
|
||||
fromValue = node.context().global.get(rule.from);
|
||||
}
|
||||
if (typeof fromValue === 'number' || fromValue instanceof Number) {
|
||||
fromType = 'num';
|
||||
} else if (typeof fromValue === 'boolean') {
|
||||
fromType = 'bool'
|
||||
} else if (fromValue instanceof RegExp) {
|
||||
fromType = 're';
|
||||
fromRE = fromValue;
|
||||
} else if (typeof fromValue === 'string') {
|
||||
fromType = 'str';
|
||||
fromRE = fromValue.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
try {
|
||||
fromRE = new RegExp(fromRE, "g");
|
||||
} catch (e) {
|
||||
valid = false;
|
||||
node.error(RED._("change.errors.invalid-from",{error:e.message}));
|
||||
return
|
||||
}
|
||||
} else if (rule.t === "set") {
|
||||
if (typeof to === "undefined") {
|
||||
delete(obj[i]);
|
||||
} else {
|
||||
obj[i] = to;
|
||||
}
|
||||
} else if (rule.t === "delete") {
|
||||
delete(obj[i]);
|
||||
|
||||
} else {
|
||||
node.error(RED._("change.errors.invalid-from",{error:"unsupported type: "+(typeof fromValue)}));
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// to property doesn't exist, don't create empty object
|
||||
if (typeof to === "undefined") {
|
||||
return;
|
||||
// setting a non-existent multilevel object, create empty parent
|
||||
} else if (!obj[i]) {
|
||||
obj[i] = {};
|
||||
}
|
||||
return obj[i];
|
||||
fromType = rule.fromt;
|
||||
fromValue = rule.from;
|
||||
fromRE = rule.fromRE;
|
||||
}
|
||||
}, msg);
|
||||
} catch (err) {}
|
||||
}
|
||||
if (rule.pt === 'msg') {
|
||||
if (rule.t === 'delete') {
|
||||
RED.util.setMessageProperty(msg,property,undefined);
|
||||
} else if (rule.t === 'set') {
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
} else if (rule.t === 'change') {
|
||||
current = RED.util.getMessageProperty(msg,property);
|
||||
if (typeof current === 'string') {
|
||||
if ((fromType === 'num' || fromType === 'bool') && current === fromValue) {
|
||||
// str representation of exact from number/boolean
|
||||
// only replace if they match exactly
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
} else {
|
||||
current = current.replace(fromRE,value);
|
||||
RED.util.setMessageProperty(msg,property,current);
|
||||
}
|
||||
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
|
||||
if (current == Number(fromValue)) {
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
}
|
||||
} else if (typeof current === 'boolean' && fromType === 'bool') {
|
||||
if (current.toString() === fromValue) {
|
||||
RED.util.setMessageProperty(msg,property,value);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var target;
|
||||
if (rule.pt === 'flow') {
|
||||
target = node.context().flow;
|
||||
} else if (rule.pt === 'global') {
|
||||
target = node.context().global;
|
||||
}
|
||||
if (target) {
|
||||
if (rule.t === 'delete') {
|
||||
target.set(property,undefined);
|
||||
} else if (rule.t === 'set') {
|
||||
target.set(property,value);
|
||||
} else if (rule.t === 'change') {
|
||||
current = target.get(msg,property);
|
||||
if (typeof current === 'string') {
|
||||
if ((fromType === 'num' || fromType === 'bool') && current === fromValue) {
|
||||
// str representation of exact from number/boolean
|
||||
// only replace if they match exactly
|
||||
target.set(property,value);
|
||||
} else {
|
||||
current = current.replace(fromRE,value);
|
||||
target.set(property,current);
|
||||
}
|
||||
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
|
||||
if (current == Number(fromValue)) {
|
||||
target.set(property,value);
|
||||
}
|
||||
} else if (typeof current === 'boolean' && fromType === 'bool') {
|
||||
if (current.toString() === fromValue) {
|
||||
target.set(property,value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} catch(err) {/*console.log(err.stack)*/}
|
||||
return msg;
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
var node = this;
|
||||
this.on('input', function(msg) {
|
||||
for (var i=0;i<this.rules.length;i++) {
|
||||
msg = applyRule(msg,this.rules[i]);
|
||||
if (msg === null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
node.send(msg);
|
||||
});
|
||||
|
||||
@@ -68,12 +68,12 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="csv">
|
||||
<p>A function that parses the <b>msg.payload</b> to convert CSV to/from a javascript object.
|
||||
<p>A function that parses the <code>msg.payload</code> to convert CSV to/from a javascript object.
|
||||
Places the result in the payload.</p>
|
||||
<p>If the input is a string it tries to parse it as CSV and creates a javascript object.</p>
|
||||
<p>If the input is a javascript object it tries to build a CSV string.</p>
|
||||
<p>If the input is a simple array the output is just a CSV generated from that array.</p>
|
||||
<p>If the input is an array of arrays or an array of objects a multiple-line CSV is created.</p>
|
||||
<p>If the input is an array of arrays, or an array of objects, a multiple-line CSV is created.</p>
|
||||
<p>The columns template should contain an ordered list of column headers. For CSV input these become the property names.
|
||||
For CSV output these specify the properties to extract from the object and the order for the CSV.</p>
|
||||
<p>If the input is an array then the columns template does not matter, but can be used to generate a row of column titles.</p>
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<label for="node-input-ret"><i class="fa fa-sign-out"></i> <span data-i18n="html.label.output"></span></label>
|
||||
<select id="node-input-ret" style="width:76% !important">
|
||||
<option value="html" data-i18n="html.output.html"></option>
|
||||
<option value="text"data-i18n="html.output.text"></option>
|
||||
<!-- <option value="attr">an object of any attributes</option> -->
|
||||
<option value="text" data-i18n="html.output.text"></option>
|
||||
<option value="attr" data-i18n="html.output.attr"></option>
|
||||
<!-- <option value="val">return the value from a form element</option> -->
|
||||
</select>
|
||||
</div>
|
||||
@@ -44,7 +44,7 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="html">
|
||||
<p>Extracts elements from an html document held in <b>msg.payload</b> using a selector.</p>
|
||||
<p>Extracts elements from an html document held in <code>msg.payload</code> using a selector.</p>
|
||||
<p>The selector uses <a href=="https://github.com/cheeriojs/cheerio/blob/master/Readme.md" target="_new">Cheerio</a>
|
||||
which uses the <a href="https://github.com/fb55/CSSselect#user-content-supported-selectors" target="_new">CSS selector</a> syntax.</p>
|
||||
<p>The result can be either a single message with a payload containing an array of the matched elements, or multiple
|
||||
|
||||
@@ -32,9 +32,9 @@ module.exports = function(RED) {
|
||||
$(node.tag).each(function() {
|
||||
if (node.as === "multi") {
|
||||
var pay2 = null;
|
||||
if (node.ret === "html") { pay2 = $(this).html(); }
|
||||
if (node.ret === "html") { pay2 = cheerio.load($(this).html().trim()).xml(); }
|
||||
if (node.ret === "text") { pay2 = $(this).text(); }
|
||||
//if (node.ret === "attr") { pay2 = $(this)[0]["attribs"]; }
|
||||
if (node.ret === "attr") { pay2 = this.attribs; }
|
||||
//if (node.ret === "val") { pay2 = $(this).val(); }
|
||||
/* istanbul ignore else */
|
||||
if (pay2) {
|
||||
@@ -43,9 +43,9 @@ module.exports = function(RED) {
|
||||
}
|
||||
}
|
||||
if (node.as === "single") {
|
||||
if (node.ret === "html") { pay.push( $(this).html() ); }
|
||||
if (node.ret === "html") { pay.push( cheerio.load($(this).html().trim()).xml() ); }
|
||||
if (node.ret === "text") { pay.push( $(this).text() ); }
|
||||
//if (node.ret === "attr") { pay.push( $(this)[0]["attribs"] ); }
|
||||
if (node.ret === "attr") { pay.push( this.attribs ); }
|
||||
//if (node.ret === "val") { pay.push( $(this).val() ); }
|
||||
}
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="json">
|
||||
<p>A function that parses the <b>msg.payload</b> to convert a JSON string to/from a javascript object. Places the result back into the payload.</p>
|
||||
<p>A function that parses the <code>msg.payload</code> to convert a JSON string to/from a javascript object. Places the result back into the payload.</p>
|
||||
<p>If the input is a JSON string it tries to parse it to a javascript object.</p>
|
||||
<p>If the input is a javascript object it creates a JSON string.</p>
|
||||
</script>
|
||||
|
||||
@@ -33,11 +33,11 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="xml">
|
||||
<p>A function that parses the <b>msg.payload</b> to convert xml to/from a javascript object. Places the result in the payload.</p>
|
||||
<p>A function that parses the <code>msg.payload</code> to convert xml to/from a javascript object. Places the result in the payload.</p>
|
||||
<p>If the input is a string it tries to parse it as XML and creates a javascript object.</p>
|
||||
<p>If the input is a javascript object it tries to build an XML string.</p>
|
||||
<p>You can also pass in a <b>msg.options</b> object to overide all the multitude of parameters. See
|
||||
<a href="https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md" target="_new">the xml2js docs <i>here</i></a>
|
||||
<p>You can also pass in a <code>msg.options</code> object to overide all the multitude of parameters. See
|
||||
<a href="https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md#options" target="_new">the xml2js docs</a>
|
||||
for more information.</p>
|
||||
<p>If set, options in the edit dialogue override those passed in on the msg.options object.</p>
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
Copyright 2013 IBM Corp.
|
||||
Copyright 2013,2016 IBM Corp.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -15,11 +15,18 @@
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="tail">
|
||||
<div class="form-row node-input-filename">
|
||||
<div class="form-row">
|
||||
<label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="tail.label.filename"></span></label>
|
||||
<input type="text" id="node-input-filename">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-filetype"><i class="fa fa-file-text-o"></i> <span data-i18n="tail.label.type"></span></label>
|
||||
<select type="text" id="node-input-filetype">
|
||||
<option value="text" data-i18n="tail.action.text"></option>
|
||||
<option value="binary" data-i18n="tail.action.binary"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="node-tail-split">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-split" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-split" style="width: 70%;"><span data-i18n="tail.label.splitlines"></span></label>
|
||||
@@ -28,12 +35,12 @@
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<!-- <div class="form-tips">WON'T work on Windows.</div> -->
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="tail">
|
||||
<p>Tails (watches for things to be added) to the configured file. (Linux/Mac ONLY)</p>
|
||||
<p>This won't work on Windows filesystems, as it relies on the tail -F command.</p>
|
||||
<p>This will not work on Windows filesystems, as it relies on the <b>tail -F</b> command.</p>
|
||||
<p>Text (UTF-8) files will be returned as strings. Binary files will be returned as a Buffer object.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
@@ -41,6 +48,7 @@
|
||||
category: 'storage-input',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
filetype: {value:"text"},
|
||||
split: {value:false},
|
||||
filename: {value:"",required:true}
|
||||
},
|
||||
@@ -53,6 +61,12 @@
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
$("#node-input-filetype").on("change",function() {
|
||||
if (this.value === "text") { $("#node-tail-split").show(); }
|
||||
else { $("#node-tail-split").hide(); }
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013, 2014 IBM Corp.
|
||||
* Copyright 2013,2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,31 +27,36 @@ module.exports = function(RED) {
|
||||
RED.nodes.createNode(this,n);
|
||||
|
||||
this.filename = n.filename;
|
||||
this.split = n.split;
|
||||
this.filetype = n.filetype || "text";
|
||||
this.split = n.split || false;
|
||||
var node = this;
|
||||
|
||||
var err = "";
|
||||
// TODO: rewrite to use node-tail
|
||||
var tail = spawn("tail", ["-F", "-n", "0", this.filename]);
|
||||
tail.stdout.on("data", function (data) {
|
||||
if (node.split) {
|
||||
// TODO: allow customisation of the line break - as we do elsewhere
|
||||
var strings = data.toString().split("\n");
|
||||
for (var s in strings) {
|
||||
//TODO: should we really filter blanks? Is that expected?
|
||||
if (strings[s] !== "") {
|
||||
node.send({
|
||||
topic: node.filename,
|
||||
payload: strings[s]
|
||||
});
|
||||
var msg = { topic:node.filename };
|
||||
if (node.filetype === "text") {
|
||||
if (node.split) {
|
||||
// TODO: allow customisation of the line break - as we do elsewhere
|
||||
var strings = data.toString().split("\n");
|
||||
for (var s in strings) {
|
||||
//TODO: should we really filter blanks? Is that expected?
|
||||
if (strings[s] !== "") {
|
||||
node.send({
|
||||
topic: node.filename,
|
||||
payload: strings[s]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
msg.payload = data.toString();
|
||||
node.send(msg);
|
||||
}
|
||||
}
|
||||
else {
|
||||
var msg = {
|
||||
topic:node.filename,
|
||||
payload: data.toString()
|
||||
};
|
||||
msg.payload = data;
|
||||
node.send(msg);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -44,11 +44,12 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="file">
|
||||
<p>Writes <b>msg.payload</b> to the file specified, e.g. to create a log.</p>
|
||||
<p>The filename can be configured in the node, if left blank it should be set in an incoming message on <b>msg.filename</b>.</p>
|
||||
<p>Writes <code>msg.payload</code> to the file specified, for example to create a log.</p>
|
||||
<p>The filename can be configured in the node. If left blank it should be
|
||||
set by <code>msg.filename</code> on the incoming message.</p>
|
||||
<p>A newline is added to every message. But this can be turned off if required, for example, to allow binary files to be written.</p>
|
||||
<p>The default behaviour is to append to the file. This can be changed to overwrite the file each time, for example if you want to output a "static" web page or report.</p>
|
||||
<p>This node can also be configured to delete a file if required. <i>Note:</i> Using msg.delete is now deprecated.</p>
|
||||
<p>This node can also be configured to delete a file if required.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="file in">
|
||||
@@ -70,8 +71,10 @@
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="file in">
|
||||
<p>Reads the specified file and sends the content as <b>msg.payload</b>, and the filename as <b>msg.filename</b>.</p>
|
||||
<p>The filename can be configured in the node, if left blank it should be set in an incoming message on <b>msg.filename</b>.</p>
|
||||
<p>Reads the specified file and sends the content as <code>msg.payload</code>,
|
||||
and the filename as <code>msg.filename</code>.</p>
|
||||
<p>The filename can be configured in the node. If left blank it should be
|
||||
set by <code>msg.filename</code> on the incoming message.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
@@ -42,6 +42,7 @@ module.exports = function(RED) {
|
||||
if (typeof data === "boolean") { data = data.toString(); }
|
||||
if (typeof data === "number") { data = data.toString(); }
|
||||
if ((this.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
|
||||
data = new Buffer(data);
|
||||
if (this.overwriteFile === "true") {
|
||||
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while
|
||||
fs.writeFile(filename, data, "binary", function (err) {
|
||||
|
||||
44
package.json
44
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name" : "node-red",
|
||||
"version" : "0.12.4",
|
||||
"version" : "0.13.4",
|
||||
"description" : "A visual tool for wiring the Internet of Things",
|
||||
"homepage" : "http://nodered.org",
|
||||
"license" : "Apache-2.0",
|
||||
@@ -22,38 +22,38 @@
|
||||
{"name": "Dave Conway-Jones"}
|
||||
],
|
||||
"keywords": [
|
||||
"editor", "messaging", "iot", "m2m", "pi", "arduino", "beaglebone", "ibm", "flow"
|
||||
"editor", "messaging", "iot", "ibm", "flow"
|
||||
],
|
||||
"dependencies": {
|
||||
"basic-auth": "1.0.3",
|
||||
"bcryptjs": "2.3.0",
|
||||
"body-parser": "1.14.1",
|
||||
"body-parser": "1.15.0",
|
||||
"cheerio":"0.19.0",
|
||||
"clone": "1.0.2",
|
||||
"cors":"2.7.1",
|
||||
"cron":"1.1.0",
|
||||
"express": "4.13.3",
|
||||
"express": "4.13.4",
|
||||
"follow-redirects":"0.0.7",
|
||||
"fs-extra": "0.26.2",
|
||||
"fs-extra": "0.26.7",
|
||||
"fs.notify":"0.0.4",
|
||||
"i18next":"1.10.6",
|
||||
"is-utf8":"0.2.0",
|
||||
"is-utf8":"0.2.1",
|
||||
"media-typer": "0.3.0",
|
||||
"mqtt": "1.6.1",
|
||||
"mustache": "2.2.0",
|
||||
"mqtt": "1.7.4",
|
||||
"mustache": "2.2.1",
|
||||
"nopt": "3.0.6",
|
||||
"oauth2orize":"1.2.0",
|
||||
"oauth2orize":"1.2.2",
|
||||
"on-headers":"1.0.1",
|
||||
"passport":"0.3.2",
|
||||
"passport-http-bearer":"1.0.1",
|
||||
"passport-oauth2-client-password":"0.1.2",
|
||||
"raw-body":"2.1.5",
|
||||
"raw-body":"2.1.6",
|
||||
"semver": "5.1.0",
|
||||
"sentiment":"0.2.3",
|
||||
"uglify-js":"2.6.1",
|
||||
"when": "3.7.5",
|
||||
"sentiment":"1.0.6",
|
||||
"uglify-js":"2.6.2",
|
||||
"when": "3.7.7",
|
||||
"ws": "0.8.1",
|
||||
"xml2js":"0.4.15",
|
||||
"xml2js":"0.4.16",
|
||||
"node-red-node-feedparser":"0.1.*",
|
||||
"node-red-node-email":"0.1.*",
|
||||
"node-red-node-twitter":"0.1.*",
|
||||
@@ -67,22 +67,22 @@
|
||||
"grunt": "0.4.5",
|
||||
"grunt-chmod": "1.1.1",
|
||||
"grunt-cli": "0.1.13",
|
||||
"grunt-concurrent":"2.1.0",
|
||||
"grunt-concurrent":"2.2.1",
|
||||
"grunt-contrib-clean":"0.7.0",
|
||||
"grunt-contrib-compress": "0.14.0",
|
||||
"grunt-contrib-concat":"0.5.1",
|
||||
"grunt-contrib-copy": "0.8.2",
|
||||
"grunt-contrib-jshint": "0.11.3",
|
||||
"grunt-contrib-uglify": "0.11.0",
|
||||
"grunt-contrib-jshint": "0.12.0",
|
||||
"grunt-contrib-uglify": "0.11.1",
|
||||
"grunt-contrib-watch":"0.6.1",
|
||||
"grunt-jsonlint":"1.0.6",
|
||||
"grunt-jsonlint":"1.0.7",
|
||||
"grunt-nodemon":"0.4.1",
|
||||
"grunt-sass":"1.1.0",
|
||||
"grunt-simple-mocha": "0.4.0",
|
||||
"mocha": "2.3.4",
|
||||
"grunt-simple-mocha": "0.4.1",
|
||||
"mocha": "2.4.5",
|
||||
"should": "6.0.3",
|
||||
"sinon": "1.17.2",
|
||||
"supertest": "1.1.0"
|
||||
"sinon": "1.17.3",
|
||||
"supertest": "1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
|
||||
30
red.js
30
red.js
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
* Copyright 2013, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,7 +25,6 @@ var nopt = require("nopt");
|
||||
var path = require("path");
|
||||
var fs = require("fs-extra");
|
||||
var RED = require("./red/red.js");
|
||||
var log = require("./red/log");
|
||||
|
||||
var server;
|
||||
var app = express();
|
||||
@@ -36,12 +35,14 @@ var flowFile;
|
||||
var knownOpts = {
|
||||
"settings":[path],
|
||||
"userDir":[path],
|
||||
"port": Number,
|
||||
"v": Boolean,
|
||||
"help": Boolean
|
||||
};
|
||||
var shortHands = {
|
||||
"s":["--settings"],
|
||||
"u":["--userDir"],
|
||||
"p":["--port"],
|
||||
"?":["--help"]
|
||||
};
|
||||
nopt.invalidHandler = function(k,v,t) {
|
||||
@@ -52,11 +53,13 @@ var parsedArgs = nopt(knownOpts,shortHands,process.argv,2)
|
||||
|
||||
if (parsedArgs.help) {
|
||||
console.log("Node-RED v"+RED.version());
|
||||
console.log("Usage: node-red [-v] [-?] [--settings settings.js] [--userDir DIR] [flows.json]");
|
||||
console.log("Usage: node-red [-v] [-?] [--settings settings.js] [--userDir DIR]");
|
||||
console.log(" [--port PORT] [flows.json]");
|
||||
console.log("");
|
||||
console.log("Options:");
|
||||
console.log(" -s, --settings FILE use specified settings file");
|
||||
console.log(" -u, --userDir DIR use specified user directory");
|
||||
console.log(" -p, --port PORT port to listen on");
|
||||
console.log(" -v enable verbose output");
|
||||
console.log(" -?, --help show usage");
|
||||
console.log("");
|
||||
@@ -154,7 +157,7 @@ if (settings.httpNodeRoot !== false) {
|
||||
settings.httpNodeAuth = settings.httpNodeAuth || settings.httpAuth;
|
||||
}
|
||||
|
||||
settings.uiPort = settings.uiPort||1880;
|
||||
settings.uiPort = parsedArgs.port||settings.uiPort||1880;
|
||||
settings.uiHost = settings.uiHost||"0.0.0.0";
|
||||
|
||||
if (flowFile) {
|
||||
@@ -195,6 +198,9 @@ function basicAuthMiddleware(user,pass) {
|
||||
}
|
||||
|
||||
return function(req,res,next) {
|
||||
if (req.method === 'OPTIONS') {
|
||||
return next();
|
||||
}
|
||||
var requestUser = basicAuth(req);
|
||||
if (!requestUser || requestUser.name !== user || !checkPassword(requestUser.pass)) {
|
||||
res.set('WWW-Authenticate', 'Basic realm=Authorization Required');
|
||||
@@ -205,7 +211,7 @@ function basicAuthMiddleware(user,pass) {
|
||||
}
|
||||
|
||||
if (settings.httpAdminRoot !== false && settings.httpAdminAuth) {
|
||||
RED.log.warn(log._("server.httpadminauth-deprecated"));
|
||||
RED.log.warn(RED.log._("server.httpadminauth-deprecated"));
|
||||
app.use(settings.httpAdminRoot, basicAuthMiddleware(settings.httpAdminAuth.user,settings.httpAdminAuth.pass));
|
||||
}
|
||||
|
||||
@@ -243,10 +249,10 @@ RED.start().then(function() {
|
||||
if (settings.httpAdminRoot !== false || settings.httpNodeRoot !== false || settings.httpStatic) {
|
||||
server.on('error', function(err) {
|
||||
if (err.errno === "EADDRINUSE") {
|
||||
RED.log.error(log._("server.unable-to-listen", {listenpath:getListenPath()}));
|
||||
RED.log.error(log._("server.port-in-use"));
|
||||
RED.log.error(RED.log._("server.unable-to-listen", {listenpath:getListenPath()}));
|
||||
RED.log.error(RED.log._("server.port-in-use"));
|
||||
} else {
|
||||
RED.log.error(log._("server.uncaught-exception"));
|
||||
RED.log.error(RED.log._("server.uncaught-exception"));
|
||||
if (err.stack) {
|
||||
RED.log.error(err.stack);
|
||||
} else {
|
||||
@@ -257,16 +263,16 @@ RED.start().then(function() {
|
||||
});
|
||||
server.listen(settings.uiPort,settings.uiHost,function() {
|
||||
if (settings.httpAdminRoot === false) {
|
||||
RED.log.info(log._("server.admin-ui-disabled"));
|
||||
RED.log.info(RED.log._("server.admin-ui-disabled"));
|
||||
}
|
||||
process.title = 'node-red';
|
||||
RED.log.info(log._("server.now-running", {listenpath:getListenPath()}));
|
||||
RED.log.info(RED.log._("server.now-running", {listenpath:getListenPath()}));
|
||||
});
|
||||
} else {
|
||||
RED.log.info(log._("server.headless-mode"));
|
||||
RED.log.info(RED.log._("server.headless-mode"));
|
||||
}
|
||||
}).otherwise(function(err) {
|
||||
RED.log.error(log._("server.failed-to-start"));
|
||||
RED.log.error(RED.log._("server.failed-to-start"));
|
||||
if (err.stack) {
|
||||
RED.log.error(err.stack);
|
||||
} else {
|
||||
|
||||
@@ -25,7 +25,7 @@ var permissions = require("./permissions");
|
||||
var theme = require("../theme");
|
||||
|
||||
var settings = null;
|
||||
var log = require("../../log");
|
||||
var log = null
|
||||
|
||||
|
||||
passport.use(strategies.bearerStrategy.BearerStrategy);
|
||||
@@ -36,11 +36,13 @@ var server = oauth2orize.createServer();
|
||||
|
||||
server.exchange(oauth2orize.exchange.password(strategies.passwordTokenExchange));
|
||||
|
||||
function init(_settings,storage) {
|
||||
settings = _settings;
|
||||
function init(runtime) {
|
||||
settings = runtime.settings;
|
||||
log = runtime.log;
|
||||
if (settings.adminAuth) {
|
||||
Users.init(settings.adminAuth);
|
||||
Tokens.init(settings.adminAuth,storage);
|
||||
Tokens.init(settings.adminAuth,runtime.storage);
|
||||
strategies.init(runtime);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ var Users = require("./users");
|
||||
var Clients = require("./clients");
|
||||
var permissions = require("./permissions");
|
||||
|
||||
var log = require("../../log");
|
||||
var log;
|
||||
|
||||
var bearerStrategy = function (accessToken, done) {
|
||||
// is this a valid token?
|
||||
@@ -124,6 +124,9 @@ AnonymousStrategy.prototype.authenticate = function(req) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
log = runtime.log;
|
||||
},
|
||||
bearerStrategy: bearerStrategy,
|
||||
clientPasswordStrategy: clientPasswordStrategy,
|
||||
passwordTokenExchange: passwordTokenExchange,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2014, 2015 IBM Corp.
|
||||
* Copyright 2014, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,7 +15,7 @@
|
||||
**/
|
||||
|
||||
var ws = require("ws");
|
||||
var log = require("./log");
|
||||
var log;
|
||||
|
||||
var server;
|
||||
var settings;
|
||||
@@ -29,23 +29,37 @@ var retained = {};
|
||||
var heartbeatTimer;
|
||||
var lastSentTime;
|
||||
|
||||
|
||||
function init(_server,_settings) {
|
||||
server = _server;
|
||||
settings = _settings;
|
||||
function handleStatus(event) {
|
||||
publish("status/"+event.id,event.status,true);
|
||||
}
|
||||
|
||||
function init(_server,runtime) {
|
||||
server = _server;
|
||||
settings = runtime.settings;
|
||||
log = runtime.log;
|
||||
|
||||
runtime.events.removeListener("node-status",handleStatus);
|
||||
runtime.events.on("node-status",handleStatus);
|
||||
}
|
||||
|
||||
function start() {
|
||||
var Tokens = require("./api/auth/tokens");
|
||||
var Users = require("./api/auth/users");
|
||||
var Permissions = require("./api/auth/permissions");
|
||||
var Tokens = require("./auth/tokens");
|
||||
var Users = require("./auth/users");
|
||||
var Permissions = require("./auth/permissions");
|
||||
if (!settings.disableEditor) {
|
||||
Users.default().then(function(anonymousUser) {
|
||||
var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000;
|
||||
var path = settings.httpAdminRoot || "/";
|
||||
path = (path.slice(0,1) != "/" ? "/":"") + path + (path.slice(-1) == "/" ? "":"/") + "comms";
|
||||
wsServer = new ws.Server({server:server,path:path});
|
||||
wsServer = new ws.Server({
|
||||
server:server,
|
||||
path:path,
|
||||
// Disable the deflate option due to this issue
|
||||
// https://github.com/websockets/ws/pull/632
|
||||
// that is fixed in the 1.x release of the ws module
|
||||
// that we cannot currently pickup as it drops node 0.10 support
|
||||
perMessageDeflate: false
|
||||
});
|
||||
|
||||
wsServer.on('connection',function(ws) {
|
||||
log.audit({event: "comms.open"});
|
||||
@@ -151,15 +165,17 @@ function stop() {
|
||||
}
|
||||
|
||||
function publish(topic,data,retain) {
|
||||
if (retain) {
|
||||
retained[topic] = data;
|
||||
} else {
|
||||
delete retained[topic];
|
||||
if (server) {
|
||||
if (retain) {
|
||||
retained[topic] = data;
|
||||
} else {
|
||||
delete retained[topic];
|
||||
}
|
||||
lastSentTime = Date.now();
|
||||
activeConnections.forEach(function(conn) {
|
||||
publishTo(conn,topic,data);
|
||||
});
|
||||
}
|
||||
lastSentTime = Date.now();
|
||||
activeConnections.forEach(function(conn) {
|
||||
publishTo(conn,topic,data);
|
||||
});
|
||||
}
|
||||
|
||||
function publishTo(ws,topic,data) {
|
||||
@@ -14,10 +14,14 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var log = require("../log");
|
||||
var api = require("../nodes");
|
||||
var log;
|
||||
var api;
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
log = runtime.log;
|
||||
api = runtime.nodes;
|
||||
},
|
||||
get: function (req, res) {
|
||||
// TODO: It should verify the given node id is of the type specified -
|
||||
// but that would add a dependency from this module to the
|
||||
|
||||
87
red/api/flow.js
Normal file
87
red/api/flow.js
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright 2014, 2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var log;
|
||||
var redNodes;
|
||||
var settings;
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
settings = runtime.settings;
|
||||
redNodes = runtime.nodes;
|
||||
log = runtime.log;
|
||||
},
|
||||
get: function(req,res) {
|
||||
var id = req.params.id;
|
||||
var flow = redNodes.getFlow(id);
|
||||
if (flow) {
|
||||
log.audit({event: "flow.get",id:id},req);
|
||||
res.json(flow);
|
||||
} else {
|
||||
log.audit({event: "flow.get",id:id,error:"not_found"},req);
|
||||
res.status(404).end();
|
||||
}
|
||||
},
|
||||
post: function(req,res) {
|
||||
var flow = req.body;
|
||||
redNodes.addFlow(flow).then(function(id) {
|
||||
log.audit({event: "flow.add",id:id},req);
|
||||
res.json({id:id});
|
||||
}).otherwise(function(err) {
|
||||
log.audit({event: "flow.add",error:err.code||"unexpected_error",message:err.toString()},req);
|
||||
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
|
||||
})
|
||||
|
||||
},
|
||||
put: function(req,res) {
|
||||
var id = req.params.id;
|
||||
var flow = req.body;
|
||||
try {
|
||||
redNodes.updateFlow(id,flow).then(function() {
|
||||
log.audit({event: "flow.update",id:id},req);
|
||||
res.json({id:id});
|
||||
}).otherwise(function(err) {
|
||||
log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()},req);
|
||||
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
|
||||
})
|
||||
} catch(err) {
|
||||
if (err.code === 404) {
|
||||
log.audit({event: "flow.update",id:id,error:"not_found"},req);
|
||||
res.status(404).end();
|
||||
} else {
|
||||
log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()},req);
|
||||
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
|
||||
}
|
||||
}
|
||||
},
|
||||
delete: function(req,res) {
|
||||
var id = req.params.id;
|
||||
try {
|
||||
redNodes.removeFlow(id).then(function() {
|
||||
log.audit({event: "flow.remove",id:id},req);
|
||||
res.status(204).end();
|
||||
})
|
||||
} catch(err) {
|
||||
if (err.code === 404) {
|
||||
log.audit({event: "flow.remove",id:id,error:"not_found"},req);
|
||||
res.status(404).end();
|
||||
} else {
|
||||
log.audit({event: "flow.remove",id:id,error:err.code||"unexpected_error",message:err.toString()},req);
|
||||
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,16 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var log = require("../log");
|
||||
|
||||
var redNodes = require("../nodes");
|
||||
var settings = require("../settings");
|
||||
var log;
|
||||
var redNodes;
|
||||
var settings;
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
settings = runtime.settings;
|
||||
redNodes = runtime.nodes;
|
||||
log = runtime.log;
|
||||
},
|
||||
get: function(req,res) {
|
||||
log.audit({event: "flows.get"},req);
|
||||
res.json(redNodes.getFlows());
|
||||
@@ -28,12 +32,22 @@ module.exports = {
|
||||
var flows = req.body;
|
||||
var deploymentType = req.get("Node-RED-Deployment-Type")||"full";
|
||||
log.audit({event: "flows.set",type:deploymentType},req);
|
||||
redNodes.setFlows(flows,deploymentType).then(function() {
|
||||
res.status(204).end();
|
||||
}).otherwise(function(err) {
|
||||
log.warn(log._("api.flows.error-save",{message:err.message}));
|
||||
log.warn(err.stack);
|
||||
res.status(500).json({error:"unexpected_error", message:err.message});
|
||||
});
|
||||
if (deploymentType === 'reload') {
|
||||
redNodes.loadFlows().then(function() {
|
||||
res.status(204).end();
|
||||
}).otherwise(function(err) {
|
||||
log.warn(log._("api.flows.error-reload",{message:err.message}));
|
||||
log.warn(err.stack);
|
||||
res.status(500).json({error:"unexpected_error", message:err.message});
|
||||
});
|
||||
} else {
|
||||
redNodes.setFlows(flows,deploymentType).then(function() {
|
||||
res.status(204).end();
|
||||
}).otherwise(function(err) {
|
||||
log.warn(log._("api.flows.error-save",{message:err.message}));
|
||||
log.warn(err.stack);
|
||||
res.status(500).json({error:"unexpected_error", message:err.message});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
195
red/api/index.js
195
red/api/index.js
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2014 IBM Corp.
|
||||
* Copyright 2014, 2016 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,22 +19,28 @@ var bodyParser = require("body-parser");
|
||||
var util = require('util');
|
||||
var path = require('path');
|
||||
var passport = require('passport');
|
||||
var when = require('when');
|
||||
|
||||
var ui = require("./ui");
|
||||
var nodes = require("./nodes");
|
||||
var flows = require("./flows");
|
||||
var flow = require("./flow");
|
||||
var library = require("./library");
|
||||
var info = require("./info");
|
||||
var theme = require("./theme");
|
||||
var locales = require("./locales");
|
||||
var credentials = require("./credentials");
|
||||
|
||||
var log = require("../log");
|
||||
var comms = require("./comms");
|
||||
|
||||
var auth = require("./auth");
|
||||
var needsPermission = auth.needsPermission;
|
||||
|
||||
var settings = require("../settings");
|
||||
var i18n;
|
||||
var log;
|
||||
var adminApp;
|
||||
var nodeApp;
|
||||
var server;
|
||||
var runtime;
|
||||
|
||||
var errorHandler = function(err,req,res,next) {
|
||||
if (err.message === "request entity too large") {
|
||||
@@ -46,71 +52,126 @@ var errorHandler = function(err,req,res,next) {
|
||||
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||
};
|
||||
|
||||
function init(adminApp,storage) {
|
||||
|
||||
auth.init(settings,storage);
|
||||
// Editor
|
||||
if (!settings.disableEditor) {
|
||||
ui.init(settings);
|
||||
var editorApp = express();
|
||||
editorApp.get("/",ui.ensureSlash,ui.editor);
|
||||
editorApp.get("/icons/:icon",ui.icon);
|
||||
if (settings.editorTheme) {
|
||||
editorApp.use("/theme",theme.init(settings));
|
||||
}
|
||||
editorApp.use("/",ui.editorResources);
|
||||
adminApp.use(editorApp);
|
||||
var ensureRuntimeStarted = function(req,res,next) {
|
||||
if (!runtime.isStarted()) {
|
||||
log.error("Node-RED runtime not started");
|
||||
res.status(503).send("Not started");
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
var maxApiRequestSize = settings.apiMaxLength || '1mb';
|
||||
adminApp.use(bodyParser.json({limit:maxApiRequestSize}));
|
||||
adminApp.use(bodyParser.urlencoded({limit:maxApiRequestSize,extended:true}));
|
||||
|
||||
adminApp.get("/auth/login",auth.login);
|
||||
|
||||
if (settings.adminAuth) {
|
||||
//TODO: all passport references ought to be in ./auth
|
||||
adminApp.use(passport.initialize());
|
||||
adminApp.post("/auth/token",
|
||||
auth.ensureClientSecret,
|
||||
auth.authenticateClient,
|
||||
auth.getToken,
|
||||
auth.errorHandler
|
||||
);
|
||||
adminApp.post("/auth/revoke",needsPermission(""),auth.revoke);
|
||||
}
|
||||
|
||||
// Flows
|
||||
adminApp.get("/flows",needsPermission("flows.read"),flows.get);
|
||||
adminApp.post("/flows",needsPermission("flows.write"),flows.post);
|
||||
|
||||
// Nodes
|
||||
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll);
|
||||
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post);
|
||||
|
||||
adminApp.get("/nodes/:mod",needsPermission("nodes.read"),nodes.getModule);
|
||||
adminApp.put("/nodes/:mod",needsPermission("nodes.write"),nodes.putModule);
|
||||
adminApp.delete("/nodes/:mod",needsPermission("nodes.write"),nodes.delete);
|
||||
|
||||
adminApp.get("/nodes/:mod/:set",needsPermission("nodes.read"),nodes.getSet);
|
||||
adminApp.put("/nodes/:mod/:set",needsPermission("nodes.write"),nodes.putSet);
|
||||
|
||||
adminApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get);
|
||||
|
||||
adminApp.get(/locales\/(.+)\/?$/,locales.get);
|
||||
|
||||
// Library
|
||||
library.init(adminApp);
|
||||
adminApp.post(new RegExp("/library/flows\/(.*)"),needsPermission("library.write"),library.post);
|
||||
adminApp.get("/library/flows",needsPermission("library.read"),library.getAll);
|
||||
adminApp.get(new RegExp("/library/flows\/(.*)"),needsPermission("library.read"),library.get);
|
||||
|
||||
// Settings
|
||||
adminApp.get("/settings",needsPermission("settings.read"),info.settings);
|
||||
|
||||
// Error Handler
|
||||
adminApp.use(errorHandler);
|
||||
}
|
||||
|
||||
function init(_server,_runtime) {
|
||||
server = _server;
|
||||
runtime = _runtime;
|
||||
var settings = runtime.settings;
|
||||
i18n = runtime.i18n;
|
||||
log = runtime.log;
|
||||
if (settings.httpNodeRoot !== false) {
|
||||
nodeApp = express();
|
||||
}
|
||||
if (settings.httpAdminRoot !== false) {
|
||||
comms.init(server,runtime);
|
||||
adminApp = express();
|
||||
auth.init(runtime);
|
||||
credentials.init(runtime);
|
||||
flows.init(runtime);
|
||||
flow.init(runtime);
|
||||
info.init(runtime);
|
||||
library.init(adminApp,runtime);
|
||||
locales.init(runtime);
|
||||
nodes.init(runtime);
|
||||
|
||||
// Editor
|
||||
if (!settings.disableEditor) {
|
||||
ui.init(runtime);
|
||||
var editorApp = express();
|
||||
editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor);
|
||||
editorApp.get("/icons/:icon",ui.icon);
|
||||
theme.init(runtime);
|
||||
if (settings.editorTheme) {
|
||||
editorApp.use("/theme",theme.app());
|
||||
}
|
||||
editorApp.use("/",ui.editorResources);
|
||||
adminApp.use(editorApp);
|
||||
}
|
||||
var maxApiRequestSize = settings.apiMaxLength || '1mb';
|
||||
adminApp.use(bodyParser.json({limit:maxApiRequestSize}));
|
||||
adminApp.use(bodyParser.urlencoded({limit:maxApiRequestSize,extended:true}));
|
||||
|
||||
adminApp.get("/auth/login",auth.login,errorHandler);
|
||||
|
||||
if (settings.adminAuth) {
|
||||
//TODO: all passport references ought to be in ./auth
|
||||
adminApp.use(passport.initialize());
|
||||
adminApp.post("/auth/token",
|
||||
auth.ensureClientSecret,
|
||||
auth.authenticateClient,
|
||||
auth.getToken,
|
||||
auth.errorHandler
|
||||
);
|
||||
adminApp.post("/auth/revoke",needsPermission(""),auth.revoke,errorHandler);
|
||||
}
|
||||
|
||||
// Flows
|
||||
adminApp.get("/flows",needsPermission("flows.read"),flows.get,errorHandler);
|
||||
adminApp.post("/flows",needsPermission("flows.write"),flows.post,errorHandler);
|
||||
|
||||
adminApp.get("/flow/:id",needsPermission("flows.read"),flow.get,errorHandler);
|
||||
adminApp.post("/flow",needsPermission("flows.write"),flow.post,errorHandler);
|
||||
adminApp.delete("/flow/:id",needsPermission("flows.write"),flow.delete,errorHandler);
|
||||
adminApp.put("/flow/:id",needsPermission("flows.write"),flow.put,errorHandler);
|
||||
|
||||
// Nodes
|
||||
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,errorHandler);
|
||||
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,errorHandler);
|
||||
|
||||
adminApp.get("/nodes/:mod",needsPermission("nodes.read"),nodes.getModule,errorHandler);
|
||||
adminApp.put("/nodes/:mod",needsPermission("nodes.write"),nodes.putModule,errorHandler);
|
||||
adminApp.delete("/nodes/:mod",needsPermission("nodes.write"),nodes.delete,errorHandler);
|
||||
|
||||
adminApp.get("/nodes/:mod/:set",needsPermission("nodes.read"),nodes.getSet,errorHandler);
|
||||
adminApp.put("/nodes/:mod/:set",needsPermission("nodes.write"),nodes.putSet,errorHandler);
|
||||
|
||||
adminApp.get('/credentials/:type/:id', needsPermission("credentials.read"),credentials.get,errorHandler);
|
||||
|
||||
adminApp.get(/locales\/(.+)\/?$/,locales.get,errorHandler);
|
||||
|
||||
// Library
|
||||
adminApp.post(new RegExp("/library/flows\/(.*)"),needsPermission("library.write"),library.post,errorHandler);
|
||||
adminApp.get("/library/flows",needsPermission("library.read"),library.getAll,errorHandler);
|
||||
adminApp.get(new RegExp("/library/flows\/(.*)"),needsPermission("library.read"),library.get,errorHandler);
|
||||
|
||||
// Settings
|
||||
adminApp.get("/settings",needsPermission("settings.read"),info.settings,errorHandler);
|
||||
|
||||
// Error Handler
|
||||
//adminApp.use(errorHandler);
|
||||
}
|
||||
}
|
||||
function start() {
|
||||
return i18n.registerMessageCatalog("editor",path.resolve(path.join(__dirname,"locales")),"editor.json").then(function(){
|
||||
comms.start();
|
||||
});
|
||||
}
|
||||
function stop() {
|
||||
comms.stop();
|
||||
return when.resolve();
|
||||
}
|
||||
module.exports = {
|
||||
init: init
|
||||
init: init,
|
||||
start: start,
|
||||
stop: stop,
|
||||
library: {
|
||||
register: library.register
|
||||
},
|
||||
auth: {
|
||||
needsPermission: auth.needsPermission
|
||||
},
|
||||
comms: {
|
||||
publish: comms.publish
|
||||
},
|
||||
get adminApp() { return adminApp; },
|
||||
get nodeApp() { return nodeApp; },
|
||||
get server() { return server; }
|
||||
};
|
||||
|
||||
@@ -13,28 +13,30 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
var settings = require('../settings');
|
||||
var theme = require("./theme");
|
||||
|
||||
var util = require('util');
|
||||
var settings;
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
settings = runtime.settings;
|
||||
},
|
||||
settings: function(req,res) {
|
||||
var safeSettings = {
|
||||
httpNodeRoot: settings.httpNodeRoot,
|
||||
version: settings.version,
|
||||
user: req.user
|
||||
}
|
||||
|
||||
|
||||
var themeSettings = theme.settings();
|
||||
if (themeSettings) {
|
||||
safeSettings.editorTheme = themeSettings;
|
||||
}
|
||||
|
||||
|
||||
if (util.isArray(settings.paletteCategories)) {
|
||||
safeSettings.paletteCategories = settings.paletteCategories;
|
||||
}
|
||||
|
||||
|
||||
res.json(safeSettings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
var fs = require('fs');
|
||||
var fspath = require('path');
|
||||
var when = require('when');
|
||||
|
||||
var redApp = null;
|
||||
var storage = require("../storage");
|
||||
var log = require("../log");
|
||||
|
||||
var storage;
|
||||
var log;
|
||||
var needsPermission = require("./auth").needsPermission;
|
||||
|
||||
function createLibrary(type) {
|
||||
@@ -69,38 +71,135 @@ function createLibrary(type) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var exampleRoots = {};
|
||||
var exampleFlows = {d:{}};
|
||||
var exampleCount = 0;
|
||||
|
||||
function getFlowsFromPath(path) {
|
||||
return when.promise(function(resolve,reject) {
|
||||
var result = {};
|
||||
fs.readdir(path,function(err,files) {
|
||||
var promises = [];
|
||||
var validFiles = [];
|
||||
files.forEach(function(file) {
|
||||
var fullPath = fspath.join(path,file);
|
||||
var stats = fs.lstatSync(fullPath);
|
||||
if (stats.isDirectory()) {
|
||||
validFiles.push(file);
|
||||
promises.push(getFlowsFromPath(fullPath));
|
||||
} else if (/\.json$/.test(file)){
|
||||
validFiles.push(file);
|
||||
exampleCount++;
|
||||
promises.push(when.resolve(file.split(".")[0]))
|
||||
}
|
||||
})
|
||||
var i=0;
|
||||
when.all(promises).then(function(results) {
|
||||
results.forEach(function(r) {
|
||||
if (typeof r === 'string') {
|
||||
result.f = result.f||[];
|
||||
result.f.push(r);
|
||||
} else {
|
||||
result.d = result.d||{};
|
||||
result.d[validFiles[i]] = r;
|
||||
}
|
||||
i++;
|
||||
})
|
||||
|
||||
resolve(result);
|
||||
})
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function addNodeExamplesDir(module) {
|
||||
exampleRoots[module.name] = module.path;
|
||||
getFlowsFromPath(module.path).then(function(result) {
|
||||
exampleFlows.d[module.name] = result;
|
||||
});
|
||||
}
|
||||
function removeNodeExamplesDir(module) {
|
||||
delete exampleRoots[module];
|
||||
delete exampleFlows.d[module];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(app) {
|
||||
init: function(app,runtime) {
|
||||
redApp = app;
|
||||
log = runtime.log;
|
||||
storage = runtime.storage;
|
||||
// TODO: this allows init to be called multiple times without
|
||||
// registering multiple instances of the listener.
|
||||
// It isn't.... ideal.
|
||||
runtime.events.removeListener("node-examples-dir",addNodeExamplesDir);
|
||||
runtime.events.on("node-examples-dir",addNodeExamplesDir);
|
||||
runtime.events.removeListener("node-module-uninstalled",removeNodeExamplesDir);
|
||||
runtime.events.on("node-module-uninstalled",removeNodeExamplesDir);
|
||||
|
||||
},
|
||||
register: createLibrary,
|
||||
|
||||
getAll: function(req,res) {
|
||||
storage.getAllFlows().then(function(flows) {
|
||||
log.audit({event: "library.get.all",type:"flow"},req);
|
||||
if (exampleCount > 0) {
|
||||
flows.d = flows.d||{};
|
||||
flows.d._examples_ = exampleFlows;
|
||||
}
|
||||
res.json(flows);
|
||||
});
|
||||
},
|
||||
get: function(req,res) {
|
||||
storage.getFlow(req.params[0]).then(function(data) {
|
||||
// data is already a JSON string
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0]},req);
|
||||
res.set('Content-Type', 'application/json');
|
||||
res.send(data);
|
||||
}).otherwise(function(err) {
|
||||
if (err) {
|
||||
log.warn(log._("api.library.error-load-flow",{path:req.params[0],message:err.toString()}));
|
||||
if (err.code === 'forbidden') {
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"forbidden"},req);
|
||||
res.status(403).end();
|
||||
return;
|
||||
if (req.params[0].indexOf("_examples_/") === 0) {
|
||||
var m = /^_examples_\/([^\/]+)\/(.*)$/.exec(req.params[0]);
|
||||
if (m) {
|
||||
var module = m[1];
|
||||
var path = m[2]+".json";
|
||||
if (exampleRoots[module]) {
|
||||
var fullPath = fspath.join(exampleRoots[module],path);
|
||||
try {
|
||||
fs.statSync(fullPath);
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0]},req);
|
||||
return res.sendFile(fullPath,{
|
||||
headers:{
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
// IF we get here, we didn't find the file
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"not_found"},req);
|
||||
res.status(404).end();
|
||||
});
|
||||
return res.status(404).end();
|
||||
} else {
|
||||
storage.getFlow(req.params[0]).then(function(data) {
|
||||
// data is already a JSON string
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0]},req);
|
||||
res.set('Content-Type', 'application/json');
|
||||
res.send(data);
|
||||
}).otherwise(function(err) {
|
||||
if (err) {
|
||||
log.warn(log._("api.library.error-load-flow",{path:req.params[0],message:err.toString()}));
|
||||
if (err.code === 'forbidden') {
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"forbidden"},req);
|
||||
res.status(403).end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"not_found"},req);
|
||||
res.status(404).end();
|
||||
});
|
||||
}
|
||||
},
|
||||
post: function(req,res) {
|
||||
// if (req.params[0].indexOf("_examples_/") === 0) {
|
||||
// log.warn(log._("api.library.error-save-flow",{path:req.params[0],message:"forbidden"}));
|
||||
// log.audit({event: "library.set",type:"flow",path:req.params[0],error:"forbidden"},req);
|
||||
// return res.status(403).send({error:"unexpected_error", message:"forbidden"});
|
||||
// }
|
||||
var flow = JSON.stringify(req.body);
|
||||
storage.saveFlow(req.params[0],flow).then(function() {
|
||||
log.audit({event: "library.set",type:"flow",path:req.params[0]},req);
|
||||
|
||||
@@ -13,18 +13,52 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
var i18n = require("../i18n");
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var i18n;
|
||||
var supportedLangs = [];
|
||||
|
||||
var apiLocalDir = path.resolve(path.join(__dirname,"locales"));
|
||||
|
||||
var initSupportedLangs = function() {
|
||||
fs.readdir(apiLocalDir, function(err,files) {
|
||||
if(!err) {
|
||||
supportedLangs = files;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function determineLangFromHeaders(acceptedLanguages){
|
||||
var lang = i18n.defaultLang;
|
||||
acceptedLanguages = acceptedLanguages || [];
|
||||
for (var i=0;i<acceptedLanguages.length;i++){
|
||||
if (supportedLangs.indexOf(acceptedLanguages[i]) !== -1){
|
||||
lang = acceptedLanguages[i];
|
||||
break;
|
||||
// check the language without the country code
|
||||
} else if (supportedLangs.indexOf(acceptedLanguages[i].split("-")[0]) !== -1) {
|
||||
lang = acceptedLanguages[i].split("-")[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return lang;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
i18n = runtime.i18n;
|
||||
initSupportedLangs();
|
||||
},
|
||||
get: function(req,res) {
|
||||
var namespace = req.params[0];
|
||||
namespace = namespace.replace(/\.json$/,"");
|
||||
var lang = i18n.determineLangFromHeaders(req.acceptsLanguages() || []);
|
||||
var lang = determineLangFromHeaders(req.acceptsLanguages() || []);
|
||||
var prevLang = i18n.i.lng();
|
||||
i18n.i.setLng(lang, function(){
|
||||
var catalog = i18n.catalog(namespace,lang);
|
||||
res.json(catalog||{});
|
||||
});
|
||||
i18n.i.setLng(prevLang);
|
||||
}
|
||||
},
|
||||
determineLangFromHeaders: determineLangFromHeaders
|
||||
}
|
||||
|
||||
@@ -9,28 +9,33 @@
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"defaultName": "Sheet __number__",
|
||||
"renameSheet": "Rename sheet",
|
||||
"defaultName": "Flow __number__",
|
||||
"renameSheet": "Rename flow",
|
||||
"confirmDelete": "Confirm delete",
|
||||
"delete": "Are you sure you want to delete '__label__'?",
|
||||
"dropFlowHere": "Drop the flow here"
|
||||
},
|
||||
"menu": {
|
||||
"label": {
|
||||
"sidebar": {
|
||||
"sidebar": "Sidebar",
|
||||
"show": "Toggle Sidebar"
|
||||
"view": {
|
||||
"view": "View",
|
||||
"showGrid": "Show grid",
|
||||
"snapGrid": "Snap to grid"
|
||||
},
|
||||
"displayStatus": "Display Node Status",
|
||||
"displayConfig": "Configuration Nodes",
|
||||
"sidebar": {
|
||||
"show": "Show sidebar"
|
||||
},
|
||||
"displayStatus": "Show node status",
|
||||
"displayConfig": "Configuration nodes",
|
||||
"import": "Import",
|
||||
"export": "Export",
|
||||
"clipboard": "Clipboard",
|
||||
"library": "Library",
|
||||
"examples": "Examples",
|
||||
"subflows": "Subflows",
|
||||
"createSubflow": "Create Subflow",
|
||||
"selectionToSubflow": "Selection to Subflow",
|
||||
"flows": "Tabs",
|
||||
"flows": "Flows",
|
||||
"add": "Add",
|
||||
"rename": "Rename",
|
||||
"delete": "Delete",
|
||||
@@ -78,7 +83,9 @@
|
||||
"modifiedFlowsDesc": "Only deploys flows that contain changed nodes",
|
||||
"modifiedNodes": "Modified Nodes",
|
||||
"modifiedNodesDesc": "Only deploys nodes that have changed",
|
||||
"successfulDeploy": "Successfully Deployed",
|
||||
"successfulDeploy": "Successfully deployed",
|
||||
"unusedConfigNodes":"You have some unused configuration nodes.",
|
||||
"unusedConfigNodesLink":"Click here to see them",
|
||||
"errors": {
|
||||
"noResponse": "no response from server"
|
||||
},
|
||||
@@ -90,7 +97,6 @@
|
||||
"undeployedChanges": "You have undeployed changes.\n\nLeaving this page will lose these changes.",
|
||||
"improperlyConfigured": "The workspace contains some nodes that are not properly configured:",
|
||||
"unknown": "The workspace contains some unknown node types:",
|
||||
"unusedConfig": "The workspace contains some unused configuration nodes:",
|
||||
"confirm": "Are you sure you want to deploy?"
|
||||
}
|
||||
},
|
||||
@@ -197,11 +203,22 @@
|
||||
"config": {
|
||||
"name": "Configuration nodes",
|
||||
"label": "config",
|
||||
"local": "on this flow",
|
||||
"global": "on all flows",
|
||||
"global": "Global",
|
||||
"none": "none",
|
||||
"subflows": "subflows",
|
||||
"flows": "flows"
|
||||
"flows": "flows",
|
||||
"filterUnused":"unused",
|
||||
"filterAll":"all",
|
||||
"filtered": "__count__ hidden"
|
||||
}
|
||||
},
|
||||
"typedInput": {
|
||||
"type": {
|
||||
"str": "string",
|
||||
"num": "number",
|
||||
"re": "regular expression",
|
||||
"bool": "boolean",
|
||||
"json": "JSON"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,22 +13,28 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
var redNodes = require("../nodes");
|
||||
var comms = require("../comms");
|
||||
var log = require("../log");
|
||||
var i18n = require("../i18n");
|
||||
|
||||
var when = require("when");
|
||||
|
||||
var settings = require("../settings");
|
||||
var comms = require("./comms");
|
||||
var locales = require("./locales");
|
||||
var redNodes;
|
||||
var log;
|
||||
var i18n;
|
||||
var settings;
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
redNodes = runtime.nodes;
|
||||
log = runtime.log;
|
||||
i18n = runtime.i18n;
|
||||
settings = runtime.settings;
|
||||
},
|
||||
getAll: function(req,res) {
|
||||
if (req.get("accept") == "application/json") {
|
||||
log.audit({event: "nodes.list.get"},req);
|
||||
res.json(redNodes.getNodeList());
|
||||
} else {
|
||||
var lang = i18n.determineLangFromHeaders(req.acceptsLanguages());
|
||||
var lang = locales.determineLangFromHeaders(req.acceptsLanguages());
|
||||
log.audit({event: "nodes.configs.get"},req);
|
||||
res.send(redNodes.getNodeConfigs(lang));
|
||||
}
|
||||
@@ -121,7 +127,7 @@ module.exports = {
|
||||
res.status(404).end();
|
||||
}
|
||||
} else {
|
||||
var lang = i18n.determineLangFromHeaders(req.acceptsLanguages());
|
||||
var lang = locales.determineLangFromHeaders(req.acceptsLanguages());
|
||||
result = redNodes.getNodeConfig(id,lang);
|
||||
if (result) {
|
||||
log.audit({event: "nodes.config.get",id:id},req);
|
||||
@@ -157,8 +163,8 @@ module.exports = {
|
||||
res.status(400).json({error:"invalid_request", message:"Invalid request"});
|
||||
return;
|
||||
}
|
||||
var id = req.params.mod + "/" + req.params.set;
|
||||
try {
|
||||
var id = req.params.mod + "/" + req.params.set;
|
||||
var node = redNodes.getNodeInfo(id);
|
||||
var info;
|
||||
if (!node) {
|
||||
@@ -189,8 +195,8 @@ module.exports = {
|
||||
res.status(400).json({error:"invalid_request", message:"Invalid request"});
|
||||
return;
|
||||
}
|
||||
var mod = req.params.mod;
|
||||
try {
|
||||
var mod = req.params.mod;
|
||||
var module = redNodes.getModuleInfo(mod);
|
||||
if (!module) {
|
||||
log.audit({event: "nodes.module.set",module:mod,error:"not_found"},req);
|
||||
|
||||
168
red/api/theme.js
168
red/api/theme.js
@@ -34,6 +34,7 @@ var defaultContext = {
|
||||
}
|
||||
};
|
||||
|
||||
var theme = null;
|
||||
var themeContext = clone(defaultContext);
|
||||
var themeSettings = null;
|
||||
|
||||
@@ -53,102 +54,107 @@ function serveFile(app,baseUrl,file) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(settings) {
|
||||
init: function(runtime) {
|
||||
var settings = runtime.settings;
|
||||
themeContext = clone(defaultContext);
|
||||
if (runtime.version) {
|
||||
themeContext.version = runtime.version();
|
||||
}
|
||||
themeSettings = null;
|
||||
theme = settings.editorTheme;
|
||||
|
||||
},
|
||||
|
||||
app: function() {
|
||||
var i;
|
||||
var url;
|
||||
themeContext = clone(defaultContext);
|
||||
themeSettings = null;
|
||||
themeSettings = {};
|
||||
|
||||
if (settings.editorTheme) {
|
||||
var theme = settings.editorTheme;
|
||||
themeSettings = {};
|
||||
var themeApp = express();
|
||||
|
||||
var themeApp = express();
|
||||
|
||||
if (theme.page) {
|
||||
if (theme.page.css) {
|
||||
var styles = theme.page.css;
|
||||
if (!util.isArray(styles)) {
|
||||
styles = [styles];
|
||||
}
|
||||
themeContext.page.css = [];
|
||||
|
||||
for (i=0;i<styles.length;i++) {
|
||||
url = serveFile(themeApp,"/css/",styles[i]);
|
||||
if (url) {
|
||||
themeContext.page.css.push(url);
|
||||
}
|
||||
}
|
||||
if (theme.page) {
|
||||
if (theme.page.css) {
|
||||
var styles = theme.page.css;
|
||||
if (!util.isArray(styles)) {
|
||||
styles = [styles];
|
||||
}
|
||||
themeContext.page.css = [];
|
||||
|
||||
if (theme.page.favicon) {
|
||||
url = serveFile(themeApp,"/favicon/",theme.page.favicon)
|
||||
for (i=0;i<styles.length;i++) {
|
||||
url = serveFile(themeApp,"/css/",styles[i]);
|
||||
if (url) {
|
||||
themeContext.page.favicon = url;
|
||||
}
|
||||
}
|
||||
|
||||
themeContext.page.title = theme.page.title || themeContext.page.title;
|
||||
}
|
||||
|
||||
if (theme.header) {
|
||||
|
||||
themeContext.header.title = theme.header.title || themeContext.header.title;
|
||||
|
||||
if (theme.header.hasOwnProperty("url")) {
|
||||
themeContext.header.url = theme.header.url;
|
||||
}
|
||||
|
||||
if (theme.header.hasOwnProperty("image")) {
|
||||
if (theme.header.image) {
|
||||
url = serveFile(themeApp,"/header/",theme.header.image);
|
||||
if (url) {
|
||||
themeContext.header.image = url;
|
||||
}
|
||||
} else {
|
||||
themeContext.header.image = null;
|
||||
themeContext.page.css.push(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (theme.deployButton) {
|
||||
if (theme.deployButton.type == "simple") {
|
||||
themeSettings.deployButton = {
|
||||
type: "simple"
|
||||
}
|
||||
if (theme.deployButton.label) {
|
||||
themeSettings.deployButton.label = theme.deployButton.label;
|
||||
}
|
||||
if (theme.deployButton.icon) {
|
||||
url = serveFile(themeApp,"/deploy/",theme.deployButton.icon);
|
||||
if (url) {
|
||||
themeSettings.deployButton.icon = url;
|
||||
}
|
||||
}
|
||||
if (theme.page.favicon) {
|
||||
url = serveFile(themeApp,"/favicon/",theme.page.favicon)
|
||||
if (url) {
|
||||
themeContext.page.favicon = url;
|
||||
}
|
||||
}
|
||||
|
||||
if (theme.hasOwnProperty("userMenu")) {
|
||||
themeSettings.userMenu = theme.userMenu;
|
||||
}
|
||||
|
||||
if (theme.login) {
|
||||
if (theme.login.image) {
|
||||
url = serveFile(themeApp,"/login/",theme.login.image);
|
||||
if (url) {
|
||||
themeContext.login = {
|
||||
image: url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (theme.hasOwnProperty("menu")) {
|
||||
themeSettings.menu = theme.menu;
|
||||
}
|
||||
|
||||
return themeApp;
|
||||
themeContext.page.title = theme.page.title || themeContext.page.title;
|
||||
}
|
||||
|
||||
if (theme.header) {
|
||||
|
||||
themeContext.header.title = theme.header.title || themeContext.header.title;
|
||||
|
||||
if (theme.header.hasOwnProperty("url")) {
|
||||
themeContext.header.url = theme.header.url;
|
||||
}
|
||||
|
||||
if (theme.header.hasOwnProperty("image")) {
|
||||
if (theme.header.image) {
|
||||
url = serveFile(themeApp,"/header/",theme.header.image);
|
||||
if (url) {
|
||||
themeContext.header.image = url;
|
||||
}
|
||||
} else {
|
||||
themeContext.header.image = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (theme.deployButton) {
|
||||
if (theme.deployButton.type == "simple") {
|
||||
themeSettings.deployButton = {
|
||||
type: "simple"
|
||||
}
|
||||
if (theme.deployButton.label) {
|
||||
themeSettings.deployButton.label = theme.deployButton.label;
|
||||
}
|
||||
if (theme.deployButton.icon) {
|
||||
url = serveFile(themeApp,"/deploy/",theme.deployButton.icon);
|
||||
if (url) {
|
||||
themeSettings.deployButton.icon = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (theme.hasOwnProperty("userMenu")) {
|
||||
themeSettings.userMenu = theme.userMenu;
|
||||
}
|
||||
|
||||
if (theme.login) {
|
||||
if (theme.login.image) {
|
||||
url = serveFile(themeApp,"/login/",theme.login.image);
|
||||
if (url) {
|
||||
themeContext.login = {
|
||||
image: url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (theme.hasOwnProperty("menu")) {
|
||||
themeSettings.menu = theme.menu;
|
||||
}
|
||||
|
||||
return themeApp;
|
||||
},
|
||||
context: function() {
|
||||
return themeContext;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013, 2014 IBM Corp.
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,26 +21,26 @@ var theme = require("./theme");
|
||||
|
||||
var Mustache = require("mustache");
|
||||
|
||||
var events = require("../events");
|
||||
var settings;
|
||||
|
||||
var icon_paths = [path.resolve(__dirname + '/../../public/icons')];
|
||||
var iconCache = {};
|
||||
//TODO: create a default icon
|
||||
var defaultIcon = path.resolve(__dirname + '/../../public/icons/arrow-in.png');
|
||||
|
||||
events.on("node-icon-dir",function(dir) {
|
||||
icon_paths.push(path.resolve(dir));
|
||||
});
|
||||
|
||||
var templateDir = path.resolve(__dirname+"/../../editor/templates");
|
||||
var editorTemplate;
|
||||
|
||||
function nodeIconDir(dir) {
|
||||
icon_paths.push(path.resolve(dir));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(_settings) {
|
||||
settings = _settings;
|
||||
init: function(runtime) {
|
||||
editorTemplate = fs.readFileSync(path.join(templateDir,"index.mst"),"utf8");
|
||||
Mustache.parse(editorTemplate);
|
||||
// TODO: this allows init to be called multiple times without
|
||||
// registering multiple instances of the listener.
|
||||
// It isn't.... ideal.
|
||||
runtime.events.removeListener("node-icon-dir",nodeIconDir);
|
||||
runtime.events.on("node-icon-dir",nodeIconDir);
|
||||
},
|
||||
|
||||
ensureSlash: function(req,res,next) {
|
||||
@@ -59,10 +59,13 @@ module.exports = {
|
||||
} else {
|
||||
for (var p=0;p<icon_paths.length;p++) {
|
||||
var iconPath = path.join(icon_paths[p],req.params.icon);
|
||||
if (fs.existsSync(iconPath)) {
|
||||
try {
|
||||
fs.statSync(iconPath);
|
||||
res.sendFile(iconPath);
|
||||
iconCache[req.params.icon] = iconPath;
|
||||
return;
|
||||
} catch(err) {
|
||||
// iconPath doesn't exist
|
||||
}
|
||||
}
|
||||
res.sendFile(defaultIcon);
|
||||
|
||||
105
red/red.js
105
red/red.js
@@ -14,22 +14,19 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var server = require("./server");
|
||||
var nodes = require("./nodes");
|
||||
var library = require("./api/library");
|
||||
var comms = require("./comms");
|
||||
var log = require("./log");
|
||||
var util = require("./util");
|
||||
var i18n = require("./i18n");
|
||||
var fs = require("fs");
|
||||
var settings = require("./settings");
|
||||
var credentials = require("./nodes/credentials");
|
||||
var auth = require("./api/auth");
|
||||
var path = require('path');
|
||||
var events = require("events");
|
||||
|
||||
var runtime = require("./runtime");
|
||||
var api = require("./api");
|
||||
|
||||
process.env.NODE_RED_HOME = process.env.NODE_RED_HOME || path.resolve(__dirname+"/..");
|
||||
|
||||
var nodeApp = null;
|
||||
var adminApp = null;
|
||||
var server = null;
|
||||
var apiEnabled = false;
|
||||
|
||||
function checkBuild() {
|
||||
var editorFile = path.resolve(path.join(__dirname,"..","public","red","red.min.js"));
|
||||
try {
|
||||
@@ -41,42 +38,60 @@ function checkBuild() {
|
||||
}
|
||||
}
|
||||
|
||||
var RED = {
|
||||
module.exports = {
|
||||
init: function(httpServer,userSettings) {
|
||||
if (!userSettings) {
|
||||
userSettings = httpServer;
|
||||
httpServer = null;
|
||||
}
|
||||
|
||||
if (!userSettings.SKIP_BUILD_CHECK) {
|
||||
checkBuild();
|
||||
}
|
||||
userSettings.version = this.version();
|
||||
log.init(userSettings);
|
||||
settings.init(userSettings);
|
||||
server.init(httpServer,settings);
|
||||
return server.app;
|
||||
},
|
||||
start: server.start,
|
||||
stop: server.stop,
|
||||
nodes: nodes,
|
||||
library: { register: library.register },
|
||||
credentials: credentials,
|
||||
events: events,
|
||||
log: log,
|
||||
comms: comms,
|
||||
settings:settings,
|
||||
util: util,
|
||||
auth: {
|
||||
needsPermission: auth.needsPermission
|
||||
},
|
||||
version: function () {
|
||||
var p = require(path.join(process.env.NODE_RED_HOME,"package.json")).version;
|
||||
/* istanbul ignore else */
|
||||
if (fs.existsSync(path.join(process.env.NODE_RED_HOME,".git"))) {
|
||||
p += "-git";
|
||||
}
|
||||
return p;
|
||||
},
|
||||
get app() { console.log("Deprecated use of RED.app - use RED.httpAdmin instead"); return server.app },
|
||||
get httpAdmin() { return server.app },
|
||||
get httpNode() { return server.nodeApp },
|
||||
get server() { return server.server }
|
||||
};
|
||||
|
||||
module.exports = RED;
|
||||
if (!userSettings.coreNodesDir) {
|
||||
userSettings.coreNodesDir = path.resolve(path.join(__dirname,"..","nodes"));
|
||||
}
|
||||
|
||||
if (userSettings.httpAdminRoot !== false || userSettings.httpNodeRoot !== false) {
|
||||
runtime.init(userSettings,api);
|
||||
api.init(httpServer,runtime);
|
||||
apiEnabled = true;
|
||||
} else {
|
||||
runtime.init(userSettings);
|
||||
apiEnabled = false;
|
||||
}
|
||||
adminApp = runtime.adminApi.adminApp;
|
||||
nodeApp = runtime.adminApi.nodeApp;
|
||||
server = runtime.adminApi.server;
|
||||
return;
|
||||
},
|
||||
start: function() {
|
||||
return runtime.start().then(function() {
|
||||
if (apiEnabled) {
|
||||
return api.start();
|
||||
}
|
||||
});
|
||||
},
|
||||
stop: function() {
|
||||
return runtime.stop().then(function() {
|
||||
if (apiEnabled) {
|
||||
return api.stop();
|
||||
}
|
||||
})
|
||||
},
|
||||
nodes: runtime.nodes,
|
||||
log: runtime.log,
|
||||
settings:runtime.settings,
|
||||
util: runtime.util,
|
||||
version: runtime.version,
|
||||
|
||||
comms: api.comms,
|
||||
library: api.library,
|
||||
auth: api.auth,
|
||||
|
||||
get app() { console.log("Deprecated use of RED.app - use RED.httpAdmin instead"); return runtime.app },
|
||||
get httpAdmin() { return adminApp },
|
||||
get httpNode() { return nodeApp },
|
||||
get server() { return server }
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user