Compare commits

..

81 Commits

Author SHA1 Message Date
Nick O'Leary
03558b012c Bump version and dependencies 2016-01-18 11:09:52 +00:00
Nick O'Leary
3288efdad6 Remove unimplemented flow.enable/disable functions 2016-01-18 10:53:50 +00:00
Dave Conway-Jones
3902a343f3 Add ports in use warning to udp node
to close #786
Thanks @hugobox
2016-01-17 10:34:40 +00:00
Dave Conway-Jones
882b7d0391 change settings.js example to octalbonescript 2016-01-16 13:58:24 +00:00
Nick O'Leary
81f082825d Add 'previous value' option to Switch node 2016-01-15 11:35:59 +00:00
Nick O'Leary
392fd6fed3 Allow existing nodes to splice into links on drag 2016-01-14 15:59:45 +00:00
Nick O'Leary
51afed4041 Ensure config list refreshes properly on tab delete 2016-01-14 15:22:00 +00:00
Nick O'Leary
17e3b71d9c Allow update of global flow 2016-01-14 14:57:13 +00:00
Nick O'Leary
6e75089f3a CORS not properly configured on multiple http routes
Fixes #783
2016-01-13 12:54:34 +00:00
Nick O'Leary
6dc640b129 Add hidden count when config node filtered 2016-01-13 10:30:24 +00:00
Nick O'Leary
27cbaac343 Restore shift-drag to snap/unsnap to grid 2016-01-13 09:16:24 +00:00
Nick O'Leary
fa4006619e Make debug/config sidebar headers consistent 2016-01-12 23:55:18 +00:00
Nick O'Leary
cb8fe8462a Moving nodes with keyboard should flag workspace dirty 2016-01-12 23:08:13 +00:00
Nick O'Leary
abd51a5511 Notifications flagged as fixed should not be click-closable 2016-01-12 23:06:18 +00:00
Nick O'Leary
a0cc1e6b0c Add config node filter 2016-01-12 23:03:33 +00:00
Nick O'Leary
50399c6bfa Rework config sidebar and deploy warning 2016-01-12 17:54:53 +00:00
Nick O'Leary
de48c1be44 Wrap http request object to match http response object 2016-01-11 22:35:31 +00:00
Nick O'Leary
0786ec4b66 Move typedInput icons and update boolean 2016-01-11 21:24:35 +00:00
Nick O'Leary
db319e0ebc Ensure global context is seeded properly 2016-01-11 11:28:01 +00:00
Nick O'Leary
4fc568856a Clear link_splice style on drag end 2016-01-11 11:00:54 +00:00
Nick O'Leary
4c6771669b Fix palette node link splicing on Firefox 2016-01-10 22:25:20 +00:00
Nick O'Leary
9bca2a91c9 Tidy up view menu 2016-01-10 21:25:05 +00:00
Nick O'Leary
66eaaf5a48 Add 'view' menu and reorganise a few things 2016-01-09 20:39:03 +00:00
Nick O'Leary
9837f0e2e1 Highlight node port when dragging wires and undash the wires 2016-01-09 13:47:05 +00:00
Nick O'Leary
6b8ffb4c68 Fix lint issues in view 2016-01-09 00:31:05 +00:00
Nick O'Leary
f35dd34da9 Allow shift-click to detach existing wires 2016-01-09 00:29:04 +00:00
Nick O'Leary
ed19e4fa08 Splice nodes dragged from palette into links 2016-01-08 22:34:10 +00:00
Dave Conway-Jones
661e1a4f90 try to trim imported/dragged flows to [ ] 2016-01-08 19:54:16 +00:00
Nick O'Leary
5826de76ca Make dragging nodes from the palette line up better 2016-01-08 14:42:05 +00:00
Nick O'Leary
05888740e5 Update jquery version to 0.11.3 2016-01-08 14:41:37 +00:00
Nick O'Leary
41f3b0c333 Fix variable leak in theme.js 2016-01-08 13:41:33 +00:00
Nick O'Leary
70f3e72a20 Move version number as title of NR logo 2016-01-08 13:36:49 +00:00
Nick O'Leary
e873afd40b Moving nodes mark workspace as dirty 2016-01-08 11:08:48 +00:00
Nick O'Leary
2777c2937a Make version number slightly darker 2016-01-07 22:29:20 +00:00
Nick O'Leary
798903e4cc Move layout menu down one position 2016-01-07 22:28:03 +00:00
Nick O'Leary
58622ba18f Attach dialog close handlers to dialog parents 2016-01-07 20:08:31 +00:00
Nick O'Leary
c368dcd5b7 Ok/Cancel edit dialogs with Ctrl-Enter/Escape 2016-01-07 17:10:59 +00:00
Nick O'Leary
0b4c652ce7 Add version number of sidebar footer 2016-01-07 16:42:10 +00:00
Nick O'Leary
dbaacc411a Handle OSX Meta key when selecting nodes 2016-01-07 15:09:14 +00:00
Nick O'Leary
1850185d1e Add grid-alignment options 2016-01-07 14:39:01 +00:00
Nick O'Leary
2e9d445d36 Add oneditresize function definition 2016-01-06 17:01:14 +00:00
Nick O'Leary
aed89d82fb Fix template test 2016-01-06 17:01:14 +00:00
Nick O'Leary
231adac6d8 Rename typedInput.options 2016-01-06 17:01:14 +00:00
Nick O'Leary
587c4e5915 Update template node to use typedInput 2016-01-06 17:01:14 +00:00
Nick O'Leary
55f1cbf18f Ensure inject payload exists 2016-01-06 17:01:13 +00:00
Nick O'Leary
38168a545b Update Inject node to use typedInput 2016-01-06 17:01:13 +00:00
Nick O'Leary
43c6df49d7 Update typedInput nls 2016-01-06 17:01:13 +00:00
Nick O'Leary
f1c59faf72 Rename propertySelect to typedInput and add boolean opt 2016-01-06 17:01:13 +00:00
Nick O'Leary
5f7019325c Update switch/change help text to reflect updates 2016-01-06 17:01:13 +00:00
Nick O'Leary
fe4dae8518 Add propertySelect to switch node 2016-01-06 17:01:13 +00:00
Nick O'Leary
1f848b205b Add propertySelect support to Change node 2016-01-06 17:01:13 +00:00
Nick O'Leary
742c470d81 Add context/flow/global support to Function node 2016-01-06 17:01:13 +00:00
Nick O'Leary
5ead3342cc Add node context/flow/global 2016-01-06 17:01:13 +00:00
Nick O'Leary
b95dc2ecce Add propertySelect jquery widget 2016-01-06 17:01:13 +00:00
Nick O'Leary
4d0950215f Don't allow tabs or subflows to be added with new flow 2016-01-06 17:01:13 +00:00
Nick O'Leary
da0ce9fe0d Simplify flow api implementation and add logging messages 2016-01-06 17:01:13 +00:00
Nick O'Leary
ca62e720b5 Add missing spec file 2016-01-06 17:01:13 +00:00
Nick O'Leary
c4b1795396 Add add/update/delete flow apis 2016-01-06 17:01:13 +00:00
Nick O'Leary
fd2e47ed73 WIP: add flow api 2016-01-06 17:01:12 +00:00
Nick O'Leary
d5f2255a68 Handle null coreNodesPath 2016-01-06 17:01:12 +00:00
Nick O'Leary
05b58e9263 Allow core nodes dir to be provided to runtime via settings 2016-01-06 17:01:12 +00:00
Nick O'Leary
4a91c27e4b Allow server to be option on red.init 2016-01-06 17:01:12 +00:00
Nick O'Leary
3a03d46d8d Fix lint error in registry.js 2016-01-06 17:01:12 +00:00
Nick O'Leary
f03aff7006 Tidy up API passed to node modules 2016-01-06 17:01:12 +00:00
Nick O'Leary
043b8a3105 Register node message catalog directly, not via event 2016-01-06 17:01:12 +00:00
Nick O'Leary
1dd9984521 Pickup default language from i18n module 2016-01-06 17:01:12 +00:00
Nick O'Leary
d2be7f8c8f Move locale files under api/runtime components 2016-01-06 17:01:12 +00:00
Nick O'Leary
88dc202db2 Fix node test helper for api/runtime changes 2016-01-06 17:01:12 +00:00
Nick O'Leary
083d54b008 Add unit test for flow reload api 2016-01-06 17:01:11 +00:00
Nick O'Leary
87d77efa57 Add flow reload admin api 2016-01-06 17:01:11 +00:00
Nick O'Leary
35c4a41d7b Node id generation should only be done in runtime/util 2016-01-06 17:01:11 +00:00
Nick O'Leary
1ca3ca07d5 api/nodes accessing comms module incorrectly 2016-01-06 17:01:11 +00:00
Nick O'Leary
d673846e3d WIP: runtime api for node modules 2016-01-06 17:01:11 +00:00
Nick O'Leary
f62b7afede Remove all uses of fs.exists as it is deprecated
The tests still use it in places - particular localfilesystem tests,
but those tests need to be redone with sinon stubbing in place and
not rely on real fs operations.
2016-01-06 17:01:11 +00:00
Nick O'Leary
e65770a53a Add missing test resources
They were ignored as they have node_modules in the path...
2016-01-06 17:01:11 +00:00
Nick O'Leary
a92a741932 Fix incorrect async test completion 2016-01-06 17:01:11 +00:00
Nick O'Leary
45f67191ba Improve node registry test coverage 2016-01-06 17:01:11 +00:00
Nick O'Leary
93f5da325b Fix node test helper for runtime/api changes 2016-01-06 17:01:11 +00:00
Nick O'Leary
8fb955e182 Move comms from runtime to api component 2016-01-06 17:01:11 +00:00
Nick O'Leary
9f5e6a4b37 Update tests for runtime/api separation 2016-01-06 17:01:11 +00:00
Nick O'Leary
f43738446e WIP: separate runtime and api components 2016-01-06 17:01:11 +00:00
173 changed files with 5645 additions and 3268 deletions

View File

@@ -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 %>')
}]

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

View File

@@ -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.
@@ -80,6 +80,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 +174,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,6 +282,7 @@ RED.history = (function() {
RED.view.redraw(true);
RED.palette.refresh();
RED.workspaces.refresh();
RED.sidebar.config.refresh();
}
}
}

View File

@@ -156,11 +156,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 +173,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}
]
});

View File

@@ -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
}
})

View File

@@ -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
}
})();

View File

@@ -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;

View File

@@ -348,6 +348,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 +368,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 +395,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();
}
});
}
@@ -1008,6 +1024,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();
}
});
}
@@ -1129,6 +1151,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();
}
});
}

