1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge remote-tracking branch 'upstream/0.19' into 0.19

This commit is contained in:
Hideki Nakamura 2018-07-05 11:44:13 -07:00
commit 6104bb98f1
105 changed files with 5906 additions and 1740 deletions

View File

@ -144,10 +144,12 @@ module.exports = function(grunt) {
"editor/js/ui/keyboard.js", "editor/js/ui/keyboard.js",
"editor/js/ui/workspaces.js", "editor/js/ui/workspaces.js",
"editor/js/ui/view.js", "editor/js/ui/view.js",
"editor/js/ui/view-navigator.js",
"editor/js/ui/sidebar.js", "editor/js/ui/sidebar.js",
"editor/js/ui/palette.js", "editor/js/ui/palette.js",
"editor/js/ui/tab-info.js", "editor/js/ui/tab-info.js",
"editor/js/ui/tab-config.js", "editor/js/ui/tab-config.js",
"editor/js/ui/tab-context.js",
"editor/js/ui/palette-editor.js", "editor/js/ui/palette-editor.js",
"editor/js/ui/editor.js", "editor/js/ui/editor.js",
"editor/js/ui/tray.js", "editor/js/ui/tray.js",

BIN
editor/icons/file-in.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

BIN
editor/icons/file-out.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

View File

@ -10,6 +10,7 @@
"ctrl-g i": "core:show-info-tab", "ctrl-g i": "core:show-info-tab",
"ctrl-g d": "core:show-debug-tab", "ctrl-g d": "core:show-debug-tab",
"ctrl-g c": "core:show-config-tab", "ctrl-g c": "core:show-config-tab",
"ctrl-g x": "core:show-context-tab",
"ctrl-e": "core:show-export-dialog", "ctrl-e": "core:show-export-dialog",
"ctrl-i": "core:show-import-dialog", "ctrl-i": "core:show-import-dialog",
"ctrl-space": "core:toggle-sidebar", "ctrl-space": "core:toggle-sidebar",

View File

@ -153,13 +153,13 @@
loadFlows(function() { loadFlows(function() {
var project = RED.projects.getActiveProject(); var project = RED.projects.getActiveProject();
var message = { var message = {
"change-branch":"Change to local branch '"+project.git.branches.local+"'", "change-branch": RED._("notification.project.change-branch", {project: project.git.branches.local}),
"merge-abort":"Git merge aborted", "merge-abort": RED._("notification.project.merge-abort"),
"loaded":"Project '"+msg.project+"' loaded", "loaded": RED._("notification.project.loaded", {project: msg.project}),
"updated":"Project '"+msg.project+"' updated", "updated": RED._("notification.project.updated", {project: msg.project}),
"pull":"Project '"+msg.project+"' reloaded", "pull": RED._("notification.project.pull", {project: msg.project}),
"revert": "Project '"+msg.project+"' reloaded", "revert": RED._("notification.project.revert", {project: msg.project}),
"merge-complete":"Git merge completed" "merge-complete": RED._("notification.project.merge-complete")
}[msg.action]; }[msg.action];
RED.notify("<p>"+message+"</p>"); RED.notify("<p>"+message+"</p>");
RED.sidebar.info.refresh() RED.sidebar.info.refresh()
@ -183,7 +183,7 @@
if (!!RED.projects.getActiveProject()) { if (!!RED.projects.getActiveProject()) {
options.buttons = [ options.buttons = [
{ {
text: "Manage project dependencies", text: RED._("notification.label.manage-project-dep"),
click: function() { click: function() {
persistentNotifications[notificationId].hideNotification(); persistentNotifications[notificationId].hideNotification();
RED.projects.settings.show('deps'); RED.projects.settings.show('deps');
@ -194,7 +194,7 @@
} else { } else {
options.buttons = [ options.buttons = [
{ {
text: "Close", text: RED._("common.label.close"),
click: function() { click: function() {
persistentNotifications[notificationId].hideNotification(); persistentNotifications[notificationId].hideNotification();
} }
@ -207,7 +207,7 @@
if (RED.user.hasPermission("projects.write")) { if (RED.user.hasPermission("projects.write")) {
options.buttons = [ options.buttons = [
{ {
text: "Setup credentials", text: RED._("notification.label.setup-cred"),
click: function() { click: function() {
persistentNotifications[notificationId].hideNotification(); persistentNotifications[notificationId].hideNotification();
RED.projects.showCredentialsPrompt(); RED.projects.showCredentialsPrompt();
@ -218,7 +218,7 @@
} else { } else {
options.buttons = [ options.buttons = [
{ {
text: "Close", text: RED._("common.label.close"),
click: function() { click: function() {
persistentNotifications[notificationId].hideNotification(); persistentNotifications[notificationId].hideNotification();
} }
@ -229,7 +229,7 @@
if (RED.user.hasPermission("projects.write")) { if (RED.user.hasPermission("projects.write")) {
options.buttons = [ options.buttons = [
{ {
text: "Setup project files", text: RED._("notification.label.setup-project"),
click: function() { click: function() {
persistentNotifications[notificationId].hideNotification(); persistentNotifications[notificationId].hideNotification();
RED.projects.showFilesPrompt(); RED.projects.showFilesPrompt();
@ -241,7 +241,7 @@
if (RED.user.hasPermission("projects.write")) { if (RED.user.hasPermission("projects.write")) {
options.buttons = [ options.buttons = [
{ {
text: "Create default package file", text: RED._("notification.label.create-default-package"),
click: function() { click: function() {
persistentNotifications[notificationId].hideNotification(); persistentNotifications[notificationId].hideNotification();
RED.projects.createDefaultPackageFile(); RED.projects.createDefaultPackageFile();
@ -253,13 +253,13 @@
if (RED.user.hasPermission("projects.write")) { if (RED.user.hasPermission("projects.write")) {
options.buttons = [ options.buttons = [
{ {
text: "No thanks", text: RED._("notification.label.no-thanks"),
click: function() { click: function() {
persistentNotifications[notificationId].hideNotification(); persistentNotifications[notificationId].hideNotification();
} }
}, },
{ {
text: "Create default project files", text: RED._("notification.label.create-default-project"),
click: function() { click: function() {
persistentNotifications[notificationId].hideNotification(); persistentNotifications[notificationId].hideNotification();
RED.projects.createDefaultFileSet(); RED.projects.createDefaultFileSet();
@ -273,7 +273,7 @@
if (RED.user.hasPermission("projects.write")) { if (RED.user.hasPermission("projects.write")) {
options.buttons = [ options.buttons = [
{ {
text: "Show merge conflicts", text: RED._("notification.label.show-merge-conflicts"),
click: function() { click: function() {
persistentNotifications[notificationId].hideNotification(); persistentNotifications[notificationId].hideNotification();
RED.sidebar.versionControl.showLocalChanges(); RED.sidebar.versionControl.showLocalChanges();
@ -382,10 +382,10 @@
function loadEditor() { function loadEditor() {
var menuOptions = []; var menuOptions = [];
if (RED.settings.theme("projects.enabled",false)) { if (RED.settings.theme("projects.enabled",false)) {
menuOptions.push({id:"menu-item-projects-menu",label:"Projects",options:[ menuOptions.push({id:"menu-item-projects-menu",label:RED._("menu.label.projects"),options:[
{id:"menu-item-projects-new",label:"New",disabled:false,onselect:"core:new-project"}, {id:"menu-item-projects-new",label:RED._("menu.label.projects-new"),disabled:false,onselect:"core:new-project"},
{id:"menu-item-projects-open",label:"Open",disabled:false,onselect:"core:open-project"}, {id:"menu-item-projects-open",label:RED._("menu.label.projects-open"),disabled:false,onselect:"core:open-project"},
{id:"menu-item-projects-settings",label:"Project Settings",disabled:false,onselect:"core:show-project-settings"} {id:"menu-item-projects-settings",label:RED._("menu.label.projects-settings"),disabled:false,onselect:"core:show-project-settings"}
]}); ]});
} }
@ -410,8 +410,8 @@
{id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:"core:show-import-dialog"}, {id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:"core:show-import-dialog"},
{id:"menu-item-import-library",label:RED._("menu.label.library"),options:[]} {id:"menu-item-import-library",label:RED._("menu.label.library"),options:[]}
]}); ]});
menuOptions.push({id:"menu-item-export",label:RED._("menu.label.export"),disabled:true,options:[ menuOptions.push({id:"menu-item-export",label:RED._("menu.label.export"),options:[
{id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),disabled:true,onselect:"core:show-export-dialog"}, {id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),onselect:"core:show-export-dialog"},
{id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:"core:library-export"} {id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:"core:library-export"}
]}); ]});
menuOptions.push(null); menuOptions.push(null);

View File

@ -133,7 +133,7 @@ RED.nodes = (function() {
registerNodeType: function(nt,def) { registerNodeType: function(nt,def) {
nodeDefinitions[nt] = def; nodeDefinitions[nt] = def;
def.type = nt; def.type = nt;
if (def.category != "subflows") { if (nt.substring(0,8) != "subflow:") {
def.set = nodeSets[typeToId[nt]]; def.set = nodeSets[typeToId[nt]];
nodeSets[typeToId[nt]].added = true; nodeSets[typeToId[nt]].added = true;
nodeSets[typeToId[nt]].enabled = true; nodeSets[typeToId[nt]].enabled = true;
@ -356,7 +356,7 @@ RED.nodes = (function() {
defaults:{name:{value:""}}, defaults:{name:{value:""}},
info: sf.info, info: sf.info,
icon: function() { return sf.icon||"subflow.png" }, icon: function() { return sf.icon||"subflow.png" },
category: "subflows", category: sf.category || "subflows",
inputs: sf.in.length, inputs: sf.in.length,
outputs: sf.out.length, outputs: sf.out.length,
color: "#da9", color: "#da9",
@ -519,6 +519,7 @@ RED.nodes = (function() {
node.type = n.type; node.type = n.type;
node.name = n.name; node.name = n.name;
node.info = n.info; node.info = n.info;
node.category = n.category;
node.in = []; node.in = [];
node.out = []; node.out = [];

View File

@ -276,10 +276,21 @@ RED.clipboard = (function() {
if (typeof value !== "string" ) { if (typeof value !== "string" ) {
value = JSON.stringify(value, function(key,value) { value = JSON.stringify(value, function(key,value) {
if (value !== null && typeof value === 'object') { if (value !== null && typeof value === 'object') {
if (value.__encoded__ && value.hasOwnProperty('data') && value.hasOwnProperty('length')) { if (value.__encoded__) {
if (value.hasOwnProperty('data') && value.hasOwnProperty('length')) {
truncated = value.data.length !== value.length; truncated = value.data.length !== value.length;
return value.data; return value.data;
} }
if (value.type === 'function' || value.type === 'internal') {
return undefined
}
if (value.type === 'number') {
// Handle NaN and Infinity - they are not permitted
// in JSON. We can either substitute with a String
// representation or null
return null;
}
}
} }
return value; return value;
}); });
@ -309,18 +320,6 @@ RED.clipboard = (function() {
$('<input type="text" id="clipboard-hidden">').appendTo("body"); $('<input type="text" id="clipboard-hidden">').appendTo("body");
RED.events.on("view:selection-changed",function(selection) {
if (!selection.nodes) {
RED.menu.setDisabled("menu-item-export",true);
RED.menu.setDisabled("menu-item-export-clipboard",true);
RED.menu.setDisabled("menu-item-export-library",true);
} else {
RED.menu.setDisabled("menu-item-export",false);
RED.menu.setDisabled("menu-item-export-clipboard",false);
RED.menu.setDisabled("menu-item-export-library",false);
}
});
RED.actions.add("core:show-export-dialog",exportNodes); RED.actions.add("core:show-export-dialog",exportNodes);
RED.actions.add("core:show-import-dialog",importNodes); RED.actions.add("core:show-import-dialog",importNodes);

View File

@ -19,12 +19,14 @@ RED.popover = (function() {
"default": { "default": {
top: 10, top: 10,
leftRight: 17, leftRight: 17,
leftLeft: 25 leftLeft: 25,
leftBottom: 8,
}, },
"small": { "small": {
top: 5, top: 5,
leftRight: 17, leftRight: 17,
leftLeft: 16 leftLeft: 16,
leftBottom: 3,
} }
} }
function createPopover(options) { function createPopover(options) {
@ -46,29 +48,39 @@ RED.popover = (function() {
var openPopup = function(instant) { var openPopup = function(instant) {
if (active) { if (active) {
div = $('<div class="red-ui-popover red-ui-popover-'+direction+'"></div>').appendTo("body"); div = $('<div class="red-ui-popover red-ui-popover-'+direction+'"></div>');
if (size !== "default") { if (size !== "default") {
div.addClass("red-ui-popover-size-"+size); div.addClass("red-ui-popover-size-"+size);
} }
if (typeof content === 'function') { if (typeof content === 'function') {
content.call(res).appendTo(div); var result = content.call(res);
if (result === null) {
return;
}
if (typeof result === 'string') {
div.text(result);
} else {
div.append(result);
}
} else { } else {
div.html(content); div.html(content);
} }
if (width !== "auto") { if (width !== "auto") {
div.width(width); div.width(width);
} }
div.appendTo("body");
var targetPos = target.offset(); var targetPos = target.offset();
var targetWidth = target.width(); var targetWidth = target.outerWidth();
var targetHeight = target.height(); var targetHeight = target.outerHeight();
var divHeight = div.height(); var divHeight = div.height();
var divWidth = div.width(); var divWidth = div.width();
if (direction === 'right') { if (direction === 'right') {
div.css({top: targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top,left:targetPos.left+targetWidth+deltaSizes[size].leftRight}); div.css({top: targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top,left:targetPos.left+targetWidth+deltaSizes[size].leftRight});
} else if (direction === 'left') { } else if (direction === 'left') {
div.css({top: targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top,left:targetPos.left-deltaSizes[size].leftLeft-divWidth}); div.css({top: targetPos.top+targetHeight/2-divHeight/2-deltaSizes[size].top,left:targetPos.left-deltaSizes[size].leftLeft-divWidth});
} else if (direction === 'bottom') {
div.css({top: targetPos.top+targetHeight+deltaSizes[size].top,left:targetPos.left+targetWidth/2-divWidth/2 - deltaSizes[size].leftBottom});
} }
if (instant) { if (instant) {
div.show(); div.show();
@ -143,7 +155,17 @@ RED.popover = (function() {
} }
return { return {
create: createPopover create: createPopover,
tooltip: function(target,content) {
RED.popover.create({
target:target,
trigger: "hover",
size: "small",
direction: "bottom",
content: content,
delay: { show: 550, hide: 10 }
});
}
} }
})(); })();

View File

@ -19,8 +19,10 @@
RED.tabs = (function() { RED.tabs = (function() {
function createTabs(options) { function createTabs(options) {
var tabs = {}; var tabs = {};
var pinnedTabsCount = 0;
var currentTabWidth; var currentTabWidth;
var currentActiveTabWidth = 0; var currentActiveTabWidth = 0;
var collapsibleMenu;
var ul = options.element || $("#"+options.id); var ul = options.element || $("#"+options.id);
var wrapper = ul.wrap( "<div>" ).parent(); var wrapper = ul.wrap( "<div>" ).parent();
@ -50,6 +52,55 @@ RED.tabs = (function() {
scrollRight = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-right"><a href="#" style="display:none;"><i class="fa fa-caret-right"></i></a></div>').appendTo(wrapper).find("a"); scrollRight = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-right"><a href="#" style="display:none;"><i class="fa fa-caret-right"></i></a></div>').appendTo(wrapper).find("a");
scrollRight.on('mousedown',function(evt) { scrollEventHandler(evt,'+=150') }).on('click',function(evt){ evt.preventDefault();}); scrollRight.on('mousedown',function(evt) { scrollEventHandler(evt,'+=150') }).on('click',function(evt){ evt.preventDefault();});
} }
if (options.collapsible) {
// var dropDown = $('<div>',{class:"red-ui-tabs-select"}).appendTo(wrapper);
// ul.hide();
wrapper.addClass("red-ui-tabs-collapsible");
var collapsedButtonsRow = $('<div class="red-ui-tab-link-buttons"></div>').appendTo(wrapper);
var selectButton = $('<a href="#"><i class="fa fa-caret-down"></i></a>').appendTo(collapsedButtonsRow);
selectButton.addClass("red-ui-tab-link-button-menu")
selectButton.click(function(evt) {
evt.preventDefault();
if (!collapsibleMenu) {
var pinnedOptions = [];
var options = [];
ul.children().each(function(i,el) {
var id = $(el).data('tabId');
var opt = {
id:"red-ui-tabs-menu-option-"+id,
label: tabs[id].name,
onselect: function() {
activateTab(id);
}
};
if (tabs[id].pinned) {
pinnedOptions.push(opt);
} else {
options.push(opt);
}
});
options = pinnedOptions.concat(options);
collapsibleMenu = RED.menu.init({id:"debug-message-option-menu",options: options});
collapsibleMenu.css({
position: "absolute"
})
collapsibleMenu.on('mouseleave', function(){ $(this).hide() });
collapsibleMenu.on('mouseup', function() { $(this).hide() });
collapsibleMenu.appendTo("body");
}
var elementPos = selectButton.offset();
collapsibleMenu.css({
top: (elementPos.top+selectButton.height()-20)+"px",
left: (elementPos.left - collapsibleMenu.width() + selectButton.width())+"px"
})
collapsibleMenu.toggle();
})
}
function scrollEventHandler(evt,dir) { function scrollEventHandler(evt,dir) {
evt.preventDefault(); evt.preventDefault();
if ($(this).hasClass('disabled')) { if ($(this).hasClass('disabled')) {
@ -118,6 +169,9 @@ RED.tabs = (function() {
ul.children().removeClass("active"); ul.children().removeClass("active");
ul.children().css({"transition": "width 100ms"}); ul.children().css({"transition": "width 100ms"});
link.parent().addClass("active"); link.parent().addClass("active");
var parentId = link.parent().attr('id');
wrapper.find(".red-ui-tab-link-button").removeClass("active selected");
$("#"+parentId+"-link-button").addClass("active selected");
if (options.scrollable) { if (options.scrollable) {
var pos = link.parent().position().left; var pos = link.parent().position().left;
if (pos-21 < 0) { if (pos-21 < 0) {
@ -155,6 +209,30 @@ RED.tabs = (function() {
var tabs = ul.find("li.red-ui-tab"); var tabs = ul.find("li.red-ui-tab");
var width = wrapper.width(); var width = wrapper.width();
var tabCount = tabs.size(); var tabCount = tabs.size();
var tabWidth;
if (options.collapsible) {
tabWidth = width - collapsedButtonsRow.width()-10;
if (tabWidth < 198) {
var delta = 198 - tabWidth;
var b = collapsedButtonsRow.find("a:last").prev();
while (b.is(":not(:visible)")) {
b = b.prev();
}
if (!b.hasClass("red-ui-tab-link-button-pinned")) {
b.hide();
}
tabWidth = width - collapsedButtonsRow.width()-10;
} else {
var space = width - 198 - collapsedButtonsRow.width();
if (space > 40) {
collapsedButtonsRow.find("a:not(:visible):first").show();
tabWidth = width - collapsedButtonsRow.width()-10;
}
}
tabs.css({width:tabWidth});
} else {
var tabWidth = (width-12-(tabCount*6))/tabCount; var tabWidth = (width-12-(tabCount*6))/tabCount;
currentTabWidth = (100*tabWidth/width)+"%"; currentTabWidth = (100*tabWidth/width)+"%";
currentActiveTabWidth = currentTabWidth+"%"; currentActiveTabWidth = currentTabWidth+"%";
@ -175,6 +253,10 @@ RED.tabs = (function() {
currentActiveTabWidth = 0; currentActiveTabWidth = 0;
} }
} }
if (options.collapsible) {
console.log(currentTabWidth);
}
tabs.css({width:currentTabWidth}); tabs.css({width:currentTabWidth});
if (tabWidth < 50) { if (tabWidth < 50) {
ul.find(".red-ui-tab-close").hide(); ul.find(".red-ui-tab-close").hide();
@ -191,6 +273,7 @@ RED.tabs = (function() {
ul.find("li.red-ui-tab.active .red-ui-tab-icon").show(); ul.find("li.red-ui-tab.active .red-ui-tab-icon").show();
ul.find("li.red-ui-tab.active .red-ui-tab-label").css({paddingLeft:""}) ul.find("li.red-ui-tab.active .red-ui-tab-label").css({paddingLeft:""})
} }
}
} }
@ -210,11 +293,15 @@ RED.tabs = (function() {
activateTab(tab.find("a")); activateTab(tab.find("a"));
} }
li.remove(); li.remove();
if (tabs[id].pinned) {
pinnedTabsCount--;
}
if (options.onremove) { if (options.onremove) {
options.onremove(tabs[id]); options.onremove(tabs[id]);
} }
delete tabs[id]; delete tabs[id];
updateTabWidths(); updateTabWidths();
collapsibleMenu = null;
} }
return { return {
@ -223,13 +310,48 @@ RED.tabs = (function() {
var li = $("<li/>",{class:"red-ui-tab"}).appendTo(ul); var li = $("<li/>",{class:"red-ui-tab"}).appendTo(ul);
li.attr('id',"red-ui-tab-"+(tab.id.replace(".","-"))); li.attr('id',"red-ui-tab-"+(tab.id.replace(".","-")));
li.data("tabId",tab.id); li.data("tabId",tab.id);
if (options.maximumTabWidth) {
li.css("maxWidth",options.maximumTabWidth+"px");
}
var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li); var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li);
if (tab.icon) { if (tab.icon) {
$('<img src="'+tab.icon+'" class="red-ui-tab-icon"/>').appendTo(link); $('<img src="'+tab.icon+'" class="red-ui-tab-icon"/>').appendTo(link);
} else if (tab.iconClass) {
$('<i>',{class:"red-ui-tab-icon "+tab.iconClass}).appendTo(link);
} }
var span = $('<span/>',{class:"bidiAware"}).text(tab.label).appendTo(link); var span = $('<span/>',{class:"bidiAware"}).text(tab.label).appendTo(link);
span.attr('dir', RED.text.bidi.resolveBaseTextDir(tab.label)); span.attr('dir', RED.text.bidi.resolveBaseTextDir(tab.label));
if (options.collapsible) {
li.addClass("red-ui-tab-pinned");
var pinnedLink = $('<a href="#'+tab.id+'" class="red-ui-tab-link-button"></a>');
if (tab.pinned) {
if (pinnedTabsCount === 0) {
pinnedLink.prependTo(collapsedButtonsRow)
} else {
pinnedLink.insertAfter(collapsedButtonsRow.find("a.red-ui-tab-link-button-pinned:last"));
}
} else {
pinnedLink.insertBefore(collapsedButtonsRow.find("a:last"));
}
pinnedLink.attr('id',li.attr('id')+"-link-button");
if (tab.iconClass) {
$('<i>',{class:tab.iconClass}).appendTo(pinnedLink);
} else {
$('<i>',{class:"fa fa-lemon-o"}).appendTo(pinnedLink);
}
pinnedLink.click(function(evt) {
evt.preventDefault();
activateTab(tab.id);
});
if (tab.pinned) {
pinnedLink.addClass("red-ui-tab-link-button-pinned");
pinnedTabsCount++;
}
RED.popover.tooltip($(pinnedLink), tab.name);
}
link.on("click",onTabClick); link.on("click",onTabClick);
link.on("dblclick",onTabDblClick); link.on("dblclick",onTabDblClick);
if (tab.closeable) { if (tab.closeable) {
@ -326,6 +448,7 @@ RED.tabs = (function() {
} }
}) })
} }
collapsibleMenu = null;
}, },
removeTab: removeTab, removeTab: removeTab,
activateTab: activateTab, activateTab: activateTab,

View File

@ -14,10 +14,43 @@
* limitations under the License. * limitations under the License.
**/ **/
(function($) { (function($) {
var contextParse = function(v) {
var parts = {};
var m = /^#:\((\S+?)\)::(.*)$/.exec(v);
if (m) {
parts.option = m[1];
parts.value = m[2];
} else {
parts.value = v;
parts.option = RED.settings.context.default;
}
return parts;
}
var contextExport = function(v,opt) {
if (!opt) {
return v;
}
var store = ((typeof opt === "string")?opt:opt.value)
if (store !== RED.settings.context.default) {
return "#:("+store+")::"+v;
} else {
return v;
}
}
var allOptions = { var allOptions = {
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression}, msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression},
flow: {value:"flow",label:"flow.",validate:RED.utils.validatePropertyExpression}, flow: {value:"flow",label:"flow.",hasValue:true,
global: {value:"global",label:"global.",validate:RED.utils.validatePropertyExpression}, options:[],
validate:RED.utils.validatePropertyExpression,
parse: contextParse,
export: contextExport
},
global: {value:"global",label:"global.",hasValue:true,
options:[],
validate:RED.utils.validatePropertyExpression,
parse: contextParse,
export: contextExport
},
str: {value:"str",label:"string",icon:"red/images/typedInput/az.png"}, 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]+)?$/}, 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"]}, bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.png",options:["true","false"]},
@ -76,31 +109,51 @@
} }
}) })
} }
},
env: {
value: "env",
label: "env variable",
icon: "red/images/typedInput/env.png"
} }
}; };
var nlsd = false; var nlsd = false;
$.widget( "nodered.typedInput", { $.widget( "nodered.typedInput", {
_create: function() { _create: function() {
try {
if (!nlsd && RED && RED._) { if (!nlsd && RED && RED._) {
for (var i in allOptions) { for (var i in allOptions) {
if (allOptions.hasOwnProperty(i)) { if (allOptions.hasOwnProperty(i)) {
allOptions[i].label = RED._("typedInput.type."+i,{defaultValue:allOptions[i].label}); allOptions[i].label = RED._("typedInput.type."+i,{defaultValue:allOptions[i].label});
} }
} }
var contextStores = RED.settings.context.stores;
var contextOptions = contextStores.map(function(store) {
return {value:store,label: store, icon:'<i class="red-ui-typedInput-icon fa fa-database" style="color: #'+(store==='memory'?'ddd':'777')+'"></i>'}
})
if (contextOptions.length < 2) {
allOptions.flow.options = [];
allOptions.global.options = [];
} else {
allOptions.flow.options = contextOptions;
allOptions.global.options = contextOptions;
}
} }
nlsd = true; nlsd = true;
var that = this; var that = this;
this.disarmClick = false; this.disarmClick = false;
this.input = $('<input type="text"></input>');
this.input.insertAfter(this.element);
this.input.val(this.element.val());
this.element.addClass('red-ui-typedInput'); this.element.addClass('red-ui-typedInput');
this.uiWidth = this.element.outerWidth(); this.uiWidth = this.element.outerWidth();
this.elementDiv = this.element.wrap("<div>").parent().addClass('red-ui-typedInput-input'); this.elementDiv = this.input.wrap("<div>").parent().addClass('red-ui-typedInput-input');
this.uiSelect = this.elementDiv.wrap( "<div>" ).parent(); this.uiSelect = this.elementDiv.wrap( "<div>" ).parent();
var attrStyle = this.element.attr('style'); var attrStyle = this.element.attr('style');
var m; var m;
if ((m = /width\s*:\s*(calc\s*\(.*\)|\d+(%|px))/i.exec(attrStyle)) !== null) { if ((m = /width\s*:\s*(calc\s*\(.*\)|\d+(%|px))/i.exec(attrStyle)) !== null) {
this.element.css('width','100%'); this.input.css('width','100%');
this.uiSelect.width(m[1]); this.uiSelect.width(m[1]);
this.uiWidth = null; this.uiWidth = null;
} else { } else {
@ -109,17 +162,19 @@
["Right","Left"].forEach(function(d) { ["Right","Left"].forEach(function(d) {
var m = that.element.css("margin"+d); var m = that.element.css("margin"+d);
that.uiSelect.css("margin"+d,m); that.uiSelect.css("margin"+d,m);
that.element.css("margin"+d,0); that.input.css("margin"+d,0);
}); });
this.uiSelect.addClass("red-ui-typedInput-container"); this.uiSelect.addClass("red-ui-typedInput-container");
this.element.attr('type','hidden');
this.options.types = this.options.types||Object.keys(allOptions); this.options.types = this.options.types||Object.keys(allOptions);
this.selectTrigger = $('<button tabindex="0"></button>').prependTo(this.uiSelect); this.selectTrigger = $('<button tabindex="0"></button>').prependTo(this.uiSelect);
if (this.options.types.length > 1) { $('<i class="red-ui-typedInput-icon fa fa-sort-desc"></i>').toggle(this.options.types.length > 1).appendTo(this.selectTrigger);
$('<i class="fa fa-sort-desc"></i>').appendTo(this.selectTrigger);
} this.selectLabel = $('<span class="red-ui-typedInput-type-label"></span>').appendTo(this.selectTrigger);
this.selectLabel = $('<span></span>').appendTo(this.selectTrigger);
this.types(this.options.types); this.types(this.options.types);
@ -133,14 +188,16 @@
this.typeField = $("<input>",{type:'hidden'}).appendTo(this.uiSelect); this.typeField = $("<input>",{type:'hidden'}).appendTo(this.uiSelect);
} }
this.element.on('focus', function() { this.input.on('focus', function() {
that.uiSelect.addClass('red-ui-typedInput-focus'); that.uiSelect.addClass('red-ui-typedInput-focus');
}); });
this.element.on('blur', function() { this.input.on('blur', function() {
that.uiSelect.removeClass('red-ui-typedInput-focus'); that.uiSelect.removeClass('red-ui-typedInput-focus');
}); });
this.element.on('change', function() { this.input.on('change', function() {
that.validate(); that.validate();
that.element.val(that.value());
that.element.trigger('change',that.propertyType,that.value());
}) })
this.selectTrigger.click(function(event) { this.selectTrigger.click(function(event) {
event.preventDefault(); event.preventDefault();
@ -156,8 +213,11 @@
}) })
// explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline' // explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline'
this.optionSelectTrigger = $('<button tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="fa fa-sort-desc"></i></span></button>').appendTo(this.uiSelect); this.optionSelectTrigger = $('<button tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="red-ui-typedInput-icon fa fa-sort-desc"></i></span></button>').appendTo(this.uiSelect);
this.optionSelectLabel = $('<span class="red-ui-typedInput-option-label"></span>').prependTo(this.optionSelectTrigger); this.optionSelectLabel = $('<span class="red-ui-typedInput-option-label"></span>').prependTo(this.optionSelectTrigger);
RED.popover.tooltip(this.optionSelectLabel,function() {
return that.optionValue;
});
this.optionSelectTrigger.click(function(event) { this.optionSelectTrigger.click(function(event) {
event.preventDefault(); event.preventDefault();
that._showOptionSelectMenu(); that._showOptionSelectMenu();
@ -172,17 +232,18 @@
that.uiSelect.addClass('red-ui-typedInput-focus'); that.uiSelect.addClass('red-ui-typedInput-focus');
}); });
this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"><i class="fa fa-ellipsis-h"></i></button>').appendTo(this.uiSelect); this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"><i class="red-ui-typedInput-icon fa fa-ellipsis-h"></i></button>').appendTo(this.uiSelect);
this.type(this.options.default||this.typeList[0].value); this.type(this.options.default||this.typeList[0].value);
}catch(err) {
console.log(err.stack);
}
}, },
_showTypeMenu: function() { _showTypeMenu: function() {
if (this.typeList.length > 1) { if (this.typeList.length > 1) {
this._showMenu(this.menu,this.selectTrigger); this._showMenu(this.menu,this.selectTrigger);
this.menu.find("[value='"+this.propertyType+"']").focus(); this.menu.find("[value='"+this.propertyType+"']").focus();
} else { } else {
this.element.focus(); this.input.focus();
} }
}, },
_showOptionSelectMenu: function() { _showOptionSelectMenu: function() {
@ -191,8 +252,8 @@
minWidth:this.optionSelectLabel.width() minWidth:this.optionSelectLabel.width()
}); });
this._showMenu(this.optionMenu,this.optionSelectLabel); this._showMenu(this.optionMenu,this.optionSelectTrigger);
var selectedOption = this.optionMenu.find("[value='"+this.value()+"']"); var selectedOption = this.optionMenu.find("[value='"+this.optionValue+"']");
if (selectedOption.length === 0) { if (selectedOption.length === 0) {
selectedOption = this.optionMenu.children(":first"); selectedOption = this.optionMenu.children(":first");
} }
@ -204,7 +265,7 @@
$(document).off("mousedown.close-property-select"); $(document).off("mousedown.close-property-select");
menu.hide(); menu.hide();
if (this.elementDiv.is(":visible")) { if (this.elementDiv.is(":visible")) {
this.element.focus(); this.input.focus();
} else if (this.optionSelectTrigger.is(":visible")){ } else if (this.optionSelectTrigger.is(":visible")){
this.optionSelectTrigger.focus(); this.optionSelectTrigger.focus();
} else { } else {
@ -223,10 +284,19 @@
op.text(opt.label); op.text(opt.label);
} }
if (opt.icon) { if (opt.icon) {
if (opt.icon.indexOf("<") === 0) {
$(opt.icon).prependTo(op);
} else if (opt.icon.indexOf("/") !== -1) {
$('<img>',{src:opt.icon,style:"margin-right: 4px; height: 18px;"}).prependTo(op); $('<img>',{src:opt.icon,style:"margin-right: 4px; height: 18px;"}).prependTo(op);
} else {
$('<i>',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(op);
}
} else { } else {
op.css({paddingLeft: "18px"}); op.css({paddingLeft: "18px"});
} }
if (!opt.icon && !opt.label) {
op.text(opt.value);
}
op.click(function(event) { op.click(function(event) {
event.preventDefault(); event.preventDefault();
@ -305,7 +375,8 @@
if (this.uiWidth !== null) { if (this.uiWidth !== null) {
this.uiSelect.width(this.uiWidth); this.uiSelect.width(this.uiWidth);
} }
if (this.typeMap[this.propertyType] && this.typeMap[this.propertyType].hasValue === false) { var type = this.typeMap[this.propertyType];
if (type && type.hasValue === false) {
this.selectTrigger.addClass("red-ui-typedInput-full-width"); this.selectTrigger.addClass("red-ui-typedInput-full-width");
} else { } else {
this.selectTrigger.removeClass("red-ui-typedInput-full-width"); this.selectTrigger.removeClass("red-ui-typedInput-full-width");
@ -315,11 +386,63 @@
this.elementDiv.css('right',"22px"); this.elementDiv.css('right',"22px");
} else { } else {
this.elementDiv.css('right','0'); this.elementDiv.css('right','0');
this.input.css({
'border-top-right-radius': '4px',
'border-bottom-right-radius': '4px'
});
} }
// if (this.optionSelectTrigger) {
// this.optionSelectTrigger.css({'left':(labelWidth)+"px",'width':'calc( 100% - '+labelWidth+'px )'});
// }
if (this.optionSelectTrigger) { if (this.optionSelectTrigger) {
this.optionSelectTrigger.css({'left':(labelWidth)+"px",'width':'calc( 100% - '+labelWidth+'px )'}); if (type && type.options && type.hasValue === true) {
this.optionSelectLabel.css({'left':'auto'})
var lw = this._getLabelWidth(this.optionSelectLabel);
this.optionSelectTrigger.css({'width':(23+lw)+"px"});
this.elementDiv.css('right',(23+lw)+"px");
this.input.css({
'border-top-right-radius': 0,
'border-bottom-right-radius': 0
});
} else {
this.optionSelectLabel.css({'left':'0'})
this.optionSelectTrigger.css({'width':'calc( 100% - '+labelWidth+'px )'});
if (!this.optionExpandButton.is(":visible")) {
this.elementDiv.css({'right':0});
this.input.css({
'border-top-right-radius': '4px',
'border-bottom-right-radius': '4px'
});
} }
} }
}
}
},
_updateOptionSelectLabel: function(o) {
var opt = this.typeMap[this.propertyType];
this.optionSelectLabel.empty();
if (o.icon) {
if (o.icon.indexOf("<") === 0) {
$(o.icon).prependTo(this.optionSelectLabel);
} else if (o.icon.indexOf("/") !== -1) {
// url
$('<img>',{src:o.icon,style:"height: 18px;"}).prependTo(this.optionSelectLabel);
} else {
// icon class
$('<i>',{class:"red-ui-typedInput-icon "+o.icon}).prependTo(this.optionSelectLabel);
}
} else if (o.label) {
this.optionSelectLabel.text(o.label);
} else {
this.optionSelectLabel.text(o.value);
}
if (opt.hasValue) {
this.optionValue = o.value;
this._resize();
this.input.trigger('change',this.propertyType,this.value());
}
}, },
_destroy: function() { _destroy: function() {
this.menu.remove(); this.menu.remove();
@ -339,13 +462,18 @@
return result; return result;
}); });
this.selectTrigger.toggleClass("disabled", this.typeList.length === 1); this.selectTrigger.toggleClass("disabled", this.typeList.length === 1);
this.selectTrigger.find(".fa-sort-desc").toggle(this.typeList.length > 1)
if (this.menu) { if (this.menu) {
this.menu.remove(); this.menu.remove();
} }
this.menu = this._createMenu(this.typeList, function(v) { that.type(v) }); this.menu = this._createMenu(this.typeList, function(v) { that.type(v) });
if (currentType && !this.typeMap.hasOwnProperty(currentType)) { if (currentType && !this.typeMap.hasOwnProperty(currentType)) {
this.type(this.typeList[0].value); this.type(this.typeList[0].value);
} else {
this.propertyType = null;
this.type(currentType);
} }
setTimeout(function() {that._resize();},0);
}, },
width: function(desiredWidth) { width: function(desiredWidth) {
this.uiWidth = desiredWidth; this.uiWidth = desiredWidth;
@ -353,33 +481,33 @@
}, },
value: function(value) { value: function(value) {
if (!arguments.length) { if (!arguments.length) {
return this.element.val(); var v = this.input.val();
if (this.typeMap[this.propertyType].export) {
v = this.typeMap[this.propertyType].export(v,this.optionValue)
}
return v;
} else { } else {
var selectedOption;
if (this.typeMap[this.propertyType].options) { if (this.typeMap[this.propertyType].options) {
var validValue = false;
var label;
for (var i=0;i<this.typeMap[this.propertyType].options.length;i++) { for (var i=0;i<this.typeMap[this.propertyType].options.length;i++) {
var op = this.typeMap[this.propertyType].options[i]; var op = this.typeMap[this.propertyType].options[i];
if (typeof op === "string") { if (typeof op === "string") {
if (op === value) { if (op === value) {
label = value; selectedOption = this.activeOptions[op];
validValue = true;
break; break;
} }
} else if (op.value === value) { } else if (op.value === value) {
label = op.label||op.value; selectedOption = op;
validValue = true;
break; break;
} }
} }
if (!validValue) { if (!selectedOption) {
value = ""; selectedOption = {value:""}
label = "";
} }
this.optionSelectLabel.text(label); this._updateOptionSelectLabel(selectedOption)
} }
this.element.val(value); this.input.val(value);
this.element.trigger('change',this.type(),value); this.input.trigger('change',this.type(),value);
} }
}, },
type: function(type) { type: function(type) {
@ -390,7 +518,9 @@
var opt = this.typeMap[type]; var opt = this.typeMap[type];
if (opt && this.propertyType !== type) { if (opt && this.propertyType !== type) {
this.propertyType = type; this.propertyType = type;
if (this.typeField) {
this.typeField.val(type); this.typeField.val(type);
}
this.selectLabel.empty(); this.selectLabel.empty();
var image; var image;
if (opt.icon) { if (opt.icon) {
@ -407,24 +537,41 @@
} }
if (this.optionSelectTrigger) { if (this.optionSelectTrigger) {
this.optionSelectTrigger.show(); this.optionSelectTrigger.show();
if (!opt.hasValue) {
this.elementDiv.hide(); this.elementDiv.hide();
this.optionMenu = this._createMenu(opt.options,function(v){ } else {
that.optionSelectLabel.text(v); this.elementDiv.show();
that.value(v); }
this.activeOptions = {};
opt.options.forEach(function(o) {
if (typeof o === 'string') {
that.activeOptions[o] = {label:o,value:o};
} else {
that.activeOptions[o.value] = o;
}
});
if (!that.activeOptions.hasOwnProperty(that.optionValue)) {
that.optionValue = null;
}
this.optionMenu = this._createMenu(opt.options,function(v){
that._updateOptionSelectLabel(that.activeOptions[v]);
if (!opt.hasValue) {
that.value(that.activeOptions[v].value)
}
}); });
var currentVal = this.element.val();
var validValue = false;
var op; var op;
if (!opt.hasValue) {
var currentVal = this.input.val();
var validValue = false;
for (var i=0;i<opt.options.length;i++) { for (var i=0;i<opt.options.length;i++) {
op = opt.options[i]; op = opt.options[i];
if (typeof op === "string") { if (typeof op === "string" && op === currentVal) {
if (op === currentVal) { that._updateOptionSelectLabel({value:currentVal});
this.optionSelectLabel.text(currentVal);
validValue = true; validValue = true;
break; break;
}
} else if (op.value === currentVal) { } else if (op.value === currentVal) {
this.optionSelectLabel.text(op.label||op.value); that._updateOptionSelectLabel(op);
validValue = true; validValue = true;
break; break;
} }
@ -433,11 +580,45 @@
op = opt.options[0]; op = opt.options[0];
if (typeof op === "string") { if (typeof op === "string") {
this.value(op); this.value(op);
that._updateOptionSelectLabel({value:op});
} else { } else {
this.value(op.value); this.value(op.value);
that._updateOptionSelectLabel(op);
}
}
} else {
var selectedOption = this.optionValue||opt.options[0];
if (opt.parse) {
var parts = opt.parse(this.input.val());
if (parts.option) {
selectedOption = parts.option;
if (!this.activeOptions.hasOwnProperty(selectedOption)) {
parts.option = Object.keys(this.activeOptions)[0];
selectedOption = parts.option
}
}
this.input.val(parts.value);
if (opt.export) {
this.element.val(opt.export(parts.value,parts.option||selectedOption));
}
}
if (typeof selectedOption === "string") {
this.optionValue = selectedOption;
if (!this.activeOptions.hasOwnProperty(selectedOption)) {
selectedOption = Object.keys(this.activeOptions)[0];
}
if (!selectedOption) {
this.optionSelectTrigger.hide();
} else {
this._updateOptionSelectLabel(this.activeOptions[selectedOption]);
}
} else if (selectedOption) {
this.optionValue = selectedOption.value;
this._updateOptionSelectLabel(selectedOption);
} else {
this.optionSelectTrigger.hide();
} }
} }
console.log(validValue);
} }
} else { } else {
if (this.optionMenu) { if (this.optionMenu) {
@ -448,16 +629,17 @@
this.optionSelectTrigger.hide(); this.optionSelectTrigger.hide();
} }
if (opt.hasValue === false) { if (opt.hasValue === false) {
this.oldValue = this.element.val(); this.oldValue = this.input.val();
this.element.val(""); this.input.val("");
this.elementDiv.hide(); this.elementDiv.hide();
} else { } else {
if (this.oldValue !== undefined) { if (this.oldValue !== undefined) {
this.element.val(this.oldValue); this.input.val(this.oldValue);
delete this.oldValue; delete this.oldValue;
} }
this.elementDiv.show(); this.elementDiv.show();
} }
if (this.optionExpandButton) {
if (opt.expand && typeof opt.expand === 'function') { if (opt.expand && typeof opt.expand === 'function') {
this.optionExpandButton.show(); this.optionExpandButton.show();
this.optionExpandButton.off('click'); this.optionExpandButton.off('click');
@ -468,7 +650,8 @@
} else { } else {
this.optionExpandButton.hide(); this.optionExpandButton.hide();
} }
this.element.trigger('change',this.propertyType,this.value()); }
this.input.trigger('change',this.propertyType,this.value());
} }
if (image) { if (image) {
image.onload = function() { that._resize(); } image.onload = function() { that._resize(); }

View File

@ -1233,7 +1233,7 @@ RED.diff = (function() {
// currentDiff = diff; // currentDiff = diff;
var trayOptions = { var trayOptions = {
title: options.title||"Review Changes", //TODO: nls title: options.title||RED._("diff.reviewChanges"),
width: Infinity, width: Infinity,
overlay: true, overlay: true,
buttons: [ buttons: [
@ -1416,7 +1416,7 @@ RED.diff = (function() {
function showTextDiff(textA,textB) { function showTextDiff(textA,textB) {
var trayOptions = { var trayOptions = {
title: "Compare Changes", //TODO: nls title: RED._("diff.compareChanges"),
width: Infinity, width: Infinity,
overlay: true, overlay: true,
buttons: [ buttons: [
@ -1747,7 +1747,7 @@ RED.diff = (function() {
try { try {
commonFlow = JSON.parse(commonVersion.content||"[]"); commonFlow = JSON.parse(commonVersion.content||"[]");
} catch(err) { } catch(err) {
console.log("Common Version doesn't contain valid JSON:",commonVersionUrl); console.log(RED._("diff.commonVersionError"),commonVersionUrl);
console.log(err); console.log(err);
return; return;
} }
@ -1755,7 +1755,7 @@ RED.diff = (function() {
try { try {
oldFlow = JSON.parse(oldVersion.content||"[]"); oldFlow = JSON.parse(oldVersion.content||"[]");
} catch(err) { } catch(err) {
console.log("Old Version doesn't contain valid JSON:",oldVersionUrl); console.log(RED._("diff.oldVersionError"),oldVersionUrl);
console.log(err); console.log(err);
return; return;
} }
@ -1765,7 +1765,7 @@ RED.diff = (function() {
try { try {
newFlow = JSON.parse(newVersion.content||"[]"); newFlow = JSON.parse(newVersion.content||"[]");
} catch(err) { } catch(err) {
console.log("New Version doesn't contain valid JSON:",newFlow); console.log(RED._("diff.newVersionError"),newFlow);
console.log(err); console.log(err);
return; return;
} }
@ -1797,11 +1797,11 @@ RED.diff = (function() {
if (isBinary) { if (isBinary) {
var diffBinaryRow = $('<tr class="node-text-diff-header">').appendTo(codeBody); var diffBinaryRow = $('<tr class="node-text-diff-header">').appendTo(codeBody);
var binaryContent = $('<td colspan="3"></td>').appendTo(diffBinaryRow); var binaryContent = $('<td colspan="3"></td>').appendTo(diffBinaryRow);
$('<span></span>').text("Cannot show binary file contents").appendTo(binaryContent); $('<span></span>').text(RED._("diff.noBinaryFileShowed")).appendTo(binaryContent);
} else { } else {
if (commitOptions.unmerged) { if (commitOptions.unmerged) {
conflictHeader = $('<span style="float: right;"><span>'+resolvedConflicts+'</span> of <span>'+unresolvedConflicts+'</span> conflicts resolved</span>').appendTo(content); conflictHeader = $('<span style="float: right;">'+RED._("diff.conflictHeader",{resolved:resolvedConflicts, unresolved:unresolvedConflicts})+'</span>').appendTo(content);
} }
hunks.forEach(function(hunk) { hunks.forEach(function(hunk) {
var diffRow = $('<tr class="node-text-diff-header">').appendTo(codeBody); var diffRow = $('<tr class="node-text-diff-header">').appendTo(codeBody);
@ -1914,7 +1914,7 @@ RED.diff = (function() {
diffRow.remove(); diffRow.remove();
addedRows.find(".linetext").addClass('added'); addedRows.find(".linetext").addClass('added');
conflictHeader.empty(); conflictHeader.empty();
$('<span><span>'+resolvedConflicts+'</span> of <span>'+unresolvedConflicts+'</span> conflicts resolved</span>').appendTo(conflictHeader); $('<span>'+RED._("diff.conflictHeader",{resolved:resolvedConflicts, unresolved:unresolvedConflicts})+'</span>').appendTo(conflictHeader);
conflictResolutions[file.file] = conflictResolutions[file.file] || {}; conflictResolutions[file.file] = conflictResolutions[file.file] || {};
conflictResolutions[file.file][hunk.localChangeStart] = { conflictResolutions[file.file][hunk.localChangeStart] = {
@ -1946,7 +1946,7 @@ RED.diff = (function() {
function showCommitDiff(options) { function showCommitDiff(options) {
var commit = parseCommitDiff(options.commit); var commit = parseCommitDiff(options.commit);
var trayOptions = { var trayOptions = {
title: "View Commit Changes", //TODO: nls title: RED._("diff.viewCommitDiff"),
width: Infinity, width: Infinity,
overlay: true, overlay: true,
buttons: [ buttons: [
@ -2008,7 +2008,7 @@ RED.diff = (function() {
} }
var trayOptions = { var trayOptions = {
title: title||"Compare Changes", //TODO: nls title: title|| RED._("diff.compareChanges"),
width: Infinity, width: Infinity,
overlay: true, overlay: true,
buttons: [ buttons: [
@ -2041,7 +2041,7 @@ RED.diff = (function() {
trayOptions.buttons.push( trayOptions.buttons.push(
{ {
id: "node-diff-view-resolve-diff", id: "node-diff-view-resolve-diff",
text: "Save conflict resolution", text: RED._("diff.saveConflict"),
class: "primary disabled", class: "primary disabled",
click: function() { click: function() {
if (!$("#node-diff-view-resolve-diff").hasClass('disabled')) { if (!$("#node-diff-view-resolve-diff").hasClass('disabled')) {

View File

@ -108,17 +108,6 @@ RED.editor = (function() {
} }
} }
} }
if (node.icon) {
var iconPath = RED.utils.separateIconPath(node.icon);
if (!iconPath.module) {
return isValid;
}
var iconSets = RED.nodes.getIconSets();
var iconFileList = iconSets[iconPath.module];
if (!iconFileList || iconFileList.indexOf(iconPath.file) === -1) {
isValid = false;
}
}
return isValid; return isValid;
} }
@ -170,27 +159,6 @@ RED.editor = (function() {
} }
} }
} }
validateIcon(node);
}
function validateIcon(node) {
if (node._def.hasOwnProperty("defaults") && !node._def.defaults.hasOwnProperty("icon") && node.icon) {
var iconPath = RED.utils.separateIconPath(node.icon);
var iconSets = RED.nodes.getIconSets();
var iconFileList = iconSets[iconPath.module];
var iconModule = $("#node-settings-icon-module");
var iconFile = $("#node-settings-icon-file");
if (!iconFileList) {
iconModule.addClass("input-error");
iconFile.removeClass("input-error");
} else if (iconFileList.indexOf(iconPath.file) === -1) {
iconModule.removeClass("input-error");
iconFile.addClass("input-error");
} else {
iconModule.removeClass("input-error");
iconFile.removeClass("input-error");
}
}
} }
function validateNodeEditorProperty(node,defaults,property,prefix) { function validateNodeEditorProperty(node,defaults,property,prefix) {
@ -711,6 +679,97 @@ RED.editor = (function() {
} }
return result; return result;
} }
function showIconPicker(container, node, iconPath, done) {
var containerPos = container.offset();
var pickerBackground = $('<div>').css({
position: "absolute",top:0,bottom:0,left:0,right:0,zIndex:20
}).appendTo("body");
var top = containerPos.top - 30;
if (top+280 > $( window ).height()) {
top = $( window ).height() - 280;
}
var picker = $('<div class="red-ui-icon-picker">').css({
top: top+"px",
left: containerPos.left+"px",
}).appendTo("body");
var hide = function() {
pickerBackground.remove();
picker.remove();
RED.keyboard.remove("escape");
}
RED.keyboard.add("*","escape",function(){hide()});
pickerBackground.on("mousedown", hide);
var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(picker);
searchInput = $('<input type="text">').attr("placeholder","Search icons").appendTo(searchDiv).searchBox({
delay: 50,
change: function() {
var searchTerm = $(this).val().trim();
if (searchTerm === "") {
iconList.find(".red-ui-icon-list-module").show();
iconList.find(".red-ui-icon-list-icon").show();
} else {
iconList.find(".red-ui-icon-list-module").hide();
iconList.find(".red-ui-icon-list-icon").each(function(i,n) {
if ($(n).data('icon').indexOf(searchTerm) === -1) {
$(n).hide();
} else {
$(n).show();
}
});
}
}
});
var row = $('<div>').appendTo(picker);
var iconList = $('<div class="red-ui-icon-list">').appendTo(picker);
var metaRow = $('<div class="red-ui-icon-meta"></div>').appendTo(picker);
var summary = $('<span>').appendTo(metaRow);
var resetButton = $('<button class="editor-button editor-button-small">use default</button>').appendTo(metaRow).click(function(e) {
e.preventDefault();
hide();
done(null);
});
var iconSets = RED.nodes.getIconSets();
Object.keys(iconSets).forEach(function(moduleName) {
var icons = iconSets[moduleName];
if (icons.length > 0) {
// selectIconModule.append($("<option></option>").val(moduleName).text(moduleName));
var header = $('<div class="red-ui-icon-list-module"></div>').text(moduleName).appendTo(iconList);
$('<i class="fa fa-cube"></i>').prependTo(header);
icons.forEach(function(icon) {
var iconDiv = $('<div>',{class:"red-ui-icon-list-icon"}).appendTo(iconList);
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(iconDiv);
var colour = node._def.color;
var icon_url = "icons/"+moduleName+"/"+icon;
iconDiv.data('icon',icon_url)
nodeDiv.css('backgroundColor',colour);
var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
$('<div/>',{class:"palette_icon",style:"background-image: url("+icon_url+")"}).appendTo(iconContainer);
if (iconPath.module === moduleName && iconPath.file === icon) {
iconDiv.addClass("selected");
}
iconDiv.on("mouseover", function() {
summary.text(icon);
})
iconDiv.on("mouseout", function() {
summary.html("&nbsp;");
})
iconDiv.click(function() {
hide();
done(moduleName+"/"+icon);
})
})
}
});
picker.slideDown(100);
searchInput.focus();
}
function buildLabelForm(container,node) { function buildLabelForm(container,node) {
var dialogForm = $('<form class="dialog-form form-horizontal" autocomplete="off"></form>').appendTo(container); var dialogForm = $('<form class="dialog-form form-horizontal" autocomplete="off"></form>').appendTo(container);
@ -748,81 +807,36 @@ RED.editor = (function() {
} }
if ((!node._def.defaults || !node._def.defaults.hasOwnProperty("icon"))) { if ((!node._def.defaults || !node._def.defaults.hasOwnProperty("icon"))) {
$('<div class="form-row"><div id="node-settings-icon"></div></div>').appendTo(dialogForm); $('<hr>').appendTo(dialogForm);
var iconDiv = $("#node-settings-icon"); var iconRow = $('<div class="form-row"></div>').appendTo(dialogForm);
$('<label data-i18n="editor.settingIcon">').appendTo(iconDiv); $('<label style="width: 50px" data-i18n="editor.settingIcon">').appendTo(iconRow);
var iconForm = $('<div>',{class:"node-label-form-row"});
iconForm.appendTo(iconDiv);
$('<label>').appendTo(iconForm);
var selectIconModule = $('<select id="node-settings-icon-module"><option value=""></option></select>').appendTo(iconForm); var iconButton = $('<button class="editor-button">').appendTo(iconRow);
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(iconButton);
var colour = node._def.color;
var icon_url = RED.utils.getNodeIcon(node._def,node);
nodeDiv.css('backgroundColor',colour);
var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
var iconDiv = $('<div/>',{class:"palette_icon",style:"background-image: url("+icon_url+")"}).appendTo(iconContainer);
iconButton.click(function(e) {
e.preventDefault();
var iconPath; var iconPath;
if (node.icon) { var icon = $("#node-settings-icon").text()||"";
iconPath = RED.utils.separateIconPath(node.icon); if (icon) {
iconPath = RED.utils.separateIconPath(icon);
} else { } else {
iconPath = RED.utils.getDefaultNodeIcon(node._def, node); iconPath = RED.utils.getDefaultNodeIcon(node._def, node);
} }
var iconSets = RED.nodes.getIconSets(); showIconPicker(iconRow,node,iconPath,function(newIcon) {
Object.keys(iconSets).forEach(function(moduleName) { $("#node-settings-icon").text(newIcon||"");
selectIconModule.append($("<option></option>").val(moduleName).text(moduleName)); var icon_url = RED.utils.getNodeIcon(node._def,{type:node.type,icon:newIcon});
iconDiv.css("backgroundImage","url("+icon_url+")");
}); });
if (iconPath.module && !iconSets[iconPath.module]) { })
selectIconModule.append($("<option disabled></option>").val(iconPath.module).text(iconPath.module)); $('<div class="uneditable-input" id="node-settings-icon">').text(node.icon).appendTo(iconRow);
} }
selectIconModule.val(iconPath.module);
var iconModuleHidden = $('<input type="hidden" id="node-settings-icon-module-hidden"></input>').appendTo(iconForm);
iconModuleHidden.val(iconPath.module);
var selectIconFile = $('<select id="node-settings-icon-file"><option value=""></option></select>').appendTo(iconForm);
selectIconModule.change(function() {
moduleChange(selectIconModule, selectIconFile, iconModuleHidden, iconFileHidden, iconSets, true);
});
var iconFileHidden = $('<input type="hidden" id="node-settings-icon-file-hidden"></input>').appendTo(iconForm);
iconFileHidden.val(iconPath.file);
selectIconFile.change(function() {
selectIconFile.removeClass("input-error");
var fileName = selectIconFile.val();
iconFileHidden.val(fileName);
});
var clear = $('<button class="editor-button editor-button-small"><i class="fa fa-times"></i></button>').appendTo(iconForm);
clear.click(function(evt) {
evt.preventDefault();
var iconPath = RED.utils.getDefaultNodeIcon(node._def, node);
selectIconModule.val(iconPath.module);
moduleChange(selectIconModule, selectIconFile, iconModuleHidden, iconFileHidden, iconSets, true);
selectIconFile.removeClass("input-error");
selectIconFile.val(iconPath.file);
iconFileHidden.val(iconPath.file);
});
moduleChange(selectIconModule, selectIconFile, iconModuleHidden, iconFileHidden, iconSets, false);
var iconFileList = iconSets[selectIconModule.val()];
if (!iconFileList || iconFileList.indexOf(iconPath.file) === -1) {
selectIconFile.append($("<option disabled></option>").val(iconPath.file).text(iconPath.file));
}
selectIconFile.val(iconPath.file);
}
}
function moduleChange(selectIconModule, selectIconFile, iconModuleHidden, iconFileHidden, iconSets, updateIconFile) {
selectIconFile.children().remove();
var moduleName = selectIconModule.val();
if (moduleName !== null) {
iconModuleHidden.val(moduleName);
}
var iconFileList = iconSets[moduleName];
if (iconFileList) {
iconFileList.forEach(function(fileName) {
if (updateIconFile) {
updateIconFile = false;
iconFileHidden.val(fileName);
}
selectIconFile.append($("<option></option>").val(fileName).text(fileName));
});
}
selectIconFile.prop("disabled", !iconFileList);
selectIconFile.removeClass("input-error");
selectIconModule.removeClass("input-error");
} }
function updateLabels(editing_node, changes, outputMap) { function updateLabels(editing_node, changes, outputMap) {
@ -1086,9 +1100,7 @@ RED.editor = (function() {
} }
if (!editing_node._def.defaults || !editing_node._def.defaults.hasOwnProperty("icon")) { if (!editing_node._def.defaults || !editing_node._def.defaults.hasOwnProperty("icon")) {
var iconModule = $("#node-settings-icon-module-hidden").val(); var icon = $("#node-settings-icon").text()||""
var iconFile = $("#node-settings-icon-file-hidden").val();
var icon = (iconModule && iconFile) ? iconModule+"/"+iconFile : "";
if (!isDefaultIcon) { if (!isDefaultIcon) {
if (icon !== editing_node.icon) { if (icon !== editing_node.icon) {
changes.icon = editing_node.icon; changes.icon = editing_node.icon;
@ -1692,15 +1704,28 @@ RED.editor = (function() {
if (updateLabels(editing_node, changes, null)) { if (updateLabels(editing_node, changes, null)) {
changed = true; changed = true;
} }
var iconModule = $("#node-settings-icon-module-hidden").val(); var icon = $("#node-settings-icon").text()||"";
var iconFile = $("#node-settings-icon-file-hidden").val();
var icon = (iconModule && iconFile) ? iconModule+"/"+iconFile : "";
if ((editing_node.icon === undefined && icon !== "node-red/subflow.png") || if ((editing_node.icon === undefined && icon !== "node-red/subflow.png") ||
(editing_node.icon !== undefined && editing_node.icon !== icon)) { (editing_node.icon !== undefined && editing_node.icon !== icon)) {
changes.icon = editing_node.icon; changes.icon = editing_node.icon;
editing_node.icon = icon; editing_node.icon = icon;
changed = true; changed = true;
} }
var newCategory = $("#subflow-input-category").val().trim();
if (newCategory === "_custom_") {
newCategory = $("#subflow-input-custom-category").val().trim();
if (newCategory === "") {
newCategory = editing_node.category;
}
}
if (newCategory === 'subflows') {
newCategory = '';
}
if (newCategory != editing_node.category) {
changes['category'] = editing_node.category;
editing_node.category = newCategory;
changed = true;
}
RED.palette.refresh(); RED.palette.refresh();
@ -1773,8 +1798,6 @@ RED.editor = (function() {
}); });
portLabels.content.addClass("editor-tray-content"); portLabels.content.addClass("editor-tray-content");
if (editing_node) { if (editing_node) {
RED.sidebar.info.refresh(editing_node); RED.sidebar.info.refresh(editing_node);
} }
@ -1787,6 +1810,33 @@ RED.editor = (function() {
$("#subflow-input-name").val(subflow.name); $("#subflow-input-name").val(subflow.name);
RED.text.bidi.prepareInput($("#subflow-input-name")); RED.text.bidi.prepareInput($("#subflow-input-name"));
$("#subflow-input-category").empty();
var categories = RED.palette.getCategories();
categories.sort(function(A,B) {
return A.label.localeCompare(B.label);
})
categories.forEach(function(cat) {
$("#subflow-input-category").append($("<option></option>").val(cat.id).text(cat.label));
})
$("#subflow-input-category").append($("<option></option>").attr('disabled',true).text("---"));
$("#subflow-input-category").append($("<option></option>").val("_custom_").text(RED._("palette.addCategory")));
$("#subflow-input-category").change(function() {
var val = $(this).val();
if (val === "_custom_") {
$("#subflow-input-category").width(120);
$("#subflow-input-custom-category").show();
} else {
$("#subflow-input-category").width(250);
$("#subflow-input-custom-category").hide();
}
})
$("#subflow-input-category").val(subflow.category||"subflows");
subflowEditor.getSession().setValue(subflow.info||"",-1); subflowEditor.getSession().setValue(subflow.info||"",-1);
var userCount = 0; var userCount = 0;
var subflowType = "subflow:"+editing_node.id; var subflowType = "subflow:"+editing_node.id;
@ -1799,7 +1849,6 @@ RED.editor = (function() {
$("#subflow-dialog-user-count").text(RED._("subflow.subflowInstances", {count:userCount})).show(); $("#subflow-dialog-user-count").text(RED._("subflow.subflowInstances", {count:userCount})).show();
buildLabelForm(portLabels.content,subflow); buildLabelForm(portLabels.content,subflow);
validateIcon(subflow);
trayBody.i18n(); trayBody.i18n();
}, },
close: function() { close: function() {

View File

@ -414,12 +414,8 @@ RED.library = (function() {
RED.events.on("view:selection-changed",function(selection) { RED.events.on("view:selection-changed",function(selection) {
if (!selection.nodes) { if (!selection.nodes) {
RED.menu.setDisabled("menu-item-export",true);
RED.menu.setDisabled("menu-item-export-clipboard",true);
RED.menu.setDisabled("menu-item-export-library",true); RED.menu.setDisabled("menu-item-export-library",true);
} else { } else {
RED.menu.setDisabled("menu-item-export",false);
RED.menu.setDisabled("menu-item-export-clipboard",false);
RED.menu.setDisabled("menu-item-export-library",false); RED.menu.setDisabled("menu-item-export-library",false);
} }
}); });

View File

@ -96,7 +96,7 @@ RED.notifications = (function() {
if (options.buttons) { if (options.buttons) {
var buttonSet = $('<div style="margin-top: 20px;" class="ui-dialog-buttonset"></div>').appendTo(n) var buttonSet = $('<div style="margin-top: 20px;" class="ui-dialog-buttonset"></div>').appendTo(n)
options.buttons.forEach(function(buttonDef) { options.buttons.forEach(function(buttonDef) {
var b = $('<button>').text(buttonDef.text).click(buttonDef.click).appendTo(buttonSet); var b = $('<button>').html(buttonDef.text).click(buttonDef.click).appendTo(buttonSet);
if (buttonDef.id) { if (buttonDef.id) {
b.attr('id',buttonDef.id); b.attr('id',buttonDef.id);
} }

View File

@ -21,7 +21,18 @@ RED.palette = (function() {
var categoryContainers = {}; var categoryContainers = {};
function createCategoryContainer(category, label) {
function createCategory(originalCategory,rootCategory,category,ns) {
if ($("#palette-base-category-"+rootCategory).length === 0) {
createCategoryContainer(originalCategory,rootCategory, ns+":palette.label."+rootCategory);
}
$("#palette-container-"+rootCategory).show();
if ($("#palette-"+category).length === 0) {
$("#palette-base-category-"+rootCategory).append('<div id="palette-'+category+'"></div>');
}
}
function createCategoryContainer(originalCategory,category, labelId) {
var label = RED._(labelId, {defaultValue:category});
label = (label || category).replace(/_/g, " "); label = (label || category).replace(/_/g, " ");
var catDiv = $('<div id="palette-container-'+category+'" class="palette-category palette-close hide">'+ var catDiv = $('<div id="palette-container-'+category+'" class="palette-category palette-close hide">'+
'<div id="palette-header-'+category+'" class="palette-header"><i class="expanded fa fa-angle-down"></i><span>'+label+'</span></div>'+ '<div id="palette-header-'+category+'" class="palette-header"><i class="expanded fa fa-angle-down"></i><span>'+label+'</span></div>'+
@ -31,7 +42,8 @@ RED.palette = (function() {
'<div id="palette-'+category+'-function"></div>'+ '<div id="palette-'+category+'-function"></div>'+
'</div>'+ '</div>'+
'</div>').appendTo("#palette-container"); '</div>').appendTo("#palette-container");
catDiv.data('category',originalCategory);
catDiv.data('label',label);
categoryContainers[category] = { categoryContainers[category] = {
container: catDiv, container: catDiv,
close: function() { close: function() {
@ -133,6 +145,7 @@ RED.palette = (function() {
} }
if (exclusion.indexOf(def.category)===-1) { if (exclusion.indexOf(def.category)===-1) {
var originalCategory = def.category;
var category = def.category.replace(/ /g,"_"); var category = def.category.replace(/ /g,"_");
var rootCategory = category.split("-")[0]; var rootCategory = category.split("-")[0];
@ -153,7 +166,6 @@ RED.palette = (function() {
d.className="palette_node"; d.className="palette_node";
if (def.icon) { if (def.icon) {
var icon_url = RED.utils.getNodeIcon(def); var icon_url = RED.utils.getNodeIcon(def);
var iconContainer = $('<div/>',{class:"palette_icon_container"+(def.align=="right"?" palette_icon_container_right":"")}).appendTo(d); var iconContainer = $('<div/>',{class:"palette_icon_container"+(def.align=="right"?" palette_icon_container_right":"")}).appendTo(d);
@ -174,21 +186,12 @@ RED.palette = (function() {
d.appendChild(portIn); d.appendChild(portIn);
} }
if ($("#palette-base-category-"+rootCategory).length === 0) { createCategory(def.category,rootCategory,category,(coreCategories.indexOf(rootCategory) !== -1)?"node-red":def.set.id);
if(coreCategories.indexOf(rootCategory) !== -1){
createCategoryContainer(rootCategory, RED._("node-red:palette.label."+rootCategory, {defaultValue:rootCategory}));
} else {
var ns = def.set.id;
createCategoryContainer(rootCategory, RED._(ns+":palette.label."+rootCategory, {defaultValue:rootCategory}));
}
}
$("#palette-container-"+rootCategory).show();
if ($("#palette-"+category).length === 0) {
$("#palette-base-category-"+rootCategory).append('<div id="palette-'+category+'"></div>');
}
$("#palette-"+category).append(d); $("#palette-"+category).append(d);
$(d).data('category',rootCategory);
d.onmousedown = function(e) { e.preventDefault(); }; d.onmousedown = function(e) { e.preventDefault(); };
var popover = RED.popover.create({ var popover = RED.popover.create({
@ -308,7 +311,7 @@ RED.palette = (function() {
}); });
var nodeInfo = null; var nodeInfo = null;
if (def.category == "subflows") { if (nt.indexOf("subflow:") === 0) {
$(d).dblclick(function(e) { $(d).dblclick(function(e) {
RED.workspaces.show(nt.substring(8)); RED.workspaces.show(nt.substring(8));
e.preventDefault(); e.preventDefault();
@ -382,6 +385,31 @@ RED.palette = (function() {
} }
setLabel(sf.type+":"+sf.id,paletteNode,sf.name,marked(sf.info||"")); setLabel(sf.type+":"+sf.id,paletteNode,sf.name,marked(sf.info||""));
setIcon(paletteNode,sf); setIcon(paletteNode,sf);
var currentCategory = paletteNode.data('category');
var newCategory = (sf.category||"subflows");
if (currentCategory !== newCategory) {
var category = newCategory.replace(/ /g,"_");
createCategory(newCategory,category,category,"node-red");
var currentCategoryNode = paletteNode.closest(".palette-category");
var newCategoryNode = $("#palette-"+category);
newCategoryNode.append(paletteNode);
if (newCategoryNode.find(".palette_node").length === 1) {
categoryContainers[category].open();
}
paletteNode.data('category',newCategory);
if (currentCategoryNode.find(".palette_node").length === 0) {
if (currentCategoryNode.find("i").hasClass("expanded")) {
currentCategoryNode.find(".palette-content").slideToggle();
currentCategoryNode.find("i").toggleClass("expanded");
}
}
}
}); });
} }
@ -471,7 +499,7 @@ RED.palette = (function() {
categoryList = coreCategories categoryList = coreCategories
} }
categoryList.forEach(function(category){ categoryList.forEach(function(category){
createCategoryContainer(category, RED._("palette.label."+category,{defaultValue:category})); createCategoryContainer(category, category, "palette.label."+category);
}); });
$("#palette-collapse-all").on("click", function(e) { $("#palette-collapse-all").on("click", function(e) {
@ -491,13 +519,20 @@ RED.palette = (function() {
} }
}); });
} }
function getCategories() {
var categories = [];
$("#palette-container .palette-category").each(function(i,d) {
categories.push({id:$(d).data('category'),label:$(d).data('label')});
})
return categories;
}
return { return {
init: init, init: init,
add:addNodeType, add:addNodeType,
remove:removeNodeType, remove:removeNodeType,
hide:hideNodeType, hide:hideNodeType,
show:showNodeType, show:showNodeType,
refresh:refreshNodeTypes refresh:refreshNodeTypes,
getCategories: getCategories
}; };
})(); })();

View File

@ -49,7 +49,7 @@ RED.projects.settings = (function() {
var tabContainer; var tabContainer;
var trayOptions = { var trayOptions = {
title: "Project Settings",// RED._("menu.label.userSettings"),, // TODO: nls title: RED._("menu.label.userSettings"),
buttons: [ buttons: [
{ {
id: "node-dialog-ok", id: "node-dialog-ok",
@ -173,14 +173,14 @@ RED.projects.settings = (function() {
container.empty(); container.empty();
var bg = $('<span class="button-row" style="position: relative; float: right; margin-right:0;"></span>').appendTo(container); var bg = $('<span class="button-row" style="position: relative; float: right; margin-right:0;"></span>').appendTo(container);
var input = $('<input type="text" style="width: calc(100% - 150px); margin-right: 10px;">').val(summary||"").appendTo(container); var input = $('<input type="text" style="width: calc(100% - 150px); margin-right: 10px;">').val(summary||"").appendTo(container);
$('<button class="editor-button">Cancel</button>') $('<button class="editor-button">' + RED._("common.label.cancel") + '</button>')
.appendTo(bg) .appendTo(bg)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
updateProjectSummary(activeProject.summary, container); updateProjectSummary(activeProject.summary, container);
editButton.show(); editButton.show();
}); });
$('<button class="editor-button">Save</button>') $('<button class="editor-button">' + RED._("common.label.save") + '</button>')
.appendTo(bg) .appendTo(bg)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
@ -223,7 +223,7 @@ RED.projects.settings = (function() {
if (summary) { if (summary) {
container.text(summary).removeClass('node-info-node'); container.text(summary).removeClass('node-info-node');
} else { } else {
container.text("No summary available").addClass('node-info-none');// TODO: nls container.text(RED._("sidebar.project.projectSettings.noSummaryAvailable")).addClass('node-info-none');
} }
} }
@ -235,7 +235,7 @@ RED.projects.settings = (function() {
var summaryContent = $('<div></div>',{style:"color: #999"}).appendTo(summary); var summaryContent = $('<div></div>',{style:"color: #999"}).appendTo(summary);
updateProjectSummary(activeProject.summary, summaryContent); updateProjectSummary(activeProject.summary, summaryContent);
if (RED.user.hasPermission("projects.write")) { if (RED.user.hasPermission("projects.write")) {
$('<button class="editor-button editor-button-small" style="float: right;">edit description</button>') $('<button class="editor-button editor-button-small" style="float: right;">' + RED._('sidebar.project.editDescription') + '</button>')
.prependTo(summary) .prependTo(summary)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
@ -250,7 +250,7 @@ RED.projects.settings = (function() {
updateProjectDescription(activeProject, descriptionContent); updateProjectDescription(activeProject, descriptionContent);
if (RED.user.hasPermission("projects.write")) { if (RED.user.hasPermission("projects.write")) {
$('<button class="editor-button editor-button-small" style="float: right;">edit README.md</button>') $('<button class="editor-button editor-button-small" style="float: right;">' + RED._('sidebar.project.editReadme') + '</button>')
.prependTo(description) .prependTo(description)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
@ -316,7 +316,7 @@ RED.projects.settings = (function() {
// depsList.editableList('addItem',{index:3, label:"Unused dependencies"}); // TODO: nls // depsList.editableList('addItem',{index:3, label:"Unused dependencies"}); // TODO: nls
// } // }
if (totalCount === 0) { if (totalCount === 0) {
depsList.editableList('addItem',{index:0, label:"None"}); // TODO: nls depsList.editableList('addItem',{index:0, label:RED._("sidebar.project.projectSettings.none")});
} }
} }
@ -381,7 +381,7 @@ RED.projects.settings = (function() {
function createDependenciesPane(activeProject) { function createDependenciesPane(activeProject) {
var pane = $('<div id="project-settings-tab-deps" class="project-settings-tab-pane node-help"></div>'); var pane = $('<div id="project-settings-tab-deps" class="project-settings-tab-pane node-help"></div>');
if (RED.user.hasPermission("projects.write")) { if (RED.user.hasPermission("projects.write")) {
$('<button class="editor-button editor-button-small" style="margin-top:10px;float: right;">edit</button>') $('<button class="editor-button editor-button-small" style="margin-top:10px;float: right;">' + RED._("sidebar.project.projectSettings.edit") + '</button>')
.appendTo(pane) .appendTo(pane)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
@ -451,7 +451,7 @@ RED.projects.settings = (function() {
var buttons = $('<div class="palette-module-button-group"></div>').appendTo(metaRow); var buttons = $('<div class="palette-module-button-group"></div>').appendTo(metaRow);
if (RED.user.hasPermission("projects.write")) { if (RED.user.hasPermission("projects.write")) {
if (!entry.installed && RED.settings.theme('palette.editable') !== false) { if (!entry.installed && RED.settings.theme('palette.editable') !== false) {
$('<a href="#" class="editor-button editor-button-small">install</a>').appendTo(buttons) $('<a href="#" class="editor-button editor-button-small">' + RED._("sidebar.project.projectSettings.install") + '</a>').appendTo(buttons)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
RED.palette.editor.install(entry,row,function(err) { RED.palette.editor.install(entry,row,function(err) {
@ -468,7 +468,7 @@ RED.projects.settings = (function() {
}); });
}) })
} else if (entry.known && entry.count === 0) { } else if (entry.known && entry.count === 0) {
$('<a href="#" class="editor-button editor-button-small">remove from project</a>').appendTo(buttons) $('<a href="#" class="editor-button editor-button-small">' + RED._("sidebar.project.projectSettings.removeFromProject") + '</a>').appendTo(buttons)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
var deps = $.extend(true, {}, activeProject.dependencies); var deps = $.extend(true, {}, activeProject.dependencies);
@ -484,7 +484,7 @@ RED.projects.settings = (function() {
}); });
}); });
} else if (!entry.known) { } else if (!entry.known) {
$('<a href="#" class="editor-button editor-button-small">add to project</a>').appendTo(buttons) $('<a href="#" class="editor-button editor-button-small">' + RED._("sidebar.project.projectSettings.addToProject") + '</a>').appendTo(buttons)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
var deps = $.extend(true, {}, activeProject.dependencies); var deps = $.extend(true, {}, activeProject.dependencies);
@ -723,10 +723,10 @@ RED.projects.settings = (function() {
// } // }
function createFilesSection(activeProject,pane) { function createFilesSection(activeProject,pane) {
var title = $('<h3></h3>').text("Files").appendTo(pane); var title = $('<h3></h3>').text(RED._("sidebar.project.projectSettings.files")).appendTo(pane);
var filesContainer = $('<div class="user-settings-section"></div>').appendTo(pane); var filesContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
if (RED.user.hasPermission("projects.write")) { if (RED.user.hasPermission("projects.write")) {
var editFilesButton = $('<button class="editor-button editor-button-small" style="float: right;">edit</button>') var editFilesButton = $('<button class="editor-button editor-button-small" style="float: right;">' + RED._('sidebar.project.projectSettings.edit') + '</button>')
.appendTo(title) .appendTo(title)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
@ -750,7 +750,7 @@ RED.projects.settings = (function() {
// Flow files // Flow files
row = $('<div class="user-settings-row"></div>').appendTo(filesContainer); row = $('<div class="user-settings-row"></div>').appendTo(filesContainer);
$('<label for=""></label>').text('Flow').appendTo(row); $('<label for=""></label>').text(RED._("sidebar.project.projectSettings.flow")).appendTo(row);
var flowFileLabel = $('<div class="uneditable-input" style="padding:0">').appendTo(row); var flowFileLabel = $('<div class="uneditable-input" style="padding:0">').appendTo(row);
var flowFileLabelText = $('<span style="display:inline-block; padding: 6px">').text(activeProject.files.flow).appendTo(flowFileLabel); var flowFileLabelText = $('<span style="display:inline-block; padding: 6px">').text(activeProject.files.flow).appendTo(flowFileLabel);
@ -787,7 +787,7 @@ RED.projects.settings = (function() {
}) })
row = $('<div class="user-settings-row"></div>').appendTo(filesContainer); row = $('<div class="user-settings-row"></div>').appendTo(filesContainer);
$('<label for=""></label>').text('Credentials').appendTo(row); $('<label for=""></label>').text(RED._("sidebar.project.projectSettings.credentials")).appendTo(row);
var credFileLabel = $('<div class="uneditable-input">').text(activeProject.files.credentials).appendTo(row); var credFileLabel = $('<div class="uneditable-input">').text(activeProject.files.credentials).appendTo(row);
var credFileInput = $('<div class="uneditable-input">').text(activeProject.files.credentials).hide().insertAfter(credFileLabel); var credFileInput = $('<div class="uneditable-input">').text(activeProject.files.credentials).hide().insertAfter(credFileLabel);
@ -899,12 +899,12 @@ RED.projects.settings = (function() {
var credentialFormRows = $('<div>',{style:"margin-top:10px"}).hide().appendTo(credentialStateLabel); var credentialFormRows = $('<div>',{style:"margin-top:10px"}).hide().appendTo(credentialStateLabel);
var credentialSetLabel = $('<div style="margin: 20px 0 10px 5px;">Set the encryption key:</div>').hide().appendTo(credentialFormRows); var credentialSetLabel = $('<div style="margin: 20px 0 10px 5px;">' + RED._("sidebar.project.projectSettings.setTheEncryptionKey") + '</div>').hide().appendTo(credentialFormRows);
var credentialChangeLabel = $('<div style="margin: 20px 0 10px 5px;">Change the encryption key:</div>').hide().appendTo(credentialFormRows); var credentialChangeLabel = $('<div style="margin: 20px 0 10px 5px;">' + RED._("sidebar.project.projectSettings.changeTheEncryptionKey") + '</div>').hide().appendTo(credentialFormRows);
var credentialResetLabel = $('<div style="margin: 20px 0 10px 5px;">Reset the encryption key:</div>').hide().appendTo(credentialFormRows); var credentialResetLabel = $('<div style="margin: 20px 0 10px 5px;">' + RED._("sidebar.project.projectSettings.resetTheEncryptionKey") + '</div>').hide().appendTo(credentialFormRows);
var credentialSecretExistingRow = $('<div class="user-settings-row user-settings-row-credentials"></div>').appendTo(credentialFormRows); var credentialSecretExistingRow = $('<div class="user-settings-row user-settings-row-credentials"></div>').appendTo(credentialFormRows);
$('<label for=""></label>').text('Current key').appendTo(credentialSecretExistingRow); $('<label for=""></label>').text(RED._("sidebar.project.projectSettings.currentKey")).appendTo(credentialSecretExistingRow);
var credentialSecretExistingInput = $('<input type="password">').appendTo(credentialSecretExistingRow) var credentialSecretExistingInput = $('<input type="password">').appendTo(credentialSecretExistingRow)
.on("change keyup paste",function() { .on("change keyup paste",function() {
if (popover) { if (popover) {
@ -917,10 +917,10 @@ RED.projects.settings = (function() {
var credentialSecretNewRow = $('<div class="user-settings-row user-settings-row-credentials"></div>').appendTo(credentialFormRows); var credentialSecretNewRow = $('<div class="user-settings-row user-settings-row-credentials"></div>').appendTo(credentialFormRows);
$('<label for=""></label>').text('New key').appendTo(credentialSecretNewRow); $('<label for=""></label>').text(RED._("sidebar.project.projectSettings.newKey")).appendTo(credentialSecretNewRow);
var credentialSecretNewInput = $('<input type="password">').appendTo(credentialSecretNewRow).on("change keyup paste",checkFiles); var credentialSecretNewInput = $('<input type="password">').appendTo(credentialSecretNewRow).on("change keyup paste",checkFiles);
var credentialResetWarning = $('<div class="form-tips form-warning" style="margin: 10px;"><i class="fa fa-warning"></i> This will delete all existing credentials</div>').hide().appendTo(credentialFormRows); var credentialResetWarning = $('<div class="form-tips form-warning" style="margin: 10px;"><i class="fa fa-warning"></i>' + RED._("sidebar.project.projectSettings.credentialsAlert") + '</div>').hide().appendTo(credentialFormRows);
var hideEditForm = function() { var hideEditForm = function() {
@ -950,13 +950,13 @@ RED.projects.settings = (function() {
} }
var formButtons = $('<span class="button-row" style="position: relative; float: right; margin-right:0;"></span>').hide().appendTo(filesContainer); var formButtons = $('<span class="button-row" style="position: relative; float: right; margin-right:0;"></span>').hide().appendTo(filesContainer);
$('<button class="editor-button">Cancel</button>') $('<button class="editor-button">' + RED._("common.label.cancel") + '</button>')
.appendTo(formButtons) .appendTo(formButtons)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
hideEditForm(); hideEditForm();
}); });
var saveButton = $('<button class="editor-button">Save</button>') var saveButton = $('<button class="editor-button">' + RED._("common.label.save") + '</button>')
.appendTo(formButtons) .appendTo(formButtons)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
@ -1032,13 +1032,13 @@ RED.projects.settings = (function() {
var updateForm = function() { var updateForm = function() {
if (activeProject.settings.credentialSecretInvalid) { if (activeProject.settings.credentialSecretInvalid) {
credentialStateLabel.find(".user-settings-credentials-state-icon").removeClass().addClass("user-settings-credentials-state-icon fa fa-warning"); credentialStateLabel.find(".user-settings-credentials-state-icon").removeClass().addClass("user-settings-credentials-state-icon fa fa-warning");
credentialStateLabel.find(".user-settings-credentials-state").text("Invalid encryption key"); credentialStateLabel.find(".user-settings-credentials-state").text(RED._("sidebar.project.projectSettings.invalidEncryptionKey"));
} else if (activeProject.settings.credentialsEncrypted) { } else if (activeProject.settings.credentialsEncrypted) {
credentialStateLabel.find(".user-settings-credentials-state-icon").removeClass().addClass("user-settings-credentials-state-icon fa fa-lock"); credentialStateLabel.find(".user-settings-credentials-state-icon").removeClass().addClass("user-settings-credentials-state-icon fa fa-lock");
credentialStateLabel.find(".user-settings-credentials-state").text("Encryption enabled"); credentialStateLabel.find(".user-settings-credentials-state").text(RED._("sidebar.project.projectSettings.encryptionEnabled"));
} else { } else {
credentialStateLabel.find(".user-settings-credentials-state-icon").removeClass().addClass("user-settings-credentials-state-icon fa fa-unlock"); credentialStateLabel.find(".user-settings-credentials-state-icon").removeClass().addClass("user-settings-credentials-state-icon fa fa-unlock");
credentialStateLabel.find(".user-settings-credentials-state").text("Encryption disabled"); credentialStateLabel.find(".user-settings-credentials-state").text(RED._("sidebar.project.projectSettings.encryptionDisabled"));
} }
credentialSecretResetButton.toggleClass('disabled',!activeProject.settings.credentialSecretInvalid && !activeProject.settings.credentialsEncrypted); credentialSecretResetButton.toggleClass('disabled',!activeProject.settings.credentialSecretInvalid && !activeProject.settings.credentialsEncrypted);
credentialSecretResetButton.prop('disabled',!activeProject.settings.credentialSecretInvalid && !activeProject.settings.credentialsEncrypted); credentialSecretResetButton.prop('disabled',!activeProject.settings.credentialSecretInvalid && !activeProject.settings.credentialsEncrypted);
@ -1050,7 +1050,7 @@ RED.projects.settings = (function() {
function createLocalBranchListSection(activeProject,pane) { function createLocalBranchListSection(activeProject,pane) {
var localBranchContainer = $('<div class="user-settings-section"></div>').appendTo(pane); var localBranchContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
$('<h4></h4>').text("Branches").appendTo(localBranchContainer); $('<h4></h4>').text(RED._("sidebar.project.projectSettings.branches")).appendTo(localBranchContainer);
var row = $('<div class="user-settings-row projects-dialog-list"></div>').appendTo(localBranchContainer); var row = $('<div class="user-settings-row projects-dialog-list"></div>').appendTo(localBranchContainer);
@ -1063,7 +1063,7 @@ RED.projects.settings = (function() {
var container = $('<div class="projects-dialog-list-entry">').appendTo(row); var container = $('<div class="projects-dialog-list-entry">').appendTo(row);
if (entry.empty) { if (entry.empty) {
container.addClass('red-ui-search-empty'); container.addClass('red-ui-search-empty');
container.text("No branches"); container.text(RED._("sidebar.project.projectSettings.noBranches"));
return; return;
} }
if (entry.current) { if (entry.current) {
@ -1095,7 +1095,7 @@ RED.projects.settings = (function() {
.click(function(e) { .click(function(e) {
e.preventDefault(); e.preventDefault();
var spinner = utils.addSpinnerOverlay(row).addClass('projects-dialog-spinner-contain'); var spinner = utils.addSpinnerOverlay(row).addClass('projects-dialog-spinner-contain');
var notification = RED.notify("Are you sure you want to delete the local branch '"+entry.name+"'? This cannot be undone.", { var notification = RED.notify(RED._("sidebar.project.projectSettings.deleteConfirm", { name: entry.name }), {
type: "warning", type: "warning",
modal: true, modal: true,
fixed: true, fixed: true,
@ -1123,7 +1123,7 @@ RED.projects.settings = (function() {
}, },
400: { 400: {
'git_delete_branch_unmerged': function(error) { 'git_delete_branch_unmerged': function(error) {
notification = RED.notify("The local branch '"+entry.name+"' has unmerged changes that will be lost. Are you sure you want to delete it?", { notification = RED.notify(RED._("sidebar.project.projectSettings.unmergedConfirm", { name: entry.name }), {
type: "warning", type: "warning",
modal: true, modal: true,
fixed: true, fixed: true,
@ -1135,7 +1135,7 @@ RED.projects.settings = (function() {
notification.close(); notification.close();
} }
},{ },{
text: 'Delete unmerged branch', text: RED._("sidebar.project.projectSettings.deleteUnmergedBranch"),
click: function() { click: function() {
options.url += "?force=true"; options.url += "?force=true";
notification.close(); notification.close();
@ -1183,14 +1183,14 @@ RED.projects.settings = (function() {
} }
function createRemoteRepositorySection(activeProject,pane) { function createRemoteRepositorySection(activeProject,pane) {
$('<h3></h3>').text("Version Control").appendTo(pane); $('<h3></h3>').text(RED._("sidebar.project.projectSettings.versionControl")).appendTo(pane);
createLocalBranchListSection(activeProject,pane); createLocalBranchListSection(activeProject,pane);
var repoContainer = $('<div class="user-settings-section"></div>').appendTo(pane); var repoContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
var title = $('<h4></h4>').text("Git remotes").appendTo(repoContainer); var title = $('<h4></h4>').text(RED._("sidebar.project.projectSettings.gitRemotes")).appendTo(repoContainer);
var editRepoButton = $('<button class="editor-button editor-button-small" style="float: right; margin-right: 10px;">add remote</button>') var editRepoButton = $('<button class="editor-button editor-button-small" style="float: right; margin-right: 10px;">' + RED._("sidebar.project.projectSettings.addRemote") + '</button>')
.appendTo(title) .appendTo(title)
.click(function(evt) { .click(function(evt) {
editRepoButton.attr('disabled',true); editRepoButton.attr('disabled',true);
@ -1221,7 +1221,7 @@ RED.projects.settings = (function() {
var container = $('<div class="projects-dialog-list-entry">').appendTo(row); var container = $('<div class="projects-dialog-list-entry">').appendTo(row);
if (entry.empty) { if (entry.empty) {
container.addClass('red-ui-search-empty'); container.addClass('red-ui-search-empty');
container.text("No remotes"); container.text(RED._("sidebar.project.projectSettings.noRemotes"));
return; return;
} else { } else {
$('<span class="entry-icon"><i class="fa fa-globe"></i></span>').appendTo(container); $('<span class="entry-icon"><i class="fa fa-globe"></i></span>').appendTo(container);
@ -1240,7 +1240,7 @@ RED.projects.settings = (function() {
.click(function(e) { .click(function(e) {
e.preventDefault(); e.preventDefault();
var spinner = utils.addSpinnerOverlay(row).addClass('projects-dialog-spinner-contain'); var spinner = utils.addSpinnerOverlay(row).addClass('projects-dialog-spinner-contain');
var notification = RED.notify("Are you sure you want to delete the remote '"+entry.name+"'?", { var notification = RED.notify(RED._("sidebar.project.projectSettings.deleteRemoteConfrim", { name: entry.name }), {
type: "warning", type: "warning",
modal: true, modal: true,
fixed: true, fixed: true,
@ -1252,7 +1252,7 @@ RED.projects.settings = (function() {
notification.close(); notification.close();
} }
},{ },{
text: 'Delete remote', text: RED._("sidebar.project.projectSettings.deleteRemote"),
click: function() { click: function() {
notification.close(); notification.close();
@ -1315,10 +1315,10 @@ RED.projects.settings = (function() {
// var validRepo = /^(?:file|git|ssh|https?|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?[\w\.@:\/~_-]+(?:\.git)?(?:\/?|\#[\d\w\.\-_]+?)$/.test(remoteURLInput.val()); // var validRepo = /^(?:file|git|ssh|https?|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?[\w\.@:\/~_-]+(?:\.git)?(?:\/?|\#[\d\w\.\-_]+?)$/.test(remoteURLInput.val());
var validRepo = repo.length > 0 && !/\s/.test(repo); var validRepo = repo.length > 0 && !/\s/.test(repo);
if (/^https?:\/\/[^/]+@/i.test(repo)) { if (/^https?:\/\/[^/]+@/i.test(repo)) {
remoteURLLabel.text("Do not include the username/password in the url"); remoteURLLabel.text(RED._("sidebar.project.projectSettings.urlRule2"));
validRepo = false; validRepo = false;
} else { } else {
remoteURLLabel.text("https://, ssh:// or file://"); remoteURLLabel.text(RED._("sidebar.project.projectSettings.urlRule"));
} }
saveButton.attr('disabled',(!validName || !validRepo)) saveButton.attr('disabled',(!validName || !validRepo))
remoteNameInput.toggleClass('input-error',remoteNameInputChanged&&!validName); remoteNameInput.toggleClass('input-error',remoteNameInputChanged&&!validName);
@ -1332,22 +1332,22 @@ RED.projects.settings = (function() {
var remoteNameInputChanged = false; var remoteNameInputChanged = false;
var remoteURLInputChanged = false; var remoteURLInputChanged = false;
$('<div class="projects-dialog-list-dialog-header">').text('Add remote').appendTo(addRemoteDialog); $('<div class="projects-dialog-list-dialog-header">').text(RED._('sidebar.project.projectSettings.addRemote2')).appendTo(addRemoteDialog);
row = $('<div class="user-settings-row"></div>').appendTo(addRemoteDialog); row = $('<div class="user-settings-row"></div>').appendTo(addRemoteDialog);
$('<label for=""></label>').text('Remote name').appendTo(row); $('<label for=""></label>').text(RED._("sidebar.project.projectSettings.remoteName")).appendTo(row);
var remoteNameInput = $('<input type="text">').appendTo(row).on("change keyup paste",function() { var remoteNameInput = $('<input type="text">').appendTo(row).on("change keyup paste",function() {
remoteNameInputChanged = true; remoteNameInputChanged = true;
validateForm(); validateForm();
}); });
$('<label class="projects-edit-form-sublabel"><small>Must contain only A-Z 0-9 _ -</small></label>').appendTo(row).find("small"); $('<label class="projects-edit-form-sublabel"><small>' + RED._("sidebar.project.projectSettings.nameRule") + '</small></label>').appendTo(row).find("small");
row = $('<div class="user-settings-row"></div>').appendTo(addRemoteDialog); row = $('<div class="user-settings-row"></div>').appendTo(addRemoteDialog);
$('<label for=""></label>').text('URL').appendTo(row); $('<label for=""></label>').text(RED._("sidebar.project.projectSettings.url")).appendTo(row);
var remoteURLInput = $('<input type="text">').appendTo(row).on("change keyup paste",function() { var remoteURLInput = $('<input type="text">').appendTo(row).on("change keyup paste",function() {
remoteURLInputChanged = true; remoteURLInputChanged = true;
validateForm() validateForm()
}); });
var remoteURLLabel = $('<label class="projects-edit-form-sublabel"><small>https://, ssh:// or file://</small></label>').appendTo(row).find("small"); var remoteURLLabel = $('<label class="projects-edit-form-sublabel"><small>' + RED._("sidebar.project.projectSettings.urlRule") +'</small></label>').appendTo(row).find("small");
var hideEditForm = function() { var hideEditForm = function() {
editRepoButton.attr('disabled',false); editRepoButton.attr('disabled',false);
@ -1361,13 +1361,13 @@ RED.projects.settings = (function() {
} }
var formButtons = $('<span class="button-row" style="position: relative; float: right; margin: 10px;"></span>') var formButtons = $('<span class="button-row" style="position: relative; float: right; margin: 10px;"></span>')
.appendTo(addRemoteDialog); .appendTo(addRemoteDialog);
$('<button class="editor-button">Cancel</button>') $('<button class="editor-button">' + RED._("common.label.cancel") + '</button>')
.appendTo(formButtons) .appendTo(formButtons)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
hideEditForm(); hideEditForm();
}); });
var saveButton = $('<button class="editor-button">Add remote</button>') var saveButton = $('<button class="editor-button">' + RED._("sidebar.project.projectSettings.addRemote2") + '</button>')
.appendTo(formButtons) .appendTo(formButtons)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
@ -1484,19 +1484,19 @@ RED.projects.settings = (function() {
utils = _utils; utils = _utils;
addPane({ addPane({
id:'main', id:'main',
title: "Project", // TODO: nls title: RED._("sidebar.project.name"),
get: createMainPane, get: createMainPane,
close: function() { } close: function() { }
}); });
addPane({ addPane({
id:'deps', id:'deps',
title: "Dependencies", // TODO: nls title: RED._("sidebar.project.dependencies"),
get: createDependenciesPane, get: createDependenciesPane,
close: function() { } close: function() { }
}); });
addPane({ addPane({
id:'settings', id:'settings',
title: "Settings", // TODO: nls title: RED._("sidebar.project.settings"),
get: createSettingsPane, get: createSettingsPane,
close: function() { close: function() {
if (popover) { if (popover) {

View File

@ -24,18 +24,18 @@ RED.projects.userSettings = (function() {
var currentGitSettings = RED.settings.get('git') || {}; var currentGitSettings = RED.settings.get('git') || {};
currentGitSettings.user = currentGitSettings.user || {}; currentGitSettings.user = currentGitSettings.user || {};
var title = $('<h3></h3>').text("Committer Details").appendTo(pane); var title = $('<h3></h3>').text(RED._("editor:sidebar.project.userSettings.committerDetail")).appendTo(pane);
var gitconfigContainer = $('<div class="user-settings-section"></div>').appendTo(pane); var gitconfigContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
$('<div style="color:#aaa;"></div>').appendTo(gitconfigContainer).text("Leave blank to use system default"); $('<div style="color:#aaa;"></div>').appendTo(gitconfigContainer).text(RED._("editor:sidebar.project.userSettings.committerTip"));
var row = $('<div class="user-settings-row"></div>').appendTo(gitconfigContainer); var row = $('<div class="user-settings-row"></div>').appendTo(gitconfigContainer);
$('<label for=""></label>').text('Username').appendTo(row); $('<label for=""></label>').text(RED._("editor:sidebar.project.userSettings.userName")).appendTo(row);
gitUsernameInput = $('<input type="text">').appendTo(row); gitUsernameInput = $('<input type="text">').appendTo(row);
gitUsernameInput.val(currentGitSettings.user.name||""); gitUsernameInput.val(currentGitSettings.user.name||"");
row = $('<div class="user-settings-row"></div>').appendTo(gitconfigContainer); row = $('<div class="user-settings-row"></div>').appendTo(gitconfigContainer);
$('<label for=""></label>').text('Email').appendTo(row); $('<label for=""></label>').text(RED._("editor:sidebar.project.userSettings.email")).appendTo(row);
gitEmailInput = $('<input type="text">').appendTo(row); gitEmailInput = $('<input type="text">').appendTo(row);
gitEmailInput.val(currentGitSettings.user.email||""); gitEmailInput.val(currentGitSettings.user.email||"");
} }
@ -44,10 +44,10 @@ RED.projects.userSettings = (function() {
function createSSHKeySection(pane) { function createSSHKeySection(pane) {
var container = $('<div class="user-settings-section"></div>').appendTo(pane); var container = $('<div class="user-settings-section"></div>').appendTo(pane);
var popover; var popover;
var title = $('<h3></h3>').text("SSH Keys").appendTo(container); var title = $('<h3></h3>').text(RED._("editor:sidebar.project.userSettings.sshKeys")).appendTo(container);
var subtitle = $('<div style="color:#aaa;"></div>').appendTo(container).text("Allows you to create secure connections to remote git repositories."); var subtitle = $('<div style="color:#aaa;"></div>').appendTo(container).text(RED._("editor:sidebar.project.userSettings.sshKeysTip"));
var addKeyButton = $('<button id="user-settings-gitconfig-add-key" class="editor-button editor-button-small" style="float: right; margin-right: 10px;">add key</button>') var addKeyButton = $('<button id="user-settings-gitconfig-add-key" class="editor-button editor-button-small" style="float: right; margin-right: 10px;">'+RED._("editor:sidebar.project.userSettings.add")+'</button>')
.appendTo(subtitle) .appendTo(subtitle)
.click(function(evt) { .click(function(evt) {
addKeyButton.attr('disabled',true); addKeyButton.attr('disabled',true);
@ -72,9 +72,9 @@ RED.projects.userSettings = (function() {
var validPassphrase = passphrase.length === 0 || passphrase.length >= 8; var validPassphrase = passphrase.length === 0 || passphrase.length >= 8;
passphraseInput.toggleClass('input-error',!validPassphrase); passphraseInput.toggleClass('input-error',!validPassphrase);
if (!validPassphrase) { if (!validPassphrase) {
passphraseInputSubLabel.text("Passphrase too short"); passphraseInputSubLabel.text(RED._("editor:sidebar.project.userSettings.passphraseShort"));
} else if (passphrase.length === 0) { } else if (passphrase.length === 0) {
passphraseInputSubLabel.text("Optional"); passphraseInputSubLabel.text(RED._("editor:sidebar.project.userSettings.optional"));
} else { } else {
passphraseInputSubLabel.text(""); passphraseInputSubLabel.text("");
} }
@ -91,11 +91,11 @@ RED.projects.userSettings = (function() {
var row = $('<div class="user-settings-row"></div>').appendTo(container); var row = $('<div class="user-settings-row"></div>').appendTo(container);
var addKeyDialog = $('<div class="projects-dialog-list-dialog"></div>').hide().appendTo(row); var addKeyDialog = $('<div class="projects-dialog-list-dialog"></div>').hide().appendTo(row);
$('<div class="projects-dialog-list-dialog-header">').text('Add SSH Key').appendTo(addKeyDialog); $('<div class="projects-dialog-list-dialog-header">').text(RED._("editor:sidebar.project.userSettings.addSshKey")).appendTo(addKeyDialog);
var addKeyDialogBody = $('<div>').appendTo(addKeyDialog); var addKeyDialogBody = $('<div>').appendTo(addKeyDialog);
row = $('<div class="user-settings-row"></div>').appendTo(addKeyDialogBody); row = $('<div class="user-settings-row"></div>').appendTo(addKeyDialogBody);
$('<div style="color:#aaa;"></div>').appendTo(row).text("Generate a new public/private key pair"); $('<div style="color:#aaa;"></div>').appendTo(row).text(RED._("editor:sidebar.project.userSettings.addSshKeyTip"));
// var bg = $('<div></div>',{class:"button-group", style:"text-align: center"}).appendTo(row); // var bg = $('<div></div>',{class:"button-group", style:"text-align: center"}).appendTo(row);
// var addLocalButton = $('<button class="editor-button toggle selected">use local key</button>').appendTo(bg); // var addLocalButton = $('<button class="editor-button toggle selected">use local key</button>').appendTo(bg);
// var uploadButton = $('<button class="editor-button toggle">upload key</button>').appendTo(bg); // var uploadButton = $('<button class="editor-button toggle">upload key</button>').appendTo(bg);
@ -125,19 +125,19 @@ RED.projects.userSettings = (function() {
row = $('<div class="user-settings-row"></div>').appendTo(addKeyDialogBody); row = $('<div class="user-settings-row"></div>').appendTo(addKeyDialogBody);
$('<label for=""></label>').text('Name').appendTo(row); $('<label for=""></label>').text(RED._("editor:sidebar.project.userSettings.name")).appendTo(row);
var keyNameInputChanged = false; var keyNameInputChanged = false;
var keyNameInput = $('<input type="text">').appendTo(row).on("change keyup paste",function() { var keyNameInput = $('<input type="text">').appendTo(row).on("change keyup paste",function() {
keyNameInputChanged = true; keyNameInputChanged = true;
validateForm(); validateForm();
}); });
$('<label class="projects-edit-form-sublabel"><small>Must contain only A-Z 0-9 _ -</small></label>').appendTo(row).find("small"); $('<label class="projects-edit-form-sublabel"><small>'+RED._("editor:sidebar.project.userSettings.nameRule")+'</small></label>').appendTo(row).find("small");
var generateKeyPane = $('<div>').appendTo(addKeyDialogBody); var generateKeyPane = $('<div>').appendTo(addKeyDialogBody);
row = $('<div class="user-settings-row"></div>').appendTo(generateKeyPane); row = $('<div class="user-settings-row"></div>').appendTo(generateKeyPane);
$('<label for=""></label>').text('Passphrase').appendTo(row); $('<label for=""></label>').text(RED._("editor:sidebar.project.userSettings.passphrase")).appendTo(row);
var passphraseInput = $('<input type="password">').appendTo(row).on("change keyup paste",validateForm); var passphraseInput = $('<input type="password">').appendTo(row).on("change keyup paste",validateForm);
var passphraseInputSubLabel = $('<label class="projects-edit-form-sublabel"><small>Optional</small></label>').appendTo(row).find("small"); var passphraseInputSubLabel = $('<label class="projects-edit-form-sublabel"><small>'+RED._("editor:sidebar.project.userSettings.optional")+'</small></label>').appendTo(row).find("small");
// var addLocalKeyPane = $('<div>').hide().appendTo(addKeyDialogBody); // var addLocalKeyPane = $('<div>').hide().appendTo(addKeyDialogBody);
// row = $('<div class="user-settings-row"></div>').appendTo(addLocalKeyPane); // row = $('<div class="user-settings-row"></div>').appendTo(addLocalKeyPane);
@ -179,13 +179,13 @@ RED.projects.userSettings = (function() {
} }
} }
var formButtons = $('<span class="button-row" style="position: relative; float: right; margin: 10px;"></span>').appendTo(addKeyDialog); var formButtons = $('<span class="button-row" style="position: relative; float: right; margin: 10px;"></span>').appendTo(addKeyDialog);
$('<button class="editor-button">Cancel</button>') $('<button class="editor-button">'+RED._("editor:sidebar.project.userSettings.cancel")+'</button>')
.appendTo(formButtons) .appendTo(formButtons)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
hideEditForm(); hideEditForm();
}); });
var saveButton = $('<button class="editor-button">Generate key</button>') var saveButton = $('<button class="editor-button">'+RED._("editor:sidebar.project.userSettings.generate")+'</button>')
.appendTo(formButtons) .appendTo(formButtons)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
@ -264,7 +264,7 @@ RED.projects.userSettings = (function() {
utils.sendRequest(options); utils.sendRequest(options);
var formButtons = $('<span class="button-row" style="position: relative; float: right; margin: 10px;"></span>').appendTo(row); var formButtons = $('<span class="button-row" style="position: relative; float: right; margin: 10px;"></span>').appendTo(row);
$('<button class="editor-button editor-button-small">Copy public key to clipboard</button>') $('<button class="editor-button editor-button-small">'+RED._("editor:sidebar.project.userSettings.copyPublicKey")+'</button>')
.appendTo(formButtons) .appendTo(formButtons)
.click(function(evt) { .click(function(evt) {
try { try {
@ -289,7 +289,7 @@ RED.projects.userSettings = (function() {
var container = $('<div class="projects-dialog-list-entry">').appendTo(row); var container = $('<div class="projects-dialog-list-entry">').appendTo(row);
if (entry.empty) { if (entry.empty) {
container.addClass('red-ui-search-empty'); container.addClass('red-ui-search-empty');
container.text("No SSH keys"); container.text(RED._("editor:sidebar.project.userSettings.noSshKeys"));
return; return;
} }
var topRow = $('<div class="projects-dialog-ssh-key-header">').appendTo(container); var topRow = $('<div class="projects-dialog-ssh-key-header">').appendTo(container);
@ -313,7 +313,7 @@ RED.projects.userSettings = (function() {
.click(function(e) { .click(function(e) {
e.stopPropagation(); e.stopPropagation();
var spinner = utils.addSpinnerOverlay(row).addClass('projects-dialog-spinner-contain'); var spinner = utils.addSpinnerOverlay(row).addClass('projects-dialog-spinner-contain');
var notification = RED.notify("Are you sure you want to delete the SSH key '"+entry.name+"'? This cannot be undone.", { var notification = RED.notify(RED._("editor:sidebar.project.userSettings.deleteConfirm", {name:entry.name}), {
type: 'warning', type: 'warning',
modal: true, modal: true,
fixed: true, fixed: true,
@ -326,7 +326,7 @@ RED.projects.userSettings = (function() {
} }
}, },
{ {
text: "Delete key", text: RED._("editor:sidebar.project.userSettings.delete"),
click: function() { click: function() {
notification.close(); notification.close();
var url = "settings/user/keys/"+entry.name; var url = "settings/user/keys/"+entry.name;
@ -400,7 +400,7 @@ RED.projects.userSettings = (function() {
utils = _utils; utils = _utils;
RED.userSettings.add({ RED.userSettings.add({
id:'gitconfig', id:'gitconfig',
title: "Git config", // TODO: nls title: RED._("editor:sidebar.project.userSettings.gitConfig"),
get: createSettingsPane, get: createSettingsPane,
close: function() { close: function() {
var currentGitSettings = RED.settings.get('git') || {}; var currentGitSettings = RED.settings.get('git') || {};

View File

@ -22,18 +22,18 @@ RED.projects = (function() {
function reportUnexpectedError(error) { function reportUnexpectedError(error) {
var notification; var notification;
if (error.error === 'git_missing_user') { if (error.error === 'git_missing_user') {
notification = RED.notify("<p>You Git client is not configured with a username/email.</p>",{ notification = RED.notify("<p>"+RED._("projects.errors.no-username-email")+"</p>",{
fixed: true, fixed: true,
type:'error', type:'error',
buttons: [ buttons: [
{ {
text: "Cancel", text: RED._("common.label.cancel"),
click: function() { click: function() {
notification.close(); notification.close();
} }
}, },
{ {
text: "Configure Git client", text: RED._("projects.config-git"),
click: function() { click: function() {
RED.userSettings.show('gitconfig'); RED.userSettings.show('gitconfig');
notification.close(); notification.close();
@ -43,13 +43,13 @@ RED.projects = (function() {
}) })
} else { } else {
console.log(error); console.log(error);
notification = RED.notify("<p>An unexpected error occurred:</p><p>"+error.message+"</p><small>code: "+error.error+"</small>",{ notification = RED.notify("<p>"+RED._("projects.errors.unexpected")+":</p><p>"+error.message+"</p><small>"+RED._("projects.errors.code")+": "+error.error+"</small>",{
fixed: true, fixed: true,
modal: true, modal: true,
type: 'error', type: 'error',
buttons: [ buttons: [
{ {
text: "Close", text: RED._("common.label.close"),
click: function() { click: function() {
notification.close(); notification.close();
} }
@ -75,14 +75,14 @@ RED.projects = (function() {
migrateProjectHeader.appendTo(container); migrateProjectHeader.appendTo(container);
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container); var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
$('<p>').text("Hello! We have introduced 'projects' to Node-RED.").appendTo(body); $('<p>').text(RED._("projects.welcome.hello")).appendTo(body);
$('<p>').text("This is a new way for you to manage your flow files and includes version control of your flows.").appendTo(body); $('<p>').text(RED._("projects.welcome.desc0")).appendTo(body);
$('<p>').text("To get started you can create your first project or clone an existing project from a git repository.").appendTo(body); $('<p>').text(RED._("projects.welcome.desc1")).appendTo(body);
$('<p>').text("If you are not sure, you can skip this for now. You will still be able to create your first project from the 'Projects' menu at any time.").appendTo(body); $('<p>').text(RED._("projects.welcome.desc2")).appendTo(body);
var row = $('<div style="text-align: center"></div>').appendTo(body); var row = $('<div style="text-align: center"></div>').appendTo(body);
var createAsEmpty = $('<button data-type="empty" class="editor-button projects-dialog-screen-create-type"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-asterisk"></i><br/>Create Project</button>').appendTo(row); var createAsEmpty = $('<button data-type="empty" class="editor-button projects-dialog-screen-create-type"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-asterisk"></i><br/>'+RED._("projects.welcome.create")+'</button>').appendTo(row);
var createAsClone = $('<button data-type="clone" class="editor-button projects-dialog-screen-create-type"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-git"></i><br/>Clone Repository</button>').appendTo(row); var createAsClone = $('<button data-type="clone" class="editor-button projects-dialog-screen-create-type"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-git"></i><br/>'+RED._("projects.welcome.clone")+'</button>').appendTo(row);
createAsEmpty.click(function(e) { createAsEmpty.click(function(e) {
e.preventDefault(); e.preventDefault();
@ -105,7 +105,7 @@ RED.projects = (function() {
buttons: [ buttons: [
{ {
// id: "clipboard-dialog-cancel", // id: "clipboard-dialog-cancel",
text: "Not right now", text: RED._("projects.welcome.not-right-now"),
click: function() { click: function() {
createProjectOptions = {}; createProjectOptions = {};
$( this ).dialog( "close" ); $( this ).dialog( "close" );
@ -139,23 +139,23 @@ RED.projects = (function() {
migrateProjectHeader.appendTo(container); migrateProjectHeader.appendTo(container);
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container); var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
$('<p>').text("Setup your version control client").appendTo(body); $('<p>').text(RED._("projects.git-config.setup")).appendTo(body);
$('<p>').text("Node-RED uses the open source tool Git for version control. It tracks changes to your project files and lets you push them to remote repositories.").appendTo(body); $('<p>').text(RED._("projects.git-config.desc0")).appendTo(body);
$('<p>').text("When you commit a set of changes, Git records who made the changes with a username and email address. The Username can be anything you want - it does not need to be your real name.").appendTo(body); $('<p>').text(RED._("projects.git-config.desc1")).appendTo(body);
if (isGlobalConfig) { if (isGlobalConfig) {
$('<p>').text("Your Git client is already configured with the details below.").appendTo(body); $('<p>').text(RED._("projects.git-config.desc2")).appendTo(body);
} }
$('<p>').text("You can change these settings later under the 'Git config' tab of the settings dialog.").appendTo(body); $('<p>').text(RED._("projects.git-config.desc3")).appendTo(body);
var row = $('<div class="form-row"></div>').appendTo(body); var row = $('<div class="form-row"></div>').appendTo(body);
$('<label for="">Username</label>').appendTo(row); $('<label for="">'+RED._("projects.git-config.username")+'</label>').appendTo(row);
gitUsernameInput = $('<input type="text">').val((existingGitSettings&&existingGitSettings.name)||"").appendTo(row); gitUsernameInput = $('<input type="text">').val((existingGitSettings&&existingGitSettings.name)||"").appendTo(row);
// $('<div style="position:relative;"></div>').text("This does not need to be your real name").appendTo(row); // $('<div style="position:relative;"></div>').text("This does not need to be your real name").appendTo(row);
gitUsernameInput.on("change keyup paste",validateForm); gitUsernameInput.on("change keyup paste",validateForm);
row = $('<div class="form-row"></div>').appendTo(body); row = $('<div class="form-row"></div>').appendTo(body);
$('<label for="">Email</label>').appendTo(row); $('<label for="">'+RED._("projects.git-config.email")+'</label>').appendTo(row);
gitEmailInput = $('<input type="text">').val((existingGitSettings&&existingGitSettings.email)||"").appendTo(row); gitEmailInput = $('<input type="text">').val((existingGitSettings&&existingGitSettings.email)||"").appendTo(row);
gitEmailInput.on("change keyup paste",validateForm); gitEmailInput.on("change keyup paste",validateForm);
// $('<div style="position:relative;"></div>').text("Something something email").appendTo(row); // $('<div style="position:relative;"></div>').text("Something something email").appendTo(row);
@ -168,14 +168,14 @@ RED.projects = (function() {
buttons: [ buttons: [
{ {
// id: "clipboard-dialog-cancel", // id: "clipboard-dialog-cancel",
text: "Back", text: RED._("common.label.back"),
click: function() { click: function() {
show('welcome'); show('welcome');
} }
}, },
{ {
id: "projects-dialog-git-config", id: "projects-dialog-git-config",
text: "Next", // TODO: nls text: RED._("common.label.next"),
class: "primary", class: "primary",
click: function() { click: function() {
var currentGitSettings = RED.settings.get('git') || {}; var currentGitSettings = RED.settings.get('git') || {};
@ -216,10 +216,10 @@ RED.projects = (function() {
migrateProjectHeader.appendTo(container); migrateProjectHeader.appendTo(container);
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container); var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
$('<p>').text("Create your project").appendTo(body); $('<p>').text(RED._("projects.project-details.create")).appendTo(body);
$('<p>').text("A project is maintained as a Git repository. It makes it much easier to share your flows with others and to collaborate on them.").appendTo(body); $('<p>').text(RED._("projects.project-details.desc0")).appendTo(body);
$('<p>').text("You can create multiple projects and quickly switch between them from the editor.").appendTo(body); $('<p>').text(RED._("projects.project-details.desc1")).appendTo(body);
$('<p>').text("To begin, your project needs a name and an optional description.").appendTo(body); $('<p>').text(RED._("projects.project-details.desc2")).appendTo(body);
var validateForm = function() { var validateForm = function() {
var projectName = projectNameInput.val(); var projectName = projectNameInput.val();
@ -236,14 +236,14 @@ RED.projects = (function() {
projectNameValid = false; projectNameValid = false;
valid = false; valid = false;
if (projectList[projectName]) { if (projectList[projectName]) {
projectNameSublabel.text("Project already exists"); projectNameSublabel.text(RED._("projects.project-details.already-exists"));
} else { } else {
projectNameSublabel.text("Must contain only A-Z 0-9 _ -"); projectNameSublabel.text(RED._("projects.project-details.must-contain"));
} }
} else { } else {
projectNameInput.removeClass("input-error"); projectNameInput.removeClass("input-error");
$('<i style="margin-top: 8px;" class="fa fa-check"></i>').appendTo(projectNameStatus); $('<i style="margin-top: 8px;" class="fa fa-check"></i>').appendTo(projectNameStatus);
projectNameSublabel.text("Must contain only A-Z 0-9 _ -"); projectNameSublabel.text(RED._("projects.project-details.must-contain"));
projectNameValid = true; projectNameValid = true;
} }
projectNameLastChecked = projectName; projectNameLastChecked = projectName;
@ -253,7 +253,7 @@ RED.projects = (function() {
} }
var row = $('<div class="form-row"></div>').appendTo(body); var row = $('<div class="form-row"></div>').appendTo(body);
$('<label for="projects-dialog-screen-create-project-name">Project name</label>').appendTo(row); $('<label for="projects-dialog-screen-create-project-name">'+RED._("projects.project-details.project-name")+'</label>').appendTo(row);
var subrow = $('<div style="position:relative;"></div>').appendTo(row); var subrow = $('<div style="position:relative;"></div>').appendTo(row);
projectNameInput = $('<input id="projects-dialog-screen-create-project-name" type="text"></input>').val(createProjectOptions.name||"").appendTo(subrow); projectNameInput = $('<input id="projects-dialog-screen-create-project-name" type="text"></input>').val(createProjectOptions.name||"").appendTo(subrow);
@ -283,13 +283,13 @@ RED.projects = (function() {
checkProjectName = null; checkProjectName = null;
},300) },300)
}); });
projectNameSublabel = $('<label class="projects-edit-form-sublabel"><small>Must contain only A-Z 0-9 _ -</small></label>').appendTo(row).find("small"); projectNameSublabel = $('<label class="projects-edit-form-sublabel"><small>'+RED._("projects.project-details.must-contain")+'</small></label>').appendTo(row).find("small");
// Empty Project // Empty Project
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(body); row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(body);
$('<label for="projects-dialog-screen-create-project-desc">Description</label>').appendTo(row); $('<label for="projects-dialog-screen-create-project-desc">'+RED._("projects.project-details.desc")+'</label>').appendTo(row);
projectSummaryInput = $('<input id="projects-dialog-screen-create-project-desc" type="text">').val(createProjectOptions.summary||"").appendTo(row); projectSummaryInput = $('<input id="projects-dialog-screen-create-project-desc" type="text">').val(createProjectOptions.summary||"").appendTo(row);
$('<label class="projects-edit-form-sublabel"><small>Optional</small></label>').appendTo(row); $('<label class="projects-edit-form-sublabel"><small>'+RED._("projects.project-details.opt")+'</small></label>').appendTo(row);
setTimeout(function() { setTimeout(function() {
projectNameInput.focus(); projectNameInput.focus();
@ -300,7 +300,7 @@ RED.projects = (function() {
buttons: function(options) { buttons: function(options) {
return [ return [
{ {
text: "Back", text: RED._("common.label.back"),
click: function() { click: function() {
show('git-config'); show('git-config');
} }
@ -308,7 +308,7 @@ RED.projects = (function() {
{ {
id: "projects-dialog-create-name", id: "projects-dialog-create-name",
disabled: true, disabled: true,
text: "Next", // TODO: nls text: RED._("common.label.next"),
class: "primary disabled", class: "primary disabled",
click: function() { click: function() {
createProjectOptions.name = projectNameInput.val(); createProjectOptions.name = projectNameInput.val();
@ -344,8 +344,8 @@ RED.projects = (function() {
var container = $('<div class="projects-dialog-screen-start"></div>'); var container = $('<div class="projects-dialog-screen-start"></div>');
migrateProjectHeader.appendTo(container); migrateProjectHeader.appendTo(container);
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container); var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
$('<p>').text("Clone a project").appendTo(body); $('<p>').text(RED._("projects.clone-project.clone")).appendTo(body);
$('<p>').text("If you already have a git repository containing a project, you can clone it to get started.").appendTo(body); $('<p>').text(RED._("projects.clone-project.desc0")).appendTo(body);
var projectList = null; var projectList = null;
var pendingFormValidation = false; var pendingFormValidation = false;
@ -376,14 +376,14 @@ RED.projects = (function() {
projectNameValid = false; projectNameValid = false;
valid = false; valid = false;
if (projectList[projectName]) { if (projectList[projectName]) {
projectNameSublabel.text("Project already exists"); projectNameSublabel.text(RED._("projects.clone-project.already-exists"));
} else { } else {
projectNameSublabel.text("Must contain only A-Z 0-9 _ -"); projectNameSublabel.text(RED._("projects.clone-project.must-contain"));
} }
} else { } else {
projectNameInput.removeClass("input-error"); projectNameInput.removeClass("input-error");
$('<i style="margin-top: 8px;" class="fa fa-check"></i>').appendTo(projectNameStatus); $('<i style="margin-top: 8px;" class="fa fa-check"></i>').appendTo(projectNameStatus);
projectNameSublabel.text("Must contain only A-Z 0-9 _ -"); projectNameSublabel.text(RED._("projects.clone-project.must-contain"));
projectNameValid = true; projectNameValid = true;
} }
projectNameLastChecked = projectName; projectNameLastChecked = projectName;
@ -395,7 +395,7 @@ RED.projects = (function() {
// var validRepo = /^(?:file|git|ssh|https?|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?[\w\.@:\/~_-]+(?:\/?|\#[\d\w\.\-_]+?)$/.test(repo); // var validRepo = /^(?:file|git|ssh|https?|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?[\w\.@:\/~_-]+(?:\/?|\#[\d\w\.\-_]+?)$/.test(repo);
var validRepo = repo.length > 0 && !/\s/.test(repo); var validRepo = repo.length > 0 && !/\s/.test(repo);
if (/^https?:\/\/[^/]+@/i.test(repo)) { if (/^https?:\/\/[^/]+@/i.test(repo)) {
$("#projects-dialog-screen-create-project-repo-label small").text("Do not include the username/password in the url"); $("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.clone-project.no-info-in-url"));
validRepo = false; validRepo = false;
} }
if (!validRepo) { if (!validRepo) {
@ -426,7 +426,7 @@ RED.projects = (function() {
var row; var row;
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty projects-dialog-screen-create-row-clone"></div>').appendTo(body); row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty projects-dialog-screen-create-row-clone"></div>').appendTo(body);
$('<label for="projects-dialog-screen-create-project-name">Project name</label>').appendTo(row); $('<label for="projects-dialog-screen-create-project-name">'+RED._("projects.clone-project.project-name")+'</label>').appendTo(row);
var subrow = $('<div style="position:relative;"></div>').appendTo(row); var subrow = $('<div style="position:relative;"></div>').appendTo(row);
projectNameInput = $('<input id="projects-dialog-screen-create-project-name" type="text"></input>').appendTo(subrow); projectNameInput = $('<input id="projects-dialog-screen-create-project-name" type="text"></input>').appendTo(subrow);
@ -456,19 +456,19 @@ RED.projects = (function() {
checkProjectName = null; checkProjectName = null;
},300) },300)
}); });
projectNameSublabel = $('<label class="projects-edit-form-sublabel"><small>Must contain only A-Z 0-9 _ -</small></label>').appendTo(row).find("small"); projectNameSublabel = $('<label class="projects-edit-form-sublabel"><small>'+RED._("projects.clone-project.must-contain")+'</small></label>').appendTo(row).find("small");
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(body); row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(body);
$('<label for="projects-dialog-screen-create-project-repo">Git repository URL</label>').appendTo(row); $('<label for="projects-dialog-screen-create-project-repo">'+RED._("projects.clone-project.git-url")+'</label>').appendTo(row);
projectRepoInput = $('<input id="projects-dialog-screen-create-project-repo" type="text" placeholder="https://git.example.com/path/my-project.git"></input>').appendTo(row); projectRepoInput = $('<input id="projects-dialog-screen-create-project-repo" type="text" placeholder="https://git.example.com/path/my-project.git"></input>').appendTo(row);
$('<label id="projects-dialog-screen-create-project-repo-label" class="projects-edit-form-sublabel"><small>https://, ssh:// or file://</small></label>').appendTo(row); $('<label id="projects-dialog-screen-create-project-repo-label" class="projects-edit-form-sublabel"><small>'+RED._("projects.clone-project.protocols")+'</small></label>').appendTo(row);
var projectRepoChanged = false; var projectRepoChanged = false;
var lastProjectRepo = ""; var lastProjectRepo = "";
projectRepoInput.on("change keyup paste",function() { projectRepoInput.on("change keyup paste",function() {
projectRepoChanged = true; projectRepoChanged = true;
var repo = $(this).val(); var repo = $(this).val();
if (lastProjectRepo !== repo) { if (lastProjectRepo !== repo) {
$("#projects-dialog-screen-create-project-repo-label small").text("https://, ssh:// or file://"); $("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.clone-project.protocols"));
} }
lastProjectRepo = repo; lastProjectRepo = repo;
@ -486,24 +486,24 @@ RED.projects = (function() {
var cloneAuthRows = $('<div class="projects-dialog-screen-create-row"></div>').appendTo(body); var cloneAuthRows = $('<div class="projects-dialog-screen-create-row"></div>').appendTo(body);
row = $('<div class="form-row projects-dialog-screen-create-row-auth-error"></div>').hide().appendTo(cloneAuthRows); row = $('<div class="form-row projects-dialog-screen-create-row-auth-error"></div>').hide().appendTo(cloneAuthRows);
$('<div><i class="fa fa-warning"></i> Authentication failed</div>').appendTo(row); $('<div><i class="fa fa-warning"></i> '+RED._("projects.clone-project.auth-failed")+'</div>').appendTo(row);
// Repo credentials - username/password ---------------- // Repo credentials - username/password ----------------
row = $('<div class="hide form-row projects-dialog-screen-create-row-creds"></div>').hide().appendTo(cloneAuthRows); row = $('<div class="hide form-row projects-dialog-screen-create-row-creds"></div>').hide().appendTo(cloneAuthRows);
var subrow = $('<div style="width: calc(50% - 10px); display:inline-block;"></div>').appendTo(row); var subrow = $('<div style="width: calc(50% - 10px); display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-user">Username</label>').appendTo(subrow); $('<label for="projects-dialog-screen-create-project-repo-user">'+RED._("projects.clone-project.username")+'</label>').appendTo(subrow);
projectRepoUserInput = $('<input id="projects-dialog-screen-create-project-repo-user" type="text"></input>').appendTo(subrow); projectRepoUserInput = $('<input id="projects-dialog-screen-create-project-repo-user" type="text"></input>').appendTo(subrow);
subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row); subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-pass">Password</label>').appendTo(subrow); $('<label for="projects-dialog-screen-create-project-repo-pass">'+RED._("projects.clone-project.passwd")+'</label>').appendTo(subrow);
projectRepoPasswordInput = $('<input id="projects-dialog-screen-create-project-repo-pass" type="password"></input>').appendTo(subrow); projectRepoPasswordInput = $('<input id="projects-dialog-screen-create-project-repo-pass" type="password"></input>').appendTo(subrow);
// ----------------------------------------------------- // -----------------------------------------------------
// Repo credentials - key/passphrase ------------------- // Repo credentials - key/passphrase -------------------
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-sshkey"></div>').hide().appendTo(cloneAuthRows); row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-sshkey"></div>').hide().appendTo(cloneAuthRows);
subrow = $('<div style="width: calc(50% - 10px); display:inline-block;"></div>').appendTo(row); subrow = $('<div style="width: calc(50% - 10px); display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-passphrase">SSH Key</label>').appendTo(subrow); $('<label for="projects-dialog-screen-create-project-repo-passphrase">'+RED._("projects.clone-project.ssh-key")+'</label>').appendTo(subrow);
projectRepoSSHKeySelect = $("<select>",{style:"width: 100%"}).appendTo(subrow); projectRepoSSHKeySelect = $("<select>",{style:"width: 100%"}).appendTo(subrow);
$.getJSON("settings/user/keys", function(data) { $.getJSON("settings/user/keys", function(data) {
@ -523,14 +523,14 @@ RED.projects = (function() {
} }
}); });
subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row); subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-passphrase">Passphrase</label>').appendTo(subrow); $('<label for="projects-dialog-screen-create-project-repo-passphrase">'+RED._("projects.clone-project.passphrase")+'</label>').appendTo(subrow);
projectRepoPassphrase = $('<input id="projects-dialog-screen-create-project-repo-passphrase" type="password"></input>').appendTo(subrow); projectRepoPassphrase = $('<input id="projects-dialog-screen-create-project-repo-passphrase" type="password"></input>').appendTo(subrow);
subrow = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-sshkey"></div>').appendTo(cloneAuthRows); subrow = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-sshkey"></div>').appendTo(cloneAuthRows);
var sshwarningRow = $('<div class="projects-dialog-screen-create-row-auth-error-no-keys"></div>').hide().appendTo(subrow); var sshwarningRow = $('<div class="projects-dialog-screen-create-row-auth-error-no-keys"></div>').hide().appendTo(subrow);
$('<div class="form-row"><i class="fa fa-warning"></i> Before you can clone a repository over ssh you must add an SSH key to access it.</div>').appendTo(sshwarningRow); $('<div class="form-row"><i class="fa fa-warning"></i> '+RED._("projects.clone-project.ssh-key-desc")+'</div>').appendTo(sshwarningRow);
subrow = $('<div style="text-align: center">').appendTo(sshwarningRow); subrow = $('<div style="text-align: center">').appendTo(sshwarningRow);
$('<button class="editor-button">Add an ssh key</button>').appendTo(subrow).click(function(e) { $('<button class="editor-button">'+RED._("projects.clone-project.ssh-key-add")+'</button>').appendTo(subrow).click(function(e) {
e.preventDefault(); e.preventDefault();
$('#projects-dialog-cancel').click(); $('#projects-dialog-cancel').click();
RED.userSettings.show('gitconfig'); RED.userSettings.show('gitconfig');
@ -543,7 +543,7 @@ RED.projects = (function() {
// Secret - clone // Secret - clone
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(body); row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(body);
$('<label>Credentials encryption key</label>').appendTo(row); $('<label>'+RED._("projects.clone-project.credential-key")+'</label>').appendTo(row);
projectSecretInput = $('<input type="password"></input>').appendTo(row); projectSecretInput = $('<input type="password"></input>').appendTo(row);
@ -553,7 +553,7 @@ RED.projects = (function() {
buttons: function(options) { buttons: function(options) {
return [ return [
{ {
text: "Back", text: RED._("common.label.back"),
click: function() { click: function() {
show('git-config'); show('git-config');
} }
@ -561,7 +561,7 @@ RED.projects = (function() {
{ {
id: "projects-dialog-clone-project", id: "projects-dialog-clone-project",
disabled: true, disabled: true,
text: "Clone project", // TODO: nls text: RED._("common.label.clone"),
class: "primary disabled", class: "primary disabled",
click: function() { click: function() {
var projectType = $(".projects-dialog-screen-create-type.selected").data('type'); var projectType = $(".projects-dialog-screen-create-type.selected").data('type');
@ -585,7 +585,7 @@ RED.projects = (function() {
}; };
} }
else { else {
console.log("Error! Can't get selected SSH key path."); console.log(RED._("projects.clone-project.cant-get-ssh-key"));
return; return;
} }
} }
@ -602,7 +602,7 @@ RED.projects = (function() {
} }
$(".projects-dialog-screen-create-row-auth-error").hide(); $(".projects-dialog-screen-create-row-auth-error").hide();
$("#projects-dialog-screen-create-project-repo-label small").text("https://, ssh:// or file://"); $("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.clone-project.protocols"));
projectRepoUserInput.removeClass("input-error"); projectRepoUserInput.removeClass("input-error");
projectRepoPasswordInput.removeClass("input-error"); projectRepoPasswordInput.removeClass("input-error");
@ -622,22 +622,22 @@ RED.projects = (function() {
}, },
400: { 400: {
'project_exists': function(error) { 'project_exists': function(error) {
console.log("already exists"); console.log(RED._("projects.clone-project.already-exists"));
}, },
'git_error': function(error) { 'git_error': function(error) {
console.log("git error",error); console.log(RED._("projects.clone-project.git-error"),error);
}, },
'git_connection_failed': function(error) { 'git_connection_failed': function(error) {
projectRepoInput.addClass("input-error"); projectRepoInput.addClass("input-error");
$("#projects-dialog-screen-create-project-repo-label small").text("Connection failed"); $("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.clone-project.connection-failed"));
}, },
'git_not_a_repository': function(error) { 'git_not_a_repository': function(error) {
projectRepoInput.addClass("input-error"); projectRepoInput.addClass("input-error");
$("#projects-dialog-screen-create-project-repo-label small").text("Not a git repository"); $("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.clone-project.not-git-repo"));
}, },
'git_repository_not_found': function(error) { 'git_repository_not_found': function(error) {
projectRepoInput.addClass("input-error"); projectRepoInput.addClass("input-error");
$("#projects-dialog-screen-create-project-repo-label small").text("Repository not found"); $("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.clone-project.repo-not-found"));
}, },
'git_auth_failed': function(error) { 'git_auth_failed': function(error) {
$(".projects-dialog-screen-create-row-auth-error").show(); $(".projects-dialog-screen-create-row-auth-error").show();
@ -689,11 +689,11 @@ RED.projects = (function() {
migrateProjectHeader.appendTo(container); migrateProjectHeader.appendTo(container);
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container); var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
$('<p>').text("Create your project files").appendTo(body); $('<p>').text(RED._("projects.default-files.create")).appendTo(body);
$('<p>').text("A project contains your flow files, a README file and a package.json file.").appendTo(body); $('<p>').text(RED._("projects.default-files.desc0")).appendTo(body);
$('<p>').text("It can contain any other files you want to maintain in the Git repository.").appendTo(body); $('<p>').text(RED._("projects.default-files.desc1")).appendTo(body);
if (!options.existingProject && RED.settings.files) { if (!options.existingProject && RED.settings.files) {
$('<p>').text("Your existing flow and credential files will be copied into the project.").appendTo(body); $('<p>').text(RED._("projects.default-files.desc2")).appendTo(body);
} }
var validateForm = function() { var validateForm = function() {
@ -724,7 +724,7 @@ RED.projects = (function() {
$("#projects-dialog-create-default-files").prop('disabled',!valid).toggleClass('disabled ui-button-disabled ui-state-disabled',!valid); $("#projects-dialog-create-default-files").prop('disabled',!valid).toggleClass('disabled ui-button-disabled ui-state-disabled',!valid);
} }
var row = $('<div class="form-row"></div>').appendTo(body); var row = $('<div class="form-row"></div>').appendTo(body);
$('<label for="projects-dialog-screen-create-project-file">Flow file</label>').appendTo(row); $('<label for="projects-dialog-screen-create-project-file">'+RED._("projects.default-files.flow-file")+'</label>').appendTo(row);
var subrow = $('<div style="position:relative;"></div>').appendTo(row); var subrow = $('<div style="position:relative;"></div>').appendTo(row);
var defaultFlowFile = (createProjectOptions.files &&createProjectOptions.files.flow) || (RED.settings.files && RED.settings.files.flow)||"flow.json"; var defaultFlowFile = (createProjectOptions.files &&createProjectOptions.files.flow) || (RED.settings.files && RED.settings.files.flow)||"flow.json";
projectFlowFileInput = $('<input id="projects-dialog-screen-create-project-file" type="text">').val(defaultFlowFile) projectFlowFileInput = $('<input id="projects-dialog-screen-create-project-file" type="text">').val(defaultFlowFile)
@ -735,7 +735,7 @@ RED.projects = (function() {
var defaultCredentialsFile = (createProjectOptions.files &&createProjectOptions.files.credentials) || (RED.settings.files && RED.settings.files.credentials)||"flow_cred.json"; var defaultCredentialsFile = (createProjectOptions.files &&createProjectOptions.files.credentials) || (RED.settings.files && RED.settings.files.credentials)||"flow_cred.json";
row = $('<div class="form-row"></div>').appendTo(body); row = $('<div class="form-row"></div>').appendTo(body);
$('<label for="projects-dialog-screen-create-project-credfile">Credentials file</label>').appendTo(row); $('<label for="projects-dialog-screen-create-project-credfile">'+RED._("projects.default-files.credentials-file")+'</label>').appendTo(row);
subrow = $('<div style="position:relative;"></div>').appendTo(row); subrow = $('<div style="position:relative;"></div>').appendTo(row);
projectCredentialFileInput = $('<div style="width: 100%" class="uneditable-input" id="projects-dialog-screen-create-project-credentials">').text(defaultCredentialsFile) projectCredentialFileInput = $('<div style="width: 100%" class="uneditable-input" id="projects-dialog-screen-create-project-credentials">').text(defaultCredentialsFile)
.appendTo(subrow); .appendTo(subrow);
@ -752,7 +752,7 @@ RED.projects = (function() {
return [ return [
{ {
// id: "clipboard-dialog-cancel", // id: "clipboard-dialog-cancel",
text: options.existingProject?"Cancel":"Back", text: RED._(options.existingProject ? "common.label.cancel": "common.label.back"),
click: function() { click: function() {
if (options.existingProject) { if (options.existingProject) {
$(this).dialog('close'); $(this).dialog('close');
@ -763,7 +763,7 @@ RED.projects = (function() {
}, },
{ {
id: "projects-dialog-create-default-files", id: "projects-dialog-create-default-files",
text: "Next", // TODO: nls text: RED._("common.label.next"),
class: "primary", class: "primary",
click: function() { click: function() {
createProjectOptions.files = { createProjectOptions.files = {
@ -789,22 +789,22 @@ RED.projects = (function() {
migrateProjectHeader.appendTo(container); migrateProjectHeader.appendTo(container);
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container); var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
$('<p>').text("Setup encryption of your credentials file").appendTo(body); $('<p>').text(RED._("projects.encryption-config.setup")).appendTo(body);
if (options.existingProject) { if (options.existingProject) {
$('<p>').text("Your flow credentials file can be encrypted to keep its contents secure.").appendTo(body); $('<p>').text(RED._("projects.encryption-config.desc0")).appendTo(body);
$('<p>').text("If you want to store these credentials in a public Git repository, you must encrypt them by providing a secret key phrase.").appendTo(body); $('<p>').text(RED._("projects.encryption-config.desc1")).appendTo(body);
} else { } else {
if (RED.settings.flowEncryptionType === 'disabled') { if (RED.settings.flowEncryptionType === 'disabled') {
$('<p>').text("Your flow credentials file is not currently encrypted.").appendTo(body); $('<p>').text(RED._("projects.encryption-config.desc2")).appendTo(body);
$('<p>').text("That means its contents, such as passwords and access tokens, can be read by anyone with access to the file.").appendTo(body); $('<p>').text(RED._("projects.encryption-config.desc3")).appendTo(body);
$('<p>').text("If you want to store these credentials in a public Git repository, you must encrypt them by providing a secret key phrase.").appendTo(body); $('<p>').text(RED._("projects.encryption-config.desc4")).appendTo(body);
} else { } else {
if (RED.settings.flowEncryptionType === 'user') { if (RED.settings.flowEncryptionType === 'user') {
$('<p>').text("Your flow credentials file is currently encrypted using the credentialSecret property from your settings file as the key.").appendTo(body); $('<p>').text(RED._("projects.encryption-config.desc5")).appendTo(body);
} else if (RED.settings.flowEncryptionType === 'system') { } else if (RED.settings.flowEncryptionType === 'system') {
$('<p>').text("Your flow credentials file is currently encrypted using a system-generated key. You should provide a new secret key for this project.").appendTo(body); $('<p>').text(RED._("projects.encryption-config.desc6")).appendTo(body);
} }
$('<p>').text("The key will be stored separately from your project files. You will need to provide the key to use this project in another instance of Node-RED.").appendTo(body); $('<p>').text(RED._("projects.encryption-config.desc7")).appendTo(body);
} }
} }
@ -832,16 +832,16 @@ RED.projects = (function() {
var row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(body); var row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(body);
$('<label>Credentials</label>').appendTo(row); $('<label>'+RED._("projects.encryption-config.credentials")+'</label>').appendTo(row);
var credentialsBox = $('<div style="width: 550px">').appendTo(row); var credentialsBox = $('<div style="width: 550px">').appendTo(row);
var credentialsRightBox = $('<div style="min-height:150px; box-sizing: border-box; float: right; vertical-align: top; width: 331px; margin-left: -1px; padding: 15px; margin-top: -15px; border: 1px solid #ccc; border-radius: 3px; display: inline-block">').appendTo(credentialsBox); var credentialsRightBox = $('<div style="min-height:150px; box-sizing: border-box; float: right; vertical-align: top; width: 331px; margin-left: -1px; padding: 15px; margin-top: -15px; border: 1px solid #ccc; border-radius: 3px; display: inline-block">').appendTo(credentialsBox);
var credentialsLeftBox = $('<div style="vertical-align: top; width: 220px; display: inline-block">').appendTo(credentialsBox); var credentialsLeftBox = $('<div style="vertical-align: top; width: 220px; display: inline-block">').appendTo(credentialsBox);
var credentialsEnabledBox = $('<div class="form-row" style="padding: 7px 8px 3px 8px;border: 1px solid #ccc;border-radius: 4px;border-top-right-radius: 0;border-bottom-right-radius: 0;border-right-color: white;"></div>').appendTo(credentialsLeftBox); var credentialsEnabledBox = $('<div class="form-row" style="padding: 7px 8px 3px 8px;border: 1px solid #ccc;border-radius: 4px;border-top-right-radius: 0;border-bottom-right-radius: 0;border-right-color: white;"></div>').appendTo(credentialsLeftBox);
$('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" name="projects-encryption-type" value="enabled"> <i style="font-size: 1.4em; margin-right: 8px; vertical-align: middle; color: #888;" class="fa fa-lock"></i> <span style="vertical-align: middle;">Enable encryption</span></label>').appendTo(credentialsEnabledBox); $('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" name="projects-encryption-type" value="enabled"> <i style="font-size: 1.4em; margin-right: 8px; vertical-align: middle; color: #888;" class="fa fa-lock"></i> <span style="vertical-align: middle;">'+RED._("projects.encryption-config.enable")+'</span></label>').appendTo(credentialsEnabledBox);
var credentialsDisabledBox = $('<div class="form-row" style="padding: 7px 8px 3px 8px;border: 1px solid white;border-radius: 4px;border-top-right-radius: 0;border-bottom-right-radius: 0;border-right-color: #ccc; "></div>').appendTo(credentialsLeftBox); var credentialsDisabledBox = $('<div class="form-row" style="padding: 7px 8px 3px 8px;border: 1px solid white;border-radius: 4px;border-top-right-radius: 0;border-bottom-right-radius: 0;border-right-color: #ccc; "></div>').appendTo(credentialsLeftBox);
$('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" name="projects-encryption-type" value="disabled"> <i style="font-size: 1.4em; margin-right: 8px; vertical-align: middle; color: #888;" class="fa fa-unlock"></i> <span style="vertical-align: middle;">Disable encryption</span></label>').appendTo(credentialsDisabledBox); $('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" name="projects-encryption-type" value="disabled"> <i style="font-size: 1.4em; margin-right: 8px; vertical-align: middle; color: #888;" class="fa fa-unlock"></i> <span style="vertical-align: middle;">'+RED._("projects.encryption-config.disable")+'</span></label>').appendTo(credentialsDisabledBox);
credentialsLeftBox.find("input[name=projects-encryption-type]").click(function(e) { credentialsLeftBox.find("input[name=projects-encryption-type]").click(function(e) {
var val = $(this).val(); var val = $(this).val();
@ -876,15 +876,15 @@ RED.projects = (function() {
}) })
row = $('<div class="form-row projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox); row = $('<div class="form-row projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox);
$('<label class="projects-edit-form-inline-label '+((RED.settings.flowEncryptionType !== 'user')?'disabled':'')+'" style="margin-left: 5px"><input '+((RED.settings.flowEncryptionType !== 'user')?'disabled':'')+' type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" value="default" name="projects-encryption-key"> <span style="vertical-align: middle;">Copy over existing key</span></label>').appendTo(row); $('<label class="projects-edit-form-inline-label '+((RED.settings.flowEncryptionType !== 'user')?'disabled':'')+'" style="margin-left: 5px"><input '+((RED.settings.flowEncryptionType !== 'user')?RED._("projects.encryption-config.disabled"):'')+' type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" value="default" name="projects-encryption-key"> <span style="vertical-align: middle;">'+RED._("projects.encryption-config.copy")+'</span></label>').appendTo(row);
row = $('<div class="form-row projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox); row = $('<div class="form-row projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox);
$('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" value="custom" name="projects-encryption-key"> <span style="vertical-align: middle;">Use custom key</span></label>').appendTo(row); $('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" value="custom" name="projects-encryption-key"> <span style="vertical-align: middle;">'+RED._("projects.encryption-config.use-custom")+'</span></label>').appendTo(row);
row = $('<div class="projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox); row = $('<div class="projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox);
emptyProjectCredentialInput = $('<input disabled type="password" style="margin-left: 25px; width: calc(100% - 30px);"></input>').appendTo(row); emptyProjectCredentialInput = $('<input disabled type="password" style="margin-left: 25px; width: calc(100% - 30px);"></input>').appendTo(row);
emptyProjectCredentialInput.on("change keyup paste", validateForm); emptyProjectCredentialInput.on("change keyup paste", validateForm);
row = $('<div class="form-row projects-encryption-disabled-row"></div>').hide().appendTo(credentialsRightBox); row = $('<div class="form-row projects-encryption-disabled-row"></div>').hide().appendTo(credentialsRightBox);
$('<div class="" style="padding: 5px 20px;"><i class="fa fa-warning"></i> The credentials file will not be encrypted and its contents easily read</div>').appendTo(row); $('<div class="" style="padding: 5px 20px;"><i class="fa fa-warning"></i> '+RED._("projects.encryption-config.desc8")+'</div>').appendTo(row);
credentialsRightBox.find("input[name=projects-encryption-key]").click(function() { credentialsRightBox.find("input[name=projects-encryption-key]").click(function() {
var val = $(this).val(); var val = $(this).val();
@ -911,14 +911,14 @@ RED.projects = (function() {
return [ return [
{ {
// id: "clipboard-dialog-cancel", // id: "clipboard-dialog-cancel",
text: "Back", text: RED._("common.label.back"),
click: function() { click: function() {
show('default-files',options); show('default-files',options);
} }
}, },
{ {
id: "projects-dialog-create-encryption", id: "projects-dialog-create-encryption",
text: options.existingProject?"Create project files":"Create project", // TODO: nls text: RED._(options.existingProject?"projects.encryption-config.create-project-files":"projects.encryption-config.create-project"),
class: "primary disabled", class: "primary disabled",
disabled: true, disabled: true,
click: function() { click: function() {
@ -966,10 +966,10 @@ RED.projects = (function() {
}, },
400: { 400: {
'project_exists': function(error) { 'project_exists': function(error) {
console.log("already exists"); console.log(RED._("projects.encryption-config.already-exists"));
}, },
'git_error': function(error) { 'git_error': function(error) {
console.log("git error",error); console.log(RED._("projects.encryption-config.git-error"),error);
}, },
'git_connection_failed': function(error) { 'git_connection_failed': function(error) {
projectRepoInput.addClass("input-error"); projectRepoInput.addClass("input-error");
@ -978,7 +978,7 @@ RED.projects = (function() {
projectRepoUserInput.addClass("input-error"); projectRepoUserInput.addClass("input-error");
projectRepoPasswordInput.addClass("input-error"); projectRepoPasswordInput.addClass("input-error");
// getRepoAuthDetails(req); // getRepoAuthDetails(req);
console.log("git auth error",error); console.log(RED._("projects.encryption-config.git-auth-error"),error);
}, },
'*': function(error) { '*': function(error) {
reportUnexpectedError(error); reportUnexpectedError(error);
@ -1004,19 +1004,16 @@ RED.projects = (function() {
migrateProjectHeader.appendTo(container); migrateProjectHeader.appendTo(container);
var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container); var body = $('<div class="projects-dialog-screen-start-body"></div>').appendTo(container);
$('<p>').text("You have successfully created your first project!").appendTo(body); $('<p>').text(RED._("projects.create-success.success")).appendTo(body);
$('<p>').text("You can now continue to use Node-RED just as you always have.").appendTo(body); $('<p>').text(RED._("projects.create-success.desc0")).appendTo(body);
$('<p>').text("The 'info' tab in the sidebar shows you what your current active project is. "+ $('<p>').text(RED._("projects.create-success.desc1")).appendTo(body);
"The button next to the name can be used to access the project settings view.").appendTo(body); $('<p>').text(RED._("projects.create-success.desc2")).appendTo(body);
$('<p>').text("The 'history' tab in the sidebar can be used to view files that have changed "+
"in your project and to commit them. It shows you a complete history of your commits and "+
"allows you to push your changes to a remote repository.").appendTo(body);
return container; return container;
}, },
buttons: [ buttons: [
{ {
text: "Done", text: RED._("common.label.done"),
click: function() { click: function() {
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
@ -1043,7 +1040,7 @@ RED.projects = (function() {
var selectedProject; var selectedProject;
return { return {
title: "Projects", // TODO: NLS title: RED._("projects.create.projects"),
content: function(options) { content: function(options) {
var projectList = null; var projectList = null;
selectedProject = null; selectedProject = null;
@ -1077,14 +1074,14 @@ RED.projects = (function() {
projectNameValid = false; projectNameValid = false;
valid = false; valid = false;
if (projectList[projectName]) { if (projectList[projectName]) {
projectNameSublabel.text("Project already exists"); projectNameSublabel.text(RED._("projects.create.already-exists"));
} else { } else {
projectNameSublabel.text("Must contain only A-Z 0-9 _ -"); projectNameSublabel.text(RED._("projects.create.must-contain"));
} }
} else { } else {
projectNameInput.removeClass("input-error"); projectNameInput.removeClass("input-error");
$('<i style="margin-top: 8px;" class="fa fa-check"></i>').appendTo(projectNameStatus); $('<i style="margin-top: 8px;" class="fa fa-check"></i>').appendTo(projectNameStatus);
projectNameSublabel.text("Must contain only A-Z 0-9 _ -"); projectNameSublabel.text(RED._("projects.create.must-contain"));
projectNameValid = true; projectNameValid = true;
} }
projectNameLastChecked = projectName; projectNameLastChecked = projectName;
@ -1102,7 +1099,7 @@ RED.projects = (function() {
// var validRepo = /^(?:file|git|ssh|https?|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?[\w\.@:\/~_-]+(?:\/?|\#[\d\w\.\-_]+?)$/.test(repo); // var validRepo = /^(?:file|git|ssh|https?|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?[\w\.@:\/~_-]+(?:\/?|\#[\d\w\.\-_]+?)$/.test(repo);
var validRepo = repo.length > 0 && !/\s/.test(repo); var validRepo = repo.length > 0 && !/\s/.test(repo);
if (/^https?:\/\/[^/]+@/i.test(repo)) { if (/^https?:\/\/[^/]+@/i.test(repo)) {
$("#projects-dialog-screen-create-project-repo-label small").text("Do not include the username/password in the url"); $("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.create.no-info-in-url"));
validRepo = false; validRepo = false;
} }
if (!validRepo) { if (!validRepo) {
@ -1159,10 +1156,10 @@ RED.projects = (function() {
row = $('<div class="form-row button-group"></div>').appendTo(container); row = $('<div class="form-row button-group"></div>').appendTo(container);
var openProject = $('<button data-type="open" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-folder-open"></i><br/>Open Project</button>').appendTo(row); var openProject = $('<button data-type="open" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-folder-open"></i><br/>'+RED._("projects.create.open")+'</button>').appendTo(row);
var createAsEmpty = $('<button data-type="empty" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-asterisk"></i><br/>Create Project</button>').appendTo(row); var createAsEmpty = $('<button data-type="empty" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-asterisk"></i><br/>'+RED._("projects.create.create")+'</button>').appendTo(row);
// var createAsCopy = $('<button data-type="copy" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i class="fa fa-long-arrow-right fa-2x"></i><i class="fa fa-archive fa-2x"></i><br/>Copy existing</button>').appendTo(row); // var createAsCopy = $('<button data-type="copy" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i class="fa fa-long-arrow-right fa-2x"></i><i class="fa fa-archive fa-2x"></i><br/>Copy existing</button>').appendTo(row);
var createAsClone = $('<button data-type="clone" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-git"></i><br/>Clone Repository</button>').appendTo(row); var createAsClone = $('<button data-type="clone" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-git"></i><br/>'+RED._("projects.create.clone")+'</button>').appendTo(row);
// var createAsClone = $('<button data-type="clone" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-git fa-2x"></i><i class="fa fa-arrows-h fa-2x"></i><i class="fa fa-archive fa-2x"></i><br/>Clone Repository</button>').appendTo(row); // var createAsClone = $('<button data-type="clone" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-git fa-2x"></i><i class="fa fa-arrows-h fa-2x"></i><i class="fa fa-archive fa-2x"></i><br/>Clone Repository</button>').appendTo(row);
row.find(".projects-dialog-screen-create-type").click(function(evt) { row.find(".projects-dialog-screen-create-type").click(function(evt) {
evt.preventDefault(); evt.preventDefault();
@ -1173,9 +1170,9 @@ RED.projects = (function() {
validateForm(); validateForm();
projectNameInput.focus(); projectNameInput.focus();
switch ($(this).data('type')) { switch ($(this).data('type')) {
case "open": $("#projects-dialog-create").text("Open project"); break; case "open": $("#projects-dialog-create").text(RED._("projects.create.open")); break;
case "empty": $("#projects-dialog-create").text("Create project"); break; case "empty": $("#projects-dialog-create").text(RED._("projects.create.create")); break;
case "clone": $("#projects-dialog-create").text("Clone project"); break; case "clone": $("#projects-dialog-create").text(RED._("projects.create.clone")); break;
} }
}) })
@ -1201,7 +1198,7 @@ RED.projects = (function() {
}).appendTo(row); }).appendTo(row);
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty projects-dialog-screen-create-row-clone"></div>').appendTo(container); row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty projects-dialog-screen-create-row-clone"></div>').appendTo(container);
$('<label for="projects-dialog-screen-create-project-name">Project name</label>').appendTo(row); $('<label for="projects-dialog-screen-create-project-name">'+RED._("projects.create.project-name")+'</label>').appendTo(row);
var subrow = $('<div style="position:relative;"></div>').appendTo(row); var subrow = $('<div style="position:relative;"></div>').appendTo(row);
projectNameInput = $('<input id="projects-dialog-screen-create-project-name" type="text"></input>').appendTo(subrow); projectNameInput = $('<input id="projects-dialog-screen-create-project-name" type="text"></input>').appendTo(subrow);
@ -1231,16 +1228,16 @@ RED.projects = (function() {
checkProjectName = null; checkProjectName = null;
},300) },300)
}); });
projectNameSublabel = $('<label class="projects-edit-form-sublabel"><small>Must contain only A-Z 0-9 _ -</small></label>').appendTo(row).find("small"); projectNameSublabel = $('<label class="projects-edit-form-sublabel"><small>'+RED._("projects.create.must-contain")+'</small></label>').appendTo(row).find("small");
// Empty Project // Empty Project
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(container); row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(container);
$('<label for="projects-dialog-screen-create-project-desc">Description</label>').appendTo(row); $('<label for="projects-dialog-screen-create-project-desc">'+RED._("projects.create.desc")+'</label>').appendTo(row);
projectSummaryInput = $('<input id="projects-dialog-screen-create-project-desc" type="text">').appendTo(row); projectSummaryInput = $('<input id="projects-dialog-screen-create-project-desc" type="text">').appendTo(row);
$('<label class="projects-edit-form-sublabel"><small>Optional</small></label>').appendTo(row); $('<label class="projects-edit-form-sublabel"><small>'+RED._("projects.create.opt")+'</small></label>').appendTo(row);
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(container); row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(container);
$('<label for="projects-dialog-screen-create-project-file">Flow file</label>').appendTo(row); $('<label for="projects-dialog-screen-create-project-file">'+RED._("projects.create.flow-file")+'</label>').appendTo(row);
subrow = $('<div style="position:relative;"></div>').appendTo(row); subrow = $('<div style="position:relative;"></div>').appendTo(row);
projectFlowFileInput = $('<input id="projects-dialog-screen-create-project-file" type="text">').val("flow.json") projectFlowFileInput = $('<input id="projects-dialog-screen-create-project-file" type="text">').val("flow.json")
.on("change keyup paste",validateForm) .on("change keyup paste",validateForm)
@ -1249,16 +1246,16 @@ RED.projects = (function() {
$('<label class="projects-edit-form-sublabel"><small>*.json</small></label>').appendTo(row); $('<label class="projects-edit-form-sublabel"><small>*.json</small></label>').appendTo(row);
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(container); row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(container);
$('<label>Credentials</label>').appendTo(row); $('<label>'+RED._("projects.create.credentials")+'</label>').appendTo(row);
var credentialsBox = $('<div style="width: 550px">').appendTo(row); var credentialsBox = $('<div style="width: 550px">').appendTo(row);
var credentialsRightBox = $('<div style="min-height:150px; box-sizing: border-box; float: right; vertical-align: top; width: 331px; margin-left: -1px; padding: 15px; margin-top: -15px; border: 1px solid #ccc; border-radius: 3px; display: inline-block">').appendTo(credentialsBox); var credentialsRightBox = $('<div style="min-height:150px; box-sizing: border-box; float: right; vertical-align: top; width: 331px; margin-left: -1px; padding: 15px; margin-top: -15px; border: 1px solid #ccc; border-radius: 3px; display: inline-block">').appendTo(credentialsBox);
var credentialsLeftBox = $('<div style="vertical-align: top; width: 220px; display: inline-block">').appendTo(credentialsBox); var credentialsLeftBox = $('<div style="vertical-align: top; width: 220px; display: inline-block">').appendTo(credentialsBox);
var credentialsEnabledBox = $('<div class="form-row" style="padding: 7px 8px 3px 8px;border: 1px solid #ccc;border-radius: 4px;border-top-right-radius: 0;border-bottom-right-radius: 0;border-right-color: white;"></div>').appendTo(credentialsLeftBox); var credentialsEnabledBox = $('<div class="form-row" style="padding: 7px 8px 3px 8px;border: 1px solid #ccc;border-radius: 4px;border-top-right-radius: 0;border-bottom-right-radius: 0;border-right-color: white;"></div>').appendTo(credentialsLeftBox);
$('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" checked style="vertical-align: middle; margin-top:0; margin-right: 10px;" name="projects-encryption-type" value="enabled"> <i style="font-size: 1.4em; margin-right: 8px; vertical-align: middle; color: #888;" class="fa fa-lock"></i> <span style="vertical-align: middle;">Enable encryption</span></label>').appendTo(credentialsEnabledBox); $('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" checked style="vertical-align: middle; margin-top:0; margin-right: 10px;" name="projects-encryption-type" value="enabled"> <i style="font-size: 1.4em; margin-right: 8px; vertical-align: middle; color: #888;" class="fa fa-lock"></i> <span style="vertical-align: middle;">'+RED._("projects.create.enable-encryption")+'</span></label>').appendTo(credentialsEnabledBox);
var credentialsDisabledBox = $('<div class="form-row" style="padding: 7px 8px 3px 8px;border: 1px solid white;border-radius: 4px;border-top-right-radius: 0;border-bottom-right-radius: 0;border-right-color: #ccc; "></div>').appendTo(credentialsLeftBox); var credentialsDisabledBox = $('<div class="form-row" style="padding: 7px 8px 3px 8px;border: 1px solid white;border-radius: 4px;border-top-right-radius: 0;border-bottom-right-radius: 0;border-right-color: #ccc; "></div>').appendTo(credentialsLeftBox);
$('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" name="projects-encryption-type" value="disabled"> <i style="font-size: 1.4em; margin-right: 8px; vertical-align: middle; color: #888;" class="fa fa-unlock"></i> <span style="vertical-align: middle;">Disable encryption</span></label>').appendTo(credentialsDisabledBox); $('<label class="projects-edit-form-inline-label" style="margin-left: 5px"><input type="radio" style="vertical-align: middle; margin-top:0; margin-right: 10px;" name="projects-encryption-type" value="disabled"> <i style="font-size: 1.4em; margin-right: 8px; vertical-align: middle; color: #888;" class="fa fa-unlock"></i> <span style="vertical-align: middle;">'+RED._("projects.create.disable-encryption")+'</span></label>').appendTo(credentialsDisabledBox);
credentialsLeftBox.find("input[name=projects-encryption-type]").click(function(e) { credentialsLeftBox.find("input[name=projects-encryption-type]").click(function(e) {
var val = $(this).val(); var val = $(this).val();
@ -1292,15 +1289,15 @@ RED.projects = (function() {
}) })
row = $('<div class="form-row projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox); row = $('<div class="form-row projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox);
$('<label class="projects-edit-form-inline-label">Encryption key</label>').appendTo(row); $('<label class="projects-edit-form-inline-label">'+RED._("projects.create.encryption-key")+'</label>').appendTo(row);
// row = $('<div class="projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox); // row = $('<div class="projects-encryption-enabled-row"></div>').appendTo(credentialsRightBox);
emptyProjectCredentialInput = $('<input type="password"></input>').appendTo(row); emptyProjectCredentialInput = $('<input type="password"></input>').appendTo(row);
emptyProjectCredentialInput.on("change keyup paste", validateForm); emptyProjectCredentialInput.on("change keyup paste", validateForm);
$('<label class="projects-edit-form-sublabel"><small>A phrase to secure your credentials with</small></label>').appendTo(row); $('<label class="projects-edit-form-sublabel"><small>'+RED._("projects.create.desc0")+'</small></label>').appendTo(row);
row = $('<div class="form-row projects-encryption-disabled-row"></div>').hide().appendTo(credentialsRightBox); row = $('<div class="form-row projects-encryption-disabled-row"></div>').hide().appendTo(credentialsRightBox);
$('<div class="" style="padding: 5px 20px;"><i class="fa fa-warning"></i> The credentials file will not be encrypted and its contents easily read</div>').appendTo(row); $('<div class="" style="padding: 5px 20px;"><i class="fa fa-warning"></i> '+RED._("projects.create.desc1")+'</div>').appendTo(row);
credentialsRightBox.find("input[name=projects-encryption-key]").click(function() { credentialsRightBox.find("input[name=projects-encryption-key]").click(function() {
var val = $(this).val(); var val = $(this).val();
@ -1313,9 +1310,9 @@ RED.projects = (function() {
// Clone Project // Clone Project
row = $('<div class="hide form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(container); row = $('<div class="hide form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(container);
$('<label for="projects-dialog-screen-create-project-repo">Git repository URL</label>').appendTo(row); $('<label for="projects-dialog-screen-create-project-repo">'+RED._("projects.create.git-url")+'</label>').appendTo(row);
projectRepoInput = $('<input id="projects-dialog-screen-create-project-repo" type="text" placeholder="https://git.example.com/path/my-project.git"></input>').appendTo(row); projectRepoInput = $('<input id="projects-dialog-screen-create-project-repo" type="text" placeholder="https://git.example.com/path/my-project.git"></input>').appendTo(row);
$('<label id="projects-dialog-screen-create-project-repo-label" class="projects-edit-form-sublabel"><small>https://, ssh:// or file://</small></label>').appendTo(row); $('<label id="projects-dialog-screen-create-project-repo-label" class="projects-edit-form-sublabel"><small>'+RED._("projects.create.protocols")+'</small></label>').appendTo(row);
var projectRepoChanged = false; var projectRepoChanged = false;
var lastProjectRepo = ""; var lastProjectRepo = "";
@ -1323,7 +1320,7 @@ RED.projects = (function() {
projectRepoChanged = true; projectRepoChanged = true;
var repo = $(this).val(); var repo = $(this).val();
if (lastProjectRepo !== repo) { if (lastProjectRepo !== repo) {
$("#projects-dialog-screen-create-project-repo-label small").text("https://, ssh:// or file://"); $("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.create.protocols"));
} }
lastProjectRepo = repo; lastProjectRepo = repo;
@ -1342,24 +1339,24 @@ RED.projects = (function() {
var cloneAuthRows = $('<div class="hide projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').hide().appendTo(container); var cloneAuthRows = $('<div class="hide projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').hide().appendTo(container);
row = $('<div class="form-row projects-dialog-screen-create-row-auth-error"></div>').hide().appendTo(cloneAuthRows); row = $('<div class="form-row projects-dialog-screen-create-row-auth-error"></div>').hide().appendTo(cloneAuthRows);
$('<div><i class="fa fa-warning"></i> Authentication failed</div>').appendTo(row); $('<div><i class="fa fa-warning"></i> '+RED._("projects.create.auth-failed")+'</div>').appendTo(row);
// Repo credentials - username/password ---------------- // Repo credentials - username/password ----------------
row = $('<div class="hide form-row projects-dialog-screen-create-row-creds"></div>').hide().appendTo(cloneAuthRows); row = $('<div class="hide form-row projects-dialog-screen-create-row-creds"></div>').hide().appendTo(cloneAuthRows);
var subrow = $('<div style="width: calc(50% - 10px); display:inline-block;"></div>').appendTo(row); var subrow = $('<div style="width: calc(50% - 10px); display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-user">Username</label>').appendTo(subrow); $('<label for="projects-dialog-screen-create-project-repo-user">'+RED._("projects.create.username")+'</label>').appendTo(subrow);
projectRepoUserInput = $('<input id="projects-dialog-screen-create-project-repo-user" type="text"></input>').appendTo(subrow); projectRepoUserInput = $('<input id="projects-dialog-screen-create-project-repo-user" type="text"></input>').appendTo(subrow);
subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row); subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-pass">Password</label>').appendTo(subrow); $('<label for="projects-dialog-screen-create-project-repo-pass">'+RED._("projects.create.password")+'</label>').appendTo(subrow);
projectRepoPasswordInput = $('<input id="projects-dialog-screen-create-project-repo-pass" type="password"></input>').appendTo(subrow); projectRepoPasswordInput = $('<input id="projects-dialog-screen-create-project-repo-pass" type="password"></input>').appendTo(subrow);
// ----------------------------------------------------- // -----------------------------------------------------
// Repo credentials - key/passphrase ------------------- // Repo credentials - key/passphrase -------------------
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-sshkey"></div>').hide().appendTo(cloneAuthRows); row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-sshkey"></div>').hide().appendTo(cloneAuthRows);
subrow = $('<div style="width: calc(50% - 10px); display:inline-block;"></div>').appendTo(row); subrow = $('<div style="width: calc(50% - 10px); display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-passphrase">SSH Key</label>').appendTo(subrow); $('<label for="projects-dialog-screen-create-project-repo-passphrase">'+RED._("projects.create.ssh-key")+'</label>').appendTo(subrow);
projectRepoSSHKeySelect = $("<select>",{style:"width: 100%"}).appendTo(subrow); projectRepoSSHKeySelect = $("<select>",{style:"width: 100%"}).appendTo(subrow);
$.getJSON("settings/user/keys", function(data) { $.getJSON("settings/user/keys", function(data) {
@ -1379,14 +1376,14 @@ RED.projects = (function() {
} }
}); });
subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row); subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-passphrase">Passphrase</label>').appendTo(subrow); $('<label for="projects-dialog-screen-create-project-repo-passphrase">'+RED._("projects.create.passphrase")+'</label>').appendTo(subrow);
projectRepoPassphrase = $('<input id="projects-dialog-screen-create-project-repo-passphrase" type="password"></input>').appendTo(subrow); projectRepoPassphrase = $('<input id="projects-dialog-screen-create-project-repo-passphrase" type="password"></input>').appendTo(subrow);
subrow = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-sshkey"></div>').appendTo(cloneAuthRows); subrow = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-sshkey"></div>').appendTo(cloneAuthRows);
var sshwarningRow = $('<div class="projects-dialog-screen-create-row-auth-error-no-keys"></div>').hide().appendTo(subrow); var sshwarningRow = $('<div class="projects-dialog-screen-create-row-auth-error-no-keys"></div>').hide().appendTo(subrow);
$('<div class="form-row"><i class="fa fa-warning"></i> Before you can clone a repository over ssh you must add an SSH key to access it.</div>').appendTo(sshwarningRow); $('<div class="form-row"><i class="fa fa-warning"></i> '+RED._("projects.create.desc2")+'</div>').appendTo(sshwarningRow);
subrow = $('<div style="text-align: center">').appendTo(sshwarningRow); subrow = $('<div style="text-align: center">').appendTo(sshwarningRow);
$('<button class="editor-button">Add an ssh key</button>').appendTo(subrow).click(function(e) { $('<button class="editor-button">'+RED._("projects.create.add-ssh-key")+'</button>').appendTo(subrow).click(function(e) {
e.preventDefault(); e.preventDefault();
$('#projects-dialog-cancel').click(); $('#projects-dialog-cancel').click();
RED.userSettings.show('gitconfig'); RED.userSettings.show('gitconfig');
@ -1399,7 +1396,7 @@ RED.projects = (function() {
// Secret - clone // Secret - clone
row = $('<div class="hide form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(container); row = $('<div class="hide form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(container);
$('<label>Credentials encryption key</label>').appendTo(row); $('<label>'+RED._("projects.create.credentials-encryption-key")+'</label>').appendTo(row);
projectSecretInput = $('<input type="password"></input>').appendTo(row); projectSecretInput = $('<input type="password"></input>').appendTo(row);
@ -1421,9 +1418,9 @@ RED.projects = (function() {
buttons: function(options) { buttons: function(options) {
var initialLabel; var initialLabel;
switch (options.screen||"empty") { switch (options.screen||"empty") {
case "open": initialLabel = "Open project"; break; case "open": initialLabel = RED._("projects.create.open"); break;
case "empty": initialLabel = "Create project"; break; case "empty": initialLabel = RED._("projects.create.create"); break;
case "clone": initialLabel = "Clone project"; break; case "clone": initialLabel = RED._("projects.create.clone"); break;
} }
return [ return [
{ {
@ -1477,7 +1474,7 @@ RED.projects = (function() {
}; };
} }
else { else {
console.log("Error! Can't get selected SSH key path."); console.log(RED._("projects.create.cant-get-ssh-key-path"));
return; return;
} }
} }
@ -1497,14 +1494,14 @@ RED.projects = (function() {
dialog.dialog( "close" ); dialog.dialog( "close" );
if (err) { if (err) {
if (err.error !== 'credentials_load_failed') { if (err.error !== 'credentials_load_failed') {
console.log("unexpected_error",err) console.log(RED._("projects.create.unexpected_error"),err)
} }
} }
}) })
} }
$(".projects-dialog-screen-create-row-auth-error").hide(); $(".projects-dialog-screen-create-row-auth-error").hide();
$("#projects-dialog-screen-create-project-repo-label small").text("https://, ssh:// or file://"); $("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.create.protocols"));
projectRepoUserInput.removeClass("input-error"); projectRepoUserInput.removeClass("input-error");
projectRepoPasswordInput.removeClass("input-error"); projectRepoPasswordInput.removeClass("input-error");
@ -1524,22 +1521,22 @@ RED.projects = (function() {
}, },
400: { 400: {
'project_exists': function(error) { 'project_exists': function(error) {
console.log("already exists"); console.log(RED._("projects.create.already-exists-2"));
}, },
'git_error': function(error) { 'git_error': function(error) {
console.log("git error",error); console.log(RED._("projects.create.git-error"),error);
}, },
'git_connection_failed': function(error) { 'git_connection_failed': function(error) {
projectRepoInput.addClass("input-error"); projectRepoInput.addClass("input-error");
$("#projects-dialog-screen-create-project-repo-label small").text("Connection failed"); $("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.create.con-failed"));
}, },
'git_not_a_repository': function(error) { 'git_not_a_repository': function(error) {
projectRepoInput.addClass("input-error"); projectRepoInput.addClass("input-error");
$("#projects-dialog-screen-create-project-repo-label small").text("Not a git repository"); $("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.create.not-git"));
}, },
'git_repository_not_found': function(error) { 'git_repository_not_found': function(error) {
projectRepoInput.addClass("input-error"); projectRepoInput.addClass("input-error");
$("#projects-dialog-screen-create-project-repo-label small").text("Repository not found"); $("#projects-dialog-screen-create-project-repo-label small").text(RED._("projects.create.no-resource"));
}, },
'git_auth_failed': function(error) { 'git_auth_failed': function(error) {
$(".projects-dialog-screen-create-row-auth-error").show(); $(".projects-dialog-screen-create-row-auth-error").show();
@ -1619,15 +1616,15 @@ RED.projects = (function() {
whitespace: "nowrap", whitespace: "nowrap",
width:"1000px" width:"1000px"
}).click(function(evt) { evt.stopPropagation(); }).appendTo(row); }).click(function(evt) { evt.stopPropagation(); }).appendTo(row);
$('<span>').css({"lineHeight":"40px"}).text("Are you sure you want to delete this project?").appendTo(cover); $('<span>').css({"lineHeight":"40px"}).text(RED._("projects.delete.confirm")).appendTo(cover);
$('<button style="margin-left:20px" class="editor-button">Cancel</button>') $('<button style="margin-left:20px" class="editor-button">'+RED._("common.label.cancel")+'</button>')
.appendTo(cover) .appendTo(cover)
.click(function(e) { .click(function(e) {
e.stopPropagation(); e.stopPropagation();
cover.remove(); cover.remove();
done(true); done(true);
}); });
$('<button style="margin-left:20px" class="editor-button primary">Delete</button>') $('<button style="margin-left:20px" class="editor-button primary">'+RED._("common.label.delete")+'</button>')
.appendTo(cover) .appendTo(cover)
.click(function(e) { .click(function(e) {
e.stopPropagation(); e.stopPropagation();
@ -1681,7 +1678,7 @@ RED.projects = (function() {
var filterTerm = ""; var filterTerm = "";
var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(container); var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(container);
var searchInput = $('<input id="projects-dialog-project-list-search" type="text" placeholder="search your projects">').appendTo(searchDiv).searchBox({ var searchInput = $('<input id="projects-dialog-project-list-search" type="text" placeholder="'+RED._("projects.create-project-list.search")+'">').appendTo(searchDiv).searchBox({
//data-i18n="[placeholder]menu.label.searchInput" //data-i18n="[placeholder]menu.label.searchInput"
delay: 200, delay: 200,
change: function() { change: function() {
@ -1790,7 +1787,7 @@ RED.projects = (function() {
$('<span class="projects-dialog-project-list-entry-name" style=""></span>').text(entry.name).appendTo(header); $('<span class="projects-dialog-project-list-entry-name" style=""></span>').text(entry.name).appendTo(header);
if (activeProject && activeProject.name === entry.name) { if (activeProject && activeProject.name === entry.name) {
header.addClass("projects-list-entry-current"); header.addClass("projects-list-entry-current");
$('<span class="projects-dialog-project-list-entry-current">current</span>').appendTo(header); $('<span class="projects-dialog-project-list-entry-current">'+RED._("projects.create-project-list.current")+'</span>').appendTo(header);
if (options.canSelectActive === false) { if (options.canSelectActive === false) {
// active project cannot be selected; so skip the rest // active project cannot be selected; so skip the rest
return return
@ -1852,7 +1849,7 @@ RED.projects = (function() {
function requireCleanWorkspace(done) { function requireCleanWorkspace(done) {
if (RED.nodes.dirty()) { if (RED.nodes.dirty()) {
var message = '<p>You have undeployed changes that will be lost.</p><p>Do you want to continue?</p>'; var message = RED._("projects.require-clean.confirm");
var cleanNotification = RED.notify(message,{ var cleanNotification = RED.notify(message,{
type:"info", type:"info",
fixed: true, fixed: true,
@ -1867,7 +1864,7 @@ RED.projects = (function() {
done(true); done(true);
} }
},{ },{
text: 'Continue', text: RED._("common.label.cont"),
click: function() { click: function() {
cleanNotification.close(); cleanNotification.close();
done(false); done(false);
@ -1945,14 +1942,14 @@ RED.projects = (function() {
var url = activeProject.git.remotes[xhr.responseJSON.remote||options.remote||'origin'].fetch; var url = activeProject.git.remotes[xhr.responseJSON.remote||options.remote||'origin'].fetch;
var message = $('<div>'+ var message = $('<div>'+
'<div class="form-row">Authentication required for repository:</div>'+ '<div class="form-row">'+RED._("projects.send-req.auth-req")+':</div>'+
'<div class="form-row"><div style="margin-left: 20px;">'+url+'</div></div>'+ '<div class="form-row"><div style="margin-left: 20px;">'+url+'</div></div>'+
'</div>'); '</div>');
var isSSH = false; var isSSH = false;
if (/^https?:\/\//.test(url)) { if (/^https?:\/\//.test(url)) {
$('<div class="form-row"><label for="projects-user-auth-username">Username</label><input id="projects-user-auth-username" type="text"></input></div>'+ $('<div class="form-row"><label for="projects-user-auth-username">'+RED._("projects.send-req.username")+'</label><input id="projects-user-auth-username" type="text"></input></div>'+
'<div class="form-row"><label for=projects-user-auth-password">Password</label><input id="projects-user-auth-password" type="password"></input></div>').appendTo(message); '<div class="form-row"><label for=projects-user-auth-password">'+RED._("projects.send-req.password")+'</label><input id="projects-user-auth-password" type="password"></input></div>').appendTo(message);
} else if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(url)) { } else if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(url)) {
isSSH = true; isSSH = true;
var row = $('<div class="form-row"></div>').appendTo(message); var row = $('<div class="form-row"></div>').appendTo(message);
@ -1969,7 +1966,7 @@ RED.projects = (function() {
} }
}); });
row = $('<div class="form-row"></div>').appendTo(message); row = $('<div class="form-row"></div>').appendTo(message);
$('<label for="projects-user-auth-passphrase">Passphrase</label>').appendTo(row); $('<label for="projects-user-auth-passphrase">'+RED._("projects.send-req.passphrase")+'</label>').appendTo(row);
$('<input id="projects-user-auth-passphrase" type="password"></input>').appendTo(row); $('<input id="projects-user-auth-passphrase" type="password"></input>').appendTo(row);
} }
@ -1986,7 +1983,7 @@ RED.projects = (function() {
notification.close(); notification.close();
} }
},{ },{
text: $('<span><i class="fa fa-refresh"></i> Retry</span>'), text: '<span><i class="fa fa-refresh"></i> ' +RED._("projects.send-req.retry") +'</span>',
click: function() { click: function() {
body = body || {}; body = body || {};
var authBody = {}; var authBody = {};
@ -1999,7 +1996,7 @@ RED.projects = (function() {
} }
var done = function(err) { var done = function(err) {
if (err) { if (err) {
console.log("Failed to update auth"); console.log(RED._("projects.send-req.update-failed"));
console.log(err); console.log(err);
} else { } else {
sendRequest(options,body); sendRequest(options,body);
@ -2039,7 +2036,7 @@ RED.projects = (function() {
return; return;
} }
} }
console.log("Unhandled error response:"); console.log(RED._("projects.send-req.unhandled")+":");
console.log(xhr); console.log(xhr);
console.log(textStatus); console.log(textStatus);
console.log(err); console.log(err);
@ -2073,7 +2070,7 @@ RED.projects = (function() {
branchFilterCreateItem.addClass("input-error"); branchFilterCreateItem.addClass("input-error");
branchFilterCreateItem.find("i").addClass("fa-warning").removeClass("fa-code-fork"); branchFilterCreateItem.find("i").addClass("fa-warning").removeClass("fa-code-fork");
} }
branchFilterCreateItem.find("span").text("Invalid branch: "+branchPrefix+branchFilterTerm); branchFilterCreateItem.find("span").text(RED._("projects.create-branch-list.invalid")+": "+branchPrefix+branchFilterTerm);
} else { } else {
if (branchFilterCreateItem.hasClass("input-error")) { if (branchFilterCreateItem.hasClass("input-error")) {
branchFilterCreateItem.removeClass("input-error"); branchFilterCreateItem.removeClass("input-error");
@ -2093,14 +2090,14 @@ RED.projects = (function() {
if (!entry.hasOwnProperty('commit')) { if (!entry.hasOwnProperty('commit')) {
branchFilterCreateItem = container; branchFilterCreateItem = container;
$('<i class="fa fa-code-fork"></i>').appendTo(container); $('<i class="fa fa-code-fork"></i>').appendTo(container);
$('<span>').text("Create branch:").appendTo(container); $('<span>').text(RED._("projects.create-branch-list.create")+":").appendTo(container);
$('<div class="sidebar-version-control-branch-list-entry-create-name" style="margin-left: 10px;">').text(entry.name).appendTo(container); $('<div class="sidebar-version-control-branch-list-entry-create-name" style="margin-left: 10px;">').text(entry.name).appendTo(container);
} else { } else {
$('<i class="fa fa-code-fork"></i>').appendTo(container); $('<i class="fa fa-code-fork"></i>').appendTo(container);
$('<span>').text(entry.name).appendTo(container); $('<span>').text(entry.name).appendTo(container);
if (entry.current) { if (entry.current) {
container.addClass("selected"); container.addClass("selected");
$('<span class="current"></span>').text(options.currentLabel||"current").appendTo(container); $('<span class="current"></span>').text(options.currentLabel||RED._("projects.create-branch-list.current")).appendTo(container);
} }
} }
container.click(function(evt) { container.click(function(evt) {
@ -2240,9 +2237,9 @@ RED.projects = (function() {
function createDefaultFileSet() { function createDefaultFileSet() {
if (!activeProject) { if (!activeProject) {
throw new Error("Cannot create default file set without an active project"); throw new Error(RED._("projects.create-default-file-set.no-active"));
} else if (!activeProject.empty) { } else if (!activeProject.empty) {
throw new Error("Cannot create default file set on a non-empty project"); throw new Error(RED._("projects.create-default-file-set.no-empty"));
} }
if (!RED.user.hasPermission("projects.write")) { if (!RED.user.hasPermission("projects.write")) {
RED.notify(RED._("user.errors.notAuthorized"),"error"); RED.notify(RED._("user.errors.notAuthorized"),"error");
@ -2269,7 +2266,7 @@ RED.projects = (function() {
200: function(data) { }, 200: function(data) { },
400: { 400: {
'git_error': function(error) { 'git_error': function(error) {
console.log("git error",error); console.log(RED._("projects.create-default-file-set.git-error"),error);
}, },
'missing_flow_file': function(error) { 'missing_flow_file': function(error) {
// This is a natural next error - but let the runtime event // This is a natural next error - but let the runtime event

View File

@ -52,11 +52,11 @@ RED.sidebar.versionControl = (function() {
200: function(data) { 200: function(data) {
var title; var title;
if (state === 'unstaged') { if (state === 'unstaged') {
title = 'Unstaged changes : '+entry.file title = RED._("sidebar.project.versionControl.unstagedChanges")+' : '+entry.file
} else if (state === 'staged') { } else if (state === 'staged') {
title = 'Staged changes : '+entry.file title = RED._("sidebar.project.versionControl.stagedChanges")+' : '+entry.file
} else { } else {
title = 'Resolve conflicts : '+entry.file title = RED._("sidebar.project.versionControl.resolveConflicts")+' : '+entry.file
} }
var options = { var options = {
diff: data.diff, diff: data.diff,
@ -65,18 +65,18 @@ RED.sidebar.versionControl = (function() {
project: activeProject project: activeProject
} }
if (state == 'unstaged') { if (state == 'unstaged') {
options.oldRevTitle = entry.indexStatus === " "?"HEAD":"Staged"; options.oldRevTitle = entry.indexStatus === " "?RED._("sidebar.project.versionControl.head"):RED._("sidebar.project.versionControl.staged");
options.newRevTitle = "Unstaged"; options.newRevTitle = RED._("sidebar.project.versionControl.unstaged");
options.oldRev = entry.indexStatus === " "?"@":":0"; options.oldRev = entry.indexStatus === " "?"@":":0";
options.newRev = "_"; options.newRev = "_";
} else if (state === 'staged') { } else if (state === 'staged') {
options.oldRevTitle = "HEAD"; options.oldRevTitle = RED._("sidebar.project.versionControl.head");
options.newRevTitle = "Staged"; options.newRevTitle = RED._("sidebar.project.versionControl.staged");
options.oldRev = "@"; options.oldRev = "@";
options.newRev = ":0"; options.newRev = ":0";
} else { } else {
options.oldRevTitle = "Local"; options.oldRevTitle = RED._("sidebar.project.versionControl.local");
options.newRevTitle = "Remote"; options.newRevTitle = RED._("sidebar.project.versionControl.remote");
options.commonRev = ":1"; options.commonRev = ":1";
options.oldRev = ":2"; options.oldRev = ":2";
options.newRev = ":3"; options.newRev = ":3";
@ -156,7 +156,7 @@ RED.sidebar.versionControl = (function() {
evt.preventDefault(); evt.preventDefault();
var spinner = utils.addSpinnerOverlay(container).addClass('projects-dialog-spinner-contain'); var spinner = utils.addSpinnerOverlay(container).addClass('projects-dialog-spinner-contain');
var notification = RED.notify("Are you sure you want to revert the changes to '"+entry.file+"'? This cannot be undone.", { var notification = RED.notify(RED._("sidebar.project.versionControl.revert",{file:entry.file}), {
type: "warning", type: "warning",
modal: true, modal: true,
fixed: true, fixed: true,
@ -168,7 +168,7 @@ RED.sidebar.versionControl = (function() {
notification.close(); notification.close();
} }
},{ },{
text: 'Revert changes', text: RED._("sidebar.project.versionControl.revertChanges"),
click: function() { click: function() {
notification.close(); notification.close();
var activeProject = RED.projects.getActiveProject(); var activeProject = RED.projects.getActiveProject();
@ -281,6 +281,8 @@ RED.sidebar.versionControl = (function() {
entry["update"+((state==='unstaged')?"Unstaged":"Staged")](entry, status); entry["update"+((state==='unstaged')?"Unstaged":"Staged")](entry, status);
} }
var utils; var utils;
var emptyStagedItem;
var emptyMergedItem;
function init(_utils) { function init(_utils) {
utils = _utils; utils = _utils;
@ -312,7 +314,7 @@ RED.sidebar.versionControl = (function() {
}); });
localChanges = sections.add({ localChanges = sections.add({
title: "Local Changes", title: RED._("sidebar.project.versionControl.localChanges"),
collapsible: true collapsible: true
}); });
localChanges.expand(); localChanges.expand();
@ -326,10 +328,12 @@ RED.sidebar.versionControl = (function() {
refresh(true); refresh(true);
}) })
emptyStagedItem = { label: RED._("sidebar.project.versionControl.none") };
emptyMergedItem = { label: RED._("sidebar.project.versionControl.conflictResolve") };
var unstagedContent = $('<div class="sidebar-version-control-change-container"></div>').appendTo(localChanges.content); var unstagedContent = $('<div class="sidebar-version-control-change-container"></div>').appendTo(localChanges.content);
var header = $('<div class="sidebar-version-control-change-header">Local files</div>').appendTo(unstagedContent); var header = $('<div class="sidebar-version-control-change-header">'+RED._("sidebar.project.versionControl.localFiles")+'</div>').appendTo(unstagedContent);
stageAllButton = $('<button class="editor-button editor-button-small" style="float: right"><i class="fa fa-plus"></i> all</button>') stageAllButton = $('<button class="editor-button editor-button-small" style="float: right"><i class="fa fa-plus"></i> '+RED._("sidebar.project.versionControl.all")+'</button>')
.appendTo(header) .appendTo(header)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
@ -359,9 +363,9 @@ RED.sidebar.versionControl = (function() {
unmergedContent = $('<div class="sidebar-version-control-change-container"></div>').appendTo(localChanges.content); unmergedContent = $('<div class="sidebar-version-control-change-container"></div>').appendTo(localChanges.content);
header = $('<div class="sidebar-version-control-change-header">Unmerged changes</div>').appendTo(unmergedContent); header = $('<div class="sidebar-version-control-change-header">'+RED._("sidebar.project.versionControl.unmergedChanges")+'</div>').appendTo(unmergedContent);
bg = $('<div style="float: right"></div>').appendTo(header); bg = $('<div style="float: right"></div>').appendTo(header);
var abortMergeButton = $('<button class="editor-button editor-button-small" style="margin-right: 5px;">abort merge</button>') var abortMergeButton = $('<button class="editor-button editor-button-small" style="margin-right: 5px;">'+RED._("sidebar.project.versionControl.abortMerge")+'</button>')
.appendTo(bg) .appendTo(bg)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
@ -399,7 +403,7 @@ RED.sidebar.versionControl = (function() {
addItem: function(row,index,entry) { addItem: function(row,index,entry) {
if (entry === emptyMergedItem) { if (entry === emptyMergedItem) {
entry.button = { entry.button = {
label: 'commit', label: RED._("sidebar.project.versionControl.commit"),
click: function(evt) { click: function(evt) {
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation(); evt.stopPropagation();
@ -423,7 +427,7 @@ RED.sidebar.versionControl = (function() {
var stagedContent = $('<div class="sidebar-version-control-change-container"></div>').appendTo(localChanges.content); var stagedContent = $('<div class="sidebar-version-control-change-container"></div>').appendTo(localChanges.content);
header = $('<div class="sidebar-version-control-change-header">Changes to commit</div>').appendTo(stagedContent); header = $('<div class="sidebar-version-control-change-header">'+RED._("sidebar.project.versionControl.changeToCommit")+'</div>').appendTo(stagedContent);
bg = $('<div style="float: right"></div>').appendTo(header); bg = $('<div style="float: right"></div>').appendTo(header);
var showCommitBox = function() { var showCommitBox = function() {
@ -446,14 +450,14 @@ RED.sidebar.versionControl = (function() {
abortMergeButton.attr("disabled",true); abortMergeButton.attr("disabled",true);
commitMessage.focus(); commitMessage.focus();
} }
commitButton = $('<button class="editor-button editor-button-small" style="margin-right: 5px;">commit</button>') commitButton = $('<button class="editor-button editor-button-small" style="margin-right: 5px;">'+RED._("sidebar.project.versionControl.commit")+'</button>')
.appendTo(bg) .appendTo(bg)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation(); evt.stopPropagation();
showCommitBox(); showCommitBox();
}); });
unstageAllButton = $('<button class="editor-button editor-button-small"><i class="fa fa-minus"></i> all</button>') unstageAllButton = $('<button class="editor-button editor-button-small"><i class="fa fa-minus"></i> '+RED._("sidebar.project.versionControl.all")+'</button>')
.appendTo(bg) .appendTo(bg)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
@ -480,14 +484,14 @@ RED.sidebar.versionControl = (function() {
commitBox = $('<div class="sidebar-version-control-slide-box sidebar-version-control-slide-box-bottom"></div>').hide().appendTo(localChanges.content); commitBox = $('<div class="sidebar-version-control-slide-box sidebar-version-control-slide-box-bottom"></div>').hide().appendTo(localChanges.content);
var commitMessage = $('<textarea placeholder="Enter your commit message"></textarea>') var commitMessage = $('<textarea placeholder='+RED._("sidebar.project.versionControl.commitPlaceholder")+'></textarea>')
.appendTo(commitBox) .appendTo(commitBox)
.on("change keyup paste",function() { .on("change keyup paste",function() {
submitCommitButton.attr('disabled',$(this).val().trim()===""); submitCommitButton.attr('disabled',$(this).val().trim()==="");
}); });
var commitToolbar = $('<div class="sidebar-version-control-slide-box-toolbar button-group">').appendTo(commitBox); var commitToolbar = $('<div class="sidebar-version-control-slide-box-toolbar button-group">').appendTo(commitBox);
var cancelCommitButton = $('<button class="editor-button">Cancel</button>') var cancelCommitButton = $('<button class="editor-button">'+RED._("sidebar.project.versionControl.cancelCapital")+'</button>')
.appendTo(commitToolbar) .appendTo(commitToolbar)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
@ -505,7 +509,7 @@ RED.sidebar.versionControl = (function() {
abortMergeButton.attr("disabled",false); abortMergeButton.attr("disabled",false);
}) })
var submitCommitButton = $('<button class="editor-button">Commit</button>') var submitCommitButton = $('<button class="editor-button">'+RED._("sidebar.project.versionControl.commitCapital")+'</button>')
.appendTo(commitToolbar) .appendTo(commitToolbar)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
@ -541,7 +545,7 @@ RED.sidebar.versionControl = (function() {
var localHistory = sections.add({ var localHistory = sections.add({
title: "Commit History", title: RED._("sidebar.project.versionControl.commitHistory"),
collapsible: true collapsible: true
}); });
@ -555,7 +559,7 @@ RED.sidebar.versionControl = (function() {
var localBranchToolbar = $('<div class="sidebar-version-control-change-header" style="text-align: right;"></div>').appendTo(localHistory.content); var localBranchToolbar = $('<div class="sidebar-version-control-change-header" style="text-align: right;"></div>').appendTo(localHistory.content);
var localBranchButton = $('<button class="editor-button editor-button-small"><i class="fa fa-code-fork"></i> Branch: <span id="sidebar-version-control-local-branch"></span></button>') var localBranchButton = $('<button class="editor-button editor-button-small"><i class="fa fa-code-fork"></i> '+RED._("sidebar.project.versionControl.branch")+' <span id="sidebar-version-control-local-branch"></span></button>')
.appendTo(localBranchToolbar) .appendTo(localBranchToolbar)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
@ -612,7 +616,7 @@ RED.sidebar.versionControl = (function() {
row.addClass('sidebar-version-control-commit-entry'); row.addClass('sidebar-version-control-commit-entry');
if (entry.url) { if (entry.url) {
row.addClass('sidebar-version-control-commit-more'); row.addClass('sidebar-version-control-commit-more');
row.text("+ "+(entry.total-entry.totalKnown)+" more commit(s)"); row.text("+ "+(entry.total-entry.totalKnown)+RED._("sidebar.project.versionControl.moreCommits"));
row.click(function(e) { row.click(function(e) {
e.preventDefault(); e.preventDefault();
getCommits(entry.url,localCommitList,row,entry.limit,entry.before); getCommits(entry.url,localCommitList,row,entry.limit,entry.before);
@ -626,8 +630,8 @@ RED.sidebar.versionControl = (function() {
result.parents = entry.parents; result.parents = entry.parents;
result.oldRev = entry.sha+"~1"; result.oldRev = entry.sha+"~1";
result.newRev = entry.sha; result.newRev = entry.sha;
result.oldRevTitle = "Commit "+entry.sha.substring(0,7)+"~1"; result.oldRevTitle = RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7)+"~1";
result.newRevTitle = "Commit "+entry.sha.substring(0,7); result.newRevTitle = RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7);
result.date = humanizeSinceDate(parseInt(entry.date)); result.date = humanizeSinceDate(parseInt(entry.date));
RED.diff.showCommitDiff(result); RED.diff.showCommitDiff(result);
}); });
@ -666,10 +670,10 @@ RED.sidebar.versionControl = (function() {
} }
var localBranchBox = $('<div class="sidebar-version-control-slide-box sidebar-version-control-slide-box-top" style="top:30px;"></div>').hide().appendTo(localHistory.content); var localBranchBox = $('<div class="sidebar-version-control-slide-box sidebar-version-control-slide-box-top" style="top:30px;"></div>').hide().appendTo(localHistory.content);
$('<div class="sidebar-version-control-slide-box-header"></div>').text("Change local branch").appendTo(localBranchBox); $('<div class="sidebar-version-control-slide-box-header"></div>').text(RED._("sidebar.project.versionControl.changeLocalBranch")).appendTo(localBranchBox);
var localBranchList = utils.createBranchList({ var localBranchList = utils.createBranchList({
placeholder: "Find or create a branch", placeholder: RED._("sidebar.project.versionControl.createBranchPlaceholder"),
container: localBranchBox, container: localBranchBox,
onselect: function(body) { onselect: function(body) {
if (body.current) { if (body.current) {
@ -701,7 +705,7 @@ RED.sidebar.versionControl = (function() {
400: { 400: {
'git_local_overwrite': function(error) { 'git_local_overwrite': function(error) {
spinner.remove(); spinner.remove();
RED.notify("You have local changes that would be overwritten by changing the branch. You must either commit or undo those changes first.",{ RED.notify(RED._("sidebar.project.versionControl.localOverwrite"),{
type:'error', type:'error',
timeout: 8000 timeout: 8000
}); });
@ -744,10 +748,10 @@ RED.sidebar.versionControl = (function() {
},200); },200);
} }
} }
$('<div class="sidebar-version-control-slide-box-header"></div>').text("Manage remote branch").appendTo(remoteBox); $('<div class="sidebar-version-control-slide-box-header"></div>').text(RED._("sidebar.project.versionControl.manageRemoteBranch")).appendTo(remoteBox);
var remoteBranchRow = $('<div style="margin-bottom: 5px;"></div>').appendTo(remoteBox); var remoteBranchRow = $('<div style="margin-bottom: 5px;"></div>').appendTo(remoteBox);
var remoteBranchButton = $('<button id="sidebar-version-control-repo-branch" class="sidebar-version-control-repo-action editor-button"><i class="fa fa-code-fork"></i> Remote: <span id="sidebar-version-control-remote-branch"></span></button>') var remoteBranchButton = $('<button id="sidebar-version-control-repo-branch" class="sidebar-version-control-repo-action editor-button"><i class="fa fa-code-fork"></i> '+RED._("sidebar.project.versionControl.remote")+': <span id="sidebar-version-control-remote-branch"></span></button>')
.appendTo(remoteBranchRow) .appendTo(remoteBranchRow)
.click(function(evt) { .click(function(evt) {
evt.preventDefault(); evt.preventDefault();
@ -770,9 +774,9 @@ RED.sidebar.versionControl = (function() {
var errorMessage = $('<div id="sidebar-version-control-repo-toolbar-error-message" class="sidebar-version-control-slide-box-header" style="min-height: 100px;"></div>').hide().appendTo(remoteBox); var errorMessage = $('<div id="sidebar-version-control-repo-toolbar-error-message" class="sidebar-version-control-slide-box-header" style="min-height: 100px;"></div>').hide().appendTo(remoteBox);
$('<div style="margin-top: 10px;"><i class="fa fa-warning"></i> Unable to access remote repository</div>').appendTo(errorMessage) $('<div style="margin-top: 10px;"><i class="fa fa-warning"></i> '+RED._("sidebar.project.versionControl.unableToAccess")+'</div>').appendTo(errorMessage)
var buttonRow = $('<div style="margin: 10px 30px; text-align: center"></div>').appendTo(errorMessage); var buttonRow = $('<div style="margin: 10px 30px; text-align: center"></div>').appendTo(errorMessage);
$('<button class="editor-button" style="width: 80%;"><i class="fa fa-refresh"></i> Retry</button>') $('<button class="editor-button" style="width: 80%;"><i class="fa fa-refresh"></i> '+RED._("sidebar.project.versionControl.retry")+'</button>')
.appendTo(buttonRow) .appendTo(buttonRow)
.click(function(e) { .click(function(e) {
e.preventDefault(); e.preventDefault();
@ -810,12 +814,12 @@ RED.sidebar.versionControl = (function() {
}); });
}) })
$('<div class="sidebar-version-control-slide-box-header" style="height: 20px;"><label id="sidebar-version-control-repo-toolbar-set-upstream-row" for="sidebar-version-control-repo-toolbar-set-upstream" class="hide"><input type="checkbox" id="sidebar-version-control-repo-toolbar-set-upstream"> Set as upstream branch</label></div>').appendTo(remoteBox); $('<div class="sidebar-version-control-slide-box-header" style="height: 20px;"><label id="sidebar-version-control-repo-toolbar-set-upstream-row" for="sidebar-version-control-repo-toolbar-set-upstream" class="hide"><input type="checkbox" id="sidebar-version-control-repo-toolbar-set-upstream"> '+RED._("sidebar.project.versionControl.setUpstreamBranch")+'</label></div>').appendTo(remoteBox);
var remoteBranchSubRow = $('<div style="height: 0;overflow:hidden; transition: height 0.2s ease-in-out;"></div>').hide().appendTo(remoteBranchRow); var remoteBranchSubRow = $('<div style="height: 0;overflow:hidden; transition: height 0.2s ease-in-out;"></div>').hide().appendTo(remoteBranchRow);
var remoteBranchList = utils.createBranchList({ var remoteBranchList = utils.createBranchList({
placeholder: "Find or create a remote branch", placeholder: RED._("sidebar.project.versionControl.createRemoteBranchPlaceholder"),
currentLabel: "upstream", currentLabel: RED._("sidebar.project.versionControl.upstream"),
remote: function() { remote: function() {
var project = RED.projects.getActiveProject(); var project = RED.projects.getActiveProject();
var remotes = Object.keys(project.git.remotes); var remotes = Object.keys(project.git.remotes);
@ -845,11 +849,11 @@ RED.sidebar.versionControl = (function() {
}) })
} else { } else {
if (!activeProject.git.branches.remote) { if (!activeProject.git.branches.remote) {
$('#sidebar-version-control-repo-toolbar-message').text("The created branch will be set as the tracked upstream branch."); $('#sidebar-version-control-repo-toolbar-message').text(RED._("sidebar.project.versionControl.trackedUpstreamBranch"));
$("#sidebar-version-control-repo-toolbar-set-upstream").prop('checked',true); $("#sidebar-version-control-repo-toolbar-set-upstream").prop('checked',true);
$("#sidebar-version-control-repo-toolbar-set-upstream").prop('disabled',true); $("#sidebar-version-control-repo-toolbar-set-upstream").prop('disabled',true);
} else { } else {
$('#sidebar-version-control-repo-toolbar-message').text("The branch will be created. Select below to set it as the tracked upstream branch."); $('#sidebar-version-control-repo-toolbar-message').text(RED._("sidebar.project.versionControl.selectUpstreamBranch"));
} }
$("#sidebar-version-control-repo-pull").attr('disabled',true); $("#sidebar-version-control-repo-pull").attr('disabled',true);
$("#sidebar-version-control-repo-push").attr('disabled',false); $("#sidebar-version-control-repo-push").attr('disabled',false);
@ -861,7 +865,7 @@ RED.sidebar.versionControl = (function() {
var row = $('<div style="margin-bottom: 5px;"></div>').appendTo(remoteBox); var row = $('<div style="margin-bottom: 5px;"></div>').appendTo(remoteBox);
$('<button id="sidebar-version-control-repo-push" class="sidebar-version-control-repo-sub-action editor-button"><i class="fa fa-long-arrow-up"></i> <span>push</span></button>') $('<button id="sidebar-version-control-repo-push" class="sidebar-version-control-repo-sub-action editor-button"><i class="fa fa-long-arrow-up"></i> <span data-i18n="sidebar.project.versionControl.push"></span></button>')
.appendTo(row) .appendTo(row)
.click(function(e) { .click(function(e) {
e.preventDefault(); e.preventDefault();
@ -893,8 +897,8 @@ RED.sidebar.versionControl = (function() {
}, },
400: { 400: {
'git_push_failed': function(err) { 'git_push_failed': function(err) {
// TODO: better message + NLS // TODO: better message
RED.notify("NLS: Push failed as the remote has more recent commits. Pull first and write a better error message!","error"); RED.notify(RED._("sidebar.project.versionControl.pushFailed"),"error");
}, },
'unexpected_error': function(error) { 'unexpected_error': function(error) {
console.log(error); console.log(error);
@ -945,18 +949,18 @@ RED.sidebar.versionControl = (function() {
}, },
400: { 400: {
'git_local_overwrite': function(err) { 'git_local_overwrite': function(err) {
RED.notify("<p>Unable to pull remote changes; your unstaged local changes would be overwritten.</p><p>Commit your changes and try again.</p>"+ RED.notify(RED._("sidebar.project.versionControl.unablePull")+
'<p><a href="#" onclick="RED.sidebar.versionControl.showLocalChanges(); return false;">'+'Show unstaged changes'+'</a></p>',"error",false,10000000); '<p><a href="#" onclick="RED.sidebar.versionControl.showLocalChanges(); return false;">'+RED._("sidebar.project.versionControl.showUnstagedChanges")+'</a></p>',"error",false,10000000);
}, },
'git_pull_merge_conflict': function(err) { 'git_pull_merge_conflict': function(err) {
refresh(true); refresh(true);
closeRemoteBox(); closeRemoteBox();
}, },
'git_connection_failed': function(err) { 'git_connection_failed': function(err) {
RED.notify("Could not connect to remote repository: "+err.toString(),"warning") RED.notify(RED._("sidebar.project.versionControl.connectionFailed")+err.toString(),"warning")
}, },
'git_pull_unrelated_history': function(error) { 'git_pull_unrelated_history': function(error) {
var notification = RED.notify("<p>The remote has an unrelated history of commits.</p><p>Are you sure you want to pull the changes into your local repository?</p>",{ var notification = RED.notify(RED._("sidebar.project.versionControl.pullUnrelatedHistory"),{
type: 'error', type: 'error',
modal: true, modal: true,
fixed: true, fixed: true,
@ -967,7 +971,7 @@ RED.sidebar.versionControl = (function() {
notification.close(); notification.close();
} }
},{ },{
text: 'Pull changes', text: RED._("sidebar.project.versionControl.pullChanges"),
click: function() { click: function() {
notification.close(); notification.close();
options.allowUnrelatedHistories = true; options.allowUnrelatedHistories = true;
@ -986,7 +990,7 @@ RED.sidebar.versionControl = (function() {
spinner.remove(); spinner.remove();
}); });
} }
$('<button id="sidebar-version-control-repo-pull" class="sidebar-version-control-repo-sub-action editor-button"><i class="fa fa-long-arrow-down"></i> <span>pull</span></button>') $('<button id="sidebar-version-control-repo-pull" class="sidebar-version-control-repo-sub-action editor-button"><i class="fa fa-long-arrow-down"></i> <span data-i18n="sidebar.project.versionControl.pull"></span></button>')
.appendTo(row) .appendTo(row)
.click(function(e) { .click(function(e) {
e.preventDefault(); e.preventDefault();
@ -999,10 +1003,12 @@ RED.sidebar.versionControl = (function() {
RED.sidebar.addTab({ RED.sidebar.addTab({
id: "version-control", id: "version-control",
label: "history", label: RED._("sidebar.project.versionControl.history"),
name: "Project History", name: "Project History",
content: sidebarContent, content: sidebarContent,
enableOnEdit: false, enableOnEdit: false,
pinned: true,
iconClass: "fa fa-code-fork",
onchange: function() { onchange: function() {
setTimeout(function() { setTimeout(function() {
sections.resize(); sections.resize();
@ -1019,17 +1025,17 @@ RED.sidebar.versionControl = (function() {
if (daysDelta > 30) { if (daysDelta > 30) {
return (new Date(date*1000)).toLocaleDateString(); return (new Date(date*1000)).toLocaleDateString();
} else if (daysDelta > 0) { } else if (daysDelta > 0) {
return daysDelta+" day"+(daysDelta>1?"s":"")+" ago"; return RED._("sidebar.project.versionControl.daysAgo", {count:daysDelta})
} }
var hoursDelta = Math.floor(delta / (60*60)); var hoursDelta = Math.floor(delta / (60*60));
if (hoursDelta > 0) { if (hoursDelta > 0) {
return hoursDelta+" hour"+(hoursDelta>1?"s":"")+" ago"; return RED._("sidebar.project.versionControl.hoursAgo", {count:hoursDelta})
} }
var minutesDelta = Math.floor(delta / 60); var minutesDelta = Math.floor(delta / 60);
if (minutesDelta > 0) { if (minutesDelta > 0) {
return minutesDelta+" minute"+(minutesDelta>1?"s":"")+" ago"; return RED._("sidebar.project.versionControl.minsAgo", {count:minutesDelta})
} }
return "Seconds ago"; return RED._("sidebar.project.versionControl.secondsAgo");
} }
function updateBulk(files,unstaged) { function updateBulk(files,unstaged) {
@ -1064,9 +1070,6 @@ RED.sidebar.versionControl = (function() {
var refreshInProgress = false; var refreshInProgress = false;
var emptyStagedItem = { label:"None" };
var emptyMergedItem = { label:"All conflicts resolved. Commit the changes to complete the merge." };
function getCommits(url,targetList,spinnerTarget,limit,before) { function getCommits(url,targetList,spinnerTarget,limit,before) {
var spinner = utils.addSpinnerOverlay(spinnerTarget); var spinner = utils.addSpinnerOverlay(spinnerTarget);
var fullUrl = url+"?limit="+(limit||20); var fullUrl = url+"?limit="+(limit||20);
@ -1274,7 +1277,7 @@ RED.sidebar.versionControl = (function() {
refreshFiles(result); refreshFiles(result);
$('#sidebar-version-control-local-branch').text(result.branches.local); $('#sidebar-version-control-local-branch').text(result.branches.local);
$('#sidebar-version-control-remote-branch').text(result.branches.remote||"none"); $('#sidebar-version-control-remote-branch').text(result.branches.remote||RED._("sidebar.project.versionControl.none"));
var commitsAhead = result.commits.ahead || 0; var commitsAhead = result.commits.ahead || 0;
var commitsBehind = result.commits.behind || 0; var commitsBehind = result.commits.behind || 0;
@ -1304,7 +1307,7 @@ RED.sidebar.versionControl = (function() {
$('#sidebar-version-control-commits-ahead').text(""); $('#sidebar-version-control-commits-ahead').text("");
$('#sidebar-version-control-commits-behind').text(""); $('#sidebar-version-control-commits-behind').text("");
$('#sidebar-version-control-repo-toolbar-message').text("Your local branch is not currently tracking a remote branch."); $('#sidebar-version-control-repo-toolbar-message').text(RED._("sidebar.project.versionControl.notTracking"));
$("#sidebar-version-control-repo-pull").attr('disabled',true); $("#sidebar-version-control-repo-pull").attr('disabled',true);
$("#sidebar-version-control-repo-push").attr('disabled',true); $("#sidebar-version-control-repo-push").attr('disabled',true);
} }
@ -1330,23 +1333,26 @@ RED.sidebar.versionControl = (function() {
$('#sidebar-version-control-commits-ahead').text(commitsAhead); $('#sidebar-version-control-commits-ahead').text(commitsAhead);
$('#sidebar-version-control-commits-behind').text(commitsBehind); $('#sidebar-version-control-commits-behind').text(commitsBehind);
if (isMerging) { if (isMerging) {
$('#sidebar-version-control-repo-toolbar-message').text("Your repository has unmerged changes. You need to fix the conflicts and commit the result."); $('#sidebar-version-control-repo-toolbar-message').text(RED._("sidebar.project.versionControl.statusUnmergedChanged"));
$("#sidebar-version-control-repo-pull").attr('disabled',true); $("#sidebar-version-control-repo-pull").attr('disabled',true);
$("#sidebar-version-control-repo-push").attr('disabled',true); $("#sidebar-version-control-repo-push").attr('disabled',true);
} else if (commitsAhead > 0 && commitsBehind === 0) { } else if (commitsAhead > 0 && commitsBehind === 0) {
$('#sidebar-version-control-repo-toolbar-message').text("Your repository is "+commitsAhead+" commit"+(commitsAhead===1?'':'s')+" ahead of the remote. You can push "+(commitsAhead===1?'this commit':'these commits')+" now."); $('#sidebar-version-control-repo-toolbar-message').text(RED._("sidebar.project.versionControl.commitsAhead", {count:commitsAhead}));
$("#sidebar-version-control-repo-pull").attr('disabled',true); $("#sidebar-version-control-repo-pull").attr('disabled',true);
$("#sidebar-version-control-repo-push").attr('disabled',false); $("#sidebar-version-control-repo-push").attr('disabled',false);
} else if (commitsAhead === 0 && commitsBehind > 0) { } else if (commitsAhead === 0 && commitsBehind > 0) {
$('#sidebar-version-control-repo-toolbar-message').text("Your repository is "+commitsBehind+" commit"+(commitsBehind===1?'':'s')+" behind of the remote. You can pull "+(commitsBehind===1?'this commit':'these commits')+" now."); $('#sidebar-version-control-repo-toolbar-message').text(RED._("sidebar.project.versionControl.commitsBehind",{ count: commitsBehind }));
$("#sidebar-version-control-repo-pull").attr('disabled',false); $("#sidebar-version-control-repo-pull").attr('disabled',false);
$("#sidebar-version-control-repo-push").attr('disabled',true); $("#sidebar-version-control-repo-push").attr('disabled',true);
} else if (commitsAhead > 0 && commitsBehind > 0) { } else if (commitsAhead > 0 && commitsBehind > 0) {
$('#sidebar-version-control-repo-toolbar-message').text("Your repository is "+commitsBehind+" commit"+(commitsBehind===1?'':'s')+" behind and "+commitsAhead+" commit"+(commitsAhead===1?'':'s')+" ahead of the remote. You must pull the remote commit"+(commitsBehind===1?'':'s')+" down before pushing."); $('#sidebar-version-control-repo-toolbar-message').text(
RED._("sidebar.project.versionControl.commitsAheadAndBehind1",{ count:commitsBehind })+
RED._("sidebar.project.versionControl.commitsAheadAndBehind2",{ count:commitsAhead })+
RED._("sidebar.project.versionControl.commitsAheadAndBehind3",{ count:commitsBehind }));
$("#sidebar-version-control-repo-pull").attr('disabled',false); $("#sidebar-version-control-repo-pull").attr('disabled',false);
$("#sidebar-version-control-repo-push").attr('disabled',true); $("#sidebar-version-control-repo-push").attr('disabled',true);
} else if (commitsAhead === 0 && commitsBehind === 0) { } else if (commitsAhead === 0 && commitsBehind === 0) {
$('#sidebar-version-control-repo-toolbar-message').text("Your repository is up to date."); $('#sidebar-version-control-repo-toolbar-message').text(RED._("sidebar.project.versionControl.repositoryUpToDate"));
$("#sidebar-version-control-repo-pull").attr('disabled',true); $("#sidebar-version-control-repo-pull").attr('disabled',true);
$("#sidebar-version-control-repo-push").attr('disabled',true); $("#sidebar-version-control-repo-push").attr('disabled',true);
} }

View File

@ -35,7 +35,8 @@ RED.sidebar = (function() {
tab.onremove.call(tab); tab.onremove.call(tab);
} }
}, },
minimumActiveTabWidth: 70 // minimumActiveTabWidth: 70,
collapsible: true
// scrollable: true // scrollable: true
}); });
@ -59,6 +60,8 @@ RED.sidebar = (function() {
options = title; options = title;
} }
delete options.closeable;
options.wrapper = $('<div>',{style:"height:100%"}).appendTo("#sidebar-content") options.wrapper = $('<div>',{style:"height:100%"}).appendTo("#sidebar-content")
options.wrapper.append(options.content); options.wrapper.append(options.content);
options.wrapper.hide(); options.wrapper.hide();
@ -82,6 +85,8 @@ RED.sidebar = (function() {
group: "sidebar-tabs" group: "sidebar-tabs"
}); });
options.iconClass = options.iconClass || "fa fa-square-o"
knownTabs[options.id] = options; knownTabs[options.id] = options;
if (options.visible !== false) { if (options.visible !== false) {
@ -213,6 +218,7 @@ RED.sidebar = (function() {
showSidebar(); showSidebar();
RED.sidebar.info.init(); RED.sidebar.info.init();
RED.sidebar.config.init(); RED.sidebar.config.init();
RED.sidebar.context.init();
// hide info bar at start if screen rather narrow... // hide info bar at start if screen rather narrow...
if ($(window).width() < 600) { RED.menu.setSelected("menu-item-sidebar",false); } if ($(window).width() < 600) { RED.menu.setSelected("menu-item-sidebar",false); }
} }

View File

@ -23,5 +23,6 @@ RED.state = {
EXPORT: 6, EXPORT: 6,
IMPORT: 7, IMPORT: 7,
IMPORT_DRAGGING: 8, IMPORT_DRAGGING: 8,
QUICK_JOINING: 9 QUICK_JOINING: 9,
PANNING: 10
} }

View File

@ -221,8 +221,7 @@ RED.sidebar.config = (function() {
name: RED._("sidebar.config.name"), name: RED._("sidebar.config.name"),
content: content, content: content,
toolbar: toolbar, toolbar: toolbar,
closeable: true, iconClass: "fa fa-cog",
visible: false,
onchange: function() { refreshConfigNodeList(); } onchange: function() { refreshConfigNodeList(); }
}); });
RED.actions.add("core:show-config-tab",function() {RED.sidebar.show('config')}); RED.actions.add("core:show-config-tab",function() {RED.sidebar.show('config')});

271
editor/js/ui/tab-context.js Normal file
View File

@ -0,0 +1,271 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.sidebar.context = (function() {
var content;
var sections;
var localCache = {};
var nodeSection;
// var subflowSection;
var flowSection;
var globalSection;
var currentNode;
var currentFlow;
function init() {
content = $("<div>").css({"position":"relative","height":"100%"});
content.className = "sidebar-context"
// var toolbar = $('<div class="sidebar-header">'+
// '<span class="button-group"><a id="sidebar-context-toggle-live" class="sidebar-header-button-toggle single" href="#"><i class="fa fa-play"></i> <span></span></a></span>'+
// '</div>').appendTo(content);
var footerToolbar = $('<div>'+
// '<span class="button-group"><a class="sidebar-footer-button" href="#" data-i18n="[title]node-red:debug.sidebar.openWindow"><i class="fa fa-desktop"></i></a></span> ' +
'</div>');
var stackContainer = $("<div>",{class:"sidebar-context-stack"}).appendTo(content);
sections = RED.stack.create({
container: stackContainer
});
nodeSection = sections.add({
title: "Node",
collapsible: true,
// onexpand: function() {
// updateNode(currentNode,true);
// }
});
nodeSection.expand();
nodeSection.content.css({height:"100%"});
nodeSection.timestamp = $('<div class="sidebar-context-updated">&nbsp;</div>').appendTo(nodeSection.content);
var table = $('<table class="node-info"></table>').appendTo(nodeSection.content);
nodeSection.table = $('<tbody>').appendTo(table);
var bg = $('<div style="float: right"></div>').appendTo(nodeSection.header);
$('<button class="editor-button editor-button-small"><i class="fa fa-refresh"></i></button>')
.appendTo(bg)
.click(function(evt) {
evt.stopPropagation();
evt.preventDefault();
updateNode(currentNode, true);
})
// subflowSection = sections.add({
// title: "Subflow",
// collapsible: true
// });
// subflowSection.expand();
// subflowSection.content.css({height:"100%"});
// bg = $('<div style="float: right"></div>').appendTo(subflowSection.header);
// $('<button class="editor-button editor-button-small"><i class="fa fa-refresh"></i></button>')
// .appendTo(bg)
// .click(function(evt) {
// evt.stopPropagation();
// evt.preventDefault();
// })
//
// subflowSection.container.hide();
flowSection = sections.add({
title: "Flow",
collapsible: true
});
flowSection.expand();
flowSection.content.css({height:"100%"});
flowSection.timestamp = $('<div class="sidebar-context-updated">&nbsp;</div>').appendTo(flowSection.content);
var table = $('<table class="node-info"></table>').appendTo(flowSection.content);
flowSection.table = $('<tbody>').appendTo(table);
bg = $('<div style="float: right"></div>').appendTo(flowSection.header);
$('<button class="editor-button editor-button-small"><i class="fa fa-refresh"></i></button>')
.appendTo(bg)
.click(function(evt) {
evt.stopPropagation();
evt.preventDefault();
updateFlow(currentFlow);
})
globalSection = sections.add({
title: "Global",
collapsible: true
});
globalSection.expand();
globalSection.content.css({height:"100%"});
globalSection.timestamp = $('<div class="sidebar-context-updated">&nbsp;</div>').appendTo(globalSection.content);
var table = $('<table class="node-info"></table>').appendTo(globalSection.content);
globalSection.table = $('<tbody>').appendTo(table);
bg = $('<div style="float: right"></div>').appendTo(globalSection.header);
$('<button class="editor-button editor-button-small"><i class="fa fa-refresh"></i></button>')
.appendTo(bg)
.click(function(evt) {
evt.stopPropagation();
evt.preventDefault();
updateEntry(globalSection,"context/global","global");
})
RED.actions.add("core:show-context-tab",show);
RED.sidebar.addTab({
id: "context",
label: RED._("sidebar.context.label"),
name: RED._("sidebar.context.name"),
iconClass: "fa fa-database",
content: content,
toolbar: footerToolbar,
// pinned: true,
enableOnEdit: false
});
// var toggleLiveButton = $("#sidebar-context-toggle-live");
// toggleLiveButton.click(function(evt) {
// evt.preventDefault();
// if ($(this).hasClass("selected")) {
// $(this).removeClass("selected");
// $(this).find("i").removeClass("fa-pause");
// $(this).find("i").addClass("fa-play");
// } else {
// $(this).addClass("selected");
// $(this).find("i").removeClass("fa-play");
// $(this).find("i").addClass("fa-pause");
// }
// });
// RED.popover.tooltip(toggleLiveButton, function() {
// if (toggleLiveButton.hasClass("selected")) {
// return "Pause live updates"
// } else {
// return "Start live updates"
// }
// });
RED.events.on("view:selection-changed", function(event) {
var selectedNode = event.nodes && event.nodes.length === 1 && event.nodes[0];
updateNode(selectedNode);
})
RED.events.on("workspace:change", function(event) {
updateFlow(RED.nodes.workspace(event.workspace));
})
updateEntry(globalSection,"context/global","global");
}
function updateNode(node,force) {
currentNode = node;
if (force) {
if (node) {
updateEntry(nodeSection,"context/node/"+node.id,node.id);
// if (/^subflow:/.test(node.type)) {
// subflowSection.container.show();
// updateEntry(subflowSection,"context/flow/"+node.id,node.id);
// } else {
// subflowSection.container.hide();
// }
} else {
// subflowSection.container.hide();
updateEntry(nodeSection)
}
} else {
$(nodeSection.table).empty();
if (node) {
$('<tr class="node-info-node-row red-ui-search-empty blank" colspan="2"><td>refresh to load</td></tr>').appendTo(nodeSection.table);
} else {
$('<tr class="node-info-node-row red-ui-search-empty blank" colspan="2"><td>none selected</td></tr>').appendTo(nodeSection.table);
}
nodeSection.timestamp.html("&nbsp;");
}
}
function updateFlow(flow) {
currentFlow = flow;
if (flow) {
updateEntry(flowSection,"context/flow/"+flow.id,flow.id);
} else {
updateEntry(flowSection)
}
}
function refreshEntry(section,baseUrl,id) {
var container = section.table;
$.getJSON(baseUrl, function(data) {
$(container).empty();
var propRow;
var keys = Object.keys(data);
keys.sort();
var l = keys.length;
for (var i = 0; i < l; i++) {
var k = keys[i];
propRow = $('<tr class="node-info-node-row"><td class="sidebar-context-property"></td><td></td></tr>').appendTo(container);
var obj = $(propRow.children()[0]);
obj.text(k);
var tools = $('<span class="debug-message-tools button-group"></span>').appendTo(obj);
var refreshItem = $('<button class="editor-button editor-button-small"><i class="fa fa-refresh"></i></button>').appendTo(tools).click(function(e) {
e.preventDefault();
e.stopPropagation();
$.getJSON(baseUrl+"/"+k, function(data) {
$(propRow.children()[1]).empty();
var payload = data.msg;
var format = data.format;
payload = RED.utils.decodeObject(payload,format);
RED.utils.createObjectElement(payload, {
typeHint: data.format,
sourceId: id+"."+k
}).appendTo(propRow.children()[1]);
})
});
var payload = data[k].msg;
var format = data[k].format;
payload = RED.utils.decodeObject(payload,format);
RED.utils.createObjectElement(payload, {
typeHint: data[k].format,
sourceId: id+"."+k
}).appendTo(propRow.children()[1]);
}
if (l === 0) {
$('<tr class="node-info-node-row red-ui-search-empty blank" colspan="2"><td>empty</td></tr>').appendTo(container);
}
$(section.timestamp).text(new Date().toLocaleString());
});
}
function updateEntry(section,baseUrl,id) {
var container = section.table;
if (id) {
refreshEntry(section,baseUrl,id);
} else {
$(container).empty();
$('<tr class="node-info-node-row red-ui-search-empty blank" colspan="2"><td>none selected</td></tr>').appendTo(container);
}
}
function show() {
RED.sidebar.show("context");
}
return {
init: init
}
})();

View File

@ -83,7 +83,9 @@ RED.sidebar.info = (function() {
id: "info", id: "info",
label: RED._("sidebar.info.label"), label: RED._("sidebar.info.label"),
name: RED._("sidebar.info.name"), name: RED._("sidebar.info.name"),
iconClass: "fa fa-info",
content: content, content: content,
pinned: true,
enableOnEdit: true enableOnEdit: true
}); });
if (tips.enabled()) { if (tips.enabled()) {
@ -168,25 +170,41 @@ RED.sidebar.info = (function() {
if (node.type === "tab") { if (node.type === "tab") {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.status")+'</td><td></td></tr>').appendTo(tableBody); propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.status")+'</td><td></td></tr>').appendTo(tableBody);
$(propRow.children()[1]).text((!!!node.disabled)?RED._("sidebar.info.enabled"):RED._("sidebar.info.disabled")) $(propRow.children()[1]).text((!!!node.disabled)?RED._("sidebar.info.enabled"):RED._("sidebar.info.disabled"))
} else if (node.type === "subflow") {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("subflow.category")+'</td><td></td></tr>').appendTo(tableBody);
var category = node.category||"subflows";
$(propRow.children()[1]).text(RED._("palette.label."+category,{defaultValue:category}))
} }
} else { } else {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.node")+"</td><td></td></tr>").appendTo(tableBody); propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.node")+"</td><td></td></tr>").appendTo(tableBody);
RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]); RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]);
if (node.type !== "subflow" && node.name) { if (node.type !== "subflow" && node.type !== "unknown" && node.name) {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("common.label.name")+'</td><td></td></tr>').appendTo(tableBody); propRow = $('<tr class="node-info-node-row"><td>'+RED._("common.label.name")+'</td><td></td></tr>').appendTo(tableBody);
$('<span class="bidiAware" dir="'+RED.text.bidi.resolveBaseTextDir(node.name)+'"></span>').text(node.name).appendTo(propRow.children()[1]); $('<span class="bidiAware" dir="'+RED.text.bidi.resolveBaseTextDir(node.name)+'"></span>').text(node.name).appendTo(propRow.children()[1]);
} }
if (!m) { if (!m) {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.type")+"</td><td></td></tr>").appendTo(tableBody); propRow = $('<tr class="node-info-node-row"><td>'+RED._("sidebar.info.type")+"</td><td></td></tr>").appendTo(tableBody);
$(propRow.children()[1]).text(node.type); $(propRow.children()[1]).text((node.type === "unknown")?node._orig.type:node.type);
if (node.type === "unknown") {
$('<span style="float: right; font-size: 0.8em"><i class="fa fa-warning"></i></span>').prependTo($(propRow.children()[1]))
}
} }
if (!m && node.type != "subflow" && node.type != "comment") { if (!m && node.type != "subflow" && node.type != "comment") {
if (node._def) { var defaults;
if (node.type === 'unknown') {
defaults = {};
Object.keys(node._orig).forEach(function(k) {
if (k !== 'type') {
defaults[k] = {};
}
})
} else if (node._def) {
defaults = node._def.defaults;
}
if (defaults) {
var count = 0; var count = 0;
var defaults = node._def.defaults;
for (var n in defaults) { for (var n in defaults) {
if (n != "name" && defaults.hasOwnProperty(n)) { if (n != "name" && defaults.hasOwnProperty(n)) {
var val = node[n]; var val = node[n];
@ -233,6 +251,9 @@ RED.sidebar.info = (function() {
} }
} }
if (m) { if (m) {
propRow = $('<tr class="node-info-node-row"><td>'+RED._("subflow.category")+'</td><td></td></tr>').appendTo(tableBody);
var category = subflowNode.category||"subflows";
$(propRow.children()[1]).text(RED._("palette.label."+category,{defaultValue:category}))
$('<tr class="node-info-subflow-row"><td>'+RED._("sidebar.info.instances")+"</td><td>"+subflowUserCount+'</td></tr>').appendTo(tableBody); $('<tr class="node-info-subflow-row"><td>'+RED._("sidebar.info.instances")+"</td><td>"+subflowUserCount+'</td></tr>').appendTo(tableBody);
} }

View File

@ -34,6 +34,10 @@ RED.utils = (function() {
result = $('<span class="debug-message-object-value debug-message-type-meta"></span>').text('buffer['+value.length+']'); result = $('<span class="debug-message-object-value debug-message-type-meta"></span>').text('buffer['+value.length+']');
} else if (value.hasOwnProperty('type') && value.type === 'array' && value.hasOwnProperty('data')) { } else if (value.hasOwnProperty('type') && value.type === 'array' && value.hasOwnProperty('data')) {
result = $('<span class="debug-message-object-value debug-message-type-meta"></span>').text('array['+value.length+']'); result = $('<span class="debug-message-object-value debug-message-type-meta"></span>').text('array['+value.length+']');
} else if (value.hasOwnProperty('type') && value.type === 'function') {
result = $('<span class="debug-message-object-value debug-message-type-meta"></span>').text('function');
} else if (value.hasOwnProperty('type') && value.type === 'number') {
result = $('<span class="debug-message-object-value debug-message-type-number"></span>').text(value.data);
} else { } else {
result = $('<span class="debug-message-object-value debug-message-type-meta">object</span>'); result = $('<span class="debug-message-object-value debug-message-type-meta">object</span>');
} }
@ -45,6 +49,8 @@ RED.utils = (function() {
subvalue = sanitize(value); subvalue = sanitize(value);
} }
result = $('<span class="debug-message-object-value debug-message-type-string"></span>').html('"'+formatString(subvalue)+'"'); result = $('<span class="debug-message-object-value debug-message-type-string"></span>').html('"'+formatString(subvalue)+'"');
} else if (typeof value === 'number') {
result = $('<span class="debug-message-object-value debug-message-type-number"></span>').text(""+value);
} else { } else {
result = $('<span class="debug-message-object-value debug-message-type-other"></span>').text(""+value); result = $('<span class="debug-message-object-value debug-message-type-other"></span>').text(""+value);
} }
@ -125,7 +131,7 @@ RED.utils = (function() {
e.stopPropagation(); e.stopPropagation();
RED.clipboard.copyText(msg,copyPayload,"clipboard.copyMessageValue"); RED.clipboard.copyText(msg,copyPayload,"clipboard.copyMessageValue");
}) })
if (strippedKey !== '') { if (strippedKey !== undefined && strippedKey !== '') {
var isPinned = pinnedPaths[sourceId].hasOwnProperty(strippedKey); var isPinned = pinnedPaths[sourceId].hasOwnProperty(strippedKey);
var pinPath = $('<button class="editor-button editor-button-small debug-message-tools-pin"><i class="fa fa-map-pin"></i></button>').appendTo(tools).click(function(e) { var pinPath = $('<button class="editor-button editor-button-small debug-message-tools-pin"><i class="fa fa-map-pin"></i></button>').appendTo(tools).click(function(e) {
@ -296,9 +302,14 @@ RED.utils = (function() {
isArray = true; isArray = true;
isArrayObject = true; isArrayObject = true;
} }
if (obj === null || obj === undefined) { if (obj === null || obj === undefined) {
$('<span class="debug-message-type-null">'+obj+'</span>').appendTo(entryObj); $('<span class="debug-message-type-null">'+obj+'</span>').appendTo(entryObj);
} else if (obj.__encoded__ && obj.type === 'number') {
e = $('<span class="debug-message-type-number debug-message-object-header"></span>').text(obj.data).appendTo(entryObj);
} else if (typeHint === "function" || (obj.__encoded__ && obj.type === 'function')) {
e = $('<span class="debug-message-type-meta debug-message-object-header"></span>').text("function").appendTo(entryObj);
} else if (typeHint === "internal" || (obj.__encoded__ && obj.type === 'internal')) {
e = $('<span class="debug-message-type-meta debug-message-object-header"></span>').text("[internal]").appendTo(entryObj);
} else if (typeof obj === 'string') { } else if (typeof obj === 'string') {
if (/[\t\n\r]/.test(obj)) { if (/[\t\n\r]/.test(obj)) {
element.addClass('collapsed'); element.addClass('collapsed');
@ -791,6 +802,33 @@ RED.utils = (function() {
return spinner; return spinner;
} }
function decodeObject(payload,format) {
if ((format === 'number') && (payload === "NaN")) {
payload = Number.NaN;
} else if ((format === 'number') && (payload === "Infinity")) {
payload = Infinity;
} else if ((format === 'number') && (payload === "-Infinity")) {
payload = -Infinity;
} else if (format === 'Object' || /^array/.test(format) || format === 'boolean' || format === 'number' ) {
payload = JSON.parse(payload);
} else if (/error/i.test(format)) {
payload = JSON.parse(payload);
payload = (payload.name?payload.name+": ":"")+payload.message;
} else if (format === 'null') {
payload = null;
} else if (format === 'undefined') {
payload = undefined;
} else if (/^buffer/.test(format)) {
var buffer = payload;
payload = [];
for (var c = 0; c < buffer.length; c += 2) {
payload.push(parseInt(buffer.substr(c, 2), 16));
}
}
return payload;
}
return { return {
createObjectElement: buildMessageElement, createObjectElement: buildMessageElement,
getMessageProperty: getMessageProperty, getMessageProperty: getMessageProperty,
@ -800,6 +838,7 @@ RED.utils = (function() {
getDefaultNodeIcon: getDefaultNodeIcon, getDefaultNodeIcon: getDefaultNodeIcon,
getNodeIcon: getNodeIcon, getNodeIcon: getNodeIcon,
getNodeLabel: getNodeLabel, getNodeLabel: getNodeLabel,
addSpinnerOverlay: addSpinnerOverlay addSpinnerOverlay: addSpinnerOverlay,
decodeObject: decodeObject
} }
})(); })();

View File

@ -0,0 +1,164 @@
/**
* 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.
**/
RED.view.navigator = (function() {
var nav_scale = 25;
var nav_width = 5000/nav_scale;
var nav_height = 5000/nav_scale;
var navContainer;
var navBox;
var navBorder;
var navVis;
var scrollPos;
var scaleFactor;
var chartSize;
var dimensions;
var isDragging;
var isShowing = false;
function refreshNodes() {
if (!isShowing) {
return;
}
var navNode = navVis.selectAll(".navnode").data(RED.view.getActiveNodes(),function(d){return d.id});
navNode.exit().remove();
navNode.enter().insert("rect")
.attr('class','navnode')
.attr("pointer-events", "none");
navNode.each(function(d) {
d3.select(this).attr("x",function(d) { return (d.x-d.w/2)/nav_scale })
.attr("y",function(d) { return (d.y-d.h/2)/nav_scale })
.attr("width",function(d) { return Math.max(9,d.w/nav_scale) })
.attr("height",function(d) { return Math.max(3,d.h/nav_scale) })
.attr("fill",function(d) { return d._def.color;})
});
}
function onScroll() {
if (!isDragging) {
resizeNavBorder();
}
}
function resizeNavBorder() {
if (navBorder) {
scaleFactor = RED.view.scale();
chartSize = [ $("#chart").width(), $("#chart").height()];
scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()];
navBorder.attr('x',scrollPos[0]/nav_scale)
.attr('y',scrollPos[1]/nav_scale)
.attr('width',chartSize[0]/nav_scale/scaleFactor)
.attr('height',chartSize[1]/nav_scale/scaleFactor)
}
}
return {
init: function() {
$(window).resize(resizeNavBorder);
RED.events.on("sidebar:resize",resizeNavBorder);
var hideTimeout;
navContainer = $('<div>').css({
"position":"absolute",
"bottom":$("#workspace-footer").height(),
"right":0,
zIndex: 1
}).appendTo("#workspace").hide();
navBox = d3.select(navContainer[0])
.append("svg:svg")
.attr("width", nav_width)
.attr("height", nav_height)
.attr("pointer-events", "all")
.style({
position: "absolute",
bottom: 0,
right:0,
zIndex: 101,
"border-left": "1px solid #ccc",
"border-top": "1px solid #ccc",
background: "rgba(245,245,245,0.5)",
"box-shadow": "-1px 0 3px rgba(0,0,0,0.1)"
});
navBox.append("rect").attr("x",0).attr("y",0).attr("width",nav_width).attr("height",nav_height).style({
fill:"none",
stroke:"none",
pointerEvents:"all"
}).on("mousedown", function() {
// Update these in case they have changed
scaleFactor = RED.view.scale();
chartSize = [ $("#chart").width(), $("#chart").height()];
dimensions = [chartSize[0]/nav_scale/scaleFactor, chartSize[1]/nav_scale/scaleFactor];
var newX = Math.max(0,Math.min(d3.event.offsetX+dimensions[0]/2,nav_width)-dimensions[0]);
var newY = Math.max(0,Math.min(d3.event.offsetY+dimensions[1]/2,nav_height)-dimensions[1]);
navBorder.attr('x',newX).attr('y',newY);
isDragging = true;
$("#chart").scrollLeft(newX*nav_scale*scaleFactor);
$("#chart").scrollTop(newY*nav_scale*scaleFactor);
}).on("mousemove", function() {
if (!isDragging) { return }
if (d3.event.buttons === 0) {
isDragging = false;
return;
}
var newX = Math.max(0,Math.min(d3.event.offsetX+dimensions[0]/2,nav_width)-dimensions[0]);
var newY = Math.max(0,Math.min(d3.event.offsetY+dimensions[1]/2,nav_height)-dimensions[1]);
navBorder.attr('x',newX).attr('y',newY);
$("#chart").scrollLeft(newX*nav_scale*scaleFactor);
$("#chart").scrollTop(newY*nav_scale*scaleFactor);
}).on("mouseup", function() {
isDragging = false;
})
navBorder = navBox.append("rect")
.attr("stroke-dasharray","5,5")
.attr("pointer-events", "none")
.style({
stroke: "#999",
strokeWidth: 1,
fill: "white",
});
navVis = navBox.append("svg:g")
$("#btn-navigate").click(function(evt) {
evt.preventDefault();
if (!isShowing) {
isShowing = true;
$("#btn-navigate").addClass("selected");
resizeNavBorder();
refreshNodes();
$("#chart").on("scroll",onScroll);
navContainer.fadeIn(200);
} else {
isShowing = false;
navContainer.fadeOut(100);
$("#chart").off("scroll",onScroll);
$("#btn-navigate").removeClass("selected");
}
})
},
refresh: refreshNodes,
resize: resizeNavBorder
}
})();

View File

@ -58,7 +58,10 @@ RED.view = (function() {
lastClickNode = null, lastClickNode = null,
dblClickPrimed = null, dblClickPrimed = null,
clickTime = 0, clickTime = 0,
clickElapsed = 0; clickElapsed = 0,
scroll_position = [],
quickAddActive = false,
quickAddLink = null;
var clipboard = ""; var clipboard = "";
@ -73,6 +76,8 @@ RED.view = (function() {
var PORT_TYPE_INPUT = 1; var PORT_TYPE_INPUT = 1;
var PORT_TYPE_OUTPUT = 0; var PORT_TYPE_OUTPUT = 0;
var chart = $("#chart");
var outer = d3.select("#chart") var outer = d3.select("#chart")
.append("svg:svg") .append("svg:svg")
.attr("width", space_width) .attr("width", space_width)
@ -94,6 +99,16 @@ RED.view = (function() {
.on("mousemove", canvasMouseMove) .on("mousemove", canvasMouseMove)
.on("mousedown", canvasMouseDown) .on("mousedown", canvasMouseDown)
.on("mouseup", canvasMouseUp) .on("mouseup", canvasMouseUp)
.on("mouseenter", function() {
if (lasso) {
if (d3.event.buttons !== 1) {
lasso.remove();
lasso = null;
}
} else if (mouse_mode === RED.state.PANNING && d3.event.buttons !== 4) {
resetMouseVars();
}
})
.on("touchend", function() { .on("touchend", function() {
clearTimeout(touchStartTime); clearTimeout(touchStartTime);
touchStartTime = null; touchStartTime = null;
@ -283,7 +298,6 @@ RED.view = (function() {
function init() { function init() {
RED.events.on("workspace:change",function(event) { RED.events.on("workspace:change",function(event) {
var chart = $("#chart");
if (event.old !== 0) { if (event.old !== 0) {
workspaceScrollPositions[event.old] = { workspaceScrollPositions[event.old] = {
left:chart.scrollLeft(), left:chart.scrollLeft(),
@ -320,6 +334,8 @@ RED.view = (function() {
redraw(); redraw();
}); });
RED.view.navigator.init();
$("#btn-zoom-out").click(function() {zoomOut();}); $("#btn-zoom-out").click(function() {zoomOut();});
$("#btn-zoom-zero").click(function() {zoomZero();}); $("#btn-zoom-zero").click(function() {zoomZero();});
$("#btn-zoom-in").click(function() {zoomIn();}); $("#btn-zoom-in").click(function() {zoomIn();});
@ -448,6 +464,82 @@ RED.view = (function() {
RED.actions.add("core:step-selection-left", function() { moveSelection(-20,0);}); RED.actions.add("core:step-selection-left", function() { moveSelection(-20,0);});
} }
function generateLinkPath(origX,origY, destX, destY, sc) {
var dy = destY-origY;
var dx = destX-origX;
var delta = Math.sqrt(dy*dy+dx*dx);
var scale = lineCurveScale;
var scaleY = 0;
if (dx*sc > 0) {
if (delta < node_width) {
scale = 0.75-0.75*((node_width-delta)/node_width);
// 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)) ;
// }
}
} else {
scale = 0.4-0.2*(Math.max(0,(node_width-Math.min(Math.abs(dx),Math.abs(dy)))/node_width));
}
if (dx*sc > 0) {
return "M "+origX+" "+origY+
" C "+(origX+sc*(node_width*scale))+" "+(origY+scaleY*node_height)+" "+
(destX-sc*(scale)*node_width)+" "+(destY-scaleY*node_height)+" "+
destX+" "+destY
} else {
var midX = Math.floor(destX-dx/2);
var midY = Math.floor(destY-dy/2);
//
if (dy === 0) {
midY = destY + node_height;
}
var cp_height = node_height/2;
var y1 = (destY + midY)/2
var topX =origX + sc*node_width*scale;
var topY = dy>0?Math.min(y1 - dy/2 , origY+cp_height):Math.max(y1 - dy/2 , origY-cp_height);
var bottomX = destX - sc*node_width*scale;
var bottomY = dy>0?Math.max(y1, destY-cp_height):Math.min(y1, destY+cp_height);
var x1 = (origX+topX)/2;
var scy = dy>0?1:-1;
var cp = [
// Orig -> Top
[x1,origY],
[topX,dy>0?Math.max(origY, topY-cp_height):Math.min(origY, topY+cp_height)],
// Top -> Mid
// [Mirror previous cp]
[x1,dy>0?Math.min(midY, topY+cp_height):Math.max(midY, topY-cp_height)],
// Mid -> Bottom
// [Mirror previous cp]
[bottomX,dy>0?Math.max(midY, bottomY-cp_height):Math.min(midY, bottomY+cp_height)],
// Bottom -> Dest
// [Mirror previous cp]
[(destX+bottomX)/2,destY]
];
if (cp[2][1] === topY+scy*cp_height) {
if (Math.abs(dy) < cp_height*10) {
cp[1][1] = topY-scy*cp_height/2;
cp[3][1] = bottomY-scy*cp_height/2;
}
cp[2][0] = topX;
}
return "M "+origX+" "+origY+
" C "+
cp[0][0]+" "+cp[0][1]+" "+
cp[1][0]+" "+cp[1][1]+" "+
topX+" "+topY+
" S "+
cp[2][0]+" "+cp[2][1]+" "+
midX+" "+midY+
" S "+
cp[3][0]+" "+cp[3][1]+" "+
bottomX+" "+bottomY+
" S "+
cp[4][0]+" "+cp[4][1]+" "+
destX+" "+destY
}
}
function addNode(type,x,y) { function addNode(type,x,y) {
var m = /^subflow:(.+)$/.exec(type); var m = /^subflow:(.+)$/.exec(type);
@ -526,6 +618,15 @@ RED.view = (function() {
function canvasMouseDown() { function canvasMouseDown() {
var point; var point;
if (d3.event.button === 1) {
// Middle Click pan
mouse_mode = RED.state.PANNING;
mouse_position = [d3.event.pageX,d3.event.pageY]
scroll_position = [chart.scrollLeft(),chart.scrollTop()];
return;
}
if (!mousedown_node && !mousedown_link) { if (!mousedown_node && !mousedown_link) {
selected_link = null; selected_link = null;
updateSelection(); updateSelection();
@ -546,14 +647,16 @@ RED.view = (function() {
mouse_mode = RED.state.QUICK_JOINING; mouse_mode = RED.state.QUICK_JOINING;
$(window).on('keyup',disableQuickJoinEventHandler); $(window).on('keyup',disableQuickJoinEventHandler);
} }
quickAddActive = true;
RED.typeSearch.show({ RED.typeSearch.show({
x:d3.event.clientX-mainPos.left-node_width/2, x:d3.event.clientX-mainPos.left-node_width/2,
y:d3.event.clientY-mainPos.top-node_height/2, y:d3.event.clientY-mainPos.top-node_height/2,
cancel: function() { cancel: function() {
quickAddActive = false;
resetMouseVars(); resetMouseVars();
}, },
add: function(type) { add: function(type) {
quickAddActive = false;
var result = addNode(type); var result = addNode(type);
if (!result) { if (!result) {
return; return;
@ -562,11 +665,10 @@ RED.view = (function() {
var historyEvent = result.historyEvent; var historyEvent = result.historyEvent;
nn.x = point[0]; nn.x = point[0];
nn.y = point[1]; nn.y = point[1];
if (mouse_mode === RED.state.QUICK_JOINING) { if (mouse_mode === RED.state.QUICK_JOINING || quickAddLink) {
if (drag_lines.length > 0) { if (quickAddLink || drag_lines.length > 0) {
var drag_line = drag_lines[0]; var drag_line = quickAddLink||drag_lines[0];
var src = null,dst,src_port; var src = null,dst,src_port;
if (drag_line.portType === PORT_TYPE_OUTPUT && nn.inputs > 0) { if (drag_line.portType === PORT_TYPE_OUTPUT && nn.inputs > 0) {
src = drag_line.node; src = drag_line.node;
src_port = drag_line.port; src_port = drag_line.port;
@ -581,9 +683,9 @@ RED.view = (function() {
RED.nodes.addLink(link); RED.nodes.addLink(link);
historyEvent.links = [link]; historyEvent.links = [link];
hideDragLines(); hideDragLines();
if (drag_line.portType === PORT_TYPE_OUTPUT && nn.outputs > 0) { if (!quickAddLink && drag_line.portType === PORT_TYPE_OUTPUT && nn.outputs > 0) {
showDragLines([{node:nn,port:0,portType:PORT_TYPE_OUTPUT}]); showDragLines([{node:nn,port:0,portType:PORT_TYPE_OUTPUT}]);
} else if (drag_line.portType === PORT_TYPE_INPUT && nn.inputs > 0) { } else if (!quickAddLink && drag_line.portType === PORT_TYPE_INPUT && nn.inputs > 0) {
showDragLines([{node:nn,port:0,portType:PORT_TYPE_INPUT}]); showDragLines([{node:nn,port:0,portType:PORT_TYPE_INPUT}]);
} else { } else {
resetMouseVars(); resetMouseVars();
@ -601,9 +703,9 @@ RED.view = (function() {
resetMouseVars(); resetMouseVars();
} }
} }
quickAddLink = null;
} }
RED.history.push(historyEvent); RED.history.push(historyEvent);
RED.nodes.add(nn); RED.nodes.add(nn);
RED.editor.validateNode(nn); RED.editor.validateNode(nn);
@ -644,7 +746,6 @@ RED.view = (function() {
function canvasMouseMove() { function canvasMouseMove() {
var i; var i;
var node; var node;
mouse_position = d3.touches(this)[0]||d3.mouse(this);
// Prevent touch scrolling... // Prevent touch scrolling...
//if (d3.touches(this)[0]) { //if (d3.touches(this)[0]) {
// d3.event.preventDefault(); // d3.event.preventDefault();
@ -655,6 +756,22 @@ RED.view = (function() {
//if (point[0]-container.scrollLeft < 30 && container.scrollLeft > 0) { container.scrollLeft -= 15; } //if (point[0]-container.scrollLeft < 30 && container.scrollLeft > 0) { container.scrollLeft -= 15; }
//console.log(d3.mouse(this),container.offsetWidth,container.offsetHeight,container.scrollLeft,container.scrollTop); //console.log(d3.mouse(this),container.offsetWidth,container.offsetHeight,container.scrollLeft,container.scrollTop);
if (mouse_mode === RED.state.PANNING) {
var pos = [d3.event.pageX,d3.event.pageY];
var deltaPos = [
mouse_position[0]-pos[0],
mouse_position[1]-pos[1]
];
chart.scrollLeft(scroll_position[0]+deltaPos[0])
chart.scrollTop(scroll_position[1]+deltaPos[1])
return
}
mouse_position = d3.touches(this)[0]||d3.mouse(this);
if (lasso) { if (lasso) {
var ox = parseInt(lasso.attr("ox")); var ox = parseInt(lasso.attr("ox"));
var oy = parseInt(lasso.attr("oy")); var oy = parseInt(lasso.attr("oy"));
@ -753,28 +870,7 @@ RED.view = (function() {
var sc = (drag_line.portType === PORT_TYPE_OUTPUT)?1:-1; var sc = (drag_line.portType === PORT_TYPE_OUTPUT)?1:-1;
var dy = mousePos[1]-(drag_line.node.y+portY); drag_line.el.attr("d",generateLinkPath(drag_line.node.x+sc*drag_line.node.w/2,drag_line.node.y+portY,mousePos[0],mousePos[1],sc));
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(); d3.event.preventDefault();
} else if (mouse_mode == RED.state.MOVING) { } else if (mouse_mode == RED.state.MOVING) {
@ -906,6 +1002,10 @@ RED.view = (function() {
function canvasMouseUp() { function canvasMouseUp() {
var i; var i;
var historyEvent; var historyEvent;
if (mouse_mode === RED.state.PANNING) {
resetMouseVars();
return
}
if (mouse_mode === RED.state.QUICK_JOINING) { if (mouse_mode === RED.state.QUICK_JOINING) {
return; return;
} }
@ -1020,17 +1120,20 @@ RED.view = (function() {
function zoomIn() { function zoomIn() {
if (scaleFactor < 2) { if (scaleFactor < 2) {
scaleFactor += 0.1; scaleFactor += 0.1;
RED.view.navigator.resize();
redraw(); redraw();
} }
} }
function zoomOut() { function zoomOut() {
if (scaleFactor > 0.3) { if (scaleFactor > 0.3) {
scaleFactor -= 0.1; scaleFactor -= 0.1;
RED.view.navigator.resize();
redraw(); redraw();
} }
} }
function zoomZero() { function zoomZero() {
scaleFactor = 1; scaleFactor = 1;
RED.view.navigator.resize();
redraw(); redraw();
} }
@ -1367,6 +1470,9 @@ RED.view = (function() {
function disableQuickJoinEventHandler(evt) { function disableQuickJoinEventHandler(evt) {
// Check for ctrl (all browsers), "Meta" (Chrome/FF), keyCode 91 (Safari) // Check for ctrl (all browsers), "Meta" (Chrome/FF), keyCode 91 (Safari)
if (evt.keyCode === 17 || evt.key === "Meta" || evt.keyCode === 91) { if (evt.keyCode === 17 || evt.key === "Meta" || evt.keyCode === 91) {
if (quickAddActive && drag_lines.length > 0) {
quickAddLink = drag_lines[0];
}
resetMouseVars(); resetMouseVars();
hideDragLines(); hideDragLines();
redraw(); redraw();
@ -1378,6 +1484,10 @@ RED.view = (function() {
//console.log(d,portType,portIndex); //console.log(d,portType,portIndex);
// disable zoom // disable zoom
//vis.call(d3.behavior.zoom().on("zoom"), null); //vis.call(d3.behavior.zoom().on("zoom"), null);
if (d3.event.button === 1) {
return;
}
mousedown_node = d; mousedown_node = d;
mousedown_port_type = portType; mousedown_port_type = portType;
mousedown_port_index = portIndex || 0; mousedown_port_index = portIndex || 0;
@ -1609,6 +1719,9 @@ RED.view = (function() {
function nodeMouseDown(d) { function nodeMouseDown(d) {
focusView(); focusView();
if (d3.event.button === 1) {
return;
}
//var touch0 = d3.event; //var touch0 = d3.event;
//var pos = [touch0.pageX,touch0.pageY]; //var pos = [touch0.pageX,touch0.pageY];
//RED.touch.radialMenu.show(d3.select(this),pos); //RED.touch.radialMenu.show(d3.select(this),pos);
@ -1652,7 +1765,9 @@ RED.view = (function() {
clickElapsed = now-clickTime; clickElapsed = now-clickTime;
clickTime = now; clickTime = now;
dblClickPrimed = (lastClickNode == mousedown_node); dblClickPrimed = (lastClickNode == mousedown_node &&
d3.event.buttons === 1 &&
!d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey);
lastClickNode = mousedown_node; lastClickNode = mousedown_node;
var i; var i;
@ -2341,32 +2456,17 @@ RED.view = (function() {
var numOutputs = d.source.outputs || 1; var numOutputs = d.source.outputs || 1;
var sourcePort = d.sourcePort || 0; var sourcePort = d.sourcePort || 0;
var y = -((numOutputs-1)/2)*13 +13*sourcePort; var y = -((numOutputs-1)/2)*13 +13*sourcePort;
var dy = d.target.y-(d.source.y+y);
var dx = (d.target.x-d.target.w/2)-(d.source.x+d.source.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 < 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)) ;
}
}
d.x1 = d.source.x+d.source.w/2; d.x1 = d.source.x+d.source.w/2;
d.y1 = d.source.y+y; d.y1 = d.source.y+y;
d.x2 = d.target.x-d.target.w/2; d.x2 = d.target.x-d.target.w/2;
d.y2 = d.target.y; d.y2 = d.target.y;
return "M "+d.x1+" "+d.y1+ // return "M "+d.x1+" "+d.y1+
" C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+ // " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+
(d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+ // (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+
d.x2+" "+d.y2; // d.x2+" "+d.y2;
return generateLinkPath(d.x1,d.y1,d.x2,d.y2,1);
}); });
} }
}) })
@ -2502,7 +2602,7 @@ RED.view = (function() {
} }
).classed("link_selected", false); ).classed("link_selected", false);
} }
RED.view.navigator.refresh();
if (d3.event) { if (d3.event) {
d3.event.preventDefault(); d3.event.preventDefault();
} }
@ -2776,7 +2876,9 @@ RED.view = (function() {
gridSize = Math.max(5,v); gridSize = Math.max(5,v);
updateGrid(); updateGrid();
} }
},
getActiveNodes: function() {
return activeNodes;
} }
}; };
})(); })();

View File

@ -64,6 +64,7 @@
display: inline-block; display: inline-block;
} }
} }
}
.debug-message-row { .debug-message-row {
.debug-message-tools-pin { .debug-message-tools-pin {
display: none; display: none;
@ -83,7 +84,6 @@
} }
} }
} }
}
.debug-message-meta .debug-message-tools { .debug-message-meta .debug-message-tools {
.editor-button-small { .editor-button-small {
font-size: 11px; font-size: 11px;

View File

@ -353,3 +353,64 @@
} }
} }
#node-settings-icon {
margin-left: 10px;
width: calc(100% - 163px);
}
.red-ui-icon-picker {
position: absolute;
border: 1px solid $primary-border-color;
box-shadow: 0 1px 6px -3px black;
background: white;
z-Index: 21;
display: none;
select {
box-sizing: border-box;
margin: 3px;
width: calc(100% - 6px);
}
}
.red-ui-icon-list {
width: 308px;
height: 200px;
overflow-y: scroll;
line-height: 0px;
}
.red-ui-icon-list-icon {
display: inline-block;
margin: 2px;
padding: 4px;
cursor: pointer;
border-radius: 4px;
&:hover {
background: lighten($node-selected-color,20%);
}
&.selected {
background: lighten($node-selected-color,20%);
.red-ui-search-result-node {
border-color: white;
}
}
}
.red-ui-icon-list-module {
background: $palette-header-background;
font-size: 0.9em;
padding: 3px;
color: #666;
clear: both;
i {
margin-right: 5px;
}
}
.red-ui-icon-meta {
border-top: 1px solid $secondary-border-color;
span {
padding: 4px;
color: #666;
font-size: 0.9em;
}
button {
float: right;
margin: 2px;
}
}

View File

@ -248,7 +248,7 @@
.link_outline { .link_outline {
stroke: #fff; stroke: #fff;
stroke-width: 4; stroke-width: 5;
cursor: crosshair; cursor: crosshair;
fill: none; fill: none;
pointer-events: none; pointer-events: none;

View File

@ -134,15 +134,18 @@
color: $workspace-button-toggle-color !important; color: $workspace-button-toggle-color !important;
background:$workspace-button-background-active; background:$workspace-button-background-active;
margin-bottom: 1px; margin-bottom: 1px;
&.selected:not(.disabled) {
&.selected:not(.disabled):not(:disabled) {
color: $workspace-button-toggle-color-selected !important; color: $workspace-button-toggle-color-selected !important;
background: $workspace-button-background; background: $workspace-button-background;
border-bottom-width: 2px; border-bottom-width: 2px;
border-bottom-color: $form-input-border-selected-color; border-bottom-color: $form-input-border-selected-color;
margin-bottom: 0; margin-bottom: 0;
&:not(.single) {
cursor: default; cursor: default;
} }
&.disabled { }
&.disabled,&:disabled {
color: $workspace-button-toggle-color-disabled !important; color: $workspace-button-toggle-color-disabled !important;
} }
} }
@ -203,7 +206,7 @@
height: 25px; height: 25px;
line-height: 23px; line-height: 23px;
padding: 0 10px; padding: 0 10px;
user-select: none;
.button-group:not(:last-child) { .button-group:not(:last-child) {
margin-right: 5px; margin-right: 5px;
@ -227,6 +230,7 @@
font-size: 11px; font-size: 11px;
line-height: 17px; line-height: 17px;
height: 18px; height: 18px;
width: 18px;
&.text-button { &.text-button {
width: auto; width: auto;
padding: 0 5px; padding: 0 5px;

View File

@ -90,13 +90,14 @@
text-align: left; text-align: left;
padding: 9px; padding: 9px;
font-weight: bold; font-weight: bold;
padding-left: 30px;
overflow: hidden; overflow: hidden;
white-space: nowrap; user-select: none;
text-overflow: ellipsis;
} }
.palette-header > i { .palette-header > i {
margin: 3px 10px 3px 3px; position: absolute;
left: 11px;
top: 12px;
-webkit-transition: all 0.2s ease-in-out; -webkit-transition: all 0.2s ease-in-out;
-moz-transition: all 0.2s ease-in-out; -moz-transition: all 0.2s ease-in-out;
-o-transition: all 0.2s ease-in-out; -o-transition: all 0.2s ease-in-out;

View File

@ -30,7 +30,6 @@
} }
.red-ui-popover:after, .red-ui-popover:before { .red-ui-popover:after, .red-ui-popover:before {
top: 50%;
border: solid transparent; border: solid transparent;
content: " "; content: " ";
height: 0; height: 0;
@ -39,12 +38,18 @@
pointer-events: none; pointer-events: none;
} }
.red-ui-popover.red-ui-popover-right:after, .red-ui-popover.red-ui-popover-right:before { .red-ui-popover.red-ui-popover-right:after, .red-ui-popover.red-ui-popover-right:before {
top: 50%;
right: 100%; right: 100%;
} }
.red-ui-popover.red-ui-popover-left:after, .red-ui-popover.red-ui-popover-left:before { .red-ui-popover.red-ui-popover-left:after, .red-ui-popover.red-ui-popover-left:before {
top: 50%;
left: 100%; left: 100%;
} }
.red-ui-popover.red-ui-popover-bottom:after, .red-ui-popover.red-ui-popover-bottom:before {
bottom: 100%;
left: 50%;
}
.red-ui-popover.red-ui-popover-right:after { .red-ui-popover.red-ui-popover-right:after {
border-color: rgba(136, 183, 213, 0); border-color: rgba(136, 183, 213, 0);
@ -72,6 +77,21 @@
margin-top: -11px; margin-top: -11px;
} }
.red-ui-popover.red-ui-popover-bottom:after {
border-color: rgba(136, 183, 213, 0);
border-bottom-color: #fff;
border-width: 10px;
margin-left: -10px;
}
.red-ui-popover.red-ui-popover-bottom:before {
border-color: rgba(194, 225, 245, 0);
border-bottom-color: $primary-border-color;
border-width: 11px;
margin-left: -11px;
}
.red-ui-popover-size-small { .red-ui-popover-size-small {
font-size: 11px; font-size: 11px;
padding: 5px; padding: 5px;
@ -93,4 +113,12 @@
border-width: 6px; border-width: 6px;
margin-top: -6px; margin-top: -6px;
} }
&.red-ui-popover-bottom:after {
border-width: 5px;
margin-left: -5px;
}
&.red-ui-popover-bottom:before {
border-width: 6px;
margin-left: -6px;
}
} }

View File

@ -40,6 +40,7 @@
@import "panels"; @import "panels";
@import "tabs"; @import "tabs";
@import "tab-config"; @import "tab-config";
@import "tab-context";
@import "tab-info"; @import "tab-info";
@import "popover"; @import "popover";
@import "flow"; @import "flow";

View File

@ -0,0 +1,47 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.
**/
.sidebar-context-stack {
position: absolute;
top: 0; // with toolbar: 43px;
bottom: 0;
left: 0;
right: 0;
overflow-y: scroll;
.palette-category {
&:not(.palette-category-expanded) button {
display: none;
}
}
}
.sidebar-context-property {
position: relative;
.debug-message-tools {
right: 3px;
display: none;
}
&:hover .debug-message-tools {
display: inline-block;
}
}
.sidebar-context-updated {
text-align: right;
font-size: 11px;
color: #bbb;
padding: 1px 3px;
}

View File

@ -93,7 +93,7 @@
color: $workspace-button-color-hover; color: $workspace-button-color-hover;
} }
} }
.red-ui-tab-icon { img.red-ui-tab-icon {
opacity: 0.2; opacity: 0.2;
} }
} }
@ -113,6 +113,21 @@
&.red-ui-tabs-add.red-ui-tabs-scrollable { &.red-ui-tabs-add.red-ui-tabs-scrollable {
padding-right: 59px; padding-right: 59px;
} }
&.red-ui-tabs-collapsible {
li:not(.active) {
display: none;
&.red-ui-tab-pinned {
a {
padding-left: 0;
text-align: center;
}
span {
display: none;
}
width: 32px;
}
}
}
&.red-ui-tabs-vertical { &.red-ui-tabs-vertical {
box-sizing: border-box; box-sizing: border-box;
@ -157,6 +172,15 @@
} }
} }
} }
.red-ui-tabs-select {
position: absolute;
top:0;
bottom: 0;
left: 0;
right: 0;
opacity: 0.4;
background: red;
}
} }
.red-ui-tab-button { .red-ui-tab-button {
position: absolute; position: absolute;
@ -180,7 +204,32 @@
z-index: 2; z-index: 2;
} }
} }
.red-ui-tab-link-buttons {
position: absolute;
box-sizing: border-box;
top: 0;
right: 0;
height: 35px;
background: #fff;
border-bottom: 1px solid $primary-border-color;
z-index: 2;
a {
@include workspace-button-toggle;
line-height: 26px;
height: 28px;
width: 28px;
margin: 4px 3px 3px;
z-index: 2;
&.red-ui-tab-link-button {
&:not(.active) {
background: #eee;
}
}
&.red-ui-tab-link-button-menu {
border-color: white;
}
}
}
.red-ui-tab-scroll { .red-ui-tab-scroll {
width: 21px; width: 21px;
top: 0; top: 0;
@ -216,7 +265,7 @@
right: 38px; right: 38px;
} }
.red-ui-tab-icon { img.red-ui-tab-icon {
margin-left: -8px; margin-left: -8px;
margin-right: 3px; margin-right: 3px;
margin-top: -2px; margin-top: -2px;
@ -225,6 +274,11 @@
height: 20px; height: 20px;
vertical-align: middle; vertical-align: middle;
} }
i.red-ui-tab-icon {
opacity: 0.7;
width: 18px;
height: 20px;
}
.red-ui-tabs-badges { .red-ui-tabs-badges {
position: absolute; position: absolute;

View File

@ -23,7 +23,7 @@
margin: 0; margin: 0;
vertical-align: middle; vertical-align: middle;
box-sizing: border-box; box-sizing: border-box;
overflow:hidden; overflow:visible;
position: relative; position: relative;
.red-ui-typedInput-input { .red-ui-typedInput-input {
position: absolute; position: absolute;
@ -43,6 +43,7 @@
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
box-shadow: none; box-shadow: none;
vertical-align: middle; vertical-align: middle;
// backgroun/d: #f0fff0;
} }
&.red-ui-typedInput-focus:not(.input-error) { &.red-ui-typedInput-focus:not(.input-error) {
@ -63,7 +64,7 @@
line-height: 32px; line-height: 32px;
vertical-align: middle; vertical-align: middle;
color: #555; color: #555;
i { i.red-ui-typedInput-icon {
position: relative; position: relative;
top: -3px; top: -3px;
margin-left: 1px; margin-left: 1px;
@ -76,11 +77,11 @@
} }
&.disabled { &.disabled {
cursor: default; cursor: default;
i { i.red-ui-typedInput-icon {
color: #bbb; color: #bbb;
} }
} }
span { .red-ui-typedInput-type-label,.red-ui-typedInput-option-label {
display: inline-block; display: inline-block;
height: 100%; height: 100%;
padding: 0 1px 0 5px; padding: 0 1px 0 5px;
@ -121,26 +122,25 @@
border-bottom-right-radius: 4px; border-bottom-right-radius: 4px;
padding: 0 0 0 0; padding: 0 0 0 0;
position:absolute; position:absolute;
width: calc( 100% ); right: 0;
i {
position:absolute;
right: 4px;
top: 7px;
}
.red-ui-typedInput-option-label { .red-ui-typedInput-option-label {
background:#fff; background:$typedInput-button-background;
position:absolute; position:absolute;
left:0; left:0;
right:23px; right:23px;
top: 0; top: 0;
padding: 0 5px 0 5px; padding: 0 5px 0 8px;
i.red-ui-typedInput-icon {
margin-right: 4px;
margin-top: 4px;
}
} }
.red-ui-typedInput-option-caret { .red-ui-typedInput-option-caret {
top: 0; top: 0;
position: absolute; position: absolute;
right: 0; right: 0;
width: 17px; width: 17px;
padding-left: 6px;
} }
&:focus { &:focus {
box-shadow: none; box-shadow: none;
@ -175,4 +175,7 @@
background: $typedInput-button-background-active; background: $typedInput-button-background-active;
} }
} }
.red-ui-typedInput-icon {
margin-right: 4px;
}
} }

View File

@ -47,7 +47,9 @@
.workspace-footer-button { .workspace-footer-button {
@include component-footer-button; @include component-footer-button;
} }
.workspace-footer-button-toggle {
@include component-footer-button-toggle;
}
#workspace-footer { #workspace-footer {
@include component-footer; @include component-footer;
} }

View File

@ -51,6 +51,7 @@
<a class="workspace-footer-button" id="btn-zoom-out" href="#"><i class="fa fa-minus"></i></a> <a class="workspace-footer-button" id="btn-zoom-out" href="#"><i class="fa fa-minus"></i></a>
<a class="workspace-footer-button" id="btn-zoom-zero" href="#"><i class="fa fa-circle-o"></i></a> <a class="workspace-footer-button" id="btn-zoom-zero" href="#"><i class="fa fa-circle-o"></i></a>
<a class="workspace-footer-button" id="btn-zoom-in" href="#"><i class="fa fa-plus"></i></a> <a class="workspace-footer-button" id="btn-zoom-in" href="#"><i class="fa fa-plus"></i></a>
<a class="workspace-footer-button-toggle single" id="btn-navigate" href="#"><i class="fa fa-map-o"></i></a>
</div> </div>
<div id="editor-shade" class="hide"></div> <div id="editor-shade" class="hide"></div>
</div> </div>
@ -131,6 +132,10 @@
<i class="fa fa-tag"></i> <i class="fa fa-tag"></i>
<label for="subflow-input-name" data-i18n="common.label.name"></label><input type="text" id="subflow-input-name"> <label for="subflow-input-name" data-i18n="common.label.name"></label><input type="text" id="subflow-input-name">
</div> </div>
<div class="form-row">
<i class="fa fa-folder-o"></i>
<label for="subflow-input-category" data-i18n="editor:subflow.category"></label><select style="width: 250px;" id="subflow-input-category"></select><input style="display:none; margin-left: 10px; width:calc(100% - 250px)" type="text" id="subflow-input-custom-category">
</div>
<div class="form-row" style="margin-bottom: 0px;"> <div class="form-row" style="margin-bottom: 0px;">
<label for="subflow-input-info" data-i18n="editor:subflow.info"></label> <label for="subflow-input-info" data-i18n="editor:subflow.info"></label>
<a href="https://help.github.com/articles/markdown-basics/" style="font-size: 0.8em; float: right;" data-i18n="[html]subflow.format"></a> <a href="https://help.github.com/articles/markdown-basics/" style="font-size: 0.8em; float: right;" data-i18n="[html]subflow.format"></a>

View File

@ -117,6 +117,7 @@
'$contains':{ args:[ 'str', 'pattern' ]}, '$contains':{ args:[ 'str', 'pattern' ]},
'$count':{ args:[ 'array' ]}, '$count':{ args:[ 'array' ]},
'$each':{ args:[ 'object', 'function' ]}, '$each':{ args:[ 'object', 'function' ]},
'$env': { args:[ 'arg' ]},
'$exists':{ args:[ 'arg' ]}, '$exists':{ args:[ 'arg' ]},
'$filter':{ args:[ 'array', 'function' ]}, '$filter':{ args:[ 'array', 'function' ]},
'$floor':{ args:[ 'number' ]}, '$floor':{ args:[ 'number' ]},

View File

@ -237,10 +237,13 @@ If you want every 20 minutes from now - use the <i>"interval"</i> option.</p>
} else { } else {
return this._("inject.timestamp")+suffix; return this._("inject.timestamp")+suffix;
} }
} else if (this.payloadType === 'flow' && this.payload.length < 19) { } else if (this.payloadType === 'flow' || this.payloadType === 'global') {
return 'flow.'+this.payload+suffix; var key = this.payload;
} else if (this.payloadType === 'global' && this.payload.length < 17) { var m = /^#:\((\S+?)\)::(.*)$/.exec(key);
return 'global.'+this.payload+suffix; if (m) {
key = m[2];
}
return 'flow.'+key+suffix;
} else { } else {
return this._("inject.inject")+suffix; return this._("inject.inject")+suffix;
} }
@ -263,7 +266,7 @@ If you want every 20 minutes from now - use the <i>"interval"</i> option.</p>
$("#node-input-payload").typedInput({ $("#node-input-payload").typedInput({
default: 'str', default: 'str',
typeField: $("#node-input-payloadType"), typeField: $("#node-input-payloadType"),
types:['flow','global','str','num','bool','json','bin','date'] types:['flow','global','str','num','bool','json','bin','date','env']
}); });
$("#inject-time-type-select").change(function() { $("#inject-time-type-select").change(function() {

View File

@ -154,7 +154,9 @@
name: this._("debug.sidebar.name"), name: this._("debug.sidebar.name"),
content: uiComponents.content, content: uiComponents.content,
toolbar: uiComponents.footer, toolbar: uiComponents.footer,
enableOnEdit: true enableOnEdit: true,
pinned: true,
iconClass: "fa fa-list-alt"
}); });
RED.actions.add("core:show-debug-tab",function() { RED.sidebar.show('debug'); }); RED.actions.add("core:show-debug-tab",function() { RED.sidebar.show('debug'); });

View File

@ -4,7 +4,6 @@ module.exports = function(RED) {
var util = require("util"); var util = require("util");
var events = require("events"); var events = require("events");
var path = require("path"); var path = require("path");
var safeJSONStringify = require("json-stringify-safe");
var debuglength = RED.settings.debugMaxLength || 1000; var debuglength = RED.settings.debugMaxLength || 1000;
var useColors = RED.settings.debugUseColors || false; var useColors = RED.settings.debugUseColors || false;
util.inspect.styles.boolean = "red"; util.inspect.styles.boolean = "red";
@ -104,111 +103,7 @@ module.exports = function(RED) {
function sendDebug(msg) { function sendDebug(msg) {
// don't put blank errors in sidebar (but do add to logs) // don't put blank errors in sidebar (but do add to logs)
//if ((msg.msg === "") && (msg.hasOwnProperty("level")) && (msg.level === 20)) { return; } //if ((msg.msg === "") && (msg.hasOwnProperty("level")) && (msg.level === 20)) { return; }
if (msg.msg instanceof Error) { msg = RED.util.encodeObject(msg,{maxLength:debuglength});
msg.format = "error";
var errorMsg = {};
if (msg.msg.name) {
errorMsg.name = msg.msg.name;
}
if (msg.msg.hasOwnProperty('message')) {
errorMsg.message = msg.msg.message;
} else {
errorMsg.message = msg.msg.toString();
}
msg.msg = JSON.stringify(errorMsg);
} else if (msg.msg instanceof Buffer) {
msg.format = "buffer["+msg.msg.length+"]";
msg.msg = msg.msg.toString('hex');
if (msg.msg.length > debuglength) {
msg.msg = msg.msg.substring(0,debuglength);
}
} else if (msg.msg && typeof msg.msg === 'object') {
try {
msg.format = msg.msg.constructor.name || "Object";
// Handle special case of msg.req/res objects from HTTP In node
if (msg.format === "IncomingMessage" || msg.format === "ServerResponse") {
msg.format = "Object";
}
} catch(err) {
msg.format = "Object";
}
if (/error/i.test(msg.format)) {
msg.msg = JSON.stringify({
name: msg.msg.name,
message: msg.msg.message
});
} else {
var isArray = util.isArray(msg.msg);
if (isArray) {
msg.format = "array["+msg.msg.length+"]";
if (msg.msg.length > debuglength) {
// msg.msg = msg.msg.slice(0,debuglength);
msg.msg = {
__encoded__: true,
type: "array",
data: msg.msg.slice(0,debuglength),
length: msg.msg.length
}
}
}
if (isArray || (msg.format === "Object")) {
msg.msg = safeJSONStringify(msg.msg, function(key, value) {
if (key === '_req' || key === '_res') {
value = "[internal]"
} else if (value instanceof Error) {
value = value.toString()
} else if (util.isArray(value) && value.length > debuglength) {
value = {
__encoded__: true,
type: "array",
data: value.slice(0,debuglength),
length: value.length
}
} else if (typeof value === 'string') {
if (value.length > debuglength) {
value = value.substring(0,debuglength)+"...";
}
} else if (value && value.constructor) {
if (value.type === "Buffer") {
value.__encoded__ = true;
value.length = value.data.length;
if (value.length > debuglength) {
value.data = value.data.slice(0,debuglength);
}
} else if (value.constructor.name === "ServerResponse") {
value = "[internal]"
} else if (value.constructor.name === "Socket") {
value = "[internal]"
}
}
return value;
}," ");
} else {
try { msg.msg = msg.msg.toString(); }
catch(e) { msg.msg = "[Type not printable]"; }
}
}
} else if (typeof msg.msg === "boolean") {
msg.format = "boolean";
msg.msg = msg.msg.toString();
} else if (typeof msg.msg === "number") {
msg.format = "number";
msg.msg = msg.msg.toString();
} else if (msg.msg === 0) {
msg.format = "number";
msg.msg = "0";
} else if (msg.msg === null || typeof msg.msg === "undefined") {
msg.format = (msg.msg === null)?"null":"undefined";
msg.msg = "(undefined)";
} else {
msg.format = "string["+msg.msg.length+"]";
if (msg.msg.length > debuglength) {
msg.msg = msg.msg.substring(0,debuglength)+"...";
}
}
// if (msg.msg.length > debuglength) {
// msg.msg = msg.msg.substr(0,debuglength) +" ....";
// }
RED.comms.publish("debug",msg); RED.comms.publish("debug",msg);
} }

View File

@ -52,6 +52,12 @@
<p>The Catch node can also be used to handle errors. To invoke a Catch node, <p>The Catch node can also be used to handle errors. To invoke a Catch node,
pass <code>msg</code> as a second argument to <code>node.error</code>:</p> pass <code>msg</code> as a second argument to <code>node.error</code>:</p>
<pre>node.error("Error",msg);</pre> <pre>node.error("Error",msg);</pre>
<h4>Referring Node Information</h4>
<p>In the function block, id and name of the node can be referenced using the following properties:</p>
<ul>
<li><code>node.id</code> - id of the node</li>
<li><code>node.name</code> - name of the node</li>
</ul>
</script> </script>
<script type="text/javascript"> <script type="text/javascript">

View File

@ -62,6 +62,8 @@ module.exports = function(RED) {
"results = (function(msg){ "+ "results = (function(msg){ "+
"var __msgid__ = msg._msgid;"+ "var __msgid__ = msg._msgid;"+
"var node = {"+ "var node = {"+
"id:__node__.id,"+
"name:__node__.name,"+
"log:__node__.log,"+ "log:__node__.log,"+
"error:__node__.error,"+ "error:__node__.error,"+
"warn:__node__.warn,"+ "warn:__node__.warn,"+
@ -85,6 +87,8 @@ module.exports = function(RED) {
util: RED.util util: RED.util
}, },
__node__: { __node__: {
id: node.id,
name: node.name,
log: function() { log: function() {
node.log.apply(node, arguments); node.log.apply(node, arguments);
}, },

View File

@ -455,24 +455,8 @@ RED.debug = (function() {
$('<span class="debug-message-name">'+name+'</span>').appendTo(metaRow); $('<span class="debug-message-name">'+name+'</span>').appendTo(metaRow);
} }
if ((format === 'number') && (payload === "NaN")) { payload = RED.utils.decodeObject(payload,format);
payload = Number.NaN;
} else if (format === 'Object' || /^array/.test(format) || format === 'boolean' || format === 'number' ) {
payload = JSON.parse(payload);
} else if (/error/i.test(format)) {
payload = JSON.parse(payload);
payload = (payload.name?payload.name+": ":"")+payload.message;
} else if (format === 'null') {
payload = null;
} else if (format === 'undefined') {
payload = undefined;
} else if (/^buffer/.test(format)) {
var buffer = payload;
payload = [];
for (var c = 0; c < buffer.length; c += 2) {
payload.push(parseInt(buffer.substr(c, 2), 16));
}
}
var el = $('<span class="debug-message-payload"></span>').appendTo(msg); var el = $('<span class="debug-message-payload"></span>').appendTo(msg);
var path = o.property||''; var path = o.property||'';
var debugMessage = RED.utils.createObjectElement(payload, { var debugMessage = RED.utils.createObjectElement(payload, {

View File

@ -6,35 +6,36 @@ module.exports = function(RED) {
var fs = require('fs'); var fs = require('fs');
var gpioCommand = __dirname+'/nrgpio'; var gpioCommand = __dirname+'/nrgpio';
var allOK = true;
try { try {
var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString(); var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString();
if (cpuinfo.indexOf(": BCM") === -1) { throw "Info : "+RED._("rpi-gpio.errors.ignorenode"); } if (cpuinfo.indexOf(": BCM") === -1) {
} catch(err) { allOK = false;
throw "Info : "+RED._("rpi-gpio.errors.ignorenode"); RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.ignorenode"));
} }
try { try {
fs.statSync("/usr/share/doc/python-rpi.gpio"); // test on Raspbian fs.statSync("/usr/share/doc/python-rpi.gpio"); // test on Raspbian
// /usr/lib/python2.7/dist-packages/RPi/GPIO // /usr/lib/python2.7/dist-packages/RPi/GPIO
} catch(err) { } catch(err) {
try { try {
fs.statSync("/usr/lib/python2.7/site-packages/RPi/GPIO"); // test on Arch fs.statSync("/usr/lib/python2.7/site-packages/RPi/GPIO"); // test on Arch
} } catch(err) {
catch(err) {
try { try {
fs.statSync("/usr/lib/python2.7/dist-packages/RPi/GPIO"); // test on Hypriot fs.statSync("/usr/lib/python2.7/dist-packages/RPi/GPIO"); // test on Hypriot
} } catch(err) {
catch(err) { RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.libnotfound"));
RED.log.warn(RED._("rpi-gpio.errors.libnotfound")); allOK = false;
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})); RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.needtobeexecutable",{command:gpioCommand}));
throw "Error : "+RED._("rpi-gpio.errors.mustbeexecutable"); allOK = false;
}
} catch(err) {
allOK = false;
RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.ignorenode"));
} }
// the magic to make python print stuff immediately // the magic to make python print stuff immediately
@ -61,6 +62,7 @@ module.exports = function(RED) {
} }
} }
if (allOK === true) {
if (node.pin !== undefined) { if (node.pin !== undefined) {
node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]); node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]);
node.running = true; node.running = true;
@ -104,6 +106,19 @@ module.exports = function(RED) {
else { else {
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin); node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
} }
}
else {
node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
if (node.read === true) {
var val;
if (node.intype == "up") { val = 1; }
if (node.intype == "down") { val = 0; }
setTimeout(function(){
node.send({ topic:"pi/"+node.pin, payload:val });
node.status({fill:"grey",shape:"dot",text:RED._("rpi-gpio.status.na",{value:val})});
},250);
}
}
node.on("close", function(done) { node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
@ -155,6 +170,7 @@ module.exports = function(RED) {
else { node.warn(RED._("rpi-gpio.errors.invalidinput")+": "+out); } else { node.warn(RED._("rpi-gpio.errors.invalidinput")+": "+out); }
} }
if (allOK === true) {
if (node.pin !== undefined) { if (node.pin !== undefined) {
if (node.set && (node.out === "out")) { if (node.set && (node.out === "out")) {
node.child = spawn(gpioCommand, [node.out,node.pin,node.level]); node.child = spawn(gpioCommand, [node.out,node.pin,node.level]);
@ -196,6 +212,13 @@ module.exports = function(RED) {
else { else {
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin); node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
} }
}
else {
node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
node.on("input", function(msg){
node.status({fill:"grey",shape:"dot",text:RED._("rpi-gpio.status.na",{value:msg.payload.toString()})});
});
}
node.on("close", function(done) { node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"}); node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
@ -216,12 +239,13 @@ module.exports = function(RED) {
this.butt = n.butt || 7; this.butt = n.butt || 7;
var node = this; var node = this;
if (allOK === true) {
node.child = spawn(gpioCommand+".py", ["mouse",node.butt]); node.child = spawn(gpioCommand+".py", ["mouse",node.butt]);
node.status({fill:"green",shape:"dot",text:"common.status.ok"}); node.status({fill:"green",shape:"dot",text:"common.status.ok"});
node.child.stdout.on('data', function (data) { node.child.stdout.on('data', function (data) {
data = Number(data); data = Number(data);
if (data === 1) { node.send({ topic:"pi/mouse", button:data, payload:1 }); } if (data !== 0) { node.send({ topic:"pi/mouse", button:data, payload:1 }); }
else { node.send({ topic:"pi/mouse", button:data, payload:0 }); } else { node.send({ topic:"pi/mouse", button:data, payload:0 }); }
}); });
@ -256,12 +280,17 @@ module.exports = function(RED) {
else { done(); } else { done(); }
}); });
} }
else {
node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
}
}
RED.nodes.registerType("rpi-mouse",PiMouseNode); RED.nodes.registerType("rpi-mouse",PiMouseNode);
function PiKeyboardNode(n) { function PiKeyboardNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
var node = this; var node = this;
if (allOK === true) {
node.child = spawn(gpioCommand+".py", ["kbd","0"]); node.child = spawn(gpioCommand+".py", ["kbd","0"]);
node.status({fill:"green",shape:"dot",text:"common.status.ok"}); node.status({fill:"green",shape:"dot",text:"common.status.ok"});
@ -304,9 +333,14 @@ module.exports = function(RED) {
else { done(); } else { done(); }
}); });
} }
else {
node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
}
}
RED.nodes.registerType("rpi-keyboard",PiKeyboardNode); RED.nodes.registerType("rpi-keyboard",PiKeyboardNode);
var pitype = { type:"" }; var pitype = { type:"" };
if (allOK === true) {
exec(gpioCommand+" info", function(err,stdout,stderr) { exec(gpioCommand+" info", function(err,stdout,stderr) {
if (err) { if (err) {
RED.log.info(RED._("rpi-gpio.errors.version")); RED.log.info(RED._("rpi-gpio.errors.version"));
@ -321,6 +355,7 @@ module.exports = function(RED) {
} }
} }
}); });
}
RED.httpAdmin.get('/rpi-gpio/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) { RED.httpAdmin.get('/rpi-gpio/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) {
res.json(pitype); res.json(pitype);

View File

@ -21,8 +21,6 @@ module.exports = function(RED) {
var cookieParser = require("cookie-parser"); var cookieParser = require("cookie-parser");
var getBody = require('raw-body'); var getBody = require('raw-body');
var cors = require('cors'); var cors = require('cors');
var jsonParser = bodyParser.json();
var urlencParser = bodyParser.urlencoded({extended:true});
var onHeaders = require('on-headers'); var onHeaders = require('on-headers');
var typer = require('media-typer'); var typer = require('media-typer');
var isUtf8 = require('is-utf8'); var isUtf8 = require('is-utf8');
@ -212,6 +210,10 @@ module.exports = function(RED) {
} }
} }
var maxApiRequestSize = RED.settings.apiMaxLength || '5mb';
var jsonParser = bodyParser.json({limit:maxApiRequestSize});
var urlencParser = bodyParser.urlencoded({limit:maxApiRequestSize,extended:true});
var metricsHandler = function(req,res,next) { next(); } var metricsHandler = function(req,res,next) { next(); }
if (this.metric()) { if (this.metric()) {
metricsHandler = function(req, res, next) { metricsHandler = function(req, res, next) {

View File

@ -16,9 +16,7 @@
module.exports = function(RED) { module.exports = function(RED) {
"use strict"; "use strict";
var http = require("follow-redirects").http; var request = require("request");
var https = require("follow-redirects").https;
var urllib = require("url");
var mustache = require("mustache"); var mustache = require("mustache");
var querystring = require("querystring"); var querystring = require("querystring");
var cookie = require("cookie"); var cookie = require("cookie");
@ -78,9 +76,13 @@ module.exports = function(RED) {
if (msg.method && n.method && (n.method === "use")) { if (msg.method && n.method && (n.method === "use")) {
method = msg.method.toUpperCase(); // use the msg parameter method = msg.method.toUpperCase(); // use the msg parameter
} }
var opts = urllib.parse(url); var opts = {};
opts.url = url;
opts.timeout = node.reqTimeout;
opts.method = method; opts.method = method;
opts.headers = {}; opts.headers = {};
opts.encoding = null; // Force NodeJs to return a Buffer (instead of a string)
opts.maxRedirects = 21;
var ctSet = "Content-Type"; // set default camel case var ctSet = "Content-Type"; // set default camel case
var clSet = "Content-Length"; var clSet = "Content-Length";
if (msg.headers) { if (msg.headers) {
@ -109,7 +111,7 @@ module.exports = function(RED) {
} }
} }
if (msg.hasOwnProperty('followRedirects')) { if (msg.hasOwnProperty('followRedirects')) {
opts.followRedirects = msg.followRedirects; opts.followRedirect = msg.followRedirects;
} }
if (msg.cookies) { if (msg.cookies) {
var cookies = []; var cookies = [];
@ -134,7 +136,10 @@ module.exports = function(RED) {
} }
} }
if (this.credentials && this.credentials.user) { if (this.credentials && this.credentials.user) {
opts.auth = this.credentials.user+":"+(this.credentials.password||""); opts.auth = {
user: this.credentials.user,
pass: this.credentials.password||""
};
} }
var payload = null; var payload = null;
@ -160,6 +165,7 @@ module.exports = function(RED) {
opts.headers[clSet] = Buffer.byteLength(payload); opts.headers[clSet] = Buffer.byteLength(payload);
} }
} }
opts.body = payload;
} }
// revert to user supplied Capitalisation if needed. // revert to user supplied Capitalisation if needed.
if (opts.headers.hasOwnProperty('content-type') && (ctSet !== 'content-type')) { if (opts.headers.hasOwnProperty('content-type') && (ctSet !== 'content-type')) {
@ -170,7 +176,6 @@ module.exports = function(RED) {
opts.headers[clSet] = opts.headers['content-length']; opts.headers[clSet] = opts.headers['content-length'];
delete opts.headers['content-length']; delete opts.headers['content-length'];
} }
var urltotest = url;
var noproxy; var noproxy;
if (noprox) { if (noprox) {
for (var i in noprox) { for (var i in noprox) {
@ -180,23 +185,12 @@ module.exports = function(RED) {
if (prox && !noproxy) { if (prox && !noproxy) {
var match = prox.match(/^(http:\/\/)?(.+)?:([0-9]+)?/i); var match = prox.match(/^(http:\/\/)?(.+)?:([0-9]+)?/i);
if (match) { if (match) {
//opts.protocol = "http:"; opts.proxy = prox;
//opts.host = opts.hostname = match[2]; } else {
//opts.port = (match[3] != null ? match[3] : 80); node.warn("Bad proxy url: "+ prox);
opts.headers['Host'] = opts.host; opts.proxy = null;
var heads = opts.headers;
var path = opts.pathname = opts.href;
opts = urllib.parse(prox);
opts.path = opts.pathname = path;
opts.headers = heads;
opts.method = method;
urltotest = match[0];
if (opts.auth) {
opts.headers['Proxy-Authorization'] = "Basic "+new Buffer(opts.auth).toString('Base64')
} }
} }
else { node.warn("Bad proxy url: "+process.env.http_proxy); }
}
if (tlsNode) { if (tlsNode) {
tlsNode.addTLSOptions(opts); tlsNode.addTLSOptions(opts);
} else { } else {
@ -204,16 +198,23 @@ module.exports = function(RED) {
opts.rejectUnauthorized = msg.rejectUnauthorized; opts.rejectUnauthorized = msg.rejectUnauthorized;
} }
} }
var req = ((/^https/.test(urltotest))?https:http).request(opts,function(res) { request(opts, function(err, res, body) {
// Force NodeJs to return a Buffer (instead of a string) if(err){
// See https://github.com/nodejs/node/issues/6038 if(err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT') {
res.setEncoding(null); node.error(RED._("common.notification.errors.no-response"), msg);
delete res._readableState.decoder; node.status({fill:"red", shape:"ring", text:"common.notification.errors.no-response"});
}else{
node.error(err,msg);
node.status({fill:"red", shape:"ring", text:err.code});
}
msg.payload = err.toString() + " : " + url;
msg.statusCode = err.code;
node.send(msg);
}else{
msg.statusCode = res.statusCode; msg.statusCode = res.statusCode;
msg.headers = res.headers; msg.headers = res.headers;
msg.responseUrl = res.responseUrl; msg.responseUrl = res.request.uri.href;
msg.payload = []; msg.payload = body;
if (msg.headers.hasOwnProperty('set-cookie')) { if (msg.headers.hasOwnProperty('set-cookie')) {
msg.responseCookies = {}; msg.responseCookies = {};
@ -224,22 +225,10 @@ module.exports = function(RED) {
parsedCookie.value = parsedCookie[key]; parsedCookie.value = parsedCookie[key];
delete parsedCookie[key]; delete parsedCookie[key];
msg.responseCookies[key] = parsedCookie; msg.responseCookies[key] = parsedCookie;
});
})
} }
msg.headers['x-node-red-request-node'] = hashSum(msg.headers); msg.headers['x-node-red-request-node'] = hashSum(msg.headers);
// msg.url = url; // revert when warning above finally removed // msg.url = url; // revert when warning above finally removed
res.on('data',function(chunk) {
if (!Buffer.isBuffer(chunk)) {
// if the 'setEncoding(null)' fix above stops working in
// a new Node.js release, throw a noisy error so we know
// about it.
throw new Error("HTTP Request data chunk not a Buffer");
}
msg.payload.push(chunk);
});
res.on('end',function() {
if (node.metric()) { if (node.metric()) {
// Calculate request time // Calculate request time
var diff = process.hrtime(preRequestTimestamp); var diff = process.hrtime(preRequestTimestamp);
@ -251,13 +240,7 @@ module.exports = function(RED) {
} }
} }
// Check that msg.payload is an array - if the req error
// handler has been called, it will have been set to a string
// and the error already handled - so no further action should
// be taken. #1344
if (Array.isArray(msg.payload)) {
// Convert the payload to the required return type // Convert the payload to the required return type
msg.payload = Buffer.concat(msg.payload); // bin
if (node.ret !== "bin") { if (node.ret !== "bin") {
msg.payload = msg.payload.toString('utf8'); // txt msg.payload = msg.payload.toString('utf8'); // txt
@ -271,25 +254,6 @@ module.exports = function(RED) {
} }
}); });
}); });
req.setTimeout(node.reqTimeout, function() {
node.error(RED._("common.notification.errors.no-response"),msg);
setTimeout(function() {
node.status({fill:"red",shape:"ring",text:"common.notification.errors.no-response"});
},10);
req.abort();
});
req.on('error',function(err) {
node.error(err,msg);
msg.payload = err.toString() + " : " + url;
msg.statusCode = err.code;
node.status({fill:"red",shape:"ring",text:err.code});
node.send(msg);
});
if (payload) {
req.write(payload);
}
req.end();
});
this.on("close",function() { this.on("close",function() {
node.status({}); node.status({});

View File

@ -786,8 +786,8 @@
"na": "N/A : __value__" "na": "N/A : __value__"
}, },
"errors": { "errors": {
"ignorenode": "Ignoring Raspberry Pi specific node", "ignorenode": "Raspberry Pi specific node set inactive",
"version": "Version command failed", "version": "Failed to get version from Pi",
"sawpitype": "Saw Pi Type", "sawpitype": "Saw Pi Type",
"libnotfound": "Cannot find Pi RPi.GPIO python library", "libnotfound": "Cannot find Pi RPi.GPIO python library",
"alreadyset": "GPIO pin __pin__ already set as type: __type__", "alreadyset": "GPIO pin __pin__ already set as type: __type__",

View File

@ -230,12 +230,12 @@
selectField.append($("<option></option>").val(operators[d].v).text(/^switch/.test(operators[d].t)?node._(operators[d].t):operators[d].t)); selectField.append($("<option></option>").val(operators[d].v).text(/^switch/.test(operators[d].t)?node._(operators[d].t):operators[d].t));
} }
} }
var valueField = $('<input/>',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'str',types:['msg','flow','global','str','num','jsonata',previousValueType]}); var valueField = $('<input/>',{class:"node-input-rule-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'str',types:['msg','flow','global','str','num','jsonata','env',previousValueType]});
var numValueField = $('<input/>',{class:"node-input-rule-num-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['flow','global','num','jsonata']}); var numValueField = $('<input/>',{class:"node-input-rule-num-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'num',types:['flow','global','num','jsonata','env']});
var expValueField = $('<input/>',{class:"node-input-rule-exp-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'jsonata',types:['jsonata']}); var expValueField = $('<input/>',{class:"node-input-rule-exp-value",type:"text",style:"margin-left: 5px;"}).appendTo(row).typedInput({default:'jsonata',types:['jsonata']});
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','jsonata',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','jsonata','env',previousValueType]});
var btwnAndLabel = $('<span/>',{class:"node-input-rule-btwn-label"}).text(" "+andLabel+" ").appendTo(row3); 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','jsonata',previousValueType]}); 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','jsonata','env',previousValueType]});
var typeValueField = $('<input/>',{class:"node-input-rule-type-value",type:"text",style:"margin-left: 5px;"}).appendTo(row) var typeValueField = $('<input/>',{class:"node-input-rule-type-value",type:"text",style:"margin-left: 5px;"}).appendTo(row)
.typedInput({default:'string',types:[ .typedInput({default:'string',types:[
{value:"string",label:"string",hasValue:false}, {value:"string",label:"string",hasValue:false},

View File

@ -146,7 +146,7 @@
.appendTo(row2); .appendTo(row2);
var propertyValue = $('<input/>',{class:"node-input-rule-property-value",type:"text"}) var propertyValue = $('<input/>',{class:"node-input-rule-property-value",type:"text"})
.appendTo(row2) .appendTo(row2)
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','date','jsonata']}); .typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','date','jsonata','env']});
var row3_1 = $('<div/>').appendTo(row3); var row3_1 = $('<div/>').appendTo(row3);
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"}) $('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
@ -154,7 +154,7 @@
.appendTo(row3_1); .appendTo(row3_1);
var fromValue = $('<input/>',{class:"node-input-rule-property-search-value",type:"text"}) var fromValue = $('<input/>',{class:"node-input-rule-property-search-value",type:"text"})
.appendTo(row3_1) .appendTo(row3_1)
.typedInput({default:'str',types:['msg','flow','global','str','re','num','bool']}); .typedInput({default:'str',types:['msg','flow','global','str','re','num','bool','env']});
var row3_2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(row3); var row3_2 = $('<div/>',{style:"margin-top:8px;"}).appendTo(row3);
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"}) $('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
@ -162,7 +162,7 @@
.appendTo(row3_2); .appendTo(row3_2);
var toValue = $('<input/>',{class:"node-input-rule-property-replace-value",type:"text"}) var toValue = $('<input/>',{class:"node-input-rule-property-replace-value",type:"text"})
.appendTo(row3_2) .appendTo(row3_2)
.typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin']}); .typedInput({default:'str',types:['msg','flow','global','str','num','bool','json','bin','env']});
$('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"}) $('<div/>',{style:"display:inline-block;text-align:right; width:120px; padding-right:10px; box-sizing:border-box;"})
.text(to) .text(to)

View File

@ -93,6 +93,8 @@ module.exports = function(RED) {
valid = false; valid = false;
this.error(RED._("change.errors.invalid-expr",{error:e.message})); this.error(RED._("change.errors.invalid-expr",{error:e.message}));
} }
} else if (rule.tot === 'env') {
rule.to = RED.util.evaluateNodeProperty(rule.to,'env');
} }
} }
@ -121,7 +123,7 @@ module.exports = function(RED) {
try{ try{
value = RED.util.evaluateJSONataExpression(rule.to,msg); value = RED.util.evaluateJSONataExpression(rule.to,msg);
} catch(err) { } catch(err) {
node.error(RED._("change.errors.invalid-expr",{error:err.message})); node.error(RED._("change.errors.invalid-expr",{error:err.message}),msg);
return; return;
} }
} }
@ -148,11 +150,11 @@ module.exports = function(RED) {
fromRE = new RegExp(fromRE, "g"); fromRE = new RegExp(fromRE, "g");
} catch (e) { } catch (e) {
valid = false; valid = false;
node.error(RED._("change.errors.invalid-from",{error:e.message})); node.error(RED._("change.errors.invalid-from",{error:e.message}),msg);
return; return;
} }
} else { } else {
node.error(RED._("change.errors.invalid-from",{error:"unsupported type: "+(typeof fromValue)})); node.error(RED._("change.errors.invalid-from",{error:"unsupported type: "+(typeof fromValue)}),msg);
return return
} }
} else { } else {

View File

@ -123,9 +123,8 @@
}, },
color:"BurlyWood", color:"BurlyWood",
inputs:1, inputs:1,
outputs:0, outputs:1,
icon: "file.png", icon: "file-out.png",
align: "right",
label: function() { label: function() {
if (this.overwriteFile === "delete") { if (this.overwriteFile === "delete") {
return this.name||this._("file.label.deletelabel",{file:this.filename}); return this.name||this._("file.label.deletelabel",{file:this.filename});
@ -159,7 +158,7 @@
outputLabels: function(i) { outputLabels: function(i) {
return (this.format === "utf8") ? "UTF8 string" : "binary buffer"; return (this.format === "utf8") ? "UTF8 string" : "binary buffer";
}, },
icon: "file.png", icon: "file-in.png",
label: function() { label: function() {
return this.name||this.filename||this._("file.label.filelabel"); return this.name||this.filename||this._("file.label.filelabel");
}, },

View File

@ -39,14 +39,20 @@ module.exports = function(RED) {
node.tout = null; node.tout = null;
},333); },333);
} }
if (filename === "") { node.warn(RED._("file.errors.nofilename")); } if (filename === "") {
else if (node.overwriteFile === "delete") { node.warn(RED._("file.errors.nofilename"));
} else if (node.overwriteFile === "delete") {
fs.unlink(filename, function (err) { fs.unlink(filename, function (err) {
if (err) { node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg); } if (err) {
else if (RED.settings.verbose) { node.log(RED._("file.status.deletedfile",{file:filename})); } node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg);
}); } else {
if (RED.settings.verbose) {
node.log(RED._("file.status.deletedfile",{file:filename}));
} }
else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) { node.send(msg);
}
});
} else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
var dir = path.dirname(filename); var dir = path.dirname(filename);
if (node.createDir) { if (node.createDir) {
try { try {
@ -64,15 +70,21 @@ module.exports = function(RED) {
if (typeof data === "boolean") { data = data.toString(); } if (typeof data === "boolean") { data = data.toString(); }
if (typeof data === "number") { data = data.toString(); } if (typeof data === "number") { data = data.toString(); }
if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; } if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
node.data.push(Buffer.from(data)); node.data.push({msg:msg,data:Buffer.from(data)});
while (node.data.length > 0) { while (node.data.length > 0) {
if (node.overwriteFile === "true") { if (node.overwriteFile === "true") {
(function(packet) {
node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true }); node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true });
node.wstream.on("error", function(err) { node.wstream.on("error", function(err) {
node.error(RED._("file.errors.writefail",{error:err.toString()}),msg); node.error(RED._("file.errors.writefail",{error:err.toString()}),msg);
}); });
node.wstream.end(node.data.shift()); node.wstream.on("open", function() {
node.wstream.end(packet.data, function() {
node.send(packet.msg);
});
})
})(node.data.shift());
} }
else { else {
// Append mode // Append mode
@ -115,10 +127,17 @@ module.exports = function(RED) {
} }
if (node.filename) { if (node.filename) {
// Static filename - write and reuse the stream next time // Static filename - write and reuse the stream next time
node.wstream.write(node.data.shift()); var packet = node.data.shift()
node.wstream.write(packet.data, function() {
node.send(packet.msg);
});
} else { } else {
// Dynamic filename - write and close the stream // Dynamic filename - write and close the stream
node.wstream.end(node.data.shift()); var packet = node.data.shift()
node.wstream.end(packet.data, function() {
node.send(packet.msg);
});
delete node.wstream; delete node.wstream;
delete node.wstreamIno; delete node.wstreamIno;
} }

View File

@ -44,7 +44,6 @@
"cron": "1.3.0", "cron": "1.3.0",
"express": "4.16.3", "express": "4.16.3",
"express-session": "1.15.6", "express-session": "1.15.6",
"follow-redirects": "1.4.1",
"fs-extra": "5.0.0", "fs-extra": "5.0.0",
"fs.notify": "0.0.4", "fs.notify": "0.0.4",
"hash-sum": "1.0.2", "hash-sum": "1.0.2",
@ -69,6 +68,7 @@
"passport-http-bearer": "1.0.1", "passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2", "passport-oauth2-client-password": "0.1.2",
"raw-body": "2.3.3", "raw-body": "2.3.3",
"request": "2.87.0",
"semver": "5.5.0", "semver": "5.5.0",
"sentiment": "2.1.0", "sentiment": "2.1.0",
"uglify-js": "3.3.25", "uglify-js": "3.3.25",

72
red/api/admin/context.js Normal file
View File

@ -0,0 +1,72 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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 util;
module.exports = {
init: function(runtime) {
redNodes = runtime.nodes;
log = runtime.log;
util = runtime.util;
},
get: function(req,res) {
var scope = req.params.scope;
var id = req.params.id;
var key = req.params[0];
var ctx;
if (scope === 'global') {
ctx = redNodes.getContext('global');
} else if (scope === 'flow') {
ctx = redNodes.getContext(id);
} else if (scope === 'node') {
var node = redNodes.getNode(id);
if (node) {
ctx = node.context();
}
}
if (ctx) {
if (key) {
ctx.get(key,function(err, v) {
res.json(util.encodeObject({msg:v}));
});
return;
} else {
ctx.keys(function(err, keys) {
var result = {};
var c = keys.length;
if (c === 0) {
res.json(result);
} else {
keys.forEach(function(key) {
ctx.get(key,function(err, v) {
result[key] = util.encodeObject({msg:v});
c--;
if (c === 0) {
res.json(result);
}
});
});
}
});
}
} else {
res.json({});
}
}
}

View File

@ -19,6 +19,7 @@ var express = require("express");
var nodes = require("./nodes"); var nodes = require("./nodes");
var flows = require("./flows"); var flows = require("./flows");
var flow = require("./flow"); var flow = require("./flow");
var context = require("./context");
var auth = require("../auth"); var auth = require("../auth");
var apiUtil = require("../util"); var apiUtil = require("../util");
@ -28,6 +29,7 @@ module.exports = {
flows.init(runtime); flows.init(runtime);
flow.init(runtime); flow.init(runtime);
nodes.init(runtime); nodes.init(runtime);
context.init(runtime);
var needsPermission = auth.needsPermission; var needsPermission = auth.needsPermission;
@ -52,6 +54,12 @@ module.exports = {
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.read"),nodes.getSet,apiUtil.errorHandler); adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.read"),nodes.getSet,apiUtil.errorHandler);
adminApp.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.write"),nodes.putSet,apiUtil.errorHandler); adminApp.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.write"),nodes.putSet,apiUtil.errorHandler);
// Context
adminApp.get("/context/:scope(global)",needsPermission("context.read"),context.get,apiUtil.errorHandler);
adminApp.get("/context/:scope(global)/*",needsPermission("context.read"),context.get,apiUtil.errorHandler);
adminApp.get("/context/:scope(node|flow)/:id",needsPermission("context.read"),context.get,apiUtil.errorHandler);
adminApp.get("/context/:scope(node|flow)/:id/*",needsPermission("context.read"),context.get,apiUtil.errorHandler);
return adminApp; return adminApp;
} }
} }

View File

@ -10,7 +10,11 @@
"load": "Load", "load": "Load",
"save": "Save", "save": "Save",
"import": "Import", "import": "Import",
"export": "Export" "export": "Export",
"back": "Back",
"next": "Next",
"clone": "Clone project",
"cont": "Continue"
} }
}, },
"workspace": { "workspace": {
@ -67,7 +71,11 @@
"editPalette":"Manage palette", "editPalette":"Manage palette",
"other": "Other", "other": "Other",
"showTips": "Show tips", "showTips": "Show tips",
"help": "Node-RED website" "help": "Node-RED website",
"projects": "Projects",
"projects-new": "New",
"projects-open": "Open",
"projects-settings": "Project Settings"
} }
}, },
"user": { "user": {
@ -108,6 +116,24 @@
"cannotAddCircularReference": "Cannot add subflow - circular reference detected", "cannotAddCircularReference": "Cannot add subflow - circular reference detected",
"unsupportedVersion": "<p>Using an unsupported version of Node.js</p><p>You should upgrade to the latest Node.js LTS release</p>", "unsupportedVersion": "<p>Using an unsupported version of Node.js</p><p>You should upgrade to the latest Node.js LTS release</p>",
"failedToAppendNode": "<p>Failed to load '__module__'</p><p>__error__</p>" "failedToAppendNode": "<p>Failed to load '__module__'</p><p>__error__</p>"
},
"project": {
"change-branch": "Change to local branch '__project__'",
"merge-abort": "Git merge aborted",
"loaded": "Project '__project__' loaded",
"updated": "Project '__project__' updated",
"pull": "Project '__project__' reloaded",
"revert": "Project '__project__' reloaded",
"merge-complete": "Git merge completed"
},
"label": {
"manage-project-dep": "Manage project dependencies",
"setup-cred": "Setup credentials",
"setup-project": "Setup project files",
"create-default-package": "Create default package file",
"no-thanks": "No thanks",
"create-default-project": "Create default project files",
"show-merge-conflicts": "Show merge conflicts"
} }
}, },
"clipboard": { "clipboard": {
@ -193,8 +219,16 @@
"nodeCount": "__count__ node", "nodeCount": "__count__ node",
"nodeCount_plural": "__count__ nodes", "nodeCount_plural": "__count__ nodes",
"local":"Local changes", "local":"Local changes",
"remote":"Remote changes" "remote":"Remote changes",
"reviewChanges": "Review Changes",
"noBinaryFileShowed": "Cannot show binary file contents",
"viewCommitDiff": "View Commit Changes",
"compareChanges": "Compare Changes",
"saveConflict": "Save conflict resolution",
"conflictHeader": "<span>__resolved__</span> of <span>__unresolved__</span> conflicts resolved",
"commonVersionError": "Common Version doesn't contain valid JSON:",
"oldVersionError": "Old Version doesn't contain valid JSON:",
"newVersionError": "New Version doesn't contain valid JSON:"
}, },
"subflow": { "subflow": {
"editSubflow": "Edit flow template: __name__", "editSubflow": "Edit flow template: __name__",
@ -206,6 +240,7 @@
"output": "outputs:", "output": "outputs:",
"deleteSubflow": "delete subflow", "deleteSubflow": "delete subflow",
"info": "Description", "info": "Description",
"category": "Category",
"format":"markdown format", "format":"markdown format",
"errors": { "errors": {
"noNodesSelected": "<strong>Cannot create subflow</strong>: no nodes selected", "noNodesSelected": "<strong>Cannot create subflow</strong>: no nodes selected",
@ -284,6 +319,7 @@
"noInfo": "no information available", "noInfo": "no information available",
"filter": "filter nodes", "filter": "filter nodes",
"search": "search modules", "search": "search modules",
"addCategory": "Add new...",
"label": { "label": {
"subflows": "subflows", "subflows": "subflows",
"input": "input", "input": "input",
@ -423,6 +459,10 @@
"filterAll":"all", "filterAll":"all",
"filtered": "__count__ hidden" "filtered": "__count__ hidden"
}, },
"context": {
"name":"Context",
"label":"context"
},
"palette": { "palette": {
"name": "Palette management", "name": "Palette management",
"label": "palette" "label": "palette"
@ -433,8 +473,137 @@
"description": "Description", "description": "Description",
"dependencies": "Dependencies", "dependencies": "Dependencies",
"settings": "Settings", "settings": "Settings",
"noSummaryAvailable": "No summary available",
"editDescription": "Edit project description", "editDescription": "Edit project description",
"editDependencies": "Edit project dependencies" "editDependencies": "Edit project dependencies",
"editReadme": "Edit README.md",
"projectSettings": {
"edit": "edit",
"none": "None",
"install": "install",
"removeFromProject": "remove from project",
"addToProject": "add to project",
"none": "None",
"files": "Files",
"flow": "Flow",
"credentials": "Credentials",
"invalidEncryptionKey": "Invalid encryption key",
"encryptionEnabled": "Encryption enabled",
"encryptionDisabled": "Encryption disabled",
"resetTheEncryptionKey": "Reset the encryption key:",
"setTheEncryptionKey": "Set the encryption key:",
"changeTheEncryptionKey": "Change the encryption key:",
"currentKey": "Current key",
"newKey": "New key",
"credentialsAlert": "This will delete all existing credentials",
"versionControl": "Version Control",
"branches": "Branches",
"noBranches": "No branches",
"deleteConfirm": "Are you sure you want to delete the local branch '__name__'? This cannot be undone.",
"unmergedConfirm": "The local branch '__name__' has unmerged changes that will be lost. Are you sure you want to delete it?",
"deleteUnmergedBranch": "Delete unmerged branch",
"gitRemotes": "Git remotes",
"addRemote": "add remote",
"addRemote2": "Add remote",
"remoteName": "Remote name",
"nameRule": "Must contain only A-Z 0-9 _ -",
"url": "URL",
"urlRule": "https://, ssh:// or file://",
"urlRule2": "Do not include the username/password in the URL",
"noRemotes": "No remotes",
"deleteRemoteConfrim": "Are you sure you want to delete the remote '__name__'?",
"deleteRemote": "Delete remote"
},
"userSettings": {
"committerDetail": "Committer Details",
"committerTip": "Leave blank to use system default",
"userName": "Username",
"email": "Email",
"sshKeys": "SSH Keys",
"sshKeysTip": "Allows you to create secure connections to remote git repositories.",
"add": "add key",
"addSshKey": "Add SSH Key",
"addSshKeyTip": "Generate a new public/private key pair",
"name": "Name",
"nameRule": "Must contain only A-Z 0-9 _ -",
"passphrase": "Passphrase",
"passphraseShort": "Passphrase too short",
"optional": "Optional",
"cancel": "Cancel",
"generate": "Generate key",
"noSshKeys": "No SSH keys",
"copyPublicKey": "Copy public key to clipboard",
"delete": "Delete key",
"gitConfig": "Git config",
"deleteConfirm": "Are you sure you want to delete the SSH key __name__? This cannot be undone."
},
"versionControl": {
"unstagedChanges": "Unstaged changes",
"stagedChanges": "Staged changes",
"resolveConflicts": "Resolve conflicts",
"head": "HEAD",
"staged": "Staged",
"unstaged": "Unstaged",
"local": "Local",
"remote": "Remote",
"revert": "Are you sure you want to revert the changes to '__file__'? This cannot be undone.",
"revertChanges": "Revert changes",
"localChanges": "Local Changes",
"none": "None",
"conflictResolve": "All conflicts resolved. Commit the changes to complete the merge.",
"localFiles": "Local files",
"all": "all",
"unmergedChanges": "Unmerged changes",
"abortMerge": "abort merge",
"commit": "commit",
"changeToCommit": "Changes to commit",
"commitPlaceholder": "Enter your commit message",
"cancelCapital": "Cancel",
"commitCapital": "Commit",
"commitHistory": "Commit History",
"branch": "Branch:",
"moreCommits": " more commit(s)",
"changeLocalBranch": "Change local branch",
"createBranchPlaceholder": "Find or create a branch",
"upstream": "upstream",
"localOverwrite": "You have local changes that would be overwritten by changing the branch. You must either commit or undo those changes first.",
"manageRemoteBranch": "Manage remote branch",
"unableToAccess": "Unable to access remote repository",
"retry": "Retry",
"setUpstreamBranch": "Set as upstream branch",
"createRemoteBranchPlaceholder": "Find or create a remote branch",
"trackedUpstreamBranch": "The created branch will be set as the tracked upstream branch.",
"selectUpstreamBranch": "The branch will be created. Select below to set it as the tracked upstream branch.",
"pushFailed": "Push failed as the remote has more recent commits. Pull and merge first, then push again.",
"push": "push",
"pull": "pull",
"unablePull": "<p>Unable to pull remote changes; your unstaged local changes would be overwritten.</p><p>Commit your changes and try again.</p>",
"showUnstagedChanges": "Show unstaged changes",
"connectionFailed": "Could not connect to remote repository: ",
"pullUnrelatedHistory": "<p>The remote has an unrelated history of commits.</p><p>Are you sure you want to pull the changes into your local repository?</p>",
"pullChanges": "Pull changes",
"history": "history",
"daysAgo": "__count__ day ago",
"daysAgo_plural": "__count__ days ago",
"hoursAgo": "__count__ hour ago",
"hoursAgo_plural": "__count__ hours ago",
"minsAgo": "__count__ min ago",
"minsAgo_plural": "__count__ mins ago",
"secondsAgo": "Seconds ago",
"notTracking": "Your local branch is not currently tracking a remote branch.",
"statusUnmergedChanged": "Your repository has unmerged changes. You need to fix the conflicts and commit the result.",
"repositoryUpToDate": "Your repository is up to date.",
"commitsAhead": "Your repository is __count__ commit ahead of the remote. You can push this commit now.",
"commitsAhead_plural": "Your repository is __count__ commits ahead of the remote. You can push these commits now.",
"commitsBehind": "Your repository is __count__ commit behind of the remote. You can pull this commit now.",
"commitsBehind_plural": "Your repository is __count__ commits behind of the remote. You can pull these commits now.",
"commitsAheadAndBehind1": "Your repository is __count__ commit behind and ",
"commitsAheadAndBehind1_plural": "Your repository is __count__ commits behind and ",
"commitsAheadAndBehind2": "__count__ commit ahead of the remote. ",
"commitsAheadAndBehind2_plural": "__count__ commits ahead of the remote. ",
"commitsAheadAndBehind3": "You must pull the remote commit down before pushing.",
"commitsAheadAndBehind3_plural": "You must pull the remote commits down before pushing."
}
} }
}, },
"typedInput": { "typedInput": {
@ -486,5 +655,168 @@
"modeString": "Handle as UTF-8 String", "modeString": "Handle as UTF-8 String",
"modeArray": "Handle as JSON array", "modeArray": "Handle as JSON array",
"modeDesc":"<h3>Buffer editor</h3><p>The Buffer type is stored as a JSON array of byte values. The editor will attempt to parse the entered value as a JSON array. If it is not valid JSON, it will be treated as a UTF-8 String and converted to an array of the individual character code points.</p><p>For example, a value of <code>Hello World</code> will be converted to the JSON array:<pre>[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]</pre></p>" "modeDesc":"<h3>Buffer editor</h3><p>The Buffer type is stored as a JSON array of byte values. The editor will attempt to parse the entered value as a JSON array. If it is not valid JSON, it will be treated as a UTF-8 String and converted to an array of the individual character code points.</p><p>For example, a value of <code>Hello World</code> will be converted to the JSON array:<pre>[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]</pre></p>"
},
"projects": {
"config-git": "Configure Git client",
"welcome": {
"hello": "Hello! We have introduced 'projects' to Node-RED.",
"desc0": "This is a new way for you to manage your flow files and includes version control of your flows.",
"desc1": "To get started you can create your first project or clone an existing project from a git repository.",
"desc2": "If you are not sure, you can skip this for now. You will still be able to create your first project from the 'Projects' menu at any time.",
"create": "Create Project",
"clone": "Clone Repository",
"not-right-now": "Not right now"
},
"git-config": {
"setup": "Setup your version control client",
"desc0": "Node-RED uses the open source tool Git for version control. It tracks changes to your project files and lets you push them to remote repositories.",
"desc1": "When you commit a set of changes, Git records who made the changes with a username and email address. The Username can be anything you want - it does not need to be your real name.",
"desc2": "Your Git client is already configured with the details below.",
"desc3": "You can change these settings later under the 'Git config' tab of the settings dialog.",
"username": "Username",
"email": "Email"
},
"project-details": {
"create": "Create your project",
"desc0": "A project is maintained as a Git repository. It makes it much easier to share your flows with others and to collaborate on them.",
"desc1": "You can create multiple projects and quickly switch between them from the editor.",
"desc2": "To begin, your project needs a name and an optional description.",
"already-exists": "Project already exists",
"must-contain": "Must contain only A-Z 0-9 _ -",
"project-name": "Project name",
"desc": "Description",
"opt": "Optional"
},
"clone-project": {
"clone": "Clone a project",
"desc0": "If you already have a git repository containing a project, you can clone it to get started.",
"already-exists": "Project already exists",
"must-contain": "Must contain only A-Z 0-9 _ -",
"project-name": "Project name",
"no-info-in-url": "Do not include the username/password in the url",
"git-url": "Git repository URL",
"protocols": "https://, ssh:// or file://",
"auth-failed": "Authentication failed",
"username": "Username",
"passwd": "Password",
"ssh-key": "SSH Key",
"passphrase": "Passphrase",
"ssh-key-desc": "Before you can clone a repository over ssh you must add an SSH key to access it.",
"ssh-key-add": "Add an ssh key",
"credential-key": "Credentials encryption key",
"cant-get-ssh-key": "Error! Can't get selected SSH key path.",
"already-exists": "already exists",
"git-error": "git error",
"connection-failed": "Connection failed",
"not-git-repo": "Not a git repository",
"repo-not-found": "Repository not found"
},
"default-files": {
"create": "Create your project files",
"desc0": "A project contains your flow files, a README file and a package.json file.",
"desc1": "It can contain any other files you want to maintain in the Git repository.",
"desc2": "Your existing flow and credential files will be copied into the project.",
"flow-file": "Flow file",
"credentials-file": "Credentials file"
},
"encryption-config": {
"setup": "Setup encryption of your credentials file",
"desc0": "Your flow credentials file can be encrypted to keep its contents secure.",
"desc1": "If you want to store these credentials in a public Git repository, you must encrypt them by providing a secret key phrase.",
"desc2": "Your flow credentials file is not currently encrypted.",
"desc3": "That means its contents, such as passwords and access tokens, can be read by anyone with access to the file.",
"desc4": "If you want to store these credentials in a public Git repository, you must encrypt them by providing a secret key phrase.",
"desc5": "Your flow credentials file is currently encrypted using the credentialSecret property from your settings file as the key.",
"desc6": "Your flow credentials file is currently encrypted using a system-generated key. You should provide a new secret key for this project.",
"desc7": "The key will be stored separately from your project files. You will need to provide the key to use this project in another instance of Node-RED.",
"credentials": "Credentials",
"enable": "Enable encryption",
"disable": "Disable encryption",
"disabled": "disabled",
"copy": "Copy over existing key",
"use-custom": "Use custom key",
"desc8": "The credentials file will not be encrypted and its contents easily read",
"create-project-files": "Create project files",
"create-project": "Create project",
"already-exists": "already exists",
"git-error": "git error",
"git-auth-error": "git auth error"
},
"create-success": {
"success": "You have successfully created your first project!",
"desc0": "You can now continue to use Node-RED just as you always have.",
"desc1": "The 'info' tab in the sidebar shows you what your current active project is. The button next to the name can be used to access the project settings view.",
"desc2": "The 'history' tab in the sidebar can be used to view files that have changed in your project and to commit them. It shows you a complete history of your commits and allows you to push your changes to a remote repository."
},
"create": {
"projects": "Projects",
"already-exists": "Project already exists",
"must-contain": "Must contain only A-Z 0-9 _ -",
"no-info-in-url": "Do not include the username/password in the url",
"open": "Open Project",
"create": "Create Project",
"clone": "Clone Repository",
"project-name": "Project name",
"desc": "Description",
"opt": "Optional",
"flow-file": "Flow file",
"credentials": "Credentials",
"enable-encryption": "Enable encryption",
"disable-encryption": "Disable encryption",
"encryption-key": "Encryption key",
"desc0": "A phrase to secure your credentials with",
"desc1": "The credentials file will not be encrypted and its contents easily read",
"git-url": "Git repository URL",
"protocols": "https://, ssh:// or file://",
"auth-failed": "Authentication failed",
"username": "Username",
"password": "Password",
"ssh-key": "SSH Key",
"passphrase": "Passphrase",
"desc2": "Before you can clone a repository over ssh you must add an SSH key to access it.",
"add-ssh-key": "Add an ssh key",
"credentials-encryption-key": "Credentials encryption key",
"already-exists-2": "already exists",
"git-error": "git error",
"con-failed": "Connection failed",
"not-git": "Not a git repository",
"no-resource": "Repository not found",
"cant-get-ssh-key-path": "Error! Can't get selected SSH key path.",
"unexpected_error": "unexpected_error"
},
"delete": {
"confirm": "Are you sure you want to delete this project?"
},
"create-project-list": {
"search": "search your projects",
"current": "current"
},
"require-clean": {
"confirm": "<p>You have undeployed changes that will be lost.</p><p>Do you want to continue?</p>"
},
"send-req": {
"auth-req": "Authentication required for repository",
"username": "Username",
"password": "Password",
"passphrase": "Passphrase",
"retry": "Retry",
"update-failed": "Failed to update auth",
"unhandled": "Unhandled error response"
},
"create-branch-list": {
"invalid": "Invalid branch",
"create": "Create branch",
"current": "current"
},
"create-default-file-set": {
"no-active": "Cannot create default file set without an active project",
"no-empty": "Cannot create default file set on a non-empty project",
"git-error": "git error"
},
"errors" : {
"no-username-email": "Your Git client is not configured with a username/email.",
"unexpected": "An unexpected error occurred",
"code": "code"
}
} }
} }

View File

@ -190,11 +190,11 @@
}, },
"$flowContext": { "$flowContext": {
"args": "string", "args": "string",
"desc": "Retrieves a flow context property." "desc": "Retrieves a flow context property.\n\nThis is a Node-RED defined function."
}, },
"$globalContext": { "$globalContext": {
"args": "string", "args": "string",
"desc": "Retrieves a global context property." "desc": "Retrieves a global context property.\n\nThis is a Node-RED defined function."
}, },
"$pad": { "$pad": {
"args": "string, width [, char]", "args": "string, width [, char]",
@ -215,5 +215,9 @@
"$toMillis": { "$toMillis": {
"args": "timestamp", "args": "timestamp",
"desc": "Convert a `timestamp` string in the ISO 8601 format to the number of milliseconds since the Unix Epoch (1 January, 1970 UTC) as a number. An error is thrown if the string is not in the correct format." "desc": "Convert a `timestamp` string in the ISO 8601 format to the number of milliseconds since the Unix Epoch (1 January, 1970 UTC) as a number. An error is thrown if the string is not in the correct format."
},
"$env": {
"args": "arg",
"desc": "Returns the value of an environment variable.\n\nThis is a Node-RED defined function."
} }
} }

View File

@ -10,7 +10,11 @@
"load": "読み込み", "load": "読み込み",
"save": "保存", "save": "保存",
"import": "読み込み", "import": "読み込み",
"export": "書き出し" "export": "書き出し",
"back": "戻る",
"next": "進む",
"clone": "プロジェクトをクローン",
"cont": "続ける"
} }
}, },
"workspace": { "workspace": {
@ -67,7 +71,11 @@
"editPalette": "パレットの管理", "editPalette": "パレットの管理",
"other": "その他", "other": "その他",
"showTips": "ヒントを表示", "showTips": "ヒントを表示",
"help": "Node-REDウェブサイト" "help": "Node-REDウェブサイト",
"projects": "プロジェクト",
"projects-new": "新規",
"projects-open": "開く",
"projects-settings": "設定"
} }
}, },
"user": { "user": {
@ -102,7 +110,26 @@
"lostConnectionTry": "すぐに接続", "lostConnectionTry": "すぐに接続",
"cannotAddSubflowToItself": "サブフロー自身を追加できません", "cannotAddSubflowToItself": "サブフロー自身を追加できません",
"cannotAddCircularReference": "循環参照を検出したため、サブフローを追加できません", "cannotAddCircularReference": "循環参照を検出したため、サブフローを追加できません",
"unsupportedVersion": "サポートされていないバージョンのNode.jsを使用しています。<br/>最新のNode.js LTSに更新してください。" "unsupportedVersion": "サポートされていないバージョンのNode.jsを使用しています。<br/>最新のNode.js LTSに更新してください。",
"failedToAppendNode": "<p>'__module__'がロードできませんでした。</p><p>__error__</p>"
},
"project": {
"change-branch": "ローカルブランチ'__project__'に変更しました",
"merge-abort": "Gitマージを中止しました",
"loaded": "プロジェクト'__project__'をロードしました",
"updated": "プロジェクト'__project__'を更新しました",
"pull": "プロジェクト'__project__'を再ロードしました",
"revert": "プロジェクト'__project__'を再ロードしました",
"merge-complete": "Gitマージが完了しました"
},
"label": {
"manage-project-dep": "プロジェクトの依存関係の管理",
"setup-cred": "認証情報の設定",
"setup-project": "プロジェクトファイルの設定",
"create-default-package": "デフォルトパッケージファイルの作成",
"no-thanks": "不要",
"create-default-project": "デフォルトプロジェクトファイルの作成",
"show-merge-conflicts": "マージ競合を表示"
} }
}, },
"clipboard": { "clipboard": {
@ -188,7 +215,16 @@
"nodeCount": "__count__ 個のノード", "nodeCount": "__count__ 個のノード",
"nodeCount_plural": "__count__ 個のノード", "nodeCount_plural": "__count__ 個のノード",
"local": "ローカルの変更", "local": "ローカルの変更",
"remote": "リモートの変更" "remote": "リモートの変更",
"reviewChanges": "変更を表示",
"noBinaryFileShowed": "バイナリファイルの中身は表示することができません",
"viewCommitDiff": "コミットの内容を表示",
"compareChanges": "変更を比較",
"saveConflict": "解決して保存",
"conflictHeader": "<span>__unresolved__</span> 個中 <span>__resolved__</span> 個のコンフリクトを解決",
"commonVersionError": "共通バージョンは正しいJSON形式ではありません:",
"oldVersionError": "古いバージョンは正しいJSON形式ではありません:",
"newVersionError": "新しいバージョンは正しいJSON形式ではありません:"
}, },
"subflow": { "subflow": {
"editSubflow": "フローのテンプレートを編集: __name__", "editSubflow": "フローのテンプレートを編集: __name__",
@ -423,8 +459,137 @@
"description": "詳細", "description": "詳細",
"dependencies": "依存関係", "dependencies": "依存関係",
"settings": "設定", "settings": "設定",
"noSummaryAvailable": "サマリが存在しません",
"editDescription": "プロジェクトの詳細を編集", "editDescription": "プロジェクトの詳細を編集",
"editDependencies": "プロジェクトの依存関係を編集" "editDependencies": "プロジェクトの依存関係を編集",
"editReadme": "README.mdを編集",
"projectSettings": {
"edit": "編集",
"none": "なし",
"install": "インストール",
"removeFromProject": "プロジェクトから削除",
"addToProject": "プロジェクトへ追加",
"files": "ファイル",
"flow": "フロー",
"credentials": "認証情報",
"invalidEncryptionKey": "不正な暗号化キー",
"encryptionEnabled": "暗号化が有効になっています",
"encryptionDisabled": "暗号化が無効になっています",
"setTheEncryptionKey": "暗号化キーを設定:",
"resetTheEncryptionKey": "暗号化キーを初期化:",
"changeTheEncryptionKey": "暗号化キーを変更:",
"currentKey": "現在のキー",
"newKey": "新規のキー",
"credentialsAlert": "既存の認証情報は全て削除されます",
"versionControl": "バージョン管理",
"branches": "ブランチ",
"noBranches": "ブランチなし",
"deleteConfirm": "本当にローカルブランチ'__name__'を削除しますか?削除すると元に戻すことはできません。",
"unmergedConfirm": "ローカルブランチ'__name__'にはマージされていない変更があります。この変更は削除されます。本当に削除しますか?",
"deleteUnmergedBranch": "マージされていないブランチを削除",
"gitRemotes": "Gitリモート",
"addRemote": "リモートを追加",
"addRemote2": "リモートを追加",
"remoteName": "リモート名",
"nameRule": "A-Z 0-9 _ - のみを含む",
"url": "URL",
"urlRule": "https://、ssh:// または file://",
"urlRule2": "URLにユーザ名、パスワードを含んではいけません",
"noRemotes": "リモートなし",
"deleteRemoteConfrim": "本当にリモート'__name__'を削除しますか?",
"deleteRemote": "リモートを削除"
},
"userSettings": {
"committerDetail": "コミッター詳細",
"committerTip": "システムのデフォルトを使用する場合、空白のままにしてください",
"userName": "ユーザ名",
"email": "メールアドレス",
"sshKeys": "SSH キー",
"sshKeysTip": "gitリポジトリへのセキュアな接続を作成できます。",
"add": "キーを追加",
"addSshKey": "SSHキーを追加",
"addSshKeyTip": "新しい公開鍵/秘密鍵ペアを生成します",
"name": "名前",
"nameRule": "A-Z 0-9 _ - のみを含む",
"passphrase": "パスフレーズ",
"passphraseShort": "パスフレーズが短すぎます",
"optional": "任意",
"cancel": "中止",
"generate": "キーを生成",
"noSshKeys": "SSHキーがありません",
"copyPublicKey": "公開鍵をクリップボードにコピー",
"delete": "キーを削除",
"gitConfig": "Git設定",
"deleteConfirm": "SSHキー __name__ を削除してもよいですか? 削除したSSHキーを元に戻すことはできません。"
},
"versionControl": {
"unstagedChanges": "ステージングされていない変更",
"stagedChanges": "ステージングされた変更",
"resolveConflicts": "コンフリクトの解決",
"head": "最新",
"staged": "ステージング",
"unstaged": "未ステージング",
"local": "ローカル",
"remote": "リモート",
"revert": "'__file__'への変更を本当に戻しますか?この操作は元に戻せません。",
"revertChanges": "変更を戻す",
"localChanges": "ローカルの変更",
"none": "なし",
"conflictResolve": "全てのコンフリクトが解消されました。マージを完了するため、変更をコミットしてください。",
"localFiles": "ローカルファイル",
"all": "全て",
"unmergedChanges": "マージされていない変更",
"abortMerge": "マージ中止",
"commit": "コミット",
"changeToCommit": "コミット対象とする変更",
"commitPlaceholder": "コミットメッセージを入力してください。",
"cancelCapital": "キャンセル",
"commitCapital": "コミット",
"commitHistory": "コミット履歴",
"branch": "ブランチ:",
"moreCommits": "個のコミット",
"changeLocalBranch": "ローカルブランチの変更",
"createBranchPlaceholder": "ブランチの検索または作成",
"upstream": "アップストリーム",
"localOverwrite": "ブランチの変更によって上書きされたローカルの変更があります。これらの変更を先にコミットするか、あるいは元に戻さなければなりません。",
"manageRemoteBranch": "リモートブランチの管理",
"unableToAccess": "リモートのリポジトリにアクセスできません。",
"retry": "リトライ",
"setUpstreamBranch": "アップストリームとして設定する",
"createRemoteBranchPlaceholder": "リモートブランチの検索または作成",
"trackedUpstreamBranch": "作成されたブランチは、トラッキングされたアップストリームブランチとなります。",
"selectUpstreamBranch": "ブランチが作成されました。トラッキングするアップストリームブランチを選択してください。",
"pushFailed": "リモートに新しいコミットがあるため、プッシュに失敗しました。プルしてマージしてから、再度プッシュしてください。",
"push": "プッシュ",
"pull": "プル",
"unablePull": "<p>リモートの変更のプル失敗:ステージングされていないローカルの変更を上書きされてしまいます。</p><p>変更をコミットしてから再度実行してください。</p>",
"showUnstagedChanges": "ステージングされていない変更を表示",
"connectionFailed": "リモートリポジトリに接続できません: ",
"pullUnrelatedHistory": "<p>リモートに関連のないコミット履歴があります。</p><p>本当に変更をプルしてローカルリポジトリに反映しますか?</p>",
"pullChanges": "プル変更",
"history": "履歴",
"plural": "",
"daysAgo": "__count__ 日前",
"daysAgo_plural": "__count__ 日前",
"hoursAgo": "__count__ 時間前",
"hoursAgo_plural": "__count__ 時間前",
"minsAgo": "__count__ 分前",
"minsAgo_plural": "__count__ 分前",
"secondsAgo": "数秒前",
"notTracking": "ローカルブランチは現在リモートブランチをトラッキングしていません。",
"statusUnmergedChanged": "リポジトリ内にマージされていない変更があります。コンフリクトを解決してコミットしてください。",
"repositoryUpToDate": "リポジトリは最新です。",
"commitsAhead": "あなたのリポジトリはリモートより__count__コミット進んでいます。現在のコミットをプッシュできます。",
"commitsAhead_plural": "あなたのリポジトリはリモートより__count__コミット進んでいます。現在のコミットをプッシュできます。",
"commitsBehind": "あなたのリポジトリはリモートより__count__コミット遅れています。現在のコミットをプルできます。",
"commitsBehind_plural": "あなたのリポジトリはリモートより__count__コミット遅れています。現在のコミットをプルできます。",
"commitsAheadAndBehind1": "あなたのリポジトリはリモートより__count__コミット遅れており、かつ",
"commitsAheadAndBehind1_plural": "あなたのリポジトリはリモートより__count__コミット遅れており、かつ",
"commitsAheadAndBehind2": "__count__コミット進んでいます。 ",
"commitsAheadAndBehind2_plural": "__count__コミット進んでいます。 ",
"commitsAheadAndBehind3": "プッシュする前にリモートのコミットをプルしてください。",
"commitsAheadAndBehind3_plural": "プッシュする前にリモートのコミットをプルしてください。"
}
} }
}, },
"typedInput": { "typedInput": {
@ -476,5 +641,168 @@
"modeString": "UTF-8文字列として処理", "modeString": "UTF-8文字列として処理",
"modeArray": "JSON配列として処理", "modeArray": "JSON配列として処理",
"modeDesc": "<h3>バッファエディタ</h3><p>バッファ型は、バイト値から成るJSON配列として格納されます。このエディタは、入力値をJSON配列として構文解析します。もし不正なJSON配列の場合、UTF-8文字列として扱い、各文字コード番号から成る配列へ変換します。</p><p>例えば、 <code>Hello World</code> という値を、以下のJSON配列へ変換します。<pre>[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]</pre></p>" "modeDesc": "<h3>バッファエディタ</h3><p>バッファ型は、バイト値から成るJSON配列として格納されます。このエディタは、入力値をJSON配列として構文解析します。もし不正なJSON配列の場合、UTF-8文字列として扱い、各文字コード番号から成る配列へ変換します。</p><p>例えば、 <code>Hello World</code> という値を、以下のJSON配列へ変換します。<pre>[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]</pre></p>"
},
"projects": {
"config-git": "Gitクライアントの設定",
"welcome": {
"hello": "こんにちは! Node-REDで「プロジェクト」機能が利用できるようになりました。",
"desc0": "フローファイルの管理方法が刷新され、バージョン管理も可能です。",
"desc1": "まず最初にプロジェクトを作成するか、既存のGitリポジトリからプロジェクトをクローンしてください。",
"desc2": "とりあえずこの処理をスキップしてもかまいません。「プロジェクト」メニューから、いつでもプロジェクトの作成を開始できます。",
"create": "プロジェクトの作成",
"clone": "プロジェクトのクローン",
"not-right-now": "後にする"
},
"git-config": {
"setup": "バージョン管理クライアントの設定",
"desc0": "Node-REDはオープンソースツールのGitを使ってバージョン管理を行います。Gitによりプロジェクトファイルに対する変化を記録し、外部リポジトリに保存することができます。",
"desc1": "変更をコミットする際、変更を行った人物の情報としてユーザ名とEmailアドレスをGitが記憶します。ユーザ名は本名でなくても構いません。好きな名前を使ってください。",
"desc2": "Gitクライアントの現在の設定は以下の通りです。",
"desc3": "設定ダイアログの「Git設定」タブから別途変更することもできます。",
"username": "ユーザ名",
"email": "Email"
},
"project-details": {
"create": "プロジェクトの作成",
"desc0": "プロジェクトはGitリポジトリとして管理します。Gitリポジトリを使ってフローの共有やコラボレーションが簡単にできます。",
"desc1": "複数のプロジェクトを作成し、エディタから即座に変更できます。",
"desc2": "まず、プロジェクト名と説明(任意)を指定してください。",
"already-exists": "既に存在するプロジェクトです",
"must-contain": "A-Z 0-9 _ - のみ指定可能",
"project-name": "プロジェクト名",
"desc": "説明",
"opt": "任意"
},
"clone-project": {
"clone": "プロジェクトをクローン",
"desc0": "プロジェクトを含んだGitリポジトリを作成済みの場合、クローンを作成することができます。",
"already-exists": "既に存在するプロジェクトです",
"must-contain": "A-Z 0-9 _ - のみ指定可能",
"project-name": "プロジェクト名",
"no-info-in-url": "URLにユーザ名/パスワードを含めないようにしてください",
"git-url": "GitリポジトリのURL",
"protocols": "https://, ssh:// もしくは file://",
"auth-failed": "認証に失敗しました",
"username": "ユーザ名",
"passwd": "パスワード",
"ssh-key": "SSHキー",
"passphrase": "パスフレーズ",
"ssh-key-desc": "SSHでリポジトリをクローンする前にSSHキーを追加してください。",
"ssh-key-add": "SSHキーの追加",
"credential-key": "認証情報の暗号化キー",
"cant-get-ssh-key": "エラー! 選択したSSHキーのパスを取得できません。",
"already-exists": "既に存在します",
"git-error": "Gitエラー",
"connection-failed": "接続に失敗しました",
"not-git-repo": "Gitリポジトリではありません",
"repo-not-found": "リポジトリが見つかりません"
},
"default-files": {
"create": "プロジェクト関連ファアイルの作成",
"desc0": "プロジェクトはフローファイル、README、package.jsonを含みます。",
"desc1": "その他、Gitリポジトリで管理したいファイルを含めても構いません。",
"desc2": "既存のフローと認証情報ファイルをプロジェクトにコピーします。",
"flow-file": "フローファイル",
"credentials-file": "認証情報ファイル"
},
"encryption-config": {
"setup": "認証情報ファイルの暗号化設定",
"desc0": "フロー認証情報ファイルを暗号化して内容の安全性を担保できます。",
"desc1": "認証情報を公開Gitリポジトリに保存する際には、秘密キーフレーズによって暗号化します。",
"desc2": "認証情報ファイルは暗号化されていません。",
"desc3": "パスワードやアクセストークンといった認証情報を他人が参照できます。",
"desc4": "認証情報を公開Gitリポジトリに保存する際には、秘密キーフレーズによって暗号化します。",
"desc5": "フロー認証情報ファイルはsettingsファイルのcredentialSecretプロパティで暗号化されています。",
"desc6": "フロー認証情報ファイルはシステムが生成したキーによって暗号化されています。このプロジェクト用に新しい秘密キーを指定してください。",
"desc7": "キーはプロジェクトファイルとば別に保存されます。他のNode-REDでこのプロジェクトを利用するには、このプロジェクトのキーが必要です。",
"credentials": "認証情報",
"enable": "暗号化を有効にする",
"disable": "暗号化を無効にする",
"disabled": "無効",
"copy": "既存のキーをコピー",
"use-custom": "カスタムキーを使用",
"desc8": "認証情報ファイルが暗号化されないため、簡単に読み出すことができます。",
"create-project-files": "プロジェクト関連ファイル作成",
"create-project": "プロジェクト作成",
"already-exists": "既に存在",
"git-error": "Gitエラー",
"git-auth-error": "Git認証エラー"
},
"create-success": {
"success": "最初のプロジェクトの作成が成功しました!",
"desc0": "以降は、これまでと同様にNode-REDを利用できます。",
"desc1": "サイドバーの「情報」タブに現在選択されたプロジェクトを表示します。プロジェクト名の隣のボタンでプロジェクト設定画面を呼び出すことができます。",
"desc2": "サイドバーの「履歴」タブで変更が加えられたプロジェクト内のファイルを確認しコミットできます。このサイドバーでは、全てのコミット履歴を確認し、変更を外部リポジトリにプッシュすることが可能です。"
},
"create": {
"projects": "プロジェクト",
"already-exists": "プロジェクトは既に存在します",
"must-contain": "A-Z 0-9 _ - のみ指定可能",
"no-info-in-url": "URLにユーザ名/パスワードを含めないようにしてください",
"open": "プロジェクトを開く",
"create": "プロジェクトを作成",
"clone": "プロジェクトをクローン",
"project-name": "プロジェクト名",
"desc": "説明",
"opt": "任意",
"flow-file": "フローファイル",
"credentials": "認証情報",
"enable-encryption": "暗号化を有効にする",
"disable-encryption": "暗号化を無効にする",
"encryption-key": "暗号化キー",
"desc0": "認証情報をセキュアにするためのフレーズ",
"desc1": "認証情報ファイルが暗号化されないため、簡単に読み出すことができます",
"git-url": "GitリポジトリのURL",
"protocols": "https://, ssh:// もしくは file://",
"auth-failed": "認証に失敗しました",
"username": "ユーザ名",
"password": "パスワード",
"ssh-key": "SSHキー",
"passphrase": "パスフレーズ",
"desc2": "SSHでリポジトリをクローンする前にSSHキーを追加してください。",
"add-ssh-key": "SSHキーの追加",
"credentials-encryption-key": "認証情報の暗号化キー",
"already-exists-2": "既に存在します",
"git-error": "Gitエラー",
"con-failed": "接続に失敗しました",
"not-git": "Gitリポジトリではありません",
"no-resource": "リポジトリが見つかりません",
"cant-get-ssh-key-path": "エラー! 選択したSSHキーのパスを取得できません。",
"unexpected_error": "予期しないエラー"
},
"delete": {
"confirm": "プロジェクトを削除しても良いですか?"
},
"create-project-list": {
"search": "プロジェクトを検索",
"current": "有効"
},
"require-clean": {
"confirm": "<p>デプロイされていない変更は失われます。</p><p>続けますか?</p>"
},
"send-req": {
"auth-req": "リポジトリ対する認証が必要です",
"username": "ユーザ名",
"password": "パスワード",
"passphrase": "パスフレーズ",
"retry": "リトライ",
"update-failed": "認証の更新に失敗しました",
"unhandled": "エラー応答が処理されませんでした"
},
"create-branch-list": {
"invalid": "不正なブランチ",
"create": "ブランチの作成",
"current": "有効"
},
"create-default-file-set": {
"no-active": "有効なプロジェクトが無い場合、デフォルトのファイル群を作成できません。",
"no-empty": "デフォルトのファイル群を空でないプロジェクトに作成することはできません。",
"git-error": "Gitエラー"
},
"errors" : {
"no-username-email": "Gitクライアントのユーザ名/emailが設定されていません。",
"unexpected": "予期しないエラーが発生しました",
"code": "コード"
}
} }
} }

View File

@ -40,6 +40,8 @@ module.exports = {
}) })
} }
safeSettings.context = runtime.nodes.listContextStores();
var themeSettings = theme.settings(); var themeSettings = theme.settings();
if (themeSettings) { if (themeSettings) {
safeSettings.editorTheme = themeSettings; safeSettings.editorTheme = themeSettings;

View File

@ -81,16 +81,32 @@ var MessageFileLoader = {
} }
function getCurrentLocale() {
var env = process.env;
for (var name of ['LC_ALL', 'LC_MESSAGES', 'LANG']) {
if (name in env) {
var val = env[name];
return val.substring(0, 2);
}
}
return undefined;
}
function init() { function init() {
return when.promise(function(resolve,reject) { return when.promise(function(resolve,reject) {
i18n.backend(MessageFileLoader); i18n.backend(MessageFileLoader);
i18n.init({ var opt = {
ns: { ns: {
namespaces: [], namespaces: [],
defaultNs: "runtime" defaultNs: "runtime"
}, },
fallbackLng: [defaultLang] fallbackLng: [defaultLang]
},function() { };
var lang = getCurrentLocale();
if (lang) {
opt.lng = lang;
}
i18n.init(opt ,function() {
resolve(); resolve();
}); });
}); });

View File

@ -90,6 +90,7 @@ function start() {
}) })
.then(function() { return storage.init(runtime)}) .then(function() { return storage.init(runtime)})
.then(function() { return settings.load(storage)}) .then(function() { return settings.load(storage)})
.then(function() { return redNodes.loadContextsPlugin()})
.then(function() { .then(function() {
if (log.metric()) { if (log.metric()) {
@ -229,7 +230,9 @@ function stop() {
clearTimeout(reinstallTimeout); clearTimeout(reinstallTimeout);
} }
started = false; started = false;
return redNodes.stopFlows(); return redNodes.stopFlows().then(function(){
return redNodes.closeContextsPlugin();
});
} }
var runtime = module.exports = { var runtime = module.exports = {

View File

@ -150,8 +150,19 @@
"disabled": "Projects disabled : editorTheme.projects.enabled=false", "disabled": "Projects disabled : editorTheme.projects.enabled=false",
"disabledNoFlag": "Projects disabled : set editorTheme.projects.enabled=true to enable", "disabledNoFlag": "Projects disabled : set editorTheme.projects.enabled=true to enable",
"git-not-found": "Projects disabled : git command not found", "git-not-found": "Projects disabled : git command not found",
"git-version-old": "Projects disabled : git __version__ not supported. Requires 2.x" "git-version-old": "Projects disabled : git __version__ not supported. Requires 2.x",
"summary": "A Node-RED Project",
"readme": "### About\n\nThis is your project's README.md file. It helps users understand what your\nproject does, how to use it and anything else they may need to know."
} }
} }
},
"context": {
"error-module-not-loaded": "'__module__' could not be loaded",
"error-loading-module": "Error loading context module '__module__': __message__ ",
"error-module-not-defined": "'module' is not defined in '__storage__' of settings.contextStorage",
"error-invalid-default-module": "Invalid storage '__storage__' is specified as a default storage",
"error-use-undefined-storage": "Undefined storage '__storage__' is specified"
} }
} }

View File

@ -0,0 +1,10 @@
{
"storage": {
"localfilesystem": {
"projects": {
"summary": "Node-REDプロジェクト",
"readme": "### 説明\nこれはプロジェクトのREADME.mdファイルです。このファイルには、\nプロジェクトの説明、利用方法、その他の情報を記載します。"
}
}
}
}

View File

@ -105,12 +105,12 @@ Node.prototype.close = function(removed) {
if (promises.length > 0) { if (promises.length > 0) {
return when.settle(promises).then(function() { return when.settle(promises).then(function() {
if (this._context) { if (this._context) {
context.delete(this._alias||this.id,this.z); return context.delete(this._alias||this.id,this.z);
} }
}); });
} else { } else {
if (this._context) { if (this._context) {
context.delete(this._alias||this.id,this.z); return context.delete(this._alias||this.id,this.z);
} }
return; return;
} }

View File

@ -1,91 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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);
}
obj.keys = function() {
var keysData = Object.keys(data);
if (seed == null) {
return keysData;
} else {
return keysData.filter(function (key) {
return key !== "set" && key !== "get" && key !== "keys";
});
}
}
return obj;
}
var contexts = {};
var globalContext = null;
function getContext(localId,flowId) {
var contextId = localId;
if (flowId) {
contextId = localId+":"+flowId;
}
if (contexts.hasOwnProperty(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.hasOwnProperty(idParts[0])) {
delete contexts[id];
}
}
}
}
module.exports = {
init: function(settings) {
globalContext = createContext("global",settings.functionGlobalContext || {});
},
get: getContext,
delete: deleteContext,
clean:clean
};

View File

@ -0,0 +1,331 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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 log = require("../../log");
var memory = require("./memory");
var settings;
// A map of scope id to context instance
var contexts = {};
// A map of store name to instance
var stores = {};
var storeList = [];
var defaultStore;
// Whether there context storage has been configured or left as default
var hasConfiguredStore = false;
function init(_settings) {
settings = _settings;
stores = {};
var seed = settings.functionGlobalContext || {};
contexts['global'] = createContext("global",seed);
stores["_"] = new memory();
defaultStore = "memory";
}
function load() {
return new Promise(function(resolve,reject) {
// load & init plugins in settings.contextStorage
var plugins = settings.contextStorage || {};
var defaultIsAlias = false;
var promises = [];
if (plugins && Object.keys(plugins).length > 0) {
var hasDefault = plugins.hasOwnProperty('default');
var defaultName;
for (var pluginName in plugins) {
if (plugins.hasOwnProperty(pluginName)) {
// "_" is a reserved name - do not allow it to be overridden
if (pluginName === "_") {
continue;
}
// Check if this is setting the 'default' context to be a named plugin
if (pluginName === "default" && typeof plugins[pluginName] === "string") {
// Check the 'default' alias exists before initialising anything
if (!plugins.hasOwnProperty(plugins[pluginName])) {
return reject(new Error(log._("context.error-invalid-default-module", {storage:plugins["default"]})));
}
defaultIsAlias = true;
continue;
}
if (!hasDefault && !defaultName) {
defaultName = pluginName;
}
var plugin;
if (plugins[pluginName].hasOwnProperty("module")) {
// Get the provided config and copy in the 'approved' top-level settings (eg userDir)
var config = plugins[pluginName].config || {};
copySettings(config, settings);
if (typeof plugins[pluginName].module === "string") {
// This config identifies the module by name - assume it is a built-in one
// TODO: check it exists locally, if not, try to require it as-is
try {
plugin = require("./"+plugins[pluginName].module);
} catch(err) {
return reject(new Error(log._("context.error-module-not-loaded", {module:plugins[pluginName].module})));
}
} else {
// Assume `module` is an already-required module we can use
plugin = plugins[pluginName].module;
}
try {
// Create a new instance of the plugin by calling its module function
stores[pluginName] = plugin(config);
} catch(err) {
return reject(new Error(log._("context.error-loading-module",{module:pluginName,message:err.toString()})));
}
} else {
// Plugin does not specify a 'module'
return reject(new Error(log._("context.error-module-not-defined", {storage:pluginName})));
}
}
}
// Open all of the configured contexts
for (var plugin in stores) {
if (stores.hasOwnProperty(plugin)) {
promises.push(stores[plugin].open());
}
}
// There is a 'default' listed in the configuration
if (hasDefault) {
// If 'default' is an alias, point it at the right module - we have already
// checked that it exists. If it isn't an alias, then it will
// already be set to a configured store
if (defaultIsAlias) {
stores["_"] = stores[plugins["default"]];
defaultStore = plugins["default"];
} else {
stores["_"] = stores["default"];
defaultStore = "default";
}
} else if (defaultName) {
// No 'default' listed, so pick first in list as the default
stores["_"] = stores[defaultName];
defaultStore = defaultName;
defaultIsAlias = true;
} else {
// else there were no stores list the config object - fall through
// to below where we default to a memory store
storeList = ["memory"];
defaultStore = "memory";
}
hasConfiguredStore = true;
storeList = Object.keys(stores).filter(n=>!(defaultIsAlias && n==="default") && n!== "_");
} else {
// No configured plugins
promises.push(stores["_"].open())
storeList = ["memory"];
defaultStore = "memory";
}
return resolve(Promise.all(promises));
});
}
function copySettings(config, settings){
var copy = ["userDir"]
config.settings = {};
copy.forEach(function(setting){
config.settings[setting] = clone(settings[setting]);
});
}
function getContextStorage(storage) {
if (stores.hasOwnProperty(storage)) {
// A known context
return stores[storage];
} else if (stores.hasOwnProperty("_")) {
// Not known, but we have a default to fall back to
return stores["_"];
} else {
// Not known and no default configured
var contextError = new Error(log._("context.error-use-undefined-storage", {storage:storage}));
contextError.name = "ContextError";
throw contextError;
}
}
function createContext(id,seed) {
// Seed is only set for global context - sourced from functionGlobalContext
var scope = id;
var obj = seed || {};
var seedKeys;
if (seed) {
seedKeys = Object.keys(seed);
}
obj.get = function(key, storage, callback) {
var context;
if (!storage && !callback) {
context = stores["_"];
} else {
if (typeof storage === 'function') {
callback = storage;
storage = "_";
}
if (typeof callback !== 'function'){
throw new Error("Callback must be a function");
}
context = getContextStorage(storage);
}
if (seed) {
// Get the value from the underlying store. If it is undefined,
// check the seed for a default value.
if (callback) {
context.get(scope,key,function(err, v) {
if (v === undefined) {
callback(err, seed[key]);
} else {
callback(err, v);
}
})
} else {
// No callback, attempt to do this synchronously
var storeValue = context.get(scope,key);
if (storeValue === undefined) {
return seed[key];
} else {
return storeValue;
}
}
} else {
return context.get(scope, key, callback);
}
};
obj.set = function(key, value, storage, callback) {
var context;
if (!storage && !callback) {
context = stores["_"];
} else {
if (typeof storage === 'function') {
callback = storage;
storage = "_";
}
if (callback && typeof callback !== 'function') {
throw new Error("Callback must be a function");
}
context = getContextStorage(storage);
}
context.set(scope, key, value, callback);
};
obj.keys = function(storage, callback) {
var context;
if (!storage && !callback) {
context = stores["_"];
} else {
if (typeof storage === 'function') {
callback = storage;
storage = "_";
}
if (typeof callback !== 'function') {
throw new Error("Callback must be a function");
}
context = getContextStorage(storage);
}
if (seed) {
if (callback) {
context.keys(scope, function(err,keys) {
callback(err,Array.from(new Set(seedKeys.concat(keys)).keys()));
});
} else {
var keys = context.keys(scope);
return Array.from(new Set(seedKeys.concat(keys)).keys())
}
} else {
return context.keys(scope, callback);
}
};
return obj;
}
function getContext(localId,flowId) {
var contextId = localId;
if (flowId) {
contextId = localId+":"+flowId;
}
if (contexts.hasOwnProperty(contextId)) {
return contexts[contextId];
}
var newContext = createContext(contextId);
if (flowId) {
newContext.flow = getContext(flowId);
}
newContext.global = contexts['global'];
contexts[contextId] = newContext;
return newContext;
}
function deleteContext(id,flowId) {
if(!hasConfiguredStore){
// only delete context if there's no configured storage.
var contextId = id;
if (flowId) {
contextId = id+":"+flowId;
}
delete contexts[contextId];
return stores["_"].delete(contextId);
}else{
return Promise.resolve();
}
}
function clean(flowConfig) {
var promises = [];
for(var plugin in stores){
if(stores.hasOwnProperty(plugin)){
promises.push(stores[plugin].clean(Object.keys(flowConfig.allNodes)));
}
}
for (var id in contexts) {
if (contexts.hasOwnProperty(id) && id !== "global") {
var idParts = id.split(":");
if (!flowConfig.allNodes.hasOwnProperty(idParts[0])) {
delete contexts[id];
}
}
}
return Promise.all(promises);
}
function close() {
var promises = [];
for(var plugin in stores){
if(stores.hasOwnProperty(plugin)){
promises.push(stores[plugin].close());
}
}
return Promise.all(promises);
}
function listStores() {
return {default:defaultStore,stores:storeList};
}
module.exports = {
init: init,
load: load,
listStores: listStores,
get: getContext,
delete: deleteContext,
clean: clean,
close: close
};

View File

@ -0,0 +1,266 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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.
**/
/**
* Local file-system based context storage
*
* Configuration options:
* {
* base: "contexts", // the base directory to use
* // default: "contexts"
* dir: "/path/to/storage", // the directory to create the base directory in
* // default: settings.userDir
* cache: true // whether to cache contents in memory
* // default: true
* }
*
*
* $HOME/.node-red/contexts
* global
* global_context.json
* <id of Flow 1>
* flow_context.json
* <id of Node a>.json
* <id of Node b>.json
* <id of Flow 2>
* flow_context.json
* <id of Node x>.json
* <id of Node y>.json
*/
var fs = require('fs-extra');
var path = require("path");
var util = require("../../util");
var MemoryStore = require("./memory");
function getStoragePath(storageBaseDir, scope) {
if(scope.indexOf(":") === -1){
if(scope === "global"){
return path.join(storageBaseDir,"global",scope);
}else{ // scope:flow
return path.join(storageBaseDir,scope,"flow");
}
}else{ // scope:local
var ids = scope.split(":")
return path.join(storageBaseDir,ids[1],ids[0]);
}
}
function getBasePath(config) {
var base = config.base || "contexts";
var storageBaseDir;
if (!config.dir) {
if(config.settings && config.settings.userDir){
storageBaseDir = path.join(config.settings.userDir, base);
}else{
try {
fs.statSync(path.join(process.env.NODE_RED_HOME,".config.json"));
storageBaseDir = path.join(process.env.NODE_RED_HOME, base);
} catch(err) {
try {
// Consider compatibility for older versions
if (process.env.HOMEPATH) {
fs.statSync(path.join(process.env.HOMEPATH,".node-red",".config.json"));
storageBaseDir = path.join(process.env.HOMEPATH, ".node-red", base);
}
} catch(err) {
}
if (!storageBaseDir) {
storageBaseDir = path.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red", base);
}
}
}
}else{
storageBaseDir = path.join(config.dir, base);
}
return storageBaseDir;
}
function loadFile(storagePath){
return fs.pathExists(storagePath).then(function(exists){
if(exists === true){
return fs.readFile(storagePath, "utf8");
}else{
return Promise.resolve(undefined);
}
}).catch(function(err){
throw Promise.reject(err);
});
}
function LocalFileSystem(config){
this.config = config;
this.storageBaseDir = getBasePath(this.config);
if (config.hasOwnProperty('cache')?config.cache:true) {
this.cache = MemoryStore({});
}
}
LocalFileSystem.prototype.open = function(){
var self = this;
if (this.cache) {
var scopes = [];
var promises = [];
var subdirs = [];
var subdirPromises = [];
return fs.readdir(self.storageBaseDir).then(function(dirs){
dirs.forEach(function(fn) {
var p = getStoragePath(self.storageBaseDir ,fn)+".json";
scopes.push(fn);
promises.push(loadFile(p));
subdirs.push(path.join(self.storageBaseDir,fn));
subdirPromises.push(fs.readdir(path.join(self.storageBaseDir,fn)));
})
return Promise.all(subdirPromises);
}).then(function(dirs) {
dirs.forEach(function(files,i) {
files.forEach(function(fn) {
if (fn !== 'flow.json' && fn !== 'global.json') {
scopes.push(fn.substring(0,fn.length-5)+":"+scopes[i]);
promises.push(loadFile(path.join(subdirs[i],fn)))
}
});
})
return Promise.all(promises);
}).then(function(res) {
scopes.forEach(function(scope,i) {
var data = res[i]?JSON.parse(res[i]):{};
Object.keys(data).forEach(function(key) {
self.cache.set(scope,key,data[key]);
})
});
})
} else {
return Promise.resolve();
}
}
LocalFileSystem.prototype.close = function(){
return Promise.resolve();
}
LocalFileSystem.prototype.get = function(scope, key, callback) {
if (this.cache) {
return this.cache.get(scope,key,callback);
}
if(typeof callback !== "function"){
throw new Error("Callback must be a function");
}
var storagePath = getStoragePath(this.storageBaseDir ,scope);
loadFile(storagePath + ".json").then(function(data){
if(data){
callback(null, util.getMessageProperty(JSON.parse(data),key));
}else{
callback(null, undefined);
}
}).catch(function(err){
callback(err);
});
};
LocalFileSystem.prototype._set = function(scope, key, value, callback) {
var storagePath = getStoragePath(this.storageBaseDir ,scope);
loadFile(storagePath + ".json").then(function(data){
var obj = data ? JSON.parse(data) : {}
util.setMessageProperty(obj,key,value);
return fs.outputFile(storagePath + ".json", JSON.stringify(obj, undefined, 4), "utf8");
}).then(function(){
if(typeof callback === "function"){
callback(null);
}
}).catch(function(err){
if(typeof callback === "function"){
callback(err);
}
});
}
LocalFileSystem.prototype.set = function(scope, key, value, callback) {
if (this.cache) {
this.cache.set(scope,key,value,callback);
// With cache enabled, no need to re-read the file prior to writing.
var newContext = this.cache._export()[scope];
var storagePath = getStoragePath(this.storageBaseDir ,scope);
fs.outputFile(storagePath + ".json", JSON.stringify(newContext, undefined, 4), "utf8").catch(function(err) {
});
} else {
this._set(scope,key,value,callback);
}
};
LocalFileSystem.prototype.keys = function(scope, callback){
if (this.cache) {
return this.cache.keys(scope,callback);
}
if(typeof callback !== "function"){
throw new Error("Callback must be a function");
}
var storagePath = getStoragePath(this.storageBaseDir ,scope);
loadFile(storagePath + ".json").then(function(data){
if(data){
callback(null, Object.keys(JSON.parse(data)));
}else{
callback(null, []);
}
}).catch(function(err){
callback(err);
});
};
LocalFileSystem.prototype.delete = function(scope){
var cachePromise;
if (this.cache) {
cachePromise = this.cache.delete(scope);
} else {
cachePromise = Promise.resolve();
}
var that = this;
return cachePromise.then(function() {
var storagePath = getStoragePath(that.storageBaseDir,scope);
return fs.remove(storagePath + ".json");
});
}
LocalFileSystem.prototype.clean = function(activeNodes){
var self = this;
var cachePromise;
if (this.cache) {
cachePromise = this.cache.clean(activeNodes);
} else {
cachePromise = Promise.resolve();
}
return cachePromise.then(function() {
return fs.readdir(self.storageBaseDir).then(function(dirs){
return Promise.all(dirs.reduce(function(result, item){
if(item !== "global" && activeNodes.indexOf(item) === -1){
result.push(fs.remove(path.join(self.storageBaseDir,item)));
}
return result;
},[]));
}).catch(function(err){
if(err.code == 'ENOENT') {
return Promise.resolve();
}else{
return Promise.reject(err);
}
});
});
}
module.exports = function(config){
return new LocalFileSystem(config);
};

View File

@ -0,0 +1,119 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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");
function Memory(config){
this.data = {};
}
Memory.prototype.open = function(){
return Promise.resolve();
};
Memory.prototype.close = function(){
return Promise.resolve();
};
Memory.prototype.get = function(scope, key, callback) {
var value;
try{
if(this.data[scope]){
value = util.getMessageProperty(this.data[scope], key);
}
}catch(err){
if(callback){
callback(err);
}else{
throw err;
}
}
if(callback){
callback(null, value);
} else {
return value;
}
};
Memory.prototype.set =function(scope, key, value, callback) {
if(!this.data[scope]){
this.data[scope] = {};
}
try{
util.setMessageProperty(this.data[scope],key,value);
}catch(err){
if(callback){
callback(err);
}else{
throw err;
}
}
if(callback){
callback(null);
}
};
Memory.prototype.keys = function(scope, callback){
var values = [];
try{
if(this.data[scope]){
if (scope !== "global") {
values = Object.keys(this.data[scope]);
} else {
values = Object.keys(this.data[scope]).filter(function (key) {
return key !== "set" && key !== "get" && key !== "keys";
});
}
}
}catch(err){
if(callback){
callback(err);
}else{
throw err;
}
}
if(callback){
callback(null, values);
} else {
return values;
}
};
Memory.prototype.delete = function(scope){
delete this.data[scope];
return Promise.resolve();
};
Memory.prototype.clean = function(activeNodes){
for(var id in this.data){
if(this.data.hasOwnProperty(id) && id !== "global"){
var idParts = id.split(":");
if(activeNodes.indexOf(idParts[0]) === -1){
delete this.data[id];
}
}
}
return Promise.resolve();
}
Memory.prototype._export = function() {
return this.data;
}
module.exports = function(config){
return new Memory(config);
};

View File

@ -160,11 +160,12 @@ function setFlows(_config,type,muteLog,forceStart) {
activeFlowConfig = newFlowConfig; activeFlowConfig = newFlowConfig;
if (forceStart || started) { if (forceStart || started) {
return stop(type,diff,muteLog).then(function() { return stop(type,diff,muteLog).then(function() {
context.clean(activeFlowConfig); return context.clean(activeFlowConfig).then(function() {
start(type,diff,muteLog).then(function() { start(type,diff,muteLog).then(function() {
events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true}); events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true});
}); });
return flowRevision; return flowRevision;
});
}).catch(function(err) { }).catch(function(err) {
}) })
} else { } else {
@ -477,11 +478,19 @@ function addFlow(flow) {
} }
flow.id = redUtil.generateId(); flow.id = redUtil.generateId();
var nodes = [{ var tabNode = {
type:'tab', type:'tab',
label:flow.label, label:flow.label,
id:flow.id id:flow.id
}]; }
if (flow.hasOwnProperty('info')) {
tabNode.info = flow.info;
}
if (flow.hasOwnProperty('disabled')) {
tabNode.disabled = flow.disabled;
}
var nodes = [tabNode];
for (i=0;i<flow.nodes.length;i++) { for (i=0;i<flow.nodes.length;i++) {
node = flow.nodes[i]; node = flow.nodes[i];
@ -534,6 +543,12 @@ function getFlow(id) {
if (flow.label) { if (flow.label) {
result.label = flow.label; result.label = flow.label;
} }
if (flow.disabled) {
result.disabled = flow.disabled;
}
if (flow.hasOwnProperty('info')) {
result.info = flow.info;
}
if (id !== 'global') { if (id !== 'global') {
result.nodes = []; result.nodes = [];
} }
@ -623,6 +638,13 @@ function updateFlow(id,newFlow) {
label:newFlow.label, label:newFlow.label,
id:id id:id
} }
if (newFlow.hasOwnProperty('info')) {
tabNode.info = newFlow.info;
}
if (newFlow.hasOwnProperty('disabled')) {
tabNode.disabled = newFlow.disabled;
}
nodes = [tabNode].concat(newFlow.nodes||[]).concat(newFlow.configs||[]); nodes = [tabNode].concat(newFlow.nodes||[]).concat(newFlow.configs||[]);
nodes.forEach(function(n) { nodes.forEach(function(n) {
n.z = id; n.z = id;

View File

@ -37,7 +37,8 @@ function diffNodes(oldNode,newNode) {
return false; return false;
} }
var EnvVarPropertyRE = /^\$\((\S+)\)$/; var EnvVarPropertyRE_old = /^\$\((\S+)\)$/;
var EnvVarPropertyRE = /^\${(\S+)}$/;
function mapEnvVarProperties(obj,prop) { function mapEnvVarProperties(obj,prop) {
if (Buffer.isBuffer(obj[prop])) { if (Buffer.isBuffer(obj[prop])) {
@ -47,11 +48,9 @@ function mapEnvVarProperties(obj,prop) {
mapEnvVarProperties(obj[prop],i); mapEnvVarProperties(obj[prop],i);
} }
} else if (typeof obj[prop] === 'string') { } else if (typeof obj[prop] === 'string') {
var m; if (obj[prop][0] === "$" && (EnvVarPropertyRE_old.test(obj[prop]) || EnvVarPropertyRE.test(obj[prop])) ) {
if ( (m = EnvVarPropertyRE.exec(obj[prop])) !== null) { var envVar = obj[prop].substring(2,obj[prop].length-1);
if (process.env.hasOwnProperty(m[1])) { obj[prop] = process.env.hasOwnProperty(envVar)?process.env[envVar]:obj[prop];
obj[prop] = process.env[m[1]];
}
} }
} else { } else {
for (var p in obj[prop]) { for (var p in obj[prop]) {

View File

@ -167,6 +167,8 @@ module.exports = {
createNode: createNode, createNode: createNode,
getNode: flows.get, getNode: flows.get,
eachNode: flows.eachNode, eachNode: flows.eachNode,
getContext: context.get,
paletteEditorEnabled: registry.paletteEditorEnabled, paletteEditorEnabled: registry.paletteEditorEnabled,
installModule: installModule, installModule: installModule,
@ -216,5 +218,10 @@ module.exports = {
setCredentialSecret: credentials.setKey, setCredentialSecret: credentials.setKey,
clearCredentials: credentials.clear, clearCredentials: credentials.clear,
exportCredentials: credentials.export, exportCredentials: credentials.export,
getCredentialKeyType: credentials.getKeyType getCredentialKeyType: credentials.getKeyType,
// Contexts
loadContextsPlugin: context.load,
closeContextsPlugin: context.close,
listContextStores: context.listStores
}; };

View File

@ -62,6 +62,17 @@ function copyObjectProperties(src,dst,copyList,blockList) {
} }
} }
} }
function requireModule(name) {
var moduleInfo = registry.getModuleInfo(name);
if (moduleInfo && moduleInfo.path) {
var relPath = path.relative(__dirname, moduleInfo.path);
return require(relPath);
} else {
var err = new Error(`Cannot find module '${name}'`);
err.code = "MODULE_NOT_FOUND";
throw err;
}
}
function createNodeApi(node) { function createNodeApi(node) {
var red = { var red = {
@ -71,6 +82,7 @@ function createNodeApi(node) {
events: runtime.events, events: runtime.events,
util: runtime.util, util: runtime.util,
version: runtime.version, version: runtime.version,
require: requireModule
} }
copyObjectProperties(runtime.nodes,red.nodes,["createNode","getNode","eachNode","addCredentials","getCredentials","deleteCredentials" ]); copyObjectProperties(runtime.nodes,red.nodes,["createNode","getNode","eachNode","addCredentials","getCredentials","deleteCredentials" ]);
red.nodes.registerType = function(type,constructor,opts) { red.nodes.registerType = function(type,constructor,opts) {
@ -112,6 +124,7 @@ function createNodeApi(node) {
function loadNodeFiles(nodeFiles) { function loadNodeFiles(nodeFiles) {
var promises = []; var promises = [];
var nodes = [];
for (var module in nodeFiles) { for (var module in nodeFiles) {
/* istanbul ignore else */ /* istanbul ignore else */
if (nodeFiles.hasOwnProperty(module)) { if (nodeFiles.hasOwnProperty(module)) {
@ -119,6 +132,7 @@ function loadNodeFiles(nodeFiles) {
!semver.satisfies(runtime.version().replace(/(\-[1-9A-Za-z-][0-9A-Za-z-\.]*)?(\+[0-9A-Za-z-\.]+)?$/,""), nodeFiles[module].redVersion)) { !semver.satisfies(runtime.version().replace(/(\-[1-9A-Za-z-][0-9A-Za-z-\.]*)?(\+[0-9A-Za-z-\.]+)?$/,""), nodeFiles[module].redVersion)) {
//TODO: log it //TODO: log it
runtime.log.warn("["+module+"] "+runtime.log._("server.node-version-mismatch",{version:nodeFiles[module].redVersion})); runtime.log.warn("["+module+"] "+runtime.log._("server.node-version-mismatch",{version:nodeFiles[module].redVersion}));
nodeFiles[module].err = "version_mismatch";
continue; continue;
} }
if (module == "node-red" || !registry.getModuleInfo(module)) { if (module == "node-red" || !registry.getModuleInfo(module)) {
@ -148,7 +162,14 @@ function loadNodeFiles(nodeFiles) {
} }
try { try {
promises.push(loadNodeConfig(nodeFiles[module].nodes[node])) promises.push(loadNodeConfig(nodeFiles[module].nodes[node]).then((function() {
var m = module;
var n = node;
return function(nodeSet) {
nodeFiles[m].nodes[n] = nodeSet;
nodes.push(nodeSet);
}
})()));
} catch(err) { } catch(err) {
// //
} }
@ -158,16 +179,19 @@ function loadNodeFiles(nodeFiles) {
} }
} }
return when.settle(promises).then(function(results) { return when.settle(promises).then(function(results) {
var nodes = results.map(function(r) { for (var module in nodeFiles) {
registry.addNodeSet(r.value.id,r.value,r.value.version); if (nodeFiles.hasOwnProperty(module)) {
return r.value; if (!nodeFiles[module].err) {
}); registry.addModule(nodeFiles[module]);
}
}
}
return loadNodeSetList(nodes); return loadNodeSetList(nodes);
}); });
} }
function loadNodeConfig(fileInfo) { function loadNodeConfig(fileInfo) {
return when.promise(function(resolve) { return new Promise(function(resolve) {
var file = fileInfo.file; var file = fileInfo.file;
var module = fileInfo.module; var module = fileInfo.module;
var name = fileInfo.name; var name = fileInfo.name;
@ -292,7 +316,7 @@ function loadNodeSet(node) {
var nodeDir = path.dirname(node.file); var nodeDir = path.dirname(node.file);
var nodeFn = path.basename(node.file); var nodeFn = path.basename(node.file);
if (!node.enabled) { if (!node.enabled) {
return when.resolve(node); return Promise.resolve(node);
} else { } else {
} }
try { try {
@ -316,7 +340,7 @@ function loadNodeSet(node) {
if (loadPromise == null) { if (loadPromise == null) {
node.enabled = true; node.enabled = true;
node.loaded = true; node.loaded = true;
loadPromise = when.resolve(node); loadPromise = Promise.resolve(node);
} }
return loadPromise; return loadPromise;
} catch(err) { } catch(err) {
@ -333,7 +357,7 @@ function loadNodeSet(node) {
} }
} }
} }
return when.resolve(node); return Promise.resolve(node);
} }
} }
@ -365,19 +389,19 @@ function addModule(module) {
// TODO: nls // TODO: nls
var e = new Error("module_already_loaded"); var e = new Error("module_already_loaded");
e.code = "module_already_loaded"; e.code = "module_already_loaded";
return when.reject(e); return Promise.reject(e);
} }
try { try {
var moduleFiles = localfilesystem.getModuleFiles(module); var moduleFiles = localfilesystem.getModuleFiles(module);
return loadNodeFiles(moduleFiles); return loadNodeFiles(moduleFiles);
} catch(err) { } catch(err) {
return when.reject(err); return Promise.reject(err);
} }
} }
function loadNodeHelp(node,lang) { function loadNodeHelp(node,lang) {
var base = path.basename(node.template); var base = path.basename(node.template);
var localePath = undefined; var localePath;
if (node.module === 'node-red') { if (node.module === 'node-red') {
var cat_dir = path.dirname(node.template); var cat_dir = path.dirname(node.template);
var cat = path.basename(cat_dir); var cat = path.basename(cat_dir);

View File

@ -14,7 +14,6 @@
* limitations under the License. * limitations under the License.
**/ **/
var when = require("when");
var fs = require("fs"); var fs = require("fs");
var path = require("path"); var path = require("path");
@ -85,10 +84,11 @@ function getLocalNodeFiles(dir) {
var result = []; var result = [];
var files = []; var files = [];
var icons = [];
try { try {
files = fs.readdirSync(dir); files = fs.readdirSync(dir);
} catch(err) { } catch(err) {
return result; return {files: [], icons: []};
} }
files.sort(); files.sort();
files.forEach(function(fn) { files.forEach(function(fn) {
@ -103,14 +103,16 @@ function getLocalNodeFiles(dir) {
} else if (stats.isDirectory()) { } else if (stats.isDirectory()) {
// Ignore /.dirs/, /lib/ /node_modules/ // Ignore /.dirs/, /lib/ /node_modules/
if (!/^(\..*|lib|icons|node_modules|test|locales)$/.test(fn)) { if (!/^(\..*|lib|icons|node_modules|test|locales)$/.test(fn)) {
result = result.concat(getLocalNodeFiles(path.join(dir,fn))); var subDirResults = getLocalNodeFiles(path.join(dir,fn));
result = result.concat(subDirResults.files);
icons = icons.concat(subDirResults.icons);
} else if (fn === "icons") { } else if (fn === "icons") {
var iconList = scanIconDir(path.join(dir,fn)); var iconList = scanIconDir(path.join(dir,fn));
events.emit("node-icon-dir",{name:'node-red',path:path.join(dir,fn),icons:iconList}); icons.push({path:path.join(dir,fn),icons:iconList});
} }
} }
}); });
return result; return {files: result, icons: icons}
} }
function scanDirForNodesModules(dir,moduleName) { function scanDirForNodesModules(dir,moduleName) {
@ -198,7 +200,7 @@ function getModuleNodeFiles(module) {
var nodes = pkg['node-red'].nodes||{}; var nodes = pkg['node-red'].nodes||{};
var results = []; var results = [];
var iconDirs = []; var iconDirs = [];
var iconList = [];
for (var n in nodes) { for (var n in nodes) {
/* istanbul ignore else */ /* istanbul ignore else */
if (nodes.hasOwnProperty(n)) { if (nodes.hasOwnProperty(n)) {
@ -213,47 +215,54 @@ function getModuleNodeFiles(module) {
if (iconDirs.indexOf(iconDir) == -1) { if (iconDirs.indexOf(iconDir) == -1) {
try { try {
fs.statSync(iconDir); fs.statSync(iconDir);
var iconList = scanIconDir(iconDir); var icons = scanIconDir(iconDir);
events.emit("node-icon-dir",{name:pkg.name,path:iconDir,icons:iconList}); iconList.push({path:iconDir,icons:icons});
iconDirs.push(iconDir); iconDirs.push(iconDir);
} catch(err) { } catch(err) {
} }
} }
} }
} }
var result = {files:results,icons:iconList};
var examplesDir = path.join(moduleDir,"examples"); var examplesDir = path.join(moduleDir,"examples");
try { try {
fs.statSync(examplesDir) fs.statSync(examplesDir)
events.emit("node-examples-dir",{name:pkg.name,path:examplesDir}); events.emit("node-examples-dir",{name:pkg.name,path:examplesDir});
} catch(err) { } catch(err) {
} }
return results; return result;
} }
function getNodeFiles(disableNodePathScan) { function getNodeFiles(disableNodePathScan) {
var dir; var dir;
// Find all of the nodes to load // Find all of the nodes to load
var nodeFiles = []; var nodeFiles = [];
var results;
var dir = path.resolve(__dirname + '/../../../../public/icons'); var dir = path.resolve(__dirname + '/../../../../public/icons');
var iconList = scanIconDir(dir); var iconList = [{path:dir,icons:scanIconDir(dir)}];
events.emit("node-icon-dir",{name:'node-red',path:dir,icons:iconList});
if (settings.coreNodesDir) { if (settings.coreNodesDir) {
nodeFiles = getLocalNodeFiles(path.resolve(settings.coreNodesDir)); results = getLocalNodeFiles(path.resolve(settings.coreNodesDir));
nodeFiles = nodeFiles.concat(results.files);
iconList = iconList.concat(results.icons);
var defaultLocalesPath = path.join(settings.coreNodesDir,"core","locales"); var defaultLocalesPath = path.join(settings.coreNodesDir,"core","locales");
i18n.registerMessageCatalog("node-red",defaultLocalesPath,"messages.json"); i18n.registerMessageCatalog("node-red",defaultLocalesPath,"messages.json");
} }
if (settings.userDir) { if (settings.userDir) {
dir = path.join(settings.userDir,"lib","icons"); dir = path.join(settings.userDir,"lib","icons");
iconList = scanIconDir(dir); var icons = scanIconDir(dir);
if (iconList.length > 0) { if (icons.length > 0) {
events.emit("node-icon-dir",{name:'Library',path:dir,icons:iconList}); iconList.push({path:dir,icons:icons});
} }
dir = path.join(settings.userDir,"nodes"); dir = path.join(settings.userDir,"nodes");
nodeFiles = nodeFiles.concat(getLocalNodeFiles(dir)); results = getLocalNodeFiles(path.resolve(dir));
nodeFiles = nodeFiles.concat(results.files);
iconList = iconList.concat(results.icons);
} }
if (settings.nodesDir) { if (settings.nodesDir) {
dir = settings.nodesDir; dir = settings.nodesDir;
@ -261,7 +270,9 @@ function getNodeFiles(disableNodePathScan) {
dir = [dir]; dir = [dir];
} }
for (var i=0;i<dir.length;i++) { for (var i=0;i<dir.length;i++) {
nodeFiles = nodeFiles.concat(getLocalNodeFiles(dir[i])); results = getLocalNodeFiles(dir[i]);
nodeFiles = nodeFiles.concat(results.files);
iconList = iconList.concat(results.icons);
} }
} }
@ -269,7 +280,8 @@ function getNodeFiles(disableNodePathScan) {
"node-red": { "node-red": {
name: "node-red", name: "node-red",
version: settings.version, version: settings.version,
nodes: {} nodes: {},
icons: iconList
} }
} }
nodeFiles.forEach(function(node) { nodeFiles.forEach(function(node) {
@ -283,20 +295,22 @@ function getNodeFiles(disableNodePathScan) {
nodeList[moduleFile.package.name] = { nodeList[moduleFile.package.name] = {
name: moduleFile.package.name, name: moduleFile.package.name,
version: moduleFile.package.version, version: moduleFile.package.version,
path: moduleFile.dir,
local: moduleFile.local||false, local: moduleFile.local||false,
nodes: {} nodes: {},
icons: nodeModuleFiles.icons
}; };
if (moduleFile.package['node-red'].version) { if (moduleFile.package['node-red'].version) {
nodeList[moduleFile.package.name].redVersion = moduleFile.package['node-red'].version; nodeList[moduleFile.package.name].redVersion = moduleFile.package['node-red'].version;
} }
nodeModuleFiles.forEach(function(node) { nodeModuleFiles.files.forEach(function(node) {
node.local = moduleFile.local||false; node.local = moduleFile.local||false;
nodeList[moduleFile.package.name].nodes[node.name] = node; nodeList[moduleFile.package.name].nodes[node.name] = node;
}); });
nodeFiles = nodeFiles.concat(nodeModuleFiles); nodeFiles = nodeFiles.concat(nodeModuleFiles.files);
}); });
} else { } else {
console.log("node path scan disabled"); // console.log("node path scan disabled");
} }
return nodeList; return nodeList;
} }
@ -316,12 +330,13 @@ function getModuleFiles(module) {
nodeList[moduleFile.package.name] = { nodeList[moduleFile.package.name] = {
name: moduleFile.package.name, name: moduleFile.package.name,
version: moduleFile.package.version, version: moduleFile.package.version,
nodes: {} nodes: {},
icons: nodeModuleFiles.icons
}; };
if (moduleFile.package['node-red'].version) { if (moduleFile.package['node-red'].version) {
nodeList[moduleFile.package.name].redVersion = moduleFile.package['node-red'].version; nodeList[moduleFile.package.name].redVersion = moduleFile.package['node-red'].version;
} }
nodeModuleFiles.forEach(function(node) { nodeModuleFiles.files.forEach(function(node) {
nodeList[moduleFile.package.name].nodes[node.name] = node; nodeList[moduleFile.package.name].nodes[node.name] = node;
nodeList[moduleFile.package.name].nodes[node.name].local = moduleFile.local || false; nodeList[moduleFile.package.name].nodes[node.name].local = moduleFile.local || false;
}); });

View File

@ -181,7 +181,14 @@ function loadNodeConfigs() {
} }
} }
function addNodeSet(id,set,version) { function addModule(module) {
moduleNodes[module.name] = [];
moduleConfigs[module.name] = module;
for (var setName in module.nodes) {
if (module.nodes.hasOwnProperty(setName)) {
var set = module.nodes[setName];
moduleNodes[module.name].push(set.name);
nodeList.push(set.id);
if (!set.err) { if (!set.err) {
set.types.forEach(function(t) { set.types.forEach(function(t) {
if (nodeTypeToId.hasOwnProperty(t)) { if (nodeTypeToId.hasOwnProperty(t)) {
@ -197,30 +204,20 @@ function addNodeSet(id,set,version) {
}); });
if (!set.err) { if (!set.err) {
set.types.forEach(function(t) { set.types.forEach(function(t) {
nodeTypeToId[t] = id; nodeTypeToId[t] = set.id;
}); });
} }
} }
moduleNodes[set.module] = moduleNodes[set.module]||[];
moduleNodes[set.module].push(set.name);
if (!moduleConfigs[set.module]) {
moduleConfigs[set.module] = {
name: set.module,
nodes: {}
};
} }
if (version) {
moduleConfigs[set.module].version = version;
} }
moduleConfigs[set.module].local = set.local; if (module.icons) {
icon_paths[module.name] = [];
moduleConfigs[set.module].nodes[set.name] = set; module.icons.forEach(icon=>icon_paths[module.name].push(path.resolve(icon.path)) )
nodeList.push(id); }
nodeConfigCache = null; nodeConfigCache = null;
} }
function removeNode(id) { function removeNode(id) {
var config = moduleConfigs[getModule(id)].nodes[getNode(id)]; var config = moduleConfigs[getModule(id)].nodes[getNode(id)];
if (!config) { if (!config) {
@ -346,6 +343,7 @@ function getModuleInfo(module) {
name: module, name: module,
version: moduleConfigs[module].version, version: moduleConfigs[module].version,
local: moduleConfigs[module].local, local: moduleConfigs[module].local,
path: moduleConfigs[module].path,
nodes: [] nodes: []
}; };
for (var i = 0; i < nodes.length; ++i) { for (var i = 0; i < nodes.length; ++i) {
@ -592,6 +590,7 @@ var iconCache = {};
var defaultIcon = path.resolve(__dirname + '/../../../../public/icons/arrow-in.png'); var defaultIcon = path.resolve(__dirname + '/../../../../public/icons/arrow-in.png');
function nodeIconDir(dir) { function nodeIconDir(dir) {
return;
icon_paths[dir.name] = icon_paths[dir.name] || []; icon_paths[dir.name] = icon_paths[dir.name] || [];
icon_paths[dir.name].push(path.resolve(dir.path)); icon_paths[dir.name].push(path.resolve(dir.path));
@ -647,11 +646,11 @@ function getNodeIcons() {
for (var module in moduleConfigs) { for (var module in moduleConfigs) {
if (moduleConfigs.hasOwnProperty(module)) { if (moduleConfigs.hasOwnProperty(module)) {
if (moduleConfigs[module].icons) { if (moduleConfigs[module].icons) {
iconList[module] = moduleConfigs[module].icons; iconList[module] = [];
moduleConfigs[module].icons.forEach(icon=>{ iconList[module] = iconList[module].concat(icon.icons) });
} }
} }
} }
return iconList; return iconList;
} }
@ -663,7 +662,9 @@ var registry = module.exports = {
registerNodeConstructor: registerNodeConstructor, registerNodeConstructor: registerNodeConstructor,
getNodeConstructor: getNodeConstructor, getNodeConstructor: getNodeConstructor,
addNodeSet: addNodeSet,
addModule: addModule,
enableNodeSet: enableNodeSet, enableNodeSet: enableNodeSet,
disableNodeSet: disableNodeSet, disableNodeSet: disableNodeSet,

View File

@ -72,7 +72,8 @@ var localfilesystem = {
var defaultPackage = { var defaultPackage = {
"name": "node-red-project", "name": "node-red-project",
"description": "A Node-RED Project", "description": "A Node-RED Project",
"version": "0.0.1" "version": "0.0.1",
"private": true
}; };
return util.writeFile(packageFile,JSON.stringify(defaultPackage,"",4)); return util.writeFile(packageFile,JSON.stringify(defaultPackage,"",4));
} }

View File

@ -162,7 +162,7 @@ Project.prototype.initialise = function(user,data) {
if (defaultFileSet.hasOwnProperty(file)) { if (defaultFileSet.hasOwnProperty(file)) {
var path = fspath.join(project.path,file); var path = fspath.join(project.path,file);
if (!fs.existsSync(path)) { if (!fs.existsSync(path)) {
promises.push(util.writeFile(path,defaultFileSet[file](project))); promises.push(util.writeFile(path,defaultFileSet[file](project, runtime)));
} }
} }
@ -850,7 +850,7 @@ function createDefaultProject(user, project) {
} }
for (var file in defaultFileSet) { for (var file in defaultFileSet) {
if (defaultFileSet.hasOwnProperty(file)) { if (defaultFileSet.hasOwnProperty(file)) {
promises.push(util.writeFile(fspath.join(projectPath,file),defaultFileSet[file](project))); promises.push(util.writeFile(fspath.join(projectPath,file),defaultFileSet[file](project, runtime)));
} }
} }

View File

@ -15,10 +15,11 @@
**/ **/
module.exports = { module.exports = {
"package.json": function(project) { "package.json": function(project, runtime) {
var i18n = runtime.i18n;
var package = { var package = {
"name": project.name, "name": project.name,
"description": project.summary||"A Node-RED Project", "description": project.summary||i18n._("storage.localfilesystem.projects.summary"),
"version": "0.0.1", "version": "0.0.1",
"dependencies": {}, "dependencies": {},
"node-red": { "node-red": {
@ -34,13 +35,13 @@ module.exports = {
} }
return JSON.stringify(package,"",4); return JSON.stringify(package,"",4);
}, },
"README.md": function(project) { "README.md": function(project, runtime) {
var i18n = runtime.i18n;
var content = project.name+"\n"+("=".repeat(project.name.length))+"\n\n"; var content = project.name+"\n"+("=".repeat(project.name.length))+"\n\n";
if (project.summary) { if (project.summary) {
content += project.summary+"\n\n"; content += project.summary+"\n\n";
} }
content += "### About\n\nThis is your project's README.md file. It helps users understand what your\nproject does, how to use it and anything else they may need to know."; content += i18n._("storage.localfilesystem.projects.readme");
return content; return content;
}, },
".gitignore": function() { return "*.backup" ;} ".gitignore": function() { return "*.backup" ;}

View File

@ -16,6 +16,8 @@
var clone = require("clone"); var clone = require("clone");
var jsonata = require("jsonata"); var jsonata = require("jsonata");
var safeJSONStringify = require("json-stringify-safe");
var util = require("util");
function generateId() { function generateId() {
return (1+Math.random()*4294967295).toString(16); return (1+Math.random()*4294967295).toString(16);
@ -303,35 +305,73 @@ function setMessageProperty(msg,prop,value,createMissing) {
} }
} }
function evaluateNodeProperty(value, type, node, msg) { function evaluteEnvProperty(value) {
if (type === 'str') { if (/^\${[^}]+}$/.test(value)) {
return ""+value; // ${ENV_VAR}
} else if (type === 'num') { value = value.substring(2,value.length-1);
return Number(value); value = process.env.hasOwnProperty(value)?process.env[value]:""
} else if (type === 'json') { } else if (!/\${\S+}/.test(value)) {
return JSON.parse(value); // ENV_VAR
} else if (type === 're') { value = process.env.hasOwnProperty(value)?process.env[value]:""
return new RegExp(value); } else {
} else if (type === 'date') { // FOO${ENV_VAR}BAR
return Date.now(); value = value.replace(/\${([^}]+)}/g, function(match, v) {
} else if (type === 'bin') { return process.env.hasOwnProperty(v)?process.env[v]:""
var data = JSON.parse(value); });
return Buffer.from(data);
} 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);
} else if (type === 'jsonata') {
var expr = prepareJSONataExpression(value,node);
return evaluateJSONataExpression(expr,msg);
} }
return value; return value;
} }
var parseContextStore = function(key) {
var parts = {};
var m = /^#:\((\S+?)\)::(.*)$/.exec(key);
if (m) {
parts.store = m[1];
parts.key = m[2];
} else {
parts.key = key;
}
return parts;
}
function evaluateNodeProperty(value, type, node, msg, callback) {
var result;
if (type === 'str') {
result = ""+value;
} else if (type === 'num') {
result = Number(value);
} else if (type === 'json') {
result = JSON.parse(value);
} else if (type === 're') {
result = new RegExp(value);
} else if (type === 'date') {
result = Date.now();
} else if (type === 'bin') {
var data = JSON.parse(value);
result = Buffer.from(data);
} else if (type === 'msg' && msg) {
result = getMessageProperty(msg,value);
} else if ((type === 'flow' || type === 'global') && node) {
var contextKey = parseContextStore(value);
result = node.context()[type].get(contextKey.key,contextKey.store,callback);
if (callback) {
return;
}
} else if (type === 'bool') {
result = /^true$/i.test(value);
} else if (type === 'jsonata') {
var expr = prepareJSONataExpression(value,node);
result = evaluateJSONataExpression(expr,msg);
} else if (type === 'env') {
result = evaluteEnvProperty(value);
}
if (callback) {
callback(result);
} else {
return value;
}
}
function prepareJSONataExpression(value,node) { function prepareJSONataExpression(value,node) {
var expr = jsonata(value); var expr = jsonata(value);
expr.assign('flowContext',function(val) { expr.assign('flowContext',function(val) {
@ -340,6 +380,9 @@ function prepareJSONataExpression(value,node) {
expr.assign('globalContext',function(val) { expr.assign('globalContext',function(val) {
return node.context().global.get(val); return node.context().global.get(val);
}); });
expr.assign('env', function(val) {
return process.env[val];
})
expr.registerFunction('clone', cloneMessage, '<(oa)-:o>'); expr.registerFunction('clone', cloneMessage, '<(oa)-:o>');
expr._legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(value); expr._legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(value);
return expr; return expr;
@ -367,7 +410,138 @@ function normaliseNodeTypeName(name) {
return result; return result;
} }
function encodeObject(msg,opts) {
var debuglength = 1000;
if (opts && opts.hasOwnProperty('maxLength')) {
debuglength = opts.maxLength;
}
var msgType = typeof msg.msg;
if (msg.msg instanceof Error) {
msg.format = "error";
var errorMsg = {};
if (msg.msg.name) {
errorMsg.name = msg.msg.name;
}
if (msg.msg.hasOwnProperty('message')) {
errorMsg.message = msg.msg.message;
} else {
errorMsg.message = msg.msg.toString();
}
msg.msg = JSON.stringify(errorMsg);
} else if (msg.msg instanceof Buffer) {
msg.format = "buffer["+msg.msg.length+"]";
msg.msg = msg.msg.toString('hex');
if (msg.msg.length > debuglength) {
msg.msg = msg.msg.substring(0,debuglength);
}
} else if (msg.msg && msgType === 'object') {
try {
msg.format = msg.msg.constructor.name || "Object";
// Handle special case of msg.req/res objects from HTTP In node
if (msg.format === "IncomingMessage" || msg.format === "ServerResponse") {
msg.format = "Object";
}
} catch(err) {
msg.format = "Object";
}
if (/error/i.test(msg.format)) {
msg.msg = JSON.stringify({
name: msg.msg.name,
message: msg.msg.message
});
} else {
var isArray = util.isArray(msg.msg);
if (isArray) {
msg.format = "array["+msg.msg.length+"]";
if (msg.msg.length > debuglength) {
// msg.msg = msg.msg.slice(0,debuglength);
msg.msg = {
__encoded__: true,
type: "array",
data: msg.msg.slice(0,debuglength),
length: msg.msg.length
}
}
}
if (isArray || (msg.format === "Object")) {
msg.msg = safeJSONStringify(msg.msg, function(key, value) {
if (key === '_req' || key === '_res') {
value = {
__encoded__: true,
type: "internal"
}
} else if (value instanceof Error) {
value = value.toString()
} else if (util.isArray(value) && value.length > debuglength) {
value = {
__encoded__: true,
type: "array",
data: value.slice(0,debuglength),
length: value.length
}
} else if (typeof value === 'string') {
if (value.length > debuglength) {
value = value.substring(0,debuglength)+"...";
}
} else if (typeof value === 'function') {
value = {
__encoded__: true,
type: "function"
}
} else if (typeof value === 'number') {
if (isNaN(value) || value === Infinity || value === -Infinity) {
value = {
__encoded__: true,
type: "number",
data: value.toString()
}
}
} else if (value && value.constructor) {
if (value.type === "Buffer") {
value.__encoded__ = true;
value.length = value.data.length;
if (value.length > debuglength) {
value.data = value.data.slice(0,debuglength);
}
} else if (value.constructor.name === "ServerResponse") {
value = "[internal]"
} else if (value.constructor.name === "Socket") {
value = "[internal]"
}
}
return value;
}," ");
} else {
try { msg.msg = msg.msg.toString(); }
catch(e) { msg.msg = "[Type not printable]"; }
}
}
} else if (msgType === "function") {
msg.format = "function";
msg.msg = "[function]"
} else if (msgType === "boolean") {
msg.format = "boolean";
msg.msg = msg.msg.toString();
} else if (msgType === "number") {
msg.format = "number";
msg.msg = msg.msg.toString();
} else if (msg.msg === 0) {
msg.format = "number";
msg.msg = "0";
} else if (msg.msg === null || msgType === "undefined") {
msg.format = (msg.msg === null)?"null":"undefined";
msg.msg = "(undefined)";
} else {
msg.format = "string["+msg.msg.length+"]";
if (msg.msg.length > debuglength) {
msg.msg = msg.msg.substring(0,debuglength)+"...";
}
}
return msg;
}
module.exports = { module.exports = {
encodeObject: encodeObject,
ensureString: ensureString, ensureString: ensureString,
ensureBuffer: ensureBuffer, ensureBuffer: ensureBuffer,
cloneMessage: cloneMessage, cloneMessage: cloneMessage,

View File

@ -33,6 +33,7 @@ var payloadType = {
"json": 6, "json": 6,
"bin": 7, "bin": 7,
"date": 8, "date": 8,
"env": 9,
}; };
injectNode.prototype.setPayload = function(type, value) { injectNode.prototype.setPayload = function(type, value) {
@ -42,7 +43,7 @@ injectNode.prototype.setPayload = function(type, value) {
var payloadTypeXPath = '//*[@class="red-ui-typedInput-options"]/a[' + payloadType[type] + ']'; var payloadTypeXPath = '//*[@class="red-ui-typedInput-options"]/a[' + payloadType[type] + ']';
browser.clickWithWait(payloadTypeXPath); browser.clickWithWait(payloadTypeXPath);
// Input a value. // Input a value.
browser.setValue('#node-input-payload', value); browser.setValue('//*[@class="red-ui-typedInput-input"]/input', value);
} }
injectNode.prototype.setTopic = function(value) { injectNode.prototype.setTopic = function(value) {

View File

@ -15,7 +15,7 @@
**/ **/
function open() { function open() {
browser.clickWithWait('#red-ui-tab-debug'); browser.clickWithWait('#red-ui-tab-debug-link-button');
} }
function getMessage(index) { function getMessage(index) {

View File

@ -508,6 +508,33 @@ describe('function node', function() {
}); });
}); });
it('should allow accessing node.id', function(done) {
var flow = [{id:"n1",type:"function",wires:[["n2"]],func:"msg.payload = node.id; return msg;"},
{id:"n2", type:"helper"}];
helper.load(functionNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('payload', n1.id);
done();
});
n1.receive({payload:"foo",topicb: "bar"});
});
});
it('should allow accessing node.name', function(done) {
var flow = [{id:"n1",type:"function",wires:[["n2"]],func:"msg.payload = node.name; return msg;", "name":"name of node"},
{id:"n2", type:"helper"}];
helper.load(functionNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('payload', n1.name);
done();
});
n1.receive({payload:"foo",topicb: "bar"});
});
});
it('should use the same Date object from outside the sandbox', function(done) { it('should use the same Date object from outside the sandbox', function(done) {
var flow = [{id:"n1",type:"function",wires:[["n2"]],func:"msg.payload=global.get('typeTest')(new Date());return msg;"}, var flow = [{id:"n1",type:"function",wires:[["n2"]],func:"msg.payload=global.get('typeTest')(new Date());return msg;"},

View File

@ -428,13 +428,14 @@ describe('trigger node', function() {
n2.on("input", function(msg) { n2.on("input", function(msg) {
try { try {
if (c === 0) { if (c === 0) {
console.log(c,Date.now() - ss,msg);
msg.should.have.a.property("payload", "Hello"); msg.should.have.a.property("payload", "Hello");
c += 1; c += 1;
} }
else { else {
console.log(c,Date.now() - ss,msg);
msg.should.have.a.property("payload", "World"); msg.should.have.a.property("payload", "World");
//console.log(Date.now() - ss); (Date.now() - ss).should.be.greaterThan(150);
(Date.now() - ss).should.be.greaterThan(140);
done(); done();
} }
} }
@ -444,7 +445,7 @@ describe('trigger node', function() {
n1.emit("input", {payload:"Hello"}); n1.emit("input", {payload:"Hello"});
setTimeout( function() { setTimeout( function() {
n1.emit("input", {payload:"Error"}); n1.emit("input", {payload:"Error"});
},20); },30);
setTimeout( function() { setTimeout( function() {
n1.emit("input", {payload:"World"}); n1.emit("input", {payload:"World"});
},150); },150);

View File

@ -0,0 +1,89 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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 rpi = require("../../../../nodes/core/hardware/36-rpi-gpio.js");
var helper = require("node-red-node-test-helper");
var fs = require("fs");
describe('RPI GPIO Node', function() {
before(function(done) {
helper.startServer(done);
});
after(function(done) {
helper.stopServer(done);
});
afterEach(function() {
helper.unload();
});
var checkIgnore = function(done) {
setTimeout(function() {
try {
var logEvents = helper.log().args.filter(function(evt) {
return ((evt[0].level == 30) && (evt[0].msg.indexOf("rpi-gpio")===0));
});
logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith("rpi-gpio : rpi-gpio.errors.ignorenode");
done();
} catch(err) {
done(err);
}
},25);
}
it('should load Input node', function(done) {
var flow = [{id:"n1", type:"rpi-gpio in", name:"rpi-gpio in" }];
helper.load(rpi, flow, function() {
var n1 = helper.getNode("n1");
n1.should.have.property('name', 'rpi-gpio in');
try {
var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString();
if (cpuinfo.indexOf(": BCM") === 1) {
done(); // It's ON a PI ... should really do more tests !
} else {
checkIgnore(done);
}
}
catch(e) {
checkIgnore(done);
}
});
});
it('should load Output node', function(done) {
var flow = [{id:"n1", type:"rpi-gpio out", name:"rpi-gpio out" }];
helper.load(rpi, flow, function() {
var n1 = helper.getNode("n1");
n1.should.have.property('name', 'rpi-gpio out');
try {
var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString();
if (cpuinfo.indexOf(": BCM") === 1) {
done(); // It's ON a PI ... should really do more tests !
} else {
checkIgnore(done);
}
}
catch(e) {
checkIgnore(done);
}
});
});
});

View File

@ -796,6 +796,25 @@ describe('HTTP Request Node', function() {
}); });
}); });
it('should prevent following redirect when msg.followRedirects is false', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:getTestURL('/redirectToText')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('statusCode',302);
msg.should.have.property('responseUrl', getTestURL('/redirectToText'));
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo",followRedirects:false});
});
});
it('shuold output an error when request timeout occurred', function(done) { it('shuold output an error when request timeout occurred', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/timeout')}, var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/timeout')},
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
@ -806,7 +825,13 @@ describe('HTTP Request Node', function() {
var n2 = helper.getNode("n2"); var n2 = helper.getNode("n2");
n2.on("input", function(msg) { n2.on("input", function(msg) {
try { try {
msg.should.have.property('statusCode','ECONNRESET'); msg.should.have.property('statusCode','ESOCKETTIMEDOUT');
var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == 'http request';
});
logEvents.should.have.length(1);
var tstmp = logEvents[0][0].timestamp;
logEvents[0][0].should.eql({level:helper.log().ERROR, id:'n1',type:'http request',msg:'common.notification.errors.no-response', timestamp:tstmp});
done(); done();
} catch(err) { } catch(err) {
done(err); done(err);

View File

@ -409,6 +409,33 @@ describe('change Node', function() {
}); });
}); });
describe('env var', function() {
before(function() {
process.env.NR_TEST_A = 'foo';
})
after(function() {
delete process.env.NR_TEST_A;
})
it('sets the value using env property', function(done) {
var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"set","p":"payload","pt":"msg","to":"NR_TEST_A","tot":"env"}],"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("foo");
done();
} catch(err) {
done(err);
}
});
changeNode1.receive({payload:"123",topic:"ABC"});
});
});
});
it('changes the value using jsonata', function(done) { it('changes the value using jsonata', function(done) {
var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"set","p":"payload","to":"$length(payload)","tot":"jsonata"}],"name":"changeNode","wires":[["helperNode1"]]}, var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"set","p":"payload","to":"$length(payload)","tot":"jsonata"}],"name":"changeNode","wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}]; {id:"helperNode1", type:"helper", wires:[]}];
@ -872,6 +899,33 @@ describe('change Node', function() {
changeNode1.receive({payload:""}); changeNode1.receive({payload:""});
}); });
}); });
describe('env var', function() {
before(function() {
process.env.NR_TEST_A = 'foo';
})
after(function() {
delete process.env.NR_TEST_A;
})
it('changes the value using env property', function(done) {
var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"change","p":"payload","from":"topic","to":"NR_TEST_A","fromt":"msg","tot":"env"}],"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("abcfooabc");
done();
} catch(err) {
done(err);
}
});
changeNode1.receive({payload:"abcABCabc",topic:"ABC"});
});
});
});
}); });
describe("#delete", function() { describe("#delete", function() {

View File

@ -0,0 +1,27 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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 request = require('supertest');
var express = require('express');
var bodyParser = require('body-parser');
var sinon = require('sinon');
var when = require('when');
var context = require("../../../../red/api/admin/context");
describe("api/admin/context", function() {
});

View File

@ -59,7 +59,8 @@ describe("api/editor/settings", function() {
}, },
nodes: { nodes: {
paletteEditorEnabled: function() { return true; }, paletteEditorEnabled: function() { return true; },
getCredentialKeyType: function() { return "test-key-type"} getCredentialKeyType: function() { return "test-key-type"},
listContextStores: function() { return {default: "foo", stores: ["foo","bar"]}}
}, },
log: { error: console.error }, log: { error: console.error },
storage: {} storage: {}
@ -73,6 +74,7 @@ describe("api/editor/settings", function() {
} }
res.body.should.have.property("httpNodeRoot","testHttpNodeRoot"); res.body.should.have.property("httpNodeRoot","testHttpNodeRoot");
res.body.should.have.property("version","testVersion"); res.body.should.have.property("version","testVersion");
res.body.should.have.property("context",{default: "foo", stores: ["foo","bar"]});
res.body.should.have.property("paletteCategories",["red","blue","green"]); res.body.should.have.property("paletteCategories",["red","blue","green"]);
res.body.should.have.property("editorTheme",{test:456}); res.body.should.have.property("editorTheme",{test:456});
res.body.should.have.property("testNodeSetting","helloWorld"); res.body.should.have.property("testNodeSetting","helloWorld");
@ -95,7 +97,8 @@ describe("api/editor/settings", function() {
}, },
nodes: { nodes: {
paletteEditorEnabled: function() { return true; }, paletteEditorEnabled: function() { return true; },
getCredentialKeyType: function() { return "test-key-type"} getCredentialKeyType: function() { return "test-key-type"},
listContextStores: function() { return {default: "foo", stores: ["foo","bar"]}}
}, },
log: { error: console.error }, log: { error: console.error },
storage: {} storage: {}
@ -130,7 +133,8 @@ describe("api/editor/settings", function() {
}, },
nodes: { nodes: {
paletteEditorEnabled: function() { return true; }, paletteEditorEnabled: function() { return true; },
getCredentialKeyType: function() { return "test-key-type"} getCredentialKeyType: function() { return "test-key-type"},
listContextStores: function() { return {default: "foo", stores: ["foo","bar"]}}
}, },
log: { error: console.error }, log: { error: console.error },
storage: { storage: {
@ -169,7 +173,8 @@ describe("api/editor/settings", function() {
}, },
nodes: { nodes: {
paletteEditorEnabled: function() { return true; }, paletteEditorEnabled: function() { return true; },
getCredentialKeyType: function() { return "test-key-type"} getCredentialKeyType: function() { return "test-key-type"},
listContextStores: function() { return {default: "foo", stores: ["foo","bar"]}}
}, },
log: { error: console.error }, log: { error: console.error },
storage: { storage: {
@ -211,7 +216,8 @@ describe("api/editor/settings", function() {
}, },
nodes: { nodes: {
paletteEditorEnabled: function() { return true; }, paletteEditorEnabled: function() { return true; },
getCredentialKeyType: function() { return "test-key-type"} getCredentialKeyType: function() { return "test-key-type"},
listContextStores: function() { return {default: "foo", stores: ["foo","bar"]}}
}, },
log: { error: console.error }, log: { error: console.error },
storage: { storage: {
@ -248,8 +254,8 @@ describe("api/editor/settings", function() {
}, },
nodes: { nodes: {
paletteEditorEnabled: function() { return false; }, paletteEditorEnabled: function() { return false; },
getCredentialKeyType: function() { return "test-key-type"} getCredentialKeyType: function() { return "test-key-type"},
listContextStores: function() { return {default: "foo", stores: ["foo","bar"]}}
}, },
log: { error: console.error }, log: { error: console.error },
storage: {} storage: {}

View File

@ -79,6 +79,7 @@ describe("runtime", function() {
var redNodesGetNodeList; var redNodesGetNodeList;
var redNodesLoadFlows; var redNodesLoadFlows;
var redNodesStartFlows; var redNodesStartFlows;
var redNodesLoadContextsPlugin;
beforeEach(function() { beforeEach(function() {
storageInit = sinon.stub(storage,"init",function(settings) {return when.resolve();}); storageInit = sinon.stub(storage,"init",function(settings) {return when.resolve();});
@ -91,6 +92,7 @@ describe("runtime", function() {
redNodesCleanModuleList = sinon.stub(redNodes,"cleanModuleList",function(){}); redNodesCleanModuleList = sinon.stub(redNodes,"cleanModuleList",function(){});
redNodesLoadFlows = sinon.stub(redNodes,"loadFlows",function() {return when.resolve()}); redNodesLoadFlows = sinon.stub(redNodes,"loadFlows",function() {return when.resolve()});
redNodesStartFlows = sinon.stub(redNodes,"startFlows",function() {}); redNodesStartFlows = sinon.stub(redNodes,"startFlows",function() {});
redNodesLoadContextsPlugin = sinon.stub(redNodes,"loadContextsPlugin",function() {return when.resolve()});
}); });
afterEach(function() { afterEach(function() {
storageInit.restore(); storageInit.restore();
@ -104,6 +106,7 @@ describe("runtime", function() {
redNodesCleanModuleList.restore(); redNodesCleanModuleList.restore();
redNodesLoadFlows.restore(); redNodesLoadFlows.restore();
redNodesStartFlows.restore(); redNodesStartFlows.restore();
redNodesLoadContextsPlugin.restore();
}); });
it("reports errored/missing modules",function(done) { it("reports errored/missing modules",function(done) {
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function(cb) { redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function(cb) {
@ -183,7 +186,7 @@ describe("runtime", function() {
}); });
it("reports runtime metrics",function(done) { it("reports runtime metrics",function(done) {
var stopFlows = sinon.stub(redNodes,"stopFlows",function() {} ); var stopFlows = sinon.stub(redNodes,"stopFlows",function() { return when.resolve();} );
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {return []}); redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {return []});
logMetric.restore(); logMetric.restore();
logMetric = sinon.stub(log,"metric",function() { return true; }); logMetric = sinon.stub(log,"metric",function() { return true; });
@ -214,10 +217,18 @@ describe("runtime", function() {
}); });
it("stops components", function() { it("stops components", function(done) {
var stopFlows = sinon.stub(redNodes,"stopFlows",function() {} ); var stopFlows = sinon.stub(redNodes,"stopFlows",function() { return when.resolve();} );
runtime.stop(); var closeContextsPlugin = sinon.stub(redNodes,"closeContextsPlugin",function() { return when.resolve();} );
runtime.stop().then(function(){
stopFlows.called.should.be.true(); stopFlows.called.should.be.true();
closeContextsPlugin.called.should.be.true();
done();
}).catch(function(err){
return done(err)
}).finally(function(){
stopFlows.restore(); stopFlows.restore();
closeContextsPlugin.restore();
});
}); });
}); });

View File

@ -0,0 +1,577 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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 path = require("path");
var Context = require("../../../../../red/runtime/nodes/context/index");
describe('context', function() {
describe('local memory',function() {
beforeEach(function() {
Context.init({});
Context.load();
});
afterEach(function() {
Context.clean({allNodes:{}});
return Context.close();
});
it('stores local property',function() {
var context1 = Context.get("1","flowA");
should.not.exist(context1.get("foo"));
context1.set("foo","test");
context1.get("foo").should.equal("test");
});
it('stores local property - creates parent properties',function() {
var context1 = Context.get("1","flowA");
context1.set("foo.bar","test");
context1.get("foo").should.eql({bar:"test"});
});
it('deletes local property',function() {
var context1 = Context.get("1","flowA");
context1.set("foo.abc.bar1","test1");
context1.set("foo.abc.bar2","test2");
context1.get("foo.abc").should.eql({bar1:"test1",bar2:"test2"});
context1.set("foo.abc.bar1",undefined);
context1.get("foo.abc").should.eql({bar2:"test2"});
context1.set("foo.abc",undefined);
should.not.exist(context1.get("foo.abc"));
context1.set("foo",undefined);
should.not.exist(context1.get("foo"));
});
it('stores flow property',function() {
var context1 = Context.get("1","flowA");
should.not.exist(context1.flow.get("foo"));
context1.flow.set("foo","test");
context1.flow.get("foo").should.equal("test");
});
it('stores global property',function() {
var context1 = Context.get("1","flowA");
should.not.exist(context1.global.get("foo"));
context1.global.set("foo","test");
context1.global.get("foo").should.equal("test");
});
it('keeps local context local', function() {
var context1 = Context.get("1","flowA");
var context2 = Context.get("2","flowA");
should.not.exist(context1.get("foo"));
should.not.exist(context2.get("foo"));
context1.set("foo","test");
context1.get("foo").should.equal("test");
should.not.exist(context2.get("foo"));
});
it('flow context accessible to all flow nodes', function() {
var context1 = Context.get("1","flowA");
var context2 = Context.get("2","flowA");
should.not.exist(context1.flow.get("foo"));
should.not.exist(context2.flow.get("foo"));
context1.flow.set("foo","test");
context1.flow.get("foo").should.equal("test");
context2.flow.get("foo").should.equal("test");
});
it('flow context not shared to nodes on other flows', function() {
var context1 = Context.get("1","flowA");
var context2 = Context.get("2","flowB");
should.not.exist(context1.flow.get("foo"));
should.not.exist(context2.flow.get("foo"));
context1.flow.set("foo","test");
context1.flow.get("foo").should.equal("test");
should.not.exist(context2.flow.get("foo"));
});
it('global context shared to all nodes', function() {
var context1 = Context.get("1","flowA");
var context2 = Context.get("2","flowB");
should.not.exist(context1.global.get("foo"));
should.not.exist(context2.global.get("foo"));
context1.global.set("foo","test");
context1.global.get("foo").should.equal("test");
context2.global.get("foo").should.equal("test");
});
it('deletes context',function() {
var context = Context.get("1","flowA");
should.not.exist(context.get("foo"));
context.set("foo","abc");
context.get("foo").should.equal("abc");
return Context.delete("1","flowA").then(function(){
context = Context.get("1","flowA");
should.not.exist(context.get("foo"));
});
});
it('enumerates context keys - sync', function() {
var context = Context.get("1","flowA");
var keys = context.keys();
keys.should.be.an.Array();
keys.should.be.empty();
context.set("foo","bar");
keys = context.keys();
keys.should.have.length(1);
keys[0].should.equal("foo");
context.set("abc.def","bar");
keys = context.keys();
keys.should.have.length(2);
keys[1].should.equal("abc");
});
it('enumerates context keys - async', function(done) {
var context = Context.get("1","flowA");
var keys = context.keys(function(err,keys) {
keys.should.be.an.Array();
keys.should.be.empty();
context.set("foo","bar");
keys = context.keys(function(err,keys) {
keys.should.have.length(1);
keys[0].should.equal("foo");
context.set("abc.def","bar");
keys = context.keys(function(err,keys) {
keys.should.have.length(2);
keys[1].should.equal("abc");
done();
});
});
});
});
it('should enumerate only context keys when GlobalContext was given - sync', function() {
Context.init({functionGlobalContext: {foo:"bar"}});
Context.load().then(function(){
var context = Context.get("1","flowA");
context.global.set("foo2","bar2");
var keys = context.global.keys();
keys.should.have.length(2);
keys[0].should.equal("foo");
keys[1].should.equal("foo2");
});
});
it('should enumerate only context keys when GlobalContext was given - async', function(done) {
Context.init({functionGlobalContext: {foo:"bar"}});
Context.load().then(function(){
var context = Context.get("1","flowA");
context.global.set("foo2","bar2");
context.global.keys(function(err,keys) {
keys.should.have.length(2);
keys[0].should.equal("foo");
keys[1].should.equal("foo2");
done();
});
}).catch(done);
});
it('returns functionGlobalContext value if store value undefined', function() {
Context.init({functionGlobalContext: {foo:"bar"}});
Context.load().then(function(){
var context = Context.get("1","flowA");
var v = context.global.get('foo');
v.should.equal('bar');
});
})
});
describe('external context storage',function() {
var resourcesDir = path.resolve(path.join(__dirname,"..","resources","context"));
var sandbox = sinon.sandbox.create();
var stubGet = sandbox.stub();
var stubSet = sandbox.stub();
var stubKeys = sandbox.stub();
var stubDelete = sandbox.stub().returns(Promise.resolve());
var stubClean = sandbox.stub().returns(Promise.resolve());
var stubOpen = sandbox.stub().returns(Promise.resolve());
var stubClose = sandbox.stub().returns(Promise.resolve());
var stubGet2 = sandbox.stub();
var stubSet2 = sandbox.stub();
var stubKeys2 = sandbox.stub();
var stubDelete2 = sandbox.stub().returns(Promise.resolve());
var stubClean2 = sandbox.stub().returns(Promise.resolve());
var stubOpen2 = sandbox.stub().returns(Promise.resolve());
var stubClose2 = sandbox.stub().returns(Promise.resolve());
var testPlugin = function(config){
function Test(){}
Test.prototype.get = stubGet;
Test.prototype.set = stubSet;
Test.prototype.keys = stubKeys;
Test.prototype.delete = stubDelete;
Test.prototype.clean = stubClean;
Test.prototype.open = stubOpen;
Test.prototype.close = stubClose;
return new Test(config);
};
var testPlugin2 = function(config){
function Test2(){}
Test2.prototype.get = stubGet2;
Test2.prototype.set = stubSet2;
Test2.prototype.keys = stubKeys2;
Test2.prototype.delete = stubDelete2;
Test2.prototype.clean = stubClean2;
Test2.prototype.open = stubOpen2;
Test2.prototype.close = stubClose2;
return new Test2(config);
};
var contextStorage={
test:{
module: testPlugin,
config:{}
}
};
var contextDefaultStorage={
default: {
module: testPlugin2,
config:{}
},
test:{
module: testPlugin,
config:{}
}
};
var contextAlias={
default: "test",
test:{
module: testPlugin,
config:{}
}
};
var memoryStorage ={
memory:{
module: "memory"
}
};
afterEach(function() {
sandbox.reset();
return Context.clean({allNodes:{}}).then(function(){
return Context.close();
});
});
describe('load modules',function(){
it('should call open()', function() {
Context.init({contextStorage:contextDefaultStorage});
Context.load().then(function(){
stubOpen.called.should.be.true();
stubOpen2.called.should.be.true();
});
});
it('should load memory module', function() {
Context.init({contextStorage:{memory:{module:"memory"}}});
Context.load();
});
it('should load localfilesystem module', function() {
Context.init({contextStorage:{file:{module:"localfilesystem",config:{dir:resourcesDir}}}});
Context.load();
});
it('should accept special storage name', function(done) {
Context.init({
contextStorage:{
"#%&":{module:testPlugin},
\u3042:{module:testPlugin},
1:{module:testPlugin},
}
});
Context.load().then(function(){
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.set("sign","sign1","#%&",cb);
context.set("file","file2","\u3042",cb);
context.set("num","num3","1",cb);
stubSet.calledWithExactly("1:flow","sign","sign1",cb).should.be.true();
stubSet.calledWithExactly("1:flow","file","file2",cb).should.be.true();
stubSet.calledWithExactly("1:flow","num","num3",cb).should.be.true();
done();
}).catch(done);
});
it('should ignore reserved storage name `_`', function(done) {
Context.init({contextStorage:{_:{module:testPlugin}}});
Context.load().then(function(){
var context = Context.get("1","flow");
var cb = function(){}
context.set("foo","bar","_",cb);
context.get("foo","_",cb);
context.keys("_",cb);
stubSet.called.should.be.false();
stubGet.called.should.be.false();
stubKeys.called.should.be.false();
done();
}).catch(done);
});
it('should fail when using invalid default context', function(done) {
Context.init({contextStorage:{default:"noexist"}});
Context.load().then(function(){
done("An error was not thrown");
}).catch(function(){
done();
});
});
it('should fail for the storage with no module', function(done) {
Context.init({ contextStorage: { test: {}}});
Context.load().then(function(){
done("An error was not thrown");
}).catch(function(){
done();
});
});
it('should fail to load non-existent module', function(done) {
Context.init({contextStorage:{ file:{module:"nonexistent"} }});
Context.load().then(function(){
done("An error was not thrown");
}).catch(function(){
done();
});
});
});
describe('close modules',function(){
it('should call close()', function(done) {
Context.init({contextStorage:contextDefaultStorage});
Context.load().then(function(){
return Context.close().then(function(){
stubClose.called.should.be.true();
stubClose2.called.should.be.true();
done();
});
}).catch(done);
});
});
describe('store context',function() {
it('should store local property to external context storage',function(done) {
Context.init({contextStorage:contextStorage});
var cb = function(){done("An error occurred")}
Context.load().then(function(){
var context = Context.get("1","flow");
context.set("foo","bar","test",cb);
context.get("foo","test",cb);
context.keys("test",cb);
stubSet.calledWithExactly("1:flow","foo","bar",cb).should.be.true();
stubGet.calledWithExactly("1:flow","foo",cb).should.be.true();
stubKeys.calledWithExactly("1:flow",cb).should.be.true();
done();
}).catch(done);
});
it('should store flow property to external context storage',function(done) {
Context.init({contextStorage:contextStorage});
Context.load().then(function(){
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.flow.set("foo","bar","test",cb);
context.flow.get("foo","test",cb);
context.flow.keys("test",cb);
stubSet.calledWithExactly("flow","foo","bar",cb).should.be.true();
stubGet.calledWithExactly("flow","foo",cb).should.be.true();
stubKeys.calledWithExactly("flow",cb).should.be.true();
done();
}).catch(done);
});
it('should store global property to external context storage',function(done) {
Context.init({contextStorage:contextStorage});
Context.load().then(function(){
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.global.set("foo","bar","test",cb);
context.global.get("foo","test",cb);
context.global.keys("test",cb);
stubSet.calledWithExactly("global","foo","bar",cb).should.be.true();
stubGet.calledWith("global","foo").should.be.true();
stubKeys.calledWith("global").should.be.true();
done();
}).catch(done);
});
it('should store data to the default context when non-existent context storage was specified', function(done) {
Context.init({contextStorage:contextDefaultStorage});
Context.load().then(function(){
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.set("foo","bar","nonexist",cb);
context.get("foo","nonexist",cb);
context.keys("nonexist",cb);
stubGet.called.should.be.false();
stubSet.called.should.be.false();
stubKeys.called.should.be.false();
stubSet2.calledWithExactly("1:flow","foo","bar",cb).should.be.true();
stubGet2.calledWithExactly("1:flow","foo",cb).should.be.true();
stubKeys2.calledWithExactly("1:flow",cb).should.be.true();
done();
}).catch(done);
});
it('should use the default context', function(done) {
Context.init({contextStorage:contextDefaultStorage});
Context.load().then(function(){
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.set("foo","bar","defaultt",cb);
context.get("foo","default",cb);
context.keys("default",cb);
stubGet.called.should.be.false();
stubSet.called.should.be.false();
stubKeys.called.should.be.false();
stubSet2.calledWithExactly("1:flow","foo","bar",cb).should.be.true();
stubGet2.calledWithExactly("1:flow","foo",cb).should.be.true();
stubKeys2.calledWithExactly("1:flow",cb).should.be.true();
done();
}).catch(done);
});
it('should use the alias of default context', function(done) {
Context.init({contextStorage:contextDefaultStorage});
Context.load().then(function(){
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.set("foo","alias",cb);
context.get("foo",cb);
context.keys(cb);
stubGet.called.should.be.false();
stubSet.called.should.be.false();
stubKeys.called.should.be.false();
stubSet2.calledWithExactly("1:flow","foo","alias",cb).should.be.true();
stubGet2.calledWithExactly("1:flow","foo",cb).should.be.true();
stubKeys2.calledWithExactly("1:flow",cb).should.be.true();
done();
}).catch(done);
});
it('should use default as the alias of other context', function(done) {
Context.init({contextStorage:contextAlias});
Context.load().then(function(){
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.set("foo","alias",cb);
context.get("foo",cb);
context.keys(cb);
stubSet.calledWithExactly("1:flow","foo","alias",cb).should.be.true();
stubGet.calledWithExactly("1:flow","foo",cb).should.be.true();
stubKeys.calledWithExactly("1:flow",cb).should.be.true();
done();
}).catch(done);
});
it('should not throw an error using undefined storage for local context', function(done) {
Context.init({contextStorage:contextStorage});
Context.load().then(function(){
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.get("local","nonexist",cb);
done()
}).catch(done);
});
it('should throw an error using undefined storage for flow context', function(done) {
Context.init({contextStorage:contextStorage});
Context.load().then(function(){
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.flow.get("flow","nonexist",cb);
done();
}).catch(done);
});
it('should return functionGlobalContext value as a default - synchronous', function(done) {
var fGC = { "foo": 456 };
Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC });
Context.load().then(function() {
var context = Context.get("1","flow");
// Get foo - should be value from fGC
var v = context.global.get("foo");
v.should.equal(456);
// Update foo - should not touch fGC object
context.global.set("foo","new value");
fGC.foo.should.equal(456);
// Get foo - should be the updated value
v = context.global.get("foo");
v.should.equal("new value");
done();
}).catch(done);
})
it('should return functionGlobalContext value as a default - async', function(done) {
var fGC = { "foo": 456 };
Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC });
Context.load().then(function() {
var context = Context.get("1","flow");
// Get foo - should be value from fGC
context.global.get("foo", function(err, v) {
if (err) {
done(err)
} else {
v.should.equal(456);
// Update foo - should not touch fGC object
context.global.set("foo","new value", function(err) {
if (err) {
done(err)
} else {
fGC.foo.should.equal(456);
// Get foo - should be the updated value
context.global.get("foo", function(err, v) {
if (err) {
done(err)
} else {
v.should.equal("new value");
done();
}
});
}
});
}
});
}).catch(done);
})
});
describe('delete context',function(){
it('should not call delete() when external context storage is used', function(done) {
Context.init({contextStorage:contextDefaultStorage});
Context.load().then(function(){
Context.get("flowA");
return Context.delete("flowA").then(function(){
stubDelete.called.should.be.false();
stubDelete2.called.should.be.false();
done();
});
}).catch(done);
});
});
describe('clean context',function(){
it('should call clean()', function(done) {
Context.init({contextStorage:contextDefaultStorage});
Context.load().then(function(){
return Context.clean({allNodes:{}}).then(function(){
stubClean.calledWithExactly([]).should.be.true();
stubClean2.calledWithExactly([]).should.be.true();
done();
});
}).catch(done);
});
});
});
});

View File

@ -0,0 +1,378 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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 fs = require('fs-extra');
var path = require("path");
var LocalFileSystem = require('../../../../../red/runtime/nodes/context/localfilesystem');
var resourcesDir = path.resolve(path.join(__dirname,"..","resources","context"));
describe('localfilesystem',function() {
var context;
before(function() {
return fs.remove(resourcesDir);
});
beforeEach(function() {
context = LocalFileSystem({dir: resourcesDir, cache: false});
return context.open();
});
afterEach(function() {
return context.clean([]).then(function(){
return context.close().then(function(){
return fs.remove(resourcesDir);
});
});
});
describe('#get/set',function() {
it('should store property',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo","test",function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.equal("test");
done();
});
});
});
});
it('should store property - creates parent properties',function(done) {
context.set("nodeX","foo.bar","test",function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.eql({bar:"test"});
done();
});
});
});
it('should delete property',function(done) {
context.set("nodeX","foo.abc.bar1","test1",function(err){
context.set("nodeX","foo.abc.bar2","test2",function(err){
context.get("nodeX","foo.abc",function(err, value){
value.should.be.eql({bar1:"test1",bar2:"test2"});
context.set("nodeX","foo.abc.bar1",undefined,function(err){
context.get("nodeX","foo.abc",function(err, value){
value.should.be.eql({bar2:"test2"});
context.set("nodeX","foo.abc",undefined,function(err){
context.get("nodeX","foo.abc",function(err, value){
should.not.exist(value);
context.set("nodeX","foo",undefined,function(err){
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
done();
});
});
});
});
});
});
});
});
});
});
it('should not shared context with other scope', function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.get("nodeY","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo","testX",function(err){
context.set("nodeY","foo","testY",function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.equal("testX"),
context.get("nodeY","foo",function(err, value){
value.should.be.equal("testY");
done();
});
});
});
});
});
});
});
it('should store string',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo","bar",function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.String();
value.should.be.equal("bar");
context.set("nodeX","foo","1",function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.String();
value.should.be.equal("1");
done();
});
});
});
});
});
});
it('should store number',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo",1,function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.Number();
value.should.be.equal(1);
done();
});
});
});
});
it('should store null',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo",null,function(err){
context.get("nodeX","foo",function(err, value){
should(value).be.null();
done();
});
});
});
});
it('should store boolean',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo",true,function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.Boolean().and.true();
context.set("nodeX","foo",false,function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.Boolean().and.false();
done();
});
});
});
});
});
});
it('should store object',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo",{obj:"bar"},function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.Object();
value.should.eql({obj:"bar"});
done();
});
});
});
});
it('should store array',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo",["a","b","c"],function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.Array();
value.should.eql(["a","b","c"]);
context.get("nodeX","foo[1]",function(err, value){
value.should.be.String();
value.should.equal("b");
done();
});
});
});
});
});
it('should store array of arrays',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo",[["a","b","c"],[1,2,3,4],[true,false]],function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.Array();
value.should.have.length(3);
value[0].should.have.length(3);
value[1].should.have.length(4);
value[2].should.have.length(2);
context.get("nodeX","foo[1]",function(err, value){
value.should.be.Array();
value.should.have.length(4);
value.should.be.eql([1,2,3,4]);
done();
});
});
});
});
});
it('should store array of objects',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo",[{obj:"bar1"},{obj:"bar2"},{obj:"bar3"}],function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.Array();
value.should.have.length(3);
value[0].should.be.Object();
value[1].should.be.Object();
value[2].should.be.Object();
context.get("nodeX","foo[1]",function(err, value){
value.should.be.Object();
value.should.be.eql({obj:"bar2"});
done();
});
});
});
});
});
});
describe('#keys',function() {
it('should enumerate context keys', function(done) {
context.keys("nodeX",function(err, value){
value.should.be.an.Array();
value.should.be.empty();
context.set("nodeX","foo","bar",function(err){
context.keys("nodeX",function(err, value){
value.should.have.length(1);
value[0].should.equal("foo");
context.set("nodeX","abc.def","bar",function(err){
context.keys("nodeX",function(err, value){
value.should.have.length(2);
value[1].should.equal("abc");
done();
});
});
});
});
});
});
it('should enumerate context keys in each scopes', function(done) {
context.keys("nodeX",function(err, value){
value.should.be.an.Array();
value.should.be.empty();
context.keys("nodeY",function(err, value){
value.should.be.an.Array();
value.should.be.empty();
context.set("nodeX","foo","bar",function(err){
context.set("nodeY","hoge","piyo",function(err){
context.keys("nodeX",function(err, value){
value.should.have.length(1);
value[0].should.equal("foo");
context.keys("nodeY",function(err, value){
value.should.have.length(1);
value[0].should.equal("hoge");
done();
});
});
});
});
});
});
});
});
describe('#delete',function() {
it('should delete context',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.get("nodeY","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo","testX",function(err){
context.set("nodeY","foo","testY",function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.equal("testX");
context.get("nodeY","foo",function(err, value){
value.should.be.equal("testY");
context.delete("nodeX").then(function(){
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.get("nodeY","foo",function(err, value){
value.should.be.equal("testY");
done();
});
});
}).catch(done);
});
});
});
});
});
});
});
});
describe('#clean',function() {
it('should clean unnecessary context',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.get("nodeY","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo","testX",function(err){
context.set("nodeY","foo","testY",function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.equal("testX");
context.get("nodeY","foo",function(err, value){
value.should.be.equal("testY");
context.clean([]).then(function(){
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.get("nodeY","foo",function(err, value){
should.not.exist(value);
done();
});
});
});
});
});
});
});
});
});
});
it('should not clean active context',function(done) {
context.get("nodeX","foo",function(err, value){
should.not.exist(value);
context.get("nodeY","foo",function(err, value){
should.not.exist(value);
context.set("nodeX","foo","testX",function(err){
context.set("nodeY","foo","testY",function(err){
context.get("nodeX","foo",function(err, value){
value.should.be.equal("testX");
context.get("nodeY","foo",function(err, value){
value.should.be.equal("testY");
context.clean(["nodeX"]).then(function(){
context.get("nodeX","foo",function(err, value){
value.should.be.equal("testX");
context.get("nodeY","foo",function(err, value){
should.not.exist(value);
done();
});
});
});
});
});
});
});
});
});
});
});
});

View File

@ -0,0 +1,153 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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 Memory = require('../../../../../red/runtime/nodes/context/memory');
describe('memory',function() {
var context;
beforeEach(function() {
context = Memory({});
return context.open();
});
afterEach(function() {
return context.clean([]).then(function(){
return context.close();
});
});
describe('#get/set',function() {
it('should store property',function() {
should.not.exist(context.get("nodeX","foo"));
context.set("nodeX","foo","test");
context.get("nodeX","foo").should.equal("test");
});
it('should store property - creates parent properties',function() {
context.set("nodeX","foo.bar","test");
context.get("nodeX","foo").should.eql({bar:"test"});
});
it('should delete property',function() {
context.set("nodeX","foo.abc.bar1","test1");
context.set("nodeX","foo.abc.bar2","test2");
context.get("nodeX","foo.abc").should.eql({bar1:"test1",bar2:"test2"});
context.set("nodeX","foo.abc.bar1",undefined);
context.get("nodeX","foo.abc").should.eql({bar2:"test2"});
context.set("nodeX","foo.abc",undefined);
should.not.exist(context.get("nodeX","foo.abc"));
context.set("nodeX","foo",undefined);
should.not.exist(context.get("nodeX","foo"));
});
it('should not shared context with other scope', function() {
should.not.exist(context.get("nodeX","foo"));
should.not.exist(context.get("nodeY","foo"));
context.set("nodeX","foo","testX");
context.set("nodeY","foo","testY");
context.get("nodeX","foo").should.equal("testX");
context.get("nodeY","foo").should.equal("testY");
});
});
describe('#keys',function() {
it('should enumerate context keys', function() {
var keys = context.keys("nodeX");
keys.should.be.an.Array();
keys.should.be.empty();
context.set("nodeX","foo","bar");
keys = context.keys("nodeX");
keys.should.have.length(1);
keys[0].should.equal("foo");
context.set("nodeX","abc.def","bar");
keys = context.keys("nodeX");
keys.should.have.length(2);
keys[1].should.equal("abc");
});
it('should enumerate context keys in each scopes', function() {
var keysX = context.keys("nodeX");
keysX.should.be.an.Array();
keysX.should.be.empty();
var keysY = context.keys("nodeY");
keysY.should.be.an.Array();
keysY.should.be.empty();
context.set("nodeX","foo","bar");
context.set("nodeY","hoge","piyo");
keysX = context.keys("nodeX");
keysX.should.have.length(1);
keysX[0].should.equal("foo");
keysY = context.keys("nodeY");
keysY.should.have.length(1);
keysY[0].should.equal("hoge");
});
});
describe('#delete',function() {
it('should delete context',function() {
should.not.exist(context.get("nodeX","foo"));
should.not.exist(context.get("nodeY","foo"));
context.set("nodeX","foo","abc");
context.set("nodeY","foo","abc");
context.get("nodeX","foo").should.equal("abc");
context.get("nodeY","foo").should.equal("abc");
return context.delete("nodeX").then(function(){
should.not.exist(context.get("nodeX","foo"));
should.exist(context.get("nodeY","foo"));
});
});
});
describe('#clean',function() {
it('should clean unnecessary context',function() {
should.not.exist(context.get("nodeX","foo"));
should.not.exist(context.get("nodeY","foo"));
context.set("nodeX","foo","abc");
context.set("nodeY","foo","abc");
context.get("nodeX","foo").should.equal("abc");
context.get("nodeY","foo").should.equal("abc");
return context.clean([]).then(function(){
should.not.exist(context.get("nodeX","foo"));
should.not.exist(context.get("nodeY","foo"));
});
});
it('should not clean active context',function() {
should.not.exist(context.get("nodeX","foo"));
should.not.exist(context.get("nodeY","foo"));
context.set("nodeX","foo","abc");
context.set("nodeY","foo","abc");
context.get("nodeX","foo").should.equal("abc");
context.get("nodeY","foo").should.equal("abc");
return context.clean(["nodeX"]).then(function(){
should.exist(context.get("nodeX","foo"));
should.not.exist(context.get("nodeY","foo"));
});
});
});
});

View File

@ -1,144 +0,0 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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 Context = require("../../../../red/runtime/nodes/context");
describe('context', function() {
beforeEach(function() {
Context.init({});
});
afterEach(function() {
Context.clean({allNodes:{}});
});
it('stores local property',function() {
var context1 = Context.get("1","flowA");
should.not.exist(context1.get("foo"));
context1.set("foo","test");
context1.get("foo").should.eql("test");
});
it('stores local property - creates parent properties',function() {
var context1 = Context.get("1","flowA");
context1.set("foo.bar","test");
context1.get("foo").should.eql({bar:"test"});
});
it('deletes local property',function() {
var context1 = Context.get("1","flowA");
context1.set("foo.abc.bar1","test1");
context1.set("foo.abc.bar2","test2");
context1.get("foo.abc").should.eql({bar1:"test1",bar2:"test2"});
context1.set("foo.abc.bar1",undefined);
context1.get("foo.abc").should.eql({bar2:"test2"});
context1.set("foo.abc",undefined);
should.not.exist(context1.get("foo.abc"));
context1.set("foo",undefined);
should.not.exist(context1.get("foo"));
});
it('stores flow property',function() {
var context1 = Context.get("1","flowA");
should.not.exist(context1.flow.get("foo"));
context1.flow.set("foo","test");
context1.flow.get("foo").should.eql("test");
});
it('stores global property',function() {
var context1 = Context.get("1","flowA");
should.not.exist(context1.global.get("foo"));
context1.global.set("foo","test");
context1.global.get("foo").should.eql("test");
});
it('keeps local context local', function() {
var context1 = Context.get("1","flowA");
var context2 = Context.get("2","flowA");
should.not.exist(context1.get("foo"));
should.not.exist(context2.get("foo"));
context1.set("foo","test");
context1.get("foo").should.eql("test");
should.not.exist(context2.get("foo"));
});
it('flow context accessible to all flow nodes', function() {
var context1 = Context.get("1","flowA");
var context2 = Context.get("2","flowA");
should.not.exist(context1.flow.get("foo"));
should.not.exist(context2.flow.get("foo"));
context1.flow.set("foo","test");
context1.flow.get("foo").should.eql("test");
context2.flow.get("foo").should.eql("test");
});
it('flow context not shared to nodes on other flows', function() {
var context1 = Context.get("1","flowA");
var context2 = Context.get("2","flowB");
should.not.exist(context1.flow.get("foo"));
should.not.exist(context2.flow.get("foo"));
context1.flow.set("foo","test");
context1.flow.get("foo").should.eql("test");
should.not.exist(context2.flow.get("foo"));
});
it('global context shared to all nodes', function() {
var context1 = Context.get("1","flowA");
var context2 = Context.get("2","flowB");
should.not.exist(context1.global.get("foo"));
should.not.exist(context2.global.get("foo"));
context1.global.set("foo","test");
context1.global.get("foo").should.eql("test");
context2.global.get("foo").should.eql("test");
});
it('deletes context',function() {
var context = Context.get("1","flowA");
should.not.exist(context.get("foo"));
context.set("foo","abc");
context.get("foo").should.eql("abc");
Context.delete("1","flowA");
context = Context.get("1","flowA");
should.not.exist(context.get("foo"));
})
it('enumerates context keys', function() {
var context = Context.get("1","flowA");
var keys = context.keys();
keys.should.be.an.Array();
keys.should.be.empty();
context.set("foo","bar");
keys = context.keys();
keys.should.have.length(1);
keys[0].should.eql("foo");
context.set("abc.def","bar");
keys = context.keys();
keys.should.have.length(2);
keys[1].should.eql("abc");
})
});

View File

@ -35,10 +35,17 @@ describe('flows/util', function() {
}); });
describe('#mapEnvVarProperties',function() { describe('#mapEnvVarProperties',function() {
it('handles ENV substitutions in an object', function() { before(function() {
process.env.foo1 = "bar1"; process.env.foo1 = "bar1";
process.env.foo2 = "bar2"; process.env.foo2 = "bar2";
process.env.foo3 = "bar3"; process.env.foo3 = "bar3";
})
after(function() {
delete process.env.foo1;
delete process.env.foo2;
delete process.env.foo3;
})
it('handles ENV substitutions in an object - $()', function() {
var foo = {a:"$(foo1)",b:"$(foo2)",c:{d:"$(foo3)"}}; var foo = {a:"$(foo1)",b:"$(foo2)",c:{d:"$(foo3)"}};
for (var p in foo) { for (var p in foo) {
if (foo.hasOwnProperty(p)) { if (foo.hasOwnProperty(p)) {
@ -47,6 +54,15 @@ describe('flows/util', function() {
} }
foo.should.eql({ a: 'bar1', b: 'bar2', c: { d: 'bar3' } } ); foo.should.eql({ a: 'bar1', b: 'bar2', c: { d: 'bar3' } } );
}); });
it('handles ENV substitutions in an object - ${}', function() {
var foo = {a:"${foo1}",b:"${foo2}",c:{d:"${foo3}"}};
for (var p in foo) {
if (foo.hasOwnProperty(p)) {
flowUtil.mapEnvVarProperties(foo,p);
}
}
foo.should.eql({ a: 'bar1', b: 'bar2', c: { d: 'bar3' } } );
});
}); });
describe('#diffNodes',function() { describe('#diffNodes',function() {

View File

@ -79,6 +79,7 @@ describe("red/nodes/registry/loader",function() {
var result = {}; var result = {};
result["node-red"] = { result["node-red"] = {
"name": "node-red", "name": "node-red",
"version": "1.2.3",
"nodes": { "nodes": {
"TestNode1": { "TestNode1": {
"file": path.join(resourcesDir,"TestNode1","TestNode1.js"), "file": path.join(resourcesDir,"TestNode1","TestNode1.js"),
@ -91,34 +92,39 @@ describe("red/nodes/registry/loader",function() {
})); }));
stubs.push(sinon.stub(registry,"saveNodeList", function(){ return })); stubs.push(sinon.stub(registry,"saveNodeList", function(){ return }));
stubs.push(sinon.stub(registry,"addNodeSet", function(){ return })); stubs.push(sinon.stub(registry,"addModule", function(){ return }));
// This module isn't already loaded // This module isn't already loaded
stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; })); stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; }));
stubs.push(sinon.stub(nodes,"registerType")); stubs.push(sinon.stub(nodes,"registerType"));
loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}}); loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.load().then(function(result) { loader.load().then(function(result) {
registry.addNodeSet.called.should.be.true(); registry.addModule.called.should.be.true();
registry.addNodeSet.lastCall.args[0].should.eql("node-red/TestNode1"); var module = registry.addModule.lastCall.args[0];
registry.addNodeSet.lastCall.args[1].should.have.a.property('id',"node-red/TestNode1"); module.should.have.property("name","node-red");
registry.addNodeSet.lastCall.args[1].should.have.a.property('module',"node-red"); module.should.have.property("version","1.2.3");
registry.addNodeSet.lastCall.args[1].should.have.a.property('enabled',true); module.should.have.property("nodes");
registry.addNodeSet.lastCall.args[1].should.have.a.property('loaded',true); module.nodes.should.have.property("TestNode1");
registry.addNodeSet.lastCall.args[1].should.have.a.property('version',undefined); module.nodes.TestNode1.should.have.property("id","node-red/TestNode1");
registry.addNodeSet.lastCall.args[1].should.have.a.property('types'); module.nodes.TestNode1.should.have.property("module","node-red");
registry.addNodeSet.lastCall.args[1].types.should.have.a.length(1); module.nodes.TestNode1.should.have.property("name","TestNode1");
registry.addNodeSet.lastCall.args[1].types[0].should.eql('test-node-1'); module.nodes.TestNode1.should.have.property("file");
registry.addNodeSet.lastCall.args[1].should.have.a.property('config'); module.nodes.TestNode1.should.have.property("template");
registry.addNodeSet.lastCall.args[1].should.have.a.property('help'); module.nodes.TestNode1.should.have.property("enabled",true);
registry.addNodeSet.lastCall.args[1].should.have.a.property('namespace','node-red'); module.nodes.TestNode1.should.have.property("loaded",true);
registry.addNodeSet.lastCall.args[1].should.not.have.a.property('err'); module.nodes.TestNode1.should.have.property("types");
module.nodes.TestNode1.types.should.have.a.length(1);
module.nodes.TestNode1.types[0].should.eql('test-node-1');
module.nodes.TestNode1.should.have.property("config");
module.nodes.TestNode1.should.have.property("help");
module.nodes.TestNode1.should.have.property("namespace","node-red");
nodes.registerType.calledOnce.should.be.true(); nodes.registerType.calledOnce.should.be.true();
nodes.registerType.lastCall.args[0].should.eql('node-red/TestNode1'); nodes.registerType.lastCall.args[0].should.eql('node-red/TestNode1');
nodes.registerType.lastCall.args[1].should.eql('test-node-1'); nodes.registerType.lastCall.args[1].should.eql('test-node-1');
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -128,6 +134,7 @@ describe("red/nodes/registry/loader",function() {
var result = {}; var result = {};
result["node-red"] = { result["node-red"] = {
"name": "node-red", "name": "node-red",
"version": "4.5.6",
"nodes": { "nodes": {
"MultipleNodes1": { "MultipleNodes1": {
"file": path.join(resourcesDir,"MultipleNodes1","MultipleNodes1.js"), "file": path.join(resourcesDir,"MultipleNodes1","MultipleNodes1.js"),
@ -140,27 +147,33 @@ describe("red/nodes/registry/loader",function() {
})); }));
stubs.push(sinon.stub(registry,"saveNodeList", function(){ return })); stubs.push(sinon.stub(registry,"saveNodeList", function(){ return }));
stubs.push(sinon.stub(registry,"addNodeSet", function(){ return })); stubs.push(sinon.stub(registry,"addModule", function(){ return }));
// This module isn't already loaded // This module isn't already loaded
stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; })); stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; }));
stubs.push(sinon.stub(nodes,"registerType")); stubs.push(sinon.stub(nodes,"registerType"));
loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}}); loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.load().then(function(result) { loader.load().then(function(result) {
registry.addNodeSet.called.should.be.true();
registry.addNodeSet.lastCall.args[0].should.eql("node-red/MultipleNodes1"); registry.addModule.called.should.be.true();
registry.addNodeSet.lastCall.args[1].should.have.a.property('id',"node-red/MultipleNodes1"); var module = registry.addModule.lastCall.args[0];
registry.addNodeSet.lastCall.args[1].should.have.a.property('module',"node-red"); module.should.have.property("name","node-red");
registry.addNodeSet.lastCall.args[1].should.have.a.property('enabled',true); module.should.have.property("version","4.5.6");
registry.addNodeSet.lastCall.args[1].should.have.a.property('loaded',true); module.should.have.property("nodes");
registry.addNodeSet.lastCall.args[1].should.have.a.property('version',undefined); module.nodes.should.have.property("MultipleNodes1");
registry.addNodeSet.lastCall.args[1].should.have.a.property('types'); module.nodes.MultipleNodes1.should.have.property("id","node-red/MultipleNodes1");
registry.addNodeSet.lastCall.args[1].types.should.have.a.length(2); module.nodes.MultipleNodes1.should.have.property("module","node-red");
registry.addNodeSet.lastCall.args[1].types[0].should.eql('test-node-multiple-1a'); module.nodes.MultipleNodes1.should.have.property("name","MultipleNodes1");
registry.addNodeSet.lastCall.args[1].types[1].should.eql('test-node-multiple-1b'); module.nodes.MultipleNodes1.should.have.property("file");
registry.addNodeSet.lastCall.args[1].should.have.a.property('config'); module.nodes.MultipleNodes1.should.have.property("template");
registry.addNodeSet.lastCall.args[1].should.have.a.property('help'); module.nodes.MultipleNodes1.should.have.property("enabled",true);
registry.addNodeSet.lastCall.args[1].should.have.a.property('namespace','node-red'); module.nodes.MultipleNodes1.should.have.property("loaded",true);
registry.addNodeSet.lastCall.args[1].should.not.have.a.property('err'); module.nodes.MultipleNodes1.should.have.property("types");
module.nodes.MultipleNodes1.types.should.have.a.length(2);
module.nodes.MultipleNodes1.types[0].should.eql('test-node-multiple-1a');
module.nodes.MultipleNodes1.types[1].should.eql('test-node-multiple-1b');
module.nodes.MultipleNodes1.should.have.property("config");
module.nodes.MultipleNodes1.should.have.property("help");
module.nodes.MultipleNodes1.should.have.property("namespace","node-red");
nodes.registerType.calledTwice.should.be.true(); nodes.registerType.calledTwice.should.be.true();
nodes.registerType.firstCall.args[0].should.eql('node-red/MultipleNodes1'); nodes.registerType.firstCall.args[0].should.eql('node-red/MultipleNodes1');
@ -168,8 +181,9 @@ describe("red/nodes/registry/loader",function() {
nodes.registerType.secondCall.args[0].should.eql('node-red/MultipleNodes1'); nodes.registerType.secondCall.args[0].should.eql('node-red/MultipleNodes1');
nodes.registerType.secondCall.args[1].should.eql('test-node-multiple-1b'); nodes.registerType.secondCall.args[1].should.eql('test-node-multiple-1b');
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -180,6 +194,7 @@ describe("red/nodes/registry/loader",function() {
var result = {}; var result = {};
result["node-red"] = { result["node-red"] = {
"name": "node-red", "name": "node-red",
"version":"2.4.6",
"nodes": { "nodes": {
"TestNode2": { "TestNode2": {
"file": path.join(resourcesDir,"TestNode2","TestNode2.js"), "file": path.join(resourcesDir,"TestNode2","TestNode2.js"),
@ -192,34 +207,41 @@ describe("red/nodes/registry/loader",function() {
})); }));
stubs.push(sinon.stub(registry,"saveNodeList", function(){ return })); stubs.push(sinon.stub(registry,"saveNodeList", function(){ return }));
stubs.push(sinon.stub(registry,"addNodeSet", function(){ return })); stubs.push(sinon.stub(registry,"addModule", function(){ return }));
// This module isn't already loaded // This module isn't already loaded
stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; })); stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; }));
stubs.push(sinon.stub(nodes,"registerType")); stubs.push(sinon.stub(nodes,"registerType"));
loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}}); loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.load().then(function(result) { loader.load().then(function(result) {
registry.addNodeSet.called.should.be.true();
registry.addNodeSet.lastCall.args[0].should.eql("node-red/TestNode2"); registry.addModule.called.should.be.true();
registry.addNodeSet.lastCall.args[1].should.have.a.property('id',"node-red/TestNode2"); var module = registry.addModule.lastCall.args[0];
registry.addNodeSet.lastCall.args[1].should.have.a.property('module',"node-red"); module.should.have.property("name","node-red");
registry.addNodeSet.lastCall.args[1].should.have.a.property('enabled',true); module.should.have.property("version","2.4.6");
registry.addNodeSet.lastCall.args[1].should.have.a.property('loaded',true); module.should.have.property("nodes");
registry.addNodeSet.lastCall.args[1].should.have.a.property('version',undefined); module.nodes.should.have.property("TestNode2");
registry.addNodeSet.lastCall.args[1].should.have.a.property('types'); module.nodes.TestNode2.should.have.property("id","node-red/TestNode2");
registry.addNodeSet.lastCall.args[1].types.should.have.a.length(1); module.nodes.TestNode2.should.have.property("module","node-red");
registry.addNodeSet.lastCall.args[1].types[0].should.eql('test-node-2'); module.nodes.TestNode2.should.have.property("name","TestNode2");
registry.addNodeSet.lastCall.args[1].should.have.a.property('config'); module.nodes.TestNode2.should.have.property("file");
registry.addNodeSet.lastCall.args[1].should.have.a.property('help'); module.nodes.TestNode2.should.have.property("template");
registry.addNodeSet.lastCall.args[1].should.have.a.property('namespace','node-red'); module.nodes.TestNode2.should.have.property("enabled",true);
registry.addNodeSet.lastCall.args[1].should.not.have.a.property('err'); module.nodes.TestNode2.should.have.property("loaded",true);
module.nodes.TestNode2.should.have.property("types");
module.nodes.TestNode2.types.should.have.a.length(1);
module.nodes.TestNode2.types[0].should.eql('test-node-2');
module.nodes.TestNode2.should.have.property("config");
module.nodes.TestNode2.should.have.property("help");
module.nodes.TestNode2.should.have.property("namespace","node-red");
module.nodes.TestNode2.should.not.have.property('err');
nodes.registerType.calledOnce.should.be.true(); nodes.registerType.calledOnce.should.be.true();
nodes.registerType.lastCall.args[0].should.eql('node-red/TestNode2'); nodes.registerType.lastCall.args[0].should.eql('node-red/TestNode2');
nodes.registerType.lastCall.args[1].should.eql('test-node-2'); nodes.registerType.lastCall.args[1].should.eql('test-node-2');
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -230,6 +252,7 @@ describe("red/nodes/registry/loader",function() {
var result = {}; var result = {};
result["node-red"] = { result["node-red"] = {
"name": "node-red", "name": "node-red",
"version":"1.2.3",
"nodes": { "nodes": {
"TestNode3": { "TestNode3": {
"file": path.join(resourcesDir,"TestNode3","TestNode3.js"), "file": path.join(resourcesDir,"TestNode3","TestNode3.js"),
@ -242,32 +265,38 @@ describe("red/nodes/registry/loader",function() {
})); }));
stubs.push(sinon.stub(registry,"saveNodeList", function(){ return })); stubs.push(sinon.stub(registry,"saveNodeList", function(){ return }));
stubs.push(sinon.stub(registry,"addNodeSet", function(){ return })); stubs.push(sinon.stub(registry,"addModule", function(){ return }));
// This module isn't already loaded // This module isn't already loaded
stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; })); stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; }));
stubs.push(sinon.stub(nodes,"registerType")); stubs.push(sinon.stub(nodes,"registerType"));
loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}}); loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.load().then(function(result) { loader.load().then(function(result) {
registry.addNodeSet.called.should.be.true(); registry.addModule.called.should.be.true();
registry.addNodeSet.lastCall.args[0].should.eql("node-red/TestNode3"); var module = registry.addModule.lastCall.args[0];
registry.addNodeSet.lastCall.args[1].should.have.a.property('id',"node-red/TestNode3"); module.should.have.property("name","node-red");
registry.addNodeSet.lastCall.args[1].should.have.a.property('module',"node-red"); module.should.have.property("version","1.2.3");
registry.addNodeSet.lastCall.args[1].should.have.a.property('enabled',true); module.should.have.property("nodes");
registry.addNodeSet.lastCall.args[1].should.have.a.property('loaded',false); module.nodes.should.have.property("TestNode3");
registry.addNodeSet.lastCall.args[1].should.have.a.property('version',undefined); module.nodes.TestNode3.should.have.property("id","node-red/TestNode3");
registry.addNodeSet.lastCall.args[1].should.have.a.property('types'); module.nodes.TestNode3.should.have.property("module","node-red");
registry.addNodeSet.lastCall.args[1].types.should.have.a.length(1); module.nodes.TestNode3.should.have.property("name","TestNode3");
registry.addNodeSet.lastCall.args[1].types[0].should.eql('test-node-3'); module.nodes.TestNode3.should.have.property("file");
registry.addNodeSet.lastCall.args[1].should.have.a.property('config'); module.nodes.TestNode3.should.have.property("template");
registry.addNodeSet.lastCall.args[1].should.have.a.property('help'); module.nodes.TestNode3.should.have.property("enabled",true);
registry.addNodeSet.lastCall.args[1].should.have.a.property('namespace','node-red'); module.nodes.TestNode3.should.have.property("loaded",false);
registry.addNodeSet.lastCall.args[1].should.have.a.property('err','fail'); module.nodes.TestNode3.should.have.property("types");
module.nodes.TestNode3.types.should.have.a.length(1);
module.nodes.TestNode3.types[0].should.eql('test-node-3');
module.nodes.TestNode3.should.have.property("config");
module.nodes.TestNode3.should.have.property("help");
module.nodes.TestNode3.should.have.property("namespace","node-red");
module.nodes.TestNode3.should.have.property('err','fail');
nodes.registerType.calledOnce.should.be.false(); nodes.registerType.called.should.be.false();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -277,6 +306,7 @@ describe("red/nodes/registry/loader",function() {
var result = {}; var result = {};
result["node-red"] = { result["node-red"] = {
"name": "node-red", "name": "node-red",
"version":"1.2.3",
"nodes": { "nodes": {
"DoesNotExist": { "DoesNotExist": {
"file": path.join(resourcesDir,"doesnotexist"), "file": path.join(resourcesDir,"doesnotexist"),
@ -289,31 +319,37 @@ describe("red/nodes/registry/loader",function() {
})); }));
stubs.push(sinon.stub(registry,"saveNodeList", function(){ return })); stubs.push(sinon.stub(registry,"saveNodeList", function(){ return }));
stubs.push(sinon.stub(registry,"addNodeSet", function(){ return })); stubs.push(sinon.stub(registry,"addModule", function(){ return }));
// This module isn't already loaded // This module isn't already loaded
stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; })); stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; }));
stubs.push(sinon.stub(nodes,"registerType")); stubs.push(sinon.stub(nodes,"registerType"));
loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}}); loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.load().then(function(result) { loader.load().then(function(result) {
registry.addNodeSet.called.should.be.true(); registry.addModule.called.should.be.true();
registry.addNodeSet.lastCall.args[0].should.eql("node-red/DoesNotExist"); var module = registry.addModule.lastCall.args[0];
registry.addNodeSet.lastCall.args[1].should.have.a.property('id',"node-red/DoesNotExist"); module.should.have.property("name","node-red");
registry.addNodeSet.lastCall.args[1].should.have.a.property('module',"node-red"); module.should.have.property("version","1.2.3");
registry.addNodeSet.lastCall.args[1].should.have.a.property('enabled',true); module.should.have.property("nodes");
registry.addNodeSet.lastCall.args[1].should.have.a.property('loaded',false); module.nodes.should.have.property("DoesNotExist");
registry.addNodeSet.lastCall.args[1].should.have.a.property('version',undefined); module.nodes.DoesNotExist.should.have.property("id","node-red/DoesNotExist");
registry.addNodeSet.lastCall.args[1].should.have.a.property('types'); module.nodes.DoesNotExist.should.have.property("module","node-red");
registry.addNodeSet.lastCall.args[1].types.should.have.a.length(0); module.nodes.DoesNotExist.should.have.property("name","DoesNotExist");
registry.addNodeSet.lastCall.args[1].should.not.have.a.property('config'); module.nodes.DoesNotExist.should.have.property("file");
registry.addNodeSet.lastCall.args[1].should.not.have.a.property('help'); module.nodes.DoesNotExist.should.have.property("template");
registry.addNodeSet.lastCall.args[1].should.not.have.a.property('namespace','node-red'); module.nodes.DoesNotExist.should.have.property("enabled",true);
registry.addNodeSet.lastCall.args[1].should.have.a.property('err'); module.nodes.DoesNotExist.should.have.property("loaded",false);
module.nodes.DoesNotExist.should.have.property("types");
module.nodes.DoesNotExist.types.should.have.a.length(0);
module.nodes.DoesNotExist.should.not.have.property("config");
module.nodes.DoesNotExist.should.not.have.property("help");
module.nodes.DoesNotExist.should.not.have.property("namespace","node-red");
module.nodes.DoesNotExist.should.have.property('err');
nodes.registerType.calledOnce.should.be.false(); nodes.registerType.called.should.be.false();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -323,6 +359,7 @@ describe("red/nodes/registry/loader",function() {
var result = {}; var result = {};
result["node-red"] = { result["node-red"] = {
"name": "node-red", "name": "node-red",
"version": "1.2.3",
"nodes": { "nodes": {
"DuffNode": { "DuffNode": {
"file": path.join(resourcesDir,"DuffNode","DuffNode.js"), "file": path.join(resourcesDir,"DuffNode","DuffNode.js"),
@ -335,32 +372,39 @@ describe("red/nodes/registry/loader",function() {
})); }));
stubs.push(sinon.stub(registry,"saveNodeList", function(){ return })); stubs.push(sinon.stub(registry,"saveNodeList", function(){ return }));
stubs.push(sinon.stub(registry,"addNodeSet", function(){ return })); stubs.push(sinon.stub(registry,"addModule", function(){ return }));
// This module isn't already loaded // This module isn't already loaded
stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; })); stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; }));
stubs.push(sinon.stub(nodes,"registerType")); stubs.push(sinon.stub(nodes,"registerType"));
loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}}); loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.load().then(function(result) { loader.load().then(function(result) {
registry.addNodeSet.called.should.be.true();
registry.addNodeSet.lastCall.args[0].should.eql("node-red/DuffNode");
registry.addNodeSet.lastCall.args[1].should.have.a.property('id',"node-red/DuffNode");
registry.addNodeSet.lastCall.args[1].should.have.a.property('module',"node-red");
registry.addNodeSet.lastCall.args[1].should.have.a.property('enabled',true);
registry.addNodeSet.lastCall.args[1].should.have.a.property('loaded',false);
registry.addNodeSet.lastCall.args[1].should.have.a.property('version',undefined);
registry.addNodeSet.lastCall.args[1].should.have.a.property('types');
registry.addNodeSet.lastCall.args[1].types.should.have.a.length(0);
registry.addNodeSet.lastCall.args[1].should.not.have.a.property('config');
registry.addNodeSet.lastCall.args[1].should.not.have.a.property('help');
registry.addNodeSet.lastCall.args[1].should.not.have.a.property('namespace','node-red');
registry.addNodeSet.lastCall.args[1].should.have.a.property('err');
registry.addNodeSet.lastCall.args[1].err.should.endWith("DuffNode.html does not exist");
nodes.registerType.calledOnce.should.be.false(); registry.addModule.called.should.be.true();
var module = registry.addModule.lastCall.args[0];
module.should.have.property("name","node-red");
module.should.have.property("version","1.2.3");
module.should.have.property("nodes");
module.nodes.should.have.property("DuffNode");
module.nodes.DuffNode.should.have.property("id","node-red/DuffNode");
module.nodes.DuffNode.should.have.property("module","node-red");
module.nodes.DuffNode.should.have.property("name","DuffNode");
module.nodes.DuffNode.should.have.property("file");
module.nodes.DuffNode.should.have.property("template");
module.nodes.DuffNode.should.have.property("enabled",true);
module.nodes.DuffNode.should.have.property("loaded",false);
module.nodes.DuffNode.should.have.property("types");
module.nodes.DuffNode.types.should.have.a.length(0);
module.nodes.DuffNode.should.not.have.property("config");
module.nodes.DuffNode.should.not.have.property("help");
module.nodes.DuffNode.should.not.have.property("namespace","node-red");
module.nodes.DuffNode.should.have.property('err');
module.nodes.DuffNode.err.should.endWith("DuffNode.html does not exist");
nodes.registerType.called.should.be.false();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -379,7 +423,7 @@ describe("red/nodes/registry/loader",function() {
stubs.push(sinon.stub(registry,"getModuleInfo",function(){return{}})); stubs.push(sinon.stub(registry,"getModuleInfo",function(){return{}}));
loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}}); loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.addModule("test-module").otherwise(function(err) { loader.addModule("test-module").catch(function(err) {
err.code.should.eql("module_already_loaded"); err.code.should.eql("module_already_loaded");
done(); done();
}); });
@ -390,7 +434,7 @@ describe("red/nodes/registry/loader",function() {
throw new Error("failure"); throw new Error("failure");
})); }));
loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}}); loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.addModule("test-module").otherwise(function(err) { loader.addModule("test-module").catch(function(err) {
err.message.should.eql("failure"); err.message.should.eql("failure");
done(); done();
}); });
@ -419,29 +463,36 @@ describe("red/nodes/registry/loader",function() {
})); }));
stubs.push(sinon.stub(registry,"saveNodeList", function(){ return "a node list" })); stubs.push(sinon.stub(registry,"saveNodeList", function(){ return "a node list" }));
stubs.push(sinon.stub(registry,"addNodeSet", function(){ return })); stubs.push(sinon.stub(registry,"addModule", function(){ return }));
stubs.push(sinon.stub(nodes,"registerType")); stubs.push(sinon.stub(nodes,"registerType"));
loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}}); loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
loader.addModule("TestNodeModule").then(function(result) { loader.addModule("TestNodeModule").then(function(result) {
result.should.eql("a node list"); result.should.eql("a node list");
registry.addNodeSet.calledOnce.should.be.true();
registry.addNodeSet.lastCall.args[0].should.eql("TestNodeModule/TestNode1"); registry.addModule.called.should.be.true();
registry.addNodeSet.lastCall.args[1].should.have.a.property('id',"TestNodeModule/TestNode1"); var module = registry.addModule.lastCall.args[0];
registry.addNodeSet.lastCall.args[1].should.have.a.property('module',"TestNodeModule"); module.should.have.property("name","TestNodeModule");
registry.addNodeSet.lastCall.args[1].should.have.a.property('enabled',true); module.should.have.property("version","1.2.3");
registry.addNodeSet.lastCall.args[1].should.have.a.property('loaded',true); module.should.have.property("nodes");
registry.addNodeSet.lastCall.args[1].should.have.a.property('version',"1.2.3"); module.nodes.should.have.property("TestNode1");
registry.addNodeSet.lastCall.args[1].should.have.a.property('types'); module.nodes.TestNode1.should.have.property("id","TestNodeModule/TestNode1");
registry.addNodeSet.lastCall.args[1].types.should.have.a.length(1); module.nodes.TestNode1.should.have.property("module","TestNodeModule");
registry.addNodeSet.lastCall.args[1].types[0].should.eql('test-node-mod-1'); module.nodes.TestNode1.should.have.property("name","TestNode1");
registry.addNodeSet.lastCall.args[1].should.have.a.property('config'); module.nodes.TestNode1.should.have.property("file");
registry.addNodeSet.lastCall.args[1].should.have.a.property('help'); module.nodes.TestNode1.should.have.property("template");
registry.addNodeSet.lastCall.args[1].should.have.a.property('namespace','TestNodeModule'); module.nodes.TestNode1.should.have.property("enabled",true);
registry.addNodeSet.lastCall.args[1].should.not.have.a.property('err'); module.nodes.TestNode1.should.have.property("loaded",true);
module.nodes.TestNode1.should.have.property("types");
module.nodes.TestNode1.types.should.have.a.length(1);
module.nodes.TestNode1.types[0].should.eql('test-node-mod-1');
module.nodes.TestNode1.should.have.property("config");
module.nodes.TestNode1.should.have.property("help");
module.nodes.TestNode1.should.have.property("namespace","TestNodeModule");
module.nodes.TestNode1.should.not.have.property('err');
nodes.registerType.calledOnce.should.be.true(); nodes.registerType.calledOnce.should.be.true();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -469,15 +520,15 @@ describe("red/nodes/registry/loader",function() {
})); }));
stubs.push(sinon.stub(registry,"saveNodeList", function(){ return "a node list" })); stubs.push(sinon.stub(registry,"saveNodeList", function(){ return "a node list" }));
stubs.push(sinon.stub(registry,"addNodeSet", function(){ return })); stubs.push(sinon.stub(registry,"addModule", function(){ return }));
stubs.push(sinon.stub(nodes,"registerType")); stubs.push(sinon.stub(nodes,"registerType"));
loader.init({log:{"_":function(){},warn:function(){}},nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},version: function() { return "0.12.0"}, settings:{available:function(){return true;}}}); loader.init({log:{"_":function(){},warn:function(){}},nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},version: function() { return "0.12.0"}, settings:{available:function(){return true;}}});
loader.addModule("TestNodeModule").then(function(result) { loader.addModule("TestNodeModule").then(function(result) {
result.should.eql("a node list"); result.should.eql("a node list");
registry.addNodeSet.called.should.be.false(); registry.addModule.called.should.be.false();
nodes.registerType.called.should.be.false(); nodes.registerType.called.should.be.false();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -498,7 +549,7 @@ describe("red/nodes/registry/loader",function() {
node.enabled.should.be.false(); node.enabled.should.be.false();
nodes.registerType.called.should.be.false(); nodes.registerType.called.should.be.false();
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -517,7 +568,7 @@ describe("red/nodes/registry/loader",function() {
node.err.toString().should.eql("Error: fail to require (line:1)"); node.err.toString().should.eql("Error: fail to require (line:1)");
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });

View File

@ -166,7 +166,15 @@ describe("red/nodes/registry/localfilesystem",function() {
}}, }},
settings:{coreNodesDir:resourcesDir} settings:{coreNodesDir:resourcesDir}
}); });
localfilesystem.getNodeFiles(true); var list = localfilesystem.getNodeFiles(true);
list.should.have.property("node-red");
list["node-red"].should.have.property("icons");
list["node-red"].icons.should.have.length(2);
//list["node-red"].icons[1].should.have.property("path",path.join(__dirname,"resources/local/NestedDirectoryNode/NestedNode/icons"))
list["node-red"].icons[1].should.have.property("icons");
list["node-red"].icons[1].icons.should.have.length(1);
list["node-red"].icons[1].icons[0].should.eql("arrow-in.png");
done();
}); });
it("scans icons dir in library",function(done) { it("scans icons dir in library",function(done) {
var count = 0; var count = 0;
@ -188,7 +196,15 @@ describe("red/nodes/registry/localfilesystem",function() {
}}, }},
settings:{userDir:userDir} settings:{userDir:userDir}
}); });
localfilesystem.getNodeFiles(true); var list = localfilesystem.getNodeFiles(true);
list.should.have.property("node-red");
list["node-red"].should.have.property("icons");
list["node-red"].icons.should.have.length(2);
//list["node-red"].icons[1].should.have.property("path",path.join(__dirname,"resources/userDir/lib/icons"))
list["node-red"].icons[1].should.have.property("icons");
list["node-red"].icons[1].icons.should.have.length(1);
list["node-red"].icons[1].icons[0].should.eql("test_icon.png");
done();
}); });
}); });
describe("#getModuleFiles",function() { describe("#getModuleFiles",function() {
@ -256,6 +272,14 @@ describe("red/nodes/registry/localfilesystem",function() {
settings:{coreNodesDir:moduleDir} settings:{coreNodesDir:moduleDir}
}); });
var nodeModule = localfilesystem.getModuleFiles('TestNodeModule'); var nodeModule = localfilesystem.getModuleFiles('TestNodeModule');
nodeModule.should.have.property("TestNodeModule");
nodeModule.TestNodeModule.should.have.property('icons');
nodeModule.TestNodeModule.icons.should.have.length(1);
nodeModule.TestNodeModule.icons[0].should.have.property("path");
nodeModule.TestNodeModule.icons[0].should.have.property("icons");
nodeModule.TestNodeModule.icons[0].icons[0].should.eql("arrow-in.png");
done();
}); });
}); });
}); });

View File

@ -132,7 +132,7 @@ describe("red/nodes/registry/registry",function() {
}); });
describe('#addNodeSet', function() { describe.skip('#addNodeSet', function() {
it('adds a node set for an unknown module', function() { it('adds a node set for an unknown module', function() {
typeRegistry.init(settings); typeRegistry.init(settings);
@ -290,29 +290,34 @@ describe("red/nodes/registry/registry",function() {
describe('#saveNodeList',function() { describe('#saveNodeList',function() {
it('rejects when settings unavailable',function(done) { it('rejects when settings unavailable',function(done) {
typeRegistry.init(stubSettings({},false,{})); typeRegistry.init(stubSettings({},false,{}),null,events);
typeRegistry.addNodeSet("test-module/test-name",testNodeSet1, "0.0.1"); typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {"test-name":{module:"test-module",name:"test-name",types:[]}}});
typeRegistry.saveNodeList().otherwise(function(err) { typeRegistry.saveNodeList().catch(function(err) {
done(); done();
}); });
}); });
it('saves the list',function(done) { it('saves the list',function(done) {
var s = stubSettings({},true,{}); var s = stubSettings({},true,{});
typeRegistry.init(s); typeRegistry.init(s);
typeRegistry.addNodeSet("test-module/test-name",testNodeSet1, "0.0.1"); typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {
typeRegistry.addNodeSet("test-module/test-name-2",testNodeSet2WithError, "0.0.1"); "test-name":testNodeSet1,
"test-name-2":testNodeSet2WithError
}});
typeRegistry.saveNodeList().then(function() { typeRegistry.saveNodeList().then(function() {
s.set.called.should.be.true(); s.set.called.should.be.true();
s.set.lastCall.args[0].should.eql('nodes'); s.set.lastCall.args[0].should.eql('nodes');
var nodes = s.set.lastCall.args[1]; var nodes = s.set.lastCall.args[1];
nodes.should.have.property('test-module'); nodes.should.have.property('test-module');
for (var n in nodes['test-module'].nodes) { for (var n in nodes['test-module'].nodes) {
if (nodes['test-module'].nodes.hasOwnProperty(n)) {
var nn = nodes['test-module'].nodes[n]; var nn = nodes['test-module'].nodes[n];
nn.should.not.have.property('err'); nn.should.not.have.property('err');
nn.should.not.have.property('id'); nn.should.not.have.property('id');
} }
}
done(); done();
}).otherwise(function(err) { }).catch(function(err) {
done(err); done(err);
}); });
}); });
@ -338,7 +343,9 @@ describe("red/nodes/registry/registry",function() {
it('removes a known module', function() { it('removes a known module', function() {
var s = stubSettings({},true,{}); var s = stubSettings({},true,{});
typeRegistry.init(s); typeRegistry.init(s);
typeRegistry.addNodeSet("test-module/test-name",testNodeSet1, "0.0.1"); typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {
"test-name":testNodeSet1
}});
var moduleList = typeRegistry.getModuleList(); var moduleList = typeRegistry.getModuleList();
moduleList.should.have.a.property("test-module"); moduleList.should.have.a.property("test-module");
typeRegistry.getNodeList().should.have.lengthOf(1); typeRegistry.getNodeList().should.have.lengthOf(1);
@ -355,7 +362,9 @@ describe("red/nodes/registry/registry",function() {
typeRegistry.init(settings,{ typeRegistry.init(settings,{
getNodeHelp: function(config) { return "HE"+config.name+"LP" } getNodeHelp: function(config) { return "HE"+config.name+"LP" }
}); });
typeRegistry.addNodeSet("test-module/test-name",{
typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {
"test-name":{
id: "test-module/test-name", id: "test-module/test-name",
module: "test-module", module: "test-module",
name: "test-name", name: "test-name",
@ -363,11 +372,8 @@ describe("red/nodes/registry/registry",function() {
loaded: false, loaded: false,
config: "configA", config: "configA",
types: [ "test-a","test-b"] types: [ "test-a","test-b"]
}, "0.0.1"); },
typeRegistry.getNodeConfig("test-module/test-name").should.eql('<!-- --- [red-module:test-module/test-name] --- -->\nconfigAHEtest-nameLP'); "test-name-2":{
typeRegistry.getAllNodeConfigs().should.eql('\n<!-- --- [red-module:test-module/test-name] --- -->\nconfigAHEtest-nameLP');
typeRegistry.addNodeSet("test-module/test-name-2",{
id: "test-module/test-name-2", id: "test-module/test-name-2",
module: "test-module", module: "test-module",
name: "test-name-2", name: "test-name-2",
@ -375,7 +381,9 @@ describe("red/nodes/registry/registry",function() {
loaded: false, loaded: false,
config: "configB", config: "configB",
types: [ "test-c","test-d"] types: [ "test-c","test-d"]
}, "0.0.1"); }
}});
typeRegistry.getNodeConfig("test-module/test-name").should.eql('<!-- --- [red-module:test-module/test-name] --- -->\nconfigAHEtest-nameLP');
typeRegistry.getNodeConfig("test-module/test-name-2").should.eql('<!-- --- [red-module:test-module/test-name-2] --- -->\nconfigBHEtest-name-2LP'); typeRegistry.getNodeConfig("test-module/test-name-2").should.eql('<!-- --- [red-module:test-module/test-name-2] --- -->\nconfigBHEtest-name-2LP');
typeRegistry.getAllNodeConfigs().should.eql('\n<!-- --- [red-module:test-module/test-name] --- -->\nconfigAHEtest-nameLP\n<!-- --- [red-module:test-module/test-name-2] --- -->\nconfigBHEtest-name-2LP'); typeRegistry.getAllNodeConfigs().should.eql('\n<!-- --- [red-module:test-module/test-name] --- -->\nconfigAHEtest-nameLP\n<!-- --- [red-module:test-module/test-name-2] --- -->\nconfigBHEtest-name-2LP');
}); });
@ -383,7 +391,8 @@ describe("red/nodes/registry/registry",function() {
describe('#getModuleInfo', function() { describe('#getModuleInfo', function() {
it('returns module info', function() { it('returns module info', function() {
typeRegistry.init(settings,{}); typeRegistry.init(settings,{});
typeRegistry.addNodeSet("test-module/test-name",{ typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {
"test-name":{
id: "test-module/test-name", id: "test-module/test-name",
module: "test-module", module: "test-module",
name: "test-name", name: "test-name",
@ -392,7 +401,8 @@ describe("red/nodes/registry/registry",function() {
config: "configA", config: "configA",
types: [ "test-a","test-b"], types: [ "test-a","test-b"],
file: "abc" file: "abc"
}, "0.0.1"); }
}});
var moduleInfo = typeRegistry.getModuleInfo("test-module"); var moduleInfo = typeRegistry.getModuleInfo("test-module");
moduleInfo.should.have.a.property('name','test-module'); moduleInfo.should.have.a.property('name','test-module');
moduleInfo.should.have.a.property('version','0.0.1'); moduleInfo.should.have.a.property('version','0.0.1');
@ -405,7 +415,8 @@ describe("red/nodes/registry/registry",function() {
describe('#getNodeInfo', function() { describe('#getNodeInfo', function() {
it('returns node info', function() { it('returns node info', function() {
typeRegistry.init(settings,{}); typeRegistry.init(settings,{});
typeRegistry.addNodeSet("test-module/test-name",{ typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {
"test-name":{
id: "test-module/test-name", id: "test-module/test-name",
module: "test-module", module: "test-module",
name: "test-name", name: "test-name",
@ -414,7 +425,8 @@ describe("red/nodes/registry/registry",function() {
config: "configA", config: "configA",
types: [ "test-a","test-b"], types: [ "test-a","test-b"],
file: "abc" file: "abc"
}, "0.0.1"); }
}});
var nodeSetInfo = typeRegistry.getNodeInfo("test-module/test-name"); var nodeSetInfo = typeRegistry.getNodeInfo("test-module/test-name");
nodeSetInfo.should.have.a.property('id',"test-module/test-name"); nodeSetInfo.should.have.a.property('id',"test-module/test-name");
nodeSetInfo.should.not.have.a.property('config'); nodeSetInfo.should.not.have.a.property('config');
@ -424,7 +436,8 @@ describe("red/nodes/registry/registry",function() {
describe('#getFullNodeInfo', function() { describe('#getFullNodeInfo', function() {
it('returns node info', function() { it('returns node info', function() {
typeRegistry.init(settings,{}); typeRegistry.init(settings,{});
typeRegistry.addNodeSet("test-module/test-name",{ typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {
"test-name":{
id: "test-module/test-name", id: "test-module/test-name",
module: "test-module", module: "test-module",
name: "test-name", name: "test-name",
@ -434,7 +447,8 @@ describe("red/nodes/registry/registry",function() {
types: [ "test-a","test-b"], types: [ "test-a","test-b"],
file: "abc" file: "abc"
}, "0.0.1"); }
}});
var nodeSetInfo = typeRegistry.getFullNodeInfo("test-module/test-name"); var nodeSetInfo = typeRegistry.getFullNodeInfo("test-module/test-name");
nodeSetInfo.should.have.a.property('id',"test-module/test-name"); nodeSetInfo.should.have.a.property('id',"test-module/test-name");
nodeSetInfo.should.have.a.property('config'); nodeSetInfo.should.have.a.property('config');
@ -447,7 +461,8 @@ describe("red/nodes/registry/registry",function() {
describe('#getNodeList', function() { describe('#getNodeList', function() {
it("returns a filtered list", function() { it("returns a filtered list", function() {
typeRegistry.init(settings,{}); typeRegistry.init(settings,{});
typeRegistry.addNodeSet("test-module/test-name",{ typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {
"test-name":{
id: "test-module/test-name", id: "test-module/test-name",
module: "test-module", module: "test-module",
name: "test-name", name: "test-name",
@ -456,8 +471,8 @@ describe("red/nodes/registry/registry",function() {
config: "configA", config: "configA",
types: [ "test-a","test-b"], types: [ "test-a","test-b"],
file: "abc" file: "abc"
}, "0.0.1"); },
typeRegistry.addNodeSet("test-module/test-name-2",{ "test-name-2":{
id: "test-module/test-name-2", id: "test-module/test-name-2",
module: "test-module", module: "test-module",
name: "test-name-2", name: "test-name-2",
@ -466,7 +481,8 @@ describe("red/nodes/registry/registry",function() {
config: "configB", config: "configB",
types: [ "test-c","test-d"], types: [ "test-c","test-d"],
file: "def" file: "def"
}, "0.0.1"); }
}});
var filterCallCount = 0; var filterCallCount = 0;
var filteredList = typeRegistry.getNodeList(function(n) { filterCallCount++; return n.name === 'test-name-2';}); var filteredList = typeRegistry.getNodeList(function(n) { filterCallCount++; return n.name === 'test-name-2';});
filterCallCount.should.eql(2); filterCallCount.should.eql(2);
@ -532,10 +548,22 @@ describe("red/nodes/registry/registry",function() {
}); });
it('returns a registered icon' , function() { it('returns a registered icon' , function() {
var testIcon = path.resolve(__dirname+'/../../../../resources/icons/test_icon.png'); var testIcon = path.resolve(__dirname+'/../../../../resources/icons/');
events.emit("node-icon-dir",{name:"test-module", path: path.resolve(__dirname+'/../../../../resources/icons'), icons:[]}); typeRegistry.init(settings,{});
typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {
"test-name":{
id: "test-module/test-name",
module: "test-module",
name: "test-name",
enabled: true,
loaded: false,
config: "configA",
types: [ "test-a","test-b"],
file: "abc"
}
},icons: [{path:testIcon,icons:['test_icon.png']}]});
var iconPath = typeRegistry.getNodeIconPath('test-module','test_icon.png'); var iconPath = typeRegistry.getNodeIconPath('test-module','test_icon.png');
iconPath.should.eql(testIcon); iconPath.should.eql(path.resolve(testIcon+"/test_icon.png"));
}); });
it('returns the debug icon when getting an unknown module', function() { it('returns the debug icon when getting an unknown module', function() {
@ -552,14 +580,26 @@ describe("red/nodes/registry/registry",function() {
}); });
it('returns an icon list of registered node module', function() { it('returns an icon list of registered node module', function() {
typeRegistry.addNodeSet("test-module/test-name",testNodeSet1,"0.0.1"); var testIcon = path.resolve(__dirname+'/resources/userDir/lib/icons/');
events.emit("node-icon-dir",{name:"test-module", path:"",icons:["test_icon1.png"]}); typeRegistry.init(settings,{},events);
typeRegistry.addModule({name: "test-module",version:"0.0.1",nodes: {
"test-name":{
id: "test-module/test-name",
module: "test-module",
name: "test-name",
enabled: true,
loaded: false,
config: "configA",
types: [ "test-a","test-b"],
file: "abc"
}
},icons: [{path:testIcon,icons:['test_icon.png']}]});
var iconList = typeRegistry.getNodeIcons(); var iconList = typeRegistry.getNodeIcons();
iconList.should.eql({"test-module":["test_icon1.png"]}); iconList.should.eql({"test-module":["test_icon.png"]});
}); });
it('returns an icon list of unregistered node module', function() { it.skip('returns an icon list of unregistered node module', function() {
events.emit("node-icon-dir",{name:"test-module", path:"", icons:["test_icon1.png", "test_icon2.png"]}); // events.emit("node-icon-dir",{name:"test-module", path:"", icons:["test_icon1.png", "test_icon2.png"]});
var iconList = typeRegistry.getNodeIcons(); var iconList = typeRegistry.getNodeIcons();
iconList.should.eql({"test-module":["test_icon1.png","test_icon2.png"]}); iconList.should.eql({"test-module":["test_icon1.png","test_icon2.png"]});
}); });

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