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

Merge pull request #694 from node-red/i18n

Add i18n support
This commit is contained in:
Nick O'Leary 2015-07-02 10:54:22 +01:00
commit b2d7f079b7
101 changed files with 3693 additions and 2238 deletions

2
.gitignore vendored
View File

@ -15,3 +15,5 @@ flows*.json
nodes/node-red-nodes/ nodes/node-red-nodes/
node_modules node_modules
public public
locales/zz-ZZ
nodes/core/locales/zz-ZZ

View File

@ -93,6 +93,7 @@ module.exports = function(grunt) {
// Ensure editor source files are concatenated in // Ensure editor source files are concatenated in
// the right order // the right order
"editor/js/main.js", "editor/js/main.js",
"editor/js/i18n.js",
"editor/js/settings.js", "editor/js/settings.js",
"editor/js/user.js", "editor/js/user.js",
"editor/js/comms.js", "editor/js/comms.js",
@ -128,7 +129,8 @@ module.exports = function(grunt) {
"editor/vendor/jquery/js/jquery.ui.touch-punch.min.js", "editor/vendor/jquery/js/jquery.ui.touch-punch.min.js",
"editor/vendor/marked/marked.min.js", "editor/vendor/marked/marked.min.js",
"editor/vendor/orion/built-editor.min.js", "editor/vendor/orion/built-editor.min.js",
"editor/vendor/d3/d3.v3.min.js" "editor/vendor/d3/d3.v3.min.js",
"editor/vendor/i18next/i18next.min.js"
], ],
"public/vendor/vendor.css": [ "public/vendor/vendor.css": [
"editor/vendor/orion/built-editor.css" "editor/vendor/orion/built-editor.css"
@ -156,6 +158,15 @@ module.exports = function(grunt) {
}] }]
} }
}, },
jsonlint: {
messages: {
src: [
'nodes/core/locales/en-US/messages.json',
'locales/en-US/editor.json',
'locales/en-US/runtime.json'
]
}
},
attachCopyright: { attachCopyright: {
js: { js: {
src: [ src: [
@ -196,6 +207,14 @@ module.exports = function(grunt) {
'editor/sass/**/*.scss' 'editor/sass/**/*.scss'
], ],
tasks: ['sass','attachCopyright:css'] tasks: ['sass','attachCopyright:css']
},
json: {
files: [
'nodes/core/locales/en-US/messages.json',
'locales/en-US/editor.json',
'locales/en-US/runtime.json'
],
tasks: ['jsonlint:messages']
} }
}, },
@ -205,7 +224,7 @@ module.exports = function(grunt) {
script: 'red.js', script: 'red.js',
options: { options: {
args:['-v'], args:['-v'],
ext: 'js,html', ext: 'js,html,json',
watch: [ watch: [
'red','nodes' 'red','nodes'
] ]
@ -312,6 +331,7 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-compress'); grunt.loadNpmTasks('grunt-contrib-compress');
grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-chmod'); grunt.loadNpmTasks('grunt-chmod');
grunt.loadNpmTasks('grunt-jsonlint');
grunt.registerMultiTask('attachCopyright', function() { grunt.registerMultiTask('attachCopyright', function() {
var files = this.data.src; var files = this.data.src;
@ -377,7 +397,7 @@ module.exports = function(grunt) {
grunt.registerTask('build', grunt.registerTask('build',
'Builds editor content', 'Builds editor content',
['clean:build','concat:build','concat:vendor','uglify:build','sass:build','copy:build','attachCopyright']); ['clean:build','concat:build','concat:vendor','uglify:build','sass:build','jsonlint:messages','copy:build','attachCopyright']);
grunt.registerTask('dev', grunt.registerTask('dev',
'Developer mode: run node-red, watch for source changes and build/restart', 'Developer mode: run node-red, watch for source changes and build/restart',

View File

@ -13,16 +13,16 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
**/ **/
RED.comms = (function() { RED.comms = (function() {
var errornotification = null; var errornotification = null;
var clearErrorTimer = null; var clearErrorTimer = null;
var subscriptions = {}; var subscriptions = {};
var ws; var ws;
var pendingAuth = false; var pendingAuth = false;
function connectWS() { function connectWS() {
var path = location.hostname; var path = location.hostname;
var port = location.port; var port = location.port;
@ -32,10 +32,10 @@ RED.comms = (function() {
path = path+document.location.pathname; path = path+document.location.pathname;
path = path+(path.slice(-1) == "/"?"":"/")+"comms"; path = path+(path.slice(-1) == "/"?"":"/")+"comms";
path = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path; path = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path;
var auth_tokens = RED.settings.get("auth-tokens"); var auth_tokens = RED.settings.get("auth-tokens");
pendingAuth = (auth_tokens!=null); pendingAuth = (auth_tokens!=null);
function completeConnection() { function completeConnection() {
for (var t in subscriptions) { for (var t in subscriptions) {
if (subscriptions.hasOwnProperty(t)) { if (subscriptions.hasOwnProperty(t)) {
@ -43,7 +43,7 @@ RED.comms = (function() {
} }
} }
} }
ws = new WebSocket(path); ws = new WebSocket(path);
ws.onopen = function() { ws.onopen = function() {
if (errornotification) { if (errornotification) {
@ -81,7 +81,7 @@ RED.comms = (function() {
}; };
ws.onclose = function() { ws.onclose = function() {
if (errornotification == null) { if (errornotification == null) {
errornotification = RED.notify("<b>Error</b>: Lost connection to server","error",true); errornotification = RED.notify(RED._("notification.error",{message:RED._("notification.errors.lostConnection")}),"error",true);
} else if (clearErrorTimer) { } else if (clearErrorTimer) {
clearTimeout(clearErrorTimer); clearTimeout(clearErrorTimer);
clearErrorTimer = null; clearErrorTimer = null;
@ -89,7 +89,7 @@ RED.comms = (function() {
setTimeout(connectWS,1000); setTimeout(connectWS,1000);
} }
} }
function subscribe(topic,callback) { function subscribe(topic,callback) {
if (subscriptions[topic] == null) { if (subscriptions[topic] == null) {
subscriptions[topic] = []; subscriptions[topic] = [];
@ -99,7 +99,7 @@ RED.comms = (function() {
ws.send(JSON.stringify({subscribe:topic})); ws.send(JSON.stringify({subscribe:topic}));
} }
} }
function unsubscribe(topic,callback) { function unsubscribe(topic,callback) {
if (subscriptions[topic]) { if (subscriptions[topic]) {
for (var i=0;i<subscriptions[topic].length;i++) { for (var i=0;i<subscriptions[topic].length;i++) {
@ -113,7 +113,7 @@ RED.comms = (function() {
} }
} }
} }
return { return {
connect: connectWS, connect: connectWS,
subscribe: subscribe, subscribe: subscribe,

45
editor/js/i18n.js Normal file
View File

@ -0,0 +1,45 @@
/**
* Copyright 2013, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.i18n = (function() {
return {
init: function(done) {
i18n.init({
resGetPath: 'locales/__ns__',
dynamicLoad: false,
load:'current',
ns: {
namespaces: ["editor","node-red"],
defaultNs: "editor"
},
fallbackLng: ['en-US']
},function() {
done();
});
RED["_"] = function() {
return i18n.t.apply(null,arguments);
}
},
loadCatalog: function(namespace,done) {
i18n.loadNamespace(namespace,done);
}
}
})();

View File

@ -16,8 +16,8 @@
var RED = (function() { var RED = (function() {
function loadSettings() { function loadLocales() {
RED.settings.init(loadNodeList); RED.i18n.init(loadEditor);
} }
function loadNodeList() { function loadNodeList() {
@ -29,7 +29,23 @@ var RED = (function() {
url: 'nodes', url: 'nodes',
success: function(data) { success: function(data) {
RED.nodes.setNodeList(data); RED.nodes.setNodeList(data);
loadNodes();
var nsCount = 0;
for(var i=0;i<data.length;i++) {
var ns = data[i];
if (ns.module != "node-red") {
nsCount++;
RED.i18n.loadCatalog(ns.id, function() {
nsCount--;
if (nsCount === 0) {
loadNodes();
}
});
}
}
if (nsCount === 0) {
loadNodes();
}
} }
}); });
} }
@ -43,6 +59,9 @@ var RED = (function() {
url: 'nodes', url: 'nodes',
success: function(data) { success: function(data) {
$("body").append(data); $("body").append(data);
$("body").i18n();
$(".palette-spinner").hide(); $(".palette-spinner").hide();
$(".palette-scroll").show(); $(".palette-scroll").show();
$("#palette-search").show(); $("#palette-search").show();
@ -66,6 +85,9 @@ var RED = (function() {
var parts = topic.split("/"); var parts = topic.split("/");
var node = RED.nodes.node(parts[1]); var node = RED.nodes.node(parts[1]);
if (node) { if (node) {
if (msg.text) {
msg.text = node._(msg.text,{defaultValue:msg.text});
}
node.status = msg; node.status = msg;
if (statusEnabled) { if (statusEnabled) {
node.dirty = true; node.dirty = true;
@ -91,7 +113,7 @@ var RED = (function() {
} }
if (addedTypes.length) { if (addedTypes.length) {
typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>"; typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>";
RED.notify("Node"+(addedTypes.length!=1 ? "s":"")+" added to palette:"+typeList,"success"); RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
} }
} else if (topic == "node/removed") { } else if (topic == "node/removed") {
for (i=0;i<msg.length;i++) { for (i=0;i<msg.length;i++) {
@ -99,7 +121,7 @@ var RED = (function() {
info = RED.nodes.removeNodeSet(m.id); info = RED.nodes.removeNodeSet(m.id);
if (info.added) { if (info.added) {
typeList = "<ul><li>"+m.types.join("</li><li>")+"</li></ul>"; typeList = "<ul><li>"+m.types.join("</li><li>")+"</li></ul>";
RED.notify("Node"+(m.types.length!=1 ? "s":"")+" removed from palette:"+typeList,"success"); RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success");
} }
} }
} else if (topic == "node/enabled") { } else if (topic == "node/enabled") {
@ -108,12 +130,12 @@ var RED = (function() {
if (info.added) { if (info.added) {
RED.nodes.enableNodeSet(msg.id); RED.nodes.enableNodeSet(msg.id);
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>"; typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" enabled:"+typeList,"success"); RED.notify(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success");
} else { } else {
$.get('nodes/'+msg.id, function(data) { $.get('nodes/'+msg.id, function(data) {
$("body").append(data); $("body").append(data);
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>"; typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" added to palette:"+typeList,"success"); RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success");
}); });
} }
} }
@ -121,7 +143,7 @@ var RED = (function() {
if (msg.types) { if (msg.types) {
RED.nodes.disableNodeSet(msg.id); RED.nodes.disableNodeSet(msg.id);
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>"; typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" disabled:"+typeList,"success"); RED.notify(RED._("palette.event.nodeDisabled", {count:msg.types.length})+typeList,"success");
} }
} }
}); });
@ -138,42 +160,42 @@ var RED = (function() {
function loadEditor() { function loadEditor() {
RED.menu.init({id:"btn-sidemenu", RED.menu.init({id:"btn-sidemenu",
options: [ options: [
{id:"menu-item-sidebar",label:"Sidebar",toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true}, {id:"menu-item-sidebar",label:RED._("menu.label.sidebar"),toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true},
{id:"menu-item-status",label:"Display node status",toggle:true,onselect:toggleStatus, selected: true}, {id:"menu-item-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:toggleStatus, selected: true},
null, null,
{id:"menu-item-import",label:"Import",options:[ {id:"menu-item-import",label:RED._("menu.label.import"),options:[
{id:"menu-item-import-clipboard",label:"Clipboard",onselect:RED.clipboard.import}, {id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:RED.clipboard.import},
{id:"menu-item-import-library",label:"Library",options:[]} {id:"menu-item-import-library",label:RED._("menu.label.library"),options:[]}
]}, ]},
{id:"menu-item-export",label:"Export",disabled:true,options:[ {id:"menu-item-export",label:RED._("menu.label.export"),disabled:true,options:[
{id:"menu-item-export-clipboard",label:"Clipboard",disabled:true,onselect:RED.clipboard.export}, {id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),disabled:true,onselect:RED.clipboard.export},
{id:"menu-item-export-library",label:"Library",disabled:true,onselect:RED.library.export} {id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:RED.library.export}
]}, ]},
null, null,
{id:"menu-item-config-nodes",label:"Configuration nodes",onselect:RED.sidebar.config.show}, {id:"menu-item-config-nodes",label:RED._("menu.label.configurationNodes"),onselect:RED.sidebar.config.show},
null, null,
{id:"menu-item-subflow",label:"Subflows", options: [ {id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [
{id:"menu-item-subflow-create",label:"Create subflow",onselect:RED.subflow.createSubflow}, {id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:RED.subflow.createSubflow},
{id:"menu-item-subflow-convert",label:"Selection to subflow",disabled:true,onselect:RED.subflow.convertToSubflow}, {id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:RED.subflow.convertToSubflow},
]}, ]},
null, null,
{id:"menu-item-workspace",label:"Workspaces",options:[ {id:"menu-item-workspace",label:RED._("menu.label.workspaces"),options:[
{id:"menu-item-workspace-add",label:"Add",onselect:RED.workspaces.add}, {id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:RED.workspaces.add},
{id:"menu-item-workspace-edit",label:"Rename",onselect:RED.workspaces.edit}, {id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:RED.workspaces.edit},
{id:"menu-item-workspace-delete",label:"Delete",onselect:RED.workspaces.remove}, {id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:RED.workspaces.remove},
null null
]}, ]},
null, null,
{id:"menu-item-keyboard-shortcuts",label:"Keyboard Shortcuts",onselect:RED.keyboard.showHelp}, {id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),onselect:RED.keyboard.showHelp},
{id:"menu-item-help", {id:"menu-item-help",
label: RED.settings.theme("menu.menu-item-help.label","Node-RED Website"), label: RED.settings.theme("menu.menu-item-help.label","Node-RED Website"),
href: RED.settings.theme("menu.menu-item-help.url","http://nodered.org/docs") href: RED.settings.theme("menu.menu-item-help.url","http://nodered.org/docs")
} }
] ]
}); });
RED.user.init(); RED.user.init();
RED.library.init(); RED.library.init();
RED.palette.init(); RED.palette.init();
RED.sidebar.init(); RED.sidebar.init();
@ -181,9 +203,10 @@ var RED = (function() {
RED.workspaces.init(); RED.workspaces.init();
RED.clipboard.init(); RED.clipboard.init();
RED.view.init(); RED.view.init();
RED.editor.init();
RED.deploy.init(RED.settings.theme("deployButton",null)); RED.deploy.init(RED.settings.theme("deployButton",null));
RED.keyboard.add(/* ? */ 191,{shift:true},function(){RED.keyboard.showHelp();d3.event.preventDefault();}); RED.keyboard.add(/* ? */ 191,{shift:true},function(){RED.keyboard.showHelp();d3.event.preventDefault();});
RED.comms.connect(); RED.comms.connect();
@ -194,14 +217,14 @@ var RED = (function() {
} }
$(function() { $(function() {
if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) { if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) {
document.title = document.title+" : "+window.location.hostname; document.title = document.title+" : "+window.location.hostname;
} }
ace.require("ace/ext/language_tools"); ace.require("ace/ext/language_tools");
RED.settings.init(loadEditor); RED.settings.init(loadLocales);
}); });

View File

@ -22,20 +22,20 @@ RED.nodes = (function() {
var defaultWorkspace; var defaultWorkspace;
var workspaces = {}; var workspaces = {};
var subflows = {}; var subflows = {};
var dirty = false; var dirty = false;
function setDirty(d) { function setDirty(d) {
dirty = d; dirty = d;
eventHandler.emit("change",{dirty:dirty}); eventHandler.emit("change",{dirty:dirty});
} }
var registry = (function() { var registry = (function() {
var nodeList = []; var nodeList = [];
var nodeSets = {}; var nodeSets = {};
var typeToId = {}; var typeToId = {};
var nodeDefinitions = {}; var nodeDefinitions = {};
var exports = { var exports = {
getNodeList: function() { getNodeList: function() {
return nodeList; return nodeList;
@ -107,7 +107,23 @@ RED.nodes = (function() {
registerNodeType: function(nt,def) { registerNodeType: function(nt,def) {
nodeDefinitions[nt] = def; nodeDefinitions[nt] = def;
if (def.category != "subflows") { if (def.category != "subflows") {
def.set = nodeSets[typeToId[nt]];
nodeSets[typeToId[nt]].added = true; nodeSets[typeToId[nt]].added = true;
var ns;
if (def.set.module === "node-red") {
ns = "node-red";
} else {
ns = def.set.id;
}
def["_"] = function() {
var args = Array.prototype.slice.call(arguments, 0);
if (args[0].indexOf(":") === -1) {
args[0] = ns+":"+args[0];
}
return RED._.apply(null,args);
}
// TODO: too tightly coupled into palette UI // TODO: too tightly coupled into palette UI
} }
RED.palette.add(nt,def); RED.palette.add(nt,def);
@ -117,6 +133,7 @@ RED.nodes = (function() {
}, },
removeNodeType: function(nt) { removeNodeType: function(nt) {
if (nt.substring(0,8) != "subflow:") { if (nt.substring(0,8) != "subflow:") {
// NON-NLS - internal debug message
throw new Error("this api is subflow only. called with:",nt); throw new Error("this api is subflow only. called with:",nt);
} }
delete nodeDefinitions[nt]; delete nodeDefinitions[nt];
@ -128,12 +145,15 @@ RED.nodes = (function() {
}; };
return exports; return exports;
})(); })();
function getID() { function getID() {
return (1+Math.random()*4294967295).toString(16); return (1+Math.random()*4294967295).toString(16);
} }
function addNode(n) { function addNode(n) {
if (n.type.indexOf("subflow") !== 0) {
n["_"] = n._def._;
}
if (n._def.category == "config") { if (n._def.category == "config") {
configNodes[n.id] = n; configNodes[n.id] = n;
RED.sidebar.config.refresh(); RED.sidebar.config.refresh();
@ -265,7 +285,7 @@ RED.nodes = (function() {
var subflowNames = Object.keys(subflows).map(function(sfid) { var subflowNames = Object.keys(subflows).map(function(sfid) {
return subflows[sfid].name; return subflows[sfid].name;
}); });
subflowNames.sort(); subflowNames.sort();
var copyNumber = 1; var copyNumber = 1;
var subflowName = sf.name; var subflowName = sf.name;
@ -277,7 +297,7 @@ RED.nodes = (function() {
}); });
sf.name = subflowName; sf.name = subflowName;
} }
subflows[sf.id] = sf; subflows[sf.id] = sf;
RED.nodes.registerType("subflow:"+sf.id, { RED.nodes.registerType("subflow:"+sf.id, {
defaults:{name:{value:""}}, defaults:{name:{value:""}},
@ -288,10 +308,13 @@ RED.nodes = (function() {
color: "#da9", color: "#da9",
label: function() { return this.name||RED.nodes.subflow(sf.id).name }, label: function() { return this.name||RED.nodes.subflow(sf.id).name },
labelStyle: function() { return this.name?"node_label_italic":""; }, labelStyle: function() { return this.name?"node_label_italic":""; },
paletteLabel: function() { return RED.nodes.subflow(sf.id).name } paletteLabel: function() { return RED.nodes.subflow(sf.id).name },
set:{
module: "node-red"
}
}); });
} }
function getSubflow(id) { function getSubflow(id) {
return subflows[id]; return subflows[id];
@ -300,7 +323,7 @@ RED.nodes = (function() {
delete subflows[sf.id]; delete subflows[sf.id];
registry.removeNodeType("subflow:"+sf.id); registry.removeNodeType("subflow:"+sf.id);
} }
function subflowContains(sfid,nodeid) { function subflowContains(sfid,nodeid) {
for (var i=0;i<nodes.length;i++) { for (var i=0;i<nodes.length;i++) {
var node = nodes[i]; var node = nodes[i];
@ -320,7 +343,7 @@ RED.nodes = (function() {
} }
return false; return false;
} }
function getAllFlowNodes(node) { function getAllFlowNodes(node) {
var visited = {}; var visited = {};
visited[node.id] = true; visited[node.id] = true;
@ -411,7 +434,7 @@ RED.nodes = (function() {
node.name = n.name; node.name = n.name;
node.in = []; node.in = [];
node.out = []; node.out = [];
n.in.forEach(function(p) { n.in.forEach(function(p) {
var nIn = {x:p.x,y:p.y,wires:[]}; var nIn = {x:p.x,y:p.y,wires:[]};
var wires = links.filter(function(d) { return d.source === p }); var wires = links.filter(function(d) { return d.source === p });
@ -435,8 +458,8 @@ RED.nodes = (function() {
} }
node.out.push(nOut); node.out.push(nOut);
}); });
return node; return node;
} }
/** /**
@ -527,7 +550,7 @@ RED.nodes = (function() {
try { try {
newNodes = JSON.parse(newNodesObj); newNodes = JSON.parse(newNodesObj);
} catch(err) { } catch(err) {
var e = new Error("Invalid flow: "+err.message); var e = new Error(RED._("clipboard.invalidFlow",{message:err.message}));
e.code = "NODE_RED"; e.code = "NODE_RED";
throw e; throw e;
} }
@ -542,21 +565,19 @@ RED.nodes = (function() {
for (i=0;i<newNodes.length;i++) { for (i=0;i<newNodes.length;i++) {
n = newNodes[i]; n = newNodes[i];
// TODO: remove workspace in next release+1 // TODO: remove workspace in next release+1
if (n.type != "workspace" && if (n.type != "workspace" &&
n.type != "tab" && n.type != "tab" &&
n.type != "subflow" && n.type != "subflow" &&
!registry.getNodeType(n.type) && !registry.getNodeType(n.type) &&
n.type.substring(0,8) != "subflow:" && n.type.substring(0,8) != "subflow:" &&
unknownTypes.indexOf(n.type)==-1) { unknownTypes.indexOf(n.type)==-1) {
unknownTypes.push(n.type); unknownTypes.push(n.type);
} }
} }
if (unknownTypes.length > 0) { if (unknownTypes.length > 0) {
var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>"; var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>";
var type = "type"+(unknownTypes.length > 1?"s":""); var type = "type"+(unknownTypes.length > 1?"s":"");
RED.notify("<strong>Imported unrecognised "+type+":</strong>"+typeList,"error",false,10000); RED.notify("<strong>"+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"</strong>"+typeList,"error",false,10000);
//"DO NOT DEPLOY while in this state.<br/>Either, add missing types to Node-RED, restart and then reload page,<br/>or delete unknown "+n.name+", rewire as required, and then deploy.","error");
} }
var activeWorkspace = RED.workspaces.active(); var activeWorkspace = RED.workspaces.active();
@ -568,21 +589,20 @@ RED.nodes = (function() {
var subflowId = m[1]; var subflowId = m[1];
var err; var err;
if (subflowId === activeSubflow.id) { if (subflowId === activeSubflow.id) {
err = new Error("Cannot add subflow to itself"); err = new Error(RED._("notification.errors.cannotAddSubflowToItself"));
} }
if (subflowContains(m[1],activeSubflow.id)) { if (subflowContains(m[1],activeSubflow.id)) {
err = new Error("Cannot add subflow - circular reference detected"); err = new Error(RED._("notification.errors.cannotAddCircularReference"));
} }
if (err) { if (err) {
// TODO: standardise error codes // TODO: standardise error codes
err.code = "NODE_RED"; err.code = "NODE_RED";
throw err; throw err;
} }
} }
} }
} }
var new_workspaces = []; var new_workspaces = [];
var workspace_map = {}; var workspace_map = {};
var new_subflows = []; var new_subflows = [];
@ -704,11 +724,13 @@ RED.nodes = (function() {
defaults: {}, defaults: {},
label: "unknown: "+n.type, label: "unknown: "+n.type,
labelStyle: "node_label_italic", labelStyle: "node_label_italic",
outputs: n.outputs||n.wires.length outputs: n.outputs||n.wires.length,
set: registry.getNodeSet("node-red/unknown")
} }
} else { } else {
node._def = { node._def = {
category:"config" category:"config",
set: registry.getNodeSet("node-red/unknown")
}; };
node.users = []; node.users = [];
} }
@ -779,14 +801,14 @@ RED.nodes = (function() {
delete output.wires; delete output.wires;
}); });
} }
return [new_nodes,new_links,new_workspaces,new_subflows]; return [new_nodes,new_links,new_workspaces,new_subflows];
} }
// TODO: supports filter.z|type // TODO: supports filter.z|type
function filterNodes(filter) { function filterNodes(filter) {
var result = []; var result = [];
for (var n=0;n<nodes.length;n++) { for (var n=0;n<nodes.length;n++) {
var node = nodes[n]; var node = nodes[n];
if (filter.hasOwnProperty("z") && node.z !== filter.z) { if (filter.hasOwnProperty("z") && node.z !== filter.z) {
@ -801,7 +823,7 @@ RED.nodes = (function() {
} }
function filterLinks(filter) { function filterLinks(filter) {
var result = []; var result = [];
for (var n=0;n<links.length;n++) { for (var n=0;n<links.length;n++) {
var link = links[n]; var link = links[n];
if (filter.source) { if (filter.source) {
@ -827,11 +849,11 @@ RED.nodes = (function() {
} }
return result; return result;
} }
// TODO: DRY // TODO: DRY
var eventHandler = (function() { var eventHandler = (function() {
var handlers = {}; var handlers = {};
return { return {
on: function(evt,func) { on: function(evt,func) {
handlers[evt] = handlers[evt]||[]; handlers[evt] = handlers[evt]||[];
@ -842,43 +864,43 @@ RED.nodes = (function() {
for (var i=0;i<handlers[evt].length;i++) { for (var i=0;i<handlers[evt].length;i++) {
handlers[evt][i](arg); handlers[evt][i](arg);
} }
} }
} }
} }
})(); })();
return { return {
on: eventHandler.on, on: eventHandler.on,
registry:registry, registry:registry,
setNodeList: registry.setNodeList, setNodeList: registry.setNodeList,
getNodeSet: registry.getNodeSet, getNodeSet: registry.getNodeSet,
addNodeSet: registry.addNodeSet, addNodeSet: registry.addNodeSet,
removeNodeSet: registry.removeNodeSet, removeNodeSet: registry.removeNodeSet,
enableNodeSet: registry.enableNodeSet, enableNodeSet: registry.enableNodeSet,
disableNodeSet: registry.disableNodeSet, disableNodeSet: registry.disableNodeSet,
registerType: registry.registerNodeType, registerType: registry.registerNodeType,
getType: registry.getNodeType, getType: registry.getNodeType,
convertNode: convertNode, convertNode: convertNode,
add: addNode, add: addNode,
remove: removeNode, remove: removeNode,
addLink: addLink, addLink: addLink,
removeLink: removeLink, removeLink: removeLink,
addWorkspace: addWorkspace, addWorkspace: addWorkspace,
removeWorkspace: removeWorkspace, removeWorkspace: removeWorkspace,
workspace: getWorkspace, workspace: getWorkspace,
addSubflow: addSubflow, addSubflow: addSubflow,
removeSubflow: removeSubflow, removeSubflow: removeSubflow,
subflow: getSubflow, subflow: getSubflow,
subflowContains: subflowContains, subflowContains: subflowContains,
eachNode: function(cb) { eachNode: function(cb) {
for (var n=0;n<nodes.length;n++) { for (var n=0;n<nodes.length;n++) {
cb(nodes[n]); cb(nodes[n]);
@ -903,14 +925,14 @@ RED.nodes = (function() {
} }
} }
}, },
node: getNode, node: getNode,
filterNodes: filterNodes, filterNodes: filterNodes,
filterLinks: filterLinks, filterLinks: filterLinks,
import: importNodes, import: importNodes,
getAllFlowNodes: getAllFlowNodes, getAllFlowNodes: getAllFlowNodes,
createExportableNodeSet: createExportableNodeSet, createExportableNodeSet: createExportableNodeSet,
createCompleteNodeSet: createCompleteNodeSet, createCompleteNodeSet: createCompleteNodeSet,

View File

@ -16,62 +16,71 @@
RED.clipboard = (function() { RED.clipboard = (function() {
var dialog = $('<div id="clipboard-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
.appendTo("body")
.dialog({
modal: true,
autoOpen: false,
width: 500,
resizable: false,
buttons: [
{
id: "clipboard-dialog-ok",
text: "Ok",
click: function() {
if (/Import/.test(dialog.dialog("option","title"))) {
RED.view.importNodes($("#clipboard-import").val());
}
$( this ).dialog( "close" );
}
},
{
id: "clipboard-dialog-cancel",
text: "Cancel",
click: function() {
$( this ).dialog( "close" );
}
},
{
id: "clipboard-dialog-close",
text: "Close",
click: function() {
$( this ).dialog( "close" );
}
}
],
open: function(e) {
$(this).parent().find(".ui-dialog-titlebar-close").hide();
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
}
});
var dialogContainer = dialog.children(".dialog-form"); var dialog;
var dialogContainer;
var exportNodesDialog = '<div class="form-row">'+ var exportNodesDialog;
'<label for="node-input-export" style="display: block; width:100%;"><i class="fa fa-clipboard"></i> Nodes:</label>'+ var importNodesDialog;
'<textarea readonly style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-export" rows="5"></textarea>'+
'</div>'+ function setupDialogs(){
'<div class="form-tips">'+ dialog = $('<div id="clipboard-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
'Select the text above and copy to the clipboard with Ctrl-C.'+ .appendTo("body")
'</div>'; .dialog({
modal: true,
var importNodesDialog = '<div class="form-row">'+ autoOpen: false,
'<textarea style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-import" rows="5" placeholder="Paste nodes here"></textarea>'+ width: 500,
'</div>'; resizable: false,
buttons: [
{
id: "clipboard-dialog-ok",
text: RED._("common.label.ok"),
click: function() {
if (/Import/.test(dialog.dialog("option","title"))) {
RED.view.importNodes($("#clipboard-import").val());
}
$( this ).dialog( "close" );
}
},
{
id: "clipboard-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
},
{
id: "clipboard-dialog-close",
text: RED._("common.label.close"),
click: function() {
$( this ).dialog( "close" );
}
}
],
open: function(e) {
$(this).parent().find(".ui-dialog-titlebar-close").hide();
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
}
});
dialogContainer = dialog.children(".dialog-form");
exportNodesDialog = '<div class="form-row">'+
'<label for="node-input-export" style="display: block; width:100%;"><i class="fa fa-clipboard"></i> '+RED._("clipboard.nodes")+'</label>'+
'<textarea readonly style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-export" rows="5"></textarea>'+
'</div>'+
'<div class="form-tips">'+
RED._("clipboard.selectNodes")+
'</div>';
importNodesDialog = '<div class="form-row">'+
'<textarea style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-import" rows="5" placeholder="'+
RED._("clipboard.pasteNodes")+
'"></textarea>'+
'</div>';
}
function validateImport() { function validateImport() {
var importInput = $("#clipboard-import"); var importInput = $("#clipboard-import");
@ -87,7 +96,7 @@ RED.clipboard = (function() {
$("#clipboard-dialog-ok").button("disable"); $("#clipboard-dialog-ok").button("disable");
} }
} }
function importNodes() { function importNodes() {
dialogContainer.empty(); dialogContainer.empty();
dialogContainer.append($(importNodesDialog)); dialogContainer.append($(importNodesDialog));
@ -97,8 +106,8 @@ RED.clipboard = (function() {
$("#clipboard-dialog-ok").button("disable"); $("#clipboard-dialog-ok").button("disable");
$("#clipboard-import").keyup(validateImport); $("#clipboard-import").keyup(validateImport);
$("#clipboard-import").on('paste',function() { setTimeout(validateImport,10)}); $("#clipboard-import").on('paste',function() { setTimeout(validateImport,10)});
dialog.dialog("option","title","Import nodes").dialog("open"); dialog.dialog("option","title",RED._("clipboard.importNodes")).dialog("open");
} }
function exportNodes() { function exportNodes() {
@ -120,17 +129,18 @@ RED.clipboard = (function() {
return false; return false;
}) })
}); });
dialog.dialog("option","title","Export nodes to clipboard").dialog( "open" ); dialog.dialog("option","title",RED._("clipboard.exportNodes")).dialog( "open" );
} }
} }
function hideDropTarget() { function hideDropTarget() {
$("#dropTarget").hide(); $("#dropTarget").hide();
RED.keyboard.remove(/* ESCAPE */ 27); RED.keyboard.remove(/* ESCAPE */ 27);
} }
return { return {
init: function() { init: function() {
setupDialogs();
RED.view.on("selection-changed",function(selection) { RED.view.on("selection-changed",function(selection) {
if (!selection.nodes) { if (!selection.nodes) {
RED.menu.setDisabled("menu-item-export",true); RED.menu.setDisabled("menu-item-export",true);
@ -144,16 +154,16 @@ RED.clipboard = (function() {
}); });
RED.keyboard.add(/* e */ 69,{ctrl:true},function(){exportNodes();d3.event.preventDefault();}); RED.keyboard.add(/* e */ 69,{ctrl:true},function(){exportNodes();d3.event.preventDefault();});
RED.keyboard.add(/* i */ 73,{ctrl:true},function(){importNodes();d3.event.preventDefault();}); RED.keyboard.add(/* i */ 73,{ctrl:true},function(){importNodes();d3.event.preventDefault();});
$('#chart').on("dragenter",function(event) { $('#chart').on("dragenter",function(event) {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) { if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
$("#dropTarget").css({display:'table'}); $("#dropTarget").css({display:'table'});
RED.keyboard.add(/* ESCAPE */ 27,hideDropTarget); RED.keyboard.add(/* ESCAPE */ 27,hideDropTarget);
} }
}); });
$('#dropTarget').on("dragover",function(event) { $('#dropTarget').on("dragover",function(event) {
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) { if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
event.preventDefault(); event.preventDefault();
@ -168,15 +178,15 @@ RED.clipboard = (function() {
RED.view.importNodes(data); RED.view.importNodes(data);
event.preventDefault(); event.preventDefault();
}); });
}, },
import: importNodes, import: importNodes,
export: exportNodes export: exportNodes
} }
})(); })();

View File

@ -13,29 +13,29 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
**/ **/
RED.deploy = (function() { RED.deploy = (function() {
var deploymentTypes = { var deploymentTypes = {
"full":{img:"red/images/deploy-full-o.png"}, "full":{img:"red/images/deploy-full-o.png"},
"nodes":{img:"red/images/deploy-nodes-o.png"}, "nodes":{img:"red/images/deploy-nodes-o.png"},
"flows":{img:"red/images/deploy-flows-o.png"} "flows":{img:"red/images/deploy-flows-o.png"}
} }
var ignoreDeployWarnings = { var ignoreDeployWarnings = {
unknown: false, unknown: false,
unusedConfig: false, unusedConfig: false,
invalid: false invalid: false
} }
var deploymentType = "full"; var deploymentType = "full";
function changeDeploymentType(type) { function changeDeploymentType(type) {
deploymentType = type; deploymentType = type;
$("#btn-deploy img").attr("src",deploymentTypes[type].img); $("#btn-deploy img").attr("src",deploymentTypes[type].img);
} }
/** /**
* options: * options:
* type: "default" - Button with drop-down options - no further customisation available * type: "default" - Button with drop-down options - no further customisation available
@ -46,35 +46,35 @@ RED.deploy = (function() {
function init(options) { function init(options) {
options = options || {}; options = options || {};
var type = options.type || "default"; var type = options.type || "default";
if (type == "default") { if (type == "default") {
$('<li><span class="deploy-button-group button-group">'+ $('<li><span class="deploy-button-group button-group">'+
'<a id="btn-deploy" class="deploy-button disabled" href="#"><img id="btn-deploy-icon" src="red/images/deploy-full-o.png"> <span>Deploy</span></a>'+ '<a id="btn-deploy" class="deploy-button disabled" href="#"><img id="btn-deploy-icon" src="red/images/deploy-full-o.png"> <span>'+RED._("deploy.deploy")+'</span></a>'+
'<a id="btn-deploy-options" data-toggle="dropdown" class="deploy-button" href="#"><i class="fa fa-caret-down"></i></a>'+ '<a id="btn-deploy-options" data-toggle="dropdown" class="deploy-button" href="#"><i class="fa fa-caret-down"></i></a>'+
'</span></li>').prependTo(".header-toolbar"); '</span></li>').prependTo(".header-toolbar");
RED.menu.init({id:"btn-deploy-options", RED.menu.init({id:"btn-deploy-options",
options: [ options: [
{id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.png",label:"Full",sublabel:"Deploys everything in the workspace",selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}}, {id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.png",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
{id:"deploymenu-item-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.png",label:"Modified Flows",sublabel:"Only deploys flows that contain changed nodes", onselect:function(s) {if(s){changeDeploymentType("flows")}}}, {id:"deploymenu-item-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.png",label:RED._("deploy.modifiedFlows"),sublabel:RED._("deploy.modifiedFlowsDesc"), onselect:function(s) {if(s){changeDeploymentType("flows")}}},
{id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.png",label:"Modified Nodes",sublabel:"Only deploys nodes that have changed",onselect:function(s) { if(s){changeDeploymentType("nodes")}}} {id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.png",label:RED._("deploy.modifiedNodes"),sublabel:RED._("deploy.modifiedNodesDesc"),onselect:function(s) { if(s){changeDeploymentType("nodes")}}}
] ]
}); });
} else if (type == "simple") { } else if (type == "simple") {
var label = options.label || "Deploy"; var label = options.label || RED._("deploy.deploy");
var icon = 'red/images/deploy-full-o.png'; var icon = 'red/images/deploy-full-o.png';
if (options.hasOwnProperty('icon')) { if (options.hasOwnProperty('icon')) {
icon = options.icon; icon = options.icon;
} }
$('<li><span class="deploy-button-group button-group">'+ $('<li><span class="deploy-button-group button-group">'+
'<a id="btn-deploy" class="deploy-button disabled" href="#">'+ '<a id="btn-deploy" class="deploy-button disabled" href="#">'+
(icon?'<img id="btn-deploy-icon" src="'+icon+'"> ':'')+ (icon?'<img id="btn-deploy-icon" src="'+icon+'"> ':'')+
'<span>'+label+'</span></a>'+ '<span>'+label+'</span></a>'+
'</span></li>').prependTo(".header-toolbar"); '</span></li>').prependTo(".header-toolbar");
} }
$('#btn-deploy').click(function() { save(); }); $('#btn-deploy').click(function() { save(); });
$( "#node-dialog-confirm-deploy" ).dialog({ $( "#node-dialog-confirm-deploy" ).dialog({
title: "Confirm deploy", title: "Confirm deploy",
modal: true, modal: true,
@ -83,9 +83,9 @@ RED.deploy = (function() {
height: "auto", height: "auto",
buttons: [ buttons: [
{ {
text: "Confirm deploy", text: RED._("deploy.confirm.button.confirm"),
click: function() { click: function() {
var ignoreChecked = $( "#node-dialog-confirm-deploy-hide" ).prop("checked"); var ignoreChecked = $( "#node-dialog-confirm-deploy-hide" ).prop("checked");
if (ignoreChecked) { if (ignoreChecked) {
ignoreDeployWarnings[$( "#node-dialog-confirm-deploy-type" ).val()] = true; ignoreDeployWarnings[$( "#node-dialog-confirm-deploy-type" ).val()] = true;
@ -95,7 +95,7 @@ RED.deploy = (function() {
} }
}, },
{ {
text: "Cancel", text: RED._("deploy.confirm.button.cancel"),
click: function() { click: function() {
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
@ -114,7 +114,7 @@ RED.deploy = (function() {
RED.nodes.on('change',function(state) { RED.nodes.on('change',function(state) {
if (state.dirty) { if (state.dirty) {
window.onbeforeunload = function() { window.onbeforeunload = function() {
return "You have undeployed changes.\n\nLeaving this page will lose these changes."; return RED._("deploy.confirm.undeployedChanges");
} }
$("#btn-deploy").removeClass("disabled"); $("#btn-deploy").removeClass("disabled");
} else { } else {
@ -132,7 +132,7 @@ RED.deploy = (function() {
var hasUnknown = false; var hasUnknown = false;
var hasInvalid = false; var hasInvalid = false;
var hasUnusedConfig = false; var hasUnusedConfig = false;
var unknownNodes = []; var unknownNodes = [];
RED.nodes.eachNode(function(node) { RED.nodes.eachNode(function(node) {
hasInvalid = hasInvalid || !node.valid; hasInvalid = hasInvalid || !node.valid;
@ -143,7 +143,7 @@ RED.deploy = (function() {
} }
}); });
hasUnknown = unknownNodes.length > 0; hasUnknown = unknownNodes.length > 0;
var unusedConfigNodes = {}; var unusedConfigNodes = {};
RED.nodes.eachConfig(function(node) { RED.nodes.eachConfig(function(node) {
if (node.users.length === 0) { if (node.users.length === 0) {
@ -159,13 +159,13 @@ RED.deploy = (function() {
hasUnusedConfig = true; hasUnusedConfig = true;
} }
}); });
$( "#node-dialog-confirm-deploy-config" ).hide(); $( "#node-dialog-confirm-deploy-config" ).hide();
$( "#node-dialog-confirm-deploy-unknown" ).hide(); $( "#node-dialog-confirm-deploy-unknown" ).hide();
$( "#node-dialog-confirm-deploy-unused" ).hide(); $( "#node-dialog-confirm-deploy-unused" ).hide();
var showWarning = false; var showWarning = false;
if (hasUnknown && !ignoreDeployWarnings.unknown) { if (hasUnknown && !ignoreDeployWarnings.unknown) {
showWarning = true; showWarning = true;
$( "#node-dialog-confirm-deploy-type" ).val("unknown"); $( "#node-dialog-confirm-deploy-type" ).val("unknown");
@ -186,7 +186,7 @@ RED.deploy = (function() {
unusedConfigNodes[type].forEach(function(label) { unusedConfigNodes[type].forEach(function(label) {
unusedNodeLabels.push(type+": "+label); unusedNodeLabels.push(type+": "+label);
}); });
}); });
$( "#node-dialog-confirm-deploy-unused-list" ) $( "#node-dialog-confirm-deploy-unused-list" )
.html("<li>"+unusedNodeLabels.join("</li><li>")+"</li>"); .html("<li>"+unusedNodeLabels.join("</li><li>")+"</li>");
} }
@ -196,10 +196,10 @@ RED.deploy = (function() {
return; return;
} }
} }
var nns = RED.nodes.createCompleteNodeSet(); var nns = RED.nodes.createCompleteNodeSet();
$("#btn-deploy-icon").removeClass('fa-download'); $("#btn-deploy-icon").removeClass('fa-download');
@ -215,7 +215,7 @@ RED.deploy = (function() {
"Node-RED-Deployment-Type":deploymentType "Node-RED-Deployment-Type":deploymentType
} }
}).done(function(data,textStatus,xhr) { }).done(function(data,textStatus,xhr) {
RED.notify("Successfully deployed","success"); RED.notify(RED._("deploy.successfulDeploy"),"success");
RED.nodes.eachNode(function(node) { RED.nodes.eachNode(function(node) {
if (node.changed) { if (node.changed) {
node.dirty = true; node.dirty = true;
@ -236,9 +236,9 @@ RED.deploy = (function() {
}).fail(function(xhr,textStatus,err) { }).fail(function(xhr,textStatus,err) {
RED.nodes.dirty(true); RED.nodes.dirty(true);
if (xhr.responseText) { if (xhr.responseText) {
RED.notify("<strong>Error</strong>: "+xhr.responseJSON.message,"error"); RED.notify(RED._("notification.error",{message:xhr.responseJSON.message}),"error");
} else { } else {
RED.notify("<strong>Error</strong>: no response from server","error"); RED.notify(RED._("notification.error",{message:RED._("deploy.errors.noResponse")}),"error");
} }
}).always(function() { }).always(function() {
$("#btn-deploy-icon").removeClass('spinner'); $("#btn-deploy-icon").removeClass('spinner');

View File

@ -168,184 +168,186 @@ RED.editor = (function() {
return removedLinks; return removedLinks;
} }
$( "#dialog" ).dialog({ function createDialog(){
modal: true, $( "#dialog" ).dialog({
autoOpen: false, modal: true,
dialogClass: "ui-dialog-no-close", autoOpen: false,
closeOnEscape: false, dialogClass: "ui-dialog-no-close",
minWidth: 500, closeOnEscape: false,
width: 'auto', minWidth: 500,
buttons: [ width: 'auto',
{ buttons: [
id: "node-dialog-ok", {
text: "Ok", id: "node-dialog-ok",
click: function() { text: RED._("common.label.ok"),
if (editing_node) { click: function() {
var changes = {}; if (editing_node) {
var changed = false; var changes = {};
var wasDirty = RED.nodes.dirty(); var changed = false;
var d; var wasDirty = RED.nodes.dirty();
var d;
if (editing_node._def.oneditsave) { if (editing_node._def.oneditsave) {
var oldValues = {}; var oldValues = {};
for (d in editing_node._def.defaults) { for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) { if (editing_node._def.defaults.hasOwnProperty(d)) {
if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") { if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") {
oldValues[d] = editing_node[d]; oldValues[d] = editing_node[d];
} else { } else {
oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v; oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
}
} }
} }
} var rc = editing_node._def.oneditsave.call(editing_node);
var rc = editing_node._def.oneditsave.call(editing_node); if (rc === true) {
if (rc === true) { changed = true;
changed = true; }
}
for (d in editing_node._def.defaults) { for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) { if (editing_node._def.defaults.hasOwnProperty(d)) {
if (oldValues[d] === null || typeof oldValues[d] === "string" || typeof oldValues[d] === "number") { if (oldValues[d] === null || typeof oldValues[d] === "string" || typeof oldValues[d] === "number") {
if (oldValues[d] !== editing_node[d]) { if (oldValues[d] !== editing_node[d]) {
changes[d] = oldValues[d]; changes[d] = oldValues[d];
changed = true; changed = true;
} }
} else { } else {
if (JSON.stringify(oldValues[d]) !== JSON.stringify(editing_node[d])) { if (JSON.stringify(oldValues[d]) !== JSON.stringify(editing_node[d])) {
changes[d] = oldValues[d]; changes[d] = oldValues[d];
changed = true; changed = true;
}
} }
} }
} }
} }
}
if (editing_node._def.defaults) { if (editing_node._def.defaults) {
for (d in editing_node._def.defaults) { for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) { if (editing_node._def.defaults.hasOwnProperty(d)) {
var input = $("#node-input-"+d); var input = $("#node-input-"+d);
var newValue; var newValue;
if (input.attr('type') === "checkbox") { if (input.attr('type') === "checkbox") {
newValue = input.prop('checked'); newValue = input.prop('checked');
} else { } else {
newValue = input.val(); newValue = input.val();
} }
if (newValue != null) { if (newValue != null) {
if (editing_node[d] != newValue) {
if (d === "outputs" && (newValue.trim() === "" || isNaN(newValue))) { if (d === "outputs" && (newValue.trim() === "" || isNaN(newValue))) {
continue; continue;
} }
if (editing_node._def.defaults[d].type) { if (editing_node[d] != newValue) {
if (newValue == "_ADD_") { if (editing_node._def.defaults[d].type) {
newValue = ""; if (newValue == "_ADD_") {
} newValue = "";
// Change to a related config node }
var configNode = RED.nodes.node(editing_node[d]); // Change to a related config node
if (configNode) { var configNode = RED.nodes.node(editing_node[d]);
var users = configNode.users; if (configNode) {
users.splice(users.indexOf(editing_node),1); var users = configNode.users;
} users.splice(users.indexOf(editing_node),1);
configNode = RED.nodes.node(newValue); }
if (configNode) { configNode = RED.nodes.node(newValue);
configNode.users.push(editing_node); if (configNode) {
configNode.users.push(editing_node);
}
} }
changes[d] = editing_node[d];
editing_node[d] = newValue;
changed = true;
} }
changes[d] = editing_node[d];
editing_node[d] = newValue;
changed = true;
} }
} }
} }
} }
} if (editing_node._def.credentials) {
if (editing_node._def.credentials) { var prefix = 'node-input';
var prefix = 'node-input'; var credDefinition = editing_node._def.credentials;
var credDefinition = editing_node._def.credentials; var credsChanged = updateNodeCredentials(editing_node,credDefinition,prefix);
var credsChanged = updateNodeCredentials(editing_node,credDefinition,prefix); changed = changed || credsChanged;
changed = changed || credsChanged; }
}
var removedLinks = updateNodeProperties(editing_node); var removedLinks = updateNodeProperties(editing_node);
if (changed) { if (changed) {
var wasChanged = editing_node.changed; var wasChanged = editing_node.changed;
editing_node.changed = true; editing_node.changed = true;
RED.nodes.dirty(true); RED.nodes.dirty(true);
RED.history.push({t:'edit',node:editing_node,changes:changes,links:removedLinks,dirty:wasDirty,changed:wasChanged}); RED.history.push({t:'edit',node:editing_node,changes:changes,links:removedLinks,dirty:wasDirty,changed:wasChanged});
} }
editing_node.dirty = true; editing_node.dirty = true;
validateNode(editing_node); validateNode(editing_node);
RED.view.redraw(); RED.view.redraw();
} else if (/Export nodes to library/.test($( "#dialog" ).dialog("option","title"))) { } else if (/Export nodes to library/.test($( "#dialog" ).dialog("option","title"))) {
//TODO: move this to RED.library //TODO: move this to RED.library
var flowName = $("#node-input-filename").val(); var flowName = $("#node-input-filename").val();
if (!/^\s*$/.test(flowName)) { if (!/^\s*$/.test(flowName)) {
$.ajax({ $.ajax({
url:'library/flows/'+flowName, url:'library/flows/'+flowName,
type: "POST", type: "POST",
data: $("#node-input-filename").attr('nodes'), data: $("#node-input-filename").attr('nodes'),
contentType: "application/json; charset=utf-8" contentType: "application/json; charset=utf-8"
}).done(function() { }).done(function() {
RED.library.loadFlowLibrary(); RED.library.loadFlowLibrary();
RED.notify("Saved nodes","success"); RED.notify(RED._("library.savedNodes"),"success");
}); });
}
} }
$( this ).dialog( "close" );
} }
$( this ).dialog( "close" ); },
{
id: "node-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
if (editing_node && editing_node._def) {
if (editing_node._def.oneditcancel) {
editing_node._def.oneditcancel.call(editing_node);
}
}
$( this ).dialog( "close" );
}
}
],
resize: function(e,ui) {
if (editing_node) {
$(this).dialog('option',"sizeCache-"+editing_node.type,ui.size);
} }
}, },
{ open: function(e) {
id: "node-dialog-cancel", var minWidth = $(this).dialog('option','minWidth');
text: "Cancel", if ($(this).outerWidth() < minWidth) {
click: function() { $(this).dialog('option','width',minWidth);
if (editing_node && editing_node._def) { } else {
if (editing_node._def.oneditcancel) { $(this).dialog('option','width',$(this).outerWidth());
editing_node._def.oneditcancel.call(editing_node); }
} RED.keyboard.disable();
if (editing_node) {
var size = $(this).dialog('option','sizeCache-'+editing_node.type);
if (size) {
$(this).dialog('option','width',size.width);
$(this).dialog('option','height',size.height);
} }
$( this ).dialog( "close" );
} }
} },
], close: function(e) {
resize: function(e,ui) { RED.keyboard.enable();
if (editing_node) {
$(this).dialog('option',"sizeCache-"+editing_node.type,ui.size); if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
} RED.view.state(RED.state.DEFAULT);
},
open: function(e) {
var minWidth = $(this).dialog('option','minWidth');
if ($(this).outerWidth() < minWidth) {
$(this).dialog('option','width',minWidth);
} else {
$(this).dialog('option','width',$(this).outerWidth());
}
RED.keyboard.disable();
if (editing_node) {
var size = $(this).dialog('option','sizeCache-'+editing_node.type);
if (size) {
$(this).dialog('option','width',size.width);
$(this).dialog('option','height',size.height);
} }
} $( this ).dialog('option','height','auto');
}, $( this ).dialog('option','width','auto');
close: function(e) { if (editing_node) {
RED.keyboard.enable(); RED.sidebar.info.refresh(editing_node);
}
RED.sidebar.config.refresh();
if (RED.view.state() != RED.state.IMPORT_DRAGGING) { var buttons = $( this ).dialog("option","buttons");
RED.view.state(RED.state.DEFAULT); if (buttons.length == 3) {
$( this ).dialog("option","buttons",buttons.splice(1));
}
editing_node = null;
} }
$( this ).dialog('option','height','auto'); });
$( this ).dialog('option','width','auto'); }
if (editing_node) {
RED.sidebar.info.refresh(editing_node);
}
RED.sidebar.config.refresh();
var buttons = $( this ).dialog("option","buttons");
if (buttons.length == 3) {
$( this ).dialog("option","buttons",buttons.splice(1));
}
editing_node = null;
}
});
/** /**
* Create a config-node select box for this property * Create a config-node select box for this property
@ -392,9 +394,9 @@ RED.editor = (function() {
input.after(button); input.after(button);
if (node[property]) { if (node[property]) {
button.text("edit"); button.text(RED._("editor.configEdit"));
} else { } else {
button.text("add"); button.text(RED._("editor.configAdd"));
} }
button.click(function(e) { button.click(function(e) {
@ -565,7 +567,7 @@ RED.editor = (function() {
var buttons = $( "#dialog" ).dialog("option","buttons"); var buttons = $( "#dialog" ).dialog("option","buttons");
buttons.unshift({ buttons.unshift({
class: 'leftButton', class: 'leftButton',
text: "Edit flow", text: RED._("subflow.edit"),
click: function() { click: function() {
RED.workspaces.show(id); RED.workspaces.show(id);
$("#node-dialog-ok").click(); $("#node-dialog-ok").click();
@ -574,21 +576,44 @@ RED.editor = (function() {
$( "#dialog" ).dialog("option","buttons",buttons); $( "#dialog" ).dialog("option","buttons",buttons);
} }
$("#dialog-form").html($("script[data-template-name='"+type+"']").html()); $("#dialog-form").html($("script[data-template-name='"+type+"']").html());
var ns;
if (node._def.set.module === "node-red") {
ns = "node-red";
} else {
ns = node._def.set.id;
}
$("#dialog-form").find('[data-i18n]').each(function() {
var current = $(this).attr("data-i18n");
if (current.indexOf(":") === -1) {
var prefix = "";
if (current.indexOf("[")===0) {
var parts = current.split("]");
prefix = parts[0]+"]";
current = parts[1];
}
$(this).attr("data-i18n",prefix+ns+":"+current);
}
});
$('<input type="text" style="display: none;" />').appendTo("#dialog-form"); $('<input type="text" style="display: none;" />').appendTo("#dialog-form");
prepareEditDialog(node,node._def,"node-input"); prepareEditDialog(node,node._def,"node-input");
$("#dialog").i18n();
$( "#dialog" ).dialog("option","title","Edit "+type+" node").dialog( "open" ); $( "#dialog" ).dialog("option","title","Edit "+type+" node").dialog( "open" );
} }
function showEditConfigNodeDialog(name,type,id) { function showEditConfigNodeDialog(name,type,id) {
var adding = (id == "_ADD_"); var adding = (id == "_ADD_");
var node_def = RED.nodes.getType(type); var node_def = RED.nodes.getType(type);
var configNode = RED.nodes.node(id); var configNode = RED.nodes.node(id);
var ns;
if (node_def.set.module === "node-red") {
ns = "node-red";
} else {
ns = node_def.set.id;
}
if (configNode == null) { if (configNode == null) {
configNode = { configNode = {
id: (1+Math.random()*4294967295).toString(16), id: (1+Math.random()*4294967295).toString(16),
@ -600,9 +625,25 @@ RED.editor = (function() {
configNode[d] = node_def.defaults[d].value; configNode[d] = node_def.defaults[d].value;
} }
} }
configNode["_"] = node_def._;
} }
$("#dialog-config-form").html($("script[data-template-name='"+type+"']").html()); $("#dialog-config-form").html($("script[data-template-name='"+type+"']").html());
$("#dialog-config-form").find('[data-i18n]').each(function() {
var current = $(this).attr("data-i18n");
if (current.indexOf(":") === -1) {
var prefix = "";
if (current.indexOf("[")===0) {
var parts = current.split("]");
prefix = parts[0]+"]";
current = parts[1];
}
$(this).attr("data-i18n",prefix+ns+":"+current);
}
});
prepareEditDialog(configNode,node_def,"node-config-input"); prepareEditDialog(configNode,node_def,"node-config-input");
var buttons = $( "#node-config-dialog" ).dialog("option","buttons"); var buttons = $( "#node-config-dialog" ).dialog("option","buttons");
@ -616,7 +657,7 @@ RED.editor = (function() {
if (buttons.length == 2) { if (buttons.length == 2) {
buttons.unshift({ buttons.unshift({
class: 'leftButton', class: 'leftButton',
text: "Delete", text: RED._("editor.configDelete"),
click: function() { click: function() {
var configProperty = $(this).dialog('option','node-property'); var configProperty = $(this).dialog('option','node-property');
var configId = $(this).dialog('option','node-id'); var configId = $(this).dialog('option','node-id');
@ -648,16 +689,18 @@ RED.editor = (function() {
}); });
} }
buttons[1].text = "Update"; buttons[1].text = "Update";
$("#node-config-dialog-user-count").html(configNode.users.length+" node"+(configNode.users.length==1?" uses":"s use")+" this config").show(); $("#node-config-dialog-user-count").html(RED._("editor.nodesUse", {count:configNode.users.length})).show();
} }
$( "#node-config-dialog" ).dialog("option","buttons",buttons); $( "#node-config-dialog" ).dialog("option","buttons",buttons);
$("#node-config-dialog").i18n();
$( "#node-config-dialog" ) $( "#node-config-dialog" )
.dialog("option","node-adding",adding) .dialog("option","node-adding",adding)
.dialog("option","node-property",name) .dialog("option","node-property",name)
.dialog("option","node-id",configNode.id) .dialog("option","node-id",configNode.id)
.dialog("option","node-type",type) .dialog("option","node-type",type)
.dialog("option","title",(adding?"Add new ":"Edit ")+type+" config node") .dialog("option","title",(adding?RED._("editor.addNewConfig", {type:type}):RED._("editor.editConfig", {type:type})))
.dialog( "open" ); .dialog( "open" );
} }
@ -665,9 +708,9 @@ RED.editor = (function() {
var button = $("#node-input-edit-"+name); var button = $("#node-input-edit-"+name);
if (button.length) { if (button.length) {
if (value) { if (value) {
button.text("edit"); button.text(RED._("editor.configEdit"));
} else { } else {
button.text("add"); button.text(RED._("editor.configAdd"));
} }
$("#node-input-"+name).val(value); $("#node-input-"+name).val(value);
} else { } else {
@ -686,208 +729,211 @@ RED.editor = (function() {
select.append('<option value="'+config.id+'"'+(value==config.id?" selected":"")+'>'+label+'</option>'); select.append('<option value="'+config.id+'"'+(value==config.id?" selected":"")+'>'+label+'</option>');
} }
}); });
select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>'+RED._("editor.addNewType", {type:type})+'</option>');
select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>Add new '+type+'...</option>');
window.setTimeout(function() { select.change();},50); window.setTimeout(function() { select.change();},50);
} }
} }
$( "#node-config-dialog" ).dialog({ function createNodeConfigDialog(){
$( "#node-config-dialog" ).dialog({
modal: true,
autoOpen: false,
dialogClass: "ui-dialog-no-close",
minWidth: 500,
width: 'auto',
closeOnEscape: false,
buttons: [
{
id: "node-config-dialog-ok",
text: RED._("common.label.ok"),
click: function() {
var configProperty = $(this).dialog('option','node-property');
var configId = $(this).dialog('option','node-id');
var configType = $(this).dialog('option','node-type');
var configAdding = $(this).dialog('option','node-adding');
var configTypeDef = RED.nodes.getType(configType);
var configNode;
var d;
var input;
if (configAdding) {
configNode = {type:configType,id:configId,users:[]};
for (d in configTypeDef.defaults) {
if (configTypeDef.defaults.hasOwnProperty(d)) {
input = $("#node-config-input-"+d);
if (input.attr('type') === "checkbox") {
configNode[d] = input.prop('checked');
} else {
configNode[d] = input.val();
}
}
}
configNode.label = configTypeDef.label;
configNode._def = configTypeDef;
RED.nodes.add(configNode);
updateConfigNodeSelect(configProperty,configType,configNode.id);
} else {
configNode = RED.nodes.node(configId);
for (d in configTypeDef.defaults) {
if (configTypeDef.defaults.hasOwnProperty(d)) {
input = $("#node-config-input-"+d);
if (input.attr('type') === "checkbox") {
configNode[d] = input.prop('checked');
} else {
configNode[d] = input.val();
}
}
}
updateConfigNodeSelect(configProperty,configType,configId);
}
if (configTypeDef.credentials) {
updateNodeCredentials(configNode,configTypeDef.credentials,"node-config-input");
}
if (configTypeDef.oneditsave) {
configTypeDef.oneditsave.call(RED.nodes.node(configId));
}
validateNode(configNode);
for (var i=0;i<configNode.users.length;i++) {
var user = configNode.users[i];
validateNode(user);
}
RED.nodes.dirty(true);
$(this).dialog("close");
}
},
{
id: "node-config-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
var configType = $(this).dialog('option','node-type');
var configId = $(this).dialog('option','node-id');
var configAdding = $(this).dialog('option','node-adding');
var configTypeDef = RED.nodes.getType(configType);
if (configTypeDef.oneditcancel) {
// TODO: what to pass as this to call
if (configTypeDef.oneditcancel) {
var cn = RED.nodes.node(configId);
if (cn) {
configTypeDef.oneditcancel.call(cn,false);
} else {
configTypeDef.oneditcancel.call({id:configId},true);
}
}
}
$( this ).dialog( "close" );
}
}
],
resize: function(e,ui) {
},
open: function(e) {
var minWidth = $(this).dialog('option','minWidth');
if ($(this).outerWidth() < minWidth) {
$(this).dialog('option','width',minWidth);
}
if (RED.view.state() != RED.state.EDITING) {
RED.keyboard.disable();
}
},
close: function(e) {
$(this).dialog('option','width','auto');
$(this).dialog('option','height','auto');
$("#dialog-config-form").html("");
if (RED.view.state() != RED.state.EDITING) {
RED.keyboard.enable();
}
RED.sidebar.config.refresh();
}
});
}
function createSubflowDialog(){
$( "#subflow-dialog" ).dialog({
modal: true, modal: true,
autoOpen: false, autoOpen: false,
dialogClass: "ui-dialog-no-close", dialogClass: "ui-dialog-no-close",
closeOnEscape: false,
minWidth: 500, minWidth: 500,
width: 'auto', width: 'auto',
closeOnEscape: false,
buttons: [ buttons: [
{ {
id: "node-config-dialog-ok", id: "subflow-dialog-ok",
text: "Ok", text: RED._("common.label.ok"),
click: function() { click: function() {
var configProperty = $(this).dialog('option','node-property'); if (editing_node) {
var configId = $(this).dialog('option','node-id'); var i;
var configType = $(this).dialog('option','node-type'); var changes = {};
var configAdding = $(this).dialog('option','node-adding'); var changed = false;
var configTypeDef = RED.nodes.getType(configType); var wasDirty = RED.nodes.dirty();
var configNode;
var d;
var input;
if (configAdding) { var newName = $("#subflow-input-name").val();
configNode = {type:configType,id:configId,users:[]};
for (d in configTypeDef.defaults) { if (newName != editing_node.name) {
if (configTypeDef.defaults.hasOwnProperty(d)) { changes['name'] = editing_node.name;
input = $("#node-config-input-"+d); editing_node.name = newName;
if (input.attr('type') === "checkbox") { changed = true;
configNode[d] = input.prop('checked'); $("#menu-item-workspace-menu-"+editing_node.id.replace(".","-")).text(RED._("subflow.tabLabel",{name:newName}));
} else { }
configNode[d] = input.val();
RED.palette.refresh();
if (changed) {
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+editing_node.id) {
n.changed = true;
updateNodeProperties(n);
} }
} });
} var wasChanged = editing_node.changed;
configNode.label = configTypeDef.label; editing_node.changed = true;
configNode._def = configTypeDef; RED.nodes.dirty(true);
RED.nodes.add(configNode); var historyEvent = {
updateConfigNodeSelect(configProperty,configType,configNode.id); t:'edit',
} else { node:editing_node,
configNode = RED.nodes.node(configId); changes:changes,
for (d in configTypeDef.defaults) { dirty:wasDirty,
if (configTypeDef.defaults.hasOwnProperty(d)) { changed:wasChanged
input = $("#node-config-input-"+d); };
if (input.attr('type') === "checkbox") {
configNode[d] = input.prop('checked');
} else {
configNode[d] = input.val();
}
}
}
updateConfigNodeSelect(configProperty,configType,configId);
}
if (configTypeDef.credentials) {
updateNodeCredentials(configNode,configTypeDef.credentials,"node-config-input");
}
if (configTypeDef.oneditsave) {
configTypeDef.oneditsave.call(RED.nodes.node(configId));
}
validateNode(configNode);
for (var i=0;i<configNode.users.length;i++) {
var user = configNode.users[i];
validateNode(user);
}
RED.nodes.dirty(true); RED.history.push(historyEvent);
$(this).dialog("close");
}
},
{
id: "node-config-dialog-cancel",
text: "Cancel",
click: function() {
var configType = $(this).dialog('option','node-type');
var configId = $(this).dialog('option','node-id');
var configAdding = $(this).dialog('option','node-adding');
var configTypeDef = RED.nodes.getType(configType);
if (configTypeDef.oneditcancel) {
// TODO: what to pass as this to call
if (configTypeDef.oneditcancel) {
var cn = RED.nodes.node(configId);
if (cn) {
configTypeDef.oneditcancel.call(cn,false);
} else {
configTypeDef.oneditcancel.call({id:configId},true);
}
} }
editing_node.dirty = true;
RED.view.redraw();
} }
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
},
{
id: "subflow-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
editing_node = null;
}
} }
], ],
resize: function(e,ui) {
},
open: function(e) { open: function(e) {
RED.keyboard.disable();
var minWidth = $(this).dialog('option','minWidth'); var minWidth = $(this).dialog('option','minWidth');
if ($(this).outerWidth() < minWidth) { if ($(this).outerWidth() < minWidth) {
$(this).dialog('option','width',minWidth); $(this).dialog('option','width',minWidth);
} }
if (RED.view.state() != RED.state.EDITING) {
RED.keyboard.disable();
}
}, },
close: function(e) { close: function(e) {
$(this).dialog('option','width','auto'); RED.keyboard.enable();
$(this).dialog('option','height','auto');
$("#dialog-config-form").html(""); if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
if (RED.view.state() != RED.state.EDITING) { RED.view.state(RED.state.DEFAULT);
RED.keyboard.enable();
} }
RED.sidebar.config.refresh(); RED.sidebar.info.refresh(editing_node);
editing_node = null;
} }
}); });
$("#subflow-dialog form" ).submit(function(e) { e.preventDefault();});
$( "#subflow-dialog" ).dialog({ }
modal: true,
autoOpen: false,
dialogClass: "ui-dialog-no-close",
closeOnEscape: false,
minWidth: 500,
width: 'auto',
buttons: [
{
id: "subflow-dialog-ok",
text: "Ok",
click: function() {
if (editing_node) {
var i;
var changes = {};
var changed = false;
var wasDirty = RED.nodes.dirty();
var newName = $("#subflow-input-name").val();
if (newName != editing_node.name) {
changes['name'] = editing_node.name;
editing_node.name = newName;
changed = true;
$("#menu-item-workspace-menu-"+editing_node.id.replace(".","-")).text("Subflow: "+newName);
}
RED.palette.refresh();
if (changed) {
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+editing_node.id) {
n.changed = true;
updateNodeProperties(n);
}
});
var wasChanged = editing_node.changed;
editing_node.changed = true;
RED.nodes.dirty(true);
var historyEvent = {
t:'edit',
node:editing_node,
changes:changes,
dirty:wasDirty,
changed:wasChanged
};
RED.history.push(historyEvent);
}
editing_node.dirty = true;
RED.view.redraw();
}
$( this ).dialog( "close" );
}
},
{
id: "subflow-dialog-cancel",
text: "Cancel",
click: function() {
$( this ).dialog( "close" );
editing_node = null;
}
}
],
open: function(e) {
RED.keyboard.disable();
var minWidth = $(this).dialog('option','minWidth');
if ($(this).outerWidth() < minWidth) {
$(this).dialog('option','width',minWidth);
}
},
close: function(e) {
RED.keyboard.enable();
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
RED.view.state(RED.state.DEFAULT);
}
RED.sidebar.info.refresh(editing_node);
editing_node = null;
}
});
$("#subflow-dialog form" ).submit(function(e) { e.preventDefault();});
function showEditSubflowDialog(subflow) { function showEditSubflowDialog(subflow) {
@ -903,13 +949,18 @@ RED.editor = (function() {
} }
}); });
$("#subflow-dialog-user-count").html("There "+(userCount==1?"is":"are")+" "+userCount+" instance"+(userCount==1?" ":"s")+" of this subflow").show(); $("#subflow-dialog-user-count").html(RED._("subflow.subflowInstances", {count:userCount})).show();
$("#subflow-dialog").dialog("option","title","Edit flow "+subflow.name).dialog( "open" ); $("#subflow-dialog").dialog("option","title",RED._("subflow.editSubflow",{name:subflow.name})).dialog( "open" );
} }
return { return {
init: function(){
createDialog();
createNodeConfigDialog();
createSubflowDialog();
},
edit: showEditDialog, edit: showEditDialog,
editConfig: showEditConfigNodeDialog, editConfig: showEditConfigNodeDialog,
editSubflow: showEditSubflowDialog, editSubflow: showEditSubflowDialog,

View File

@ -69,24 +69,24 @@ RED.keyboard = (function() {
dialog = $('<div id="keyboard-help-dialog" class="hide">'+ dialog = $('<div id="keyboard-help-dialog" class="hide">'+
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+ '<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
'<table class="keyboard-shortcuts">'+ '<table class="keyboard-shortcuts">'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">a</span></td><td>Select all nodes</td></tr>'+ '<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">a</span></td><td>'+RED._("keyboard.selectAll")+'</td></tr>'+
'<tr><td><span class="help-key">Shift</span> + <span class="help-key">Click</span></td><td>Select all connected nodes</td></tr>'+ '<tr><td><span class="help-key">Shift</span> + <span class="help-key">Click</span></td><td>'+RED._("keyboard.selectAllConnected")+'</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">Click</span></td><td>Add/remove node from selection</td></tr>'+ '<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">Click</span></td><td>'+RED._("keyboard.addRemoveNode")+'</td></tr>'+
'<tr><td><span class="help-key">Delete</span></td><td>Delete selected nodes or link</td></tr>'+ '<tr><td><span class="help-key">Delete</span></td><td>'+RED._("keyboard.deleteSelected")+'</td></tr>'+
'<tr><td>&nbsp;</td><td></td></tr>'+ '<tr><td>&nbsp;</td><td></td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">i</span></td><td>Import nodes</td></tr>'+ '<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">i</span></td><td>'+RED._("keyboard.importNode")+'</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">e</span></td><td>Export selected nodes</td></tr>'+ '<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">e</span></td><td>'+RED._("keyboard.exportNode")+'</td></tr>'+
'</table>'+ '</table>'+
'</div>'+ '</div>'+
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+ '<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
'<table class="keyboard-shortcuts">'+ '<table class="keyboard-shortcuts">'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">Space</span></td><td>Toggle sidebar</td></tr>'+ '<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">Space</span></td><td>'+RED._("keyboard.toggleSidebar")+'</td></tr>'+
'<tr><td></td><td></td></tr>'+ '<tr><td></td><td></td></tr>'+
'<tr><td><span class="help-key">Delete</span></td><td>Delete selected nodes or link</td></tr>'+ '<tr><td><span class="help-key">Delete</span></td><td>'+RED._("keyboard.deleteNode")+'</td></tr>'+
'<tr><td></td><td></td></tr>'+ '<tr><td></td><td></td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">c</span></td><td>Copy selected nodes</td></tr>'+ '<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">c</span></td><td>'+RED._("keyboard.copyNode")+'</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">x</span></td><td>Cut selected nodes</td></tr>'+ '<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">x</span></td><td>'+RED._("keyboard.cutNode")+'</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">v</span></td><td>Paste nodes</td></tr>'+ '<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">v</span></td><td>'+RED._("keyboard.pasteNode")+'</td></tr>'+
'</table>'+ '</table>'+
'</div>'+ '</div>'+
'</div>') '</div>')

View File

@ -14,8 +14,8 @@
* limitations under the License. * limitations under the License.
**/ **/
RED.library = (function() { RED.library = (function() {
function loadFlowLibrary() { function loadFlowLibrary() {
$.getJSON("library/flows",function(data) { $.getJSON("library/flows",function(data) {
//console.log(data); //console.log(data);
@ -66,12 +66,12 @@ RED.library = (function() {
$("#menu-item-import-library-submenu").replaceWith(menu); $("#menu-item-import-library-submenu").replaceWith(menu);
}); });
} }
function createUI(options) { function createUI(options) {
var libraryData = {}; var libraryData = {};
var selectedLibraryItem = null; var selectedLibraryItem = null;
var libraryEditor = null; var libraryEditor = null;
// Orion editor has set/getText // Orion editor has set/getText
// ACE editor has set/getValue // ACE editor has set/getValue
// normalise to set/getValue // normalise to set/getValue
@ -84,14 +84,14 @@ RED.library = (function() {
if (options.editor.getText) { if (options.editor.getText) {
options.editor.getValue = options.editor.getText; options.editor.getValue = options.editor.getText;
} }
function buildFileListItem(item) { function buildFileListItem(item) {
var li = document.createElement("li"); var li = document.createElement("li");
li.onmouseover = function(e) { $(this).addClass("list-hover"); }; li.onmouseover = function(e) { $(this).addClass("list-hover"); };
li.onmouseout = function(e) { $(this).removeClass("list-hover"); }; li.onmouseout = function(e) { $(this).removeClass("list-hover"); };
return li; return li;
} }
function buildFileList(root,data) { function buildFileList(root,data) {
var ul = document.createElement("ul"); var ul = document.createElement("ul");
var li; var li;
@ -104,7 +104,7 @@ RED.library = (function() {
var dirName = v; var dirName = v;
return function(e) { return function(e) {
var bcli = $('<li class="active"><span class="divider">/</span> <a href="#">'+dirName+'</a></li>'); var bcli = $('<li class="active"><span class="divider">/</span> <a href="#">'+dirName+'</a></li>');
$("a",bcli).click(function(e) { $("a",bcli).click(function(e) {
$(this).parent().nextAll().remove(); $(this).parent().nextAll().remove();
$.getJSON("library/"+options.url+root+dirName,function(data) { $.getJSON("library/"+options.url+root+dirName,function(data) {
$("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data)); $("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data));
@ -141,24 +141,24 @@ RED.library = (function() {
} }
return ul; return ul;
} }
$('#node-input-name').addClass('input-append-left').css("width","65%").after( $('#node-input-name').addClass('input-append-left').css("width","65%").after(
'<div class="btn-group" style="margin-left: 0px;">'+ '<div class="btn-group" style="margin-left: 0px;">'+
'<button id="node-input-'+options.type+'-lookup" class="btn input-append-right" data-toggle="dropdown"><i class="fa fa-book"></i> <i class="fa fa-caret-down"></i></button>'+ '<button id="node-input-'+options.type+'-lookup" class="btn input-append-right" data-toggle="dropdown"><i class="fa fa-book"></i> <i class="fa fa-caret-down"></i></button>'+
'<ul class="dropdown-menu pull-right" role="menu">'+ '<ul class="dropdown-menu pull-right" role="menu">'+
'<li><a id="node-input-'+options.type+'-menu-open-library" tabindex="-1" href="#">Open Library...</a></li>'+ '<li><a id="node-input-'+options.type+'-menu-open-library" tabindex="-1" href="#">'+RED._("library.openLibrary")+'</a></li>'+
'<li><a id="node-input-'+options.type+'-menu-save-library" tabindex="-1" href="#">Save to Library...</a></li>'+ '<li><a id="node-input-'+options.type+'-menu-save-library" tabindex="-1" href="#">'+RED._("library.saveToLibrary")+'</a></li>'+
'</ul></div>' '</ul></div>'
); );
$('#node-input-'+options.type+'-menu-open-library').click(function(e) { $('#node-input-'+options.type+'-menu-open-library').click(function(e) {
$("#node-select-library").children().remove(); $("#node-select-library").children().remove();
var bc = $("#node-dialog-library-breadcrumbs"); var bc = $("#node-dialog-library-breadcrumbs");
bc.children().first().nextAll().remove(); bc.children().first().nextAll().remove();
libraryEditor.setValue('',-1); libraryEditor.setValue('',-1);
$.getJSON("library/"+options.url,function(data) { $.getJSON("library/"+options.url,function(data) {
$("#node-select-library").append(buildFileList("/",data)); $("#node-select-library").append(buildFileList("/",data));
$("#node-dialog-library-breadcrumbs a").click(function(e) { $("#node-dialog-library-breadcrumbs a").click(function(e) {
@ -168,10 +168,10 @@ RED.library = (function() {
}); });
$( "#node-dialog-library-lookup" ).dialog( "open" ); $( "#node-dialog-library-lookup" ).dialog( "open" );
}); });
e.preventDefault(); e.preventDefault();
}); });
$('#node-input-'+options.type+'-menu-save-library').click(function(e) { $('#node-input-'+options.type+'-menu-save-library').click(function(e) {
//var found = false; //var found = false;
var name = $("#node-input-name").val().replace(/(^\s*)|(\s*$)/g,""); var name = $("#node-input-name").val().replace(/(^\s*)|(\s*$)/g,"");
@ -217,7 +217,7 @@ RED.library = (function() {
$( "#node-dialog-library-save" ).dialog( "open" ); $( "#node-dialog-library-save" ).dialog( "open" );
e.preventDefault(); e.preventDefault();
}); });
libraryEditor = ace.edit('node-select-library-text'); libraryEditor = ace.edit('node-select-library-text');
libraryEditor.setTheme("ace/theme/tomorrow"); libraryEditor.setTheme("ace/theme/tomorrow");
if (options.mode) { if (options.mode) {
@ -230,16 +230,16 @@ RED.library = (function() {
}); });
libraryEditor.renderer.$cursorLayer.element.style.opacity=0; libraryEditor.renderer.$cursorLayer.element.style.opacity=0;
libraryEditor.$blockScrolling = Infinity; libraryEditor.$blockScrolling = Infinity;
$( "#node-dialog-library-lookup" ).dialog({ $( "#node-dialog-library-lookup" ).dialog({
title: options.type+" library", title: RED._("library.typeLibrary", {type:options.type}),
modal: true, modal: true,
autoOpen: false, autoOpen: false,
width: 800, width: 800,
height: 450, height: 450,
buttons: [ buttons: [
{ {
text: "Ok", text: RED._("common.label.ok"),
click: function() { click: function() {
if (selectedLibraryItem) { if (selectedLibraryItem) {
for (var i=0;i<options.fields.length;i++) { for (var i=0;i<options.fields.length;i++) {
@ -252,7 +252,7 @@ RED.library = (function() {
} }
}, },
{ {
text: "Cancel", text: RED._("common.label.cancel"),
click: function() { click: function() {
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
@ -270,16 +270,16 @@ RED.library = (function() {
$(".form-row:last-child",form).children().height(form.height()-60); $(".form-row:last-child",form).children().height(form.height()-60);
} }
}); });
function saveToLibrary(overwrite) { function saveToLibrary(overwrite) {
var name = $("#node-input-name").val().replace(/(^\s*)|(\s*$)/g,""); var name = $("#node-input-name").val().replace(/(^\s*)|(\s*$)/g,"");
if (name === "") { if (name === "") {
name = "Unnamed "+options.type; name = RED._("library.unnamedType",{type:options.type});
} }
var filename = $("#node-dialog-library-save-filename").val().replace(/(^\s*)|(\s*$)/g,""); var filename = $("#node-dialog-library-save-filename").val().replace(/(^\s*)|(\s*$)/g,"");
var pathname = $("#node-dialog-library-save-folder").val().replace(/(^\s*)|(\s*$)/g,""); var pathname = $("#node-dialog-library-save-folder").val().replace(/(^\s*)|(\s*$)/g,"");
if (filename === "" || !/.+\.js$/.test(filename)) { if (filename === "" || !/.+\.js$/.test(filename)) {
RED.notify("Invalid filename","warning"); RED.notify(RED._("library.invalidFilename"),"warning");
return; return;
} }
var fullpath = pathname+(pathname===""?"":"/")+filename; var fullpath = pathname+(pathname===""?"":"/")+filename;
@ -304,8 +304,7 @@ RED.library = (function() {
// } // }
//} //}
//if (exists) { //if (exists) {
// $("#node-dialog-library-save-type").html(options.type); // $("#node-dialog-library-save-content").html(RED._("library.dialogSaveOverwrite",{libraryType:options.type,libraryName:fullpath}));
// $("#node-dialog-library-save-name").html(fullpath);
// $("#node-dialog-library-save-confirm").dialog( "open" ); // $("#node-dialog-library-save-confirm").dialog( "open" );
// return; // return;
//} //}
@ -320,7 +319,7 @@ RED.library = (function() {
data[field] = $("#node-input-"+field).val(); data[field] = $("#node-input-"+field).val();
} }
} }
data.text = options.editor.getValue(); data.text = options.editor.getValue();
$.ajax({ $.ajax({
url:"library/"+options.url+'/'+fullpath, url:"library/"+options.url+'/'+fullpath,
@ -328,27 +327,27 @@ RED.library = (function() {
data: JSON.stringify(data), data: JSON.stringify(data),
contentType: "application/json; charset=utf-8" contentType: "application/json; charset=utf-8"
}).done(function(data,textStatus,xhr) { }).done(function(data,textStatus,xhr) {
RED.notify("Saved "+options.type,"success"); RED.notify(RED._("library.savedType", {type:options.type}),"success");
}).fail(function(xhr,textStatus,err) { }).fail(function(xhr,textStatus,err) {
RED.notify("Saved failed: "+xhr.responseJSON.message,"error"); RED.notify(RED._("library.saveFailed",{message:xhr.responseJSON.message}),"error");
}); });
} }
$( "#node-dialog-library-save-confirm" ).dialog({ $( "#node-dialog-library-save-confirm" ).dialog({
title: "Save to library", title: RED._("library.saveToLibrary"),
modal: true, modal: true,
autoOpen: false, autoOpen: false,
width: 530, width: 530,
height: 230, height: 230,
buttons: [ buttons: [
{ {
text: "Ok", text: RED._("common.label.ok"),
click: function() { click: function() {
saveToLibrary(true); saveToLibrary(true);
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
}, },
{ {
text: "Cancel", text: RED._("common.label.cancel"),
click: function() { click: function() {
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
@ -356,21 +355,21 @@ RED.library = (function() {
] ]
}); });
$( "#node-dialog-library-save" ).dialog({ $( "#node-dialog-library-save" ).dialog({
title: "Save to library", title: RED._("library.saveToLibrary"),
modal: true, modal: true,
autoOpen: false, autoOpen: false,
width: 530, width: 530,
height: 230, height: 230,
buttons: [ buttons: [
{ {
text: "Ok", text: RED._("common.label.ok"),
click: function() { click: function() {
saveToLibrary(false); saveToLibrary(false);
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
}, },
{ {
text: "Cancel", text: RED._("common.label.cancel"),
click: function() { click: function() {
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
@ -379,15 +378,16 @@ RED.library = (function() {
}); });
} }
function exportFlow() { function exportFlow() {
//TODO: don't rely on the main dialog //TODO: don't rely on the main dialog
var nns = RED.nodes.createExportableNodeSet(RED.view.selection().nodes); var nns = RED.nodes.createExportableNodeSet(RED.view.selection().nodes);
$("#dialog-form").html($("script[data-template-name='export-library-dialog']").html()); $("#dialog-form").html($("script[data-template-name='export-library-dialog']").html());
$("#node-input-filename").attr('nodes',JSON.stringify(nns)); $("#node-input-filename").attr('nodes',JSON.stringify(nns));
$( "#dialog" ).dialog("option","title","Export nodes to library").dialog( "open" ); $("#dialog").i18n();
$("#dialog").dialog("option","title",RED._("library.exportToLibrary")).dialog( "open" );
} }
return { return {
init: function() { init: function() {
RED.view.on("selection-changed",function(selection) { RED.view.on("selection-changed",function(selection) {
@ -401,16 +401,14 @@ RED.library = (function() {
RED.menu.setDisabled("menu-item-export-library",false); RED.menu.setDisabled("menu-item-export-library",false);
} }
}); });
if (RED.settings.theme("menu.menu-item-import-library") !== false) { if (RED.settings.theme("menu.menu-item-import-library") !== false) {
loadFlowLibrary(); loadFlowLibrary();
} }
}, },
create: createUI, create: createUI,
loadFlowLibrary: loadFlowLibrary, loadFlowLibrary: loadFlowLibrary,
export: exportFlow export: exportFlow
} }
})(); })();

View File

@ -19,10 +19,10 @@ RED.palette = (function() {
var exclusion = ['config','unknown','deprecated']; var exclusion = ['config','unknown','deprecated'];
var core = ['subflows', 'input', 'output', 'function', 'social', 'storage', 'analysis', 'advanced']; var core = ['subflows', 'input', 'output', 'function', 'social', 'storage', 'analysis', 'advanced'];
function createCategoryContainer(category){ function createCategoryContainer(category, label){
var escapedCategory = category.replace(" ","_"); label = label || category.replace("_", " ");
var catDiv = $("#palette-container").append('<div id="palette-container-'+category+'" class="palette-category hide">'+ var catDiv = $("#palette-container").append('<div id="palette-container-'+category+'" class="palette-category hide">'+
'<div id="palette-header-'+category+'" class="palette-header"><i class="expanded fa fa-caret-down"></i><span>'+category.replace("_"," ")+'</span></div>'+ '<div id="palette-header-'+category+'" class="palette-header"><i class="expanded fa fa-caret-down"></i><span>'+label+'</span></div>'+
'<div class="palette-content" id="palette-base-category-'+category+'">'+ '<div class="palette-content" id="palette-base-category-'+category+'">'+
'<div id="palette-'+category+'-input"></div>'+ '<div id="palette-'+category+'-input"></div>'+
'<div id="palette-'+category+'-output"></div>'+ '<div id="palette-'+category+'-output"></div>'+
@ -77,16 +77,17 @@ RED.palette = (function() {
if (label != type) { if (label != type) {
l = "<p><b>"+label+"</b><br/><i>"+type+"</i></p>"; l = "<p><b>"+label+"</b><br/><i>"+type+"</i></p>";
} }
popOverContent = $(l+($("script[data-help-name|='"+type+"']").html()||"<p>no information available</p>").trim()) popOverContent = $(l+($("script[data-help-name|='"+type+"']").html()||"<p>"+RED._("palette.noInfo")+"</p>").trim())
.filter(function(n) { .filter(function(n) {
return this.nodeType == 1 || (this.nodeType == 3 && this.textContent.trim().length > 0) return this.nodeType == 1 || (this.nodeType == 3 && this.textContent.trim().length > 0)
}).slice(0,2); }).slice(0,2);
} catch(err) { } catch(err) {
// Malformed HTML may cause errors. TODO: need to understand what can break // Malformed HTML may cause errors. TODO: need to understand what can break
console.log("Error generating pop-over label for '"+type+"'."); // NON-NLS: internal debug
console.log("Error generating pop-over label for ",type);
console.log(err.toString()); console.log(err.toString());
popOverContent = "<p><b>"+label+"</b></p><p>no information available</p>"; popOverContent = "<p><b>"+label+"</b></p><p>"+RED._("palette.noInfo")+"</p>";
} }
@ -120,12 +121,12 @@ RED.palette = (function() {
label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||""; label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||"";
} }
$('<div/>',{class:"palette_label"+(def.align=="right"?" palette_label_right":"")}).appendTo(d); $('<div/>',{class:"palette_label"+(def.align=="right"?" palette_label_right":"")}).appendTo(d);
d.className="palette_node"; d.className="palette_node";
if (def.icon) { if (def.icon) {
var icon_url = (typeof def.icon === "function" ? def.icon.call({}) : def.icon); var icon_url = (typeof def.icon === "function" ? def.icon.call({}) : def.icon);
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);
@ -147,7 +148,12 @@ RED.palette = (function() {
} }
if ($("#palette-base-category-"+rootCategory).length === 0) { if ($("#palette-base-category-"+rootCategory).length === 0) {
createCategoryContainer(rootCategory); if(core.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(); $("#palette-container-"+rootCategory).show();
@ -178,7 +184,7 @@ RED.palette = (function() {
revertDuration: 50, revertDuration: 50,
start: function() {RED.view.focus();} start: function() {RED.view.focus();}
}); });
if (def.category == "subflows") { if (def.category == "subflows") {
$(d).dblclick(function(e) { $(d).dblclick(function(e) {
RED.workspaces.show(nt.substring(8)); RED.workspaces.show(nt.substring(8));
@ -187,7 +193,7 @@ RED.palette = (function() {
} }
setLabel(nt,$(d),label); setLabel(nt,$(d),label);
var categoryNode = $("#palette-container-"+category); var categoryNode = $("#palette-container-"+category);
if (categoryNode.find(".palette_node").length === 1) { if (categoryNode.find(".palette_node").length === 1) {
if (!categoryNode.find("i").hasClass("expanded")) { if (!categoryNode.find("i").hasClass("expanded")) {
@ -195,7 +201,7 @@ RED.palette = (function() {
categoryNode.find("i").toggleClass("expanded"); categoryNode.find("i").toggleClass("expanded");
} }
} }
} }
} }
@ -268,30 +274,34 @@ RED.palette = (function() {
function init() { function init() {
$(".palette-spinner").show(); $(".palette-spinner").show();
if (RED.settings.paletteCategories) { if (RED.settings.paletteCategories) {
RED.settings.paletteCategories.forEach(createCategoryContainer); RED.settings.paletteCategories.forEach(function(category){
createCategoryContainer(category, RED._("palette.label."+category,{defaultValue:category}));
});
} else { } else {
core.forEach(createCategoryContainer); core.forEach(function(category){
createCategoryContainer(category, RED._("palette.label."+category,{defaultValue:category}));
});
} }
$("#palette-search-input").focus(function(e) { $("#palette-search-input").focus(function(e) {
RED.keyboard.disable(); RED.keyboard.disable();
}); });
$("#palette-search-input").blur(function(e) { $("#palette-search-input").blur(function(e) {
RED.keyboard.enable(); RED.keyboard.enable();
}); });
$("#palette-search-clear").on("click",function(e) { $("#palette-search-clear").on("click",function(e) {
e.preventDefault(); e.preventDefault();
$("#palette-search-input").val(""); $("#palette-search-input").val("");
filterChange(); filterChange();
$("#palette-search-input").focus(); $("#palette-search-input").focus();
}); });
$("#palette-search-input").val(""); $("#palette-search-input").val("");
$("#palette-search-input").on("keyup",function() { $("#palette-search-input").on("keyup",function() {
filterChange(); filterChange();
}); });
$("#palette-search-input").on("focus",function() { $("#palette-search-input").on("focus",function() {
$("body").one("mousedown",function() { $("body").one("mousedown",function() {
$("#palette-search-input").blur(); $("#palette-search-input").blur();

View File

@ -30,7 +30,8 @@ RED.sidebar = (function() {
function addTab(title,content,closeable) { function addTab(title,content,closeable) {
$("#sidebar-content").append(content); $("#sidebar-content").append(content);
$(content).hide(); $(content).hide();
sidebar_tabs.addTab({id:"tab-"+title,label:title,closeable:closeable}); var id = content.id || "tab-"+title.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, "\\$1" );
sidebar_tabs.addTab({id:id,label:title,closeable:closeable});
//content.style.position = "absolute"; //content.style.position = "absolute";
//$('#sidebar').tabs("refresh"); //$('#sidebar').tabs("refresh");
} }

View File

@ -15,12 +15,12 @@
**/ **/
RED.subflow = (function() { RED.subflow = (function() {
function getSubflow() { function getSubflow() {
return RED.nodes.subflow(RED.workspaces.active()); return RED.nodes.subflow(RED.workspaces.active());
} }
function findAvailableSubflowIOPosition(subflow) { function findAvailableSubflowIOPosition(subflow) {
var pos = {x:70,y:70}; var pos = {x:70,y:70};
for (var i=0;i<subflow.out.length+subflow.in.length;i++) { for (var i=0;i<subflow.out.length+subflow.in.length;i++) {
@ -37,7 +37,7 @@ RED.subflow = (function() {
} }
return pos; return pos;
} }
function addSubflowInput() { function addSubflowInput() {
var subflow = RED.nodes.subflow(RED.workspaces.active()); var subflow = RED.nodes.subflow(RED.workspaces.active());
var position = findAvailableSubflowIOPosition(subflow); var position = findAvailableSubflowIOPosition(subflow);
@ -56,7 +56,7 @@ RED.subflow = (function() {
var wasDirty = RED.nodes.dirty(); var wasDirty = RED.nodes.dirty();
var wasChanged = subflow.changed; var wasChanged = subflow.changed;
subflow.changed = true; subflow.changed = true;
RED.nodes.eachNode(function(n) { RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+subflow.id) { if (n.type == "subflow:"+subflow.id) {
n.changed = true; n.changed = true;
@ -77,11 +77,11 @@ RED.subflow = (function() {
$("#workspace-subflow-add-input").toggleClass("disabled",true); $("#workspace-subflow-add-input").toggleClass("disabled",true);
RED.view.select(); RED.view.select();
} }
function addSubflowOutput(id) { function addSubflowOutput(id) {
var subflow = RED.nodes.subflow(RED.workspaces.active()); var subflow = RED.nodes.subflow(RED.workspaces.active());
var position = findAvailableSubflowIOPosition(subflow); var position = findAvailableSubflowIOPosition(subflow);
var newOutput = { var newOutput = {
type:"subflow", type:"subflow",
direction:"out", direction:"out",
@ -97,7 +97,7 @@ RED.subflow = (function() {
var wasDirty = RED.nodes.dirty(); var wasDirty = RED.nodes.dirty();
var wasChanged = subflow.changed; var wasChanged = subflow.changed;
subflow.changed = true; subflow.changed = true;
RED.nodes.eachNode(function(n) { RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+subflow.id) { if (n.type == "subflow:"+subflow.id) {
n.changed = true; n.changed = true;
@ -117,7 +117,7 @@ RED.subflow = (function() {
RED.history.push(historyEvent); RED.history.push(historyEvent);
RED.view.select(); RED.view.select();
} }
function init() { function init() {
$("#workspace-subflow-edit").click(function(event) { $("#workspace-subflow-edit").click(function(event) {
RED.editor.editSubflow(RED.nodes.subflow(RED.workspaces.active())); RED.editor.editSubflow(RED.nodes.subflow(RED.workspaces.active()));
@ -137,13 +137,13 @@ RED.subflow = (function() {
} }
addSubflowOutput(); addSubflowOutput();
}); });
$("#workspace-subflow-delete").click(function(event) { $("#workspace-subflow-delete").click(function(event) {
event.preventDefault(); event.preventDefault();
var removedNodes = []; var removedNodes = [];
var removedLinks = []; var removedLinks = [];
var startDirty = RED.nodes.dirty(); var startDirty = RED.nodes.dirty();
RED.nodes.eachNode(function(n) { RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+getSubflow().id) { if (n.type == "subflow:"+getSubflow().id) {
removedNodes.push(n); removedNodes.push(n);
@ -152,16 +152,16 @@ RED.subflow = (function() {
removedNodes.push(n); removedNodes.push(n);
} }
}); });
for (var i=0;i<removedNodes.length;i++) { for (var i=0;i<removedNodes.length;i++) {
var rmlinks = RED.nodes.remove(removedNodes[i].id); var rmlinks = RED.nodes.remove(removedNodes[i].id);
removedLinks = removedLinks.concat(rmlinks); removedLinks = removedLinks.concat(rmlinks);
} }
var activeSubflow = getSubflow(); var activeSubflow = getSubflow();
RED.nodes.removeSubflow(activeSubflow); RED.nodes.removeSubflow(activeSubflow);
RED.history.push({ RED.history.push({
t:'delete', t:'delete',
nodes:removedNodes, nodes:removedNodes,
@ -169,12 +169,12 @@ RED.subflow = (function() {
subflow: activeSubflow, subflow: activeSubflow,
dirty:startDirty dirty:startDirty
}); });
RED.workspaces.remove(activeSubflow); RED.workspaces.remove(activeSubflow);
RED.nodes.dirty(true); RED.nodes.dirty(true);
RED.view.redraw(); RED.view.redraw();
}); });
RED.view.on("selection-changed",function(selection) { RED.view.on("selection-changed",function(selection) {
if (!selection.nodes) { if (!selection.nodes) {
RED.menu.setDisabled("menu-item-subflow-convert",true); RED.menu.setDisabled("menu-item-subflow-convert",true);
@ -182,9 +182,9 @@ RED.subflow = (function() {
RED.menu.setDisabled("menu-item-subflow-convert",false); RED.menu.setDisabled("menu-item-subflow-convert",false);
} }
}); });
} }
function createSubflow() { function createSubflow() {
var lastIndex = 0; var lastIndex = 0;
RED.nodes.eachSubflow(function(sf) { RED.nodes.eachSubflow(function(sf) {
@ -193,9 +193,9 @@ RED.subflow = (function() {
lastIndex = Math.max(lastIndex,m[1]); lastIndex = Math.max(lastIndex,m[1]);
} }
}); });
var name = "Subflow "+(lastIndex+1); var name = "Subflow "+(lastIndex+1);
var subflowId = RED.nodes.id(); var subflowId = RED.nodes.id();
var subflow = { var subflow = {
type:"subflow", type:"subflow",
@ -212,26 +212,26 @@ RED.subflow = (function() {
}); });
RED.workspaces.show(subflowId); RED.workspaces.show(subflowId);
} }
function convertToSubflow() { function convertToSubflow() {
var selection = RED.view.selection(); var selection = RED.view.selection();
if (!selection.nodes) { if (!selection.nodes) {
RED.notify("<strong>Cannot create subflow</strong>: no nodes selected","error"); RED.notify(RED._("subflow.errors.noNodesSelected"),"error");
return; return;
} }
var i; var i;
var nodes = {}; var nodes = {};
var new_links = []; var new_links = [];
var removedLinks = []; var removedLinks = [];
var candidateInputs = []; var candidateInputs = [];
var candidateOutputs = []; var candidateOutputs = [];
var boundingBox = [selection.nodes[0].x, var boundingBox = [selection.nodes[0].x,
selection.nodes[0].y, selection.nodes[0].y,
selection.nodes[0].x, selection.nodes[0].x,
selection.nodes[0].y]; selection.nodes[0].y];
for (i=0;i<selection.nodes.length;i++) { for (i=0;i<selection.nodes.length;i++) {
var n = selection.nodes[i]; var n = selection.nodes[i];
nodes[n.id] = {n:n,outputs:{}}; nodes[n.id] = {n:n,outputs:{}};
@ -242,14 +242,14 @@ RED.subflow = (function() {
Math.max(boundingBox[3],n.y) Math.max(boundingBox[3],n.y)
] ]
} }
var center = [(boundingBox[2]+boundingBox[0]) / 2,(boundingBox[3]+boundingBox[1]) / 2]; var center = [(boundingBox[2]+boundingBox[0]) / 2,(boundingBox[3]+boundingBox[1]) / 2];
RED.nodes.eachLink(function(link) { RED.nodes.eachLink(function(link) {
if (nodes[link.source.id] && nodes[link.target.id]) { if (nodes[link.source.id] && nodes[link.target.id]) {
// A link wholely within the selection // A link wholely within the selection
} }
if (nodes[link.source.id] && !nodes[link.target.id]) { if (nodes[link.source.id] && !nodes[link.target.id]) {
// An outbound link from the selection // An outbound link from the selection
candidateOutputs.push(link); candidateOutputs.push(link);
@ -261,7 +261,7 @@ RED.subflow = (function() {
removedLinks.push(link); removedLinks.push(link);
} }
}); });
var outputs = {}; var outputs = {};
candidateOutputs = candidateOutputs.filter(function(v) { candidateOutputs = candidateOutputs.filter(function(v) {
if (outputs[v.source.id+":"+v.sourcePort]) { if (outputs[v.source.id+":"+v.sourcePort]) {
@ -274,17 +274,17 @@ RED.subflow = (function() {
return true; return true;
}); });
candidateOutputs.sort(function(a,b) { return a.source.y-b.source.y}); candidateOutputs.sort(function(a,b) { return a.source.y-b.source.y});
if (candidateInputs.length > 1) { if (candidateInputs.length > 1) {
RED.notify("<strong>Cannot create subflow</strong>: multiple inputs to selection","error"); RED.notify(RED._("subflow.errors.multipleInputsToSelection"),"error");
return; return;
} }
//if (candidateInputs.length == 0) { //if (candidateInputs.length == 0) {
// RED.notify("<strong>Cannot create subflow</strong>: no input to selection","error"); // RED.notify("<strong>Cannot create subflow</strong>: no input to selection","error");
// return; // return;
//} //}
var lastIndex = 0; var lastIndex = 0;
RED.nodes.eachSubflow(function(sf) { RED.nodes.eachSubflow(function(sf) {
var m = (new RegExp("^Subflow (\\d+)$")).exec(sf.name); var m = (new RegExp("^Subflow (\\d+)$")).exec(sf.name);
@ -292,9 +292,9 @@ RED.subflow = (function() {
lastIndex = Math.max(lastIndex,m[1]); lastIndex = Math.max(lastIndex,m[1]);
} }
}); });
var name = "Subflow "+(lastIndex+1); var name = "Subflow "+(lastIndex+1);
var subflowId = RED.nodes.id(); var subflowId = RED.nodes.id();
var subflow = { var subflow = {
type:"subflow", type:"subflow",
@ -322,7 +322,7 @@ RED.subflow = (function() {
}}) }})
}; };
RED.nodes.addSubflow(subflow); RED.nodes.addSubflow(subflow);
var subflowInstance = { var subflowInstance = {
id:RED.nodes.id(), id:RED.nodes.id(),
type:"subflow:"+subflow.id, type:"subflow:"+subflow.id,
@ -337,13 +337,13 @@ RED.subflow = (function() {
subflowInstance._def = RED.nodes.getType(subflowInstance.type); subflowInstance._def = RED.nodes.getType(subflowInstance.type);
RED.editor.validateNode(subflowInstance); RED.editor.validateNode(subflowInstance);
RED.nodes.add(subflowInstance); RED.nodes.add(subflowInstance);
candidateInputs.forEach(function(l) { candidateInputs.forEach(function(l) {
var link = {source:l.source, sourcePort:l.sourcePort, target: subflowInstance}; var link = {source:l.source, sourcePort:l.sourcePort, target: subflowInstance};
new_links.push(link); new_links.push(link);
RED.nodes.addLink(link); RED.nodes.addLink(link);
}); });
candidateOutputs.forEach(function(output,i) { candidateOutputs.forEach(function(output,i) {
output.targets.forEach(function(target) { output.targets.forEach(function(target) {
var link = {source:subflowInstance, sourcePort:i, target: target}; var link = {source:subflowInstance, sourcePort:i, target: target};
@ -351,7 +351,7 @@ RED.subflow = (function() {
RED.nodes.addLink(link); RED.nodes.addLink(link);
}); });
}); });
subflow.in.forEach(function(input) { subflow.in.forEach(function(input) {
input.wires.forEach(function(wire) { input.wires.forEach(function(wire) {
var link = {source: input, sourcePort: 0, target: RED.nodes.node(wire.id) } var link = {source: input, sourcePort: 0, target: RED.nodes.node(wire.id) }
@ -366,34 +366,34 @@ RED.subflow = (function() {
RED.nodes.addLink(link); RED.nodes.addLink(link);
}); });
}); });
for (i=0;i<removedLinks.length;i++) { for (i=0;i<removedLinks.length;i++) {
RED.nodes.removeLink(removedLinks[i]); RED.nodes.removeLink(removedLinks[i]);
} }
for (i=0;i<selection.nodes.length;i++) { for (i=0;i<selection.nodes.length;i++) {
selection.nodes[i].z = subflow.id; selection.nodes[i].z = subflow.id;
} }
RED.history.push({ RED.history.push({
t:'createSubflow', t:'createSubflow',
nodes:[subflowInstance.id], nodes:[subflowInstance.id],
links:new_links, links:new_links,
subflow: subflow, subflow: subflow,
activeWorkspace: RED.workspaces.active(), activeWorkspace: RED.workspaces.active(),
removedLinks: removedLinks, removedLinks: removedLinks,
dirty:RED.nodes.dirty() dirty:RED.nodes.dirty()
}); });
RED.editor.validateNode(subflow); RED.editor.validateNode(subflow);
RED.nodes.dirty(true); RED.nodes.dirty(true);
RED.view.redraw(true); RED.view.redraw(true);
} }
return { return {
init: init, init: init,
createSubflow: createSubflow, createSubflow: createSubflow,

View File

@ -14,23 +14,23 @@
* limitations under the License. * limitations under the License.
**/ **/
RED.sidebar.config = (function() { RED.sidebar.config = (function() {
var content = document.createElement("div"); var content = document.createElement("div");
content.id = "tab-config"; content.id = "tab-config";
content.style.paddingTop = "4px"; content.style.paddingTop = "4px";
content.style.paddingLeft = "4px"; content.style.paddingLeft = "4px";
content.style.paddingRight = "4px"; content.style.paddingRight = "4px";
var list = $("<ul>",{class:"tab-config-list"}).appendTo(content); var list = $("<ul>",{class:"tab-config-list"}).appendTo(content);
function show() { function show() {
if (!RED.sidebar.containsTab("config")) { if (!RED.sidebar.containsTab("config")) {
RED.sidebar.addTab("config",content,true); RED.sidebar.addTab(RED._("sidebar.config.title"),content,true);
} }
refresh(); refresh();
RED.sidebar.show("config"); RED.sidebar.show("config");
} }
function refresh() { function refresh() {
list.empty(); list.empty();
RED.nodes.eachConfig(function(node) { RED.nodes.eachConfig(function(node) {
@ -46,12 +46,12 @@ RED.sidebar.config = (function() {
label = node._def.label; label = node._def.label;
} }
label = label || "&nbsp;"; label = label || "&nbsp;";
var entry = $('<div class="tab-config-list-entry"></div>').appendTo(li); var entry = $('<div class="tab-config-list-entry"></div>').appendTo(li);
entry.on('dblclick',function(e) { entry.on('dblclick',function(e) {
RED.editor.editConfig("", node.type, node.id); RED.editor.editConfig("", node.type, node.id);
}); });
var userArray = node.users.map(function(n) { return n.id }); var userArray = node.users.map(function(n) { return n.id });
entry.on('mouseover',function(e) { entry.on('mouseover',function(e) {
RED.nodes.eachNode(function(node) { RED.nodes.eachNode(function(node) {
@ -72,7 +72,7 @@ RED.sidebar.config = (function() {
}); });
RED.view.redraw(); RED.view.redraw();
}); });
$('<div class="tab-config-list-label">'+label+'</div>').appendTo(entry); $('<div class="tab-config-list-label">'+label+'</div>').appendTo(entry);
$('<div class="tab-config-list-users">'+node.users.length+'</div>').appendTo(entry); $('<div class="tab-config-list-users">'+node.users.length+'</div>').appendTo(entry);
}); });

View File

@ -33,10 +33,10 @@ RED.sidebar.info = (function() {
content.style.paddingRight = "4px"; content.style.paddingRight = "4px";
var propertiesExpanded = false; var propertiesExpanded = false;
function show() { function show() {
if (!RED.sidebar.containsTab("info")) { if (!RED.sidebar.containsTab("info")) {
RED.sidebar.addTab("info",content,false); RED.sidebar.addTab(RED._("sidebar.info.title"),content,false);
} }
RED.sidebar.show("info"); RED.sidebar.show("info");
} }
@ -60,13 +60,13 @@ RED.sidebar.info = (function() {
function refresh(node) { function refresh(node) {
var table = '<table class="node-info"><tbody>'; var table = '<table class="node-info"><tbody>';
table += '<tr class="blank"><td colspan="2">Node</td></tr>'; table += '<tr class="blank"><td colspan="2">'+RED._("sidebar.info.node")+'</td></tr>';
if (node.type != "subflow" && node.name) { if (node.type != "subflow" && node.name) {
table += "<tr><td>Name</td><td>&nbsp;"+node.name+"</td></tr>"; table += "<tr><td>"+RED._("common.label.name")+"</td><td>&nbsp;"+node.name+"</td></tr>";
} }
table += "<tr><td>Type</td><td>&nbsp;"+node.type+"</td></tr>"; table += "<tr><td>"+RED._("sidebar.info.type")+"</td><td>&nbsp;"+node.type+"</td></tr>";
table += "<tr><td>ID</td><td>&nbsp;"+node.id+"</td></tr>"; table += "<tr><td>"+RED._("sidebar.info.id")+"</td><td>&nbsp;"+node.id+"</td></tr>";
var m = /^subflow(:(.+))?$/.exec(node.type); var m = /^subflow(:(.+))?$/.exec(node.type);
if (m) { if (m) {
var subflowNode; var subflowNode;
@ -75,9 +75,9 @@ RED.sidebar.info = (function() {
} else { } else {
subflowNode = node; subflowNode = node;
} }
table += '<tr class="blank"><td colspan="2">Subflow</td></tr>'; table += '<tr class="blank"><td colspan="2">'+RED._("sidebar.info.subflow")+'</td></tr>';
var userCount = 0; var userCount = 0;
var subflowType = "subflow:"+subflowNode.id; var subflowType = "subflow:"+subflowNode.id;
RED.nodes.eachNode(function(n) { RED.nodes.eachNode(function(n) {
@ -85,12 +85,12 @@ RED.sidebar.info = (function() {
userCount++; userCount++;
} }
}); });
table += "<tr><td>name</td><td>"+subflowNode.name+"</td></tr>"; table += "<tr><td>"+RED._("common.label.name")+"</td><td>"+subflowNode.name+"</td></tr>";
table += "<tr><td>instances</td><td>"+userCount+"</td></tr>"; table += "<tr><td>"+RED._("sidebar.info.instances")+"</td><td>"+userCount+"</td></tr>";
} }
if (!m && node.type != "subflow" && node.type != "comment") { if (!m && node.type != "subflow" && node.type != "comment") {
table += '<tr class="blank"><td colspan="2"><a href="#" class="node-info-property-header"><i style="width: 10px; text-align: center;" class="fa fa-caret-'+(propertiesExpanded?"down":"right")+'"></i> Properties</a></td></tr>'; table += '<tr class="blank"><td colspan="2"><a href="#" class="node-info-property-header"><i style="width: 10px; text-align: center;" class="fa fa-caret-'+(propertiesExpanded?"down":"right")+'"></i> '+RED._("sidebar.info.properties")+'</a></td></tr>';
if (node._def) { if (node._def) {
for (var n in node._def.defaults) { for (var n in node._def.defaults) {
if (n != "name" && node._def.defaults.hasOwnProperty(n)) { if (n != "name" && node._def.defaults.hasOwnProperty(n)) {
@ -98,7 +98,7 @@ RED.sidebar.info = (function() {
var type = typeof val; var type = typeof val;
if (type === "string") { if (type === "string") {
if (val.length === 0) { if (val.length === 0) {
val += '<span style="font-style: italic; color: #ccc;">blank</span>'; val += '<span style="font-style: italic; color: #ccc;">'+RED._("sidebar.info.blank")+'</span>';
} else { } else {
if (val.length > 30) { if (val.length > 30) {
val = val.substring(0,30)+" ..."; val = val.substring(0,30)+" ...";
@ -114,14 +114,14 @@ RED.sidebar.info = (function() {
val += "&nbsp;"+i+": "+vv+"<br/>"; val += "&nbsp;"+i+": "+vv+"<br/>";
} }
if (node[n].length > 10) { if (node[n].length > 10) {
val += "&nbsp;... "+node[n].length+" items<br/>"; val += "&nbsp;... "+RED._("sidebar.info.arrayItems",{count:node[n].length})+"<br/>";
} }
val += "]"; val += "]";
} else { } else {
val = JSON.stringify(val,jsonFilter," "); val = JSON.stringify(val,jsonFilter," ");
val = val.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"); val = val.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
} }
table += '<tr class="node-info-property-row'+(propertiesExpanded?"":" hide")+'"><td>'+n+"</td><td>"+val+"</td></tr>"; table += '<tr class="node-info-property-row'+(propertiesExpanded?"":" hide")+'"><td>'+n+"</td><td>"+val+"</td></tr>";
} }
} }
@ -140,7 +140,7 @@ RED.sidebar.info = (function() {
} }
$("#tab-info").html(table); $("#tab-info").html(table);
$(".node-info-property-header").click(function(e) { $(".node-info-property-header").click(function(e) {
var icon = $(this).find("i"); var icon = $(this).find("i");
if (icon.hasClass("fa-caret-right")) { if (icon.hasClass("fa-caret-right")) {
@ -154,15 +154,15 @@ RED.sidebar.info = (function() {
$(".node-info-property-row").hide(); $(".node-info-property-row").hide();
propertiesExpanded = false; propertiesExpanded = false;
} }
e.preventDefault(); e.preventDefault();
}); });
} }
function clear() { function clear() {
$("#tab-info").html(""); $("#tab-info").html("");
} }
RED.view.on("selection-changed",function(selection) { RED.view.on("selection-changed",function(selection) {
if (selection.nodes) { if (selection.nodes) {
if (selection.nodes.length == 1) { if (selection.nodes.length == 1) {

View File

@ -35,7 +35,7 @@ RED.view = (function() {
var activeSubflow = null; var activeSubflow = null;
var activeNodes = []; var activeNodes = [];
var activeLinks = []; var activeLinks = [];
var selected_link = null, var selected_link = null,
mousedown_link = null, mousedown_link = null,
mousedown_node = null, mousedown_node = null,
@ -73,7 +73,7 @@ RED.view = (function() {
.on("mousedown", function() { .on("mousedown", function() {
$(this).focus(); $(this).focus();
}); });
var vis = outer var vis = outer
.append('svg:g') .append('svg:g')
.on("dblclick.zoom", null) .on("dblclick.zoom", null)
@ -233,9 +233,9 @@ RED.view = (function() {
function updateActiveNodes() { function updateActiveNodes() {
var activeWorkspace = RED.workspaces.active(); var activeWorkspace = RED.workspaces.active();
activeNodes = RED.nodes.filterNodes({z:activeWorkspace}); activeNodes = RED.nodes.filterNodes({z:activeWorkspace});
activeLinks = RED.nodes.filterLinks({ activeLinks = RED.nodes.filterLinks({
source:{z:activeWorkspace}, source:{z:activeWorkspace},
target:{z:activeWorkspace} target:{z:activeWorkspace}
@ -253,15 +253,15 @@ RED.view = (function() {
} }
var scrollStartLeft = chart.scrollLeft(); var scrollStartLeft = chart.scrollLeft();
var scrollStartTop = chart.scrollTop(); var scrollStartTop = chart.scrollTop();
activeSubflow = RED.nodes.subflow(event.workspace); activeSubflow = RED.nodes.subflow(event.workspace);
if (activeSubflow) { if (activeSubflow) {
$("#workspace-subflow-add-input").toggleClass("disabled",activeSubflow.in.length > 0); $("#workspace-subflow-add-input").toggleClass("disabled",activeSubflow.in.length > 0);
} }
RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow); RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow);
RED.menu.setDisabled("menu-item-workspace-delete",RED.workspaces.count() == 1 || activeSubflow); RED.menu.setDisabled("menu-item-workspace-delete",RED.workspaces.count() == 1 || activeSubflow);
if (workspaceScrollPositions[event.workspace]) { if (workspaceScrollPositions[event.workspace]) {
chart.scrollLeft(workspaceScrollPositions[event.workspace].left); chart.scrollLeft(workspaceScrollPositions[event.workspace].left);
chart.scrollTop(workspaceScrollPositions[event.workspace].top); chart.scrollTop(workspaceScrollPositions[event.workspace].top);
@ -283,7 +283,7 @@ RED.view = (function() {
updateActiveNodes(); updateActiveNodes();
redraw(); redraw();
}); });
$('#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();});
@ -296,50 +296,50 @@ RED.view = (function() {
else { zoomIn(); } else { zoomIn(); }
} }
}); });
// Handle nodes dragged from the palette // Handle nodes dragged from the palette
$("#chart").droppable({ $("#chart").droppable({
accept:".palette_node", accept:".palette_node",
drop: function( event, ui ) { drop: function( event, ui ) {
d3.event = event; d3.event = event;
var selected_tool = ui.draggable[0].type; var selected_tool = ui.draggable[0].type;
var m = /^subflow:(.+)$/.exec(selected_tool); var m = /^subflow:(.+)$/.exec(selected_tool);
if (activeSubflow && m) { if (activeSubflow && m) {
var subflowId = m[1]; var subflowId = m[1];
if (subflowId === activeSubflow.id) { if (subflowId === activeSubflow.id) {
RED.notify("<strong>Error</strong>: Cannot add subflow to itself","error"); RED.notify(RED._("notification.error",{message: RED._("notification.errors.cannotAddSubflowToItself")}),"error");
return; return;
} }
if (RED.nodes.subflowContains(m[1],activeSubflow.id)) { if (RED.nodes.subflowContains(m[1],activeSubflow.id)) {
RED.notify("<strong>Error</strong>: Cannot add subflow - circular reference detected","error"); RED.notify(RED._("notification.error",{message: RED._("notification.errors.cannotAddCircularReference")}),"error");
return; return;
} }
} }
var mousePos = d3.touches(this)[0]||d3.mouse(this); var mousePos = d3.touches(this)[0]||d3.mouse(this);
mousePos[1] += this.scrollTop; mousePos[1] += this.scrollTop;
mousePos[0] += this.scrollLeft; mousePos[0] += this.scrollLeft;
mousePos[1] /= scaleFactor; mousePos[1] /= scaleFactor;
mousePos[0] /= scaleFactor; mousePos[0] /= scaleFactor;
var nn = { id:(1+Math.random()*4294967295).toString(16),x: mousePos[0],y:mousePos[1],w:node_width,z:RED.workspaces.active()}; var nn = { id:(1+Math.random()*4294967295).toString(16),x: mousePos[0],y:mousePos[1],w:node_width,z:RED.workspaces.active()};
nn.type = selected_tool; nn.type = selected_tool;
nn._def = RED.nodes.getType(nn.type); nn._def = RED.nodes.getType(nn.type);
if (!m) { if (!m) {
nn.inputs = nn._def.inputs || 0; nn.inputs = nn._def.inputs || 0;
nn.outputs = nn._def.outputs; nn.outputs = nn._def.outputs;
for (var d in nn._def.defaults) { for (var d in nn._def.defaults) {
if (nn._def.defaults.hasOwnProperty(d)) { if (nn._def.defaults.hasOwnProperty(d)) {
nn[d] = nn._def.defaults[d].value; nn[d] = nn._def.defaults[d].value;
} }
} }
if (nn._def.onadd) { if (nn._def.onadd) {
nn._def.onadd.call(nn); nn._def.onadd.call(nn);
} }
@ -348,7 +348,7 @@ RED.view = (function() {
nn.inputs = subflow.in.length; nn.inputs = subflow.in.length;
nn.outputs = subflow.out.length; nn.outputs = subflow.out.length;
} }
nn.changed = true; nn.changed = true;
nn.h = Math.max(node_height,(nn.outputs||0) * 15); nn.h = Math.max(node_height,(nn.outputs||0) * 15);
RED.history.push({t:'add',nodes:[nn.id],dirty:RED.nodes.dirty()}); RED.history.push({t:'add',nodes:[nn.id],dirty:RED.nodes.dirty()});
@ -362,13 +362,13 @@ RED.view = (function() {
updateActiveNodes(); updateActiveNodes();
updateSelection(); updateSelection();
redraw(); redraw();
if (nn._def.autoedit) { if (nn._def.autoedit) {
RED.editor.edit(nn); RED.editor.edit(nn);
} }
} }
}); });
RED.keyboard.add(/* z */ 90,{ctrl:true},function(){RED.history.pop();}); RED.keyboard.add(/* z */ 90,{ctrl:true},function(){RED.history.pop();});
RED.keyboard.add(/* a */ 65,{ctrl:true},function(){selectAll();d3.event.preventDefault();}); RED.keyboard.add(/* a */ 65,{ctrl:true},function(){selectAll();d3.event.preventDefault();});
RED.keyboard.add(/* = */ 187,{ctrl:true},function(){zoomIn();d3.event.preventDefault();}); RED.keyboard.add(/* = */ 187,{ctrl:true},function(){zoomIn();d3.event.preventDefault();});
@ -652,7 +652,7 @@ RED.view = (function() {
} }
}); });
} }
selected_link = null; selected_link = null;
updateSelection(); updateSelection();
redraw(); redraw();
@ -693,17 +693,17 @@ RED.view = (function() {
} }
var selection = {}; var selection = {};
if (moving_set.length > 0) { if (moving_set.length > 0) {
selection.nodes = moving_set.map(function(n) { return n.n;}); selection.nodes = moving_set.map(function(n) { return n.n;});
} }
if (selected_link != null) { if (selected_link != null) {
selection.link = selected_link; selection.link = selected_link;
} }
eventHandler.emit("selection-changed",selection); eventHandler.emit("selection-changed",selection);
} }
function endKeyboardMove() { function endKeyboardMove() {
var ns = []; var ns = [];
for (var i=0;i<moving_set.length;i++) { for (var i=0;i<moving_set.length;i++) {
@ -746,7 +746,7 @@ RED.view = (function() {
var removedLinks = []; var removedLinks = [];
var removedSubflowOutputs = []; var removedSubflowOutputs = [];
var removedSubflowInputs = []; var removedSubflowInputs = [];
var startDirty = RED.nodes.dirty(); var startDirty = RED.nodes.dirty();
if (moving_set.length > 0) { if (moving_set.length > 0) {
for (var i=0;i<moving_set.length;i++) { for (var i=0;i<moving_set.length;i++) {
@ -789,7 +789,7 @@ RED.view = (function() {
}); });
subflowRemovedLinks.forEach(function(l) { RED.nodes.removeLink(l)}); subflowRemovedLinks.forEach(function(l) { RED.nodes.removeLink(l)});
subflowMovedLinks.forEach(function(l) { l.sourcePort--; }); subflowMovedLinks.forEach(function(l) { l.sourcePort--; });
removedLinks = removedLinks.concat(subflowRemovedLinks); removedLinks = removedLinks.concat(subflowRemovedLinks);
for (var j=output.i;j<activeSubflow.out.length;j++) { for (var j=output.i;j<activeSubflow.out.length;j++) {
activeSubflow.out[j].i--; activeSubflow.out[j].i--;
@ -813,7 +813,7 @@ RED.view = (function() {
activeSubflow.in = []; activeSubflow.in = [];
$("#workspace-subflow-add-input").toggleClass("disabled",false); $("#workspace-subflow-add-input").toggleClass("disabled",false);
} }
if (activeSubflow) { if (activeSubflow) {
RED.nodes.filterNodes({type:"subflow:"+activeSubflow.id}).forEach(function(n) { RED.nodes.filterNodes({type:"subflow:"+activeSubflow.id}).forEach(function(n) {
n.changed = true; n.changed = true;
@ -827,7 +827,7 @@ RED.view = (function() {
}); });
RED.editor.validateNode(activeSubflow); RED.editor.validateNode(activeSubflow);
} }
moving_set = []; moving_set = [];
if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0) { if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0) {
RED.nodes.dirty(true); RED.nodes.dirty(true);
@ -856,7 +856,7 @@ RED.view = (function() {
} }
} }
clipboard = JSON.stringify(nns); clipboard = JSON.stringify(nns);
RED.notify(nns.length+" node"+(nns.length>1?"s":"")+" copied"); RED.notify(RED._("clipboard.nodeCopied",{count:nns.length}));
} }
} }
@ -1043,9 +1043,9 @@ RED.view = (function() {
redraw(); redraw();
} }
} else if (d.changed) { } else if (d.changed) {
RED.notify("<strong>Warning</strong>: node has undeployed changes","warning"); RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.undeployedChanges")}),"warning");
} else { } else {
RED.notify("<strong>Warning</strong>: node actions disabled within subflow","warning"); RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabled")}),"warning");
} }
d3.event.preventDefault(); d3.event.preventDefault();
} }
@ -1070,9 +1070,9 @@ RED.view = (function() {
// Don't bother redrawing nodes if we're drawing links // Don't bother redrawing nodes if we're drawing links
if (mouse_mode != RED.state.JOINING) { if (mouse_mode != RED.state.JOINING) {
var dirtyNodes = {}; var dirtyNodes = {};
if (activeSubflow) { if (activeSubflow) {
var subflowOutputs = vis.selectAll(".subflowoutput").data(activeSubflow.out,function(d,i){ return d.id;}); var subflowOutputs = vis.selectAll(".subflowoutput").data(activeSubflow.out,function(d,i){ return d.id;});
subflowOutputs.exit().remove(); subflowOutputs.exit().remove();
@ -1082,7 +1082,7 @@ RED.view = (function() {
d.h=40; d.h=40;
}); });
outGroup.append("rect").attr("class","subflowport").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40) outGroup.append("rect").attr("class","subflowport").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40)
// TODO: This is exactly the same set of handlers used for regular nodes - DRY // TODO: This is exactly the same set of handlers used for regular nodes - DRY
.on("mouseup",nodeMouseUp) .on("mouseup",nodeMouseUp)
.on("mousedown",nodeMouseDown) .on("mousedown",nodeMouseDown)
.on("touchstart",function(d) { .on("touchstart",function(d) {
@ -1094,7 +1094,7 @@ RED.view = (function() {
touchStartTime = setTimeout(function() { touchStartTime = setTimeout(function() {
showTouchMenu(obj,pos); showTouchMenu(obj,pos);
},touchLongPressTimeout); },touchLongPressTimeout);
nodeMouseDown.call(this,d) nodeMouseDown.call(this,d)
}) })
.on("touchend", function(d) { .on("touchend", function(d) {
clearTimeout(touchStartTime); clearTimeout(touchStartTime);
@ -1105,7 +1105,7 @@ RED.view = (function() {
} }
nodeMouseUp.call(this,d); nodeMouseUp.call(this,d);
}); });
outGroup.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10).attr("x",-5).attr("y",15) outGroup.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10).attr("x",-5).attr("y",15)
.on("mousedown", function(d,i){portMouseDown(d,1,0);} ) .on("mousedown", function(d,i){portMouseDown(d,1,0);} )
.on("touchstart", function(d,i){portMouseDown(d,1,0);} ) .on("touchstart", function(d,i){portMouseDown(d,1,0);} )
@ -1125,7 +1125,7 @@ RED.view = (function() {
d.h=40; d.h=40;
}); });
inGroup.append("rect").attr("class","subflowport").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40) inGroup.append("rect").attr("class","subflowport").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40)
// TODO: This is exactly the same set of handlers used for regular nodes - DRY // TODO: This is exactly the same set of handlers used for regular nodes - DRY
.on("mouseup",nodeMouseUp) .on("mouseup",nodeMouseUp)
.on("mousedown",nodeMouseDown) .on("mousedown",nodeMouseDown)
.on("touchstart",function(d) { .on("touchstart",function(d) {
@ -1137,7 +1137,7 @@ RED.view = (function() {
touchStartTime = setTimeout(function() { touchStartTime = setTimeout(function() {
showTouchMenu(obj,pos); showTouchMenu(obj,pos);
},touchLongPressTimeout); },touchLongPressTimeout);
nodeMouseDown.call(this,d) nodeMouseDown.call(this,d)
}) })
.on("touchend", function(d) { .on("touchend", function(d) {
clearTimeout(touchStartTime); clearTimeout(touchStartTime);
@ -1148,7 +1148,7 @@ RED.view = (function() {
} }
nodeMouseUp.call(this,d); nodeMouseUp.call(this,d);
}); });
inGroup.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10).attr("x",35).attr("y",15) inGroup.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10).attr("x",35).attr("y",15)
.on("mousedown", function(d,i){portMouseDown(d,0,i);} ) .on("mousedown", function(d,i){portMouseDown(d,0,i);} )
.on("touchstart", function(d,i){portMouseDown(d,0,i);} ) .on("touchstart", function(d,i){portMouseDown(d,0,i);} )
@ -1157,9 +1157,9 @@ RED.view = (function() {
.on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type !== 0 ));}) .on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type !== 0 ));})
.on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);}); .on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);});
inGroup.append("svg:text").attr('class','port_label').attr('x',18).attr('y',20).style("font-size","10px").text("input"); inGroup.append("svg:text").attr('class','port_label').attr('x',18).attr('y',20).style("font-size","10px").text("input");
subflowOutputs.each(function(d,i) { subflowOutputs.each(function(d,i) {
if (d.dirty) { if (d.dirty) {
var output = d3.select(this); var output = d3.select(this);
@ -1183,7 +1183,7 @@ RED.view = (function() {
vis.selectAll(".subflowoutput").remove(); vis.selectAll(".subflowoutput").remove();
vis.selectAll(".subflowinput").remove(); vis.selectAll(".subflowinput").remove();
} }
var node = vis.selectAll(".nodegroup").data(activeNodes,function(d){return d.id}); var node = vis.selectAll(".nodegroup").data(activeNodes,function(d){return d.id});
node.exit().remove(); node.exit().remove();
@ -1318,7 +1318,7 @@ RED.view = (function() {
//icon.attr('class','node_icon_shade node_icon_shade_'+d._def.align); //icon.attr('class','node_icon_shade node_icon_shade_'+d._def.align);
//icon.attr('class','node_icon_shade_border node_icon_shade_border_'+d._def.align); //icon.attr('class','node_icon_shade_border node_icon_shade_border_'+d._def.align);
} }
//if (d.inputs > 0 && d._def.align == null) { //if (d.inputs > 0 && d._def.align == null) {
// icon_shade.attr("width",35); // icon_shade.attr("width",35);
// icon.attr("transform","translate(5,0)"); // icon.attr("transform","translate(5,0)");
@ -1388,7 +1388,7 @@ RED.view = (function() {
var thisNode = d3.select(this); var thisNode = d3.select(this);
//thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}}); //thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}});
thisNode.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; }); thisNode.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; });
if (mouse_mode != RED.state.MOVING_ACTIVE) { if (mouse_mode != RED.state.MOVING_ACTIVE) {
thisNode.selectAll(".node") thisNode.selectAll(".node")
.attr("width",function(d){return d.w}) .attr("width",function(d){return d.w})
@ -1398,13 +1398,13 @@ RED.view = (function() {
; ;
//thisNode.selectAll(".node-gradient-top").attr("width",function(d){return d.w}); //thisNode.selectAll(".node-gradient-top").attr("width",function(d){return d.w});
//thisNode.selectAll(".node-gradient-bottom").attr("width",function(d){return d.w}).attr("y",function(d){return d.h-30}); //thisNode.selectAll(".node-gradient-bottom").attr("width",function(d){return d.w}).attr("y",function(d){return d.h-30});
thisNode.selectAll(".node_icon_group_right").attr('transform', function(d){return "translate("+(d.w-30)+",0)"}); thisNode.selectAll(".node_icon_group_right").attr('transform', function(d){return "translate("+(d.w-30)+",0)"});
thisNode.selectAll(".node_label_right").attr('x', function(d){return d.w-38}); thisNode.selectAll(".node_label_right").attr('x', function(d){return d.w-38});
//thisNode.selectAll(".node_icon_right").attr("x",function(d){return d.w-d3.select(this).attr("width")-1-(d.outputs>0?5:0);}); //thisNode.selectAll(".node_icon_right").attr("x",function(d){return d.w-d3.select(this).attr("width")-1-(d.outputs>0?5:0);});
//thisNode.selectAll(".node_icon_shade_right").attr("x",function(d){return d.w-30;}); //thisNode.selectAll(".node_icon_shade_right").attr("x",function(d){return d.w-30;});
//thisNode.selectAll(".node_icon_shade_border_right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)}); //thisNode.selectAll(".node_icon_shade_border_right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)});
var inputPorts = thisNode.selectAll(".port_input"); var inputPorts = thisNode.selectAll(".port_input");
if (d.inputs === 0 && !inputPorts.empty()) { if (d.inputs === 0 && !inputPorts.empty()) {
inputPorts.remove(); inputPorts.remove();
@ -1419,13 +1419,13 @@ RED.view = (function() {
.on("mouseover",function(d) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type != 1 ));}) .on("mouseover",function(d) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type != 1 ));})
.on("mouseout",function(d) { var port = d3.select(this); port.classed("port_hovered",false);}) .on("mouseout",function(d) { var port = d3.select(this); port.classed("port_hovered",false);})
} }
var numOutputs = d.outputs; var numOutputs = d.outputs;
var y = (d.h/2)-((numOutputs-1)/2)*13; var y = (d.h/2)-((numOutputs-1)/2)*13;
d.ports = d.ports || d3.range(numOutputs); d.ports = d.ports || d3.range(numOutputs);
d._ports = thisNode.selectAll(".port_output").data(d.ports); d._ports = thisNode.selectAll(".port_output").data(d.ports);
var output_group = d._ports.enter().append("g").attr("class","port_output"); var output_group = d._ports.enter().append("g").attr("class","port_output");
output_group.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10) output_group.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10)
.on("mousedown",(function(){var node = d; return function(d,i){portMouseDown(node,0,i);}})() ) .on("mousedown",(function(){var node = d; return function(d,i){portMouseDown(node,0,i);}})() )
.on("touchstart",(function(){var node = d; return function(d,i){portMouseDown(node,0,i);}})() ) .on("touchstart",(function(){var node = d; return function(d,i){portMouseDown(node,0,i);}})() )
@ -1433,7 +1433,7 @@ RED.view = (function() {
.on("touchend",(function(){var node = d; return function(d,i){portMouseUp(node,0,i);}})() ) .on("touchend",(function(){var node = d; return function(d,i){portMouseUp(node,0,i);}})() )
.on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type !== 0 ));}) .on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type !== 0 ));})
.on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);}); .on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);});
d._ports.exit().remove(); d._ports.exit().remove();
if (d._ports) { if (d._ports) {
numOutputs = d.outputs || 1; numOutputs = d.outputs || 1;
@ -1461,7 +1461,7 @@ RED.view = (function() {
(d._def.align?' node_label_'+d._def.align:'')+ (d._def.align?' node_label_'+d._def.align:'')+
(d._def.labelStyle?' '+(typeof d._def.labelStyle == "function" ? d._def.labelStyle.call(d):d._def.labelStyle):'') ; (d._def.labelStyle?' '+(typeof d._def.labelStyle == "function" ? d._def.labelStyle.call(d):d._def.labelStyle):'') ;
}); });
if (d._def.icon) { if (d._def.icon) {
icon = thisNode.select(".node_icon"); icon = thisNode.select(".node_icon");
var current_url = icon.attr("xlink:href"); var current_url = icon.attr("xlink:href");
@ -1482,27 +1482,27 @@ RED.view = (function() {
} }
} }
} }
thisNode.selectAll(".node_tools").attr("x",function(d){return d.w-35;}).attr("y",function(d){return d.h-20;}); thisNode.selectAll(".node_tools").attr("x",function(d){return d.w-35;}).attr("y",function(d){return d.h-20;});
thisNode.selectAll(".node_changed") thisNode.selectAll(".node_changed")
.attr("x",function(d){return d.w-10}) .attr("x",function(d){return d.w-10})
.classed("hidden",function(d) { return !d.changed; }); .classed("hidden",function(d) { return !d.changed; });
thisNode.selectAll(".node_error") thisNode.selectAll(".node_error")
.attr("x",function(d){return d.w-10-(d.changed?13:0)}) .attr("x",function(d){return d.w-10-(d.changed?13:0)})
.classed("hidden",function(d) { return d.valid; }); .classed("hidden",function(d) { return d.valid; });
thisNode.selectAll(".port_input").each(function(d,i) { thisNode.selectAll(".port_input").each(function(d,i) {
var port = d3.select(this); var port = d3.select(this);
port.attr("transform",function(d){return "translate(-5,"+((d.h/2)-5)+")";}) port.attr("transform",function(d){return "translate(-5,"+((d.h/2)-5)+")";})
}); });
thisNode.selectAll(".node_icon").attr("y",function(d){return (d.h-d3.select(this).attr("height"))/2;}); thisNode.selectAll(".node_icon").attr("y",function(d){return (d.h-d3.select(this).attr("height"))/2;});
thisNode.selectAll(".node_icon_shade").attr("height",function(d){return d.h;}); thisNode.selectAll(".node_icon_shade").attr("height",function(d){return d.h;});
thisNode.selectAll(".node_icon_shade_border").attr("d",function(d){ return "M "+(("right" == d._def.align) ?0:30)+" 1 l 0 "+(d.h-2)}); thisNode.selectAll(".node_icon_shade_border").attr("d",function(d){ return "M "+(("right" == d._def.align) ?0:30)+" 1 l 0 "+(d.h-2)});
thisNode.selectAll('.node_button').attr("opacity",function(d) { thisNode.selectAll('.node_button').attr("opacity",function(d) {
return (activeSubflow||d.changed)?0.4:1 return (activeSubflow||d.changed)?0.4:1
}); });
@ -1522,11 +1522,11 @@ RED.view = (function() {
} }
return 1; return 1;
}); });
//thisNode.selectAll('.node_right_button').attr("transform",function(d){return "translate("+(d.w - d._def.button.width.call(d))+","+0+")";}).attr("fill",function(d) { //thisNode.selectAll('.node_right_button').attr("transform",function(d){return "translate("+(d.w - d._def.button.width.call(d))+","+0+")";}).attr("fill",function(d) {
// return typeof d._def.button.color === "function" ? d._def.button.color.call(d):(d._def.button.color != null ? d._def.button.color : d._def.color) // return typeof d._def.button.color === "function" ? d._def.button.color.call(d):(d._def.button.color != null ? d._def.button.color : d._def.color)
//}); //});
thisNode.selectAll('.node_badge_group').attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";}); thisNode.selectAll('.node_badge_group').attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";});
thisNode.selectAll('text.node_badge_label').text(function(d,i) { thisNode.selectAll('text.node_badge_label').text(function(d,i) {
if (d._def.badge) { if (d._def.badge) {
@ -1581,7 +1581,7 @@ RED.view = (function() {
} }
); );
var linkEnter = link.enter().insert("g",".node").attr("class","link"); var linkEnter = link.enter().insert("g",".node").attr("class","link");
linkEnter.each(function(d,i) { linkEnter.each(function(d,i) {
var l = d3.select(this); var l = d3.select(this);
d.added = true; d.added = true;
@ -1608,7 +1608,7 @@ RED.view = (function() {
l.append("svg:path").attr("class","link_line link_path") l.append("svg:path").attr("class","link_line link_path")
.classed("link_subflow", function(d) { return activeSubflow && (d.source.type === "subflow" || d.target.type === "subflow") }); .classed("link_subflow", function(d) { return activeSubflow && (d.source.type === "subflow" || d.target.type === "subflow") });
}); });
link.exit().remove(); link.exit().remove();
var links = vis.selectAll(".link_path"); var links = vis.selectAll(".link_path");
links.each(function(d) { links.each(function(d) {
@ -1618,7 +1618,7 @@ 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 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 dx = (d.target.x-d.target.w/2)-(d.source.x+d.source.w/2);
var delta = Math.sqrt(dy*dy+dx*dx); var delta = Math.sqrt(dy*dy+dx*dx);
@ -1627,19 +1627,19 @@ RED.view = (function() {
if (delta < node_width) { if (delta < node_width) {
scale = 0.75-0.75*((node_width-delta)/node_width); scale = 0.75-0.75*((node_width-delta)/node_width);
} }
if (dx < 0) { if (dx < 0) {
scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width)); scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width));
if (Math.abs(dy) < 3*node_height) { 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)) ; 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.source.x+d.source.w/2)+" "+(d.source.y+y)+ return "M "+(d.source.x+d.source.w/2)+" "+(d.source.y+y)+
" C "+(d.source.x+d.source.w/2+scale*node_width)+" "+(d.source.y+y+scaleY*node_height)+" "+ " C "+(d.source.x+d.source.w/2+scale*node_width)+" "+(d.source.y+y+scaleY*node_height)+" "+
(d.target.x-d.target.w/2-scale*node_width)+" "+(d.target.y-scaleY*node_height)+" "+ (d.target.x-d.target.w/2-scale*node_width)+" "+(d.target.y-scaleY*node_height)+" "+
@ -1647,9 +1647,9 @@ RED.view = (function() {
}); });
} }
}) })
link.classed("link_selected", function(d) { return d === selected_link || d.selected; }); link.classed("link_selected", function(d) { return d === selected_link || d.selected; });
link.classed("link_unknown",function(d) { link.classed("link_unknown",function(d) {
delete d.added; delete d.added;
return d.target.type == "unknown" || d.source.type == "unknown" return d.target.type == "unknown" || d.source.type == "unknown"
}); });
@ -1662,12 +1662,12 @@ RED.view = (function() {
} }
).classed("link_selected", false); ).classed("link_selected", false);
} }
if (d3.event) { if (d3.event) {
d3.event.preventDefault(); d3.event.preventDefault();
} }
} }
function focusView() { function focusView() {
@ -1688,7 +1688,7 @@ RED.view = (function() {
var new_links = result[1]; var new_links = result[1];
var new_workspaces = result[2]; var new_workspaces = result[2];
var new_subflows = result[3]; var new_subflows = result[3];
var new_ms = new_nodes.filter(function(n) { return n.z == RED.workspaces.active() }).map(function(n) { return {n:n};}); var new_ms = new_nodes.filter(function(n) { return n.z == RED.workspaces.active() }).map(function(n) { return {n:n};});
var new_node_ids = new_nodes.map(function(n){ return n.id; }); var new_node_ids = new_nodes.map(function(n){ return n.id; });
@ -1754,9 +1754,9 @@ RED.view = (function() {
} catch(error) { } catch(error) {
if (error.code != "NODE_RED") { if (error.code != "NODE_RED") {
console.log(error.stack); console.log(error.stack);
RED.notify("<strong>Error</strong>: "+error,"error"); RED.notify(RED._("notification.error")+error,"error");
} else { } else {
RED.notify("<strong>Error</strong>: "+error.message,"error"); RED.notify(RED._("notification.error")+error.message,"error");
} }
} }
} }
@ -1764,7 +1764,7 @@ RED.view = (function() {
// TODO: DRY // TODO: DRY
var eventHandler = (function() { var eventHandler = (function() {
var handlers = {}; var handlers = {};
return { return {
on: function(evt,func) { on: function(evt,func) {
handlers[evt] = handlers[evt]||[]; handlers[evt] = handlers[evt]||[];
@ -1775,12 +1775,12 @@ RED.view = (function() {
for (var i=0;i<handlers[evt].length;i++) { for (var i=0;i<handlers[evt].length;i++) {
handlers[evt][i](arg); handlers[evt][i](arg);
} }
} }
} }
} }
})(); })();
return { return {
init: init, init: init,
on: eventHandler.on, on: eventHandler.on,
@ -1791,13 +1791,13 @@ RED.view = (function() {
mouse_mode = state; mouse_mode = state;
} }
}, },
redraw: function(updateActive) { redraw: function(updateActive) {
if (updateActive) { if (updateActive) {
updateActiveNodes(); updateActiveNodes();
} }
RED.workspaces.refresh(); RED.workspaces.refresh();
redraw(); redraw();
}, },
focus: focusView, focus: focusView,
importNodes: importNodes, importNodes: importNodes,

View File

@ -16,7 +16,7 @@
RED.workspaces = (function() { RED.workspaces = (function() {
var activeWorkspace = 0; var activeWorkspace = 0;
var workspaceIndex = 0; var workspaceIndex = 0;
@ -29,7 +29,7 @@ RED.workspaces = (function() {
do { do {
workspaceIndex += 1; workspaceIndex += 1;
} while($("#workspace-tabs a[title='Sheet "+workspaceIndex+"']").size() !== 0); } while($("#workspace-tabs a[title='Sheet "+workspaceIndex+"']").size() !== 0);
ws = {type:"tab",id:tabId,label:"Sheet "+workspaceIndex}; ws = {type:"tab",id:tabId,label:"Sheet "+workspaceIndex};
RED.nodes.addWorkspace(ws); RED.nodes.addWorkspace(ws);
workspace_tabs.addTab(ws); workspace_tabs.addTab(ws);
@ -56,10 +56,10 @@ RED.workspaces = (function() {
RED.nodes.dirty(true); RED.nodes.dirty(true);
} else { } else {
$( "#node-dialog-delete-workspace" ).dialog('option','workspace',ws); $( "#node-dialog-delete-workspace" ).dialog('option','workspace',ws);
$( "#node-dialog-delete-workspace-name" ).text(ws.label); $( "#node-dialog-delete-workspace-content" ).text(RED._("workspace.delete",{label:ws.label}));
$( "#node-dialog-delete-workspace" ).dialog('open'); $( "#node-dialog-delete-workspace" ).dialog('open');
} }
} }
function showRenameWorkspaceDialog(id) { function showRenameWorkspaceDialog(id) {
var ws = RED.nodes.workspace(id); var ws = RED.nodes.workspace(id);
$( "#node-dialog-rename-workspace" ).dialog("option","workspace",ws); $( "#node-dialog-rename-workspace" ).dialog("option","workspace",ws);
@ -77,133 +77,138 @@ RED.workspaces = (function() {
$( "#node-input-workspace-name" ).val(ws.label); $( "#node-input-workspace-name" ).val(ws.label);
$( "#node-dialog-rename-workspace" ).dialog("open"); $( "#node-dialog-rename-workspace" ).dialog("open");
} }
var workspace_tabs = RED.tabs.create({
id: "workspace-tabs",
onchange: function(tab) {
if (tab.type == "subflow") {
$("#workspace-toolbar").show();
} else {
$("#workspace-toolbar").hide();
}
var event = {
old: activeWorkspace
}
activeWorkspace = tab.id;
event.workspace = activeWorkspace;
eventHandler.emit("change",event);
},
ondblclick: function(tab) {
if (tab.type != "subflow") {
showRenameWorkspaceDialog(tab.id);
} else {
RED.editor.editSubflow(RED.nodes.subflow(tab.id));
}
},
onadd: function(tab) {
RED.menu.addItem("menu-item-workspace",{
id:"menu-item-workspace-menu-"+tab.id.replace(".","-"),
label:tab.label,
onselect:function() {
workspace_tabs.activateTab(tab.id);
}
});
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
},
onremove: function(tab) {
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
RED.menu.removeItem("menu-item-workspace-menu-"+tab.id.replace(".","-"));
}
});
$("#node-dialog-rename-workspace form" ).submit(function(e) { e.preventDefault();});
$( "#node-dialog-rename-workspace" ).dialog({
modal: true,
autoOpen: false,
width: 500,
title: "Rename sheet",
buttons: [
{
class: 'leftButton',
text: "Delete",
click: function() {
var workspace = $(this).dialog('option','workspace');
$( this ).dialog( "close" );
deleteWorkspace(workspace);
}
},
{
text: "Ok",
click: function() {
var workspace = $(this).dialog('option','workspace');
var label = $( "#node-input-workspace-name" ).val();
if (workspace.label != label) {
workspace_tabs.renameTab(workspace.id,label);
RED.nodes.dirty(true);
$("#menu-item-workspace-menu-"+workspace.id.replace(".","-")).text(label);
// TODO: update entry in menu
}
$( this ).dialog( "close" );
}
},
{
text: "Cancel",
click: function() {
$( this ).dialog( "close" );
}
}
],
open: function(e) {
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
}
});
$( "#node-dialog-delete-workspace" ).dialog({
modal: true,
autoOpen: false,
width: 500,
title: "Confirm delete",
buttons: [
{
text: "Ok",
click: function() {
var workspace = $(this).dialog('option','workspace');
deleteWorkspace(workspace,true);
$( this ).dialog( "close" );
}
},
{
text: "Cancel",
click: function() {
$( this ).dialog( "close" );
}
}
],
open: function(e) {
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
}
}); var workspace_tabs;
function createWorkspaceTabs(){
workspace_tabs = RED.tabs.create({
id: "workspace-tabs",
onchange: function(tab) {
if (tab.type == "subflow") {
$("#workspace-toolbar").show();
} else {
$("#workspace-toolbar").hide();
}
var event = {
old: activeWorkspace
}
activeWorkspace = tab.id;
event.workspace = activeWorkspace;
eventHandler.emit("change",event);
},
ondblclick: function(tab) {
if (tab.type != "subflow") {
showRenameWorkspaceDialog(tab.id);
} else {
RED.editor.editSubflow(RED.nodes.subflow(tab.id));
}
},
onadd: function(tab) {
RED.menu.addItem("menu-item-workspace",{
id:"menu-item-workspace-menu-"+tab.id.replace(".","-"),
label:tab.label,
onselect:function() {
workspace_tabs.activateTab(tab.id);
}
});
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
},
onremove: function(tab) {
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
RED.menu.removeItem("menu-item-workspace-menu-"+tab.id.replace(".","-"));
}
});
$("#node-dialog-rename-workspace form" ).submit(function(e) { e.preventDefault();});
$( "#node-dialog-rename-workspace" ).dialog({
modal: true,
autoOpen: false,
width: 500,
title: RED._("workspace.renameSheet"),
buttons: [
{
class: 'leftButton',
text: RED._("common.label.delete"),
click: function() {
var workspace = $(this).dialog('option','workspace');
$( this ).dialog( "close" );
deleteWorkspace(workspace);
}
},
{
text: RED._("common.label.ok"),
click: function() {
var workspace = $(this).dialog('option','workspace');
var label = $( "#node-input-workspace-name" ).val();
if (workspace.label != label) {
workspace_tabs.renameTab(workspace.id,label);
RED.nodes.dirty(true);
$("#menu-item-workspace-menu-"+workspace.id.replace(".","-")).text(label);
// TODO: update entry in menu
}
$( this ).dialog( "close" );
}
},
{
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
}
],
open: function(e) {
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
}
});
$( "#node-dialog-delete-workspace" ).dialog({
modal: true,
autoOpen: false,
width: 500,
title: RED._("workspace.confirmDelete"),
buttons: [
{
text: RED._("common.label.ok"),
click: function() {
var workspace = $(this).dialog('option','workspace');
deleteWorkspace(workspace,true);
$( this ).dialog( "close" );
}
},
{
text: RED._("common.label.cancel"),
click: function() {
$( this ).dialog( "close" );
}
}
],
open: function(e) {
RED.keyboard.disable();
},
close: function(e) {
RED.keyboard.enable();
}
});
}
function init() { function init() {
createWorkspaceTabs();
$('#btn-workspace-add-tab').on("click",function(e) {addWorkspace(); e.preventDefault()}); $('#btn-workspace-add-tab').on("click",function(e) {addWorkspace(); e.preventDefault()});
RED.sidebar.on("resize",workspace_tabs.resize); RED.sidebar.on("resize",workspace_tabs.resize);
RED.menu.setAction('menu-item-workspace-delete',function() { RED.menu.setAction('menu-item-workspace-delete',function() {
deleteWorkspace(RED.nodes.workspace(activeWorkspace)); deleteWorkspace(RED.nodes.workspace(activeWorkspace));
}); });
} }
// TODO: DRY // TODO: DRY
var eventHandler = (function() { var eventHandler = (function() {
var handlers = {}; var handlers = {};
return { return {
on: function(evt,func) { on: function(evt,func) {
handlers[evt] = handlers[evt]||[]; handlers[evt] = handlers[evt]||[];
@ -214,12 +219,12 @@ RED.workspaces = (function() {
for (var i=0;i<handlers[evt].length;i++) { for (var i=0;i<handlers[evt].length;i++) {
handlers[evt][i](arg); handlers[evt][i](arg);
} }
} }
} }
} }
})(); })();
function removeWorkspace(ws) { function removeWorkspace(ws) {
if (!ws) { if (!ws) {
deleteWorkspace(RED.nodes.workspace(activeWorkspace)); deleteWorkspace(RED.nodes.workspace(activeWorkspace));
@ -234,7 +239,7 @@ RED.workspaces = (function() {
on: eventHandler.on, on: eventHandler.on,
add: addWorkspace, add: addWorkspace,
remove: removeWorkspace, remove: removeWorkspace,
edit: function(id) { edit: function(id) {
showRenameWorkspaceDialog(id||activeWorkspace); showRenameWorkspaceDialog(id||activeWorkspace);
}, },
@ -251,15 +256,15 @@ RED.workspaces = (function() {
if (!workspace_tabs.contains(id)) { if (!workspace_tabs.contains(id)) {
var sf = RED.nodes.subflow(id); var sf = RED.nodes.subflow(id);
if (sf) { if (sf) {
addWorkspace({type:"subflow",id:id,label:"Subflow: "+sf.name, closeable: true}); addWorkspace({type:"subflow",id:id,label:RED._("subflow.tabLabel",{name:sf.name}), closeable: true});
} }
} }
workspace_tabs.activateTab(id); workspace_tabs.activateTab(id);
}, },
refresh: function() { refresh: function() {
RED.nodes.eachSubflow(function(sf) { RED.nodes.eachSubflow(function(sf) {
if (workspace_tabs.contains(sf.id)) { if (workspace_tabs.contains(sf.id)) {
workspace_tabs.renameTab(sf.id,"Subflow: "+sf.name); workspace_tabs.renameTab(sf.id,RED._("subflow.tabLabel",{name:sf.name}));
} }
}); });
}, },

View File

@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
**/ **/
RED.user = (function() { RED.user = (function() {
function login(opts,done) { function login(opts,done) {
if (typeof opts == 'function') { if (typeof opts == 'function') {
done = opts; done = opts;
opts = {}; opts = {};
} }
var dialog = $('<div id="node-dialog-login" class="hide">'+ var dialog = $('<div id="node-dialog-login" class="hide">'+
'<div style="display: inline-block;width: 250px; vertical-align: top; margin-right: 10px; margin-bottom: 20px;"><img id="node-dialog-login-image" src=""/></div>'+ '<div style="display: inline-block;width: 250px; vertical-align: top; margin-right: 10px; margin-bottom: 20px;"><img id="node-dialog-login-image" src=""/></div>'+
'<div style="display: inline-block; width: 250px; vertical-align: bottom; margin-left: 10px; margin-bottom: 20px;">'+ '<div style="display: inline-block; width: 250px; vertical-align: bottom; margin-left: 10px; margin-bottom: 20px;">'+
@ -37,7 +37,7 @@ RED.user = (function() {
resizable: false, resizable: false,
draggable: false draggable: false
}); });
$("#node-dialog-login-fields").empty(); $("#node-dialog-login-fields").empty();
$.ajax({ $.ajax({
dataType: "json", dataType: "json",
@ -45,7 +45,7 @@ RED.user = (function() {
success: function(data) { success: function(data) {
if (data.type == "credentials") { if (data.type == "credentials") {
var i=0; var i=0;
if (data.image) { if (data.image) {
$("#node-dialog-login-image").attr("src",data.image); $("#node-dialog-login-image").attr("src",data.image);
} else { } else {
@ -56,7 +56,7 @@ RED.user = (function() {
var row = $("<div/>",{id:"rrr"+i,class:"form-row"}); var row = $("<div/>",{id:"rrr"+i,class:"form-row"});
$('<label for="node-dialog-login-'+field.id+'">'+field.label+':</label><br/>').appendTo(row); $('<label for="node-dialog-login-'+field.id+'">'+field.label+':</label><br/>').appendTo(row);
var input = $('<input style="width: 100%" id="node-dialog-login-'+field.id+'" type="'+field.type+'" tabIndex="'+(i+1)+'"/>').appendTo(row); var input = $('<input style="width: 100%" id="node-dialog-login-'+field.id+'" type="'+field.type+'" tabIndex="'+(i+1)+'"/>').appendTo(row);
if (i<data.prompts.length-1) { if (i<data.prompts.length-1) {
input.keypress( input.keypress(
(function() { (function() {
@ -72,17 +72,17 @@ RED.user = (function() {
} }
row.appendTo("#node-dialog-login-fields"); row.appendTo("#node-dialog-login-fields");
} }
$('<div class="form-row" style="text-align: right; margin-top: 10px;"><span id="node-dialog-login-failed" style="line-height: 2em;float:left;" class="hide">Login failed</span><img src="red/images/spin.svg" style="height: 30px; margin-right: 10px; " class="login-spinner hide"/>'+ $('<div class="form-row" style="text-align: right; margin-top: 10px;"><span id="node-dialog-login-failed" style="line-height: 2em;float:left;" class="hide">'+RED._("user.loginFailed")+'</span><img src="red/images/spin.svg" style="height: 30px; margin-right: 10px; " class="login-spinner hide"/>'+
(opts.cancelable?'<a href="#" id="node-dialog-login-cancel" style="margin-right: 20px;" tabIndex="'+(i+1)+'">Cancel</a>':'')+ (opts.cancelable?'<a href="#" id="node-dialog-login-cancel" style="margin-right: 20px;" tabIndex="'+(i+1)+'">'+RED._("common.label.cancel")+'</a>':'')+
'<input type="submit" id="node-dialog-login-submit" style="width: auto;" tabIndex="'+(i+2)+'" value="Login"></div>').appendTo("#node-dialog-login-fields"); '<input type="submit" id="node-dialog-login-submit" style="width: auto;" tabIndex="'+(i+2)+'" value="'+RED._("user.login")+'"></div>').appendTo("#node-dialog-login-fields");
$("#node-dialog-login-submit").button(); $("#node-dialog-login-submit").button();
$("#node-dialog-login-fields").submit(function(event) { $("#node-dialog-login-fields").submit(function(event) {
$("#node-dialog-login-submit").button("option","disabled",true); $("#node-dialog-login-submit").button("option","disabled",true);
$("#node-dialog-login-failed").hide(); $("#node-dialog-login-failed").hide();
$(".login-spinner").show(); $(".login-spinner").show();
var body = { var body = {
client_id: "node-red-editor", client_id: "node-red-editor",
grant_type: "password", grant_type: "password",
@ -116,7 +116,7 @@ RED.user = (function() {
} }
} }
dialog.dialog("open"); dialog.dialog("open");
} }
}); });
} }
@ -131,17 +131,17 @@ RED.user = (function() {
} }
}) })
} }
function updateUserMenu() { function updateUserMenu() {
$("#usermenu-submenu li").remove(); $("#usermenu-submenu li").remove();
if (RED.settings.user.anonymous) { if (RED.settings.user.anonymous) {
RED.menu.addItem("btn-usermenu",{ RED.menu.addItem("btn-usermenu",{
id:"usermenu-item-login", id:"usermenu-item-login",
label:"Login", label:RED._("menu.label.login"),
onselect: function() { onselect: function() {
RED.user.login({cancelable:true},function() { RED.user.login({cancelable:true},function() {
RED.settings.load(function() { RED.settings.load(function() {
RED.notify("Logged in as "+RED.settings.user.username,"success"); RED.notify(RED._("user.loggedInAs",{name:RED.settings.user.username}),"success");
updateUserMenu(); updateUserMenu();
}); });
}); });
@ -154,31 +154,31 @@ RED.user = (function() {
}); });
RED.menu.addItem("btn-usermenu",{ RED.menu.addItem("btn-usermenu",{
id:"usermenu-item-logout", id:"usermenu-item-logout",
label:"Logout", label:RED._("menu.label.logout"),
onselect: function() { onselect: function() {
RED.user.logout(); RED.user.logout();
} }
}); });
} }
} }
function init() { function init() {
if (RED.settings.user) { if (RED.settings.user) {
if (!RED.settings.editorTheme || !RED.settings.editorTheme.hasOwnProperty("userMenu")) { if (!RED.settings.editorTheme || !RED.settings.editorTheme.hasOwnProperty("userMenu")) {
$('<li><a id="btn-usermenu" class="button hide" data-toggle="dropdown" href="#"><i class="fa fa-user"></i></a></li>') $('<li><a id="btn-usermenu" class="button hide" data-toggle="dropdown" href="#"><i class="fa fa-user"></i></a></li>')
.prependTo(".header-toolbar"); .prependTo(".header-toolbar");
RED.menu.init({id:"btn-usermenu", RED.menu.init({id:"btn-usermenu",
options: [] options: []
}); });
updateUserMenu(); updateUserMenu();
} }
} }
} }
return { return {
init: init, init: init,

View File

@ -46,7 +46,7 @@
<div id="palette-container" class="palette-scroll"> <div id="palette-container" class="palette-scroll">
</div> </div>
<div id="palette-search"> <div id="palette-search">
<i class="fa fa-search"></i><input id="palette-search-input" type="text" placeholder="filter"><a href="#" id="palette-search-clear"><i class="fa fa-times"></i></a></input> <i class="fa fa-search"></i><input id="palette-search-input" type="text" data-i18n="[placeholder]palette.filter"><a href="#" id="palette-search-clear"><i class="fa fa-times"></i></a></input>
</div> </div>
</div><!-- /palette --> </div><!-- /palette -->
@ -55,10 +55,10 @@
<div id="workspace-add-tab"><a id="btn-workspace-add-tab" href="#"><i class="fa fa-plus"></i></a></div> <div id="workspace-add-tab"><a id="btn-workspace-add-tab" href="#"><i class="fa fa-plus"></i></a></div>
<div id="chart"></div> <div id="chart"></div>
<div id="workspace-toolbar"> <div id="workspace-toolbar">
<a class="button" id="workspace-subflow-edit" href="#"><i class="fa fa-pencil"></i> edit name</a> <a class="button" id="workspace-subflow-edit" href="#" data-i18n="[append]subflow.editSubflowName"><i class="fa fa-pencil"></i> </a>
<a class="button disabled" id="workspace-subflow-add-input" href="#"><i class="fa fa-plus"></i> input</a> <a class="button disabled" id="workspace-subflow-add-input" href="#" data-i18n="[append]subflow.input"><i class="fa fa-plus"></i> </a>
<a class="button" id="workspace-subflow-add-output" href="#"><i class="fa fa-plus"></i> output</a> <a class="button" id="workspace-subflow-add-output" href="#" data-i18n="[append]subflow.output"><i class="fa fa-plus"></i> </a>
<a class="button" id="workspace-subflow-delete" href="#"><i class="fa fa-trash"></i> delete subflow</a> <a class="button" id="workspace-subflow-delete" href="#" data-i18n="[append]subflow.deleteSubflow"><i class="fa fa-trash"></i> </a>
</div> </div>
</div> </div>
@ -80,14 +80,14 @@
</div> </div>
<div id="notifications"></div> <div id="notifications"></div>
<div id="dropTarget"><div>Drop the flow here<br/><i class="fa fa-download"></i></div></div> <div id="dropTarget"><div data-i18n="[append]workspace.dropFlowHere"><br/><i class="fa fa-download"></i></div></div>
<div id="dialog" class="hide"><form id="dialog-form" class="form-horizontal"></form></div> <div id="dialog" class="hide"><form id="dialog-form" class="form-horizontal"></form></div>
<div id="node-config-dialog" class="hide"><form id="dialog-config-form" class="form-horizontal"></form><div class="form-tips" id="node-config-dialog-user-count"></div></div> <div id="node-config-dialog" class="hide"><form id="dialog-config-form" class="form-horizontal"></form><div class="form-tips" id="node-config-dialog-user-count"></div></div>
<div id="subflow-dialog" class="hide"> <div id="subflow-dialog" class="hide">
<form class="form-horizontal"> <form class="form-horizontal">
<div class="form-row"> <div class="form-row">
<label>Name</label><input type="text" id="subflow-input-name"> <label data-i18n="common.label.name"></label><input type="text" id="subflow-input-name">
</div> </div>
</form> </form>
<div class="form-tips" id="subflow-dialog-user-count"></div> <div class="form-tips" id="subflow-dialog-user-count"></div>
@ -95,26 +95,19 @@
<div id="node-dialog-confirm-deploy" class="hide"> <div id="node-dialog-confirm-deploy" class="hide">
<form class="form-horizontal"> <form class="form-horizontal">
<div id="node-dialog-confirm-deploy-config" style="text-align: left; padding-top: 30px;"> <div id="node-dialog-confirm-deploy-config" style="text-align: left; padding-top: 30px;" data-i18n="[prepend]deploy.confirm.improperlyConfigured;[append]deploy.confirm.confirm"> </div>
Some of the nodes are not properly configured. Are you sure you want to deploy? <div id="node-dialog-confirm-deploy-unknown" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.unknown;[append]deploy.confirm.confirm">
</div>
<div id="node-dialog-confirm-deploy-unknown" style="text-align: left; padding-top: 10px;">
The workspace contains some unknown node types:
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-unknown-list"></ul> <ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-unknown-list"></ul>
Are you sure you want to deploy?
</div> </div>
<div id="node-dialog-confirm-deploy-unused" style="text-align: left; padding-top: 10px;"> <div id="node-dialog-confirm-deploy-unused" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.unusedConfig;[append]deploy.confirm.confirm">
The workspace contains some unused configuration nodes:
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-unused-list"></ul> <ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-unused-list"></ul>
Are you sure you want to deploy?
</div> </div>
</form> </form>
</div> </div>
<div id="node-dialog-library-save-confirm" class="hide"> <div id="node-dialog-library-save-confirm" class="hide">
<form class="form-horizontal"> <form class="form-horizontal">
<div style="text-align: center; padding-top: 30px;"> <div style="text-align: center; padding-top: 30px;" id="node-dialog-library-save-content">
A <span id="node-dialog-library-save-type"></span> called <span id="node-dialog-library-save-name"></span> already exists. Overwrite?
</div> </div>
</form> </form>
</div> </div>
@ -122,12 +115,12 @@
<div id="node-dialog-library-save" class="hide"> <div id="node-dialog-library-save" class="hide">
<form class="form-horizontal"> <form class="form-horizontal">
<div class="form-row"> <div class="form-row">
<label for="node-dialog-library-save-folder"><i class="fa fa-folder-open"></i> Folder</label> <label for="node-dialog-library-save-folder" data-i18n="[append]library.folder"><i class="fa fa-folder-open"></i> </label>
<input type="text" id="node-dialog-library-save-folder" placeholder="Folder"> <input type="text" id="node-dialog-library-save-folder" data-i18n="[placeholder]library.folderPlaceholder">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-dialog-library-save-filename"><i class="fa fa-file"></i> Filename</label> <label for="node-dialog-library-save-filename" data-i18n="[append]library.filename"><i class="fa fa-file"></i> </label>
<input type="text" id="node-dialog-library-save-filename" placeholder="Filename"> <input type="text" id="node-dialog-library-save-filename" data-i18n="[placeholder]library.filenamePlaceholder">
</div> </div>
</form> </form>
</div> </div>
@ -136,7 +129,7 @@
<form class="form-horizontal"> <form class="form-horizontal">
<div class="form-row"> <div class="form-row">
<ul id="node-dialog-library-breadcrumbs" class="breadcrumb"> <ul id="node-dialog-library-breadcrumbs" class="breadcrumb">
<li class="active"><a href="#">Library</a></li> <li class="active" data-i18n="[append]library.breadcrumb"><a href="#"></a></li>
</ul> </ul>
</div> </div>
<div class="form-row"> <div class="form-row">
@ -152,30 +145,29 @@
<div id="node-dialog-rename-workspace" class="hide"> <div id="node-dialog-rename-workspace" class="hide">
<form class="form-horizontal"> <form class="form-horizontal">
<div class="form-row"> <div class="form-row">
<label for="node-input-workspace-name" ><i class="fa fa-tag"></i> Name:</label> <label for="node-input-workspace-name" ><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-workspace-name"> <input type="text" id="node-input-workspace-name">
</div> </div>
</form> </form>
</div> </div>
<div id="node-dialog-delete-workspace" class="hide"> <div id="node-dialog-delete-workspace" class="hide">
<form class="form-horizontal"> <form class="form-horizontal">
<div style="text-align: center; padding-top: 30px;"> <div style="text-align: center; padding-top: 30px;" id="node-dialog-delete-workspace-content">
Are you sure you want to delete '<span id="node-dialog-delete-workspace-name"></span>'?
</div> </div>
</form> </form>
</div> </div>
<script type="text/x-red" data-template-name="export-library-dialog"> <script type="text/x-red" data-template-name="export-library-dialog">
<div class="form-row"> <div class="form-row">
<label for="node-input-filename" ><i class="fa fa-file"></i> Filename:</label> <label for="node-input-filename" data-i18n="[append]editor:library.filename"><i class="fa fa-file"></i> </label>
<input type="text" id="node-input-filename" placeholder="Filename"> <input type="text" id="node-input-filename" data-i18n="[placeholder]editor:library.fullFilenamePlaceholder">
</div> </div>
</script> </script>
<script type="text/x-red" data-template-name="subflow"> <script type="text/x-red" data-template-name="subflow">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label>
<input type="text" id="node-input-name" placeholder="name"> <input type="text" id="node-input-name">
</div> </div>
</script> </script>

5
editor/vendor/i18next/i18next.min.js vendored Normal file

File diff suppressed because one or more lines are too long

192
locales/en-US/editor.json Normal file
View File

@ -0,0 +1,192 @@
{
"common": {
"label": {
"name": "Name",
"ok": "Ok",
"cancel": "Cancel",
"delete": "Delete",
"close": "Close"
}
},
"workspace": {
"renameSheet": "Rename sheet",
"confirmDelete": "Confirm delete",
"delete": "Are you sure you want to delete '__label__'?",
"dropFlowHere": "Drop the flow here"
},
"menu": {
"label": {
"sidebar": "Sidebar",
"displayStatus": "Display node status",
"import": "Import",
"export": "Export",
"clipboard": "Clipboard",
"library": "Library",
"configurationNodes": "Configuration nodes",
"subflows": "Subflows",
"createSubflow": "Create subflow",
"selectionToSubflow": "Selection to subflow",
"workspaces": "Workspaces",
"add": "Add",
"rename": "Rename",
"delete": "Delete",
"keyboardShortcuts": "Keyboard Shortcuts",
"login": "Login",
"logout": "Logout"
}
},
"user": {
"loggedInAs": "Logged in as __name__",
"login": "Login",
"loginFailed": "Login failed"
},
"notification": {
"warning": "<strong>Warning</strong>: __message__",
"warnings": {
"undeployedChanges": "node has undeployed changes",
"nodeActionDisabled": "node actions disabled within subflow"
},
"error": "<strong>Error</strong>: __message__",
"errors": {
"lostConnection": "Lost connection to server",
"cannotAddSubflowToItself": "Cannot add subflow to itself",
"cannotAddCircularReference": "Cannot add subflow - circular reference detected"
}
},
"clipboard": {
"nodes": "Nodes:",
"selectNodes": "Select the text above and copy to the clipboard.",
"pasteNodes": "Paste nodes here",
"importNodes": "Import nodes",
"exportNodes": "Export nodes to clipboard",
"importUnrecognised": "Imported unrecognised type:",
"importUnrecognised_plural": "Imported unrecognised types:",
"nodeCopied": "__count__ node copied",
"nodeCopied_plural": "__count__ nodes copied",
"invalidFlow": "Invalid flow: __message__"
},
"deploy": {
"deploy": "Deploy",
"full": "Full",
"fullDesc": "Deploys everything in the workspace",
"modifiedFlows": "Modified Flows",
"modifiedFlowsDesc": "Only deploys flows that contain changed nodes",
"modifiedNodes": "Modified Nodes",
"modifiedNodesDesc": "Only deploys nodes that have changed",
"successfulDeploy": "Successfully Deployed",
"errors": {
"noResponse": "no response from server"
},
"confirm": {
"button": {
"confirm": "Confirm deploy",
"cancel": "Cancel"
},
"undeployedChanges": "You have undeployed changes.\n\nLeaving this page will lose these changes.",
"improperlyConfigured": "Some of the nodes are not properly configured.",
"unknown": "The workspace contains some unknown node types:",
"unusedConfig": "The workspace contains some unused configuration nodes:",
"confirm": "Are you sure you want to deploy?"
}
},
"subflow": {
"tabLabel": "Subflow: __name__",
"editSubflow": "Edit flow __name__",
"edit": "Edit flow",
"subflowInstances": "There is __count__ instance of this subflow",
"subflowInstances_plural": "There are __count__ instances of this subflow",
"editSubflowName": "edit name",
"input": "input",
"output": "output",
"deleteSubflow": "delete subflow",
"errors": {
"noNodesSelected": "<strong>Cannot create subflow</strong>: no nodes selected",
"multipleInputsToSelection": "<strong>Cannot create subflow</strong>: multiple inputs to selection"
}
},
"editor": {
"configEdit": "edit",
"configAdd": "add",
"configDelete": "Delete",
"nodesUse": "__count__ node uses this config",
"nodesUse_plural": "__count__ nodes use this config",
"addNewConfig": "Add new __type__ config node",
"editConfig": "Edit __type__ config node",
"addNewType": "Add new __type__..."
},
"keyboard": {
"selectAll": "Select all nodes",
"selectAllConnected": "Select all connected nodes",
"addRemoveNode": "Add/remove node from selection",
"deleteSelected": "Delete selected nodes or link",
"importNode": "Import nodes",
"exportNode": "Export selected nodes",
"toggleSidebar": "Toggle sidebar",
"deleteNode": "Delete selected nodes or link",
"copyNode": "Copy selected nodes",
"cutNode": "Cut selected nodes",
"pasteNode": "Paste nodes"
},
"library": {
"openLibrary": "Open Library...",
"saveToLibrary": "Save to Library...",
"typeLibrary": "__type__ library",
"unnamedType": "Unnamed __type__",
"saveToLibrary": "Save to Library",
"exportToLibrary": "Export nodes to library",
"dialogSaveOverwrite": "A __libraryType__ called __libraryName__ already exists. Overwrite?",
"invalidFilename": "Invalid filename",
"savedNodes": "Saved nodes",
"savedType": "Saved __type__",
"saveFailed": "Save failed: __message__",
"filename": "Filename",
"folder": "Folder",
"filenamePlaceholder": "file",
"fullFilenamePlaceholder": "a/b/file",
"folderPlaceholder": "a/b",
"breadcrumb": "Library"
},
"palette": {
"noInfo": "no information available",
"filter": "filter",
"label": {
"subflows": "subflows",
"input": "input",
"output": "output",
"function": "function",
"social": "social",
"storage": "storage",
"analysis": "analysis",
"advanced": "advanced"
},
"event": {
"nodeAdded": "Node added to palette:",
"nodeAdded_plural": "Nodes added to palette",
"nodeRemoved": "Node removed from palette:",
"nodeRemoved_plural": "Nodes removed from palette:",
"nodeEnabled": "Node enabled:",
"nodeEnabled_plural": "Nodes enabled:",
"nodeDisabled": "Node disabled:",
"nodeDisabled_plural": "Nodes disabled:"
}
},
"sidebar": {
"info": {
"title": "info",
"node": "Node",
"type": "Type",
"id": "ID",
"subflow": "Subflow",
"instances": "Instances",
"properties": "Properties",
"blank": "blank",
"arrayItems": "__count__ items"
},
"config": {
"title": "config"
}
}
}

123
locales/en-US/runtime.json Normal file
View File

@ -0,0 +1,123 @@
{
"runtime": {
"welcome": "Welcome to Node-RED",
"version": "__component__ version: __version__",
"paths": {
"settings": "Settings file : __path__"
}
},
"server": {
"loading": "Loading palette nodes",
"errors": "Failed to register __count__ node type",
"errors_plural": "Failed to register __count__ node types",
"errors-help": "Run with -v for details",
"missing-modules": "Missing node modules:",
"removing-modules": "Removing modules from config",
"added-types": "Added node types:",
"removed-types": "Removed node types:",
"install": {
"invalid": "Invalid module name",
"installing": "Installing module: __name__",
"installed": "Installed module: __name__",
"install-failed": "Install failed",
"install-failed-long": "Installation of module __name__ failed:",
"install-failed-not-found": "$t(install-failed-long) module not found",
"uninstalling": "Uninstalling module: __name__",
"uninstall-failed": "Uninstall failed",
"uninstall-failed-long": "Uninstall of module __name__ failed:",
"uninstalled": "Uninstalled module: __name__"
},
"unable-to-listen": "Unable to listen on __listenpath__",
"port-in-use": "Error: port in use",
"uncaught-exception": "Uncaught Exception:",
"admin-ui-disabled": "Admin UI disabled",
"now-running": "Server now running at __listenpath__",
"failed-to-start": "Failed to start server:",
"headless-mode": "Running in headless mode",
"httpadminauth-deprecated": "use of httpAdminAuth is deprecated. Use adminAuth instead"
},
"api": {
"flows": {
"error-save": "Error saving flows: __message__"
},
"library": {
"error-load-entry": "Error loading library entry '__path__': __message__",
"error-save-entry": "Error saving library entry '__path__': __message__",
"error-load-flow": "Error loading flow '__path__': __message__",
"error-save-flow": "Error saving flow '__path__': __message__"
},
"nodes": {
"enabled": "Enabled node types:",
"disabled": "Disabled node types:",
"error-enable": "Failed to enable node:"
}
},
"comms": {
"error": "Communication channel error: __message__",
"error-server": "Communication server error: __message__",
"error-send": "Communication send error: __message__"
},
"settings": {
"not-available": "Settings not available",
"property-read-only": "Property '__prop__' is read-only"
},
"nodes": {
"credentials": {
"error":"Error loading credentials: __message__",
"not-registered": "Credential type '__type__' is not registered"
},
"flows": {
"registered-missing": "Missing type registered: __type__",
"error": "Error loading flows: __message__",
"starting-modified-nodes": "Starting modified nodes",
"starting-modified-flows": "Starting modified flows",
"starting-flows": "Starting flows",
"started-modified-nodes": "Started modified nodes",
"started-modified-flows": "Started modified flows",
"started-flows": "Started flows",
"stopping-modified-nodes": "Stopping modified nodes",
"stopping-modified-flows": "Stopping modified flows",
"stopping-flows": "Stopping flows",
"stopped-modified-nodes": "Stopped modified nodes",
"stopped-modified-flows": "Stopped modified flows",
"stopped-flows": "Stopped flows",
"stopped": "Stopped",
"missing-types": "Waiting for missing types to be registered:",
"missing-type-provided": " - __type__ (provided by npm module __module__)",
"missing-type-install-1": "To install any of these missing modules, run:",
"missing-type-install-2": "in the directory:"
},
"flow": {
"unknown-type": "Unknown type: __type__",
"missing-types": "missing types",
"error-loop": "Message exceeded maximum number of catches"
},
"index": {
"unrecognised-id": "Unrecognised id: __id__",
"type-in-use": "Type in use: __msg__",
"unrecognised-module": "Unrecognised module: __module__"
},
"registry": {
"localfilesystem": {
"module-not-found": "Cannot find module '__module__'"
}
}
},
"storage": {
"index": {
"forbidden-flow-name": "forbidden flow name"
},
"localfilesystem": {
"user-dir": "User directory : __path__",
"flows-file": "Flows file : __path__",
"create": "Creating new flow file"
}
}
}

View File

@ -16,8 +16,8 @@
<script type="text/x-red" data-template-name="sentiment"> <script type="text/x-red" data-template-name="sentiment">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
</script> </script>

View File

@ -16,38 +16,48 @@
<script type="text/x-red" data-template-name="inject"> <script type="text/x-red" data-template-name="inject">
<div class="form-row node-input-payload"> <div class="form-row node-input-payload">
<label for="node-input-payloadType"><i class="fa fa-envelope"></i> Payload</label> <label for="node-input-payloadType"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
<select id="node-input-payloadType" style="width:73%"> <select id="node-input-payloadType" style="width:73%">
<option value="date">timestamp</option> <option value="date" data-i18n="inject.timestamp"></option>
<option value="string">string</option> <option value="string" data-i18n="inject.string"></option>
<option value="none">blank</option> <option value="none" data-i18n="inject.blank"></option>
</select> </select>
</div> </div>
<div class="form-row" id="node-input-row-payload"> <div class="form-row" id="node-input-row-payload">
<label for="node-input-payload"></label> <label for="node-input-payload"></label>
<input type="text" id="node-input-payload" placeholder="payload" style="width:70%"> <input type="text" id="node-input-payload" style="width:70%">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label> <label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
<input type="text" id="node-input-topic" placeholder="topic" style="width: 70%x"> <input type="text" id="node-input-topic" style="width: 70%x">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for=""><i class="fa fa-repeat"></i> Repeat</label> <label for=""><i class="fa fa-repeat"></i> <span data-i18n="inject.label.repeat"></span></label>
<select id="inject-time-type-select" style="width: 73%"><option value="none">none</option><option value="interval">interval</option><option value="interval-time">interval between times</option><option value="time">at a specific time</option></select> <select id="inject-time-type-select" style="width: 73%">
<input type="hidden" id="node-input-repeat" placeholder="payload"> <option value="none" data-i18n="inject.none"></option>
<input type="hidden" id="node-input-crontab" placeholder="payload"> <option value="interval" data-i18n="inject.interval"></option>
<option value="interval-time" data-i18n="inject.interval-time"></option>
<option value="time" data-i18n="inject.time"></option>
</select>
<input type="hidden" id="node-input-repeat">
<input type="hidden" id="node-input-crontab">
</div> </div>
<div class="form-row inject-time-row hidden" id="inject-time-row-interval"> <div class="form-row inject-time-row hidden" id="inject-time-row-interval">
every <input id="inject-time-interval-count" class="inject-time-count" value="1"></input> <span data-i18n="inject.every"></span>
<select style="width: 100px" id="inject-time-interval-units"><option value="s">seconds</option><option value="m">minutes</option><option value="h">hours</option></select><br/> <input id="inject-time-interval-count" class="inject-time-count" value="1"></input>
<select style="width: 100px" id="inject-time-interval-units">
<option value="s" data-i18n="inject.seconds"></option>
<option value="m" data-i18n="inject.minutes"></option>
<option value="h" data-i18n="inject.hours"></option>
</select><br/>
</div> </div>
<div class="form-row inject-time-row hidden" id="inject-time-row-interval-time"> <div class="form-row inject-time-row hidden" id="inject-time-row-interval-time">
at every <select style="width: 90px" id="inject-time-interval-time-units" class="inject-time-int-count" value="1"> <span data-i18n="inject.every"></span> <select style="width: 90px" id="inject-time-interval-time-units" class="inject-time-int-count" value="1">
<option value="1">1</option> <option value="1">1</option>
<option value="2">2</option> <option value="2">2</option>
<option value="3">3</option> <option value="3">3</option>
@ -60,46 +70,46 @@
<option value="20">20</option> <option value="20">20</option>
<option value="30">30</option> <option value="30">30</option>
<option value="0">60</option> <option value="0">60</option>
</select> minutes<br/> </select> <span data-i18n="inject.minutes"></span><br/>
between <select id="inject-time-interval-time-start" class="inject-time-times"></select> <span data-i18n="inject.between"></span> <select id="inject-time-interval-time-start" class="inject-time-times"></select>
and <select id="inject-time-interval-time-end" class="inject-time-times"></select><br/> <span data-i18n="inject.and"></span> <select id="inject-time-interval-time-end" class="inject-time-times"></select><br/>
<div id="inject-time-interval-time-days" class="inject-time-days"> <div id="inject-time-interval-time-days" class="inject-time-days">
<div style="display: inline-block; vertical-align: top;margin-right: 5px;">on </div> <div style="display: inline-block; vertical-align: top;margin-right: 5px;" data-i18n="inject.on">on</div>
<div style="display:inline-block;"> <div style="display:inline-block;">
<div> <div>
<label><input type='checkbox' checked value='1'/> Monday</label> <label><input type='checkbox' checked value='1'/> <span data-i18n="inject.days.0"></span></label>
<label><input type='checkbox' checked value='2'/> Tuesday</label> <label><input type='checkbox' checked value='2'/> <span data-i18n="inject.days.1"></span></label>
<label><input type='checkbox' checked value='3'/> Wednesday</label> <label><input type='checkbox' checked value='3'/> <span data-i18n="inject.days.2"></span></label>
</div> </div>
<div> <div>
<label><input type='checkbox' checked value='4'/> Thursday</label> <label><input type='checkbox' checked value='4'/> <span data-i18n="inject.days.3"></span></label>
<label><input type='checkbox' checked value='5'/> Friday</label> <label><input type='checkbox' checked value='5'/> <span data-i18n="inject.days.4"></span></label>
<label><input type='checkbox' checked value='6'/> Saturday</label> <label><input type='checkbox' checked value='6'/> <span data-i18n="inject.days.5"></span></label>
</div> </div>
<div> <div>
<label><input type='checkbox' checked value='0'/> Sunday</label> <label><input type='checkbox' checked value='0'/> <span data-i18n="inject.days.6"></span></label>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="form-row inject-time-row hidden" id="inject-time-row-time"> <div class="form-row inject-time-row hidden" id="inject-time-row-time">
at <input id="inject-time-time" value="12:00"></input><br/> <span data-i18n="inject.at"></span> <input id="inject-time-time" value="12:00"></input><br/>
<div id="inject-time-time-days" class="inject-time-days"> <div id="inject-time-time-days" class="inject-time-days">
<div style="display: inline-block; vertical-align: top;margin-right: 5px;">on </div> <div style="display: inline-block; vertical-align: top;margin-right: 5px;">on </div>
<div style="display:inline-block;"> <div style="display:inline-block;">
<div> <div>
<label><input type='checkbox' checked value='1'/> Monday</label> <label><input type='checkbox' checked value='1'/> <span data-i18n="inject.days.0"></span></label>
<label><input type='checkbox' checked value='2'/> Tuesday</label> <label><input type='checkbox' checked value='2'/> <span data-i18n="inject.days.1"></span></label>
<label><input type='checkbox' checked value='3'/> Wednesday</label> <label><input type='checkbox' checked value='3'/> <span data-i18n="inject.days.2"></span></label>
</div> </div>
<div> <div>
<label><input type='checkbox' checked value='4'/> Thursday</label> <label><input type='checkbox' checked value='4'/> <span data-i18n="inject.days.3"></span></label>
<label><input type='checkbox' checked value='5'/> Friday</label> <label><input type='checkbox' checked value='5'/> <span data-i18n="inject.days.4"></span></label>
<label><input type='checkbox' checked value='6'/> Saturday</label> <label><input type='checkbox' checked value='6'/> <span data-i18n="inject.days.5"></span></label>
</div> </div>
<div> <div>
<label><input type='checkbox' checked value='0'/> Sunday</label> <label><input type='checkbox' checked value='0'/> <span data-i18n="inject.days.6"></span></label>
</div> </div>
</div> </div>
</div> </div>
@ -108,15 +118,15 @@
<div class="form-row" id="node-once"> <div class="form-row" id="node-once">
<label>&nbsp;</label> <label>&nbsp;</label>
<input type="checkbox" id="node-input-once" style="display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-input-once" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-once" style="width: 70%;">Fire once at start ?</label> <label for="node-input-once" style="width: 70%;" data-i18n="inject.onstart"></label>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-tips"><b>Note:</b> "interval between times" and "at a specific time" will use cron.<br/>See info box for details.</div> <div class="form-tips" data-i18n="[html]inject.tip"></div>
</script> </script>
<style> <style>
.inject-time-row { .inject-time-row {
@ -197,7 +207,7 @@
if ((this.topic.length < 24) && (this.topic.length > 0)) { if ((this.topic.length < 24) && (this.topic.length > 0)) {
return this.name||this.topic; return this.name||this.topic;
} }
else { return this.name||(this.payloadType==="date"?"timestamp":null)||"inject"; } else { return this.name||(this.payloadType==="date"?this._("inject.timestamp"):null)||this._("inject.inject"); }
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
@ -474,24 +484,24 @@
button: { button: {
onclick: function() { onclick: function() {
var label = (this.name||this.payload).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"); var label = (this.name||this.payload).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
if (this.payloadType === "date") { label = "timestamp"; } if (this.payloadType === "date") { label = this._("inject.timestamp"); }
if (this.payloadType === "none") { label = "blank"; } if (this.payloadType === "none") { label = this._("inject.blank"); }
var node = this;
$.ajax({ $.ajax({
url: "inject/"+this.id, url: "inject/"+this.id,
type:"POST", type:"POST",
success: function(resp) { success: function(resp) {
RED.notify("Successfully injected: "+label,"success"); RED.notify(node._("inject.success",{label:label}),"success");
}, },
error: function(jqXHR,textStatus,errorThrown) { error: function(jqXHR,textStatus,errorThrown) {
if (jqXHR.status == 404) { if (jqXHR.status == 404) {
RED.notify("<strong>Error</strong>: inject node not deployed","error"); RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.not-deployed")}),"error");
} else if (jqXHR.status == 500) { } else if (jqXHR.status == 500) {
RED.notify("<strong>Error</strong>: inject failed, see log for details.","error"); RED.notify(node._("common.notification.error",{message:node._("inject.errors.failed")}),"error");
} else if (jqXHR.status == 0) { } else if (jqXHR.status == 0) {
RED.notify("<strong>Error</strong>: no response from server","error"); RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.no-response")}),"error");
} else { } else {
RED.notify("<strong>Error</strong>: unexpected error: ("+jqXHR.status+") "+textStatus,"error"); RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.unexpected",{status:jqXHR.status,message:textStatus})}),"error");
} }
} }
}); });

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2013, 2014 IBM Corp. * Copyright 2013, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -32,12 +32,12 @@ module.exports = function(RED) {
if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) { if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
this.repeat = this.repeat * 1000; this.repeat = this.repeat * 1000;
if (RED.settings.verbose) { this.log("repeat = "+this.repeat); } if (RED.settings.verbose) { this.log(RED._("inject.repeat",this)); }
this.interval_id = setInterval( function() { this.interval_id = setInterval( function() {
node.emit("input",{}); node.emit("input",{});
}, this.repeat ); }, this.repeat );
} else if (this.crontab) { } else if (this.crontab) {
if (RED.settings.verbose) { this.log("crontab = "+this.crontab); } if (RED.settings.verbose) { this.log(RED._("inject.crontab",this)); }
this.cronjob = new cron.CronJob(this.crontab, this.cronjob = new cron.CronJob(this.crontab,
function() { function() {
node.emit("input",{}); node.emit("input",{});
@ -68,10 +68,10 @@ module.exports = function(RED) {
InjectNode.prototype.close = function() { InjectNode.prototype.close = function() {
if (this.interval_id != null) { if (this.interval_id != null) {
clearInterval(this.interval_id); clearInterval(this.interval_id);
if (RED.settings.verbose) { this.log("inject: repeat stopped"); } if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
} else if (this.cronjob != null) { } else if (this.cronjob != null) {
this.cronjob.stop(); this.cronjob.stop();
if (RED.settings.verbose) { this.log("inject: cronjob stopped"); } if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
delete this.cronjob; delete this.cronjob;
} }
} }
@ -84,7 +84,7 @@ module.exports = function(RED) {
res.send(200); res.send(200);
} catch(err) { } catch(err) {
res.send(500); res.send(500);
node.error("Inject failed:"+err); node.error(RED._("inject.failed",{error:err.toString()}));
} }
} else { } else {
res.send(404); res.send(404);

View File

@ -16,8 +16,8 @@
<script type="text/x-red" data-template-name="catch"> <script type="text/x-red" data-template-name="catch">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
</script> </script>
<script type="text/x-red" data-help-name="catch"> <script type="text/x-red" data-help-name="catch">
@ -53,7 +53,7 @@
outputs:1, outputs:1,
icon: "alert.png", icon: "alert.png",
label: function() { label: function() {
return this.name||"catch"; return this.name||this._("catch.catch");
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";

View File

@ -16,25 +16,25 @@
<script type="text/x-red" data-template-name="debug"> <script type="text/x-red" data-template-name="debug">
<div class="form-row"> <div class="form-row">
<label for="node-input-select-complete"><i class="fa fa-list"></i> Output</label> <label for="node-input-select-complete"><i class="fa fa-list"></i> <span data-i18n="debug.output"></span></label>
<select type="text" id="node-input-select-complete" style="display: inline-block; width: 250px; vertical-align: top;"> <select type="text" id="node-input-select-complete" style="display: inline-block; width: 250px; vertical-align: top;">
<option value="false">message property</option> <option value="false" data-i18n="debug.msgprop"></option>
<option value="true">complete msg object</option> <option value="true" data-i18n="debug.msgobj"></option>
</select> </select>
</div> </div>
<div class="form-row" id="node-prop-row"> <div class="form-row" id="node-prop-row">
<label for="node-input-complete">&nbsp;</label>msg.<input type="text" style="width:208px" id="node-input-complete"> <label for="node-input-complete">&nbsp;</label>msg.<input type="text" style="width:208px" id="node-input-complete">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-console"><i class="fa fa-random"></i> to</label> <label for="node-input-console"><i class="fa fa-random"></i> <span data-i18n="debug.to"></span></label>
<select type="text" id="node-input-console" style="display: inline-block; width: 250px; vertical-align: top;"> <select type="text" id="node-input-console" style="display: inline-block; width: 250px; vertical-align: top;">
<option value="false">debug tab</option> <option value="false" data-i18n="debug.debtab"></option>
<option value="true">debug tab and console</option> <option value="true" data-i18n="debug.tabcon"></option>
</select> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
</script> </script>
@ -103,23 +103,24 @@
toggle: "active", toggle: "active",
onclick: function() { onclick: function() {
var label = this.name||"debug"; var label = this.name||"debug";
var node = this;
$.ajax({ $.ajax({
url: "debug/"+this.id+"/"+(this.active?"enable":"disable"), url: "debug/"+this.id+"/"+(this.active?"enable":"disable"),
type: "POST", type: "POST",
success: function(resp, textStatus, xhr) { success: function(resp, textStatus, xhr) {
if (xhr.status == 200) { if (xhr.status == 200) {
RED.notify("Successfully activated: "+label,"success"); RED.notify(node._("debug.notification.activated",{label:label}),"success");
} else if (xhr.status == 201) { } else if (xhr.status == 201) {
RED.notify("Successfully deactivated: "+label,"success"); RED.notify(node._("debug.notification.deactivated",{label:label}),"success");
} }
}, },
error: function(jqXHR,textStatus,errorThrown) { error: function(jqXHR,textStatus,errorThrown) {
if (jqXHR.status == 404) { if (jqXHR.status == 404) {
RED.notify("<strong>Error</strong>: debug node not deployed","error"); RED.notify(node._("common.notification.error", {message: node._("common.notification.errors.not-deployed")}),"error");
} else if (jqXHR.status == 0) { } else if (jqXHR.status == 0) {
RED.notify("<strong>Error</strong>: no response from server","error"); RED.notify(node._("common.notification.error", {message: node._("common.notification.errors.no-response")}),"error");
} else { } else {
RED.notify("<strong>Error</strong>: unexpected error: ("+err.status+") "+err.response,"error"); RED.notify(node._("common.notification.error",{message:node._("common.notification.errors.unexpected",{status:err.status,message:err.response})}),"error");
} }
} }
}); });
@ -139,7 +140,7 @@
messages.id = "debug-content"; messages.id = "debug-content";
content.appendChild(messages); content.appendChild(messages);
RED.sidebar.addTab("debug",content); RED.sidebar.addTab(this._("debug.sidebarTitle"),content);
function getTimestamp() { function getTimestamp() {
var d = new Date(); var d = new Date();

View File

@ -16,28 +16,28 @@
<script type="text/x-red" data-template-name="exec"> <script type="text/x-red" data-template-name="exec">
<div class="form-row"> <div class="form-row">
<label for="node-input-command"><i class="fa fa-file"></i> Command</label> <label for="node-input-command"><i class="fa fa-file"></i> <span data-i18n="exec.label.command"></span></label>
<input type="text" id="node-input-command" placeholder="command"> <input type="text" id="node-input-command" data-i18n="[placeholder]exec.label.command">
</div> </div>
<div class="form-row"> <div class="form-row">
<label><i class="fa fa-plus"></i> Append</label> <label><i class="fa fa-plus"></i> <span data-i18n="exec.label.append"></span></label>
<input type="checkbox" id="node-input-addpay" style="display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-input-addpay" style="display: inline-block; width: auto; vertical-align: top;">
&nbsp;msg.payload &nbsp;msg.payload
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-append"> </label> <label for="node-input-append"> </label>
<input type="text" id="node-input-append" placeholder="extra input parameters"> <input type="text" id="node-input-append" data-i18n="[placeholder]exec.placeholder.extraparams">
</div> </div>
<div class="form-row"> <div class="form-row">
<label>&nbsp;</label> <label>&nbsp;</label>
<input type="checkbox" id="node-input-useSpawn" placeholder="spawn" style="display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-input-useSpawn" placeholder="spawn" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-useSpawn" style="width: 70%;">Use spawn() instead of exec() ?</label> <label for="node-input-useSpawn" style="width: 70%;"><span data-i18n="exec.spawn"></span></label>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-tips" id="spawnTip">Tip: <i>spawn</i> expects only one command word - and appended args to be comma separated.</div> <div class="form-tips" id="spawnTip"><span data-i18n="[html]exec.tip"></span></div>
</script> </script>
<script type="text/x-red" data-help-name="exec"> <script type="text/x-red" data-help-name="exec">

View File

@ -63,7 +63,7 @@ module.exports = function(RED) {
node.error(code,msg); node.error(code,msg);
}); });
} }
else { node.error("Spawn command must be just the command - no spaces or extra parameters"); } else { node.error(RED._("exec.spawnerr")); }
} }
else { else {
var cl = node.cmd; var cl = node.cmd;
@ -75,7 +75,7 @@ module.exports = function(RED) {
try { try {
if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); } if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); }
} catch(e) { } catch(e) {
node.log("Bad STDOUT"); node.log(RED._("exec.badstdout"));
} }
var msg2 = {payload:stderr}; var msg2 = {payload:stderr};
var msg3 = null; var msg3 = null;

View File

@ -16,11 +16,11 @@
<script type="text/x-red" data-template-name="function"> <script type="text/x-red" data-template-name="function">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-row" style="margin-bottom: 0px;"> <div class="form-row" style="margin-bottom: 0px;">
<label for="node-input-func"><i class="fa fa-wrench"></i> Function</label> <label for="node-input-func"><i class="fa fa-wrench"></i> <span data-i18n="function.label.function"></span></label>
<input type="hidden" id="node-input-func" autofocus="autofocus"> <input type="hidden" id="node-input-func" autofocus="autofocus">
<input type="hidden" id="node-input-noerr"> <input type="hidden" id="node-input-noerr">
</div> </div>
@ -28,10 +28,10 @@
<div style="height: 250px;" class="node-text-editor" id="node-input-func-editor" ></div> <div style="height: 250px;" class="node-text-editor" id="node-input-func-editor" ></div>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-outputs"><i class="fa fa-random"></i> Outputs</label> <label for="node-input-outputs"><i class="fa fa-random"></i> <span data-i18n="function.label.outputs"></span></label>
<input id="node-input-outputs" style="width: 60px; height: 1.7em;" value="1"> <input id="node-input-outputs" style="width: 60px; height: 1.7em;" value="1">
</div> </div>
<div class="form-tips">See the Info tab for help writing functions.</div> <div class="form-tips"><span data-i18n="function.tip"></span></div>
</script> </script>
<script type="text/x-red" data-help-name="function"> <script type="text/x-red" data-help-name="function">

View File

@ -16,11 +16,11 @@
<script type="text/x-red" data-template-name="template"> <script type="text/x-red" data-template-name="template">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-row" style="margin-bottom: 0px;"> <div class="form-row" style="margin-bottom: 0px;">
<label for="node-input-template"><i class="fa fa-file-code-o"></i> Template</label> <label for="node-input-template"><i class="fa fa-file-code-o"></i> <span data-i18n="template.label.template"></span></label>
<input type="hidden" id="node-input-template" autofocus="autofocus"> <input type="hidden" id="node-input-template" autofocus="autofocus">
<select id="node-input-format" style=" font-size: 0.8em; margin-bottom: 3px; width:110px; float:right;"> <select id="node-input-format" style=" font-size: 0.8em; margin-bottom: 3px; width:110px; float:right;">
<option value="handlebars">mustache</option> <option value="handlebars">mustache</option>
@ -34,7 +34,7 @@
<div style="height: 250px;" class="node-text-editor" id="node-input-template-editor" ></div> <div style="height: 250px;" class="node-text-editor" id="node-input-template-editor" ></div>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-field"><i class="fa fa-edit"></i> Property</label> <label for="node-input-field"><i class="fa fa-edit"></i> <span data-i18n="template.label.property"></span></label>
msg.<input type="text" id="node-input-field" placeholder="payload" style="width:170px;"> msg.<input type="text" id="node-input-field" placeholder="payload" style="width:170px;">
</div> </div>
</script> </script>

View File

@ -18,58 +18,58 @@
<script type="text/x-red" data-template-name="delay"> <script type="text/x-red" data-template-name="delay">
<div class="form-row"> <div class="form-row">
<label for="node-input-pauseType"><i class="fa fa-tasks"></i> Action</label> <label for="node-input-pauseType"><i class="fa fa-tasks"></i> <span data-i18n="delay.action"></span></label>
<select id="node-input-pauseType" style="width:270px !important"> <select id="node-input-pauseType" style="width:270px !important">
<option value="delay">Delay message</option> <option value="delay" data-i18n="delay.delaymsg"></option>
<option value="random">Random delay</option> <option value="random" data-i18n="delay.ramdomdelay"></option>
<option value="rate">Limit rate to</option> <option value="rate" data-i18n="delay.limitrate"></option>
<option value="queue">Topic based fair queue</option> <option value="queue" data-i18n="delay.fairqueue"></option>
</select> </select>
</div> </div>
<div id="delay-details" class="form-row"> <div id="delay-details" class="form-row">
<label for="node-input-timeout"><i class="fa fa-clock-o"></i> For</label> <label for="node-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="delay.for"></span></label>
<input type="text" id="node-input-timeout" placeholder="Time" style="direction:rtl; width:50px !important"> <input type="text" id="node-input-timeout" placeholder="Time" style="direction:rtl; width:50px !important">
<select id="node-input-timeoutUnits" style="width:200px !important"> <select id="node-input-timeoutUnits" style="width:200px !important">
<option value="milliseconds">Milliseconds</option> <option value="milliseconds" data-i18n="delay.milisecs"></option>
<option value="seconds">Seconds</option> <option value="seconds" data-i18n="delay.secs"></option>
<option value="minutes">Minutes</option> <option value="minutes" data-i18n="delay.mins"></option>
<option value="hours">Hours</option> <option value="hours" data-i18n="delay.hours"></option>
<option value="days">Days</option> <option value="days" data-i18n="delay.days"></option>
</select> </select>
</div> </div>
<div id="rate-details" class="form-row"> <div id="rate-details" class="form-row">
<label for="node-input-rate"><i class="fa fa-clock-o"></i> Rate</label> <label for="node-input-rate"><i class="fa fa-clock-o"></i> <span data-i18n="delay.rate"></span></label>
<input type="text" id="node-input-rate" placeholder="1" style="direction:rtl; width:30px !important"> <input type="text" id="node-input-rate" placeholder="1" style="direction:rtl; width:30px !important">
<label for="node-input-rateUnits">msg(s) per</label> <label for="node-input-rateUnits"><span data-i18n="delay.msgper"></span></label>
<select id="node-input-rateUnits" style="width:140px !important"> <select id="node-input-rateUnits" style="width:140px !important">
<option value="second">Second</option> <option value="second" data-i18n="delay.sec"></option>
<option value="minute">Minute</option> <option value="minute" data-i18n="delay.min"></option>
<option value="hour">Hour</option> <option value="hour" data-i18n="delay.hour"></option>
<option value="day">Day</option> <option value="day" data-i18n="delay.day"></option>
</select> </select>
<br/> <br/>
<div id="node-input-dr"><input style="margin: 20px 0 20px 100px; width: 30px;" type="checkbox" id="node-input-drop"><label style="width: 250px;" for="node-input-drop">drop intermediate messages</label></div> <div id="node-input-dr"><input style="margin: 20px 0 20px 100px; width: 30px;" type="checkbox" id="node-input-drop"><label style="width: 250px;" for="node-input-drop"><span data-i18n="delay.dropmsg"></span></label></div>
</div> </div>
<div id="random-details" class="form-row"> <div id="random-details" class="form-row">
<label for="node-input-randomFirst"><i class="fa fa-clock-o"></i> Between</label> <label for="node-input-randomFirst"><i class="fa fa-clock-o"></i> <span data-i18n="delay.between"></span></label>
<input type="text" id="node-input-randomFirst" placeholder="" style="directon:rtl; width:30px !important"> <input type="text" id="node-input-randomFirst" placeholder="" style="directon:rtl; width:30px !important">
<label for="node-input-randomLast" style="width:20px"> &amp; </label> <label for="node-input-randomLast" style="width:20px"> &amp; </label>
<input type="text" id="node-input-randomLast" placeholder="" style="directon:rtl; width:30px !important"> <input type="text" id="node-input-randomLast" placeholder="" style="directon:rtl; width:30px !important">
<select id="node-input-randomUnits" style="width:140px !important"> <select id="node-input-randomUnits" style="width:140px !important">
<option value="milliseconds">Milliseconds</option> <option value="milliseconds" data-i18n="delay.milisecs"></option>
<option value="seconds">Seconds</option> <option value="seconds" data-i18n="delay.secs"></option>
<option value="minutes">Minutes</option> <option value="minutes" data-i18n="delay.mins"></option>
<option value="hours">Hours</option> <option value="hours" data-i18n="delay.hours"></option>
<option value="days">Days</option> <option value="days" data-i18n="delay.days"></option>
</select> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
</script> </script>
@ -109,16 +109,16 @@
if (this.pauseType == "delay") { if (this.pauseType == "delay") {
var units = this.timeoutUnits ? this.timeoutUnits.charAt(0) : "s"; var units = this.timeoutUnits ? this.timeoutUnits.charAt(0) : "s";
if (this.timeoutUnits == "milliseconds") { units = "ms"; } if (this.timeoutUnits == "milliseconds") { units = "ms"; }
return this.name||"delay "+this.timeout+" " + units; return this.name||this._("delay.label.delay")+" "+this.timeout+" " + units;
} else if (this.pauseType == "rate") { } else if (this.pauseType == "rate") {
var units = this.rateUnits ? this.rateUnits.charAt(0) : "s"; var units = this.rateUnits ? this.rateUnits.charAt(0) : "s";
return this.name||"limit "+this.rate+" msg/"+ units; return this.name||this._("delay.label.limit")+" "+this.rate+" msg/"+ units;
} else if (this.pauseType == "random") { } else if (this.pauseType == "random") {
return this.name || "random"; return this.name || this._("delay.label.random");
} }
else { else {
var units = this.rateUnits ? this.rateUnits.charAt(0) : "s"; var units = this.rateUnits ? this.rateUnits.charAt(0) : "s";
return this.name || "queue" +this.rate+" msg/"+ units; return this.name || this._("delay.label.queue") +this.rate+" msg/"+ units;
} }
}, },
labelStyle: function() { // sets the class to apply to the label labelStyle: function() { // sets the class to apply to the label

View File

@ -104,7 +104,7 @@ module.exports = function(RED) {
node.status({text:node.buffer.length}); node.status({text:node.buffer.length});
} }
if (node.buffer.length > 1000) { if (node.buffer.length > 1000) {
node.warn(this.name + " buffer exceeded 1000 messages"); node.warn(this.name + " " + RED._("delay.error.buffer"));
} }
} else { } else {
node.send(msg); node.send(msg);

View File

@ -16,48 +16,48 @@
<script type="text/x-red" data-template-name="trigger"> <script type="text/x-red" data-template-name="trigger">
<div class="form-row"> <div class="form-row">
Send <span data-i18n="trigger.send"></span>
<select id="node-input-op1type" style="width:200px !important"> <select id="node-input-op1type" style="width:200px !important">
<option value="val">the string payload</option> <option value="val" data-i18n="trigger.output.string"></option>
<option value="pay">the existing message</option> <option value="pay" data-i18n="trigger.output.existing"></option>
<option value="nul">nothing</option> <option value="nul" data-i18n="trigger.output.nothing"></option>
</select> </select>
<input style="width: 180px !important" type="text" id="node-input-op1"> <input style="width: 180px !important" type="text" id="node-input-op1">
</div> </div>
<div class="form-row"> <div class="form-row">
then <span data-i18n="trigger.then"></span>
<select id="node-then-type" style="width:150px;"> <select id="node-then-type" style="width:150px;">
<option value="block">wait to be reset</option> <option value="block" data-i18n="trigger.wait-reset"></option>
<option value="wait">wait for</option> <option value="wait" data-i18n="trigger.wait-for"></option>
</select> </select>
<span class="node-type-wait"> <span class="node-type-wait">
<input type="text" id="node-input-duration" style="width:70px !important"> <input type="text" id="node-input-duration" style="text-align:right; width:70px !important">
<select id="node-input-units" style="width:140px !important"> <select id="node-input-units" style="width:140px !important">
<option value="ms">Milliseconds</option> <option value="ms" data-i18n="trigger.duration.ms"></option>
<option value="s">Seconds</option> <option value="s" data-i18n="trigger.duration.s"></option>
<option value="min">Minutes</option> <option value="min" data-i18n="trigger.duration.m"></option>
<option value="hr">Hours</option> <option value="hr" data-i18n="trigger.duration.h"></option>
</select> </select>
</span> </span>
</div> </div>
<div class="form-row node-type-wait"> <div class="form-row node-type-wait">
then send <span data-i18n="trigger.then-send"></span>
<select id="node-input-op2type" style="width:200px !important"> <select id="node-input-op2type" style="width:200px !important">
<option value="val">the string payload</option> <option value="val" data-i18n="trigger.output.string"></option>
<option value="pay">the existing message</option> <option value="pay" data-i18n="trigger.output.existing"></option>
<option value="nul">nothing</option> <option value="nul" data-i18n="trigger.output.nothing"></option>
</select> </select>
<input style="width: 145px !important" type="text" id="node-input-op2"> <input style="width: 145px !important" type="text" id="node-input-op2">
</div> </div>
<div class="form-row node-type-wait"> <div class="form-row node-type-wait">
<input type="checkbox" id="node-input-extend" style="margin-left: 5px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;" for="node-input-extend">extend delay if new message arrives</label> <input type="checkbox" id="node-input-extend" style="margin-left: 5px; vertical-align: top; width: auto !important;"> <label style="width:auto !important;" for="node-input-extend" data-i18n="trigger.extend"></label>
</div> </div>
<br/> <br/>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-tips">The node can be reset by sending a message with the <b>msg.reset</b> property set</div> <div class="form-tips" data-i18n="[html]trigger.tip"></div>
</script> </script>
<script type="text/x-red" data-help-name="trigger"> <script type="text/x-red" data-help-name="trigger">
@ -95,10 +95,10 @@
icon: "trigger.png", icon: "trigger.png",
label: function() { label: function() {
if (this.duration > 0) { if (this.duration > 0) {
return this.name|| "trigger"+" "+this.duration+this.units; return this.name|| this._("trigger.label.trigger")+" "+this.duration+this.units;
} }
else { else {
return this.name|| "trigger & block"; return this.name|| this._("trigger.label.trigger-block");
} }
}, },
labelStyle: function() { labelStyle: function() {
@ -136,19 +136,19 @@
$("#node-then-type").change(); $("#node-then-type").change();
$("#node-input-op1type").change(); $("#node-input-op1type").change();
$("#node-input-op2type").change(); $("#node-input-op2type").change();
if (this.extend === "true" || this.extend === true) { if (this.extend === "true" || this.extend === true) {
$("#node-input-extend").prop("checked",true); $("#node-input-extend").prop("checked",true);
} else { } else {
$("#node-input-extend").prop("checked",false); $("#node-input-extend").prop("checked",false);
} }
}, },
oneditsave: function() { oneditsave: function() {
if ($("#node-then-type").val() == "block") { if ($("#node-then-type").val() == "block") {
$("#node-input-duration").val("0"); $("#node-input-duration").val("0");
} }
} }
}); });
</script> </script>

View File

@ -16,17 +16,17 @@
<script type="text/x-red" data-template-name="comment"> <script type="text/x-red" data-template-name="comment">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-comment"></i> Title</label> <label for="node-input-name"><i class="fa fa-comment"></i> <span data-i18n="comment.label.title"></span></label>
<input type="text" id="node-input-name" placeholder="Comment"> <input type="text" id="node-input-name">
</div> </div>
<div class="form-row" style="margin-bottom: 0px;"> <div class="form-row" style="margin-bottom: 0px;">
<label for="node-input-info" style="width: 100% !important;"><i class="fa fa-comments"></i> Body - will be rendered in info tab.</label> <label for="node-input-info" style="width: 100% !important;"><i class="fa fa-comments"></i> <span data-i18n="comment.label.body"></span></label>
<input type="hidden" id="node-input-info" autofocus="autofocus"> <input type="hidden" id="node-input-info" autofocus="autofocus">
</div> </div>
<div class="form-row node-text-editor-row"> <div class="form-row node-text-editor-row">
<div style="height: 250px;" class="node-text-editor" id="node-input-info-editor" ></div> <div style="height: 250px;" class="node-text-editor" id="node-input-info-editor"></div>
</div> </div>
<div class="form-tips">Tip: The text here can be styled as <i><a href="https://help.github.com/articles/markdown-basics/" target="_new">Github flavoured Markdown</a></i></div> <div class="form-tips" data-i18n="[html]comment.tip"></div>
</script> </script>
<script type="text/x-red" data-help-name="comment"> <script type="text/x-red" data-help-name="comment">
@ -51,8 +51,8 @@
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
}, },
info: function() { info: function() {
var t = this.name || "Comment node"; var t = this.name || this._("comment.defaulttitle");
var b = this.info || "Use this node to add simple documentation.\n\nAnything you add will be rendered in this info panel.\n\nYou may use Markdown syntax to **enhance** the *presentation*."; var b = this.info || this._("comment.defaultinfo");
return "### "+t+"\n"+b; return "### "+t+"\n"+b;
}, },
oneditprepare: function() { oneditprepare: function() {

View File

@ -15,10 +15,7 @@
--> -->
<script type="text/x-red" data-template-name="unknown"> <script type="text/x-red" data-template-name="unknown">
<div class="form-tips"><p>This node is a type unknown to your installation of Node-RED.</p> <div class="form-tips"><span data-i18n="[html]unknown.tip"></span></div>
<p><i>If you deploy with the node in this state, it's configuration will be preserved, but
the flow will not start until the missing type is installed.</i></p>
<p>See the Info side bar for more help</p></div>
</script> </script>
<script type="text/x-red" data-help-name="unknown"> <script type="text/x-red" data-help-name="unknown">
@ -42,7 +39,7 @@
outputs:1, outputs:1,
icon: "", icon: "",
label: function() { label: function() {
return "("+this.name+")"||"unknown"; return "("+this.name+")"||this._("unknown.label.unknown");
}, },
labelStyle: function() { labelStyle: function() {
return "node_label_unknown"; return "node_label_unknown";

View File

@ -16,9 +16,9 @@
<script type="text/x-red" data-template-name="rpi-gpio in"> <script type="text/x-red" data-template-name="rpi-gpio in">
<div class="form-row"> <div class="form-row">
<label for="node-input-pin"><i class="fa fa-circle"></i> GPIO Pin</label> <label for="node-input-pin"><i class="fa fa-circle"></i> <span data-i18n="rpi-gpio.label.gpiopin"></span></label>
<select type="text" id="node-input-pin" style="width: 250px;"> <select type="text" id="node-input-pin" style="width: 250px;">
<option value='' disabled selected style='display:none;'>select pin</option> <option value='' disabled selected style='display:none;'><span data-i18n="rpi-gpio.label.selectpin"></span></option>
<option value="3">3 - SDA1 - BCM2</option> <option value="3">3 - SDA1 - BCM2</option>
<option value="5">5 - SCL1 - BCM3</option> <option value="5">5 - SCL1 - BCM3</option>
<option value="7">7 - GPIO7 - BCM4</option> <option value="7">7 - GPIO7 - BCM4</option>
@ -40,25 +40,25 @@
&nbsp;<span id="pitype"></span> &nbsp;<span id="pitype"></span>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-intype"><i class="fa fa-level-up"></i> Resistor ?</label> <label for="node-input-intype"><i class="fa fa-level-up"></i> <span data-i18n="rpi-gpio.label.resistor"></span></label>
<select type="text" id="node-input-intype" style="width: 150px;"> <select type="text" id="node-input-intype" style="width: 150px;">
<option value="tri">none</option> <option value="tri" data-i18n="rpi-gpio.resistor.none"></option>
<option value="up">pullup</option> <option value="up" data-i18n="rpi-gpio.resistor.pullup"></option>
<option value="down">pulldown</option> <option value="down" data-i18n="rpi-gpio.resistor.pulldown"></option>
</select> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
<label>&nbsp;</label> <label>&nbsp;</label>
<input type="checkbox" id="node-input-read" style="display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-input-read" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-read" style="width: 70%;">Read initial state of pin on deploy/restart ?</label> <label for="node-input-read" style="width: 70%;"><span data-i18n="rpi-gpio.label.readinitial"></span></label>
</div> </div>
<br/> <br/>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-tips" id="pin-tip"><b>Pins in Use</b>: </div> <div class="form-tips" id="pin-tip"><span data-i18n="[html]rpi-gpio.tip.pin"></span></div>
<div class="form-tips">Tip: Only Digital Input is supported - input must be 0 or 1.</div> <div class="form-tips"><span data-i18n="[html]rpi-gpio.tip.in"></span></div>
</script> </script>
<script type="text/x-red" data-help-name="rpi-gpio in"> <script type="text/x-red" data-help-name="rpi-gpio in">
@ -97,6 +97,10 @@
}, },
oneditprepare: function() { oneditprepare: function() {
var pinnow = this.pin; var pinnow = this.pin;
var pintip = this._("rpi-gpio.tip.pin");
var pinname = this._("rpi-gpio.pinname");
var alreadyuse = this._("rpi-gpio.alreadyuse");
var alreadyset = this._("rpi-gpio.alreadyset");
$.getJSON('rpi-gpio/'+this.id,function(data) { $.getJSON('rpi-gpio/'+this.id,function(data) {
$('#pitype').text(data.type); $('#pitype').text(data.type);
if ((data.type === "Model B+") || (data.type === "Model A+")) { if ((data.type === "Model B+") || (data.type === "Model A+")) {
@ -117,14 +121,14 @@
$.getJSON('rpi-pins/'+this.id,function(data) { $.getJSON('rpi-pins/'+this.id,function(data) {
pinsInUse = data || {}; pinsInUse = data || {};
$('#pin-tip').html("<b>Pins in Use</b>: "+Object.keys(data)); $('#pin-tip').html(pintip + Object.keys(data));
}); });
$("#node-input-pin").change(function() { $("#node-input-pin").change(function() {
var pinnew = $("#node-input-pin").val(); var pinnew = $("#node-input-pin").val();
if ((pinnew) && (pinnew !== pinnow)) { if ((pinnew) && (pinnew !== pinnow)) {
if (pinsInUse.hasOwnProperty(pinnew)) { if (pinsInUse.hasOwnProperty(pinnew)) {
RED.notify("Pin "+pinnew+" already in use.","warn"); RED.notify(pinname+" "+pinnew+" "+alreadyuse,"warn");
} }
pinnow = pinnew; pinnow = pinnew;
} }
@ -133,7 +137,7 @@
$("#node-input-intype").change(function() { $("#node-input-intype").change(function() {
var newtype = $("#node-input-intype option:selected").val(); var newtype = $("#node-input-intype option:selected").val();
if ((pinsInUse.hasOwnProperty(pinnow)) && (pinsInUse[pinnow] !== newtype)) { if ((pinsInUse.hasOwnProperty(pinnow)) && (pinsInUse[pinnow] !== newtype)) {
RED.notify("Pin "+pinnow+" already set as "+pinsInUse[pinnow],"error"); RED.notify(pinname+" "+pinnow+" "+alreadyset+" "+pinsInUse[pinnow],"error");
} }
}); });
} }
@ -142,9 +146,9 @@
<script type="text/x-red" data-template-name="rpi-gpio out"> <script type="text/x-red" data-template-name="rpi-gpio out">
<div class="form-row"> <div class="form-row">
<label for="node-input-pin"><i class="fa fa-circle"></i> GPIO Pin</label> <label for="node-input-pin"><i class="fa fa-circle"></i> <span data-i18n="rpi-gpio.label.gpiopin"></span></label>
<select type="text" id="node-input-pin" style="width: 250px;"> <select type="text" id="node-input-pin" style="width: 250px;">
<option value='' disabled selected style='display:none;'>select pin</option> <option value='' disabled selected style='display:none;'><span data-i18n="rpi-gpio.label.selectpin"></span></option>
<option value="3">3 - SDA1 - BCM2</option> <option value="3">3 - SDA1 - BCM2</option>
<option value="5">5 - SCL1 - BCM3</option> <option value="5">5 - SCL1 - BCM3</option>
<option value="7">7 - GPIO7 - BCM4</option> <option value="7">7 - GPIO7 - BCM4</option>
@ -166,32 +170,32 @@
&nbsp;<span id="pitype"></span> &nbsp;<span id="pitype"></span>
</div> </div>
<div class="form-row" id="node-set-pwm"> <div class="form-row" id="node-set-pwm">
<label>&nbsp;&nbsp;&nbsp;&nbsp;Type</label> <label>&nbsp;&nbsp;&nbsp;&nbsp;<span data-i18n="rpi-gpio.label.type"></span></label>
<select id="node-input-out" style="width: 250px;"> <select id="node-input-out" style="width: 250px;">
<option value="out">Digital output</option> <option value="out" data-i18n="rpi-gpio.digout"></option>
<option value="pwm">PWM output</option> <option value="pwm" data-i18n="rpi-gpio.pwmout"></option>
</select> </select>
</div> </div>
<div class="form-row" id="node-set-tick"> <div class="form-row" id="node-set-tick">
<label>&nbsp;</label> <label>&nbsp;</label>
<input type="checkbox" id="node-input-set" style="display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-input-set" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-set" style="width: 70%;">Initialise pin state ?</label> <label for="node-input-set" style="width: 70%;"><span data-i18n="rpi-gpio.label.initpin"></span></label>
</div> </div>
<div class="form-row" id="node-set-state"> <div class="form-row" id="node-set-state">
<label for="node-input-level">&nbsp;</label> <label for="node-input-level">&nbsp;</label>
<select id="node-input-level" style="width: 250px;"> <select id="node-input-level" style="width: 250px;">
<option value="0">initial level of pin - low (0)</option> <option value="0" data-i18n="rpi-gpio.initpin0"></option>
<option value="1">initial level of pin - high (1)</option> <option value="1" data-i18n="rpi-gpio.initpin1"></option>
</select> </select>
</div> </div>
<br/> <br/>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-tips" id="pin-tip"><b>Pins in Use</b>: </div> <div class="form-tips" id="pin-tip"><span data-i18n="[html]rpi-gpio.tip.pin"></span></div>
<div class="form-tips" id="dig-tip"><b>Tip</b>: For digital output - input must be 0 or 1.</div> <div class="form-tips" id="dig-tip"><span data-i18n="[html]rpi-gpio.tip.dig"></span></div>
<div class="form-tips" id="pwm-tip"><b>Tip</b>: For PWM output - input must be between 0 to 100.</div> <div class="form-tips" id="pwm-tip"><span data-i18n="[html]rpi-gpio.tip.pwm"></span></div>
</script> </script>
<script type="text/x-red" data-help-name="rpi-gpio out"> <script type="text/x-red" data-help-name="rpi-gpio out">
@ -234,6 +238,10 @@
}, },
oneditprepare: function() { oneditprepare: function() {
var pinnow = this.pin; var pinnow = this.pin;
var pintip = this._("rpi-gpio.tip.pin");
var pinname = this._("rpi-gpio.pinname");
var alreadyuse = this._("rpi-gpio.alreadyuse");
var alreadyset = this._("rpi-gpio.alreadyset");
if (!$("#node-input-out").val()) { $("#node-input-out").val("out"); } if (!$("#node-input-out").val()) { $("#node-input-out").val("out"); }
$.getJSON('rpi-gpio/'+this.id,function(data) { $.getJSON('rpi-gpio/'+this.id,function(data) {
$('#pitype').text(data.type); $('#pitype').text(data.type);
@ -255,14 +263,14 @@
$.getJSON('rpi-pins/'+this.id,function(data) { $.getJSON('rpi-pins/'+this.id,function(data) {
pinsInUse = data || {}; pinsInUse = data || {};
$('#pin-tip').html("<b>Pins in Use</b>: "+Object.keys(data)); $('#pin-tip').html(pintip + Object.keys(data));
}); });
$("#node-input-pin").change(function() { $("#node-input-pin").change(function() {
var pinnew = $("#node-input-pin").val(); var pinnew = $("#node-input-pin").val();
if ((pinnew) && (pinnew !== pinnow)) { if ((pinnew) && (pinnew !== pinnow)) {
if (pinsInUse.hasOwnProperty(pinnew)) { if (pinsInUse.hasOwnProperty(pinnew)) {
RED.notify("Pin "+pinnew+" already in use.","warn"); RED.notify(pinname+" "+pinnew+" "+alreadyuse,"warn");
} }
pinnow = pinnew; pinnow = pinnew;
} }
@ -271,7 +279,7 @@
$("#node-input-out").change(function() { $("#node-input-out").change(function() {
var newtype = $("#node-input-out option:selected").val(); var newtype = $("#node-input-out option:selected").val();
if ((pinsInUse.hasOwnProperty(pinnow)) && (pinsInUse[pinnow] !== newtype)) { if ((pinsInUse.hasOwnProperty(pinnow)) && (pinsInUse[pinnow] !== newtype)) {
RED.notify("Pin "+pinnow+" already set as "+pinsInUse[pinnow],"error"); RED.notify(pinname+" "+pinnow+" "+alreadyset+" "+pinsInUse[pinnow],"error");
} }
}); });
@ -307,17 +315,17 @@
<script type="text/x-red" data-template-name="rpi-mouse"> <script type="text/x-red" data-template-name="rpi-mouse">
<div class="form-row"> <div class="form-row">
<label for="node-input-butt"><i class="fa fa-circle"></i> Button</label> <label for="node-input-butt"><i class="fa fa-circle"></i> <span data-i18n="rpi-gpio.label.button"></span></label>
<select type="text" id="node-input-butt" style="width: 250px;"> <select type="text" id="node-input-butt" style="width: 250px;">
<option value="1">left</option> <option value="1" data-i18n="rpi-gpio.left"></option>
<option value="2">right</option> <option value="2" data-i18n="rpi-gpio.right"></option>
<option value="4">middle</option> <option value="4" data-i18n="rpi-gpio.middle"></option>
<option value="7">any</option> <option value="7" data-i18n="rpi-gpio.any"></option>
</select> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
</script> </script>
@ -341,10 +349,10 @@
outputs:1, outputs:1,
icon: "rpi.png", icon: "rpi.png",
label: function() { label: function() {
var na = "Pi Mouse"; var na = this._("rpi-gpio.label.pimouse");
if (this.butt === "1") { na += " Left"; } if (this.butt === "1") { na += " "+this._("rpi-gpio.label.left"); }
if (this.butt === "2") { na += " Right"; } if (this.butt === "2") { na += " "+this._("rpi-gpio.label.right"); }
if (this.butt === "4") { na += " Middle"; } if (this.butt === "4") { na += " "+this._("rpi-gpio.label.middle"); }
return this.name||na; return this.name||na;
}, },
labelStyle: function() { labelStyle: function() {

View File

@ -23,25 +23,25 @@ module.exports = function(RED) {
var gpioCommand = __dirname+'/nrgpio'; var gpioCommand = __dirname+'/nrgpio';
if (!fs.existsSync("/dev/ttyAMA0")) { // unlikely if not on a Pi if (!fs.existsSync("/dev/ttyAMA0")) { // unlikely if not on a Pi
//RED.log.info("Ignoring Raspberry Pi specific node."); //RED.log.info(RED._("rpi-gpio.errors.ignorenode"));
throw "Info : Ignoring Raspberry Pi specific node."; throw "Info : "+RED._("rpi-gpio.errors.ignorenode");
} }
if (!fs.existsSync("/usr/share/doc/python-rpi.gpio")) { if (!fs.existsSync("/usr/share/doc/python-rpi.gpio")) {
RED.log.warn("Can't find Pi RPi.GPIO python library."); RED.log.warn(RED._("rpi-gpio.errors.libnotfound"));
throw "Warning : Can't find Pi RPi.GPIO python library."; 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(gpioCommand+" needs to be executable."); RED.log.error(RED._("rpi-gpio.errors.needtobeexecutable",{command:gpioCommand}));
throw "Error : nrgpio must to be executable."; throw "Error : "+RED._("rpi-gpio.errors.mustbeexecutable");
} }
// the magic to make python print stuff immediately // the magic to make python print stuff immediately
process.env.PYTHONUNBUFFERED = 1; process.env.PYTHONUNBUFFERED = 1;
var pinsInUse = {}; var pinsInUse = {};
var pinTypes = {"out":"digital output", "tri":"input", "up":"input with pull up", "down":"input with pull down", "pwm":"PWM output"}; var pinTypes = {"out":RED._("rpi-gpio.types.digout"), "tri":RED._("rpi-gpio.types.input"), "up":RED._("rpi-gpio.types.pullup"), "down":RED._("rpi-gpio.types.pulldown"), "pwm":RED._("rpi-gpio.types.pwmout")};
function GPIOInNode(n) { function GPIOInNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
@ -56,7 +56,7 @@ module.exports = function(RED) {
} }
else { else {
if ((pinsInUse[this.pin] !== this.intype)||(pinsInUse[this.pin] === "pwm")) { if ((pinsInUse[this.pin] !== this.intype)||(pinsInUse[this.pin] === "pwm")) {
node.warn("GPIO pin "+this.pin+" already set as "+pinTypes[pinsInUse[this.pin]]); node.warn(RED._("rpi-gpio.errors.alreadyset",{pin:this.pin,type:pinTypes[pinsInUse[this.pin]]}));
} }
} }
@ -67,7 +67,7 @@ module.exports = function(RED) {
node.child = spawn(gpioCommand, ["in",node.pin,node.intype]); node.child = spawn(gpioCommand, ["in",node.pin,node.intype]);
} }
node.running = true; node.running = true;
node.status({fill:"green",shape:"dot",text:"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 = data.toString().trim(); data = data.toString().trim();
@ -88,27 +88,27 @@ module.exports = function(RED) {
node.child.on('close', function (code) { node.child.on('close', function (code) {
node.child = null; node.child = null;
node.running = false; node.running = false;
if (RED.settings.verbose) { node.log("closed"); } if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
if (node.done) { if (node.done) {
node.status({fill:"grey",shape:"ring",text:"closed"}); node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
node.done(); node.done();
} }
else { node.status({fill:"red",shape:"ring",text:"stopped"}); } else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
}); });
node.child.on('error', function (err) { node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error('nrgpio command not found'); } if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
else if (err.errno === "EACCES") { node.error('nrgpio command not executable'); } else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
else { node.error('error: ' + err.errno); } else { node.error(RED._("rpi-gpio.errors.error",{error:err.errno})) }
}); });
} }
else { else {
node.warn("Invalid GPIO pin: "+node.pin); node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
} }
node.on("close", function(done) { node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"close"}); node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
delete pinsInUse[node.pin]; delete pinsInUse[node.pin];
if (node.child != null) { if (node.child != null) {
node.done = done; node.done = done;
@ -133,7 +133,7 @@ module.exports = function(RED) {
} }
else { else {
if ((pinsInUse[this.pin] !== this.out)||(pinsInUse[this.pin] === "pwm")) { if ((pinsInUse[this.pin] !== this.out)||(pinsInUse[this.pin] === "pwm")) {
node.warn("GPIO pin "+this.pin+" already set as "+pinTypes[pinsInUse[this.pin]]); node.warn(RED._("rpi-gpio.errors.alreadyset",{pin:this.pin,type:pinTypes[pinsInUse[this.pin]]}));
} }
} }
@ -150,11 +150,11 @@ module.exports = function(RED) {
node.status({fill:"green",shape:"dot",text:msg.payload.toString()}); node.status({fill:"green",shape:"dot",text:msg.payload.toString()});
} }
else { else {
node.error("nrpgio python command not running",msg); node.error(RED._("rpi-gpio.errors.pythoncommandnotfound"),msg);
node.status({fill:"red",shape:"ring",text:"not running"}); node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.not-running"});
} }
} }
else { node.warn("Invalid input: "+out); } else { node.warn(RED._("rpi-gpio.errors.invalidinput")+": "+out); }
} }
if (node.pin !== undefined) { if (node.pin !== undefined) {
@ -164,7 +164,7 @@ module.exports = function(RED) {
node.child = spawn(gpioCommand, [node.out,node.pin]); node.child = spawn(gpioCommand, [node.out,node.pin]);
} }
node.running = true; node.running = true;
node.status({fill:"green",shape:"dot",text:"OK"}); node.status({fill:"green",shape:"dot",text:"common.status.ok"});
node.on("input", inputlistener); node.on("input", inputlistener);
@ -179,27 +179,27 @@ module.exports = function(RED) {
node.child.on('close', function (code) { node.child.on('close', function (code) {
node.child = null; node.child = null;
node.running = false; node.running = false;
if (RED.settings.verbose) { node.log("closed"); } if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
if (node.done) { if (node.done) {
node.status({fill:"grey",shape:"ring",text:"closed"}); node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
node.done(); node.done();
} }
else { node.status({fill:"red",shape:"ring",text:"stopped"}); } else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
}); });
node.child.on('error', function (err) { node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error('nrgpio command not found'); } if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
else if (err.errno === "EACCES") { node.error('nrgpio command not executable'); } else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
else { node.error('error: ' + err.errno); } else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
}); });
} }
else { else {
node.warn("Invalid GPIO pin: "+node.pin); node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
} }
node.on("close", function(done) { node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"close"}); node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
delete pinsInUse[node.pin]; delete pinsInUse[node.pin];
if (node.child != null) { if (node.child != null) {
node.done = done; node.done = done;
@ -214,14 +214,14 @@ module.exports = function(RED) {
var pitype = { type:"" }; var pitype = { type:"" };
exec(gpioCommand+" rev 0", function(err,stdout,stderr) { exec(gpioCommand+" rev 0", function(err,stdout,stderr) {
if (err) { if (err) {
RED.log.info('Version command failed for some reason.'); RED.log.info(RED._("rpi-gpio.errors.version"));
} }
else { else {
if (stdout.trim() == "0") { pitype = { type:"Compute" }; } if (stdout.trim() == "0") { pitype = { type:"Compute" }; }
else if (stdout.trim() == "1") { pitype = { type:"A/B v1" }; } else if (stdout.trim() == "1") { pitype = { type:"A/B v1" }; }
else if (stdout.trim() == "2") { pitype = { type:"A/B v2" }; } else if (stdout.trim() == "2") { pitype = { type:"A/B v2" }; }
else if (stdout.trim() == "3") { pitype = { type:"Model B+" }; } else if (stdout.trim() == "3") { pitype = { type:"Model B+" }; }
else { RED.log.info("Saw Pi Type",stdout.trim()); } else { RED.log.info(RED._("rpi-gpio.errors.sawpitype"),stdout.trim()); }
} }
}); });
RED.nodes.registerType("rpi-gpio out",GPIOOutNode); RED.nodes.registerType("rpi-gpio out",GPIOOutNode);
@ -232,7 +232,7 @@ module.exports = function(RED) {
var node = this; var node = this;
node.child = spawn(gpioCommand+".py", ["mouse",node.butt]); node.child = spawn(gpioCommand+".py", ["mouse",node.butt]);
node.status({fill:"green",shape:"dot",text:"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);
@ -247,22 +247,22 @@ module.exports = function(RED) {
node.child.on('close', function (code) { node.child.on('close', function (code) {
node.child = null; node.child = null;
node.running = false; node.running = false;
if (RED.settings.verbose) { node.log("closed"); } if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
if (node.done) { if (node.done) {
node.status({fill:"grey",shape:"ring",text:"closed"}); node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
node.done(); node.done();
} }
else { node.status({fill:"red",shape:"ring",text:"stopped"}); } else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
}); });
node.child.on('error', function (err) { node.child.on('error', function (err) {
if (err.errno === "ENOENT") { node.error('nrgpio command not found'); } if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
else if (err.errno === "EACCES") { node.error('nrgpio ommand not executable'); } else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
else { node.error('error: ' + err.errno); } else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
}); });
node.on("close", function(done) { node.on("close", function(done) {
node.status({fill:"grey",shape:"ring",text:"close"}); node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
if (node.child != null) { if (node.child != null) {
node.done = done; node.done = done;
node.child.kill('SIGINT'); node.child.kill('SIGINT');

View File

@ -16,16 +16,16 @@
<script type="text/x-red" data-template-name="mqtt in"> <script type="text/x-red" data-template-name="mqtt in">
<div class="form-row"> <div class="form-row">
<label for="node-input-broker"><i class="fa fa-globe"></i> Broker</label> <label for="node-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>
<input type="text" id="node-input-broker"> <input type="text" id="node-input-broker">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label> <label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
<input type="text" id="node-input-topic" placeholder="Topic"> <input type="text" id="node-input-topic" data-i18n="[placeholder]common.label.topic">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
</script> </script>
@ -58,32 +58,32 @@
<script type="text/x-red" data-template-name="mqtt out"> <script type="text/x-red" data-template-name="mqtt out">
<div class="form-row"> <div class="form-row">
<label for="node-input-broker"><i class="fa fa-globe"></i> Broker</label> <label for="node-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>
<input type="text" id="node-input-broker"> <input type="text" id="node-input-broker">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label> <label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
<input type="text" id="node-input-topic" placeholder="Topic"> <input type="text" id="node-input-topic" data-i18n="[placeholder]common.label.topic">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-qos"><i class="fa fa-empire"></i> QoS</label> <label for="node-input-qos"><i class="fa fa-empire"></i> <span data-i18n="mqtt.label.qos"></span></label>
<select id="node-input-qos" style="width:125px !important"> <select id="node-input-qos" style="width:125px !important">
<option value=""></option> <option value=""></option>
<option value="0">0</option> <option value="0">0</option>
<option value="1">1</option> <option value="1">1</option>
<option value="2">2</option> <option value="2">2</option>
</select> </select>
&nbsp;&nbsp;<i class="fa fa-history"></i>&nbsp;Retain &nbsp;<select id="node-input-retain" style="width:125px !important"> &nbsp;&nbsp;<i class="fa fa-history"></i>&nbsp;<span data-i18n="mqtt.retain"></span> &nbsp;<select id="node-input-retain" style="width:125px !important">
<option value=""></option> <option value=""></option>
<option value="false">false</option> <option value="false" data-i18n="mqtt.false"></option>
<option value="true">true</option> <option value="true" data-i18n="mqtt.true"></option>
</select> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-tips">Tip: Leave topic, qos or retain blank if you want to set them via msg properties.</div> <div class="form-tips"><span data-i18n="mqtt.tip"></span></div>
</script> </script>
<script type="text/x-red" data-help-name="mqtt out"> <script type="text/x-red" data-help-name="mqtt out">
@ -118,21 +118,21 @@
<script type="text/x-red" data-template-name="mqtt-broker"> <script type="text/x-red" data-template-name="mqtt-broker">
<div class="form-row node-input-broker"> <div class="form-row node-input-broker">
<label for="node-config-input-broker"><i class="fa fa-globe"></i> Broker</label> <label for="node-config-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>
<input class="input-append-left" type="text" id="node-config-input-broker" placeholder="localhost" style="width: 40%;" > <input class="input-append-left" type="text" id="node-config-input-broker" placeholder="localhost" style="width: 40%;" >
<label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> Port</label> <label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> <span data-i18n="mqtt.label.port"></span></label>
<input type="text" id="node-config-input-port" placeholder="Port" style="width:45px"> <input type="text" id="node-config-input-port" data-i18n="[placeholder]mqtt.label.port" style="width:45px">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-config-input-clientid"><i class="fa fa-tag"></i> Client ID</label> <label for="node-config-input-clientid"><i class="fa fa-tag"></i> <span data-i18n="mqtt.label.clientid"></span></label>
<input type="text" id="node-config-input-clientid" placeholder="Leave blank for auto generated"> <input type="text" id="node-config-input-clientid" data-i18n="[placeholder]mqtt.placeholder.clientid">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-config-input-user"><i class="fa fa-user"></i> Username</label> <label for="node-config-input-user"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
<input type="text" id="node-config-input-user"> <input type="text" id="node-config-input-user">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-config-input-password"><i class="fa fa-lock"></i> Password</label> <label for="node-config-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></label>
<input type="password" id="node-config-input-password"> <input type="password" id="node-config-input-password">
</div> </div>
</script> </script>

View File

@ -42,7 +42,7 @@ module.exports = function(RED) {
this.broker = n.broker; this.broker = n.broker;
this.brokerConfig = RED.nodes.getNode(this.broker); this.brokerConfig = RED.nodes.getNode(this.broker);
if (this.brokerConfig) { if (this.brokerConfig) {
this.status({fill:"red",shape:"ring",text:"disconnected"}); this.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port,this.brokerConfig.clientid,this.brokerConfig.username,this.brokerConfig.password); this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port,this.brokerConfig.clientid,this.brokerConfig.username,this.brokerConfig.password);
var node = this; var node = this;
if (this.topic) { if (this.topic) {
@ -55,22 +55,22 @@ module.exports = function(RED) {
node.send(msg); node.send(msg);
}, this.id); }, this.id);
this.client.on("connectionlost",function() { this.client.on("connectionlost",function() {
node.status({fill:"red",shape:"ring",text:"disconnected"}); node.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
}); });
this.client.on("connect",function() { this.client.on("connect",function() {
node.status({fill:"green",shape:"dot",text:"connected"}); node.status({fill:"green",shape:"dot",text:"common.status.connected"});
}); });
if (this.client.isConnected()) { if (this.client.isConnected()) {
node.status({fill:"green",shape:"dot",text:"connected"}); node.status({fill:"green",shape:"dot",text:"common.status.connected"});
} else { } else {
this.client.connect(); this.client.connect();
} }
} }
else { else {
this.error("topic not defined"); this.error(RED._("mqtt.errors.not-defined"));
} }
} else { } else {
this.error("missing broker configuration"); this.error(RED._("mqtt.errors.missing-config"));
} }
this.on('close', function() { this.on('close', function() {
if (this.client) { if (this.client) {
@ -90,7 +90,7 @@ module.exports = function(RED) {
this.brokerConfig = RED.nodes.getNode(this.broker); this.brokerConfig = RED.nodes.getNode(this.broker);
if (this.brokerConfig) { if (this.brokerConfig) {
this.status({fill:"red",shape:"ring",text:"disconnected"}); this.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port,this.brokerConfig.clientid,this.brokerConfig.username,this.brokerConfig.password); this.client = connectionPool.get(this.brokerConfig.broker,this.brokerConfig.port,this.brokerConfig.clientid,this.brokerConfig.username,this.brokerConfig.password);
var node = this; var node = this;
this.on("input",function(msg) { this.on("input",function(msg) {
@ -110,22 +110,22 @@ module.exports = function(RED) {
if (msg.hasOwnProperty("topic") && (typeof msg.topic === "string") && (msg.topic !== "")) { // topic must exist if (msg.hasOwnProperty("topic") && (typeof msg.topic === "string") && (msg.topic !== "")) { // topic must exist
this.client.publish(msg); // send the message this.client.publish(msg); // send the message
} }
else { node.warn("Invalid topic specified"); } else { node.warn(RED._("mqtt.errors.invalid-topic")); }
} }
}); });
this.client.on("connectionlost",function() { this.client.on("connectionlost",function() {
node.status({fill:"red",shape:"ring",text:"disconnected"}); node.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
}); });
this.client.on("connect",function() { this.client.on("connect",function() {
node.status({fill:"green",shape:"dot",text:"connected"}); node.status({fill:"green",shape:"dot",text:"common.status.connected"});
}); });
if (this.client.isConnected()) { if (this.client.isConnected()) {
node.status({fill:"green",shape:"dot",text:"connected"}); node.status({fill:"green",shape:"dot",text:"common.status.connected"});
} else { } else {
this.client.connect(); this.client.connect();
} }
} else { } else {
this.error("missing broker configuration"); this.error(RED._("mqtt.errors.missing-config"));
} }
this.on('close', function() { this.on('close', function() {
if (this.client) { if (this.client) {

View File

@ -16,7 +16,7 @@
<script type="text/x-red" data-template-name="http in"> <script type="text/x-red" data-template-name="http in">
<div class="form-row"> <div class="form-row">
<label for="node-input-method"><i class="fa fa-tasks"></i> Method</label> <label for="node-input-method"><i class="fa fa-tasks"></i> <span data-i18n="httpin.label.method"></span></label>
<select type="text" id="node-input-method" style="width:72%;"> <select type="text" id="node-input-method" style="width:72%;">
<option value="get">GET</option> <option value="get">GET</option>
<option value="post">POST</option> <option value="post">POST</option>
@ -25,18 +25,18 @@
</select> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-url"><i class="fa fa-globe"></i> url</label> <label for="node-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
<input type="text" id="node-input-url" placeholder="/url"> <input type="text" id="node-input-url" placeholder="/url">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-row row-swagger-doc"> <div class="form-row row-swagger-doc">
<label for="node-input-name"><i class="fa fa-tag"></i> Doc</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="httpin.label.doc"></span></label>
<input type="text" id="node-input-swaggerDoc"> <input type="text" id="node-input-swaggerDoc">
</div> </div>
<div id="node-input-tip" class="form-tips">The url will be relative to <code><span id="node-input-path"></span></code>.</div> <div id="node-input-tip" class="form-tips"><span data-i18n="httpin.tip.in"></span><code><span id="node-input-path"></span></code>.</div>
</script> </script>
<script type="text/x-red" data-help-name="http in"> <script type="text/x-red" data-help-name="http in">
@ -67,10 +67,10 @@
<script type="text/x-red" data-template-name="http response"> <script type="text/x-red" data-template-name="http response">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-tips">The messages sent to this node <b>must</b> originate from an <i>http input</i> node</div> <div class="form-tips"><span data-i18n="[html]httpin.tip.res"></span></div>
</script> </script>
<script type="text/x-red" data-help-name="http response"> <script type="text/x-red" data-help-name="http response">
@ -86,45 +86,45 @@
<script type="text/x-red" data-template-name="http request"> <script type="text/x-red" data-template-name="http request">
<div class="form-row"> <div class="form-row">
<label for="node-input-method"><i class="fa fa-tasks"></i> Method</label> <label for="node-input-method"><i class="fa fa-tasks"></i> <span data-i18n="httpin.label.method"></span></label>
<select type="text" id="node-input-method" style="width:72%;"> <select type="text" id="node-input-method" style="width:72%;">
<option value="GET">GET</option> <option value="GET">GET</option>
<option value="POST">POST</option> <option value="POST">POST</option>
<option value="PUT">PUT</option> <option value="PUT">PUT</option>
<option value="DELETE">DELETE</option> <option value="DELETE">DELETE</option>
<option value="use">- set by msg.method -</option> <option value="use" data-i18n="httpin.setby"></option>
</select> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-url"><i class="fa fa-globe"></i> URL</label> <label for="node-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
<input type="text" id="node-input-url" placeholder="http://"> <input type="text" id="node-input-url" placeholder="http://">
</div> </div>
<div class="form-row"> <div class="form-row">
<label>&nbsp;</label> <label>&nbsp;</label>
<input type="checkbox" id="node-input-useAuth" style="display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-input-useAuth" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-useAuth" style="width: 70%;">Use basic authentication ?</label> <label for="node-input-useAuth" style="width: 70%;"><span data-i18n="httpin.basicauth"></span></label>
</div> </div>
<div class="form-row node-input-useAuth-row"> <div class="form-row node-input-useAuth-row">
<label for="node-input-user"><i class="fa fa-user"></i> Username</label> <label for="node-input-user"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
<input type="text" id="node-input-user"> <input type="text" id="node-input-user">
</div> </div>
<div class="form-row node-input-useAuth-row"> <div class="form-row node-input-useAuth-row">
<label for="node-input-password"><i class="fa fa-lock"></i> Password</label> <label for="node-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></label>
<input type="password" id="node-input-password"> <input type="password" id="node-input-password">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-ret"><i class="fa fa-arrow-left"></i> Return</label> <label for="node-input-ret"><i class="fa fa-arrow-left"></i> <span data-i18n="httpin.label.return"></span></label>
<select type="text" id="node-input-ret" style="width:72%;"> <select type="text" id="node-input-ret" style="width:72%;">
<option value="txt">a UTF-8 string</option> <option value="txt" data-i18n="httpin.utf8"></option>
<option value="bin">a binary buffer</option> <option value="bin" data-i18n="httpin.binary"></option>
<option value="obj">a parsed JSON object</option> <option value="obj" data-i18n="httpin.json"></option>
</select> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-tips" id="tip-json" hidden>Tip: If the JSON parse fails the fetched string is returned as-is.</div> <div class="form-tips" id="tip-json" hidden><span data-i18n="httpin.tip.req"></span></div>
</script> </script>
<script type="text/x-red" data-help-name="http request"> <script type="text/x-red" data-help-name="http request">
@ -241,7 +241,7 @@
outputs:1, outputs:1,
icon: "white-globe.png", icon: "white-globe.png",
label: function() { label: function() {
return this.name||"http request"; return this.name||this._("httpin.httpreq");
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";

View File

@ -137,7 +137,7 @@ module.exports = function(RED) {
} }
}); });
} else { } else {
this.warn("Cannot create http-in node when httpNodeRoot set to false"); this.warn(RED._("httpin.errors.not-created"));
} }
} }
RED.nodes.registerType("http in",HTTPIn); RED.nodes.registerType("http in",HTTPIn);
@ -172,7 +172,7 @@ module.exports = function(RED) {
msg.res.send(statusCode,msg.payload); msg.res.send(statusCode,msg.payload);
} }
} else { } else {
node.warn("No response object"); node.warn(RED._("httpin.errors.no-response"));
} }
}); });
} }
@ -195,16 +195,16 @@ module.exports = function(RED) {
this.on("input",function(msg) { this.on("input",function(msg) {
var preRequestTimestamp = process.hrtime(); var preRequestTimestamp = process.hrtime();
node.status({fill:"blue",shape:"dot",text:"requesting"}); node.status({fill:"blue",shape:"dot",text:"httpin.status.requesting"});
var url = nodeUrl || msg.url; var url = nodeUrl || msg.url;
if (msg.url && nodeUrl && (nodeUrl !== msg.url)) { // revert change below when warning is finally removed if (msg.url && nodeUrl && (nodeUrl !== msg.url)) { // revert change below when warning is finally removed
node.warn("Warning: msg properties can no longer override set node properties. See bit.ly/nr-override-msg-props"); node.warn(RED._("common.errors.nooverride"));
} }
if (isTemplatedUrl) { if (isTemplatedUrl) {
url = mustache.render(nodeUrl,msg); url = mustache.render(nodeUrl,msg);
} }
if (!url) { if (!url) {
node.error("No url specified",msg); node.error(RED._("httpin.errors.no-url"),msg);
return; return;
} }
// url must start http:// or https:// so assume http:// if not set // url must start http:// or https:// so assume http:// if not set
@ -214,7 +214,7 @@ module.exports = function(RED) {
var method = nodeMethod.toUpperCase() || "GET"; var method = nodeMethod.toUpperCase() || "GET";
if (msg.method && n.method && (n.method !== "use")) { // warn if override option not set if (msg.method && n.method && (n.method !== "use")) { // warn if override option not set
node.warn("Warning: msg properties can no longer override fixed node properties. Use explicit override option. See bit.ly/nr-override-msg-props"); node.warn(RED._("httpin.errors.not-overridden"));
} }
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
@ -312,7 +312,7 @@ module.exports = function(RED) {
} }
else if (node.ret === "obj") { else if (node.ret === "obj") {
try { msg.payload = JSON.parse(msg.payload); } try { msg.payload = JSON.parse(msg.payload); }
catch(e) { node.warn("JSON parse error"); } catch(e) { node.warn(RED._("httpin.errors.json-error")); }
} }
node.send(msg); node.send(msg);
node.status({}); node.status({});

View File

@ -1,279 +1,276 @@
<!-- <!--
Copyright 2013 IBM Corp. Copyright 2013 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<script type="text/javascript"> <!-- WebSocket Input Node -->
function ws_oneditprepare() { <script type="text/x-red" data-template-name="websocket in">
$("#websocket-client-row").hide(); <div class="form-row">
$("#node-input-mode").change(function(){ <label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> <span data-i18n="websocket.label.type"></span></label>
if( $("#node-input-mode").val() === 'client') { <select id="node-input-mode">
$("#websocket-server-row").hide(); <option value="server" data-i18n="websocket.listenon"></option>
$("#websocket-client-row").show(); <option value="client" data-i18n="websocket.connectto"></option>
} </select>
else { </div>
$("#websocket-server-row").show(); <div class="form-row" id="websocket-server-row">
$("#websocket-client-row").hide(); <label for="node-input-server"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.path"></span></label>
} <input type="text" id="node-input-server">
}); </div>
<div class="form-row" id="websocket-client-row">
if(this.client) { <label for="node-input-client"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
$("#node-input-mode").val('client').change(); <input type="text" id="node-input-client">
} </div>
else { <div class="form-row">
$("#node-input-mode").val('server').change(); <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
} <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
} </div>
</script>
function ws_oneditsave() {
if($("#node-input-mode").val() === 'client') { <script type="text/x-red" data-help-name="websocket in">
$("#node-input-server").append('<option value="">Dummy</option>'); <p>WebSocket input node.</p>
$("#node-input-server").val(''); <p>By default, the data received from the WebSocket will be in <b>msg.payload</b>.
} The socket can be configured to expect a properly formed JSON string, in which
else { case it will parse the JSON and send on the resulting object as the entire message.</p>
$("#node-input-client").append('<option value="">Dummy</option>'); </script>
$("#node-input-client").val('');
} <script type="text/javascript">
}
(function() {
function ws_label() {
var nodeid = (this.client)?this.client:this.server; function ws_oneditprepare() {
var wsNode = RED.nodes.node(nodeid); $("#websocket-client-row").hide();
return this.name||(wsNode?"[ws] "+wsNode.label():"websocket"); $("#node-input-mode").change(function(){
} if( $("#node-input-mode").val() === 'client') {
$("#websocket-server-row").hide();
function ws_validateserver() { $("#websocket-client-row").show();
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) { }
return true; else {
} $("#websocket-server-row").show();
else { $("#websocket-client-row").hide();
return RED.nodes.node(this.server) != null; }
} });
}
if(this.client) {
function ws_validateclient() { $("#node-input-mode").val('client').change();
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) { }
return RED.nodes.node(this.client) != null; else {
} $("#node-input-mode").val('server').change();
else { }
return true; }
}
} function ws_oneditsave() {
</script> if($("#node-input-mode").val() === 'client') {
<!-- WebSocket Input Node --> $("#node-input-server").append('<option value="">Dummy</option>');
<script type="text/x-red" data-template-name="websocket in"> $("#node-input-server").val('');
<div class="form-row"> }
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> Type</label> else {
<select id="node-input-mode"> $("#node-input-client").append('<option value="">Dummy</option>');
<option value="server">Listen on</option> $("#node-input-client").val('');
<option value="client">Connect to</option> }
</select> }
</div>
<div class="form-row" id="websocket-server-row"> function ws_label() {
<label for="node-input-server"><i class="fa fa-bookmark"></i> Path</label> var nodeid = (this.client)?this.client:this.server;
<input type="text" id="node-input-server"> var wsNode = RED.nodes.node(nodeid);
</div> return this.name||(wsNode?"[ws] "+wsNode.label():"websocket");
<div class="form-row" id="websocket-client-row"> }
<label for="node-input-client"><i class="fa fa-bookmark"></i> URL</label>
<input type="text" id="node-input-client"> function ws_validateserver() {
</div> if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
<div class="form-row"> return true;
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> }
<input type="text" id="node-input-name" placeholder="Name"> else {
</div> return RED.nodes.node(this.server) != null;
</script> }
}
<script type="text/x-red" data-help-name="websocket in">
<p>WebSocket input node.</p> function ws_validateclient() {
<p>By default, the data received from the WebSocket will be in <b>msg.payload</b>. if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
The socket can be configured to expect a properly formed JSON string, in which return RED.nodes.node(this.client) != null;
case it will parse the JSON and send on the resulting object as the entire message.</p> }
</script> else {
return true;
<script type="text/javascript"> }
RED.nodes.registerType('websocket in',{ }
category: 'input',
defaults: { RED.nodes.registerType('websocket in',{
name: {value:""}, category: 'input',
server: {type:"websocket-listener", validate: ws_validateserver}, defaults: {
client: {type:"websocket-client", validate: ws_validateclient} name: {value:""},
}, server: {type:"websocket-listener", validate: ws_validateserver},
color:"rgb(215, 215, 160)", client: {type:"websocket-client", validate: ws_validateclient}
inputs:0, },
outputs:1, color:"rgb(215, 215, 160)",
icon: "white-globe.png", inputs:0,
labelStyle: function() { outputs:1,
return this.name?"node_label_italic":""; icon: "white-globe.png",
}, labelStyle: function() {
label: ws_label, return this.name?"node_label_italic":"";
oneditsave: ws_oneditsave, },
oneditprepare: ws_oneditprepare label: ws_label,
}); oneditsave: ws_oneditsave,
</script> oneditprepare: ws_oneditprepare
});
<!-- WebSocket out Node -->
<script type="text/x-red" data-template-name="websocket out"> RED.nodes.registerType('websocket out',{
<div class="form-row"> category: 'output',
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> Type</label> defaults: {
<select id="node-input-mode"> name: {value:""},
<option value="server">Listen on</option> server: {type:"websocket-listener", validate: ws_validateserver},
<option value="client">Connect to</option> client: {type:"websocket-client", validate: ws_validateclient}
</select> },
</div> color:"rgb(215, 215, 160)",
<div class="form-row" id="websocket-server-row"> inputs:1,
<label for="node-input-server"><i class="fa fa-bookmark"></i> Path</label> outputs:0,
<input type="text" id="node-input-server"> icon: "white-globe.png",
</div> align: "right",
<div class="form-row" id="websocket-client-row"> labelStyle: function() {
<label for="node-input-client"><i class="fa fa-bookmark"></i> URL</label> return this.name?"node_label_italic":"";
<input type="text" id="node-input-client"> },
</div> label: ws_label,
<div class="form-row"> oneditsave: ws_oneditsave,
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> oneditprepare: ws_oneditprepare
<input type="text" id="node-input-name" placeholder="Name"> });
</div>
</script> RED.nodes.registerType('websocket-listener',{
category: 'config',
<script type="text/x-red" data-help-name="websocket out"> defaults: {
<p>WebSocket out node.</p> path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
<p>By default, <b>msg.payload</b> will be sent over the WebSocket. The socket wholemsg: {value:"false"}
can be configured to encode the entire message object as a JSON string and send that },
over the WebSocket.</p> inputs:0,
outputs:0,
<p>If the message arriving at this node started at a WebSocket In node, the message label: function() {
will be sent back to the client that triggered the flow. Otherwise, the message var root = RED.settings.httpNodeRoot;
will be broadcast to all connected clients.</p> if (root.slice(-1) != "/") {
<p>If you want to broadcast a message that started at a WebSocket In node, you root = root+"/";
should delete the <b>msg._session</b> property within the flow</p>. }
</script> if (this.path.charAt(0) == "/") {
root += this.path.slice(1);
<script type="text/javascript"> } else {
RED.nodes.registerType('websocket out',{ root += this.path;
category: 'output', }
defaults: { return root;
name: {value:""}, },
server: {type:"websocket-listener", validate: ws_validateserver}, oneditprepare: function() {
client: {type:"websocket-client", validate: ws_validateclient} var root = RED.settings.httpNodeRoot;
}, if (root.slice(-1) == "/") {
color:"rgb(215, 215, 160)", root = root.slice(0,-1);
inputs:1, }
outputs:0, if (root == "") {
icon: "white-globe.png", $("#node-config-ws-tip").hide();
align: "right", } else {
labelStyle: function() { $("#node-config-ws-path").html(root);
return this.name?"node_label_italic":""; $("#node-config-ws-tip").show();
}, }
label: ws_label, }
oneditsave: ws_oneditsave, });
oneditprepare: ws_oneditprepare
}); RED.nodes.registerType('websocket-client',{
</script> category: 'config',
defaults: {
<!-- WebSocket Server configuration node --> path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
<script type="text/x-red" data-template-name="websocket-listener"> wholemsg: {value:"false"}
<div class="form-row"> },
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> Path</label> inputs:0,
<input type="text" id="node-config-input-path" placeholder="/ws/example"> outputs:0,
</div> label: function() {
<div class="form-row"> return this.path;
<label for="node-config-input-wholemsg">&nbsp;</label> }
<select type="text" id="node-config-input-wholemsg" style="width: 70%;"> });
<option value="false">Send/Receive payload</option>
<option value="true">Send/Receive entire message</option> })();
</select> </script>
</div>
<div class="form-tips"> <!-- WebSocket out Node -->
By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. <script type="text/x-red" data-template-name="websocket out">
The listener can be configured to send or receive the entire message object as a JSON formatted string. <div class="form-row">
<p id="node-config-ws-tip">This path will be relative to <code><span id="node-config-ws-path"></span></code>.</p> <label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> <span data-i18n="websocket.label.type"></span></label>
</div> <select id="node-input-mode">
</script> <option value="server" data-i18n="websocket.listenon"></option>
<option value="client" data-i18n="websocket.connectto"></option>
<script type="text/x-red" data-help-name="websocket-listener"> </select>
<p>This configuration node creates a WebSocket Server using the specified path</p> </div>
</script> <div class="form-row" id="websocket-server-row">
<label for="node-input-server"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.path"></span></label>
<script type="text/javascript"> <input type="text" id="node-input-server">
RED.nodes.registerType('websocket-listener',{ </div>
category: 'config', <div class="form-row" id="websocket-client-row">
defaults: { <label for="node-input-client"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) }, <input type="text" id="node-input-client">
wholemsg: {value:"false"} </div>
}, <div class="form-row">
inputs:0, <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
outputs:0, <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
label: function() { </div>
var root = RED.settings.httpNodeRoot; </script>
if (root.slice(-1) != "/") {
root = root+"/"; <script type="text/x-red" data-help-name="websocket out">
} <p>WebSocket out node.</p>
if (this.path.charAt(0) == "/") { <p>By default, <b>msg.payload</b> will be sent over the WebSocket. The socket
root += this.path.slice(1); can be configured to encode the entire message object as a JSON string and send that
} else { over the WebSocket.</p>
root += this.path;
} <p>If the message arriving at this node started at a WebSocket In node, the message
return root; will be sent back to the client that triggered the flow. Otherwise, the message
}, will be broadcast to all connected clients.</p>
oneditprepare: function() { <p>If you want to broadcast a message that started at a WebSocket In node, you
var root = RED.settings.httpNodeRoot; should delete the <b>msg._session</b> property within the flow</p>.
if (root.slice(-1) == "/") { </script>
root = root.slice(0,-1);
} <!-- WebSocket Server configuration node -->
if (root == "") { <script type="text/x-red" data-template-name="websocket-listener">
$("#node-config-ws-tip").hide(); <div class="form-row">
} else { <label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.path"></span></label>
$("#node-config-ws-path").html(root); <input type="text" id="node-config-input-path" placeholder="/ws/example">
$("#node-config-ws-tip").show(); </div>
} <div class="form-row">
} <label for="node-config-input-wholemsg">&nbsp;</label>
}); <select type="text" id="node-config-input-wholemsg" style="width: 70%;">
</script> <option value="false" data-i18n="websocket.payload"></option>
<option value="true" data-i18n="websocket.message"></option>
<!-- WebSocket Client configuration node --> </select>
<script type="text/x-red" data-template-name="websocket-client"> </div>
<div class="form-row"> <div class="form-tips">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> URL</label> <span data-i18n="[html]websocket.tip.path1"></span>
<input type="text" id="node-config-input-path" placeholder="ws://example.com/ws"> <p id="node-config-ws-tip"><span data-i18n="[html]websocket.tip.path2"></span><code><span id="node-config-ws-path"></span></code>.</p>
</div> </div>
<div class="form-row"> </script>
<label for="node-config-input-wholemsg">&nbsp;</label>
<select type="text" id="node-config-input-wholemsg" style="width: 70%;"> <script type="text/x-red" data-help-name="websocket-listener">
<option value="false">Send/Receive payload</option> <p>This configuration node creates a WebSocket Server using the specified path</p>
<option value="true">Send/Receive entire message</option> </script>
</select>
</div> <!-- WebSocket Client configuration node -->
<div class="form-tips"> <script type="text/x-red" data-template-name="websocket-client">
<p>URL should use ws:&#47;&#47; or wss:&#47;&#47; scheme and point to an existing websocket listener.</p> <div class="form-row">
By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. <label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
The client can be configured to send or receive the entire message object as a JSON formatted string. <input type="text" id="node-config-input-path" placeholder="ws://example.com/ws">
</div> </div>
</script> <div class="form-row">
<label for="node-config-input-wholemsg">&nbsp;</label>
<script type="text/x-red" data-help-name="websocket-client"> <select type="text" id="node-config-input-wholemsg" style="width: 70%;">
<p>This configuration node connects a WebSocket client to the specified URL.</p> <option value="false" data-i18n="websocket.payload"></option>
</script> <option value="true" data-i18n="websocket.message"></option>
</select>
<script type="text/javascript"> </div>
RED.nodes.registerType('websocket-client',{ <div class="form-tips">
category: 'config', <p><span data-i18n="[html]websocket.tip.url1"></span></p>
defaults: { <span data-i18n="[html]websocket.tip.url2"></span>
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) }, </div>
wholemsg: {value:"false"} </script>
},
inputs:0, <script type="text/x-red" data-help-name="websocket-client">
outputs:0, <p>This configuration node connects a WebSocket client to the specified URL.</p>
label: function() { </script>
return this.path;
}
});
</script>

View File

@ -1,232 +1,234 @@
/** /**
* Copyright 2013,2015 IBM Corp. * Copyright 2013,2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
**/ **/
module.exports = function(RED) { module.exports = function(RED) {
"use strict"; "use strict";
var ws = require("ws"); var ws = require("ws");
var inspect = require("sys").inspect; var inspect = require("sys").inspect;
// A node red node that sets up a local websocket server // A node red node that sets up a local websocket server
function WebSocketListenerNode(n) { function WebSocketListenerNode(n) {
// Create a RED node // Create a RED node
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
var node = this; var node = this;
// Store local copies of the node configuration (as defined in the .html) // Store local copies of the node configuration (as defined in the .html)
node.path = n.path; node.path = n.path;
node.wholemsg = (n.wholemsg === "true"); node.wholemsg = (n.wholemsg === "true");
node._inputNodes = []; // collection of nodes that want to receive events node._inputNodes = []; // collection of nodes that want to receive events
node._clients = {}; node._clients = {};
// match absolute url // match absolute url
node.isServer = !/^ws{1,2}:\/\//i.test(node.path); node.isServer = !/^ws{1,2}:\/\//i.test(node.path);
node.closing = false; node.closing = false;
function startconn() { // Connect to remote endpoint function startconn() { // Connect to remote endpoint
var socket = new ws(node.path); var socket = new ws(node.path);
node.server = socket; // keep for closing node.server = socket; // keep for closing
handleConnection(socket); handleConnection(socket);
} }
function handleConnection(/*socket*/socket) { function handleConnection(/*socket*/socket) {
var id = (1+Math.random()*4294967295).toString(16); var id = (1+Math.random()*4294967295).toString(16);
if (node.isServer) { node._clients[id] = socket; node.emit('opened',Object.keys(node._clients).length); } if (node.isServer) { node._clients[id] = socket; node.emit('opened',Object.keys(node._clients).length); }
socket.on('open',function() { socket.on('open',function() {
if (!node.isServer) { node.emit('opened',''); } if (!node.isServer) { node.emit('opened',''); }
}); });
socket.on('close',function() { socket.on('close',function() {
if (node.isServer) { delete node._clients[id]; node.emit('opened',Object.keys(node._clients).length); } if (node.isServer) { delete node._clients[id]; node.emit('opened',Object.keys(node._clients).length); }
else { node.emit('closed'); } else { node.emit('closed'); }
if (!node.closing && !node.isServer) { if (!node.closing && !node.isServer) {
node.tout = setTimeout(function(){ startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ? node.tout = setTimeout(function(){ startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
} }
}); });
socket.on('message',function(data,flags){ socket.on('message',function(data,flags){
node.handleEvent(id,socket,'message',data,flags); node.handleEvent(id,socket,'message',data,flags);
}); });
socket.on('error', function(err) { socket.on('error', function(err) {
node.emit('erro'); node.emit('erro');
if (!node.closing && !node.isServer) { if (!node.closing && !node.isServer) {
node.tout = setTimeout(function(){ startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ? node.tout = setTimeout(function(){ startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
} }
}); });
} }
if (node.isServer) { if (node.isServer) {
var path = RED.settings.httpNodeRoot || "/"; var path = RED.settings.httpNodeRoot || "/";
path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path); path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path);
// Workaround https://github.com/einaros/ws/pull/253 // Workaround https://github.com/einaros/ws/pull/253
// Listen for 'newListener' events from RED.server // Listen for 'newListener' events from RED.server
node._serverListeners = {}; node._serverListeners = {};
var storeListener = function(/*String*/event,/*function*/listener){ var storeListener = function(/*String*/event,/*function*/listener){
if(event == "error" || event == "upgrade" || event == "listening"){ if(event == "error" || event == "upgrade" || event == "listening"){
node._serverListeners[event] = listener; node._serverListeners[event] = listener;
} }
} }
RED.server.addListener('newListener',storeListener); RED.server.addListener('newListener',storeListener);
// Create a WebSocket Server // Create a WebSocket Server
node.server = new ws.Server({server:RED.server,path:path}); node.server = new ws.Server({server:RED.server,path:path});
// Workaround https://github.com/einaros/ws/pull/253 // Workaround https://github.com/einaros/ws/pull/253
// Stop listening for new listener events // Stop listening for new listener events
RED.server.removeListener('newListener',storeListener); RED.server.removeListener('newListener',storeListener);
node.server.on('connection', handleConnection); node.server.on('connection', handleConnection);
} }
else { else {
node.closing = false; node.closing = false;
startconn(); // start outbound connection startconn(); // start outbound connection
} }
node.on("close", function() { node.on("close", function() {
// Workaround https://github.com/einaros/ws/pull/253 // Workaround https://github.com/einaros/ws/pull/253
// Remove listeners from RED.server // Remove listeners from RED.server
if (node.isServer) { if (node.isServer) {
var listener = null; var listener = null;
for (var event in node._serverListeners) { for (var event in node._serverListeners) {
if (node._serverListeners.hasOwnProperty(event)) { if (node._serverListeners.hasOwnProperty(event)) {
listener = node._serverListeners[event]; listener = node._serverListeners[event];
if (typeof listener === "function") { if (typeof listener === "function") {
RED.server.removeListener(event,listener); RED.server.removeListener(event,listener);
} }
} }
} }
node._serverListeners = {}; node._serverListeners = {};
node.server.close(); node.server.close();
node._inputNodes = []; node._inputNodes = [];
} }
else { else {
node.closing = true; node.closing = true;
node.server.close(); node.server.close();
if (node.tout) { clearTimeout(tout); } if (node.tout) { clearTimeout(tout); }
} }
}); });
} }
RED.nodes.registerType("websocket-listener",WebSocketListenerNode); RED.nodes.registerType("websocket-listener",WebSocketListenerNode);
RED.nodes.registerType("websocket-client",WebSocketListenerNode); RED.nodes.registerType("websocket-client",WebSocketListenerNode);
WebSocketListenerNode.prototype.registerInputNode = function(/*Node*/handler) { WebSocketListenerNode.prototype.registerInputNode = function(/*Node*/handler) {
this._inputNodes.push(handler); this._inputNodes.push(handler);
} }
WebSocketListenerNode.prototype.handleEvent = function(id,/*socket*/socket,/*String*/event,/*Object*/data,/*Object*/flags){ WebSocketListenerNode.prototype.handleEvent = function(id,/*socket*/socket,/*String*/event,/*Object*/data,/*Object*/flags){
var msg; var msg;
if (this.wholemsg) { if (this.wholemsg) {
try { try {
msg = JSON.parse(data); msg = JSON.parse(data);
} }
catch(err) { catch(err) {
msg = { payload:data }; msg = { payload:data };
} }
} else { } else {
msg = { msg = {
payload:data payload:data
}; };
} }
msg._session = {type:"websocket",id:id}; msg._session = {type:"websocket",id:id};
for (var i = 0; i < this._inputNodes.length; i++) { for (var i = 0; i < this._inputNodes.length; i++) {
this._inputNodes[i].send(msg); this._inputNodes[i].send(msg);
} }
} }
WebSocketListenerNode.prototype.broadcast = function(data) { WebSocketListenerNode.prototype.broadcast = function(data) {
try { try {
if(this.isServer) { if(this.isServer) {
for (var i = 0; i < this.server.clients.length; i++) { for (var i = 0; i < this.server.clients.length; i++) {
this.server.clients[i].send(data); this.server.clients[i].send(data);
} }
} }
else { else {
this.server.send(data); this.server.send(data);
} }
} }
catch(e) { // swallow any errors catch(e) { // swallow any errors
this.warn("ws:"+i+" : "+e); this.warn("ws:"+i+" : "+e);
} }
} }
WebSocketListenerNode.prototype.reply = function(id,data) { WebSocketListenerNode.prototype.reply = function(id,data) {
var session = this._clients[id]; var session = this._clients[id];
if (session) { if (session) {
try { try {
session.send(data); session.send(data);
} }
catch(e) { // swallow any errors catch(e) { // swallow any errors
} }
} }
} }
function WebSocketInNode(n) { function WebSocketInNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
this.server = (n.client)?n.client:n.server; this.server = (n.client)?n.client:n.server;
var node = this; var node = this;
this.serverConfig = RED.nodes.getNode(this.server); this.serverConfig = RED.nodes.getNode(this.server);
if (this.serverConfig) { if (this.serverConfig) {
this.serverConfig.registerInputNode(this); this.serverConfig.registerInputNode(this);
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:"connected "+n}); }); // TODO: nls
this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"error"}); }); this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:"connected "+n}); });
this.serverConfig.on('closed', function() { node.status({fill:"red",shape:"ring",text:"disconnected"}); }); this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"error"}); });
} else { this.serverConfig.on('closed', function() { node.status({fill:"red",shape:"ring",text:"disconnected"}); });
this.error("Missing server configuration"); } else {
} this.error(RED._("websocket.errors.missing-conf"));
} }
RED.nodes.registerType("websocket in",WebSocketInNode); }
RED.nodes.registerType("websocket in",WebSocketInNode);
function WebSocketOutNode(n) {
RED.nodes.createNode(this,n); function WebSocketOutNode(n) {
var node = this; RED.nodes.createNode(this,n);
this.server = (n.client)?n.client:n.server; var node = this;
this.serverConfig = RED.nodes.getNode(this.server); this.server = (n.client)?n.client:n.server;
if (!this.serverConfig) { this.serverConfig = RED.nodes.getNode(this.server);
this.error("Missing server configuration"); if (!this.serverConfig) {
} this.error(RED._("websocket.errors.missing-conf"));
else { }
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:"connected "+n}); }); else {
this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"error"}); }); // TODO: nls
this.serverConfig.on('closed', function() { node.status({fill:"red",shape:"ring",text:"disconnected"}); }); this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:"connected "+n}); });
} this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"error"}); });
this.on("input", function(msg) { this.serverConfig.on('closed', function() { node.status({fill:"red",shape:"ring",text:"disconnected"}); });
var payload; }
if (this.serverConfig.wholemsg) { this.on("input", function(msg) {
delete msg._session; var payload;
payload = JSON.stringify(msg); if (this.serverConfig.wholemsg) {
} else if (msg.hasOwnProperty("payload")) { delete msg._session;
if (!Buffer.isBuffer(msg.payload)) { // if it's not a buffer make sure it's a string. payload = JSON.stringify(msg);
payload = RED.util.ensureString(msg.payload); } else if (msg.hasOwnProperty("payload")) {
} if (!Buffer.isBuffer(msg.payload)) { // if it's not a buffer make sure it's a string.
else { payload = RED.util.ensureString(msg.payload);
payload = msg.payload; }
} else {
} payload = msg.payload;
if (payload) { }
if (msg._session && msg._session.type == "websocket") { }
node.serverConfig.reply(msg._session.id,payload); if (payload) {
} else { if (msg._session && msg._session.type == "websocket") {
node.serverConfig.broadcast(payload,function(error){ node.serverConfig.reply(msg._session.id,payload);
if (!!error) { } else {
node.warn("An error occurred while sending:" + inspect(error)); node.serverConfig.broadcast(payload,function(error){
} if (!!error) {
}); node.warn(RED._("websocket.errors.send-error")+inspect(error));
} }
} });
}); }
} }
RED.nodes.registerType("websocket out",WebSocketOutNode); });
} }
RED.nodes.registerType("websocket out",WebSocketOutNode);
}

View File

@ -16,14 +16,14 @@
<script type="text/x-red" data-template-name="watch"> <script type="text/x-red" data-template-name="watch">
<div class="form-row node-input-filename"> <div class="form-row node-input-filename">
<label for="node-input-files"><i class="fa fa-file"></i> File(s)</label> <label for="node-input-files"><i class="fa fa-file"></i> <span data-i18n="watch.label.files"></span></label>
<input type="text" id="node-input-files" placeholder="File(s) or Directory"> <input type="text" id="node-input-files" data-i18n="[placeholder]watch.placeholder.files">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div id="node-input-tip" class="form-tips">On Windows you must use double back-slashes \\ in any directory names.</div> <div id="node-input-tip" class="form-tips"><span data-i18n="watch.tip"></span></div>
</script> </script>
<script type="text/x-red" data-help-name="watch"> <script type="text/x-red" data-help-name="watch">

View File

@ -16,43 +16,42 @@
<script type="text/x-red" data-template-name="tcp in"> <script type="text/x-red" data-template-name="tcp in">
<div class="form-row"> <div class="form-row">
<label for="node-input-server"><i class="fa fa-dot-circle-o"></i> Type</label> <label for="node-input-server"><i class="fa fa-dot-circle-o"></i> <span data-i18n="tcpin.label.type"></span></label>
<select id="node-input-server" style="width:120px; margin-right:5px;"> <select id="node-input-server" style="width:120px; margin-right:5px;">
<option value="server">Listen on</option> <option value="server" data-i18n="tcpin.type.listen"></option>
<option value="client">Connect to</option> <option value="client" data-i18n="tcpin.type.connect"></option>
</select> </select>
port <input type="text" id="node-input-port" style="width: 50px"> <span data-i18n="tcpin.label.port"></span> <input type="text" id="node-input-port" style="width: 50px">
</div> </div>
<div class="form-row hidden" id="node-input-host-row" style="padding-left: 110px;"> <div class="form-row hidden" id="node-input-host-row" style="padding-left: 110px;">
at host <input type="text" id="node-input-host" placeholder="localhost" style="width: 60%;"> <span data-i18n="tcpin.label.host"></span> <input type="text" id="node-input-host" placeholder="localhost" style="width: 60%;">
</div> </div>
<div class="form-row"> <div class="form-row">
<label><i class="fa fa-sign-out"></i> Output</label> <label><i class="fa fa-sign-out"></i> <span data-i18n="tcpin.label.output"></span></label>
a
<select id="node-input-datamode" style="width:110px;"> <select id="node-input-datamode" style="width:110px;">
<option value="stream">stream of</option> <option value="stream" data-i18n="tcpin.output.stream"></option>
<option value="single">single</option> <option value="single" data-i18n="tcpin.output.single"></option>
</select> </select>
<select id="node-input-datatype" style="width:140px;"> <select id="node-input-datatype" style="width:140px;">
<option value="buffer">Buffer</option> <option value="buffer" data-i18n="tcpin.output.buffer"></option>
<option value="utf8">String</option> <option value="utf8" data-i18n="tcpin.output.string"></option>
<option value="base64">Base64 String</option> <option value="base64" data-i18n="tcpin.output.base64"></option>
</select> </select>
payload<span id="node-input-datamode-plural">s</span> <span data-i18n="tcpin.label.payload"></span>
</div> </div>
<div id="node-row-newline" class="form-row hidden" style="padding-left: 110px;"> <div id="node-row-newline" class="form-row hidden" style="padding-left: 110px;">
delimited by <input type="text" id="node-input-newline" style="width: 110px;"> <span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width: 110px;">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label> <label for="node-input-topic"><i class="fa fa-tasks"></i> <span data-i18n="common.label.topic"></span></label>
<input type="text" id="node-input-topic" placeholder="Topic"> <input type="text" id="node-input-topic" data-i18n="[placeholder]common.label.topic">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
</script> </script>
@ -96,14 +95,12 @@
var datamode = $("#node-input-datamode option:selected").val(); var datamode = $("#node-input-datamode option:selected").val();
var datatype = $("#node-input-datatype option:selected").val(); var datatype = $("#node-input-datatype option:selected").val();
if (datamode == "stream") { if (datamode == "stream") {
$("#node-input-datamode-plural").show();
if (datatype == "utf8") { if (datatype == "utf8") {
$("#node-row-newline").show(); $("#node-row-newline").show();
} else { } else {
$("#node-row-newline").hide(); $("#node-row-newline").hide();
} }
} else { } else {
$("#node-input-datamode-plural").hide();
$("#node-row-newline").hide(); $("#node-row-newline").hide();
} }
}; };
@ -118,41 +115,34 @@
<script type="text/x-red" data-template-name="tcp out"> <script type="text/x-red" data-template-name="tcp out">
<div class="form-row"> <div class="form-row">
<label for="node-input-beserver"><i class="fa fa-dot-circle-o"></i> Type</label> <label for="node-input-beserver"><i class="fa fa-dot-circle-o"></i> <span data-i18n="tcpin.label.type"></span></label>
<select id="node-input-beserver" style="width:150px; margin-right:5px;"> <select id="node-input-beserver" style="width:150px; margin-right:5px;">
<option value="server">Listen on</option> <option value="server" data-i18n="tcpin.type.listen"></option>
<option value="client">Connect to</option> <option value="client" data-i18n="tcpin.type.connect"></option>
<option value="reply">Reply to TCP</option> <option value="reply" data-i18n="tcpin.type.reply"></option>
</select> </select>
<span id="node-input-port-row">port <input type="text" id="node-input-port" style="width: 50px"></span> <span id="node-input-port-row"><span data-i18n="tcpin.label.port"></span> <input type="text" id="node-input-port" style="width: 50px"></span>
</div> </div>
<div class="form-row hidden" id="node-input-host-row" style="padding-left: 110px;"> <div class="form-row hidden" id="node-input-host-row" style="padding-left: 110px;">
at host <input type="text" id="node-input-host" placeholder="localhost" style="width: 60%;"> <span data-i18n="tcpin.label.host"></span> <input type="text" id="node-input-host" style="width: 60%;">
</div> </div>
<div class="form-row hidden" id="node-input-end-row"> <div class="form-row hidden" id="node-input-end-row">
<label>&nbsp;</label> <label>&nbsp;</label>
<input type="checkbox" id="node-input-end" style="display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-input-end" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-end" style="width: 70%;">Close connection after each message is sent ?</label> <label for="node-input-end" style="width: 70%;"><span data-i18n="tcpin.label.close-connection"></span></label>
</div> </div>
<div class="form-row"> <div class="form-row">
<label>&nbsp;</label> <label>&nbsp;</label>
<input type="checkbox" id="node-input-base64" placeholder="base64" style="display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-input-base64" placeholder="base64" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-base64" style="width: 70%;">Decode Base64 message ?</label> <label for="node-input-base64" style="width: 70%;"><span data-i18n="tcpin.label.decode-base64"></span></label>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-tips hidden" id="fin-tip">
<b>Note:</b> Closing the connection after each message is generally not a good thing - but is useful to indicate an end-of-file for example.
</div>
<div class="form-tips hidden" id="fin-tip2">
<b>Note:</b> Closing the connection after each message is generally not a good thing - but is useful to indicate an end-of-file for example. The receiving client will need to reconnect.
</div> </div>
</script> </script>
@ -201,17 +191,8 @@
if (sockettype == "client") { if (sockettype == "client") {
$("#node-input-host-row").show(); $("#node-input-host-row").show();
$("#fin-tip").show();
} else { } else {
$("#node-input-host-row").hide(); $("#node-input-host-row").hide();
$("#fin-tip").hide();
}
if (sockettype == "server") {
$("#fin-tip2").show();
}
else {
$("#fin-tip2").hide();
} }
}; };
updateOptions(); updateOptions();
@ -223,27 +204,25 @@
<script type="text/x-red" data-template-name="tcp request"> <script type="text/x-red" data-template-name="tcp request">
<div class="form-row"> <div class="form-row">
<label for="node-input-server"><i class="fa fa-globe"></i> Server</label> <label for="node-input-server"><i class="fa fa-globe"></i> <span data-i18n="tcpin.label.server"></span></label>
<input type="text" id="node-input-server" placeholder="ip.address" style="width:45%"> <input type="text" id="node-input-server" placeholder="ip.address" style="width:45%">
&nbsp;port <input type="text" id="node-input-port" placeholder="number" style="width:60px"> &nbsp;port <input type="text" id="node-input-port" style="width:60px">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-out"><i class="fa fa-sign-out"></i> Return</label> <label for="node-input-out"><i class="fa fa-sign-out"></i> <span data-i18n="tcpin.label.return"></span></label>
<select type="text" id="node-input-out" style="width:56%;"> <select type="text" id="node-input-out" style="width:56%;">
<option value="time">after a fixed timeout of</option> <option value="time" data-i18n="tcpin.return.timeout"></option>
<option value="char">when character received is</option> <option value="char" data-i18n="tcpin.return.character"></option>
<option value="count">a fixed number of characters</option> <option value="count" data-i18n="tcpin.return.number"></option>
<option value="sit">never. Keep connection open</option> <option value="sit" data-i18n="tcpin.return.never"></option>
</select> </select>
<input type="text" id="node-input-splitc" style="width:50px;"> <input type="text" id="node-input-splitc" style="width:50px;">
<span id="node-units"></span> <span id="node-units"></span>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-tips"><b>Tip:</b> Outputs a binary <b>Buffer</b>, so you may want to .toString() it.</br/>
<b>Tip:</b> Leave host and port blank if you want to overide with msg.host and msg.port properties.</div>
<script> <script>
var previous = null; var previous = null;
$("#node-input-out").on('focus', function () { previous = this.value; }).change(function() { $("#node-input-out").on('focus', function () { previous = this.value; }).change(function() {

View File

@ -43,14 +43,14 @@ module.exports = function(RED) {
var reconnectTimeout; var reconnectTimeout;
var end = false; var end = false;
var setupTcpClient = function() { var setupTcpClient = function() {
node.log("connecting to "+node.host+":"+node.port); node.log(RED._("tcpin.status.connecting",{host:node.host,port:node.port}));
node.status({fill:"grey",shape:"dot",text:"connecting"}); node.status({fill:"grey",shape:"dot",text:"common.status.connecting"});
var id = (1+Math.random()*4294967295).toString(16); var id = (1+Math.random()*4294967295).toString(16);
client = net.connect(node.port, node.host, function() { client = net.connect(node.port, node.host, function() {
buffer = (node.datatype == 'buffer')? new Buffer(0):""; buffer = (node.datatype == 'buffer')? new Buffer(0):"";
node.connected = true; node.connected = true;
node.log("connected to "+node.host+":"+node.port); node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port}));
node.status({fill:"green",shape:"dot",text:"connected"}); node.status({fill:"green",shape:"dot",text:"common.status.connected"});
}); });
connectionPool[id] = client; connectionPool[id] = client;
@ -96,14 +96,14 @@ module.exports = function(RED) {
client.on('close', function() { client.on('close', function() {
delete connectionPool[id]; delete connectionPool[id];
node.connected = false; node.connected = false;
node.status({fill:"red",shape:"ring",text:"disconnected"}); node.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
if (!node.closing) { if (!node.closing) {
if (end) { // if we were asked to close then try to reconnect once very quick. if (end) { // if we were asked to close then try to reconnect once very quick.
end = false; end = false;
reconnectTimeout = setTimeout(setupTcpClient, 20); reconnectTimeout = setTimeout(setupTcpClient, 20);
} }
else { else {
node.log("connection lost to "+node.host+":"+node.port); node.log(RED._("tcpin.errors.connection-lost",{host:node.host,port:node.port}));
reconnectTimeout = setTimeout(setupTcpClient, reconnectTime); reconnectTimeout = setTimeout(setupTcpClient, reconnectTime);
} }
} else { } else {
@ -128,7 +128,7 @@ module.exports = function(RED) {
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); } if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
var id = (1+Math.random()*4294967295).toString(16); var id = (1+Math.random()*4294967295).toString(16);
connectionPool[id] = socket; connectionPool[id] = socket;
node.status({text:++count+" connections"}); node.status({text:RED._("tcpin.status.connections",{count:++count})});
var buffer = (node.datatype == 'buffer')? new Buffer(0):""; var buffer = (node.datatype == 'buffer')? new Buffer(0):"";
socket.on('data', function (data) { socket.on('data', function (data) {
@ -170,12 +170,12 @@ module.exports = function(RED) {
} }
}); });
socket.on('timeout', function() { socket.on('timeout', function() {
node.log('timeout closed socket port '+node.port); node.log(RED._("tcpin.errors.timeout",{port:node.port}));
socket.end(); socket.end();
}); });
socket.on('close', function() { socket.on('close', function() {
delete connectionPool[id]; delete connectionPool[id];
node.status({text:--count+" connections"}); node.status({text:RED._("tcpin.status.connections",{count:--count})});
}); });
socket.on('error',function(err) { socket.on('error',function(err) {
node.log(err); node.log(err);
@ -183,15 +183,15 @@ module.exports = function(RED) {
}); });
server.on('error', function(err) { server.on('error', function(err) {
if (err) { if (err) {
node.error('unable to listen on port '+node.port+' : '+err); node.error(RED._("tcpin.errors.cannot-listen",{port:node.port,error:err.toString()}));
} }
}); });
server.listen(node.port, function(err) { server.listen(node.port, function(err) {
if (err) { if (err) {
node.error('unable to listen on port '+node.port+' : '+err); node.error(RED._("tcpin.errors.cannot-listen",{port:node.port,error:err.toString()}));
} else { } else {
node.log('listening on port '+node.port); node.log(RED._("tcpin.status.listening-port",{port:node.port}));
node.on('close', function() { node.on('close', function() {
for (var c in connectionPool) { for (var c in connectionPool) {
if (connectionPool.hasOwnProperty(c)) { if (connectionPool.hasOwnProperty(c)) {
@ -201,7 +201,7 @@ module.exports = function(RED) {
} }
node.closing = true; node.closing = true;
server.close(); server.close();
node.log('stopped listening on port '+node.port); node.log(RED._("tcpin.status.stopped-listening",{port:node.port}));
}); });
} }
}); });
@ -228,20 +228,20 @@ module.exports = function(RED) {
var end = false; var end = false;
var setupTcpClient = function() { var setupTcpClient = function() {
node.log("connecting to "+node.host+":"+node.port); node.log(RED._("tcpin.status.connecting",{host:node.host,port:node.port}));
node.status({fill:"grey",shape:"dot",text:"connecting"}); node.status({fill:"grey",shape:"dot",text:"common.status.connecting"});
client = net.connect(node.port, node.host, function() { client = net.connect(node.port, node.host, function() {
node.connected = true; node.connected = true;
node.log("connected to "+node.host+":"+node.port); node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port}));
node.status({fill:"green",shape:"dot",text:"connected"}); node.status({fill:"green",shape:"dot",text:"common.status.connected"});
}); });
client.on('error', function (err) { client.on('error', function (err) {
node.log(err); node.log(RED._("tcpin.errors.error",{error:err.toString()}));
}); });
client.on('end', function (err) { client.on('end', function (err) {
}); });
client.on('close', function() { client.on('close', function() {
node.status({fill:"red",shape:"ring",text:"disconnected"}); node.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
node.connected = false; node.connected = false;
client.destroy(); client.destroy();
if (!node.closing) { if (!node.closing) {
@ -250,7 +250,7 @@ module.exports = function(RED) {
reconnectTimeout = setTimeout(setupTcpClient,20); reconnectTimeout = setTimeout(setupTcpClient,20);
} }
else { else {
node.log("connection lost to "+node.host+":"+node.port); node.log(RED._("tcpin.errors.connection-lost",{host:node.host,port:node.port}));
reconnectTimeout = setTimeout(setupTcpClient,reconnectTime); reconnectTimeout = setTimeout(setupTcpClient,reconnectTime);
} }
} else { } else {
@ -301,26 +301,26 @@ module.exports = function(RED) {
}); });
} else { } else {
var connectedSockets = []; var connectedSockets = [];
node.status({text:"0 connections"}); node.status({text:RED._("tcpin.status.connections",{count:0})});
var server = net.createServer(function (socket) { var server = net.createServer(function (socket) {
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); } if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
var remoteDetails = socket.remoteAddress+":"+socket.remotePort; var remoteDetails = socket.remoteAddress+":"+socket.remotePort;
node.log("connection from "+remoteDetails); node.log(RED._("tcpin.status.connection-from",{host:socket.remoteAddress, port:socket.remotePort}));
connectedSockets.push(socket); connectedSockets.push(socket);
node.status({text:connectedSockets.length+" connections"}); node.status({text:connectedSockets.length+" "+"tcpin.status.connections")});
socket.on('timeout', function() { socket.on('timeout', function() {
node.log('timeout closed socket port '+node.port); node.log(RED._("tcpin.errors.timeout",{port:node.port}));
socket.end(); socket.end();
}); });
socket.on('close',function() { socket.on('close',function() {
node.log("connection closed from "+remoteDetails); node.log(RED._("tcpin.status.connection-closed",{host:socket.remoteAddress, port:socket.remotePort}));
connectedSockets.splice(connectedSockets.indexOf(socket),1); connectedSockets.splice(connectedSockets.indexOf(socket),1);
node.status({text:connectedSockets.length+" connections"}); node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
}); });
socket.on('error',function() { socket.on('error',function() {
node.log("socket error from "+remoteDetails); node.log(RED._("tcpin.errors.socket-error",{host:socket.remoteAddress, port:socket.remotePort}));
connectedSockets.splice(connectedSockets.indexOf(socket),1); connectedSockets.splice(connectedSockets.indexOf(socket),1);
node.status({text:connectedSockets.length+" connections"}); node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
}); });
}); });
@ -343,15 +343,15 @@ module.exports = function(RED) {
server.on('error', function(err) { server.on('error', function(err) {
if (err) { if (err) {
node.error('unable to listen on port '+node.port+' : '+err); node.error(RED._("tcpin.errors.cannot-listen",{port:node.port,error:err.toString()}));
} }
}); });
server.listen(node.port, function(err) { server.listen(node.port, function(err) {
if (err) { if (err) {
node.error('unable to listen on port '+node.port+' : '+err); node.error(RED._("tcpin.errors.cannot-listen",{port:node.port,error:err.toString()}));
} else { } else {
node.log('listening on port '+node.port); node.log(RED._("tcpin.status.listening-port",{port:node.port}));
node.on('close', function() { node.on('close', function() {
for (var c in connectedSockets) { for (var c in connectedSockets) {
if (connectedSockets.hasOwnProperty(c)) { if (connectedSockets.hasOwnProperty(c)) {
@ -360,7 +360,7 @@ module.exports = function(RED) {
} }
} }
server.close(); server.close();
node.log('stopped listening on port '+node.port); node.log(RED._("tcpin.status.stopped-listening",{port:node.port}));
}); });
} }
}); });
@ -401,18 +401,17 @@ module.exports = function(RED) {
if (host && port) { if (host && port) {
client.connect(port, host, function() { client.connect(port, host, function() {
//node.log('client connected'); //node.log(RED._("tcpin.errors.client-connected"));
node.status({fill:"green",shape:"dot",text:"connected"}); node.status({fill:"green",shape:"dot",text:"common.status.connected"});
node.connected = true; node.connected = true;
client.write(msg.payload); client.write(msg.payload);
}); });
} }
else { else {
node.warn("Host and/or port not set"); node.warn(RED._("tcpin.errors.no-host"));
} }
client.on('data', function(data) { client.on('data', function(data) {
//node.log("data:"+ data.length+":"+ data);
if (node.out == "sit") { // if we are staying connected just send the buffer if (node.out == "sit") { // if we are staying connected just send the buffer
node.send({"payload": data}); node.send({"payload": data});
} }
@ -477,18 +476,17 @@ module.exports = function(RED) {
}); });
client.on('error', function() { client.on('error', function() {
node.error('connect failed',msg); node.error(RED._("tcpin.errors.connect-fail"),msg);
node.status({fill:"red",shape:"ring",text:"error"}); node.status({fill:"red",shape:"ring",text:"common.status.error"});
if (client) { client.end(); } if (client) { client.end(); }
}); });
client.on('timeout',function() { client.on('timeout',function() {
node.warn('connect timeout'); node.warn(RED._("tcpin.errors.connect-timeout"));
if (client) { if (client) {
client.end(); client.end();
setTimeout(function() { setTimeout(function() {
client.connect(port, host, function() { client.connect(port, host, function() {
//node.log('client connected');
node.connected = true; node.connected = true;
client.write(msg.payload); client.write(msg.payload);
}); });

View File

@ -17,54 +17,41 @@
<!-- The Input Node --> <!-- The Input Node -->
<script type="text/x-red" data-template-name="udp in"> <script type="text/x-red" data-template-name="udp in">
<div class="form-row"> <div class="form-row">
<label for="node-input-port"><i class="fa fa-sign-in"></i> Listen for</label> <label for="node-input-port"><i class="fa fa-sign-in"></i> <span data-i18n="udp.label.listen"></span></label>
<select id="node-input-multicast" style='width:62%'> <select id="node-input-multicast" style='width:62%'>
<option value="false">udp messages</option> <option value="false" data-i18n="udp.udpmsgs"></option>
<option value="true">multicast messages</option> <option value="true" data-i18n="udp.mcmsgs"></option>
</select> </select>
</div> </div>
<div class="form-row node-input-group"> <div class="form-row node-input-group">
<label for="node-input-group"><i class="fa fa-list"></i> Group</label> <label for="node-input-group"><i class="fa fa-list"></i> <span data-i18n="udp.label.group"></span></label>
<input type="text" id="node-input-group" placeholder="225.0.18.83"> <input type="text" id="node-input-group" placeholder="225.0.18.83">
</div> </div>
<div class="form-row node-input-iface"> <div class="form-row node-input-iface">
<label for="node-input-iface"><i class="fa fa-random"></i> Interface</label> <label for="node-input-iface"><i class="fa fa-random"></i> <span data-i18n="udp.label.interface"></span></label>
<input type="text" id="node-input-iface" placeholder="(optional) ip address of eth0"> <input type="text" id="node-input-iface" data-i18n="[placeholder]udp.label.interface">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-port"><i class="fa fa-sign-in"></i> on Port</label> <label for="node-input-port"><i class="fa fa-sign-in"></i> <span data-i18n="udp.label.onport"></span></label>
<input type="text" id="node-input-port" placeholder="Port" style="width: 80px"> <input type="text" id="node-input-port" style="width: 80px">
&nbsp;&nbsp;using <select id="node-input-ipv" style="width:80px"> &nbsp;&nbsp;<span data-i18n="udp.label.using"></span> <select id="node-input-ipv" style="width:80px">
<option value="udp4">ipv4</option> <option value="udp4">ipv4</option>
<option value="udp6">ipv6</option> <option value="udp6">ipv6</option>
</select> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-datatype"><i class="fa fa-sign-out"></i> Output</label> <label for="node-input-datatype"><i class="fa fa-sign-out"></i> <span data-i18n="udp.label.output"></span></label>
<select id="node-input-datatype" style="width: 70%;"> <select id="node-input-datatype" style="width: 70%;">
<option value="buffer">a Buffer</option> <option value="buffer" data-i18n="udp.output.buffer"></option>
<option value="utf8">a String</option> <option value="utf8" data-i18n="udp.output.string"></option>
<option value="base64">a Base64 encoded string</option> <option value="base64" data-i18n="udp.output.base64"></option>
</select> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-tips">Tip: Make sure your firewall will allow the data in.</div> <div class="form-tips"><span data-i18n="udp.tip.in"></span></div>
<script>
$("#node-input-multicast").change(function() {
var id = $("#node-input-multicast option:selected").val();
if (id == "false") {
$(".node-input-group").hide();
$(".node-input-iface").hide();
}
else {
$(".node-input-group").show();
$(".node-input-iface").show();
}
});
</script>
</script> </script>
<script type="text/x-red" data-help-name="udp in"> <script type="text/x-red" data-help-name="udp in">
@ -97,6 +84,20 @@
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$("#node-input-multicast").change(function() {
var id = $("#node-input-multicast option:selected").val();
if (id == "false") {
$(".node-input-group").hide();
$(".node-input-iface").hide();
}
else {
$(".node-input-group").show();
$(".node-input-iface").show();
}
});
$("#node-input-multicast").change();
} }
}); });
</script> </script>
@ -105,62 +106,44 @@
<!-- The Output Node --> <!-- The Output Node -->
<script type="text/x-red" data-template-name="udp out"> <script type="text/x-red" data-template-name="udp out">
<div class="form-row"> <div class="form-row">
<label for="node-input-port"><i class="fa fa-envelope"></i> Send a</label> <label for="node-input-port"><i class="fa fa-envelope"></i> <span data-i18n="udp.label.send"></span></label>
<select id="node-input-multicast" style="width:40%"> <select id="node-input-multicast" style="width:40%">
<option value="false">udp message</option> <option value="false" data-i18n="udp.udpmsg"></option>
<option value="broad">broadcast message</option> <option value="broad" data-i18n="udp.bcmsg"></option>
<option value="multi">multicast message</option> <option value="multi" data-i18n="udp.mcmsg"></option>
</select> </select>
to port <input type="text" id="node-input-port" placeholder="port" style="width: 70px"> <span data-i18n="udp.label.toport"></span> <input type="text" id="node-input-port" style="width: 70px">
</div> </div>
<div class="form-row node-input-addr"> <div class="form-row node-input-addr">
<label for="node-input-addr" id="node-input-addr-label"><i class="fa fa-list"></i> Address</label> <label for="node-input-addr" id="node-input-addr-label"><i class="fa fa-list"></i> <span data-i18n="udp.label.address"></span></label>
<input type="text" id="node-input-addr" placeholder="destination ip" style="width: 50%;"> <input type="text" id="node-input-addr" data-i18n="[placeholder]udp.placeholder.address" style="width: 50%;">
<select id="node-input-ipv" style="width:70px"> <select id="node-input-ipv" style="width:70px">
<option value="udp4">ipv4</option> <option value="udp4">ipv4</option>
<option value="udp6">ipv6</option> <option value="udp6">ipv6</option>
</select> </select>
</div> </div>
<div class="form-row node-input-iface"> <div class="form-row node-input-iface">
<label for="node-input-iface"><i class="fa fa-random"></i> Interface</label> <label for="node-input-iface"><i class="fa fa-random"></i> <span data-i18n="udp.label.interface"></span></label>
<input type="text" id="node-input-iface" placeholder="(optional) ip address of eth0"> <input type="text" id="node-input-iface" data-i18n="[placeholder]udp.placeholder.interface">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-outport-type">&nbsp;</label> <label for="node-input-outport-type">&nbsp;</label>
<select id="node-input-outport-type"> <select id="node-input-outport-type">
<option id="node-input-outport-type-random" value="random">use random local port</option> <option id="node-input-outport-type-random" value="random" data-i18n="udp.bind.random"></option>
<option value="fixed">bind to local port</option> <option value="fixed" data-i18n="udp.bind.local"></option>
</select> </select>
<input type="text" id="node-input-outport" style="width: 70px;" placeholder="port"> <input type="text" id="node-input-outport" style="width: 70px;">
</div> </div>
<div class="form-row"> <div class="form-row">
<label>&nbsp;</label> <label>&nbsp;</label>
<input type="checkbox" id="node-input-base64" style="display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-input-base64" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-base64" style="width: 70%;">Decode Base64 encoded payload ?</label> <label for="node-input-base64" style="width: 70%;"><span data-i18n="udp.label.decode-base64"></span></label>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-tips">Tip: leave address and port blank if you want to set using <b>msg.ip</b> and <b>msg.port</b>.</div> <div class="form-tips"><span data-i18n="[html]udp.tip.out"></span></div>
<script>
$("#node-input-multicast").change(function() {
var id = $("#node-input-multicast option:selected").val();
if (id !== "multi") {
$(".node-input-iface").hide();
$("#node-input-addr-label").html('<i class="fa fa-list"></i> Address');
$("#node-input-addr")[0].placeholder = 'destination ip';
}
else {
$(".node-input-iface").show();
$("#node-input-addr-label").html('<i class="fa fa-list"></i> Group');
$("#node-input-addr")[0].placeholder = '225.0.18.83';
}
if (id === "broad") {
$("#node-input-addr")[0].placeholder = '255.255.255.255';
}
});
</script>
</script> </script>
<script type="text/x-red" data-help-name="udp out"> <script type="text/x-red" data-help-name="udp out">
@ -195,6 +178,12 @@
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
}, },
oneditprepare: function() { oneditprepare: function() {
var addresslabel = this._("udp.label.address");
var addressph = this._("udp.placeholder.address");
var grouplabel = this._("udp.label.group");
var bindrandom = this._("udp.bind.random");
var bindtarget = this._("udp.bind.target");
var type = this.outport==""?"random":"fixed"; var type = this.outport==""?"random":"fixed";
$("#node-input-outport-type option").filter(function() { $("#node-input-outport-type option").filter(function() {
return $(this).val() == type; return $(this).val() == type;
@ -208,15 +197,30 @@
$("#node-input-outport").show(); $("#node-input-outport").show();
} }
}); });
$("#node-input-outport-type").change(); $("#node-input-outport-type").change();
$("#node-input-multicast").change(function() { $("#node-input-multicast").change(function() {
var id = $("#node-input-multicast option:selected").val();
if (id === "multi") {
$(".node-input-iface").show();
$("#node-input-addr-label").html('<i class="fa fa-list"></i> ' + grouplabel);
$("#node-input-addr")[0].placeholder = '225.0.18.83';
}
else if (id === "broad") {
$(".node-input-iface").hide();
$("#node-input-addr-label").html('<i class="fa fa-list"></i> ' + addresslabel);
$("#node-input-addr")[0].placeholder = '255.255.255.255';
}
else {
$(".node-input-iface").hide();
$("#node-input-addr-label").html('<i class="fa fa-list"></i> ' + addresslabel);
$("#node-input-addr")[0].placeholder = addressph;
}
var type = $(this).children("option:selected").val(); var type = $(this).children("option:selected").val();
if (type == "false") { if (type == "false") {
$("#node-input-outport-type-random").html("bind to random local port"); $("#node-input-outport-type-random").html(bindrandom);
} else { } else {
$("#node-input-outport-type-random").html("bind to target port"); $("#node-input-outport-type-random").html(bindtarget);
} }
}); });
$("#node-input-multicast").change(); $("#node-input-multicast").change();

View File

@ -33,9 +33,9 @@ module.exports = function(RED) {
server.on("error", function (err) { server.on("error", function (err) {
if ((err.code == "EACCES") && (node.port < 1024)) { if ((err.code == "EACCES") && (node.port < 1024)) {
node.error("UDP access error, you may need root access for ports below 1024"); node.error(RED._("udp.errors.access-error"));
} else { } else {
node.error("UDP error : "+err.code); node.error(RED._("udp.errors.error",{error:err.code}));
} }
server.close(); server.close();
}); });
@ -54,20 +54,20 @@ module.exports = function(RED) {
server.on('listening', function () { server.on('listening', function () {
var address = server.address(); var address = server.address();
node.log('udp listener at ' + address.address + ":" + address.port); node.log(RED._("udp.status.listener-at",{host:address.address,port:address.port}));
if (node.multicast == "true") { if (node.multicast == "true") {
server.setBroadcast(true); server.setBroadcast(true);
try { try {
server.setMulticastTTL(128); server.setMulticastTTL(128);
server.addMembership(node.group,node.iface); server.addMembership(node.group,node.iface);
node.log("udp multicast group "+node.group); node.log(RED._("udp.status.mc-group",{group:node.group}));
} catch (e) { } catch (e) {
if (e.errno == "EINVAL") { if (e.errno == "EINVAL") {
node.error("Bad Multicast Address"); node.error(RED._("udp.errors.bad-mcaddress"));
} else if (e.errno == "ENODEV") { } else if (e.errno == "ENODEV") {
node.error("Must be ip address of the required interface"); node.error(RED._("udp.errors.interface"));
} else { } else {
node.error("Error :"+e.errno); node.error(RED._("udp.errors.error",{error:e.errno}));
} }
} }
} }
@ -76,7 +76,7 @@ module.exports = function(RED) {
node.on("close", function() { node.on("close", function() {
try { try {
server.close(); server.close();
node.log('udp listener stopped'); node.log(RED._("udp.status.listener-stopped"));
} catch (err) { } catch (err) {
node.error(err); node.error(err);
} }
@ -118,25 +118,25 @@ module.exports = function(RED) {
try { try {
sock.setMulticastTTL(128); sock.setMulticastTTL(128);
sock.addMembership(node.addr,node.iface); // Add to the multicast group sock.addMembership(node.addr,node.iface); // Add to the multicast group
node.log('udp multicast ready : '+node.outport+' -> '+node.addr+":"+node.port); node.log(RED._("udp.status.mc-ready",{outport:node.outport,host:node.addr,port:node.port}));
} catch (e) { } catch (e) {
if (e.errno == "EINVAL") { if (e.errno == "EINVAL") {
node.error("Bad Multicast Address"); node.error(RED._("udp.errors.bad-mcaddress"));
} else if (e.errno == "ENODEV") { } else if (e.errno == "ENODEV") {
node.error("Must be ip address of the required interface"); node.error(RED._("udp.errors.interface"));
} else { } else {
node.error("Error :"+e.errno); node.error(RED._("udp.errors.error",{error:e.errno}));
} }
} }
} else { } else {
node.log('udp broadcast ready : '+node.outport+' -> '+node.addr+":"+node.port); node.log(RED._("udp.status.bc-ready",{outport:node.outport,host:node.addr,port:node.port}));
} }
}); });
} else if (node.outport != "") { } else if (node.outport != "") {
sock.bind(node.outport); sock.bind(node.outport);
node.log('udp ready : '+node.outport+' -> '+node.addr+":"+node.port); node.log(RED._("udp.errors.ready",{outport:node.outport,host:node.addr,port:node.port}));
} else { } else {
node.log('udp ready : '+node.addr+":"+node.port); node.log(RED._("udp.errors.ready-nolocal",{host:node.addr,port:node.port}));
} }
node.on("input", function(msg) { node.on("input", function(msg) {
@ -144,11 +144,11 @@ module.exports = function(RED) {
var add = node.addr || msg.ip || ""; var add = node.addr || msg.ip || "";
var por = node.port || msg.port || 0; var por = node.port || msg.port || 0;
if (add == "") { if (add == "") {
node.warn("udp: ip address not set"); node.warn(RED._("udp.errors.ip-notset"));
} else if (por == 0) { } else if (por == 0) {
node.warn("udp: port not set"); node.warn(RED._("udp.errors.port-notset"));
} else if (isNaN(por) || (por < 1) || (por > 65535)) { } else if (isNaN(por) || (por < 1) || (por > 65535)) {
node.warn("udp: port number not valid"); node.warn(RED._("udp.errors.port-invalid"));
} else { } else {
var message; var message;
if (node.base64) { if (node.base64) {
@ -171,7 +171,7 @@ module.exports = function(RED) {
node.on("close", function() { node.on("close", function() {
try { try {
sock.close(); sock.close();
node.log('udp output stopped'); node.log(RED._("udp.status.output-stopped"));
} catch (err) { } catch (err) {
node.error(err); node.error(err);
} }

View File

@ -0,0 +1,608 @@
{
"common": {
"label": {
"payload": "Payload",
"topic": "Topic",
"name": "Name",
"username": "Username",
"password": "Password"
},
"status": {
"connected": "connected",
"not-connected": "not connected",
"disconnected": "disconnected",
"connecting": "connecting",
"error": "error",
"ok": "OK"
},
"notification": {
"error": "<strong>Error</strong>: __message__",
"errors": {
"not-deployed": "node not deployed",
"no-response": "no response from server",
"unexpected": "unexpected error (__status__) __message__"
}
},
"errors": {
"nooverride": "Warning: msg properties can no longer override set node properties. See bit.ly/nr-override-msg-props"
}
},
"inject": {
"inject": "inject",
"repeat": "repeat = __repeat__",
"crontab": "crontab = __crontab__",
"stopped": "stopped",
"failed": "Inject failed: __error__",
"label": {
"repeat": "Repeat"
},
"timestamp": "timestamp",
"string": "string",
"blank": "blank",
"none": "none",
"interval": "interval",
"interval-time": "interval between times",
"time": "at a specific time",
"seconds": "seconds",
"minutes": "minutes",
"hours": "hours",
"between": "between",
"at": "at",
"and": "and",
"every": "every",
"days": [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
],
"on": "on",
"onstart": "Inject once at start?",
"tip": "<b>Note:</b> \"interval between times\" and \"at a specific time\" will use cron.<br/>See info box for details.",
"success": "Successfully injected: __label__",
"errors": {
"failed": "inject failed, see log for details"
}
},
"catch": {
"catch": "catch"
},
"debug": {
"output": "Output",
"msgprop": "message property",
"msgobj": "complete msg object",
"to": "to",
"debtab": "debug tab",
"tabcon": "debug tab and console",
"notification": {
"activated": "Successfully activated: __label__",
"deactivated": "Successfully deactivated: __label__"
},
"sidebarTitle": "debug"
},
"exec": {
"spawnerr": "Spawn command must be just the command - no spaces or extra parameters",
"badstdout": "Bad STDOUT",
"label": {
"command": "Command",
"append": "Append"
},
"placeholder": {
"extraparams": "extra input parameters"
},
"spawn": "Use spawn() instead of exec()?",
"tip": "Tip: <i>spawn</i> expects only one command word - and appended args to be comma separated."
},
"function": {
"label": {
"function": "Function",
"outputs": "Outputs"
},
"tip": "See the Info tab for help writing functions."
},
"template": {
"label": {
"template": "Template",
"property": "Property"
},
"templatevalue": "This is the payload: {{payload}} !"
},
"delay": {
"action": "Action",
"for": "For",
"delaymsg": "Delay message",
"ramdomdelay": "Random delay",
"limitrate": "Limit rate to",
"fairqueue": "Topic based fair queue",
"milisecs": "Miliseconds",
"secs": "Seconds",
"sec": "Second",
"mins": "Minutes",
"min": "Minute",
"hours": "Hours",
"hour": "Hour",
"days": "Days",
"day": "Day",
"between": "Between",
"rate": "Rate",
"msgper": "msg(s) per",
"dropmsg": "drop intermediate messages",
"label": {
"delay": "delay",
"limitlabel": "limit",
"randomlabel": "ramdom",
"queuelabel": "queue"
},
"error": {
"buffer": "buffer exceeded 1000 messages"
}
},
"trigger": {
"send": "Send",
"then": "then",
"then-send": "then send",
"output": {
"string": "the string payload",
"existing": "the existing message",
"nothing": "nothing"
},
"wait-reset": "wait to be reset",
"wait-for": "wait for",
"duration": {
"ms": "Milliseconds",
"s": "Seconds",
"m": "Minutes",
"h": "Hours"
},
"extend": "extend delay if new message arrives",
"tip": "The node can be reset by sending a message with the <b>msg.reset</b> property set",
"label": {
"trigger": "trigger",
"trigger-block": "trigger & block"
}
},
"comment": {
"label": {
"title": "Title",
"body": "Body"
},
"tip": "Tip: The text can be styled as <a href=\"https://help.github.com/articles/markdown-basics/\" target=\"_new\">Github flavoured Markdown</a>",
"defaulttitle": "Comment node",
"defaultinfo": "Use this node to add simple documentation.\n\nAnything you add will be rendered in this info panel.\n\nYou may use Markdown syntax to **enhance** the *presentation*."
},
"unknown": {
"label": {
"unknown": "unknown"
},
"tip": "<p>This node is a type unknown to your installation of Node-RED.</p><p><i>If you deploy with the node in this state, it's configuration will be preserved, but the flow will not start until the missing type is installed.</i></p><p>See the Info side bar for more help</p>"
},
"mqtt": {
"label": {
"broker": "Broker",
"qos": "QoS",
"clientid": "Client ID",
"port": "Port"
},
"placeholder": {
"clientid": "Leave blank for auto generated"
},
"retain": "Retain",
"true": "true",
"false": "false",
"tip": "Tip: Leave topic, qos or retain blank if you want to set them via msg properties.",
"errors": {
"not-defined": "topic not defined",
"missing-config": "missing broker configuration",
"invalid-topic": "Invalid topic specified"
}
},
"httpin": {
"label": {
"method": "Method",
"url": "URL",
"doc": "Docs",
"return": "Return"
},
"setby": "- set by msg.method -",
"basicauth": "Use basic authentication?",
"utf8": "a UTF-8 string",
"binary": "a binary buffer",
"json": "a parsed JSON object",
"tip": {
"in": "The url will be relative to ",
"res": "The messages sent to this node <b>must</b> originate from an <i>http input</i> node",
"req": "Tip: If the JSON parse fails the fetched string is returned as-is."
},
"httpreq": "http request",
"errors": {
"not-created": "Cannot create http-in node when httpNodeRoot set to false",
"no-response": "No response object",
"json-error": "JSON parse error",
"no-url": "No url specified"
},
"status": {
"requesting": "requesting"
}
},
"websocket": {
"label": {
"type": "Type",
"path": "Path",
"url": "URL"
},
"listenon": "Listen on",
"connectto": "Connect to",
"payload": "Send/Receive payload",
"message": "Send/Receive entire message",
"tip": {
"path1": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The listener can be configured to send or receive the entire message object as a JSON formatted string.",
"path2": "This path will be relative to ",
"url1": "URL should use ws:&#47;&#47; or wss:&#47;&#47; scheme and point to an existing websocket listener.",
"url2": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The client can be configured to send or receive the entire message object as a JSON formatted string."
},
"errors": {
"connect-error": "An error occured on the ws connection: ",
"send-error": "An error occurred while sending: ",
"missing-conf": "Missing server configuration"
}
},
"watch": {
"label": {
"files": "File(s)"
},
"placeholder": {
"files": "Comma-separated list of files and/or directories"
},
"tip": "On Windows you must use double back-slashes \\\\ in any directory names."
},
"tcpin": {
"label": {
"type": "Type",
"output": "Output",
"port": "port",
"host": "at host",
"payload": "payload(s)",
"delimited": "delimited by",
"close-connection": "Close connection after each message is sent?",
"decode-base64": "Decode Base64 message?",
"server": "Server",
"return": "Return"
},
"type": {
"listen": "Listen on",
"connect": "Connect to",
"reply": "Reply to TCP"
},
"output": {
"stream": "stream of",
"single": "single",
"buffer": "Buffer",
"string": "String",
"base64": "Base64 String"
},
"return": {
"timeout": "after a fixed timeout of",
"character": "when character received is",
"number": "a fixed number of chars",
"never": "never - keep connection open"
},
"status": {
"connecting": "connecting to __host__:__port__",
"connected": "connected to __host__:__port__",
"listening-port": "listening on port __port__",
"stopped-listening": "stopped listening on port",
"connection-from": "connection from __host__:__port__",
"connection-closed": "connection closed from __host__:__port__",
"connections": "__count__ connection",
"connections_plural": "__count__ connections"
},
"errors": {
"connection-lost": "connection lost to __host__:__port__",
"timeout": "timeout closed socket port __port__",
"cannot-listen": "unable to listen on port __port__, error: __error__",
"error": "error: __error__",
"socket-error": "socket error from __host__:__port__",
"no-host": "Host and/or port not set",
"connect-timeout": "connect timeout",
"connect-fail": "connect failed"
}
},
"udp": {
"label": {
"listen": "Listen for",
"onport": "on Port",
"using": "using",
"output": "Output",
"group": "Group",
"interface": "Interface",
"send": "Send a",
"toport": "to port",
"address": "Address",
"decode-base64": "Decode Base64 encoded payload?"
},
"placeholder": {
"interface": "(optional) ip address of eth0",
"address": "destination ip"
},
"udpmsgs": "udp messages",
"mcmsgs": "multicast messages",
"udpmsg": "udp message",
"bcmsg": "broadcast message",
"mcmsg": "multicast message",
"output": {
"buffer": "a Buffer",
"string": "a String",
"base64": "a Base64 encoded string"
},
"bind": {
"random": "bind to random local port",
"local": "bind to local port",
"target": "bind to target port"
},
"tip": {
"in": "Tip: Make sure your firewall will allow the data in.",
"out": "Tip: leave address and port blank if you want to set using <b>msg.ip</b> and <b>msg.port</b>."
},
"status": {
"listener-at": "udp listener at __host__:__port__",
"mc-group": "udp multicast group __group__",
"listener-stopped": "udp listener stopped",
"output-stopped": "udp output stopped",
"mc-ready": "udp multicast ready: __outport__ -> __host__:__port__",
"bc-ready": "udp broadcast ready: __outport__ -> __host__:__port__",
"ready": "udp ready: __outport__ -> __host__:__port__",
"ready-nolocal": "udp ready: __host__:__port__"
},
"errors": {
"access-error": "UDP access error, you may need root access for ports below 1024",
"error": "error: __error_",
"bad-mcaddress": "Bad Multicast Address",
"interface": "Must be ip address of the required interface",
"ip-notset": "udp: ip address not set",
"port-notset": "udp: port not set",
"port-invalid": "udp: port number not valid"
}
},
"switch": {
"label": {
"property": "Property",
"rule": "rule"
},
"checkall": "checking all rules",
"stopfirst": "stopping after first match",
"rules": {
"btwn":"is between",
"cont":"contains",
"regex":"matches regex",
"true":"is true",
"false":"is false",
"null":"is null",
"nnull":"is not null",
"else":"otherwise"
}
},
"change": {
"label": {
"rules": "Rules",
"rule": "rule",
"set": "set __property__",
"change": "change __property__",
"delete": "delete __property__",
"changeCount": "change: __count__ rules",
"regex": "Use regular expressions"
},
"action": {
"set": "Set",
"change": "Change",
"delete": "Delete",
"to": "to",
"search": "Search for",
"replace": "Replace with"
},
"errors": {
"invalid-from": "Invalid 'from' property: __error__"
}
},
"range": {
"label": {
"action": "Action",
"inputrange": "Map the input range",
"resultrange": "to the result range",
"from": "from",
"to": "to",
"roundresult": "Round result to the nearest integer?"
},
"placeholder": {
"min": "e.g. 0",
"maxin": "e.g. 99",
"maxout": "e.g. 255"
},
"scale": {
"payload": "Scale msg.payload",
"limit": "Scale and limit to the target range",
"wrap": "Scale and wrap within the target range"
},
"tip": "Tip: This node ONLY works with numbers.",
"errors": {
"notnumber": "Not a number"
}
},
"csv": {
"label": {
"columns": "Columns",
"separator": "Separator",
"c2o": "CSV-to-Object options",
"o2c": "Object-to-CSV options",
"input": "Input",
"firstrow": "first row contains column names",
"output": "Output",
"includerow": "include column name row",
"newline": "Newline"
},
"placeholder": {
"columns": "comma-separated column names"
},
"separator": {
"comma": "comma",
"tab": "tab",
"space": "space",
"semicolon": "semicolon",
"colon": "colon",
"hashtag": "hashtag",
"other": "other..."
},
"output": {
"row": "a message per row",
"array": "a single message [array]"
},
"newline": {
"linux": "Linux (\\n)",
"mac": "Mac (\\r)",
"windows": "Windows (\\r\\n)"
},
"errors": {
"csv_js": "This node only handles csv strings or js objects."
}
},
"html": {
"label": {
"select": "Select",
"output": "Output"
},
"output": {
"html": "the html content of the elements",
"text": "only the text content of the elements"
},
"format": {
"single": "as a single message containing an array",
"multi": "as multiple messages, one for each element"
},
"tip": "Tip: The <b>Select</b> value is a <a href=\"https://github.com/fb55/CSSselect#user-content-supported-selectors\" target=\"_new\"><i><u>CSS Selector</u></i></a>, similar to a jQuery selector."
},
"json": {
"errors": {
"dropped-object": "Ignored non-object payload",
"dropped": "Ignored unsupported payload type"
}
},
"xml": {
"label": {
"represent": "Represent XML tag attributes as a property named",
"prefix": "Prefix to access character content",
"advanced": "Advanced options"
},
"tip": "There is no simple way to convert XML attributes to JSON so the approach taken here is to add a property, named $ by default, to the JSON structure.",
"errors": {
"xml_js": "This node only handles xml strings or js objects."
}
},
"rpi-gpio": {
"label": {
"gpiopin": "GPIO Pin",
"selectpin": "select pin",
"resistor": "Resistor?",
"readinitial": "Read initial state of pin on deploy/restart?",
"type": "Type",
"initpin": "Initialise pin state?",
"button": "Button",
"pimouse": "Pi Mouse",
"left": "Left",
"right": "Right",
"middle": "Middle"
},
"resistor": {
"none": "none",
"pullup": "pullup",
"pulldown": "pulldown"
},
"digout": "Digital output",
"pwmout": "PWM output",
"initpin0": "initial level of pin - low (0)",
"initpin1": "initial level of pin - high (1)",
"left": "left",
"right": "right",
"middle": "middle",
"any": "any",
"pinname": "Pin",
"alreadyuse": "already in use",
"alreadyset": "already set as",
"tip": {
"pin": "<b>Pins in Use</b>: ",
"in": "Tip: Only Digital Input is supported - input must be 0 or 1.",
"dig": "<b>Tip</b>: For digital output - input must be 0 or 1.",
"pwm": "<b>Tip</b>: For PWM output - input must be between 0 to 100."
},
"types": {
"digout": "digital output",
"input": "input",
"pullup": "input with pull up",
"pulldown": "input with pull down",
"pwmout": "PWM output"
},
"status": {
"stopped": "stopped",
"closed": "closed",
"not-running": "not running"
},
"errors": {
"ignorenode": "Ignoring Raspberry Pi specific node",
"version": "Version command failed",
"sawpitype": "Saw Pi Type",
"libnotfound": "Cannot find Pi RPi.GPIO python library",
"alreadyset": "GPIO pin __pin__ already set as type: __type__",
"invalidpin": "Invalid GPIO pin",
"invalidinput": "Invalid input",
"needtobeexecutable": "__command__ needs to be executable",
"mustbeexecutable": "nrgpio must to be executable",
"commandnotfound": "nrgpio command not found",
"commandnotexecutable": "nrgpio command not executable",
"error": "error: __error__",
"pythoncommandnotfound": "nrpgio python command not running"
}
},
"tail": {
"label": {
"filename": "Filename",
"splitlines": "Split lines on \\n?"
},
"errors": {
"windowsnotsupport": "Not currently supported on Windows."
}
},
"file": {
"label": {
"filename": "Filename",
"action": "Action",
"addnewline": "Add newline (\\n) to each payload?",
"outputas": "Output as",
"filelabel": "file",
"deletelabel": "delete __file__"
},
"action": {
"append": "append to file",
"overwrite": "overwrite file",
"delete": "delete file"
},
"output": {
"utf8": "a utf8 string",
"buffer": "a Buffer"
},
"status": {
"wrotefile": "wrote to file: __file__",
"deletedfile": "deleted file: __file__",
"appendedfile": "appended to file: __file__"
},
"errors": {
"nofilename": "No filename specified",
"invaliddelete": "Warning: Invalid delete. Please use specific delete option in config dialog.",
"deletefail": "failed to delete file: __error__",
"writefail": "failed to write to file: __error__",
"appendfail": "failed to append to file: __error__"
}
}
}

View File

@ -16,11 +16,11 @@
<script type="text/x-red" data-template-name="switch"> <script type="text/x-red" data-template-name="switch">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-row"> <div class="form-row">
If msg.<input type="text" id="node-input-property" style="width: 200px;"/> <span data-i18n="switch.label.property"></span> msg.<input type="text" id="node-input-property" style="width: 200px;"/>
</div> </div>
<div class="form-row node-input-rule-container-row" style="margin-bottom: 0px;"> <div class="form-row node-input-rule-container-row" style="margin-bottom: 0px;">
<div id="node-input-rule-container-div" style="box-sizing: border-box; border-radius: 5px; height: 310px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;"> <div id="node-input-rule-container-div" style="box-sizing: border-box; border-radius: 5px; height: 310px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
@ -28,12 +28,12 @@
</div> </div>
</div> </div>
<div class="form-row"> <div class="form-row">
<a href="#" class="btn btn-mini" id="node-input-add-rule" style="margin-top: 4px;"><i class="fa fa-plus"></i> rule</a> <a href="#" class="btn btn-mini" id="node-input-add-rule" style="margin-top: 4px;"><i class="fa fa-plus"></i> <span data-i18n="switch.label.rule"></span></a>
</div> </div>
<div class="form-row"> <div class="form-row">
<select id="node-input-checkall" style="width:100%; margin-right:5px;"> <select id="node-input-checkall" style="width:100%; margin-right:5px;">
<option value="true">checking all rules</option> <option value="true" data-i18n="switch.checkall"></option>
<option value="false">stopping after first match</option> <option value="false" data-i18n="switch.stopfirst"></option>
</select> </select>
</div> </div>
</script> </script>
@ -72,14 +72,14 @@
{v:"lte",t:"<="}, {v:"lte",t:"<="},
{v:"gt",t:">"}, {v:"gt",t:">"},
{v:"gte",t:">="}, {v:"gte",t:">="},
{v:"btwn",t:"is between"}, {v:"btwn",t:this._("switch.rules.btwn")},
{v:"cont",t:"contains"}, {v:"cont",t:this._("switch.rules.cont")},
{v:"regex",t:"matches regex"}, {v:"regex",t:this._("switch.rules.regex")},
{v:"true",t:"is true"}, {v:"true",t:this._("switch.rules.true")},
{v:"false",t:"is false"}, {v:"false",t:this._("switch.rules.false")},
{v:"null",t:"is null"}, {v:"null",t:this._("switch.rules.null")},
{v:"nnull",t:"is not null"}, {v:"nnull",t:this._("switch.rules.nnull")},
{v:"else",t:"otherwise"} {v:"else",t:this._("switch.rules.else")}
]; ];
function generateRule(i,rule) { function generateRule(i,rule) {

View File

@ -16,11 +16,11 @@
<script type="text/x-red" data-template-name="change"> <script type="text/x-red" data-template-name="change">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-row" style="margin-bottom:0;"> <div class="form-row" style="margin-bottom:0;">
<label><i class="fa fa-list"></i> Rules</label> <label><i class="fa fa-list"></i> <span data-i18n="change.label.rules"></span></label>
</div> </div>
<div class="form-row node-input-rule-container-row" style="margin-bottom: 0px;"> <div class="form-row node-input-rule-container-row" style="margin-bottom: 0px;">
<div id="node-input-rule-container-div" style="box-sizing: border-box; border-radius: 5px; height: 300px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;"> <div id="node-input-rule-container-div" style="box-sizing: border-box; border-radius: 5px; height: 300px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
@ -28,7 +28,7 @@
</div> </div>
</div> </div>
<div class="form-row"> <div class="form-row">
<a href="#" class="btn btn-mini" id="node-input-add-rule" style="margin-top: 4px;"><i class="fa fa-plus"></i> rule</a> <a href="#" class="btn btn-mini" id="node-input-add-rule" style="margin-top: 4px;"><i class="fa fa-plus"></i> <span data-i18n="change.label.rule"></span></a>
</div> </div>
</script> </script>
@ -68,23 +68,23 @@
} }
if (!this.rules) { if (!this.rules) {
if (this.action === "replace") { if (this.action === "replace") {
return "set msg."+this.property; return this._("change.label.set",{property:"msg."+this.property});
} else if (this.action === "change") { } else if (this.action === "change") {
return "change msg."+this.property; return this._("change.label.change",{property:"msg."+this.property});
} else { } else {
return this.action+" msg."+this.property return this._("change.label.delete",{property:"msg."+this.property});
} }
} else { } else {
if (this.rules.length == 1) { if (this.rules.length == 1) {
if (this.rules[0].t === "set") { if (this.rules[0].t === "set") {
return "set msg."+this.rules[0].p; return this._("change.label.set",{property:"msg."+this.rules[0].p});
} else if (this.rules[0].t === "change") { } else if (this.rules[0].t === "change") {
return "change msg."+this.rules[0].p; return this._("change.label.change",{property:"msg."+this.rules[0].p});
} else { } else {
return "delete msg."+this.rules[0].p; return this._("change.label.delete",{property:"msg."+this.rules[0].p});
} }
} else { } else {
return "change: "+(this.rules.length||"no")+" rules"; return this._("change.label.changeCount",{count:this.rules.length});
} }
} }
}, },
@ -92,6 +92,13 @@
return this.name ? "node_label_italic" : ""; return this.name ? "node_label_italic" : "";
}, },
oneditprepare: function() { oneditprepare: function() {
var set = this._("change.action.set");
var change = this._("change.action.change");
var del = this._("change.action.delete");
var to = this._("change.action.to");
var search = this._("change.action.search");
var replace = this._("change.action.replace");
var regex = this._("change.label.regex");
if (this.reg === null) { $("#node-input-reg").prop('checked', true); } if (this.reg === null) { $("#node-input-reg").prop('checked', true); }
$("#node-input-action").change(); $("#node-input-action").change();
@ -104,7 +111,7 @@
var row3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(container); var row3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(container);
var selectField = $('<select/>',{class:"node-input-rule-type",style:"width: 100px"}).appendTo(row1); var selectField = $('<select/>',{class:"node-input-rule-type",style:"width: 100px"}).appendTo(row1);
var selectOptions = [{v:"set",l:"Set"},{v:"change",l:"Change"},{v:"delete",l:"Delete"}]; var selectOptions = [{v:"set",l:set},{v:"change",l:change},{v:"delete",l:del}];
for (var i=0;i<3;i++) { for (var i=0;i<3;i++) {
selectField.append($("<option></option>").val(selectOptions[i].v).text(selectOptions[i].l)); selectField.append($("<option></option>").val(selectOptions[i].v).text(selectOptions[i].l));
} }
@ -118,21 +125,21 @@
$('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton); $('<i/>',{class:"fa fa-remove"}).appendTo(deleteButton);
$('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text("to").appendTo(row2); $('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text(to).appendTo(row2);
var propertyValue = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-value",type:"text"}).appendTo(row2); var propertyValue = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-value",type:"text"}).appendTo(row2);
var row3_1 = $('<div/>').appendTo(row3); var row3_1 = $('<div/>').appendTo(row3);
$('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text("Search for").appendTo(row3_1); $('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text(search).appendTo(row3_1);
var fromValue = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-search-value",type:"text"}).appendTo(row3_1); var fromValue = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-search-value",type:"text"}).appendTo(row3_1);
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:150px;padding-right: 10px; box-sizing: border-box;"}).text("replace with").appendTo(row3_2); $('<div/>',{style:"display: inline-block;text-align:right; width:150px;padding-right: 10px; box-sizing: border-box;"}).text(replace).appendTo(row3_2);
var toValue = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-replace-value",type:"text"}).appendTo(row3_2); var toValue = $('<input/>',{style:"width: 220px",class:"node-input-rule-property-replace-value",type:"text"}).appendTo(row3_2);
var row3_3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(row3); var row3_3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(row3);
var id = "node-input-rule-property-regex-"+Math.floor(Math.random()*10000); var id = "node-input-rule-property-regex-"+Math.floor(Math.random()*10000);
var useRegExp = $('<input/>',{id:id,class:"node-input-rule-property-re",type:"checkbox", style:"margin-left: 150px; margin-right: 10px; display: inline-block; width: auto; vertical-align: top;"}).appendTo(row3_3); var useRegExp = $('<input/>',{id:id,class:"node-input-rule-property-re",type:"checkbox", style:"margin-left: 150px; margin-right: 10px; display: inline-block; width: auto; vertical-align: top;"}).appendTo(row3_3);
$('<label/>',{for:id,style:"width: auto;"}).text("Use regular expressions").appendTo(row3_3); $('<label/>',{for:id,style:"width: auto;"}).text(regex).appendTo(row3_3);
selectField.change(function() { selectField.change(function() {
@ -147,7 +154,6 @@
} else if (type == "delete") { } else if (type == "delete") {
row2.hide(); row2.hide();
row3.hide(); row3.hide();
$("#node-tip").hide();
} }
}); });
deleteButton.click(function() { deleteButton.click(function() {

View File

@ -52,7 +52,7 @@ module.exports = function(RED) {
rule.from = new RegExp(rule.from, "g"); rule.from = new RegExp(rule.from, "g");
} catch (e) { } catch (e) {
valid = false; valid = false;
this.error("Invalid 'from' property: "+e.message); this.error(RED._("change.errors.invalid-from",{error:e.message}));
} }
} }
} }

View File

@ -16,35 +16,35 @@
<script type="text/x-red" data-template-name="range"> <script type="text/x-red" data-template-name="range">
<div class="form-row"> <div class="form-row">
<label for="node-input-action"><i class="fa fa-dot-circle-o"></i> Action</label> <label for="node-input-action"><i class="fa fa-dot-circle-o"></i> <span data-i18n="range.label.action"></span></label>
<select id="node-input-action" style="width:70%; margin-right:5px;"> <select id="node-input-action" style="width:70%; margin-right:5px;">
<option value="scale">Scale msg.payload</option> <option value="scale" data-i18n="range.scale.payload"></option>
<option value="clamp">Scale and limit to the target range</option> <option value="clamp" data-i18n="range.scale.limit"></option>
<option value="roll">Scale and wrap within the target range</option> <option value="roll" data-i18n="range.scale.wrap"></option>
</select> </select>
</div> </div>
<br/> <br/>
<div class="form-row"><i class="fa fa-sign-in"></i> Map the input range:</div> <div class="form-row"><i class="fa fa-sign-in"></i> <span data-i18n="range.label.inputrange"></span>:</div>
<div class="form-row"><label></label> <div class="form-row"><label></label>
from: <input type="text" id="node-input-minin" placeholder="e.g. 0" style="width:100px;"/> <span data-i18n="range.label.from"></span>: <input type="text" id="node-input-minin" data-i18n="[placeholder]range.placeholder.min" style="width:100px;"/>
&nbsp;&nbsp;to: <input type="text" id="node-input-maxin" placeholder="e.g. 99" style="width:100px;"/> &nbsp;&nbsp;<span data-i18n="range.label.to"></span>: <input type="text" id="node-input-maxin" data-i18n="[placeholder]range.placeholder.maxin" style="width:100px;"/>
</div> </div>
<div class="form-row"><i class="fa fa-sign-out"></i> to the result range:</div> <div class="form-row"><i class="fa fa-sign-out"></i> <span data-i18n="range.label.resultrange"></span>:</div>
<div class="form-row"><label></label> <div class="form-row"><label></label>
from: <input type="text" id="node-input-minout" placeholder="e.g. 0" style="width:100px;"/> <span data-i18n="range.label.from"></span>: <input type="text" id="node-input-minout" data-i18n="[placeholder]range.placeholder.min" style="width:100px;"/>
&nbsp;&nbsp;to: <input type="text" id="node-input-maxout" placeholder="e.g. 255" style="width:100px;"/> &nbsp;&nbsp;<span data-i18n="range.label.to"></span>: <input type="text" id="node-input-maxout" data-i18n="[placeholder]range.placeholder.maxout" style="width:100px;"/>
</div> </div>
<br/> <br/>
<div class="form-row"><label></label> <div class="form-row"><label></label>
<input type="checkbox" id="node-input-round" style="display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-input-round" style="display: inline-block; width: auto; vertical-align: top;">
<label style="width: auto;" for="node-input-round">Round result to the nearest integer?</label></input> <label style="width: auto;" for="node-input-round"><span data-i18n="range.label.roundresult"></span></label></input>
</div> </div>
<br/> <br/>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-tips" id="node-tip">Tip: This node ONLY works with numbers.</div> <div class="form-tips" id="node-tip"><span data-i18n="range.tip"></span></div>
</script> </script>
<script type="text/x-red" data-help-name="range"> <script type="text/x-red" data-help-name="range">

View File

@ -42,7 +42,7 @@ module.exports = function(RED) {
if (node.round) { msg.payload = Math.round(msg.payload); } if (node.round) { msg.payload = Math.round(msg.payload); }
node.send(msg); node.send(msg);
} }
else { node.log("Not a number: "+msg.payload); } else { node.log(RED._("range.errors.notnumber")+": "+msg.payload); }
} }
else { node.send(msg); } // If no payload - just pass it on. else { node.send(msg); } // If no payload - just pass it on.
}); });

View File

@ -17,52 +17,52 @@
<script type="text/x-red" data-template-name="csv"> <script type="text/x-red" data-template-name="csv">
<div class="form-row"> <div class="form-row">
<label for="node-input-temp"><i class="fa fa-list"></i> Columns</label> <label for="node-input-temp"><i class="fa fa-list"></i> <span data-i18n="csv.label.columns"></span></label>
<input type="text" id="node-input-temp" placeholder="comma-separated column names"> <input type="text" id="node-input-temp" data-i18n="[placeholder]csv.placeholder.columns">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-select-sep"><i class="fa fa-text-width"></i> Separator</label> <label for="node-input-select-sep"><i class="fa fa-text-width"></i> <span data-i18n="csv.label.separator"></span></label>
<select style="width: 150px" id="node-input-select-sep"> <select style="width: 150px" id="node-input-select-sep">
<option value=",">comma</option> <option value="," data-i18n="csv.separator.comma"></option>
<option value="\t">tab</option> <option value="\t" data-i18n="csv.separator.tab"></option>
<option value=" ">space</option> <option value=" " data-i18n="csv.separator.space"></option>
<option value=";">semicolon</option> <option value=";" data-i18n="csv.separator.semicolon"></option>
<option value=":">colon</option> <option value=":" data-i18n="csv.separator.colon"></option>
<option value="#">hashtag</option> <option value="#" data-i18n="csv.separator.hashtag"></option>
<option value="">other...</option> <option value="" data-i18n="csv.separator.other"></option>
</select> </select>
<input style="width: 40px;" type="text" id="node-input-sep" pattern="."> <input style="width: 40px;" type="text" id="node-input-sep" pattern=".">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<hr align="middle"/> <hr align="middle"/>
<div class="form-row"> <div class="form-row">
<label style="width: 100%;"><i class="fa fa-gears"></i> CSV-to-Object options</label> <label style="width: 100%;"><i class="fa fa-gears"></i> <span data-i18n="csv.label.c2o"></span></label>
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-in"></i> Input</label> <label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-in"></i> <span data-i18n="csv.label.input"></span></label>
<input style="width: 30px" type="checkbox" id="node-input-hdrin"><label style="width: auto;" for="node-input-hdrin">first row contains column names</span> <input style="width: 30px" type="checkbox" id="node-input-hdrin"><label style="width: auto;" for="node-input-hdrin"><span data-i18n="csv.label.firstrow"></span></span>
</div> </div>
<div class="form-row"> <div class="form-row">
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-out"></i> Output</label> <label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-out"></i> <span data-i18n="csv.label.output"></span></label>
<select type="text" id="node-input-multi" style="width: 250px;"> <select type="text" id="node-input-multi" style="width: 250px;">
<option value="one">a message per row</option> <option value="one" data-i18n="csv.output.row"></option>
<option value="mult">a single message [array]</option> <option value="mult" data-i18n="csv.output.array"></option>
</select> </select>
</div> </div>
<hr align="middle"/> <hr align="middle"/>
<div class="form-row"> <div class="form-row">
<label style="width: 100%;"><i class="fa fa-gears"></i> Object-to-CSV options</label> <label style="width: 100%;"><i class="fa fa-gears"></i> <span data-i18n="csv.label.o2c"></span></label>
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-in"></i> Output</label> <label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-in"></i> <span data-i18n="csv.label.output"></span></label>
<input style="width: 30px" type="checkbox" id="node-input-hdrout"><label style="width: auto;" for="node-input-hdrout">include column name row</span> <input style="width: 30px" type="checkbox" id="node-input-hdrout"><label style="width: auto;" for="node-input-hdrout"><span data-i18n="csv.label.includerow"></span></span>
</div> </div>
<div class="form-row"> <div class="form-row">
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-align-left"></i> Newline</label> <label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-align-left"></i> <span data-i18n="csv.label.newline"></span></label>
<select style="width: 150px" id="node-input-ret"> <select style="width: 150px" id="node-input-ret">
<option value='\n'>Linux (\n)</option> <option value='\n' data-i18n="csv.newline.linux"></option>
<option value='\r'>Mac (\r)</option> <option value='\r' data-i18n="csv.newline.mac"></option>
<option value='\r\n'>Windows (\r\n)</option> <option value='\r\n' data-i18n="csv.newline.windows"></option>
</select> </select>
</div> </div>
</script> </script>

View File

@ -163,7 +163,7 @@ module.exports = function(RED) {
} }
catch(e) { node.error(e,msg); } catch(e) { node.error(e,msg); }
} }
else { node.warn("This node only handles csv strings or js objects."); } else { node.warn(RED._("csv.errors.csv_js")); }
} }
else { node.send(msg); } // If no payload - just pass it on. else { node.send(msg); } // If no payload - just pass it on.
}); });

View File

@ -16,31 +16,31 @@
<script type="text/x-red" data-template-name="html"> <script type="text/x-red" data-template-name="html">
<div class="form-row"> <div class="form-row">
<label for="node-input-tag"><i class="fa fa-filter"></i> Select</label> <label for="node-input-tag"><i class="fa fa-filter"></i> <span data-i18n="html.label.select"></span></label>
<input type="text" id="node-input-tag" placeholder="h1"> <input type="text" id="node-input-tag" style="width:73% !important" placeholder="h1">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-ret"><i class="fa fa-sign-out"></i> Output</label> <label for="node-input-ret"><i class="fa fa-sign-out"></i> <span data-i18n="html.label.output"></span></label>
<select id="node-input-ret" style="width:73% !important"> <select id="node-input-ret" style="width:76% !important">
<option value="html">the html content of the elements</option> <option value="html" data-i18n="html.output.html"></option>
<option value="text">only the text content of the elements</option> <option value="text"data-i18n="html.output.text"></option>
<!-- <option value="attr">an object of any attributes</option> --> <!-- <option value="attr">an object of any attributes</option> -->
<!-- <option value="val">return the value from a form element</option> --> <!-- <option value="val">return the value from a form element</option> -->
</select> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-as">&nbsp;</label> <label for="node-input-as">&nbsp;</label>
<select id="node-input-as" style="width:73% !important"> <select id="node-input-as" style="width:76% !important">
<option value="single">as a single message containing an array</option> <option value="single" data-i18n="html.format.single"></option>
<option value="multi">as multiple messages, one for each element</option> <option value="multi" data-i18n="html.format.multi"></option>
</select> </select>
</div> </div>
<br/> <br/>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" style="width:73% !important" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-tips">Tip: The <b>Select</b> value is a <a href="https://github.com/fb55/CSSselect#user-content-supported-selectors" target="_new"><i><u>CSS Selector</u></i></a>, similar to a jQuery selector.</div> <div class="form-tips"><span data-i18n="[html]html.tip"></span></div>
</script> </script>
<script type="text/x-red" data-help-name="html"> <script type="text/x-red" data-help-name="html">

View File

@ -16,8 +16,8 @@
<script type="text/x-red" data-template-name="json"> <script type="text/x-red" data-template-name="json">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
</script> </script>

View File

@ -35,9 +35,9 @@ module.exports = function(RED) {
msg.payload = JSON.stringify(msg.payload); msg.payload = JSON.stringify(msg.payload);
node.send(msg); node.send(msg);
} }
else { node.warn("Dropped: "+msg.payload); } else { node.warn(RED._("json.errors.dropped-object")); }
} }
else { node.warn("Dropped: "+msg.payload); } else { node.warn(RED._("json.errors.dropped")); }
} }
else { node.send(msg); } // If no payload - just pass it on. else { node.send(msg); } // If no payload - just pass it on.
}); });

View File

@ -16,37 +16,20 @@
<script type="text/x-red" data-template-name="xml"> <script type="text/x-red" data-template-name="xml">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name" style="width:280px !important"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name" style="width:280px !important">
</div> </div>
<div class="form-row" id="advanced"> <div class="form-row" id="advanced">
</div> </div>
<div id="advanced-options"> <div id="advanced-options">
<div class="form-row"> <div class="form-row">
<i class="fa fa-key"></i> Represent XML tag attributes as a property named <input type="text" id="node-input-attr" style="width:20px !important"> <i class="fa fa-key"></i> <span data-i18n="xml.label.represent"></span> <input type="text" id="node-input-attr" style="width:20px !important">
</div> </div>
<div class="form-row"> <div class="form-row">
<i class="fa fa-key"></i> Prefix to access character content <input type="text" id="node-input-chr" style="width:20px !important"> <i class="fa fa-key"></i> <span data-i18n="xml.label.prefix"></span> <input type="text" id="node-input-chr" style="width:20px !important">
</div> </div>
<div class="form-tips">There is no simple way to convert XML attributes to JSON <div class="form-tips"><span data-i18n="xml.tip"></span></div>
so the approach taken here is to add a property, named $ by default, to the JSON structure.</div>
</div> </div>
<script> {
var showadvanced = showadvanced || true;
var showall = function() {
showadvanced = !showadvanced;
if (showadvanced) {
$("#advanced-options").show();
$("#advanced").html('<label for="node-advanced" style="width:200px !important"><i class="fa fa-minus-square"></i> Advanced options</label>');
}
else {
$("#advanced-options").hide();
$("#advanced").html('<label for="node-advanced" style="width:200px !important"><i class="fa fa-plus-square"></i> Advanced options ...</label>');
}
};
showall();
$("#advanced").click( function() { showall(); });
} </script>
</script> </script>
<script type="text/x-red" data-help-name="xml"> <script type="text/x-red" data-help-name="xml">
@ -73,6 +56,23 @@
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var showadvanced = showadvanced || true;
var advanced = this._("xml.label.advanced");
var showall = function() {
showadvanced = !showadvanced;
if (showadvanced) {
$("#advanced-options").show();
$("#advanced").html('<label for="node-advanced" style="width:200px !important"><i class="fa fa-minus-square"></i> '+advanced+'</label>');
}
else {
$("#advanced-options").hide();
$("#advanced").html('<label for="node-advanced" style="width:200px !important"><i class="fa fa-plus-square"></i> '+advanced+' ...</label>');
}
};
showall();
$("#advanced").click( function() { showall(); });
} }
}); });
</script> </script>

View File

@ -40,7 +40,7 @@ module.exports = function(RED) {
} }
}); });
} }
else { node.warn("This node only handles xml strings or js objects."); } else { node.warn(RED._("xml.errors.xml_js")); }
} }
else { node.send(msg); } // If no payload - just pass it on. else { node.send(msg); } // If no payload - just pass it on.
}); });

View File

@ -16,17 +16,17 @@
<script type="text/x-red" data-template-name="tail"> <script type="text/x-red" data-template-name="tail">
<div class="form-row node-input-filename"> <div class="form-row node-input-filename">
<label for="node-input-filename"><i class="fa fa-file"></i> Filename</label> <label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="tail.label.filename"></span></label>
<input type="text" id="node-input-filename" placeholder="Filename"> <input type="text" id="node-input-filename">
</div> </div>
<div class="form-row"> <div class="form-row">
<label>&nbsp;</label> <label>&nbsp;</label>
<input type="checkbox" id="node-input-split" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-input-split" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-split" style="width: 70%;">Split lines if we see \n ?</label> <label for="node-input-split" style="width: 70%;"><span data-i18n="tail.label.splitlines"></span></label>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<!-- <div class="form-tips">WON'T work on Windows.</div> --> <!-- <div class="form-tips">WON'T work on Windows.</div> -->
</script> </script>

View File

@ -20,7 +20,7 @@ module.exports = function(RED) {
var plat = require('os').platform(); var plat = require('os').platform();
if (plat.match(/^win/)) { if (plat.match(/^win/)) {
throw "Info : Currently not supported on Windows."; throw RED._("tail.errors.windowsnotsupport");
} }
function TailNode(n) { function TailNode(n) {

View File

@ -16,25 +16,25 @@
<script type="text/x-red" data-template-name="file"> <script type="text/x-red" data-template-name="file">
<div class="form-row node-input-filename"> <div class="form-row node-input-filename">
<label for="node-input-filename"><i class="fa fa-file"></i> Filename</label> <label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="file.label.filename"></span></label>
<input type="text" id="node-input-filename" placeholder="Filename"> <input type="text" id="node-input-filename">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-overwriteFile"><i class="fa fa-random"></i> Action</label> <label for="node-input-overwriteFile"><i class="fa fa-random"></i> <span data-i18n="file.label.action"></span></label>
<select type="text" id="node-input-overwriteFile" style="display: inline-block; width: 250px; vertical-align: top;"> <select type="text" id="node-input-overwriteFile" style="display: inline-block; width: 250px; vertical-align: top;">
<option value="false">append to file</option> <option value="false" data-i18n="file.action.append"></option>
<option value="true">overwrite file</option> <option value="true" data-i18n="file.action.overwrite"></option>
<option value="delete">delete file</option> <option value="delete" data-i18n="file.action.delete"></option>
</select> </select>
</div> </div>
<div class="form-row" id="node-appline"> <div class="form-row" id="node-appline">
<label>&nbsp;</label> <label>&nbsp;</label>
<input type="checkbox" id="node-input-appendNewline" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-input-appendNewline" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-appendNewline" style="width: 70%;">Add newline (\n) to each payload ?</label> <label for="node-input-appendNewline" style="width: 70%;"><span data-i18n="file.label.addnewline"></span></label>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
</script> </script>
@ -48,19 +48,19 @@
<script type="text/x-red" data-template-name="file in"> <script type="text/x-red" data-template-name="file in">
<div class="form-row"> <div class="form-row">
<label for="node-input-filename"><i class="fa fa-file"></i> Filename</label> <label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="file.label.filename"></span></label>
<input type="text" id="node-input-filename" placeholder="Filename"> <input type="text" id="node-input-filename" data-i18n="[placeholder]file.label.filename">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-format"><i class="fa fa-sign-out"></i> Output as</label> <label for="node-input-format"><i class="fa fa-sign-out"></i> <span data-i18n="file.label.outputas"></span></label>
<select id="node-input-format"> <select id="node-input-format">
<option value="utf8">a utf8 string</option> <option value="utf8" data-i18n="file.output.utf8"></option>
<option value="">a Buffer</option> <option value="" data-i18n="file.output.buffer"></option>
</select> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" placeholder="Name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
</script> </script>
@ -84,8 +84,11 @@
icon: "file.png", icon: "file.png",
align: "right", align: "right",
label: function() { label: function() {
if (this.overwriteFile === "delete") { return this.name||"delete "+this.filename; } if (this.overwriteFile === "delete") {
else { return this.name||this.filename||"file"; } return this.name||this._("file.label.deletelabel",{file:this.filename})
} else {
return this.name||this.filename||this._("file.label.filelabel");
}
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";
@ -110,7 +113,7 @@
outputs:1, outputs:1,
icon: "file.png", icon: "file.png",
label: function() { label: function() {
return this.name||this.filename||"file"; return this.name||this.filename||this._("file.label.filelabel");
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name?"node_label_italic":"";

View File

@ -27,18 +27,15 @@ module.exports = function(RED) {
this.on("input",function(msg) { this.on("input",function(msg) {
var filename = this.filename || msg.filename || ""; var filename = this.filename || msg.filename || "";
if (msg.filename && n.filename && (n.filename !== msg.filename)) { if (msg.filename && n.filename && (n.filename !== msg.filename)) {
node.warn("Warning: msg properties can no longer override set node properties. See bit.ly/nr-override-msg-props"); node.warn(RED._("common.errors.nooverride"));
} }
if (!this.filename) { if (!this.filename) {
node.status({fill:"grey",shape:"dot",text:filename}); node.status({fill:"grey",shape:"dot",text:filename});
} }
if (filename === "") { if (filename === "") {
node.warn('No filename specified'); node.warn(RED._("file.errors.nofilename"));
} else if (msg.hasOwnProperty('delete')) { // remove warning at some point in future } else if (msg.hasOwnProperty('delete')) { // remove warning at some point in future
node.warn("Warning: Invalid delete. Please use specific delete option in config dialog."); node.warn(RED._("file.errors.invaliddelete"));
//fs.unlink(filename, function (err) {
//if (err) { node.error('failed to delete file : '+err,msg); }
//});
} else if (msg.payload && (typeof msg.payload != "undefined")) { } else if (msg.payload && (typeof msg.payload != "undefined")) {
var data = msg.payload; var data = msg.payload;
if ((typeof data === "object")&&(!Buffer.isBuffer(data))) { if ((typeof data === "object")&&(!Buffer.isBuffer(data))) {
@ -50,22 +47,22 @@ module.exports = function(RED) {
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while // using "binary" not {encoding:"binary"} to be 0.8 compatible for a while
//fs.writeFile(filename, data, {encoding:"binary"}, function (err) { //fs.writeFile(filename, data, {encoding:"binary"}, function (err) {
fs.writeFile(filename, data, "binary", function (err) { fs.writeFile(filename, data, "binary", function (err) {
if (err) { node.error('failed to write to file : '+err,msg); } if (err) { node.error(RED._("file.errors.writefail",{error:err.toString()}),msg); }
else if (RED.settings.verbose) { node.log('wrote to file: '+filename); } else if (RED.settings.verbose) { node.log(RED._("file.status.wrotefile",{file:filename})); }
}); });
} }
else if (this.overwriteFile === "delete") { else if (this.overwriteFile === "delete") {
fs.unlink(filename, function (err) { fs.unlink(filename, function (err) {
if (err) { node.error('failed to delete file : '+err,msg); } if (err) { node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg); }
else if (RED.settings.verbose) { node.log("deleted file: "+filename); } else if (RED.settings.verbose) { node.log(RED._("file.status.deletedfile",{file:filename})); }
}); });
} }
else { else {
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while longer // using "binary" not {encoding:"binary"} to be 0.8 compatible for a while longer
//fs.appendFile(filename, data, {encoding:"binary"}, function (err) { //fs.appendFile(filename, data, {encoding:"binary"}, function (err) {
fs.appendFile(filename, data, "binary", function (err) { fs.appendFile(filename, data, "binary", function (err) {
if (err) { node.error('failed to append to file : '+err,msg); } if (err) { node.error(RED._("file.errors.appendfail",{error:err.toString()}),msg); }
else if (RED.settings.verbose) { node.log('appended to file: '+filename); } else if (RED.settings.verbose) { node.log(RED._("file.status.appendedfile",{file:filename})); }
}); });
} }
} }
@ -87,13 +84,13 @@ module.exports = function(RED) {
this.on("input",function(msg) { this.on("input",function(msg) {
var filename = this.filename || msg.filename || ""; var filename = this.filename || msg.filename || "";
if (msg.filename && n.filename && (n.filename !== msg.filename)) { if (msg.filename && n.filename && (n.filename !== msg.filename)) {
node.warn("Warning: msg properties can no longer override set node properties. See bit.ly/nr-override-msg-props"); node.warn(RED._("file.errors.nooverride"));
} }
if (!this.filename) { if (!this.filename) {
node.status({fill:"grey",shape:"dot",text:filename}); node.status({fill:"grey",shape:"dot",text:filename});
} }
if (filename === "") { if (filename === "") {
node.warn('No filename specified'); node.warn(RED._("file.errors.nofilename"));
} else { } else {
msg.filename = filename; msg.filename = filename;
fs.readFile(filename,options,function(err,data) { fs.readFile(filename,options,function(err,data) {

View File

@ -49,6 +49,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",
"oauth2orize":"1.0.1", "oauth2orize":"1.0.1",
"i18next":"1.7.10",
"node-red-node-feedparser":"0.0.*", "node-red-node-feedparser":"0.0.*",
"node-red-node-email":"0.0.*", "node-red-node-email":"0.0.*",
"node-red-node-twitter":"0.0.*" "node-red-node-twitter":"0.0.*"
@ -69,6 +70,7 @@
"grunt-contrib-jshint": "0.11.2", "grunt-contrib-jshint": "0.11.2",
"grunt-contrib-uglify": "0.9.1", "grunt-contrib-uglify": "0.9.1",
"grunt-contrib-watch":"0.6.1", "grunt-contrib-watch":"0.6.1",
"grunt-jsonlint":"1.0.4",
"grunt-nodemon":"0.4.0", "grunt-nodemon":"0.4.0",
"grunt-sass":"1.0.0", "grunt-sass":"1.0.0",
"grunt-simple-mocha": "0.4.0", "grunt-simple-mocha": "0.4.0",

17
red.js
View File

@ -23,6 +23,7 @@ var nopt = require("nopt");
var path = require("path"); var path = require("path");
var fs = require("fs"); var fs = require("fs");
var RED = require("./red/red.js"); var RED = require("./red/red.js");
var log = require("./red/log");
var server; var server;
var app = express(); var app = express();
@ -166,7 +167,7 @@ try {
} }
if (settings.httpAdminRoot !== false && settings.httpAdminAuth) { if (settings.httpAdminRoot !== false && settings.httpAdminAuth) {
RED.log.warn("use of httpAdminAuth is deprecated. Use adminAuth instead"); RED.log.warn(log._("server.httpadminauth-deprecated"));
app.use(settings.httpAdminRoot, app.use(settings.httpAdminRoot,
express.basicAuth(function(user, pass) { express.basicAuth(function(user, pass) {
return user === settings.httpAdminAuth.user && crypto.createHash('md5').update(pass,'utf8').digest('hex') === settings.httpAdminAuth.pass; return user === settings.httpAdminAuth.user && crypto.createHash('md5').update(pass,'utf8').digest('hex') === settings.httpAdminAuth.pass;
@ -216,10 +217,10 @@ RED.start().then(function() {
if (settings.httpAdminRoot !== false || settings.httpNodeRoot !== false || settings.httpStatic) { if (settings.httpAdminRoot !== false || settings.httpNodeRoot !== false || settings.httpStatic) {
server.on('error', function(err) { server.on('error', function(err) {
if (err.errno === "EADDRINUSE") { if (err.errno === "EADDRINUSE") {
RED.log.error('Unable to listen on '+getListenPath()); RED.log.error(log._("server.unable-to-listen", {listenpath:getListenPath()}));
RED.log.error('Error: port in use'); RED.log.error(log._("server.port-in-use"));
} else { } else {
RED.log.error('Uncaught Exception:'); RED.log.error(log._("server.uncaught-exception"));
if (err.stack) { if (err.stack) {
RED.log.error(err.stack); RED.log.error(err.stack);
} else { } else {
@ -230,16 +231,16 @@ RED.start().then(function() {
}); });
server.listen(settings.uiPort,settings.uiHost,function() { server.listen(settings.uiPort,settings.uiHost,function() {
if (settings.httpAdminRoot === false) { if (settings.httpAdminRoot === false) {
RED.log.info('Admin UI disabled'); RED.log.info(log._("server.admin-ui-disabled"));
} }
process.title = 'node-red'; process.title = 'node-red';
RED.log.info('Server now running at '+getListenPath()); RED.log.info(log._("server.now-running", {listenpath:getListenPath()}));
}); });
} else { } else {
util.log('[red] Running in headless mode'); RED.log.info(log._("server.headless-mode"));
} }
}).otherwise(function(err) { }).otherwise(function(err) {
RED.log.error("Failed to start server:"); RED.log.error(log._("server.failed-to-start"));
if (err.stack) { if (err.stack) {
RED.log.error(err.stack); RED.log.error(err.stack);
} else { } else {

View File

@ -13,12 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
**/ **/
var express = require('express');
var fs = require("fs");
var events = require("../events");
var path = require("path");
var log = require("../log"); var log = require("../log");
var redNodes = require("../nodes"); var redNodes = require("../nodes");
var settings = require("../settings"); var settings = require("../settings");
@ -34,7 +31,7 @@ module.exports = {
redNodes.setFlows(flows,deploymentType).then(function() { redNodes.setFlows(flows,deploymentType).then(function() {
res.send(204); res.send(204);
}).otherwise(function(err) { }).otherwise(function(err) {
log.warn("Error saving flows : "+err.message); log.warn(log._("api.flows.error-save",{message:err.message}));
log.warn(err.stack); log.warn(err.stack);
res.json(500,{error:"unexpected_error", message:err.message}); res.json(500,{error:"unexpected_error", message:err.message});
}); });

View File

@ -25,6 +25,7 @@ var flows = require("./flows");
var library = require("./library"); var library = require("./library");
var info = require("./info"); var info = require("./info");
var theme = require("./theme"); var theme = require("./theme");
var locales = require("./locales");
var auth = require("./auth"); var auth = require("./auth");
var needsPermission = auth.needsPermission; var needsPermission = auth.needsPermission;
@ -85,6 +86,8 @@ function init(adminApp,storage) {
adminApp.get("/nodes/:mod/:set",needsPermission("nodes.read"),nodes.getSet); adminApp.get("/nodes/:mod/:set",needsPermission("nodes.read"),nodes.getSet);
adminApp.put("/nodes/:mod/:set",needsPermission("nodes.write"),nodes.putSet); adminApp.put("/nodes/:mod/:set",needsPermission("nodes.write"),nodes.putSet);
adminApp.get(/locales\/(.+)\/?$/,needsPermission("nodes.read"),locales.get);
// Library // Library
library.init(adminApp); library.init(adminApp);
adminApp.post(new RegExp("/library/flows\/(.*)"),needsPermission("library.write"),library.post); adminApp.post(new RegExp("/library/flows\/(.*)"),needsPermission("library.write"),library.post);

View File

@ -17,6 +17,7 @@
var redApp = null; var redApp = null;
var storage = require("../storage"); var storage = require("../storage");
var log = require("../log"); var log = require("../log");
var needsPermission = require("./auth").needsPermission; var needsPermission = require("./auth").needsPermission;
function createLibrary(type) { function createLibrary(type) {
@ -34,8 +35,8 @@ function createLibrary(type) {
} }
}).otherwise(function(err) { }).otherwise(function(err) {
if (err) { if (err) {
log.warn("Error loading library entry '"+path+"' : "+err); log.warn(log._("api.library.error-load-entry",{path:path,message:err.toString()}));
if (err.message.indexOf('forbidden') === 0) { if (err.code === 'forbidden') {
log.audit({event: "library.get",type:type,error:"forbidden"},req); log.audit({event: "library.get",type:type,error:"forbidden"},req);
res.send(403); res.send(403);
return; return;
@ -56,8 +57,8 @@ function createLibrary(type) {
log.audit({event: "library.set",type:type},req); log.audit({event: "library.set",type:type},req);
res.send(204); res.send(204);
}).otherwise(function(err) { }).otherwise(function(err) {
log.warn("Error saving library entry '"+path+"' : "+err); log.warn(log._("api.library.error-save-entry",{path:path,message:err.toString()}));
if (err.message.indexOf('forbidden') === 0) { if (err.code === 'forbidden') {
log.audit({event: "library.set",type:type,error:"forbidden"},req); log.audit({event: "library.set",type:type,error:"forbidden"},req);
res.send(403); res.send(403);
return; return;
@ -88,8 +89,8 @@ module.exports = {
res.send(data); res.send(data);
}).otherwise(function(err) { }).otherwise(function(err) {
if (err) { if (err) {
log.warn("Error loading flow '"+req.params[0]+"' : "+err); log.warn(log._("api.library.error-load-flow",{path:req.params[0],message:err.toString()}));
if (err.message.indexOf('forbidden') === 0) { if (err.code === 'forbidden') {
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"forbidden"},req); log.audit({event: "library.get",type:"flow",path:req.params[0],error:"forbidden"},req);
res.send(403); res.send(403);
return; return;
@ -105,8 +106,8 @@ module.exports = {
log.audit({event: "library.set",type:"flow",path:req.params[0]},req); log.audit({event: "library.set",type:"flow",path:req.params[0]},req);
res.send(204); res.send(204);
}).otherwise(function(err) { }).otherwise(function(err) {
log.warn("Error loading flow '"+req.params[0]+"' : "+err); log.warn(log._("api.library.error-save-flow",{path:req.params[0],message:err.toString()}));
if (err.message.indexOf('forbidden') === 0) { if (err.code === 'forbidden') {
log.audit({event: "library.set",type:"flow",path:req.params[0],error:"forbidden"},req); log.audit({event: "library.set",type:"flow",path:req.params[0],error:"forbidden"},req);
res.send(403); res.send(403);
return; return;

30
red/api/locales.js Normal file
View File

@ -0,0 +1,30 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var i18n = require("../i18n");
module.exports = {
get: function(req,res) {
var namespace = req.params[0];
namespace = namespace.replace(/\.json$/,"");
var lang = i18n.determineLangFromHeaders(req.acceptedLanguages || []);
var prevLang = i18n.i.lng();
i18n.i.setLng(lang, function(){
var catalog = i18n.catalog(namespace,lang);
res.json(catalog||{});
});
i18n.i.setLng(prevLang);
}
}

View File

@ -13,16 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
**/ **/
var express = require('express');
var fs = require("fs");
var path = require("path");
var when = require('when');
var events = require("../events");
var redNodes = require("../nodes"); var redNodes = require("../nodes");
var comms = require("../comms"); var comms = require("../comms");
var server = require("../server"); var server = require("../server");
var log = require("../log"); var log = require("../log");
var i18n = require("../i18n");
var when = require("when");
var settings = require("../settings"); var settings = require("../settings");
@ -32,8 +29,9 @@ module.exports = {
log.audit({event: "nodes.list.get"},req); log.audit({event: "nodes.list.get"},req);
res.json(redNodes.getNodeList()); res.json(redNodes.getNodeList());
} else { } else {
var lang = i18n.determineLangFromHeaders(req.acceptedLanguages);
log.audit({event: "nodes.configs.get"},req); log.audit({event: "nodes.configs.get"},req);
res.send(redNodes.getNodeConfigs()); res.send(redNodes.getNodeConfigs(lang));
} }
}, },
@ -127,7 +125,8 @@ module.exports = {
res.send(404); res.send(404);
} }
} else { } else {
result = redNodes.getNodeConfig(id); var lang = i18n.determineLangFromHeaders(req.acceptedLanguages);
result = redNodes.getNodeConfig(id,lang);
if (result) { if (result) {
log.audit({event: "nodes.config.get",id:id},req); log.audit({event: "nodes.config.get",id:id},req);
res.send(result); res.send(result);
@ -232,12 +231,12 @@ function putNode(node, enabled) {
return promise.then(function(info) { return promise.then(function(info) {
if (info.enabled === enabled && !info.err) { if (info.enabled === enabled && !info.err) {
comms.publish("node/"+(enabled?"enabled":"disabled"),info,false); comms.publish("node/"+(enabled?"enabled":"disabled"),info,false);
log.info(" "+(enabled?"Enabled":"Disabled")+" node types:"); log.info(" "+log._("api.nodes."+(enabled?"enabled":"disabled")));
for (var i=0;i<info.types.length;i++) { for (var i=0;i<info.types.length;i++) {
log.info(" - "+info.types[i]); log.info(" - "+info.types[i]);
} }
} else if (enabled && info.err) { } else if (enabled && info.err) {
log.warn("Failed to enable node:"); log.warn(log._("api.nodes.error-enable"));
log.warn(" - "+info.name+" : "+info.err); log.warn(" - "+info.name+" : "+info.err);
} }
return info; return info;

View File

@ -65,7 +65,7 @@ function start() {
try { try {
msg = JSON.parse(data); msg = JSON.parse(data);
} catch(err) { } catch(err) {
log.warn("comms received malformed message : "+err.toString()); log.trace("comms received malformed message : "+err.toString());
return; return;
} }
if (!pendingAuth) { if (!pendingAuth) {
@ -119,12 +119,12 @@ function start() {
} }
}); });
ws.on('error', function(err) { ws.on('error', function(err) {
log.warn("comms error : "+err.toString()); log.warn(log._("comms.error",{message:err.toString()}));
}); });
}); });
wsServer.on('error', function(err) { wsServer.on('error', function(err) {
log.warn("comms server error : "+err.toString()); log.warn(log._("comms.error-server",{message:err.toString()}));
}); });
lastSentTime = Date.now(); lastSentTime = Date.now();
@ -167,7 +167,7 @@ function publishTo(ws,topic,data) {
try { try {
ws.send(msg); ws.send(msg);
} catch(err) { } catch(err) {
log.warn("comms send error : "+err.toString()); log.warn(log._("comms.error-send",{message:err.toString()}));
} }
} }

151
red/i18n.js Normal file
View File

@ -0,0 +1,151 @@
/**
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var i18n = require("i18next");
var when = require("when");
var path = require("path");
var fs = require("fs");
var defaultLang = "en-US";
var supportedLangs = null;
var resourceMap = {
"runtime": {
basedir: path.resolve(__dirname+"/../locales"),
file:"runtime.json"
},
"editor": {
basedir: path.resolve(__dirname+"/../locales"),
file: "editor.json"
}
}
var resourceCache = {}
function registerMessageCatalog(namespace,dir,file) {
return when.promise(function(resolve,reject) {
resourceMap[namespace] = { basedir:dir, file:file};
i18n.loadNamespace(namespace,function() {
resolve();
});
});
}
var initSupportedLangs = function() {
return when.promise(function(resolve,reject) {
fs.readdir(resourceMap.editor.basedir, function(err,files) {
if(err) {
reject(err);
} else {
supportedLangs = files;
resolve();
}
});
});
}
var MessageFileLoader = {
fetchOne: function(lng, ns, callback) {
if (resourceMap[ns]) {
var file = path.join(resourceMap[ns].basedir,lng,resourceMap[ns].file);
//console.log(file);
fs.readFile(file,"utf8",function(err,content) {
if (err) {
callback(err);
} else {
try {
//console.log(">>",ns,file,lng);
resourceCache[ns] = resourceCache[ns]||{};
resourceCache[ns][lng] = JSON.parse(content.replace(/^\uFEFF/, ''));
//console.log(resourceCache[ns][lng]);
callback(null, resourceCache[ns][lng]);
} catch(e) {
callback(e);
}
}
});
} else {
callback(new Error("Unrecognised namespace"));
}
}
}
function init() {
return when.promise(function(resolve,reject) {
i18n.backend(MessageFileLoader);
i18n.init({
ns: {
namespaces: ["runtime","editor"],
defaultNs: "runtime"
},
fallbackLng: ['en-US']
},function() {
initSupportedLangs().then(function() {
resolve();
});
});
});
}
function getCatalog(namespace,lang) {
//console.log("+",namespace,lang);
//console.log(resourceCache[namespace][lang]);
var result = null;
if (resourceCache.hasOwnProperty(namespace)) {
result = resourceCache[namespace][lang];
if (!result) {
var langParts = lang.split("-");
if (langParts.length == 2) {
result = getCatalog(namespace,langParts[0]);
}
}
}
//console.log(result);
return result;
}
function determineLangFromHeaders(acceptedLanguages){
var lang = "en-US";
for (var i=0;i<acceptedLanguages.length;i++){
if (supportedLangs.indexOf(acceptedLanguages[i]) !== -1){
lang = acceptedLanguages[i];
break;
// check the language without the country code
} else if (supportedLangs.indexOf(acceptedLanguages[i].split("-")[0]) !== -1) {
lang = acceptedLanguages[i].split("-")[0];
break;
}
}
return lang;
}
var obj = module.exports = {
init: init,
registerMessageCatalog: registerMessageCatalog,
catalog: getCatalog,
i: i18n,
determineLangFromHeaders: determineLangFromHeaders
}
obj['_'] = function() {
//var opts = {};
//if (def) {
// opts.defaultValue = def;
//}
//console.log(arguments);
return i18n.t.apply(null,arguments);
}

View File

@ -17,6 +17,8 @@
var util = require("util"); var util = require("util");
var EventEmitter = require("events").EventEmitter; var EventEmitter = require("events").EventEmitter;
var i18n = require("./i18n");
var levels = { var levels = {
off: 1, off: 1,
fatal: 10, fatal: 10,
@ -143,3 +145,5 @@ var log = module.exports = {
log.log(msg); log.log(msg);
} }
} }
log["_"] = i18n._;

View File

@ -43,7 +43,7 @@ function createNode(type,config) {
}); });
} }
} else { } else {
Log.error("Unknown type: "+type); Log.error(Log._("nodes.flow.unknown-type", {type:type}));
} }
return nn; return nn;
} }
@ -368,7 +368,7 @@ Flow.prototype.start = function(configDiff) {
this.started = true; this.started = true;
if (this.missingTypes.length > 0) { if (this.missingTypes.length > 0) {
throw new Error("missing types"); throw new Error(Log._("nodes.flow.missing-types"));
} }
events.emit("nodes-starting"); events.emit("nodes-starting");
@ -758,7 +758,7 @@ Flow.prototype.handleError = function(node,logMessage,msg) {
if (msg.error.source.id === node.id) { if (msg.error.source.id === node.id) {
count = msg.error.source.count+1; count = msg.error.source.count+1;
if (count === 10) { if (count === 10) {
node.warn("Message exceeded maximum number of catches"); node.warn(Log._("nodes.flow.error-loop"));
return; return;
} }
} }

View File

@ -17,6 +17,7 @@
var when = require("when"); var when = require("when");
var log = require("../log"); var log = require("../log");
var needsPermission = require("../api/auth").needsPermission; var needsPermission = require("../api/auth").needsPermission;
var credentialCache = {}; var credentialCache = {};
@ -75,7 +76,7 @@ module.exports = {
return storage.getCredentials().then(function (creds) { return storage.getCredentials().then(function (creds) {
credentialCache = creds; credentialCache = creds;
}).otherwise(function (err) { }).otherwise(function (err) {
log.warn("Error loading credentials : " + err); log.warn(log._("nodes.credentials.error",{message: err}));
}); });
}, },
@ -167,7 +168,7 @@ module.exports = {
var dashedType = nodeType.replace(/\s+/g, '-'); var dashedType = nodeType.replace(/\s+/g, '-');
var definition = credentialsDef[dashedType]; var definition = credentialsDef[dashedType];
if (!definition) { if (!definition) {
log.warn('Credential Type ' + nodeType + ' is not registered.'); log.warn(log._("nodes.credentials.not-registered",{type:nodeType}));
return; return;
} }

View File

@ -21,6 +21,7 @@ var typeRegistry = require("./registry");
var credentials = require("./credentials"); var credentials = require("./credentials");
var Flow = require("./Flow"); var Flow = require("./Flow");
var log = require("../log"); var log = require("../log");
var events = require("../events"); var events = require("../events");
var redUtil = require("../util"); var redUtil = require("../util");
var storage = null; var storage = null;
@ -36,7 +37,7 @@ var activeConfigNodes = {};
events.on('type-registered',function(type) { events.on('type-registered',function(type) {
if (activeFlow && activeFlow.typeRegistered(type)) { if (activeFlow && activeFlow.typeRegistered(type)) {
log.info("Missing type registered: "+type); log.info(log._("nodes.flows.registered-missing", {type:type}));
} }
}); });
@ -57,7 +58,7 @@ var flowNodes = module.exports = {
flowNodes.startFlows(); flowNodes.startFlows();
}); });
}).otherwise(function(err) { }).otherwise(function(err) {
log.warn("Error loading flows : "+err); log.warn(log._("nodes.flows.error",{message:err.toString()}));
console.log(err.stack); console.log(err.stack);
}); });
}, },
@ -132,36 +133,36 @@ var flowNodes = module.exports = {
}, },
startFlows: function(configDiff) { startFlows: function(configDiff) {
if (configDiff) { if (configDiff) {
log.info("Starting modified "+configDiff.type); log.info(log._("nodes.flows.starting-modified-"+configDiff.type));
} else { } else {
log.info("Starting flows"); log.info(log._("nodes.flows.starting-flows"));
} }
try { try {
activeFlow.start(configDiff); activeFlow.start(configDiff);
if (configDiff) { if (configDiff) {
log.info("Started modified "+configDiff.type); log.info(log._("nodes.flows.started-modified-"+configDiff.type));
} else { } else {
log.info("Started flows"); log.info(log._("nodes.flows.started-flows"));
} }
} catch(err) { } catch(err) {
var missingTypes = activeFlow.getMissingTypes(); var missingTypes = activeFlow.getMissingTypes();
if (missingTypes.length > 0) { if (missingTypes.length > 0) {
log.info("Waiting for missing types to be registered:"); log.info(log._("nodes.flows.missing-types"));
var knownUnknowns = 0; var knownUnknowns = 0;
for (var i=0;i<missingTypes.length;i++) { for (var i=0;i<missingTypes.length;i++) {
var type = missingTypes[i]; var type = missingTypes[i];
var info = deprecated.get(type); var info = deprecated.get(type);
if (info) { if (info) {
log.info(" - "+missingTypes[i]+" (provided by npm module "+info.module+")"); log.info(log._("nodes.flows.missing-type-provided",{type:missingTypes[i],module:info.module}));
knownUnknowns += 1; knownUnknowns += 1;
} else { } else {
log.info(" - "+missingTypes[i]); log.info(" - "+missingTypes[i]);
} }
} }
if (knownUnknowns > 0) { if (knownUnknowns > 0) {
log.info("To install any of these missing modules, run:"); log.info(log._("nodes.flows.missing-type-install-1"));
log.info(" npm install <module name>"); log.info(" npm install <module name>");
log.info("in the directory:"); log.info(log._("nodes.flows.missing-type-install-2"));
log.info(" "+settings.userDir); log.info(" "+settings.userDir);
} }
} }
@ -169,21 +170,21 @@ var flowNodes = module.exports = {
}, },
stopFlows: function(configDiff) { stopFlows: function(configDiff) {
if (configDiff) { if (configDiff) {
log.info("Stopping modified "+configDiff.type); log.info(log._("nodes.flows.stopping-modified-"+configDiff.type));
} else { } else {
log.info("Stopping flows"); log.info(log._("nodes.flows.stopping-flows"));
} }
if (activeFlow) { if (activeFlow) {
return activeFlow.stop(configDiff).then(function() { return activeFlow.stop(configDiff).then(function() {
if (configDiff) { if (configDiff) {
log.info("Stopped modified "+configDiff.type); log.info(log._("nodes.flows.stopped-modified-"+configDiff.type));
} else { } else {
log.info("Stopped flows"); log.info(log._("nodes.flows.stopped-flows"));
} }
return; return;
}); });
} else { } else {
log.info("Stopped"); log.info(log._("nodes.flows.stopped"));
return; return;
} }
}, },

View File

@ -17,6 +17,7 @@ var registry = require("./registry");
var credentials = require("./credentials"); var credentials = require("./credentials");
var flows = require("./flows"); var flows = require("./flows");
var Node = require("./Node"); var Node = require("./Node");
var log = require("../log");
/** /**
* Registers a node constructor * Registers a node constructor
@ -61,7 +62,7 @@ function init(_settings,storage,app) {
function checkTypeInUse(id) { function checkTypeInUse(id) {
var nodeInfo = registry.getNodeInfo(id); var nodeInfo = registry.getNodeInfo(id);
if (!nodeInfo) { if (!nodeInfo) {
throw new Error("Unrecognised id: "+id); throw new Error(log._("nodes.index.unrecognised-id", {id:id}));
} else { } else {
var inUse = {}; var inUse = {};
var config = flows.getFlows(); var config = flows.getFlows();
@ -76,7 +77,7 @@ function checkTypeInUse(id) {
}); });
if (nodesInUse.length > 0) { if (nodesInUse.length > 0) {
var msg = nodesInUse.join(", "); var msg = nodesInUse.join(", ");
var err = new Error("Type in use: "+msg); var err = new Error(log._("nodes.index.type-in-use", {msg:msg}));
err.code = "type_in_use"; err.code = "type_in_use";
throw err; throw err;
} }
@ -91,7 +92,7 @@ function removeNode(id) {
function removeModule(module) { function removeModule(module) {
var info = registry.getModuleInfo(module); var info = registry.getModuleInfo(module);
if (!info) { if (!info) {
throw new Error("Unrecognised module: "+module); throw new Error(log._("nodes.index.unrecognised-module", {module:module}));
} else { } else {
for (var i=0;i<info.nodes.length;i++) { for (var i=0;i<info.nodes.length;i++) {
checkTypeInUse(module+"/"+info.nodes[i].name); checkTypeInUse(module+"/"+info.nodes[i].name);

View File

@ -26,8 +26,8 @@ var settings;
function init(_settings) { function init(_settings) {
settings = _settings; settings = _settings;
registry.init(settings);
loader.init(settings); loader.init(settings);
registry.init(settings,loader);
} }
//TODO: defaultNodesDir/disableNodePathScan are to make testing easier. //TODO: defaultNodesDir/disableNodePathScan are to make testing easier.
// When the tests are componentized to match the new registry structure, // When the tests are componentized to match the new registry structure,

View File

@ -18,16 +18,26 @@ var when = require("when");
var fs = require("fs"); var fs = require("fs");
var path = require("path"); var path = require("path");
var events = require("../../events");
var localfilesystem = require("./localfilesystem"); var localfilesystem = require("./localfilesystem");
var registry = require("./registry"); var registry = require("./registry");
var RED;
var settings; var settings;
var i18n = require("../../i18n");
events.on("node-locales-dir", function(info) {
i18n.registerMessageCatalog(info.namespace,info.dir,info.file);
});
function init(_settings) { function init(_settings) {
settings = _settings; settings = _settings;
localfilesystem.init(settings); localfilesystem.init(settings);
RED = require('../../red');
} }
function load(defaultNodesDir,disableNodePathScan) { function load(defaultNodesDir,disableNodePathScan) {
@ -133,6 +143,7 @@ function loadNodeConfig(fileInfo) {
node.types = []; node.types = [];
node.err = err.toString(); node.err = err.toString();
} }
resolve(node);
} else { } else {
var types = []; var types = [];
@ -143,8 +154,32 @@ function loadNodeConfig(fileInfo) {
types.push(match[2]); types.push(match[2]);
} }
node.types = types; node.types = types;
node.config = content;
var langRegExp = /^<script[^>]* data-lang=['"](.+?)['"]/i;
regExp = /(<script[^>]* data-help-name=[\s\S]*?<\/script>)/gi;
match = null;
var mainContent = "";
var helpContent = {};
var index = 0;
while((match = regExp.exec(content)) !== null) {
mainContent += content.substring(index,regExp.lastIndex-match[1].length);
index = regExp.lastIndex;
var help = content.substring(regExp.lastIndex-match[1].length,regExp.lastIndex);
var lang = "en-US";
if ((match = langRegExp.exec(help)) !== null) {
lang = match[1];
}
if (!helpContent.hasOwnProperty(lang)) {
helpContent[lang] = "";
}
helpContent[lang] += help;
}
mainContent += content.substring(index);
node.config = mainContent;
node.help = helpContent;
// TODO: parse out the javascript portion of the template // TODO: parse out the javascript portion of the template
//node.script = ""; //node.script = "";
for (var i=0;i<node.types.length;i++) { for (var i=0;i<node.types.length;i++) {
@ -153,13 +188,38 @@ function loadNodeConfig(fileInfo) {
break; break;
} }
} }
fs.stat(path.join(path.dirname(file),"locales"),function(err,stat) {
if (!err) {
node.namespace = node.id;
i18n.registerMessageCatalog(node.id,
path.join(path.dirname(file),"locales"),
path.basename(file,".js")+".json")
.then(function() {
resolve(node);
});
} else {
node.namespace = node.module;
resolve(node);
}
});
} }
resolve(node);
}); });
}); });
} }
//function getAPIForNode(node) {
// var red = {
// nodes: RED.nodes,
// library: RED.library,
// credentials: RED.credentials,
// events: RED.events,
// log: RED.log,
//
// }
//
//}
/** /**
* Loads the specified node into the runtime * Loads the specified node into the runtime
@ -180,7 +240,20 @@ function loadNodeSet(node) {
var loadPromise = null; var loadPromise = null;
var r = require(node.file); var r = require(node.file);
if (typeof r === "function") { if (typeof r === "function") {
var promise = r(require('../../red'));
var red = {};
for (var i in RED) {
if (RED.hasOwnProperty(i) && !/^(init|start|stop)$/.test(i)) {
var propDescriptor = Object.getOwnPropertyDescriptor(RED,i);
Object.defineProperty(red,i,propDescriptor);
}
}
red["_"] = function() {
var args = Array.prototype.slice.call(arguments, 0);
args[0] = node.namespace+":"+args[0];
return i18n._.apply(null,args);
}
var promise = r(red);
if (promise != null && typeof promise.then === "function") { if (promise != null && typeof promise.then === "function") {
loadPromise = promise.then(function() { loadPromise = promise.then(function() {
node.enabled = true; node.enabled = true;
@ -229,7 +302,8 @@ function addModule(module) {
} }
var nodes = []; var nodes = [];
if (registry.getModuleInfo(module)) { if (registry.getModuleInfo(module)) {
var e = new Error("Module already loaded"); // TODO: nls
var e = new Error("module_already_loaded");
e.code = "module_already_loaded"; e.code = "module_already_loaded";
return when.reject(e); return when.reject(e);
} }
@ -269,10 +343,43 @@ function addFile(file) {
} }
} }
function loadNodeHelp(node,lang) {
var dir = path.dirname(node.template);
var base = path.basename(node.template);
var localePath = path.join(dir,"locales",lang,base);
try {
// TODO: make this async
var content = fs.readFileSync(localePath, "utf8")
return content;
} catch(err) {
return null;
}
}
function getNodeHelp(node,lang) {
if (!node.help[lang]) {
var help = loadNodeHelp(node,lang);
if (help == null) {
var langParts = lang.split("-");
if (langParts.length == 2) {
help = loadNodeHelp(node,langParts[0]);
}
}
if (help) {
node.help[lang] = help;
} else {
node.help[lang] = node.help["en-US"];
}
}
return node.help[lang];
}
module.exports = { module.exports = {
init: init, init: init,
load: load, load: load,
addModule: addModule, addModule: addModule,
addFile: addFile, addFile: addFile,
loadNodeSet: loadNodeSet loadNodeSet: loadNodeSet,
getNodeHelp: getNodeHelp
} }

View File

@ -19,6 +19,7 @@ var fs = require("fs");
var path = require("path"); var path = require("path");
var events = require("../../events"); var events = require("../../events");
var log = require("../../log");
var settings; var settings;
var defaultNodesDir = path.resolve(path.join(__dirname,"..","..","..","nodes")); var defaultNodesDir = path.resolve(path.join(__dirname,"..","..","..","nodes"));
@ -87,7 +88,7 @@ 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)$/.test(fn)) { if (!/^(\..*|lib|icons|node_modules|test|locales)$/.test(fn)) {
result = result.concat(getLocalNodeFiles(path.join(dir,fn))); result = result.concat(getLocalNodeFiles(path.join(dir,fn)));
} else if (fn === "icons") { } else if (fn === "icons") {
events.emit("node-icon-dir",path.join(dir,fn)); events.emit("node-icon-dir",path.join(dir,fn));
@ -196,6 +197,13 @@ function getNodeFiles(_defaultNodesDir,disableNodePathScan) {
var nodeFiles = getLocalNodeFiles(path.resolve(defaultNodesDir)); var nodeFiles = getLocalNodeFiles(path.resolve(defaultNodesDir));
//console.log(nodeFiles); //console.log(nodeFiles);
var defaultLocalesPath = path.resolve(path.join(defaultNodesDir,"core","locales"));
events.emit("node-locales-dir", {
namespace:"node-red",
dir: defaultLocalesPath,
file: "messages.json"
});
if (settings.userDir) { if (settings.userDir) {
dir = path.join(settings.userDir,"nodes"); dir = path.join(settings.userDir,"nodes");
nodeFiles = nodeFiles.concat(getLocalNodeFiles(dir)); nodeFiles = nodeFiles.concat(getLocalNodeFiles(dir));
@ -244,7 +252,7 @@ function getModuleFiles(module) {
var moduleFiles = scanTreeForNodesModules(module); var moduleFiles = scanTreeForNodesModules(module);
if (moduleFiles.length === 0) { if (moduleFiles.length === 0) {
var err = new Error("Cannot find module '" + module + "'"); var err = new Error(log._("nodes.registry.localfilesystem.module-not-found", {module:module}));
err.code = 'MODULE_NOT_FOUND'; err.code = 'MODULE_NOT_FOUND';
throw err; throw err;
} }

View File

@ -23,6 +23,8 @@ var settings;
var Node; var Node;
var loader;
var nodeConfigCache = null; var nodeConfigCache = null;
var moduleConfigs = {}; var moduleConfigs = {};
var nodeList = []; var nodeList = [];
@ -30,8 +32,9 @@ var nodeConstructors = {};
var nodeTypeToId = {}; var nodeTypeToId = {};
var moduleNodes = {}; var moduleNodes = {};
function init(_settings) { function init(_settings,_loader) {
settings = _settings; settings = _settings;
loader = _loader;
if (settings.available()) { if (settings.available()) {
moduleConfigs = loadNodeConfigs(); moduleConfigs = loadNodeConfigs();
} else { } else {
@ -323,7 +326,7 @@ function registerNodeConstructor(type,constructor) {
events.emit("type-registered",type); events.emit("type-registered",type);
} }
function getAllNodeConfigs() { function getAllNodeConfigs(lang) {
if (!nodeConfigCache) { if (!nodeConfigCache) {
var result = ""; var result = "";
var script = ""; var script = "";
@ -332,6 +335,7 @@ function getAllNodeConfigs() {
var config = moduleConfigs[getModule(id)].nodes[getNode(id)]; var config = moduleConfigs[getModule(id)].nodes[getNode(id)];
if (config.enabled && !config.err) { if (config.enabled && !config.err) {
result += config.config; result += config.config;
result += loader.getNodeHelp(config,lang||"en-US")||"";
//script += config.script; //script += config.script;
} }
} }
@ -345,7 +349,7 @@ function getAllNodeConfigs() {
return nodeConfigCache; return nodeConfigCache;
} }
function getNodeConfig(id) { function getNodeConfig(id,lang) {
var config = moduleConfigs[getModule(id)]; var config = moduleConfigs[getModule(id)];
if (!config) { if (!config) {
return null; return null;
@ -353,6 +357,8 @@ function getNodeConfig(id) {
config = config.nodes[getNode(id)]; config = config.nodes[getNode(id)];
if (config) { if (config) {
var result = config.config; var result = config.config;
result += loader.getNodeHelp(config,lang||"en-US")
//if (config.script) { //if (config.script) {
// result += '<script type="text/javascript">'+config.script+'</script>'; // result += '<script type="text/javascript">'+config.script+'</script>';
//} //}

View File

@ -20,6 +20,7 @@ var library = require("./api/library");
var comms = require("./comms"); var comms = require("./comms");
var log = require("./log"); var log = require("./log");
var util = require("./util"); var util = require("./util");
var i18n = require("./i18n");
var fs = require("fs"); var fs = require("fs");
var settings = require("./settings"); var settings = require("./settings");
var credentials = require("./nodes/credentials"); var credentials = require("./nodes/credentials");
@ -75,4 +76,5 @@ var RED = {
get httpNode() { return server.nodeApp }, get httpNode() { return server.nodeApp },
get server() { return server.server } get server() { return server.server }
}; };
module.exports = RED; module.exports = RED;

View File

@ -24,6 +24,7 @@ var redNodes = require("./nodes");
var comms = require("./comms"); var comms = require("./comms");
var storage = require("./storage"); var storage = require("./storage");
var log = require("./log"); var log = require("./log");
var i18n = require("./i18n");
var app = null; var app = null;
var nodeApp = null; var nodeApp = null;
@ -44,7 +45,8 @@ function init(_server,_settings) {
} }
function start() { function start() {
return storage.init(settings) return i18n.init()
.then(function() { return storage.init(settings)})
.then(function() { return settings.load(storage)}) .then(function() { return settings.load(storage)})
.then(function() { .then(function() {
if (settings.httpAdminRoot !== false) { if (settings.httpAdminRoot !== false) {
@ -56,12 +58,12 @@ function start() {
reportMetrics(); reportMetrics();
}, settings.runtimeMetricInterval||15000); }, settings.runtimeMetricInterval||15000);
} }
console.log("\n\nWelcome to Node-RED\n===================\n"); console.log("\n\n"+log._("runtime.welcome")+"\n===================\n");
if (settings.version) { if (settings.version) {
log.info("Node-RED version: v"+settings.version); log.info(log._("runtime.version",{component:"Node-RED",version:"v"+settings.version}));
} }
log.info("Node.js version: "+process.version); log.info(log._("runtime.version",{component:"Node.js ",version:process.version}));
log.info("Loading palette nodes"); log.info(log._("server.loading"));
redNodes.init(settings,storage,app); redNodes.init(settings,storage,app);
return redNodes.load().then(function() { return redNodes.load().then(function() {
@ -75,13 +77,13 @@ function start() {
log.warn("["+nodeErrors[i].name+"] "+nodeErrors[i].err); log.warn("["+nodeErrors[i].name+"] "+nodeErrors[i].err);
} }
} else { } else {
log.warn("Failed to register "+nodeErrors.length+" node type"+(nodeErrors.length==1?"":"s")); log.warn(log._("server.errors",{count:nodeErrors.length}));
log.warn("Run with -v for details"); log.warn(log._("server.errors-help"));
} }
log.warn("------------------------------------------"); log.warn("------------------------------------------");
} }
if (nodeMissing.length > 0) { if (nodeMissing.length > 0) {
log.warn("Missing node modules:"); log.warn(log._("server.missing-modules"));
var missingModules = {}; var missingModules = {};
for (i=0;i<nodeMissing.length;i++) { for (i=0;i<nodeMissing.length;i++) {
var missing = nodeMissing[i]; var missing = nodeMissing[i];
@ -100,11 +102,11 @@ function start() {
} }
} }
if (!settings.autoInstallModules) { if (!settings.autoInstallModules) {
log.info("Removing modules from config"); log.info(log._("server.removing-modules"));
redNodes.cleanModuleList(); redNodes.cleanModuleList();
} }
} }
log.info("Settings file : "+settings.settingsFile); log.info(log._("runtime.paths.settings",{path:settings.settingsFile}));
redNodes.loadFlows(); redNodes.loadFlows();
comms.start(); comms.start();
}).otherwise(function(err) { }).otherwise(function(err) {
@ -117,7 +119,7 @@ function start() {
function reportAddedModules(info) { function reportAddedModules(info) {
comms.publish("node/added",info.nodes,false); comms.publish("node/added",info.nodes,false);
if (info.nodes.length > 0) { if (info.nodes.length > 0) {
log.info("Added node types:"); log.info(log._("server.added-types"));
for (var i=0;i<info.nodes.length;i++) { for (var i=0;i<info.nodes.length;i++) {
for (var j=0;j<info.nodes[i].types.length;j++) { for (var j=0;j<info.nodes[i].types.length;j++) {
log.info(" - "+ log.info(" - "+
@ -133,7 +135,7 @@ function reportAddedModules(info) {
function reportRemovedModules(removedNodes) { function reportRemovedModules(removedNodes) {
comms.publish("node/removed",removedNodes,false); comms.publish("node/removed",removedNodes,false);
log.info("Removed node types:"); log.info(log._("server.removed-types"));
for (var j=0;j<removedNodes.length;j++) { for (var j=0;j<removedNodes.length;j++) {
for (var i=0;i<removedNodes[j].types.length;i++) { for (var i=0;i<removedNodes[j].types.length;i++) {
log.info(" - "+(removedNodes[j].module?removedNodes[j].module+":":"")+removedNodes[j].types[i]); log.info(" - "+(removedNodes[j].module?removedNodes[j].module+":":"")+removedNodes[j].types[i]);
@ -159,18 +161,18 @@ function installModule(module) {
//TODO: ensure module is 'safe' //TODO: ensure module is 'safe'
return when.promise(function(resolve,reject) { return when.promise(function(resolve,reject) {
if (/[\s;]/.test(module)) { if (/[\s;]/.test(module)) {
reject(new Error("Invalid module name")); reject(new Error(log._("server.install.invalid")));
return; return;
} }
if (redNodes.getModuleInfo(module)) { if (redNodes.getModuleInfo(module)) {
// TODO: nls
var err = new Error("Module already loaded"); var err = new Error("Module already loaded");
err.code = "module_already_loaded"; err.code = "module_already_loaded";
reject(err); reject(err);
return; return;
} }
log.info(i18n._("nodes.install.installing",{name: module}));
log.info("Installing module: "+module);
var installDir = settings.userDir || process.env.NODE_RED_HOME || "."; var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
var child = child_process.exec('npm install --production '+module, var child = child_process.exec('npm install --production '+module,
{ {
@ -180,19 +182,19 @@ function installModule(module) {
if (err) { if (err) {
var lookFor404 = new RegExp(" 404 .*"+module+"$","m"); var lookFor404 = new RegExp(" 404 .*"+module+"$","m");
if (lookFor404.test(stdout)) { if (lookFor404.test(stdout)) {
log.warn("Installation of module "+module+" failed: module not found"); log.warn(log._("server.install.install-failed-not-found",{name:module}));
var e = new Error(); var e = new Error();
e.code = 404; e.code = 404;
reject(e); reject(e);
} else { } else {
log.warn("Installation of module "+module+" failed:"); log.warn(log._("server.install.install-failed-long",{name:module}));
log.warn("------------------------------------------"); log.warn("------------------------------------------");
log.warn(err.toString()); log.warn(err.toString());
log.warn("------------------------------------------"); log.warn("------------------------------------------");
reject(new Error("Install failed")); reject(new Error(log._("server.install.install-failed")));
} }
} else { } else {
log.info("Installed module: "+module); log.info(log._("server.install.installed",{name:module}));
resolve(redNodes.addModule(module).then(reportAddedModules)); resolve(redNodes.addModule(module).then(reportAddedModules));
} }
} }
@ -203,30 +205,30 @@ function installModule(module) {
function uninstallModule(module) { function uninstallModule(module) {
return when.promise(function(resolve,reject) { return when.promise(function(resolve,reject) {
if (/[\s;]/.test(module)) { if (/[\s;]/.test(module)) {
reject(new Error("Invalid module name")); reject(new Error(log._("server.install.invalid")));
return; return;
} }
var installDir = settings.userDir || process.env.NODE_RED_HOME || "."; var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
var moduleDir = path.join(installDir,"node_modules",module); var moduleDir = path.join(installDir,"node_modules",module);
if (!fs.existsSync(moduleDir)) { if (!fs.existsSync(moduleDir)) {
return reject(new Error("Unabled to uninstall "+module+".")); return reject(new Error(log._("server.install.uninstall-failed",{name:module})));
} }
var list = redNodes.removeModule(module); var list = redNodes.removeModule(module);
log.info("Removing module: "+module); log.info(log._("server.install.uninstalling",{name:module}));
var child = child_process.exec('npm remove '+module, var child = child_process.exec('npm remove '+module,
{ {
cwd: installDir cwd: installDir
}, },
function(err, stdin, stdout) { function(err, stdin, stdout) {
if (err) { if (err) {
log.warn("Removal of module "+module+" failed:"); log.warn(log._("server.install.uninstall-failed-long",{name:module}));
log.warn("------------------------------------------"); log.warn("------------------------------------------");
log.warn(err.toString()); log.warn(err.toString());
log.warn("------------------------------------------"); log.warn("------------------------------------------");
reject(new Error("Removal failed")); reject(new Error(log._("server.install.uninstall-failed",{name:module})));
} else { } else {
log.info("Removed module: "+module); log.info(log._("server.install.uninstalled",{name:module}));
reportRemovedModules(list); reportRemovedModules(list);
resolve(list); resolve(list);
} }

View File

@ -17,6 +17,7 @@
var when = require("when"); var when = require("when");
var clone = require("clone"); var clone = require("clone");
var assert = require("assert"); var assert = require("assert");
var log = require("./log");
var userSettings = null; var userSettings = null;
var globalSettings = null; var globalSettings = null;
@ -48,17 +49,17 @@ var persistentSettings = {
return clone(userSettings[prop]); return clone(userSettings[prop]);
} }
if (globalSettings === null) { if (globalSettings === null) {
throw new Error("Settings not available"); throw new Error(log._("settings.not-available"));
} }
return clone(globalSettings[prop]); return clone(globalSettings[prop]);
}, },
set: function(prop,value) { set: function(prop,value) {
if (userSettings.hasOwnProperty(prop)) { if (userSettings.hasOwnProperty(prop)) {
throw new Error("Property '"+prop+"' is read-only"); throw new Error(log._("settings.property-read-only", {prop:prop}));
} }
if (globalSettings === null) { if (globalSettings === null) {
throw new Error("Settings not available"); throw new Error(log._("settings.not-available"));
} }
var current = globalSettings[prop]; var current = globalSettings[prop];
globalSettings[prop] = value; globalSettings[prop] = value;

View File

@ -16,6 +16,7 @@
var when = require('when'); var when = require('when');
var Path = require('path'); var Path = require('path');
var log = require("../log");
var storageModule; var storageModule;
var settingsAvailable; var settingsAvailable;
@ -96,13 +97,17 @@ var storageModuleInterface = {
getLibraryEntry: function(type, path) { getLibraryEntry: function(type, path) {
if (is_malicious(path)) { if (is_malicious(path)) {
return when.reject(new Error('forbidden flow name')); var err = new Error();
err.code = "forbidden";
return when.reject(err);
} }
return storageModule.getLibraryEntry(type, path); return storageModule.getLibraryEntry(type, path);
}, },
saveLibraryEntry: function(type, path, meta, body) { saveLibraryEntry: function(type, path, meta, body) {
if (is_malicious(path)) { if (is_malicious(path)) {
return when.reject(new Error('forbidden flow name')); var err = new Error();
err.code = "forbidden";
return when.reject(err);
} }
return storageModule.saveLibraryEntry(type, path, meta, body); return storageModule.saveLibraryEntry(type, path, meta, body);
}, },
@ -117,7 +122,9 @@ var storageModuleInterface = {
}, },
getFlow: function(fn) { getFlow: function(fn) {
if (is_malicious(fn)) { if (is_malicious(fn)) {
return when.reject(new Error('forbidden flow name')); var err = new Error();
err.code = "forbidden";
return when.reject(err);
} }
if (storageModule.hasOwnProperty("getFlow")) { if (storageModule.hasOwnProperty("getFlow")) {
return storageModule.getFlow(fn); return storageModule.getFlow(fn);
@ -128,7 +135,9 @@ var storageModuleInterface = {
}, },
saveFlow: function(fn, data) { saveFlow: function(fn, data) {
if (is_malicious(fn)) { if (is_malicious(fn)) {
return when.reject(new Error('forbidden flow name')); var err = new Error();
err.code = "forbidden";
return when.reject(err);
} }
if (storageModule.hasOwnProperty("saveFlow")) { if (storageModule.hasOwnProperty("saveFlow")) {
return storageModule.saveFlow(fn, data); return storageModule.saveFlow(fn, data);

View File

@ -185,15 +185,15 @@ var localfilesystem = {
getFlows: function() { getFlows: function() {
return when.promise(function(resolve) { return when.promise(function(resolve) {
log.info("User Directory : "+settings.userDir); log.info(log._("storage.localfilesystem.user-dir",{path:settings.userDir}));
log.info("Flows file : "+flowsFullPath); log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath}));
fs.exists(flowsFullPath, function(exists) { fs.exists(flowsFullPath, function(exists) {
if (exists) { if (exists) {
resolve(nodeFn.call(fs.readFile,flowsFullPath,'utf8').then(function(data) { resolve(nodeFn.call(fs.readFile,flowsFullPath,'utf8').then(function(data) {
return JSON.parse(data); return JSON.parse(data);
})); }));
} else { } else {
log.info("Creating new flows file"); log.info(log._("storage.localfilesystem.create"));
resolve([]); resolve([]);
} }
}); });
@ -257,7 +257,7 @@ var localfilesystem = {
try { try {
return JSON.parse(data); return JSON.parse(data);
} catch(err) { } catch(err) {
log.info("Corrupted config detected - resetting"); log.trace("Corrupted config detected - resetting");
return {}; return {};
} }
} else { } else {
@ -277,7 +277,7 @@ var localfilesystem = {
try { try {
return JSON.parse(data); return JSON.parse(data);
} catch(err) { } catch(err) {
log.info("Corrupted sessions file - resetting"); log.trace("Corrupted sessions file - resetting");
return {}; return {};
} }
} else { } else {

View File

@ -489,7 +489,7 @@ describe('websocket Node', function() {
}); });
logEvents.should.have.length(1); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg'); logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith("Missing server configuration"); logEvents[0][0].msg.toString().should.startWith("websocket.errors.missing-conf");
done(); done();
}); });
}); });
@ -505,7 +505,7 @@ describe('websocket Node', function() {
//console.log(logEvents); //console.log(logEvents);
logEvents.should.have.length(1); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg'); logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith("Missing server configuration"); logEvents[0][0].msg.toString().should.startWith("websocket.errors.missing-conf");
done(); done();
}); });
}); });

View File

@ -134,7 +134,7 @@ describe('range Node', function() {
var sinon = require('sinon'); var sinon = require('sinon');
sinon.stub(rangeNode1, 'log', function(log) { sinon.stub(rangeNode1, 'log', function(log) {
if(log.indexOf("Not a number") > -1) { if(log.indexOf("notnumber") > -1) {
done(); done();
} else { } else {
try { try {

View File

@ -254,9 +254,9 @@ describe('CSV node', function() {
}); });
logEvents.should.have.length(2); logEvents.should.have.length(2);
logEvents[0][0].should.have.a.property('msg'); logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith('This node only handles csv strings or js objects.'); logEvents[0][0].msg.toString().should.startWith('csv.errors.csv_js');
logEvents[1][0].should.have.a.property('msg'); logEvents[1][0].should.have.a.property('msg');
logEvents[1][0].msg.toString().should.startWith('This node only handles csv strings or js objects.'); logEvents[1][0].msg.toString().should.startWith('csv.errors.csv_js');
done(); done();
} catch(err) { } catch(err) {
done(err); done(err);

View File

@ -105,13 +105,13 @@ describe('JSON node', function() {
//console.log(logEvents); //console.log(logEvents);
logEvents.should.have.length(4); logEvents.should.have.length(4);
logEvents[0][0].should.have.a.property('msg'); logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith('Dropped: '); logEvents[0][0].msg.toString().should.eql('json.errors.dropped');
logEvents[1][0].should.have.a.property('msg'); logEvents[1][0].should.have.a.property('msg');
logEvents[1][0].msg.toString().should.startWith('Dropped: '); logEvents[1][0].msg.toString().should.eql('json.errors.dropped');
logEvents[2][0].should.have.a.property('msg'); logEvents[2][0].should.have.a.property('msg');
logEvents[2][0].msg.toString().should.startWith('Dropped: '); logEvents[2][0].msg.toString().should.eql('json.errors.dropped-object');
logEvents[3][0].should.have.a.property('msg'); logEvents[3][0].should.have.a.property('msg');
logEvents[3][0].msg.toString().should.startWith('Dropped: '); logEvents[3][0].msg.toString().should.eql('json.errors.dropped-object');
done(); done();
} catch(err) { } catch(err) {
done(err); done(err);

View File

@ -111,7 +111,7 @@ describe('XML node', function() {
return evt[0].type == "xml"; return evt[0].type == "xml";
}); });
logEvents.should.have.length(1); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg',"This node only handles xml strings or js objects."); logEvents[0][0].should.have.a.property('msg',"xml.errors.xml_js");
done(); done();
} catch(err) { } catch(err) {
done(err); done(err);

View File

@ -133,7 +133,7 @@ describe('file Nodes', function() {
//console.log(logEvents); //console.log(logEvents);
logEvents.should.have.length(1); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg'); logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.equal("No filename specified"); logEvents[0][0].msg.toString().should.equal("file.errors.nofilename");
done(); done();
} }
},wait); },wait);
@ -180,7 +180,7 @@ describe('file Nodes', function() {
//console.log(logEvents); //console.log(logEvents);
logEvents.should.have.length(1); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg'); logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith("failed to write"); logEvents[0][0].msg.toString().should.startWith("file.errors.writefail");
done(); done();
} }
catch(e) { done(e); } catch(e) { done(e); }
@ -205,7 +205,7 @@ describe('file Nodes', function() {
//console.log(logEvents); //console.log(logEvents);
logEvents.should.have.length(1); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg'); logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith("failed to append"); logEvents[0][0].msg.toString().should.startWith("file.errors.appendfail");
done(); done();
} }
catch(e) { done(e); } catch(e) { done(e); }
@ -230,7 +230,7 @@ describe('file Nodes', function() {
//console.log(logEvents); //console.log(logEvents);
logEvents.should.have.length(1); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg'); logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith("failed to delete"); logEvents[0][0].msg.toString().should.startWith("file.errors.deletefail");
done(); done();
} }
catch(e) { done(e); } catch(e) { done(e); }
@ -318,7 +318,7 @@ describe('file Nodes', function() {
}); });
logEvents.should.have.length(1); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg'); logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith("Warning: msg "); logEvents[0][0].msg.toString().should.startWith("file.errors.nooverride");
done(); done();
}); });
n1.receive({payload:"",filename:"foo.txt"}); n1.receive({payload:"",filename:"foo.txt"});
@ -335,7 +335,7 @@ describe('file Nodes', function() {
}); });
logEvents.should.have.length(1); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg'); logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.equal("No filename specified"); logEvents[0][0].msg.toString().should.equal("file.errors.nofilename");
done(); done();
},wait); },wait);
n1.receive({}); n1.receive({});
@ -352,7 +352,8 @@ describe('file Nodes', function() {
}); });
logEvents.should.have.length(1); logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg'); logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.equal("Error: ENOENT, open 'badfile'"); //logEvents[0][0].msg.toString().should.equal("Error: ENOENT, open 'badfile'");
logEvents[0][0].msg.toString().should.startWith("Error: ENOENT, open");
done(); done();
},wait); },wait);
n1.receive({payload:""}); n1.receive({payload:""});

View File

@ -86,15 +86,28 @@ module.exports = {
available: function() { return false; } available: function() { return false; }
}; };
var red = {};
for (var i in RED) {
if (RED.hasOwnProperty(i) && !/^(init|start|stop)$/.test(i)) {
var propDescriptor = Object.getOwnPropertyDescriptor(RED,i);
Object.defineProperty(red,i,propDescriptor);
}
}
red["_"] = function(messageId) {
return messageId;
};
redNodes.init(settings, storage); redNodes.init(settings, storage);
credentials.init(storage,express()); credentials.init(storage,express());
RED.nodes.registerType("helper", helperNode); RED.nodes.registerType("helper", helperNode);
if (Array.isArray(testNode)) { if (Array.isArray(testNode)) {
for (var i = 0; i < testNode.length; i++) { for (var i = 0; i < testNode.length; i++) {
testNode[i](RED); testNode[i](red);
} }
} else { } else {
testNode(RED); testNode(red);
} }
flows.load().then(function() { flows.load().then(function() {
should.deepEqual(testFlows, flows.getFlows()); should.deepEqual(testFlows, flows.getFlows());

View File

0
test/red/i18n_spec.js Normal file
View File

View File

@ -29,7 +29,7 @@ var auth = require("../../../red/api/auth");
describe('Credentials', function() { describe('Credentials', function() {
afterEach(function() { afterEach(function() {
index.clearRegistry(); index.clearRegistry();
}); });
@ -156,7 +156,7 @@ describe('Credentials', function() {
credentials.init(storage); credentials.init(storage);
credentials.load().then(function() { credentials.load().then(function() {
logmsg.should.equal("Error loading credentials : test forcing failure"); log.warn.calledOnce.should.be.true;
log.warn.restore(); log.warn.restore();
done(); done();
}).otherwise(function(err){ }).otherwise(function(err){
@ -218,7 +218,7 @@ describe('Credentials', function() {
index.loadFlows().then(function() { index.loadFlows().then(function() {
var testnode = new TestNode({id:'tab1',type:'test',name:'barney'}); var testnode = new TestNode({id:'tab1',type:'test',name:'barney'});
credentials.extract(testnode); credentials.extract(testnode);
should.equal(logmsg, 'Credential Type test is not registered.'); log.warn.calledOnce.should.be.true;
log.warn.restore(); log.warn.restore();
done(); done();
}).otherwise(function(err){ }).otherwise(function(err){

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