View File

@@ -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);

View File

@@ -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.
@@ -210,12 +210,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 +363,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();

View File

@@ -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 = {};

View File

@@ -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 selected" id="workspace-config-node-filter-all" href="#"><span data-i18n="sidebar.config.filterAll"></span></a>'+
'<a class="sidebar-header-button" 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
View 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);

View File

@@ -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);
@@ -347,7 +359,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 +379,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 - 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 +470,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 +518,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 +595,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 +627,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 +643,57 @@ RED.view = (function() {
}
}
}
if (mouse_mode == RED.state.MOVING_ACTIVE && 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 +701,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 +754,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 +764,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
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 +906,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 +1059,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 +1082,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 +1150,9 @@ RED.view = (function() {
RED.history.push(historyEvent);
updateActiveNodes();
RED.nodes.dirty(true);
} else {
}
resetMouseVars();
hideDragLines();
selected_link = null;
redraw();
}
@@ -994,10 +1198,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 +1216,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;
@@ -1123,7 +1327,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 +1370,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");
@@ -1205,7 +1409,7 @@ RED.view = (function() {
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) );
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) {
@@ -1388,8 +1592,10 @@ RED.view = (function() {
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) );
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 +1629,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 +1644,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();
@@ -1618,7 +1824,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") });
@@ -1844,6 +2050,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;
}
};
})();

View File

@@ -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" );
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 {

View File

@@ -57,7 +57,17 @@
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 component-footer {
border-top: 1px solid $primary-border-color;
background: #f3f3f3;

View File

@@ -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;

View File

@@ -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;

View File

@@ -37,7 +37,6 @@
right: 0;
bottom: 25px;
left: 0px;
padding-top: 3px;
overflow-y: auto;
}
@@ -63,6 +62,27 @@
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:not(:first-child) {
border-left: none;
}

View File

@@ -41,6 +41,8 @@
@import "popover";
@import "flow";
@import "typedInput";
@import "dragdrop";
@import "keyboard";

View File

@@ -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
View 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;
}
}
}

View File

@@ -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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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">
@@ -196,23 +188,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 +393,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();

View File

@@ -53,10 +53,12 @@ module.exports = function(RED) {
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") {
} else if (this.payloadType == null) {
msg.payload = this.payload;
} else {
} else if (this.payloadType == 'none') {
msg.payload = "";
} else {
msg.payload = RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg)||"";
}
this.send(msg);
msg = null;

View File

@@ -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;">
@@ -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>

View File

@@ -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;">
@@ -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>

View File

@@ -127,17 +127,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 +214,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 +250,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;

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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];

View File

@@ -19,28 +19,32 @@
<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">
<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="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="float:right;margin-bottom: 3px; font-size: 0.8em;">
<span data-i18n="template.label.format"></span>:
<select id="node-input-format" style=" height: 20px; font-size: 1em !important; width:110px;">
<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;">
</div>
</script>
<script type="text/x-red" data-help-name="template">
<p>Creates a new message based on the provided template.</p>
<p>Sets a property 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>For example, when a template of:
<pre>Hello {{name}}. Today is {{date}}</pre>
@@ -50,8 +54,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,6 +67,7 @@
defaults: {
name: {value:""},
field: {value:"payload"},
fieldType: {value:"msg"},
format: {value:"handlebars"},
template: {value:"This is the payload: {{payload}} !"},
},
@@ -72,30 +79,15 @@
},
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';
}
$("#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 +100,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 +112,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>

View File

@@ -23,33 +23,20 @@ module.exports = function(RED) {
this.name = n.name;
this.field = n.field || "payload";
this.template = n.template;
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 = mustache.render(node.template,msg);
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);
}

View File

@@ -58,30 +58,6 @@
$( "#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 +68,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>

View File

@@ -22,17 +22,21 @@ module.exports = function(RED) {
var gpioCommand = __dirname+'/nrgpio.py';
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");
}

View File

@@ -98,7 +98,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 +154,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) {
@@ -177,22 +184,14 @@ module.exports = function(RED) {
var msgid = RED.util.generateId();
res._msgid = msgid;
if (node.method.match(/(^post$|^delete$|^put$|^options$)/)) {
node.send({_msgid:msgid,req:req,res:createResponseWrapper(node,res),payload:req.body});
node.send({_msgid:msgid,req:createRequestWrapper(node,req),res:createResponseWrapper(node,res),payload:req.body});
} else if (node.method == "get") {
node.send({_msgid:msgid,req:req,res:createResponseWrapper(node,res),payload:req.query});
node.send({_msgid:msgid,req:createRequestWrapper(node,req),res:createResponseWrapper(node,res),payload:req.query});
} else {
node.send({_msgid:msgid,req:req,res:createResponseWrapper(node,res)});
node.send({_msgid:msgid,req:createRequestWrapper(node,req),res:createResponseWrapper(node,res)});
}
};
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) {

View File

@@ -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.
@@ -52,6 +52,7 @@
<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">
@@ -98,6 +99,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>

View File

@@ -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);

View File

