Compare commits

...

5 Commits

Author SHA1 Message Date
Nick O'Leary
3ca64057c3 Add breakpoint editing to debugger 2016-11-22 12:57:30 +00:00
Nick O'Leary
127b4f0226 Add RED.utils.getNodeLabel utility function 2016-11-22 12:57:08 +00:00
Nick O'Leary
629c63e0c9 Add initial debugger panel to debug tab 2016-11-18 12:53:05 +00:00
Nick O'Leary
416d5190bc Flow debugger initial pass 2016-11-16 15:12:30 +00:00
Nick O'Leary
cebddc0237 Add message router component 2016-11-16 15:12:30 +00:00
22 changed files with 986 additions and 251 deletions

View File

@@ -110,6 +110,7 @@ module.exports = function(grunt) {
"editor/js/nodes.js",
"editor/js/history.js",
"editor/js/validators.js",
"editor/js/debugger.js",
"editor/js/ui/common/editableList.js",
"editor/js/ui/common/menu.js",
"editor/js/ui/common/popover.js",

4
editor/js/debugger.js Normal file
View File

@@ -0,0 +1,4 @@
RED.debugger = (function() {
})();

View File

@@ -362,18 +362,7 @@ RED.deploy = (function() {
tabLabel = tab.label;
}
}
var label = "";
if (typeof node._def.label == "function") {
try {
label = node._def.label.call(node);
} catch(err) {
console.log("Definition error: "+node_def.type+".label",err);
label = node_def.type;
}
} else {
label = node._def.label;
}
label = label || node.id;
var label = RED.utils.getNodeLabel(node,node.id);
return {tab:tabLabel,type:node.type,label:label};
}
function sortNodeInfo(A,B) {

View File

@@ -265,17 +265,8 @@ RED.editor = (function() {
var configNode = RED.nodes.node(node[property]);
var node_def = RED.nodes.getType(type);
if (configNode && node_def.label) {
if (typeof node_def.label == "function") {
try {
label = node_def.label.call(configNode);
} catch(err) {
console.log("Definition error: "+node_def.type+".label",err);
label = node_def.type;
}
} else {
label = node_def.label;
}
if (configNode) {
label = RED.utils.getNodeLabel(configNode,configNode.id);
}
input.val(label);
}
@@ -1209,17 +1200,7 @@ RED.editor = (function() {
RED.nodes.eachConfig(function(config) {
if (config.type == type && (!config.z || config.z === activeWorkspace.id)) {
var label = "";
if (typeof node_def.label == "function") {
try {
label = node_def.label.call(config);
} catch(err) {
console.log("Definition error: "+node_def.type+".label",err);
label = node_def.type;
}
} else {
label = node_def.label;
}
var label = RED.utils.getNodeLabel(config,config.id);
config.__label__ = label;
configNodes.push(config);
}

View File

@@ -27,19 +27,11 @@ RED.search = (function() {
var results = [];
function indexNode(n) {
var l = "";
if (n._def && n._def.label) {
l = n._def.label;
try {
l = (typeof l === "function" ? l.call(n) : l);
if (l) {
l = (""+l).toLowerCase();
index[l] = index[l] || {};
index[l][n.id] = {node:n,label:l}
}
} catch(err) {
console.log("Definition error: "+n.type+".label",err);
}
var l = RED.utils.getNodeLabel(n);
if (l) {
l = (""+l).toLowerCase();
index[l] = index[l] || {};
index[l][n.id] = {node:n,label:l}
}
l = l||n.label||n.name||n.id||"";

View File

@@ -131,19 +131,7 @@ RED.sidebar.config = (function() {
} else {
var currentType = "";
nodes.forEach(function(node) {
var label = "";
if (typeof node._def.label == "function") {
try {
label = node._def.label.call(node);
} catch(err) {
console.log("Definition error: "+node._def.type+".label",err);
label = node._def.type;
}
} else {
label = node._def.label;
}
label = label || node.id;
var label = RED.utils.getNodeLabel(node,node.id);
if (node.type != currentType) {
$('<li class="config_node_type">'+node.type+'</li>').appendTo(list);
currentType = node.type;

View File

@@ -267,7 +267,20 @@ RED.utils = (function() {
return element;
}
function getNodeLabel(node,defaultLabel) {
defaultLabel = defaultLabel||"";
var l = node._def.label;
try {
l = (typeof l === "function" ? l.call(node) : l)||defaultLabel;
} catch(err) {
console.log("Definition error: "+node.type+".label",err);
l = defaultLabel;
}
return RED.text.bidi.enforceTextDirectionWithUCC(l);
}
return {
createObjectElement: buildMessageElement,
getNodeLabel: getNodeLabel
}
})();

View File

@@ -56,6 +56,7 @@ RED.view = (function() {
lasso = null,
showStatus = false,
lastClickNode = null,
lastClickPort = null,
dblClickPrimed = null,
clickTime = 0,
clickElapsed = 0;
@@ -260,6 +261,10 @@ RED.view = (function() {
activeNodes = RED.nodes.filterNodes({z:activeWorkspace});
activeNodes.forEach(function(n) {
n.dirty = true;
});
activeLinks = RED.nodes.filterLinks({
source:{z:activeWorkspace},
target:{z:activeWorkspace}
@@ -1317,6 +1322,15 @@ RED.view = (function() {
$(window).on('keyup',disableQuickJoinEventHandler);
}
}
var now = Date.now();
clickElapsed = now-clickTime;
clickTime = now;
dblClickPrimed = (lastClickNode === mousedown_node && lastClickPort === mousedown_port_index);
lastClickNode = mousedown_node;
lastClickPort = mousedown_port_index;
d3.event.stopPropagation();
d3.event.preventDefault();
}
@@ -1328,6 +1342,14 @@ RED.view = (function() {
return
}
}
if (dblClickPrimed && mousedown_node == d && lastClickPort === portIndex && clickElapsed > 0 && clickElapsed < 750) {
RED.debug.toggleBreakpoint(mousedown_node,portType,portIndex);
redraw();
return;
}
document.body.style.cursor = "";
if (mouse_mode == RED.state.JOINING || mouse_mode == RED.state.QUICK_JOINING) {
if (typeof TouchEvent != "undefined" && d3.event instanceof TouchEvent) {
@@ -1570,6 +1592,16 @@ RED.view = (function() {
RED.touch.radialMenu.show(obj,pos,options);
resetMouseVars();
}
function createBreakpoint(port,type) {
var breakPointGroup = port.append("g").attr("class","port_breakpoint").classed("port_breakpoint_inactive",true);
breakPointGroup.append("rect")
.attr("x",2).attr("y",2)
.attr("rx",2).attr("ry",2)
.attr("width",6).attr("height",6);
}
function redraw() {
vis.attr("transform","scale("+scaleFactor+")");
outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor);
@@ -1702,14 +1734,7 @@ RED.view = (function() {
var node = d3.select(this);
var isLink = d.type === "link in" || d.type === "link out";
node.attr("id",d.id);
var l = d._def.label;
try {
l = (typeof l === "function" ? l.call(d) : l)||"";
} catch(err) {
console.log("Definition error: "+d.type+".label",err);
l = d.type;
}
var l = RED.utils.getNodeLabel(d);
if (isLink) {
d.w = node_height;
} else {
@@ -1897,13 +1922,7 @@ RED.view = (function() {
dirtyNodes[d.id] = d;
//if (d.x < -50) deleteSelection(); // Delete nodes if dragged back to palette
if (!isLink && d.resize) {
var l = d._def.label;
try {
l = (typeof l === "function" ? l.call(d) : l)||"";
} catch(err) {
console.log("Definition error: "+d.type+".label",err);
l = d.type;
}
var l = RED.utils.getNodeLabel(d);
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);
@@ -1931,18 +1950,27 @@ RED.view = (function() {
//thisNode.selectAll(".node_icon_shade_border_right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)});
var inputPorts = thisNode.selectAll(".port_input");
var breakPointGroup;
if (d.inputs === 0 && !inputPorts.empty()) {
inputPorts.remove();
//nodeLabel.attr("x",30);
} else if (d.inputs === 1 && inputPorts.empty()) {
var inputGroup = thisNode.append("g").attr("class","port_input");
inputGroup.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10)
.on("mousedown",function(d){portMouseDown(d,1,0);})
.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 || (drag_lines.length > 0 && drag_lines[0].portType !== 1) ));})
.on("mouseout",function(d) { var port = d3.select(this); port.classed("port_hovered",false);})
} else if (d.inputs === 1) {
if (inputPorts.empty()) {
var inputGroup = thisNode.append("g").attr("class","port_input");
inputGroup.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10)
.on("mousedown",function(d){portMouseDown(d,1,0);})
.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 || (drag_lines.length > 0 && drag_lines[0].portType !== 1) ));})
.on("mouseout",function(d) { var port = d3.select(this); port.classed("port_hovered",false);})
createBreakpoint(inputGroup,0);
} else {
var breakpointState = RED.debug.checkBreakpoint(d,1,0);
inputPorts.selectAll(".port_breakpoint").classed("port_breakpoint_active",breakpointState);
inputPorts.selectAll(".port_breakpoint").classed("port_breakpoint_inactive",!breakpointState);
}
}
var numOutputs = d.outputs;
@@ -1959,31 +1987,25 @@ RED.view = (function() {
.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);});
createBreakpoint(output_group,1);
d._ports.exit().remove();
if (d._ports) {
numOutputs = d.outputs || 1;
y = (d.h/2)-((numOutputs-1)/2)*13;
var x = d.w - 5;
d._ports.each(function(d,i) {
var port = d3.select(this);
//port.attr("y",(y+13*i)-5).attr("x",x);
port.attr("transform", function(d) { return "translate("+x+","+((y+13*i)-5)+")";});
d._ports.each(function(n,i) {
var port = d3.select(this);
//port.attr("y",(y+13*i)-5).attr("x",x);
port.attr("transform", function(d) { return "translate("+x+","+((y+13*i)-5)+")";});
var breakpointState = RED.debug.checkBreakpoint(d,0,i);
port.selectAll(".port_breakpoint").classed("port_breakpoint_active",breakpointState);
port.selectAll(".port_breakpoint").classed("port_breakpoint_inactive",!breakpointState);
});
}
thisNode.selectAll("text.node_label").text(function(d,i){
var l = "";
if (d._def.label) {
l = d._def.label;
try {
l = (typeof l === "function" ? l.call(d) : l)||"";
l = RED.text.bidi.enforceTextDirectionWithUCC(l);
} catch(err) {
console.log("Definition error: "+d.type+".label",err);
l = d.type;
}
}
return l;
})
thisNode.selectAll("text.node_label")
.text(function(d,i){ return RED.utils.getNodeLabel(d); })
.attr("y", function(d){return (d.h/2)-1;})
.attr("class",function(d){
var s = "";

View File

@@ -31,6 +31,15 @@
right: 0px;
overflow-y: scroll;
}
.debug-dbgr-content {
top: 0px;
}
.debug-dbgr-content-disabled {
padding-top: 40px;
text-align: center;
background: #f3f3f3;
color: #999;
}
.debug-filter-box {
position:absolute;
top: 42px;
@@ -45,6 +54,18 @@
text-align: right;
}
.debug-debugger-box {
position:absolute;
top: 42px;
left: 0px;
right: 0px;
background: #f9f9f9;
padding: 10px;
border-bottom: 1px solid #ddd;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
.debug-message {
border-bottom: 1px solid #eee;
border-left: 8px solid #eee;
@@ -182,3 +203,34 @@
.debug-message-buffer-string > .debug-message-array-rows {
display: none;
}
.debug-dbgr-breakpoints {
background: #f9f9f9;
padding: 0;
}
.debug-dbgr-content {
.red-ui-editableList {
background: none;
input {
vertical-align: middle;
margin: 0 8px 0 4px;
}
li {
padding: 0;
}
label {
padding: 6px 2px;
&:hover {
background: #f3f3f3;
}
margin: 0;
}
.red-ui-search-result-node-type {
margin-left: 26px;
}
}
.red-ui-editableList-container {
border: none;
border-radius: 0;
padding: 0;
}
}

View File

@@ -120,13 +120,28 @@
fill: #ddd;
cursor: crosshair;
}
.port_highlight {
stroke: #6DA332;
stroke-width: 3;
fill: #fff;
pointer-events:none;
fill-opacity: 0.5;
.port_breakpoint {
pointer-events: none;
rect {
stroke: #666;
stroke-width: 0;
fill: #666;
}
}
.port_breakpoint_inactive {
rect {
fill: none;
}
}
.port_breakpoint_active {
rect {
fill: #5386de;
}
}
.port_breakpoint_triggered {
rect {
fill: #ff1010;
}
}
.node_error {

View File

@@ -47,6 +47,7 @@
margin:0;
text-decoration: none;
cursor:pointer;
padding: 0;
&.disabled {
cursor: default;

View File

@@ -89,12 +89,14 @@
font-size: 13px;
line-height: 13px;
padding: 5px 8px;
min-width: 30px;
}
.sidebar-header-button-toggle {
@include workspace-button-toggle;
font-size: 13px;
line-height: 13px;
padding: 5px 8px;
min-width: 30px;
}
.sidebar-header-button:not(:first-child) {
border-left: none;

View File

@@ -67,21 +67,20 @@ module.exports = function(RED) {
this.error(err,msg);
}
});
this.on('close', function() {
if (node.interval_id != null) {
clearInterval(node.interval_id);
if (RED.settings.verbose) { node.log(RED._("inject.stopped")); }
} else if (node.cronjob != null) {
node.cronjob.stop();
if (RED.settings.verbose) { node.log(RED._("inject.stopped")); }
delete node.cronjob;
}
});
}
RED.nodes.registerType("inject",InjectNode);
InjectNode.prototype.close = function() {
if (this.interval_id != null) {
clearInterval(this.interval_id);
if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
} else if (this.cronjob != null) {
this.cronjob.stop();
if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
delete this.cronjob;
}
}
RED.httpAdmin.post("/inject/:id", RED.auth.needsPermission("inject.write"), function(req,res) {
var node = RED.nodes.getNode(req.params.id);
if (node != null) {

View File

@@ -21,28 +21,37 @@ RED.debug = (function() {
var config;
var messageList;
var messageTable;
var debuggerView;
var filter = false;
var view = 'list';
var messages = [];
var messagesByNode = {};
var sbc;
var activeWorkspace;
var breakpointList;
var debuggerEnabled = false;
function init(_config) {
config = _config;
var content = $("<div>").css({"position":"relative","height":"100%"});
var toolbar = $('<div class="sidebar-header">'+
'<span class="button-group"><a id="debug-tab-filter" class="sidebar-header-button" href="#"><i class="fa fa-filter"></i></a></span>'+
'<span class="button-group"><a id="debug-tab-clear" title="clear log" class="sidebar-header-button" href="#"><i class="fa fa-trash"></i></a></span></div>').appendTo(content);
'<span class="button-group" style="float: left"><button id="debug-tab-view-console" class="sidebar-header-button-toggle selected">console</button><button id="debug-tab-view-debugger" class="sidebar-header-button-toggle">debugger</button></span>'+
'<span class="debug-tab-view-console-buttons">'+
'<span class="button-group"><button id="debug-tab-filter" class="sidebar-header-button"><i class="fa fa-filter"></i></button></span>'+
'<span class="button-group"><button id="debug-tab-clear" title="clear log" class="sidebar-header-button"><i class="fa fa-trash"></i></button></span>'+
'</span>'+
'<span class="debug-tab-view-dbgr-buttons hide">'+
'<span class="button-group"><button id="debug-tab-dbgr-toggle" class="sidebar-header-button"><i class="fa fa-toggle-off"></i></button></span>'+
'</span>'+
'</div>').appendTo(content);
var footerToolbar = $('<div>'+
// '<span class="button-group">'+
// '<a class="sidebar-footer-button-toggle text-button selected" id="debug-tab-view-list" href="#"><span data-i18n="">list</span></a>'+
// '<a class="sidebar-footer-button-toggle text-button" id="debug-tab-view-table" href="#"><span data-i18n="">table</span></a> '+
// '</span>'+
'<span class="button-group"><a id="debug-tab-open" title="open in new window" class="sidebar-footer-button" href="#"><i class="fa fa-desktop"></i></a></span> ' +
'<span class="button-group"><button id="debug-tab-open" title="open in new window" class="sidebar-footer-button"><i class="fa fa-desktop"></i></button></span> ' +
'</div>');
messageList = $('<div class="debug-content debug-content-list"/>').appendTo(content);
@@ -51,21 +60,164 @@ RED.debug = (function() {
var filterDialog = $('<div class="debug-filter-box hide">'+
'<div class="debug-filter-row">'+
'<span class="button-group">'+
'<a class="sidebar-header-button-toggle selected" id="debug-tab-filter-all" href="#"><span data-i18n="node-red:debug.sidebar.filterAll">all flows</span></a>'+
'<a class="sidebar-header-button-toggle" id="debug-tab-filter-current" href="#"><span data-i18n="node-red:debug.sidebar.filterCurrent">current flow</span></a> '+
'</span>'+
'<span class="button-group">'+
'<button class="sidebar-header-button-toggle selected" id="debug-tab-filter-all"><span data-i18n="node-red:debug.sidebar.filterAll">all flows</span></button>'+
'<button class="sidebar-header-button-toggle" id="debug-tab-filter-current"><span data-i18n="node-red:debug.sidebar.filterCurrent">current flow</span></button> '+
'</span>'+
'</div>'+
'</div>').appendTo(content);
debuggerView = $('<div class="debug-content hide"/>').appendTo(content);
var dbgrDisabled = $('<div class="debug-content debug-dbgr-content debug-dbgr-content-disabled">Debugger is disabled</div>').appendTo(debuggerView);
var dbgrPanel = $('<div class="debug-content debug-dbgr-content"></div>').hide().appendTo(debuggerView);
var dbgrToolbar = $('<div class="sidebar-header" style="text-align:left">'+
'<span>'+
'<button id="" class="sidebar-header-button"><i class="fa fa-pause"></i></button>'+
'<button id="" class="sidebar-header-button"><i class="fa fa-play"></i></button>'+
'<button id="" class="sidebar-header-button"><i class="fa fa-step-forward"></i></button>'+
'</span>'+
'</div>').appendTo(dbgrPanel);
/************ Breakpoints ************/
var breakpointPanel = $('<div class="palette-category">'+
'<div class="palette-header">'+
'<i class="fa fa-angle-down expanded"></i>'+
'<span>Breakpoints</span>'+
'<span id="debug-dbgr-breakpoint-count" class="config-node-filter-info"></span>'+
'</div>'+
'</div>').appendTo(dbgrPanel);
var breakPointContent = $('<div>',{class:"palette-content debug-dbgr-breakpoints"}).appendTo(breakpointPanel);
// var buttonGroup = $('<div>').css({position:"absolute", right: 0,top:0}).appendTo(breakPointContent);
// $('<button class="editor-button editor-button-small"><i class="fa fa-plus"></i> breakpoint</button>')
// .click(function(evt) {
// breakpointList.editableList('addItem',{});
// })
// .appendTo(buttonGroup);
var emptyItem = {};
breakpointList = $("<ol>").css({height: "200px"}).appendTo(breakPointContent).editableList({
addButton: false,
addItem: function(container,i,breakpoint) {
if (breakpoint === emptyItem) {
$('<div>',{class:"red-ui-search-empty"}).html('Double click on a port to add a breakpoint').appendTo(container);
return;
}
var currentCount = breakpointList.find('.debug-dbgr-breakpoint').length;
if (currentCount === 0) {
console.log("out with the old")
breakpointList.editableList('removeItem',emptyItem);
}
var id = "breakpoint_"+breakpoint.key;
var label = $('<label>',{for:id}).appendTo(container);
var cb = $('<input class="debug-dbgr-breakpoint" id="'+id+'" type="checkbox" checked></input>').appendTo(label);
$('<span>').html(RED.utils.getNodeLabel(breakpoint.node,breakpoint.node.id)).appendTo(label);
var workspace = RED.nodes.workspace(breakpoint.node.z);
if (!workspace) {
workspace = RED.nodes.subflow(breakpoint.node.z);
workspace = "subflow:"+workspace.name;
} else {
workspace = "flow:"+workspace.label;
}
var flowMeta = $('<div>',{class:"red-ui-search-result-node-flow"}).appendTo(label);
$('<div>').html(workspace).appendTo(flowMeta);
$('<div>').html((breakpoint.portType === 0?"output":"input")+" "+(breakpoint.portIndex+1)).appendTo(flowMeta);
$('<div>',{class:"red-ui-search-result-node-type"}).html(breakpoint.node.type).appendTo(label);
//+" : "+breakpoint.portType+" : "+breakpoint.portIndex).appendTo(label);
cb.on('change',function(evt) {
if ($(this).is(":checked")) {
delete breakpoint.disabled;
} else {
breakpoint.disabled = true;
}
breakpoint.node.dirty = true;
RED.view.redraw();
refreshBreakpointCount();
});
label.on('mouseenter',function() {
config.messageMouseEnter(breakpoint.node.id);
});
label.on('mouseleave',function() {
config.messageMouseLeave(breakpoint.node.id);
});
refreshBreakpointCount();
console.log(breakpointList.editableList('length'));
},
removeItem: function(breakpoint) {
refreshBreakpointCount();
if (breakpoint !== emptyItem) {
var currentCount = breakpointList.find('.debug-dbgr-breakpoint').length;
if (currentCount === 0) {
breakpointList.editableList('addItem',emptyItem);
}
}
}
});
breakpointList.editableList('addItem',emptyItem);
/************ Something else ************/
$('<div class="palette-category">'+
'<div class="palette-header">'+
'<i class="fa fa-angle-down expanded"></i>'+
'<span>Messages</span>'+
'</div>'+
'<div class="palette-content"></div>'+
'</div>').appendTo(dbgrPanel);
/************ Something else ************/
$('<div class="palette-category">'+
'<div class="palette-header">'+
'<i class="fa fa-angle-down expanded"></i>'+
'<span>Context</span>'+
'</div>'+
'<div class="palette-content"></div>'+
'</div>').appendTo(dbgrPanel);
/****************************************/
try {
content.i18n();
} catch(err) {
console.log("TODO: i18n library support");
}
dbgrPanel.find(".palette-header").on('click', function(e) {
var icon = $(this).find("i");
var content = $(this).next();
if (icon.hasClass("expanded")) {
icon.removeClass("expanded");
content.slideUp();
} else {
icon.addClass("expanded");
content.slideDown();
}
});
filterDialog.find('#debug-tab-filter-all').on("click",function(e) {
toolbar.find('#debug-tab-filter-all').on("click",function(e) {
e.preventDefault();
if (filter) {
$(this).addClass('selected');
@@ -74,7 +226,7 @@ RED.debug = (function() {
refreshMessageList();
}
});
filterDialog.find('#debug-tab-filter-current').on("click",function(e) {
toolbar.find('#debug-tab-filter-current').on("click",function(e) {
e.preventDefault();
if (!filter) {
$(this).addClass('selected');
@@ -112,6 +264,50 @@ RED.debug = (function() {
}
})
toolbar.find('#debug-tab-view-debugger').on("click",function(e) {
if ($(messageList).is(":visible")) {
messageList.hide();
debuggerView.show();
toolbar.find(".debug-tab-view-console-buttons").hide();
toolbar.find(".debug-tab-view-dbgr-buttons").show();
$(this).siblings().removeClass('selected');
$(this).addClass('selected');
}
});
toolbar.find('#debug-tab-view-console').on("click",function(e) {
if ($(debuggerView).is(":visible")) {
messageList.show();
debuggerView.hide();
toolbar.find(".debug-tab-view-console-buttons").show();
toolbar.find(".debug-tab-view-dbgr-buttons").hide();
$(this).siblings().removeClass('selected');
$(this).addClass('selected');
}
});
toolbar.find('#debug-tab-dbgr-toggle').on("click",function(e) {
var i = $(this).find("i");
if (i.hasClass('fa-toggle-off')) {
i.addClass('fa-toggle-on');
i.removeClass('fa-toggle-off');
dbgrPanel.show();
dbgrDisabled.hide();
debuggerEnabled = true;
} else {
i.addClass('fa-toggle-off');
i.removeClass('fa-toggle-on');
dbgrPanel.hide();
dbgrDisabled.show();
debuggerEnabled = false;
}
RED.view.redraw(true);
})
toolbar.find("#debug-tab-clear").click(function(e) {
e.preventDefault();
$(".debug-message").remove();
@@ -277,9 +473,72 @@ RED.debug = (function() {
messageList.scrollTop(sbc.scrollHeight);
}
}
var breakpoints = {};
function getBreakpointKey(node,portType,portIndex) {
return (node.id+"_"+portType+"_"+portIndex).replace(/\./,"_");
}
function toggleBreakpoint(node,portType,portIndex) {
if (!debuggerEnabled) {
return false;
}
var key = getBreakpointKey(node,portType,portIndex);
if (breakpoints.hasOwnProperty(key)) {
node.dirty = true;
if (breakpoints[key].disabled) {
delete breakpoints[key].disabled;
$("#breakpoint_"+key).prop('checked',true);
refreshBreakpointCount();
return true;
} else {
breakpointList.editableList('removeItem',breakpoints[key]);
delete breakpoints[key];
return false;
}
} else {
breakpoints[key] = {
key: key,
node: node,
portType: portType,
portIndex: portIndex
}
breakpointList.editableList('addItem',breakpoints[key]);
node.dirty = true;
return true;
}
}
function checkBreakpoint(node,portType,portIndex) {
var key = getBreakpointKey(node,portType,portIndex);
return debuggerEnabled && breakpoints.hasOwnProperty(key) && !breakpoints[key].disabled;
}
function refreshBreakpointCount() {
var total = 0;
var checked = 0;
breakpointList.find('.debug-dbgr-breakpoint').each(function() {
total++;
if ($(this).is(":checked")) {
checked++;
}
});
var label = "";
if (total > 0) {
if (total === checked) {
label = total;
} else {
label = checked+"/"+total;
}
}
$("#debug-dbgr-breakpoint-count").html(label);
}
return {
init: init,
refreshMessageList:refreshMessageList,
handleDebugMessage: handleDebugMessage
handleDebugMessage: handleDebugMessage,
toggleBreakpoint: toggleBreakpoint,
checkBreakpoint: checkBreakpoint
}
})();

7
red.js
View File

@@ -37,7 +37,8 @@ var knownOpts = {
"userDir":[path],
"port": Number,
"v": Boolean,
"help": Boolean
"help": Boolean,
"debug": Boolean
};
var shortHands = {
"s":["--settings"],
@@ -120,6 +121,10 @@ if (parsedArgs.v) {
settings.verbose = true;
}
if (parsedArgs.debug) {
settings.enableDebugger = true;
}
if (settings.https) {
server = https.createServer(settings.https,function(req,res){app(req,res);});
} else {

View File

@@ -22,12 +22,14 @@ var redUtil = require("../util");
var Log = require("../log");
var context = require("./context");
var flows = require("./flows");
var router = require("./router");
function Node(n) {
this.id = n.id;
this.type = n.type;
this.z = n.z;
this._closeCallbacks = [];
this._config = n;
if (n.name) {
this.name = n.name;
@@ -41,28 +43,10 @@ function Node(n) {
util.inherits(Node, EventEmitter);
Node.prototype.updateWires = function(wires) {
//console.log("UPDATE",this.id);
router.add(this,wires);
this.wires = wires || [];
delete this._wire;
var wc = 0;
this.wires.forEach(function(w) {
wc+=w.length;
});
this._wireCount = wc;
if (wc === 0) {
// With nothing wired to the node, no-op send
this.send = function(msg) {}
} else {
this.send = Node.prototype.send;
if (this.wires.length === 1 && this.wires[0].length === 1) {
// Single wire, so we can shortcut the send when
// a single message is sent
this._wire = this.wires[0][0];
}
}
}
Node.prototype.context = function() {
if (!this._context) {
this._context = context.get(this._alias||this.id,this.z);
@@ -100,103 +84,22 @@ Node.prototype.close = function() {
}
if (promises.length > 0) {
return when.settle(promises).then(function() {
router.remove(this);
if (this._context) {
context.delete(this._alias||this.id,this.z);
context.delete(this._alias||this.id,this.z);
}
});
} else {
router.remove(this);
if (this._context) {
context.delete(this._alias||this.id,this.z);
context.delete(this._alias||this.id,this.z);
}
return;
}
};
Node.prototype.send = function(msg) {
var msgSent = false;
var node;
if (msg === null || typeof msg === "undefined") {
return;
} else if (!util.isArray(msg)) {
if (this._wire) {
// A single message and a single wire on output 0
// TODO: pre-load flows.get calls - cannot do in constructor
// as not all nodes are defined at that point
if (!msg._msgid) {
msg._msgid = redUtil.generateId();
}
this.metric("send",msg);
node = flows.get(this._wire);
/* istanbul ignore else */
if (node) {
node.receive(msg);
}
return;
} else {
msg = [msg];
}
}
var numOutputs = this.wires.length;
// Build a list of send events so that all cloning is done before
// any calls to node.receive
var sendEvents = [];
var sentMessageId = null;
// for each output of node eg. [msgs to output 0, msgs to output 1, ...]
for (var i = 0; i < numOutputs; i++) {
var wires = this.wires[i]; // wires leaving output i
/* istanbul ignore else */
if (i < msg.length) {
var msgs = msg[i]; // msgs going to output i
if (msgs !== null && typeof msgs !== "undefined") {
if (!util.isArray(msgs)) {
msgs = [msgs];
}
var k = 0;
// for each recipent node of that output
for (var j = 0; j < wires.length; j++) {
node = flows.get(wires[j]); // node at end of wire j
if (node) {
// for each msg to send eg. [[m1, m2, ...], ...]
for (k = 0; k < msgs.length; k++) {
var m = msgs[k];
if (m !== null && m !== undefined) {
/* istanbul ignore else */
if (!sentMessageId) {
sentMessageId = m._msgid;
}
if (msgSent) {
var clonedmsg = redUtil.cloneMessage(m);
sendEvents.push({n:node,m:clonedmsg});
} else {
sendEvents.push({n:node,m:m});
msgSent = true;
}
}
}
}
}
}
}
}
/* istanbul ignore else */
if (!sentMessageId) {
sentMessageId = redUtil.generateId();
}
this.metric("send",{_msgid:sentMessageId});
for (i=0;i<sendEvents.length;i++) {
var ev = sendEvents[i];
/* istanbul ignore else */
if (!ev.m._msgid) {
ev.m._msgid = sentMessageId;
}
ev.n.receive(ev.m);
}
router.send(this,msg);
};
Node.prototype.receive = function(msg) {

View File

@@ -0,0 +1,262 @@
/**
* 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.
* 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 readline = require('readline');
var rl;
var runtime;
var components;
var breakpoints = {};
var enabled = false;
function enable() {
enabled = true;
}
function disable() {
enabled = false;
}
function formatNode(n) {
return n.id+" ["+n.type+"]"+(n.name?" "+n.name:"");
}
function listNodes() {
var flowIds = runtime.nodes.listFlows();
flowIds.forEach(function(id) {
var flow = runtime.nodes.getFlow(id);
console.log(flow.id+" [flow]"+(flow.label?" "+flow.label:""));
if (flow.subflows) {
flow.subflows.forEach(function(sf) {
console.log(" - "+formatNode(sf));
sf.configs.forEach(function(n) {
console.log(" - "+formatNode(n));
})
sf.nodes.forEach(function(n) {
console.log(" - "+formatNode(n));
})
})
}
if (flow.configs) {
flow.configs.forEach(function(n) {
console.log(" - "+formatNode(n));
})
}
if (flow.nodes) {
flow.nodes.forEach(function(n) {
console.log(" - "+formatNode(n));
})
}
})
}
function listNode(id) {
console.log(id);
var node = runtime.nodes.getNode(id);
if (node) {
console.log(node);
}
}
function listFlows() {
}
function handleAddBreakpoint(args) {
var valid = false;
var bp = {};
if (args.length > 0) {
bp.node = args[0];
valid = true;
if (args.length > 1) {
if (args[1]==='i' || args[1]==='o') {
bp.type = args[1];
if (args.length > 2) {
bp.index = args[2];
}
} else {
valid = false;
}
}
}
if (valid) {
var id = addBreakpoint(bp);
console.log("Added breakpoint",id?id:"");
} else {
console.log("break <nodeid> [i|o] <index>");
}
}
function handleRemoveBreakpoint(args) {
if (args.length === 0) {
breakpoints = {};
console.log("Cleared all breakpoints");
return;
}
if (args.length === 1 && breakpoints.hasOwnProperty(args[0])) {
delete breakpoints[args[0]];
console.log("Cleared all breakpoints on node",args[0]);
return;
}
if (args.length === 2 && breakpoints.hasOwnProperty(args[0]) && (args[1] === 'i' || args[1] === 'o')) {
var c = Object.keys(breakpoints[args[0]][args[1]]).length;
breakpoints[args[0]][args[1]] = {};
breakpoints[args[0]].c -= c;
if (breakpoints[args[0]].c === 0) {
delete breakpoints[args[0]];
}
console.log("Cleared all",(args[1]==='i'?'input':'output'),"breakpoints on node",args[0]);
return;
}
if (args.length === 3 && breakpoints.hasOwnProperty(args[0]) && (args[1] === 'i' || args[1] === 'o')) {
var id = args.join(":");
if (removeBreakpoint(args.join(":"))) {
console.log("Cleared breakpoint",id);
}
}
}
function handleInfo(args) {
if (args[0] === 'breakpoints') {
console.log(breakpoints);
}
}
function init(_runtime,_components) {
runtime = _runtime;
components = _components;
breakpoints = {};
enabled = true;
rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: 'nrdb> '
});
rl.on('line', function(input) {
input = input.trim();
var parts = input.split(/\s+/);
var cmd = parts[0];
var args = parts.slice(1)
switch(cmd) {
case 'pause':
case 'p':
console.log("pausing");
components.router.pause();
break;
case 'continue':
case 'c':
console.log("resuming");
components.router.resume();
break;
case 'nodes': listNodes(); break;
case 'flows': listFlows(); break;
case 'node': if (args.length === 1) { listNode(args[0]); } break;
case 'break': handleAddBreakpoint(args); break;
case 'delete': handleRemoveBreakpoint(args); break;
case 'info': handleInfo(args); break;
}
var m = /^node\s+([^\s]+)$/.exec(input);
if (m) {
listNode(m[1]);
}
rl.prompt();
}).on('close', function() {
console.log("exiting nrdb");
runtime.stop().then(function() {
process.exit(0);
});
})
rl.setPrompt('nrdb> ');
rl.prompt();
}
/*
* {
* node: id,
* type: 'i/o',
* index: 0
* }
*/
function addBreakpoint(bp) {
if (!bp.hasOwnProperty('node')) {
return;
}
var node = runtime.nodes.getNode(bp.node);
if (!node || !node._config.hasOwnProperty('wires')) {
return;
}
if (!bp.hasOwnProperty('type')) {
addBreakpoint({node:bp.node,type:'i'});
addBreakpoint({node:bp.node,type:'o'});
} else if (!bp.hasOwnProperty('index')) {
if (bp.type === 'i') {
addBreakpoint({node:bp.node,type:'i',index:0});
} else {
for (var i=0;i<node._config.wires.length;i++) {
addBreakpoint({node:bp.node,type:'o',index:i});
}
}
return;
} else {
breakpoints[bp.node] = breakpoints[bp.node] || {c:0,i:{},o:{}};
if (!breakpoints[bp.node][bp.type][bp.index]) {
breakpoints[bp.node][bp.type][bp.index] = true;
breakpoints[bp.node].c++;
}
return bp.node+":"+bp.type+":"+bp.index;
}
}
function removeBreakpoint(id) {
var m = /^(.+):([io]):(\d+)$/.exec(id);
if (m) {
var node = m[1];
var portType = m[2];
var portIndex = m[3];
if (breakpoints[node]) {
delete breakpoints[node][portType][portIndex];
breakpoints[node].c--;
if (breakpoints[node].c === 0) {
delete breakpoints[node];
}
return true;
}
}
return false;
}
function getBreakpoints() {
return breakpoints;
}
function checkSendEvent(evt) {
return (enabled && (
(breakpoints.hasOwnProperty(evt.sourceNode.id) && breakpoints[evt.sourceNode.id].o[evt.sourcePort]) ||
(breakpoints.hasOwnProperty(evt.destinationNode.id) && breakpoints[evt.destinationNode.id].i[evt.destinationPort])
));
}
module.exports = {
init:init,
enable: enable,
disable: disable,
addBreakpoint:addBreakpoint,
removeBreakpoint: removeBreakpoint,
getBreakpoints: getBreakpoints,
checkSendEvent: checkSendEvent
}

View File

@@ -586,6 +586,10 @@ function removeFlow(id) {
});
}
function listFlows() {
return Object.keys(activeFlows);
}
module.exports = {
init: init,
@@ -633,6 +637,7 @@ module.exports = {
getFlow: getFlow,
updateFlow: updateFlow,
removeFlow: removeFlow,
listFlows: listFlows,
disableFlow:null,
enableFlow:null

View File

@@ -14,16 +14,14 @@
* limitations under the License.
**/
var when = require("when");
var path = require("path");
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 router = require("./router");
var log = require("../log");
var nrdb = require("./debugger");
var events = require("../events");
@@ -81,6 +79,10 @@ function init(runtime) {
flows.init(runtime);
registry.init(runtime);
context.init(runtime.settings);
router.init(runtime);
if (settings.enableDebugger) {
nrdb.init(runtime,{router:router});
}
}
function disableNode(id) {
@@ -138,6 +140,7 @@ module.exports = {
stopFlows: flows.stopFlows,
setFlows: flows.setFlows,
getFlows: flows.getFlows,
listFlows: flows.listFlows,
addFlow: flows.addFlow,
getFlow: flows.getFlow,
@@ -146,10 +149,12 @@ module.exports = {
// disableFlow: flows.disableFlow,
// enableFlow: flows.enableFlow,
// Credentials
addCredentials: credentials.add,
getCredentials: credentials.get,
deleteCredentials: credentials.delete,
getCredentialDefinition: credentials.getDefinition
// Router
};

View File

@@ -0,0 +1,151 @@
/**
* 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.
* 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 util = require("util");
var flows = require("../flows");
var redUtil = require("../../util");
var redDebugger = require("../debugger");
var runtime;
var routes = {};
var sendQueue = [];
var paused = false;
function init(_runtime) {
runtime = _runtime;
wires = {};
}
function pause() {
paused = true;
}
function resume() {
paused = false;
setImmediate(processSendEvent);
}
function add(sourceNode, wires) {
routes[sourceNode.id] = wires;
}
function remove(sourceNode) {
delete routes[sourceNode.id];
}
function processSendEvent() {
if (!paused) {
if (sendQueue.length > 0) {
var sendEvent = sendQueue.shift();
//console.log(ev.sourceNode.id+"["+ev.sourcePort+"] -> "+ev.destinationNode.id+"["+ev.destinationPort+"] : "+redDebugger.checkSendEvent(ev));
if (!sendEvent.triggered && redDebugger.checkSendEvent(sendEvent)) {
sendEvent.triggered = true;
sendQueue.unshift(sendEvent);
pause();
} else {
sendEvent.destinationNode.receive(sendEvent.msg);
}
}
if (!paused && sendQueue.length > 0) {
setImmediate(processSendEvent);
}
}
}
function send(sourceNode, msg) {
if (msg === null || typeof msg === "undefined") {
return;
} else if (!util.isArray(msg)) {
msg = [msg];
}
var node;
var msgSent = false;
var nodeWires = routes[sourceNode.id];
if (nodeWires) {
var numOutputs = nodeWires.length;
var sendEvents = [];
var sentMessageId = null;
// for each output of node eg. [msgs to output 0, msgs to output 1, ...]
for (var i = 0; i < numOutputs; i++) {
var wires = nodeWires[i];
/* istanbul ignore else */
if (i < msg.length) {
var msgs = msg[i]; // msgs going to output i
if (msgs !== null && typeof msgs !== "undefined") {
if (!util.isArray(msgs)) {
msgs = [msgs];
}
var k = 0;
// for each recipent node of that output
for (var j = 0; j < wires.length; j++) {
node = flows.get(wires[j]); // node at end of wire j
if (node) {
// for each msg to send eg. [[m1, m2, ...], ...]
for (k = 0; k < msgs.length; k++) {
var m = msgs[k];
if (m !== null && m !== undefined) {
/* istanbul ignore else */
if (!sentMessageId) {
sentMessageId = m._msgid;
}
var sendEvent = {
sourceNode: sourceNode,
sourcePort:i,
destinationNode:node,
destinationPort:0
}
if (msgSent) {
sendEvent.msg = redUtil.cloneMessage(m);
} else {
sendEvent.msg = m;
msgSent = true;
}
sendEvents.push(sendEvent);
}
}
}
}
}
}
}
/* istanbul ignore else */
if (!sentMessageId) {
sentMessageId = redUtil.generateId();
}
for (i=0;i<sendEvents.length;i++) {
var ev = sendEvents[i];
/* istanbul ignore else */
if (!ev.msg._msgid) {
ev.msg._msgid = sentMessageId;
}
sendQueue.push(ev);
}
sourceNode.metric("send",{_msgid:sentMessageId});
processSendEvent();
}
}
module.exports = {
init:init,
add:add,
remove: remove,
send:send,
pause:pause,
resume:resume
}

View File

@@ -149,11 +149,18 @@ describe('Node', function() {
});
describe('#send', function() {
var flowGet;
afterEach(function() {
if (flowGet && flowGet.restore) {
flowGet.restore();
flowGet = null;
}
});
it('emits a single message', function(done) {
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]});
var n2 = new RedNode({id:'n2',type:'abc'});
var flowGet = sinon.stub(flows,"get",function(id) {
flowGet = sinon.stub(flows,"get",function(id) {
return {'n1':n1,'n2':n2}[id];
});
var message = {payload:"hello world"};
@@ -172,7 +179,7 @@ describe('Node', function() {
it('emits multiple messages on a single output', function(done) {
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]});
var n2 = new RedNode({id:'n2',type:'abc'});
var flowGet = sinon.stub(flows,"get",function(id) {
flowGet = sinon.stub(flows,"get",function(id) {
return {'n1':n1,'n2':n2}[id];
});
@@ -206,7 +213,7 @@ describe('Node', function() {
var n3 = new RedNode({id:'n3',type:'abc'});
var n4 = new RedNode({id:'n4',type:'abc'});
var n5 = new RedNode({id:'n5',type:'abc'});
var flowGet = sinon.stub(flows,"get",function(id) {
flowGet = sinon.stub(flows,"get",function(id) {
return {'n1':n1,'n2':n2,'n3':n3,'n4':n4,'n5':n5}[id];
});
@@ -264,7 +271,7 @@ describe('Node', function() {
it('emits no messages', function(done) {
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n2']]});
var n2 = new RedNode({id:'n2',type:'abc'});
var flowGet = sinon.stub(flows,"get",function(id) {
flowGet = sinon.stub(flows,"get",function(id) {
return {'n1':n1,'n2':n2}[id];
});
@@ -283,7 +290,7 @@ describe('Node', function() {
it('emits messages ignoring non-existent nodes', function(done) {
var n1 = new RedNode({id:'n1',type:'abc',wires:[['n9'],['n2']]});
var n2 = new RedNode({id:'n2',type:'abc'});
var flowGet = sinon.stub(flows,"get",function(id) {
flowGet = sinon.stub(flows,"get",function(id) {
return {'n1':n1,'n2':n2}[id];
});
@@ -307,7 +314,7 @@ describe('Node', function() {
var n1 = new RedNode({id:'n1',type:'abc',wires:[[['n2'],['n3']]]});
var n2 = new RedNode({id:'n2',type:'abc'});
var n3 = new RedNode({id:'n3',type:'abc'});
var flowGet = sinon.stub(flows,"get",function(id) {
flowGet = sinon.stub(flows,"get",function(id) {
return {'n1':n1,'n2':n2,'n3':n3}[id];
});
@@ -349,7 +356,7 @@ describe('Node', function() {
});
it("logs the uuid for all messages sent", function(done) {
var flowGet = sinon.stub(flows,"get",function(id) {
flowGet = sinon.stub(flows,"get",function(id) {
return {'n1':sender,'n2':receiver1,'n3':receiver2}[id];
});
var logHandler = {

View File

@@ -0,0 +1,79 @@
/**
* 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.
* 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 should = require("should");
var sinon = require('sinon');
var RedNode = require("../../../../../red/runtime/nodes/Node");
var router = require("../../../../../red/runtime/nodes/router");
var flows = require("../../../../../red/runtime/nodes/flows");
describe('Router', function() {
var flowGet;
afterEach(function() {
if (flowGet && flowGet.restore) {
flowGet.restore();
flowGet = null;
}
})
describe('#add',function() {
it('adds a route for a node', function(done) {
var senderNode = {id:'123',metric:function(){}};
var receiver = sinon.stub();
flowGet = sinon.stub(flows,"get",function(id) {
if (id === '456') {
return {receive:receiver};
}
return null;
});
router.send(senderNode,{});
flowGet.called.should.be.false();
router.add(senderNode,[['456']]);
router.send(senderNode,{});
flowGet.called.should.be.true();
receiver.called.should.be.true();
done();
});
})
describe('#remove',function() {
it('removes a route for a node', function(done) {
var senderNode = {id:'123',metric:function(){}};
var receiver = sinon.stub();
flowGet = sinon.stub(flows,"get",function(id) {
if (id === '456') {
return {receive:receiver};
}
return null;
});
router.add(senderNode,[['456']]);
router.send(senderNode,{});
flowGet.called.should.be.true();
receiver.called.should.be.true();
flowGet.reset();
receiver.reset();
router.remove(senderNode);
router.send(senderNode,{});
flowGet.called.should.be.false();
done();
});
})
})