@@ -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,8 @@
"template": {
"label": {
"template": "Template",
"property": "Property"
"property": "Property",
"format": "Syntax Highlight"
},
"templatevalue": "This is the payload: {{payload}} !"
},
@@ -398,7 +398,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 <b>msg.ip</b> and <b>msg.port</b>.",
"port": "Ports already in use: "
},
"status": {
"listener-at": "udp listener at __host__:__port__",
@@ -417,7 +418,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 +461,8 @@
"replace": "Replace with"
},
"errors": {
"invalid-from": "Invalid 'from' property: __error__"
"invalid-from": "Invalid 'from' property: __error__",
"invalid-json": "Invalid 'to' JSON property"
}
},
"range": {

View File

@@ -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,7 +39,7 @@
</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>
@@ -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(' &#8594; <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>

View File

@@ -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; }

View File

@@ -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 &amp; 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,30 @@
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 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']});
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','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 +166,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 +185,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 +209,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 +220,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>

View File

@@ -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,15 +37,35 @@ 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];
// 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") {
if (rule.re === false) {
if (rule.fromt !== 're') {
rule.from = rule.from.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
try {
@@ -55,57 +75,68 @@ module.exports = function(RED) {
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;
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 (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') {
var current = RED.util.getMessageProperty(msg,property);
if (typeof current === 'string') {
current = current.replace(rule.from,value);
RED.util.setMessageProperty(msg,property,current);
}
}
} 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') {
var current = target.get(msg,property);
if (typeof current === 'string') {
current = current.replace(rule.from,value);
target.set(property,current);
}
}
}
if (++depth === propertyParts.length) {
if (rule.t === "change") {
if (typeof obj[i] === "string") {
obj[i] = obj[i].replace(rule.from, rule.to);
}
} 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 {
// 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];
}
}, msg);
} catch (err) {}
}
} catch(err) {console.log(err.stack)}
return msg;
}
if (valid) {
var node = this;
this.on('input', function(msg) {

View File

@@ -1,6 +1,6 @@
{
"name" : "node-red",
"version" : "0.12.5",
"version" : "0.13.0",
"description" : "A visual tool for wiring the Internet of Things",
"homepage" : "http://nodered.org",
"license" : "Apache-2.0",
@@ -22,7 +22,7 @@
{"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",
@@ -49,11 +49,11 @@
"passport-oauth2-client-password":"0.1.2",
"raw-body":"2.1.5",
"semver": "5.1.0",
"sentiment":"1.0.4",
"sentiment":"1.0.5",
"uglify-js":"2.6.1",
"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.*",
@@ -72,7 +72,7 @@
"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-jshint": "0.12.0",
"grunt-contrib-uglify": "0.11.0",
"grunt-contrib-watch":"0.6.1",
"grunt-jsonlint":"1.0.7",

17
red.js
View File

@@ -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();
@@ -205,7 +204,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 +242,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 +256,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 {

View File

@@ -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);
}
}

View File

@@ -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,

View File

@@ -15,7 +15,7 @@
**/
var ws = require("ws");
var log = require("./log");
var log;
var server;
var settings;
@@ -29,17 +29,23 @@ 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;
@@ -151,15 +157,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) {

View File

@@ -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

89
red/api/flow.js Normal file
View File

@@ -0,0 +1,89 @@
/**
* 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) {
console.log(err.stack);
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 {
console.log(err.stack);
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()});
}
}
}
}

View File

@@ -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});
});
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2014 IBM Corp.
* 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.
@@ -19,22 +19,27 @@ 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 errorHandler = function(err,req,res,next) {
if (err.message === "request entity too large") {
@@ -46,71 +51,116 @@ var errorHandler = function(err,req,res,next) {
res.status(400).json({error:"unexpected_error", message:err.toString()});
};
function init(adminApp,storage) {
function init(_server,runtime) {
server = _server;
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);
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));
// Editor
if (!settings.disableEditor) {
ui.init(runtime);
var editorApp = express();
editorApp.get("/",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);
}
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);
}
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 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; }
};

View File

@@ -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);
}
}

View File

@@ -15,9 +15,8 @@
**/
var redApp = null;
var storage = require("../storage");
var log = require("../log");
var storage;
var log;
var needsPermission = require("./auth").needsPermission;
function createLibrary(type) {
@@ -70,8 +69,10 @@ function createLibrary(type) {
}
}
module.exports = {
init: function(app) {
init: function(app,runtime) {
redApp = app;
log = runtime.log;
storage = runtime.storage;
},
register: createLibrary,
@@ -90,7 +91,7 @@ module.exports = {
}).otherwise(function(err) {
if (err) {
log.warn(log._("api.library.error-load-flow",{path:req.params[0],message:err.toString()}));
if (err.code === 'forbidden') {
if (err.code === 'forbidden') {
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"forbidden"},req);
res.status(403).end();
return;

View File

@@ -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
}

View File

@@ -9,20 +9,24 @@
}
},
"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",
@@ -30,7 +34,7 @@
"subflows": "Subflows",
"createSubflow": "Create Subflow",
"selectionToSubflow": "Selection to Subflow",
"flows": "Tabs",
"flows": "Flows",
"add": "Add",
"rename": "Rename",
"delete": "Delete",
@@ -78,7 +82,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 +96,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 +202,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"
}
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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 }
};

View File

@@ -20,19 +20,9 @@ var path = require("path");
var fs = require("fs");
var defaultLang = "en-US";
var supportedLangs = [];
var resourceMap = {
"runtime": {
basedir: path.resolve(__dirname+"/../locales"),
file:"runtime.json"
},
"editor": {
basedir: path.resolve(__dirname+"/../locales"),
file: "editor.json"
}
}
var resourceCache = {}
var resourceMap = {};
var resourceCache = {};
function registerMessageCatalog(namespace,dir,file) {
return when.promise(function(resolve,reject) {
@@ -43,19 +33,6 @@ function registerMessageCatalog(namespace,dir,file) {
});
}
var initSupportedLangs = function() {
return when.promise(function(resolve,reject) {
fs.readdir(resourceMap.editor.basedir, function(err,files) {
if(err) {
reject(err);
} else {
supportedLangs = files;
resolve();
}
});
});
}
function mergeCatalog(fallback,catalog) {
for (var k in fallback) {
if (fallback.hasOwnProperty(k)) {
@@ -102,21 +79,17 @@ function init() {
i18n.backend(MessageFileLoader);
i18n.init({
ns: {
namespaces: ["runtime","editor"],
namespaces: [],
defaultNs: "runtime"
},
fallbackLng: ['en-US']
fallbackLng: [defaultLang]
},function() {
initSupportedLangs().then(function() {
resolve();
});
resolve();
});
});
}
function getCatalog(namespace,lang) {
//console.log("+",namespace,lang);
//console.log(resourceCache[namespace][lang]);
var result = null;
if (resourceCache.hasOwnProperty(namespace)) {
result = resourceCache[namespace][lang];
@@ -130,32 +103,15 @@ function getCatalog(namespace,lang) {
}
}
}
//console.log(result);
return result;
}
function determineLangFromHeaders(acceptedLanguages){
var lang = "en-US";
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;
}
var obj = module.exports = {
init: init,
registerMessageCatalog: registerMessageCatalog,
catalog: getCatalog,
i: i18n,
determineLangFromHeaders: determineLangFromHeaders
defaultLang:defaultLang
}
obj['_'] = function() {

View File

@@ -14,41 +14,75 @@
* limitations under the License.
**/
var express = require('express');
var when = require('when');
var redNodes = require("./nodes");
var comms = require("./comms");
var storage = require("./storage");
var log = require("./log");
var i18n = require("./i18n");
var app = null;
var nodeApp = null;
var server = null;
var settings = null;
var events = require("./events");
var settings = require("./settings");
var path = require('path');
var fs = require("fs");
var runtimeMetricInterval = null;
var stubbedExpressApp = {
get: function() {},
post: function() {},
put: function() {},
delete: function(){}
}
var adminApi = {
library: {
register: function(){}
},
auth: {
needsPermission: function(){}
},
comms: {
publish: function(){}
},
adminApp: stubbedExpressApp,
nodeApp: stubbedExpressApp,
server: {}
}
function init(_server,_settings) {
server = _server;
settings = _settings;
function init(userSettings,_adminApi) {
userSettings.version = getVersion();
log.init(userSettings);
settings.init(userSettings);
if (_adminApi) {
adminApi = _adminApi;
}
redNodes.init(runtime);
comms.init(_server,_settings);
}
nodeApp = express();
app = express();
var version;
function getVersion() {
if (!version) {
version = require(path.join(__dirname,"..","..","package.json")).version;
/* istanbul ignore else */
try {
fs.statSync(path.join(__dirname,"..","..",".git"));
version += "-git";
} catch(err) {
// No git directory
}
}
return version;
}
function start() {
return i18n.init()
.then(function() {
return i18n.registerMessageCatalog("runtime",path.resolve(path.join(__dirname,"locales")),"runtime.json")
})
.then(function() { return storage.init(settings)})
.then(function() { return settings.load(storage)})
.then(function() {
if (settings.httpAdminRoot !== false) {
require("./api").init(app,storage);
}
if (log.metric()) {
runtimeMetricInterval = setInterval(function() {
@@ -61,7 +95,6 @@ function start() {
}
log.info(log._("runtime.version",{component:"Node.js ",version:process.version}));
log.info(log._("server.loading"));
redNodes.init(settings,storage);
return redNodes.load().then(function() {
var i;
@@ -103,9 +136,10 @@ function start() {
redNodes.cleanModuleList();
}
}
log.info(log._("runtime.paths.settings",{path:settings.settingsFile}));
if (settings.settingsFile) {
log.info(log._("runtime.paths.settings",{path:settings.settingsFile}));
}
redNodes.loadFlows().then(redNodes.startFlows);
comms.start();
}).otherwise(function(err) {
console.log(err);
});
@@ -137,16 +171,22 @@ function stop() {
clearInterval(runtimeMetricInterval);
runtimeMetricInterval = null;
}
redNodes.stopFlows();
comms.stop();
return redNodes.stopFlows();
}
var serverAPI = module.exports = {
var runtime = module.exports = {
init: init,
start: start,
stop: stop,
get app() { return app },
get nodeApp() { return nodeApp },
get server() { return server }
version: getVersion,
log: log,
i18n: i18n,
settings: settings,
storage: storage,
events: events,
nodes: redNodes,
util: require("./util"),
get adminApi() { return adminApi }
}

View File

@@ -41,7 +41,8 @@
"api": {
"flows": {
"error-save": "Error saving flows: __message__"
"error-save": "Error saving flows: __message__",
"error-reload": "Error reloading flows: __message__"
},
"library": {
"error-load-entry": "Error loading library entry '__path__': __message__",
@@ -88,6 +89,9 @@
"stopped-modified-flows": "Stopped modified flows",
"stopped-flows": "Stopped flows",
"stopped": "Stopped",
"added-flow": "Adding flow: __label__",
"updated-flow": "Updated flow: __label__",
"removed-flow": "Removed flow: __label__",
"missing-types": "Waiting for missing types to be registered:",
"missing-type-provided": " - __type__ (provided by npm module __module__)",
"missing-type-install-1": "To install any of these missing modules, run:",

View File

@@ -50,9 +50,9 @@ var LogHandler = function(settings) {
this.logLevel = settings ? levels[settings.level]||levels.info : levels.info;
this.metricsOn = settings ? settings.metrics||false : false;
this.auditOn = settings ? settings.audit||false : false;
metricsEnabled = metricsEnabled || this.metricsOn;
this.handler = (settings && settings.handler) ? settings.handler(settings) : consoleLogger;
this.on("log",function(msg) {
if (this.shouldReportMessage(msg.level)) {
@@ -134,7 +134,7 @@ var log = module.exports = {
metric: function() {
return metricsEnabled;
},
audit: function(msg,req) {
msg.level = log.AUDIT;
if (req) {

View File

@@ -20,9 +20,8 @@ var when = require("when");
var redUtil = require("../util");
var Log = require("../log");
var context = require("./context");
var flows = require("./flows");
var comms = require("../comms");
function Node(n) {
this.id = n.id;
@@ -64,6 +63,12 @@ Node.prototype.updateWires = function(wires) {
}
}
Node.prototype.context = function() {
if (!this._context) {
this._context = context.get(this._alias||this.id,this.z);
}
return this._context;
}
Node.prototype._on = Node.prototype.on;
@@ -250,7 +255,6 @@ Node.prototype.metric = function(eventname, msg, metricValue) {
* status: { fill:"red|green", shape:"dot|ring", text:"blah" }
*/
Node.prototype.status = function(status) {
comms.publish("status/" + this.id, status, true);
flows.handleStatus(this,status);
};
module.exports = Node;

View File

@@ -0,0 +1,80 @@
/**
* 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.
**/
var clone = require("clone");
var when = require("when");
var util = require("../util");
function createContext(id,seed) {
var data = seed || {};
var obj = seed || {};
obj.get = function get(key) {
return util.getMessageProperty(data,key);
};
obj.set = function set(key, value) {
util.setMessageProperty(data,key,value);
}
return obj;
}
var contexts = {};
var globalContext = null;
function getContext(localId,flowId) {
var contextId = localId;
if (flowId) {
contextId = localId+":"+flowId;
}
if (contexts[contextId]) {
return contexts[contextId];
}
var newContext = createContext(contextId);
if (flowId) {
newContext.flow = getContext(flowId);
if (globalContext) {
newContext.global = globalContext;
}
}
contexts[contextId] = newContext;
return newContext;
}
function deleteContext(id,flowId) {
var contextId = id;
if (flowId) {
contextId = id+":"+flowId;
}
delete contexts[contextId];
}
function clean(flowConfig) {
var activeIds = {};
var contextId;
var node;
for (var id in contexts) {
if (contexts.hasOwnProperty(id)) {
var idParts = id.split(":");
if (!flowConfig.allNodes[idParts[0]]) {
delete contexts[id];
}
}
}
}
module.exports = {
init: function(settings) {
globalContext = createContext("global",settings.functionGlobalContext || {});
},
get: getContext,
clean:clean
};

View File

@@ -18,8 +18,6 @@ var when = require("when");
var log = require("../log");
var needsPermission = require("../api/auth").needsPermission;
var credentialCache = {};
var storage = null;
var credentialsDef = {};
@@ -123,6 +121,7 @@ module.exports = {
var nodeType = node.type;
var newCreds = node.credentials;
if (newCreds) {
delete node.credentials;
var savedCredentials = credentialCache[nodeID] || {};
var dashedType = nodeType.replace(/\s+/g, '-');
var definition = credentialsDef[dashedType];
@@ -147,7 +146,6 @@ module.exports = {
}
}
credentialCache[nodeID] = savedCredentials;
delete node.credentials;
}
},

View File

@@ -32,6 +32,7 @@ function Flow(global,flow) {
this.start = function(diff) {
var node;
var newNode;
var id;
catchNodeMap = {};
statusNodeMap = {};
@@ -39,11 +40,14 @@ function Flow(global,flow) {
if (flow.configs.hasOwnProperty(id)) {
node = flow.configs[id];
if (!activeNodes[id]) {
activeNodes[id] = createNode(node.type,node);
newNode = createNode(node.type,node);
if (newNode) {
activeNodes[id] = newNode;
}
}
}
}
if (diff) {
if (diff && diff.rewired) {
for (var j=0;j<diff.rewired.length;j++) {
var rewireNode = activeNodes[diff.rewired[j]];
if (rewireNode) {
@@ -57,7 +61,10 @@ function Flow(global,flow) {
node = flow.nodes[id];
if (!node.subflow) {
if (!activeNodes[id]) {
activeNodes[id] = createNode(node.type,node);
newNode = createNode(node.type,node);
if (newNode) {
activeNodes[id] = newNode;
}
}
} else {
if (!subflowInstanceNodes[id]) {
@@ -65,7 +72,9 @@ function Flow(global,flow) {
var nodes = createSubflow(flow.subflows[node.subflow]||global.subflows[node.subflow],node,flow.subflows,global.subflows,activeNodes);
subflowInstanceNodes[id] = nodes.map(function(n) { return n.id});
for (var i=0;i<nodes.length;i++) {
activeNodes[nodes[i].id] = nodes[i];
if (nodes[i]) {
activeNodes[nodes[i].id] = nodes[i];
}
}
} catch(err) {
console.log(err.stack)
@@ -227,11 +236,6 @@ function Flow(global,flow) {
}
function getID() {
return (1+Math.random()*4294967295).toString(16);
}
var EnvVarPropertyRE = /^\$\((\S+)\)$/;
function mapEnvVarProperties(obj,prop) {
@@ -258,7 +262,6 @@ function mapEnvVarProperties(obj,prop) {
}
function createNode(type,config) {
// console.log("CREATE",type,config.id);
var nn = null;
var nt = typeRegistry.get(type);
if (nt) {
@@ -297,7 +300,7 @@ function createSubflow(sf,sfn,subflows,globalSubflows,activeNodes) {
var createNodeInSubflow = function(def) {
node = clone(def);
var nid = getID();
var nid = redUtil.generateId();
node_map[node.id] = node;
node._alias = node.id;
node.id = nid;
@@ -443,8 +446,10 @@ function createSubflow(sf,sfn,subflows,globalSubflows,activeNodes) {
var m = /^subflow:(.+)$/.exec(type);
if (!m) {
var newNode = createNode(type,node);
activeNodes[node.id] = newNode;
nodes.push(newNode);
if (newNode) {
activeNodes[node.id] = newNode;
nodes.push(newNode);
}
} else {
var subflowId = m[1];
nodes = nodes.concat(createSubflow(subflows[subflowId]||globalSubflows[subflowId],node,subflows,globalSubflows,activeNodes));

View File

@@ -20,6 +20,7 @@ var when = require("when");
var Flow = require('./Flow');
var typeRegistry = require("../registry");
var context = require("../context")
var credentials = require("../credentials");
var flowUtil = require("./util");
@@ -76,7 +77,7 @@ function load() {
});
}
function setConfig(_config,type) {
function setConfig(_config,type,muteLog) {
var config = clone(_config);
type = type||"full";
@@ -115,8 +116,9 @@ function setConfig(_config,type) {
activeFlowConfig = newFlowConfig;
return credentials.clean(activeConfig).then(function() {
if (started) {
return stop(type,diff).then(function() {
start(type,diff);
return stop(type,diff,muteLog).then(function() {
context.clean(activeFlowConfig);
start(type,diff,muteLog);
}).otherwise(function(err) {
})
}
@@ -182,6 +184,10 @@ function delegateStatus(node,statusMessage) {
}
}
function handleStatus(node,statusMessage) {
events.emit("node-status",{
id: node.id,
status:statusMessage
});
if (node.z) {
delegateStatus(node,statusMessage);
} else {
@@ -195,7 +201,8 @@ function handleStatus(node,statusMessage) {
}
function start(type,diff) {
function start(type,diff,muteLog) {
//dumpActiveNodes();
type = type||"full";
started = true;
var i;
@@ -218,23 +225,29 @@ function start(type,diff) {
log.info(log._("nodes.flows.missing-type-install-2"));
log.info(" "+settings.userDir);
}
return;
return when.resolve();
}
if (diff) {
log.info(log._("nodes.flows.starting-modified-"+type));
} else {
log.info(log._("nodes.flows.starting-flows"));
if (!muteLog) {
if (diff) {
log.info(log._("nodes.flows.starting-modified-"+type));
} else {
log.info(log._("nodes.flows.starting-flows"));
}
}
var id;
if (!diff) {
activeFlows['_GLOBAL_'] = Flow.create(activeFlowConfig);
if (!activeFlows['global']) {
activeFlows['global'] = Flow.create(activeFlowConfig);
}
for (id in activeFlowConfig.flows) {
if (activeFlowConfig.flows.hasOwnProperty(id)) {
activeFlows[id] = Flow.create(activeFlowConfig,activeFlowConfig.flows[id]);
if (!activeFlows[id]) {
activeFlows[id] = Flow.create(activeFlowConfig,activeFlowConfig.flows[id]);
}
}
}
} else {
activeFlows['_GLOBAL_'].update(activeFlowConfig,activeFlowConfig);
activeFlows['global'].update(activeFlowConfig,activeFlowConfig);
for (id in activeFlowConfig.flows) {
if (activeFlowConfig.flows.hasOwnProperty(id)) {
if (activeFlows[id]) {
@@ -261,19 +274,24 @@ function start(type,diff) {
}
}
events.emit("nodes-started");
if (diff) {
log.info(log._("nodes.flows.started-modified-"+type));
} else {
log.info(log._("nodes.flows.started-flows"));
if (!muteLog) {
if (diff) {
log.info(log._("nodes.flows.started-modified-"+type));
} else {
log.info(log._("nodes.flows.started-flows"));
}
}
return when.resolve();
}
function stop(type,diff) {
function stop(type,diff,muteLog) {
type = type||"full";
if (diff) {
log.info(log._("nodes.flows.stopping-modified-"+type));
} else {
log.info(log._("nodes.flows.stopping-flows"));
if (!muteLog) {
if (diff) {
log.info(log._("nodes.flows.stopping-modified-"+type));
} else {
log.info(log._("nodes.flows.stopping-flows"));
}
}
started = false;
var promises = [];
@@ -311,10 +329,12 @@ function stop(type,diff) {
// can do... so cheat by wiping the map knowing it'll be rebuilt
// in start()
subflowInstanceNodeMap = {};
if (diff) {
log.info(log._("nodes.flows.stopped-modified-"+type));
} else {
log.info(log._("nodes.flows.stopped-flows"));
if (!muteLog) {
if (diff) {
log.info(log._("nodes.flows.stopped-modified-"+type));
} else {
log.info(log._("nodes.flows.stopped-flows"));
}
}
resolve();
});
@@ -347,6 +367,208 @@ function checkTypeInUse(id) {
}
}
function updateMissingTypes() {
var subflowInstanceRE = /^subflow:(.+)$/;
activeFlowConfig.missingTypes = [];
for (var id in activeFlowConfig.allNodes) {
if (activeFlowConfig.allNodes.hasOwnProperty(id)) {
var node = activeFlowConfig.allNodes[id];
if (node.type !== 'tab' && node.type !== 'subflow') {
var subflowDetails = subflowInstanceRE.exec(node.type);
if ( (subflowDetails && !activeFlowConfig.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(node.type)) ) {
if (activeFlowConfig.missingTypes.indexOf(node.type) === -1) {
activeFlowConfig.missingTypes.push(node.type);
}
}
}
}
}
}
function addFlow(flow) {
var i,node;
if (!flow.hasOwnProperty('nodes')) {
throw new Error('missing nodes property');
}
flow.id = redUtil.generateId();
var nodes = [{
type:'tab',
label:flow.label,
id:flow.id
}];
for (i=0;i<flow.nodes.length;i++) {
node = flow.nodes[i];
if (activeFlowConfig.allNodes[node.id]) {
// TODO nls
return when.reject(new Error('duplicate id'));
}
if (node.type === 'tab' || node.type === 'subflow') {
return when.reject(new Error('invalid node type: '+node.type));
}
node.z = flow.id;
nodes.push(node);
}
if (flow.configs) {
for (i=0;i<flow.configs.length;i++) {
node = flow.configs[i];
if (activeFlowConfig.allNodes[node.id]) {
// TODO nls
return when.reject(new Error('duplicate id'));
}
if (node.type === 'tab' || node.type === 'subflow') {
return when.reject(new Error('invalid node type: '+node.type));
}
node.z = flow.id;
nodes.push(node);
}
}
var newConfig = clone(activeConfig);
newConfig = newConfig.concat(nodes);
return setConfig(newConfig,'flows',true).then(function() {
log.info(log._("nodes.flows.added-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
return flow.id;
});
}
function getFlow(id) {
var flow;
if (id === 'global') {
flow = activeFlowConfig;
} else {
flow = activeFlowConfig.flows[id];
}
if (!flow) {
return null;
}
var result = {
id: id
};
if (flow.label) {
result.label = flow.label;
}
if (id !== 'global') {
result.nodes = [];
}
if (flow.nodes) {
var nodeIds = Object.keys(flow.nodes);
if (nodeIds.length > 0) {
result.nodes = nodeIds.map(function(nodeId) {
return clone(flow.nodes[nodeId]);
})
}
}
if (flow.configs) {
var configIds = Object.keys(flow.configs);
result.configs = configIds.map(function(configId) {
return clone(flow.configs[configId]);
})
if (result.configs.length === 0) {
delete result.configs;
}
}
if (flow.subflows) {
var subflowIds = Object.keys(flow.subflows);
result.subflows = subflowIds.map(function(subflowId) {
var subflow = clone(flow.subflows[subflowId]);
var nodeIds = Object.keys(subflow.nodes);
subflow.nodes = nodeIds.map(function(id) {
return subflow.nodes[id];
});
if (subflow.configs) {
var configIds = Object.keys(subflow.configs);
subflow.configs = configIds.map(function(id) {
return subflow.configs[id];
})
}
delete subflow.instances;
return subflow;
});
if (result.subflows.length === 0) {
delete result.subflows;
}
}
return result;
}
function updateFlow(id,newFlow) {
var label = id;
if (id !== 'global') {
if (!activeFlowConfig.flows[id]) {
var e = new Error();
e.code = 404;
throw e;
}
label = activeFlowConfig.flows[id].label;
}
var newConfig = clone(activeConfig);
var nodes;
if (id === 'global') {
// Remove all nodes whose z is not a known flow
// When subflows can be owned by a flow, this logic will have to take
// that into account
newConfig = newConfig.filter(function(node) {
return node.type === 'tab' || (node.hasOwnProperty('z') && activeFlowConfig.flows.hasOwnProperty(node.z));
})
// Add in the new config nodes
nodes = newFlow.configs||[];
if (newFlow.subflows) {
// Add in the new subflows
newFlow.subflows.forEach(function(sf) {
nodes = nodes.concat(sf.nodes||[]).concat(sf.configs||[]);
delete sf.nodes;
delete sf.configs;
nodes.push(sf);
});
}
} else {
newConfig = newConfig.filter(function(node) {
return node.z !== id && node.id !== id;
});
var tabNode = {
type:'tab',
label:newFlow.label,
id:id
}
nodes = [tabNode].concat(newFlow.nodes||[]).concat(newFlow.configs||[]);
nodes.forEach(function(n) {
n.z = id;
});
}
newConfig = newConfig.concat(nodes);
return setConfig(newConfig,'flows',true).then(function() {
log.info(log._("nodes.flows.updated-flow",{label:(label?label+" ":"")+"["+id+"]"}));
})
}
function removeFlow(id) {
if (id === 'global') {
// TODO: nls + error code
throw new Error('not allowed to remove global');
}
var flow = activeFlowConfig.flows[id];
if (!flow) {
var e = new Error();
e.code = 404;
throw e;
}
var newConfig = clone(activeConfig);
newConfig = newConfig.filter(function(node) {
return node.z !== id && node.id !== id;
});
return setConfig(newConfig,'flows',true).then(function() {
log.info(log._("nodes.flows.removed-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
});
}
module.exports = {
init: init,
@@ -383,10 +605,18 @@ module.exports = {
*/
stopFlows: stop,
get started() { return started },
handleError: handleError,
handleStatus: handleStatus,
checkTypeInUse: checkTypeInUse
checkTypeInUse: checkTypeInUse,
addFlow: addFlow,
getFlow: getFlow,
updateFlow: updateFlow,
removeFlow: removeFlow,
disableFlow:null,
enableFlow:null
};

View File

@@ -76,29 +76,28 @@ module.exports = {
if (flow.missingTypes.indexOf(n.type) === -1) {
flow.missingTypes.push(n.type);
}
} else {
var container = null;
if (flow.flows[n.z]) {
container = flow.flows[n.z];
} else if (flow.subflows[n.z]) {
container = flow.subflows[n.z];
}
var container = null;
if (flow.flows[n.z]) {
container = flow.flows[n.z];
} else if (flow.subflows[n.z]) {
container = flow.subflows[n.z];
}
if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
if (subflowDetails) {
var subflowType = subflowDetails[1]
n.subflow = subflowType;
flow.subflows[subflowType].instances.push(n)
}
if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
if (subflowDetails) {
var subflowType = subflowDetails[1]
n.subflow = subflowType;
flow.subflows[subflowType].instances.push(n)
}
if (container) {
container.nodes[n.id] = n;
}
if (container) {
container.nodes[n.id] = n;
}
} else {
if (container) {
container.configs[n.id] = n;
} else {
if (container) {
container.configs[n.id] = n;
} else {
flow.configs[n.id] = n;
flow.configs[n.id]._users = [];
}
flow.configs[n.id] = n;
flow.configs[n.id]._users = [];
}
}
}
@@ -159,7 +158,7 @@ module.exports = {
changed[removed[id].z] = newConfig.allNodes[removed[id].z];
if (changed[removed[id].z].type === "subflow") {
changedSubflows[removed[id].z] = changed[removed[id].z];
delete removed[id];
//delete removed[id];
}
}
} else {

View File

@@ -21,6 +21,7 @@ var fs = require("fs");
var registry = require("./registry");
var credentials = require("./credentials");
var flows = require("./flows");
var context = require("./context");
var Node = require("./Node");
var log = require("../log");
@@ -64,11 +65,12 @@ function createNode(node,def) {
}
}
function init(_settings,storage) {
settings = _settings;
credentials.init(storage);
flows.init(_settings,storage);
registry.init(_settings);
function init(runtime) {
settings = runtime.settings;
credentials.init(runtime.storage);
flows.init(runtime.settings,runtime.storage);
registry.init(runtime);
context.init(runtime.settings);
}
function disableNode(id) {
@@ -120,11 +122,19 @@ module.exports = {
cleanModuleList: registry.cleanModuleList,
// Flow handling
loadFlows: flows.load,
loadFlows: flows.load,
startFlows: flows.startFlows,
stopFlows: flows.stopFlows,
setFlows: flows.setFlows,
getFlows: flows.getFlows,
stopFlows: flows.stopFlows,
setFlows: flows.setFlows,
getFlows: flows.getFlows,
addFlow: flows.addFlow,
getFlow: flows.getFlow,
updateFlow: flows.updateFlow,
removeFlow: flows.removeFlow,
// disableFlow: flows.disableFlow,
// enableFlow: flows.enableFlow,
// Credentials
addCredentials: credentials.add,

View File

@@ -25,17 +25,11 @@ var installer = require("./installer");
var settings;
function init(_settings) {
settings = _settings;
installer.init(settings);
loader.init(settings);
registry.init(settings,loader);
}
//TODO: defaultNodesDir/disableNodePathScan are to make testing easier.
// When the tests are componentized to match the new registry structure,
// these flags belong on localfilesystem.load, not here.
function load(defaultNodesDir,disableNodePathScan) {
return loader.load(defaultNodesDir,disableNodePathScan);
function init(runtime) {
settings = runtime.settings;
installer.init(runtime.settings);
loader.init(runtime);
registry.init(runtime.settings,loader);
}
function addModule(module) {
@@ -58,7 +52,7 @@ function enableNodeSet(typeOrId) {
module.exports = {
init:init,
load:load,
load:loader.load,
clear: registry.clear,
registerType: registry.registerNodeConstructor,

View File

@@ -40,7 +40,7 @@ function checkModulePath(folder) {
var err;
var fullPath = path.resolve(folder);
var packageFile = path.join(fullPath,'package.json');
if (fs.existsSync(packageFile)) {
try {
var pkg = require(packageFile);
moduleName = pkg.name;
if (!pkg['node-red']) {
@@ -49,7 +49,7 @@ function checkModulePath(folder) {
err.code = 'invalid_module';
throw err;
}
} else {
} catch(err2) {
err = new Error("Module not found");
err.code = 404;
throw err;
@@ -151,7 +151,10 @@ function uninstallModule(module) {
}
var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
var moduleDir = path.join(installDir,"node_modules",module);
if (!fs.existsSync(moduleDir)) {
try {
fs.statSync(moduleDir);
} catch(err) {
return reject(new Error(log._("server.install.uninstall-failed",{name:module})));
}

View File

@@ -19,26 +19,16 @@ var fs = require("fs");
var path = require("path");
var semver = require("semver");
var events = require("../../events");
var localfilesystem = require("./localfilesystem");
var registry = require("./registry");
var RED;
var settings;
var runtime;
var i18n = require("../../i18n");
events.on("node-locales-dir", function(info) {
i18n.registerMessageCatalog(info.namespace,info.dir,info.file);
});
function init(_settings) {
settings = _settings;
localfilesystem.init(settings);
RED = require('../../red');
function init(_runtime) {
runtime = _runtime;
settings = runtime.settings;
localfilesystem.init(runtime);
}
function load(defaultNodesDir,disableNodePathScan) {
@@ -51,13 +41,73 @@ function load(defaultNodesDir,disableNodePathScan) {
return loadNodeFiles(nodeFiles);
}
function copyObjectProperties(src,dst,copyList,blockList) {
if (!src) {
return;
}
if (copyList && !blockList) {
copyList.forEach(function(i) {
if (src.hasOwnProperty(i)) {
var propDescriptor = Object.getOwnPropertyDescriptor(src,i);
Object.defineProperty(dst,i,propDescriptor);
}
});
} else if (!copyList && blockList) {
for (var i in src) {
if (src.hasOwnProperty(i) && blockList.indexOf(i) === -1) {
var propDescriptor = Object.getOwnPropertyDescriptor(src,i);
Object.defineProperty(dst,i,propDescriptor);
}
}
}
}
function createNodeApi(node) {
var red = {
nodes: {},
log: {},
settings: {},
util: runtime.util,
version: runtime.version,
}
copyObjectProperties(runtime.nodes,red.nodes,["createNode","getNode","eachNode","registerType","addCredentials","getCredentials","deleteCredentials" ]);
copyObjectProperties(runtime.log,red.log,null,["init"]);
copyObjectProperties(runtime.settings,red.settings,null,["init","load","reset"]);
if (runtime.adminApi) {
red.comms = runtime.adminApi.comms;
red.library = runtime.adminApi.library;
red.auth = runtime.adminApi.auth;
red.httpAdmin = runtime.adminApi.adminApp;
red.httpNode = runtime.adminApi.nodeApp;
red.server = runtime.adminApi.server;
} else {
red.comms = {
publish: function(){}
};
red.library = {
register: function(){}
};
red.auth = {
needsPermission: function() {}
};
// TODO: stub out httpAdmin/httpNode/server
}
red["_"] = function() {
var args = Array.prototype.slice.call(arguments, 0);
args[0] = node.namespace+":"+args[0];
return runtime.i18n._.apply(null,args);
}
return red;
}
function loadNodeFiles(nodeFiles) {
var promises = [];
for (var module in nodeFiles) {
/* istanbul ignore else */
if (nodeFiles.hasOwnProperty(module)) {
if (nodeFiles[module].redVersion &&
!semver.satisfies(RED.version().replace("-git",""), nodeFiles[module].redVersion)) {
!semver.satisfies(runtime.version().replace("-git",""), nodeFiles[module].redVersion)) {
//TODO: log it
continue;
}
@@ -172,7 +222,7 @@ function loadNodeConfig(fileInfo) {
index = regExp.lastIndex;
var help = content.substring(regExp.lastIndex-match[1].length,regExp.lastIndex);
var lang = "en-US";
var lang = runtime.i18n.defaultLang;
if ((match = langRegExp.exec(help)) !== null) {
lang = match[1];
}
@@ -197,7 +247,7 @@ function loadNodeConfig(fileInfo) {
fs.stat(path.join(path.dirname(file),"locales"),function(err,stat) {
if (!err) {
node.namespace = node.id;
i18n.registerMessageCatalog(node.id,
runtime.i18n.registerMessageCatalog(node.id,
path.join(path.dirname(file),"locales"),
path.basename(file,".js")+".json")
.then(function() {
@@ -213,20 +263,6 @@ function loadNodeConfig(fileInfo) {
});
}
//function getAPIForNode(node) {
// var red = {
// nodes: RED.nodes,
// library: RED.library,
// credentials: RED.credentials,
// events: RED.events,
// log: RED.log,
//
// }
//
//}
/**
* Loads the specified node into the runtime
* @param node a node info object - see loadNodeConfig
@@ -247,18 +283,7 @@ function loadNodeSet(node) {
var r = require(node.file);
if (typeof r === "function") {
var red = {};
for (var i in RED) {
if (RED.hasOwnProperty(i) && !/^(init|start|stop)$/.test(i)) {
var propDescriptor = Object.getOwnPropertyDescriptor(RED,i);
Object.defineProperty(red,i,propDescriptor);
}
}
red["_"] = function() {
var args = Array.prototype.slice.call(arguments, 0);
args[0] = node.namespace+":"+args[0];
return i18n._.apply(null,args);
}
var red = createNodeApi(node);
var promise = r(red);
if (promise != null && typeof promise.then === "function") {
loadPromise = promise.then(function() {
@@ -346,7 +371,7 @@ function getNodeHelp(node,lang) {
if (help) {
node.help[lang] = help;
} else {
node.help[lang] = node.help["en-US"];
node.help[lang] = node.help[runtime.i18n.defaultLang];
}
}

View File

@@ -18,21 +18,18 @@ var when = require("when");
var fs = require("fs");
var path = require("path");
var events = require("../../events");
var log = require("../../log");
var events;
var log;
var i18n;
var settings;
var defaultNodesDir = path.resolve(path.join(__dirname,"..","..","..","nodes"));
var disableNodePathScan = false;
function init(_settings,_defaultNodesDir,_disableNodePathScan) {
settings = _settings;
if (_disableNodePathScan) {
disableNodePathScan = _disableNodePathScan;
}
if (_defaultNodesDir) {
defaultNodesDir = path.resolve(_defaultNodesDir);
}
function init(runtime) {
settings = runtime.settings;
events = runtime.events;
log = runtime.log;
i18n = runtime.i18n;
}
function isExcluded(name) {
@@ -49,16 +46,17 @@ function getLocalFile(file) {
if (isExcluded(path.basename(file))) {
return null;
}
if (fs.existsSync(file.replace(/\.js$/,".html"))) {
try {
fs.statSync(file.replace(/\.js$/,".html"));
return {
file: file,
module: "node-red",
name: path.basename(file).replace(/^\d+-/,"").replace(/\.js$/,""),
version: settings.version
};
} catch(err) {
return null;
}
return null;
}
@@ -133,7 +131,7 @@ function scanDirForNodesModules(dir,moduleName) {
* @return a list of node modules: {dir,package}
*/
function scanTreeForNodesModules(moduleName) {
var dir = __dirname+"/../../nodes";
var dir = settings.coreNodesDir;
var results = [];
var userDir;
@@ -142,14 +140,16 @@ function scanTreeForNodesModules(moduleName) {
results = results.concat(scanDirForNodesModules(userDir,moduleName));
}
var up = path.resolve(path.join(dir,".."));
while (up !== dir) {
var pm = path.join(dir,"node_modules");
if (pm != userDir) {
results = results.concat(scanDirForNodesModules(pm,moduleName));
if (dir) {
var up = path.resolve(path.join(dir,".."));
while (up !== dir) {
var pm = path.join(dir,"node_modules");
if (pm != userDir) {
results = results.concat(scanDirForNodesModules(pm,moduleName));
}
dir = up;
up = path.resolve(path.join(dir,".."));
}
dir = up;
up = path.resolve(path.join(dir,".."));
}
return results;
}
@@ -175,9 +175,11 @@ function getModuleNodeFiles(module) {
});
var iconDir = path.join(moduleDir,path.dirname(nodes[n]),"icons");
if (iconDirs.indexOf(iconDir) == -1) {
if (fs.existsSync(iconDir)) {
try {
fs.statSync(iconDir);
events.emit("node-icon-dir",iconDir);
iconDirs.push(iconDir);
} catch(err) {
}
}
}
@@ -185,24 +187,16 @@ function getModuleNodeFiles(module) {
return results;
}
function getNodeFiles(_defaultNodesDir,disableNodePathScan) {
if (_defaultNodesDir) {
defaultNodesDir = _defaultNodesDir;
}
function getNodeFiles(disableNodePathScan) {
var dir;
// Find all of the nodes to load
//console.log(defaultNodesDir);
var nodeFiles = getLocalNodeFiles(path.resolve(defaultNodesDir));
//console.log(nodeFiles);
var nodeFiles = [];
var defaultLocalesPath = path.resolve(path.join(defaultNodesDir,"core","locales"));
events.emit("node-locales-dir", {
namespace:"node-red",
dir: defaultLocalesPath,
file: "messages.json"
});
if (settings.coreNodesDir) {
nodeFiles = getLocalNodeFiles(path.resolve(settings.coreNodesDir));
var defaultLocalesPath = path.resolve(path.join(settings.coreNodesDir,"core","locales"));
i18n.registerMessageCatalog("node-red",defaultLocalesPath,"messages.json");
}
if (settings.userDir) {
dir = path.join(settings.userDir,"nodes");
@@ -246,6 +240,8 @@ function getNodeFiles(_defaultNodesDir,disableNodePathScan) {
});
nodeFiles = nodeFiles.concat(nodeModuleFiles);
});
} else {
console.log("node path scan disabled");
}
return nodeList;
}

View File

@@ -256,7 +256,7 @@ function getFullNodeInfo(typeOrId) {
var module = moduleConfigs[getModule(id)];
if (module) {
return module.nodes[getNode(id)];
}
}
}
return null;
}
@@ -292,7 +292,7 @@ function getModuleList() {
//}
//return list;
return moduleConfigs;
}
function getModuleInfo(module) {
@@ -314,6 +314,18 @@ function getModuleInfo(module) {
}
}
function getCaller(){
var orig = Error.prepareStackTrace;
Error.prepareStackTrace = function(_, stack){ return stack; };
var err = new Error();
Error.captureStackTrace(err, arguments.callee);
var stack = err.stack;
Error.prepareStackTrace = orig;
stack.shift();
stack.shift();
return stack[0].getFileName();
}
function registerNodeConstructor(type,constructor) {
if (nodeConstructors[type]) {
throw new Error(type+" already registered");
@@ -358,7 +370,7 @@ function getNodeConfig(id,lang) {
if (config) {
var result = config.config;
result += loader.getNodeHelp(config,lang||"en-US")
//if (config.script) {
// result += '<script type="text/javascript">'+config.script+'</script>';
//}
@@ -490,9 +502,9 @@ var registry = module.exports = {
addNodeSet: addNodeSet,
enableNodeSet: enableNodeSet,
disableNodeSet: disableNodeSet,
removeModule: removeModule,
getNodeInfo: getNodeInfo,
getFullNodeInfo: getFullNodeInfo,
getNodeList: getNodeList,

View File

@@ -28,7 +28,7 @@ var persistentSettings = {
userSettings = settings;
for (var i in settings) {
/* istanbul ignore else */
if (settings.hasOwnProperty(i)) {
if (settings.hasOwnProperty(i) && typeof settings[i] !== "function") {
(function() {
var j = i;
persistentSettings.__defineGetter__(j,function() { return userSettings[j]; });

View File

@@ -25,6 +25,7 @@ var log = require("../log");
var promiseDir = nodeFn.lift(mkdirp);
var initialFlowLoadComplete = false;
var settings;
var flowsFile;
var flowsFullPath;
@@ -128,9 +129,10 @@ var localfilesystem = {
var promises = [];
if (!settings.userDir) {
if (fs.existsSync(fspath.join(process.env.NODE_RED_HOME,".config.json"))) {
try {
fs.statSync(fspath.join(process.env.NODE_RED_HOME,".config.json"));
settings.userDir = process.env.NODE_RED_HOME;
} else {
} catch(err) {
settings.userDir = fspath.join(process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE || process.env.NODE_RED_HOME,".node-red");
if (!settings.readOnly) {
promises.push(promiseDir(settings.userDir));
@@ -148,10 +150,11 @@ var localfilesystem = {
// Relative to cwd
flowsFullPath = fspath.join(process.cwd(),flowsFile);
} else {
if (fs.existsSync(fspath.join(process.cwd(),flowsFile))) {
try {
fs.statSync(fspath.join(process.cwd(),flowsFile));
// Found in cwd
flowsFullPath = fspath.join(process.cwd(),flowsFile);
} else {
} catch(err) {
// Use userDir
flowsFullPath = fspath.join(settings.userDir,flowsFile);
}
@@ -189,17 +192,17 @@ var localfilesystem = {
getFlows: function() {
return when.promise(function(resolve) {
log.info(log._("storage.localfilesystem.user-dir",{path:settings.userDir}));
log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath}));
fs.exists(flowsFullPath, function(exists) {
if (exists) {
resolve(nodeFn.call(fs.readFile,flowsFullPath,'utf8').then(function(data) {
return JSON.parse(data);
}));
} else {
log.info(log._("storage.localfilesystem.create"));
resolve([]);
if (!initialFlowLoadComplete) {
initialFlowLoadComplete = true;
log.info(log._("storage.localfilesystem.user-dir",{path:settings.userDir}));
log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath}));
}
fs.readFile(flowsFullPath,'utf8',function(err,data) {
if (!err) {
return resolve(JSON.parse(data));
}
log.info(log._("storage.localfilesystem.create"));
resolve([]);
});
});
},
@@ -209,8 +212,9 @@ var localfilesystem = {
return when.resolve();
}
if (fs.existsSync(flowsFullPath)) {
try {
fs.renameSync(flowsFullPath,flowsFileBackup);
} catch(err) {
}
var flowData;
@@ -225,17 +229,13 @@ var localfilesystem = {
getCredentials: function() {
return when.promise(function(resolve) {
fs.exists(credentialsFile, function(exists) {
if (exists) {
resolve(nodeFn.call(fs.readFile, credentialsFile, 'utf8').then(function(data) {
return JSON.parse(data)
}));
fs.readFile(credentialsFile,'utf8',function(err,data) {
if (!err) {
resolve(JSON.parse(data));
} else {
fs.exists(oldCredentialsFile, function(exists) {
if (exists) {
resolve(nodeFn.call(fs.readFile, oldCredentialsFile, 'utf8').then(function(data) {
return JSON.parse(data)
}));
fs.readFile(oldCredentialsFile,'utf8',function(err,data) {
if (!err) {
resolve(JSON.parse(data));
} else {
resolve({});
}
@@ -250,8 +250,9 @@ var localfilesystem = {
return when.resolve();
}
if (fs.existsSync(credentialsFile)) {
try {
fs.renameSync(credentialsFile,credentialsFileBackup);
} catch(err) {
}
var credentialData;
if (settings.flowFilePretty) {
@@ -263,21 +264,18 @@ var localfilesystem = {
},
getSettings: function() {
if (fs.existsSync(globalSettingsFile)) {
return nodeFn.call(fs.readFile,globalSettingsFile,'utf8').then(function(data) {
if (data) {
return when.promise(function(resolve,reject) {
fs.readFile(globalSettingsFile,'utf8',function(err,data) {
if (!err) {
try {
return JSON.parse(data);
} catch(err) {
return resolve(JSON.parse(data));
} catch(err2) {
log.trace("Corrupted config detected - resetting");
return {};
}
} else {
return {};
}
});
}
return when.resolve({});
return resolve({});
})
})
},
saveSettings: function(settings) {
if (settings.readOnly) {
@@ -286,21 +284,18 @@ var localfilesystem = {
return writeFile(globalSettingsFile,JSON.stringify(settings,null,1));
},
getSessions: function() {
if (fs.existsSync(sessionsFile)) {
return nodeFn.call(fs.readFile,sessionsFile,'utf8').then(function(data) {
if (data) {
return when.promise(function(resolve,reject) {
fs.readFile(sessionsFile,'utf8',function(err,data){
if (!err) {
try {
return JSON.parse(data);
} catch(err) {
return resolve(JSON.parse(data));
} catch(err2) {
log.trace("Corrupted sessions file - resetting");
return {};
}
} else {
return {};
}
});
}
return when.resolve({});
resolve({});
})
});
},
saveSessions: function(sessions) {
if (settings.readOnly) {

View File

@@ -103,10 +103,81 @@ function compareObjects(obj1,obj2) {
return true;
}
function getMessageProperty(msg,expr) {
var result = null;
if (expr.indexOf('msg.')===0) {
expr = expr.substring(4);
}
var msgPropParts = expr.split(".");
msgPropParts.reduce(function(obj, i) {
result = (typeof obj[i] !== "undefined" ? obj[i] : undefined);
return result;
}, msg);
return result;
}
function setMessageProperty(msg,prop,value,createMissing) {
if (typeof createMissing === 'undefined') {
createMissing = (typeof value !== 'undefined');
}
if (prop.indexOf('msg.')===0) {
prop = prop.substring(4);
}
var msgPropParts = prop.split(".");
var depth = 0;
msgPropParts.reduce(function(obj, i) {
if (obj === null) {
return null;
}
depth++;
if (depth === msgPropParts.length) {
if (typeof value === "undefined") {
delete obj[i];
} else {
obj[i] = value;
}
} else {
if (!obj[i]) {
if (createMissing) {
obj[i] = {};
} else {
return null;
}
}
return obj[i];
}
}, msg);
}
function evaluateNodeProperty(value, type, node, msg) {
if (type === 'str') {
return ""+value;
} else if (type === 'num') {
return Number(value);
} else if (type === 'json') {
return JSON.parse(value);
} else if (type === 're') {
return new RegExp(value);
} else if (type === 'msg' && msg) {
return getMessageProperty(msg,value);
} else if (type === 'flow' && node) {
return node.context().flow.get(value);
} else if (type === 'global' && node) {
return node.context().global.get(value);
} else if (type === 'bool') {
return /^true$/i.test(value)
}
return value;
}
module.exports = {
ensureString: ensureString,
ensureBuffer: ensureBuffer,
cloneMessage: cloneMessage,
compareObjects: compareObjects,
generateId: generateId
generateId: generateId,
getMessageProperty: getMessageProperty,
setMessageProperty: setMessageProperty,
evaluateNodeProperty: evaluateNodeProperty
};

View File

@@ -148,7 +148,7 @@ module.exports = {
functionGlobalContext: {
// os:require('os'),
// bonescript:require('bonescript'),
// octalbonescript:require('octalbonescript'),
// jfive:require("johnny-five"),
// j5board:require("johnny-five").Board({repl:false})
},

View File

@@ -68,7 +68,7 @@ describe('template node', function() {
msg.topic.foo.should.have.a.property('bar', 'payload=foo');
done();
});
n1.receive({payload:{doh:{rei:{me:"foo"}}}, topic:"bar"});
n1.receive({payload:{doh:{rei:{me:"foo"}}}});
});
});

View File

@@ -19,9 +19,6 @@ var should = require("should");
var changeNode = require("../../../../nodes/core/logic/15-change.js");
var helper = require("../../helper.js");
var Log = require("../../../../red/log.js");
describe('change Node', function() {
beforeEach(function(done) {
@@ -37,7 +34,7 @@ describe('change Node', function() {
var flow = [{ id: "c1", type: "change", name:"change1" }];
helper.load(changeNode, flow, function() {
helper.getNode("c1").should.have.property("name", "change1");
helper.getNode("c1").should.have.property("rules", [{t:undefined,p:''}]);
helper.getNode("c1").should.have.property("rules", [{fromt:'str',pt:'msg',tot:'str',t:undefined,p:''}]);
done();
});
});
@@ -45,7 +42,7 @@ describe('change Node', function() {
var flow = [{ id: "c1", type: "change", name:"change1", action:"replace" }];
helper.load(changeNode, flow, function() {
helper.getNode("c1").should.have.property("name", "change1");
helper.getNode("c1").should.have.property("rules", [ { p: '', t: 'set', to: '' } ]);
helper.getNode("c1").should.have.property("rules", [ {fromt: 'str', p: '', pt: 'msg', t: 'set', to: '', tot: 'str'} ]);
done();
});
});
@@ -54,12 +51,12 @@ describe('change Node', function() {
helper.load(changeNode, flow, function() {
//console.log(helper.getNode("c1"));
helper.getNode("c1").should.have.property("name", "change1");
helper.getNode("c1").should.have.property("rules", [ { from: /(?:)/g, p: '', re: undefined, t: 'change', to: '' } ]);
helper.getNode("c1").should.have.property("rules", [ { from: /(?:)/g,fromt: 'str', p: '',pt: 'msg', re: undefined, t: 'change', to: '',tot: 'str' } ]);
done();
});
});
describe('#replace' , function() {
describe('#set' , function() {
it('sets the value of the message property', function(done) {
var flow = [{"id":"changeNode1","type":"change","action":"replace","property":"payload","from":"","to":"changed","reg":false,"name":"changeNode","wires":[["helperNode1"]]},
@@ -67,6 +64,7 @@ describe('change Node', function() {
helper.load(changeNode, flow, function() {
var changeNode1 = helper.getNode("changeNode1");
var helperNode1 = helper.getNode("helperNode1");
var rule = helper.getNode("changeNode1").rules[0];
helperNode1.on("input", function(msg) {
try {
msg.payload.should.equal("changed");
@@ -121,6 +119,9 @@ describe('change Node', function() {
helper.load(changeNode, flow, function() {
var changeNode1 = helper.getNode("changeNode1");
var helperNode1 = helper.getNode("helperNode1");
var rule = helper.getNode("changeNode1").rules[0];
rule.t.should.eql('set');
rule.tot.should.eql('msg');
helperNode1.on("input", function(msg) {
try {
msg.foo.should.equal("bar");
@@ -240,6 +241,44 @@ describe('change Node', function() {
changeNode1.receive({pay:{load:"changeMe"}});
});
});
it('changes the value to a number', function(done) {
var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"set","p":"payload","to":"123","tot":"num"}],"name":"changeNode","wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(changeNode, flow, function() {
var changeNode1 = helper.getNode("changeNode1");
var helperNode1 = helper.getNode("helperNode1");
helperNode1.on("input", function(msg) {
try {
msg.payload.should.eql(123);
done();
} catch(err) {
done(err);
}
});
changeNode1.receive({payload:""});
});
});
it('changes the value to a js object', function(done) {
var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"set","p":"payload","to":'{"a":123}',"tot":"json"}],"name":"changeNode","wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(changeNode, flow, function() {
var changeNode1 = helper.getNode("changeNode1");
var helperNode1 = helper.getNode("helperNode1");
helperNode1.on("input", function(msg) {
try {
msg.payload.should.eql({a:123});
done();
} catch(err) {
done(err);
}
});
changeNode1.receive({payload:""});
});
});
});
describe('#change', function() {
it('changes the value of the message property', function(done) {
@@ -350,7 +389,7 @@ describe('change Node', function() {
});
});
it('Reports invalid regex', function(done) {
it('reports invalid regex', function(done) {
var sinon = require('sinon');
var flow = [{"id":"changeNode1","type":"change","action":"change","property":"payload","from":"\\+**+","to":"NUMBER","reg":true,"name":"changeNode","wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
@@ -366,6 +405,43 @@ describe('change Node', function() {
});
});
it('supports regex groups - new rule format', function(done) {
var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"change","p":"payload","from":"(Hello)","to":"$1-$1-$1","fromt":"re","tot":"str"}],"name":"changeNode","wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(changeNode, flow, function() {
var changeNode1 = helper.getNode("changeNode1");
var helperNode1 = helper.getNode("helperNode1");
helperNode1.on("input", function(msg) {
try {
msg.payload.should.equal("Hello-Hello-Hello World");
done();
} catch(err) {
done(err);
}
});
changeNode1.receive({payload:"Hello World"});
});
});
it('changes the value - new rule format', function(done) {
var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"change","p":"payload","from":"ABC","to":"123","fromt":"str","tot":"str"}],"name":"changeNode","wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(changeNode, flow, function() {
var changeNode1 = helper.getNode("changeNode1");
var helperNode1 = helper.getNode("helperNode1");
helperNode1.on("input", function(msg) {
try {
msg.payload.should.equal("abc123abc");
done();
} catch(err) {
done(err);
}
});
changeNode1.receive({payload:"abcABCabc"});
});
});
});
describe("#delete", function() {

View File

@@ -77,7 +77,6 @@ describe('JSON node', function() {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
jn2.on("input", function(msg) {
console.log(msg);
should.equal(msg.payload, '[1,2,3]');
done();
});
@@ -118,7 +117,6 @@ describe('JSON node', function() {
var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "json";
});
console.log(logEvents);
logEvents.should.have.length(3);
logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.eql('json.errors.dropped');

View File

@@ -20,7 +20,6 @@ var fs = require('fs-extra');
var sinon = require("sinon");
var fileNode = require("../../../../nodes/core/storage/50-file.js");
var helper = require("../../helper.js");
var log = require("../../../../red/log");
describe('file Nodes', function() {

View File

@@ -30,11 +30,11 @@ if (!process.version.match(/^v0\.[0-9]\./)) {
}
}
var RED = require("../../red/red.js");
var redNodes = require("../../red/nodes");
var flows = require("../../red/nodes/flows");
var credentials = require("../../red/nodes/credentials");
var comms = require("../../red/comms.js");
var log = require("../../red/log.js");
var redNodes = require("../../red/runtime/nodes");
var flows = require("../../red/runtime/nodes/flows");
var credentials = require("../../red/runtime/nodes/credentials");
var comms = require("../../red/api/comms.js");
var log = require("../../red/runtime/log.js");
var http = require('http');
var express = require('express');
@@ -99,7 +99,7 @@ module.exports = {
return messageId;
};
redNodes.init(settings, storage);
redNodes.init({settings:settings, storage:storage});
credentials.init(storage,express());
RED.nodes.registerType("helper", helperNode);
if (Array.isArray(testNode)) {

View File

@@ -24,12 +24,14 @@ var auth = require("../../../../red/api/auth");
var Users = require("../../../../red/api/auth/users");
var Tokens = require("../../../../red/api/auth/tokens");
var settings = require("../../../../red/settings");
describe("api auth middleware",function() {
describe("ensureClientSecret", function() {
before(function() {
auth.init({settings:{},log:{audit:function(){}}})
});
it("leaves client_secret alone if not present",function(done) {
var req = {
body: {
@@ -83,7 +85,7 @@ describe("api auth middleware",function() {
Users.init.restore();
});
it("returns login details - credentials", function(done) {
auth.init({adminAuth:{}},null);
auth.init({settings:{adminAuth:{}},log:{audit:function(){}}})
auth.login(null,{json: function(resp) {
resp.should.have.a.property("type","credentials");
resp.should.have.a.property("prompts");
@@ -92,7 +94,7 @@ describe("api auth middleware",function() {
}});
});
it("returns login details - none", function(done) {
auth.init({},null);
auth.init({settings:{},log:{audit:function(){}}})
auth.login(null,{json: function(resp) {
resp.should.eql({});
done();

View File

@@ -24,8 +24,10 @@ var Tokens = require("../../../../red/api/auth/tokens");
var Clients = require("../../../../red/api/auth/clients");
describe("Auth strategies", function() {
before(function() {
strategies.init({log:{audit:function(){}}})
});
describe("Password Token Exchange", function() {
var userAuthentication;
afterEach(function() {
if (userAuthentication) {
@@ -49,7 +51,7 @@ describe("Auth strategies", function() {
}
});
});
it('Handles scope overreach',function(done) {
userAuthentication = sinon.stub(Users,"authenticate",function(username,password) {
return when.resolve({username:"user",permissions:"read"});

View File

@@ -23,21 +23,26 @@ var express = require('express');
var app = express();
var WebSocket = require('ws');
var comms = require("../../red/comms.js");
var Users = require("../../red/api/auth/users");
var Tokens = require("../../red/api/auth/tokens");
var comms = require("../../../red/api/comms");
var Users = require("../../../red/api/auth/users");
var Tokens = require("../../../red/api/auth/tokens");
var address = '127.0.0.1';
var listenPort = 0; // use ephemeral port
describe("comms", function() {
describe("api/comms", function() {
describe("with default keepalive", function() {
var server;
var url;
var port;
before(function(done) {
sinon.stub(Users,"default",function() { return when.resolve(null);});
server = http.createServer(function(req,res){app(req,res)});
comms.init(server, {});
comms.init(server, {
settings:{},
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
events:{on:function(){},removeListener:function(){}}
});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
@@ -46,11 +51,12 @@ describe("comms", function() {
done();
});
});
after(function() {
Users.default.restore();
comms.stop();
});
it('accepts connection', function(done) {
var ws = new WebSocket(url);
ws.on('open', function() {
@@ -58,7 +64,7 @@ describe("comms", function() {
done();
});
});
it('publishes message after subscription', function(done) {
var ws = new WebSocket(url);
ws.on('open', function() {
@@ -71,7 +77,7 @@ describe("comms", function() {
done();
});
});
it('publishes retained message for subscription', function(done) {
comms.publish('topic2', 'bar', true);
var ws = new WebSocket(url);
@@ -84,7 +90,7 @@ describe("comms", function() {
done();
});
});
it('retained message is deleted by non-retained message', function(done) {
comms.publish('topic3', 'retained', true);
comms.publish('topic3', 'non-retained');
@@ -99,7 +105,7 @@ describe("comms", function() {
done();
});
});
it('malformed messages are ignored',function(done) {
var ws = new WebSocket(url);
ws.on('open', function() {
@@ -114,34 +120,39 @@ describe("comms", function() {
done();
});
});
// The following test currently fails due to minimum viable
// implementation. More test should be written to test topic
// matching once this one is passing
if (0) {
it('receives message on correct topic', function(done) {
var ws = new WebSocket(url);
ws.on('open', function() {
ws.send('{"subscribe":"topic4"}');
comms.publish('topic5', 'foo');
comms.publish('topic4', 'bar');
});
ws.on('message', function(msg) {
msg.should.equal('{"topic":"topic4","data":"bar"}');
ws.close();
done();
});
it.skip('receives message on correct topic', function(done) {
var ws = new WebSocket(url);
ws.on('open', function() {
ws.send('{"subscribe":"topic4"}');
comms.publish('topic5', 'foo');
comms.publish('topic4', 'bar');
});
}
ws.on('message', function(msg) {
msg.should.equal('{"topic":"topic4","data":"bar"}');
ws.close();
done();
});
});
it.skip('listens for node/status events');
});
describe("disabled editor", function() {
var server;
var url;
var port;
before(function(done) {
sinon.stub(Users,"default",function() { return when.resolve(null);});
server = http.createServer(function(req,res){app(req,res)});
comms.init(server, {disableEditor:true});
comms.init(server, {
settings:{disableEditor:true},
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
events:{on:function(){},removeListener:function(){}}
});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
@@ -150,11 +161,12 @@ describe("comms", function() {
done();
});
});
after(function() {
Users.default.restore();
comms.stop();
});
it('rejects websocket connections',function(done) {
var ws = new WebSocket(url);
ws.on('open', function() {
@@ -164,17 +176,22 @@ describe("comms", function() {
ws.on('error', function() {
done();
});
});
});
describe("non-default httpAdminRoot set: /adminPath", function() {
var server;
var url;
var port;
before(function(done) {
sinon.stub(Users,"default",function() { return when.resolve(null);});
server = http.createServer(function(req,res){app(req,res)});
comms.init(server, {httpAdminRoot:"/adminPath"});
comms.init(server, {
settings:{httpAdminRoot:"/adminPath"},
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
events:{on:function(){},removeListener:function(){}}
});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
@@ -183,11 +200,12 @@ describe("comms", function() {
done();
});
});
after(function() {
Users.default.restore();
comms.stop();
});
it('accepts connections',function(done) {
var ws = new WebSocket(url);
ws.on('open', function() {
@@ -197,17 +215,22 @@ describe("comms", function() {
ws.on('error', function() {
done(new Error("Socket connection failed"));
});
});
});
describe("non-default httpAdminRoot set: /adminPath/", function() {
var server;
var url;
var port;
before(function(done) {
sinon.stub(Users,"default",function() { return when.resolve(null);});
server = http.createServer(function(req,res){app(req,res)});
comms.init(server, {httpAdminRoot:"/adminPath/"});
comms.init(server,{
settings:{httpAdminRoot:"/adminPath"},
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
events:{on:function(){},removeListener:function(){}}
});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
@@ -216,11 +239,12 @@ describe("comms", function() {
done();
});
});
after(function() {
Users.default.restore();
comms.stop();
});
it('accepts connections',function(done) {
var ws = new WebSocket(url);
ws.on('open', function() {
@@ -230,17 +254,22 @@ describe("comms", function() {
ws.on('error', function() {
done(new Error("Socket connection failed"));
});
});
});
describe("non-default httpAdminRoot set: adminPath", function() {
var server;
var url;
var port;
before(function(done) {
sinon.stub(Users,"default",function() { return when.resolve(null);});
server = http.createServer(function(req,res){app(req,res)});
comms.init(server, {httpAdminRoot:"adminPath"});
comms.init(server, {
settings:{httpAdminRoot:"adminPath"},
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
events:{on:function(){},removeListener:function(){}}
});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
@@ -249,11 +278,12 @@ describe("comms", function() {
done();
});
});
after(function() {
Users.default.restore();
comms.stop();
});
it('accepts connections',function(done) {
var ws = new WebSocket(url);
ws.on('open', function() {
@@ -263,17 +293,22 @@ describe("comms", function() {
ws.on('error', function() {
done(new Error("Socket connection failed"));
});
});
});
describe("keep alives", function() {
var server;
var url;
var port;
before(function(done) {
sinon.stub(Users,"default",function() { return when.resolve(null);});
server = http.createServer(function(req,res){app(req,res)});
comms.init(server, {webSocketKeepAliveTime: 100});
comms.init(server, {
settings:{webSocketKeepAliveTime: 100},
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
events:{on:function(){},removeListener:function(){}}
});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
@@ -283,6 +318,7 @@ describe("comms", function() {
});
});
after(function() {
Users.default.restore();
comms.stop();
});
it('are sent', function(done) {
@@ -325,7 +361,7 @@ describe("comms", function() {
});
});
});
describe('authentication required, no anonymous',function() {
var server;
var url;
@@ -351,10 +387,14 @@ describe("comms", function() {
return when.resolve(null);
}
});
server = http.createServer(function(req,res){app(req,res)});
comms.init(server, {adminAuth:{}});
comms.init(server,{
settings:{adminAuth:{}},
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
events:{on:function(){},removeListener:function(){}}
});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
@@ -369,7 +409,7 @@ describe("comms", function() {
getToken.restore();
comms.stop();
});
it('prevents connections that do not authenticate',function(done) {
var ws = new WebSocket(url);
var count = 0;
@@ -381,7 +421,7 @@ describe("comms", function() {
done();
});
});
it('allows connections that do authenticate',function(done) {
var ws = new WebSocket(url);
var received = 0;
@@ -399,7 +439,7 @@ describe("comms", function() {
ws.close();
}
});
ws.on('close', function() {
try {
received.should.equal(2);
@@ -409,7 +449,7 @@ describe("comms", function() {
}
});
});
it('rejects connections for non-existant token',function(done) {
var ws = new WebSocket(url);
var received = 0;
@@ -440,7 +480,11 @@ describe("comms", function() {
before(function(done) {
getDefaultUser = sinon.stub(Users,"default",function() { return when.resolve({permissions:"read"});});
server = http.createServer(function(req,res){app(req,res)});
comms.init(server, {adminAuth:{}});
comms.init(server, {
settings:{adminAuth:{}},
log:{warn:function(){},_:function(){},trace:function(){},audit:function(){}},
events:{on:function(){},removeListener:function(){}}
});
server.listen(listenPort, address);
server.on('listening', function() {
port = server.address().port;
@@ -453,7 +497,7 @@ describe("comms", function() {
getDefaultUser.restore();
comms.stop();
});
it('allows anonymous connections that do not authenticate',function(done) {
var ws = new WebSocket(url);
var count = 0;
@@ -480,5 +524,5 @@ describe("comms", function() {
});
});
});

Some files were not shown because too many files have changed in this diff Show More