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

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

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,33 +160,33 @@ 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")
@ -181,6 +203,7 @@ 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));
@ -201,7 +224,7 @@ var RED = (function() {
ace.require("ace/ext/language_tools"); ace.require("ace/ext/language_tools");
RED.settings.init(loadEditor); RED.settings.init(loadLocales);
}); });

View File

@ -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];
@ -134,6 +151,9 @@ RED.nodes = (function() {
} }
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();
@ -288,7 +308,10 @@ 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"
}
}); });
@ -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;
} }
@ -548,15 +571,13 @@ RED.nodes = (function() {
!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,17 +589,16 @@ 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;
} }
} }
} }
} }
@ -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 = [];
} }

View File

@ -17,61 +17,70 @@
RED.clipboard = (function() { RED.clipboard = (function() {
var dialog = $('<div id="clipboard-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>') var dialog;
.appendTo("body") var dialogContainer;
.dialog({ var exportNodesDialog;
modal: true, var importNodesDialog;
autoOpen: false,
width: 500, function setupDialogs(){
resizable: false, dialog = $('<div id="clipboard-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
buttons: [ .appendTo("body")
{ .dialog({
id: "clipboard-dialog-ok", modal: true,
text: "Ok", autoOpen: false,
click: function() { width: 500,
if (/Import/.test(dialog.dialog("option","title"))) { resizable: false,
RED.view.importNodes($("#clipboard-import").val()); 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" );
} }
$( this ).dialog( "close" );
} }
],
open: function(e) {
$(this).parent().find(".ui-dialog-titlebar-close").hide();
RED.keyboard.disable();
}, },
{ close: function(e) {
id: "clipboard-dialog-cancel", RED.keyboard.enable();
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"); dialogContainer = dialog.children(".dialog-form");
var exportNodesDialog = '<div class="form-row">'+ exportNodesDialog = '<div class="form-row">'+
'<label for="node-input-export" style="display: block; width:100%;"><i class="fa fa-clipboard"></i> Nodes:</label>'+ '<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>'+ '<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>'+
'<div class="form-tips">'+ '<div class="form-tips">'+
'Select the text above and copy to the clipboard with Ctrl-C.'+ RED._("clipboard.selectNodes")+
'</div>'; '</div>';
var importNodesDialog = '<div class="form-row">'+ 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="Paste nodes here"></textarea>'+ '<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="'+
'</div>'; RED._("clipboard.pasteNodes")+
'"></textarea>'+
'</div>';
}
function validateImport() { function validateImport() {
var importInput = $("#clipboard-import"); var importInput = $("#clipboard-import");
@ -98,7 +107,7 @@ RED.clipboard = (function() {
$("#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,7 +129,7 @@ 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" );
} }
} }
@ -131,6 +140,7 @@ RED.clipboard = (function() {
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);

View File

@ -49,18 +49,18 @@ RED.deploy = (function() {
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;
@ -83,7 +83,7 @@ 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");
@ -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 {
@ -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

@ -146,8 +146,8 @@ RED.library = (function() {
'<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>'
); );
@ -232,14 +232,14 @@ RED.library = (function() {
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" );
} }
@ -274,12 +274,12 @@ RED.library = (function() {
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;
//} //}
@ -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" );
} }
@ -385,7 +384,8 @@ RED.library = (function() {
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 {
@ -412,5 +412,3 @@ RED.library = (function() {
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>'+
@ -78,15 +78,16 @@ RED.palette = (function() {
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>";
} }
@ -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();
@ -268,9 +274,13 @@ 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) {

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

@ -216,7 +216,7 @@ RED.subflow = (function() {
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;
@ -276,7 +276,7 @@ RED.subflow = (function() {
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) {

View File

@ -25,7 +25,7 @@ RED.sidebar.config = (function() {
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");

View File

@ -36,7 +36,7 @@ RED.sidebar.info = (function() {
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,12 +60,12 @@ 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) {
@ -76,7 +76,7 @@ RED.sidebar.info = (function() {
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;
@ -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,7 +114,7 @@ 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 {

View File

@ -309,11 +309,11 @@ RED.view = (function() {
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;
} }
@ -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();
} }
@ -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");
} }
} }
} }

View File

@ -56,7 +56,7 @@ 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');
} }
} }
@ -78,120 +78,125 @@ RED.workspaces = (function() {
$( "#node-dialog-rename-workspace" ).dialog("open"); $( "#node-dialog-rename-workspace" ).dialog("open");
} }
var workspace_tabs = RED.tabs.create({ var workspace_tabs;
id: "workspace-tabs", function createWorkspaceTabs(){
onchange: function(tab) { workspace_tabs = RED.tabs.create({
if (tab.type == "subflow") { id: "workspace-tabs",
$("#workspace-toolbar").show(); onchange: function(tab) {
} else { if (tab.type == "subflow") {
$("#workspace-toolbar").hide(); $("#workspace-toolbar").show();
} } else {
var event = { $("#workspace-toolbar").hide();
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);
} }
}); var event = {
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1); old: activeWorkspace
}, }
onremove: function(tab) { activeWorkspace = tab.id;
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1); event.workspace = activeWorkspace;
RED.menu.removeItem("menu-item-workspace-menu-"+tab.id.replace(".","-"));
}
});
$("#node-dialog-rename-workspace form" ).submit(function(e) { e.preventDefault();}); eventHandler.emit("change",event);
$( "#node-dialog-rename-workspace" ).dialog({ },
modal: true, ondblclick: function(tab) {
autoOpen: false, if (tab.type != "subflow") {
width: 500, showRenameWorkspaceDialog(tab.id);
title: "Rename sheet", } else {
buttons: [ RED.editor.editSubflow(RED.nodes.subflow(tab.id));
{
class: 'leftButton',
text: "Delete",
click: function() {
var workspace = $(this).dialog('option','workspace');
$( this ).dialog( "close" );
deleteWorkspace(workspace);
} }
}, },
{ onadd: function(tab) {
text: "Ok", RED.menu.addItem("menu-item-workspace",{
click: function() { id:"menu-item-workspace-menu-"+tab.id.replace(".","-"),
var workspace = $(this).dialog('option','workspace'); label:tab.label,
var label = $( "#node-input-workspace-name" ).val(); onselect:function() {
if (workspace.label != label) { workspace_tabs.activateTab(tab.id);
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" ); });
} RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
}, },
{ onremove: function(tab) {
text: "Cancel", RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
click: function() { RED.menu.removeItem("menu-item-workspace-menu-"+tab.id.replace(".","-"));
$( 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();
}
});
$("#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);
@ -251,7 +256,7 @@ 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);
@ -259,7 +264,7 @@ RED.workspaces = (function() {
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

@ -72,9 +72,9 @@ 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();
@ -137,11 +137,11 @@ RED.user = (function() {
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,7 +154,7 @@ 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();
} }

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() {

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

@ -13,83 +13,26 @@
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">
function ws_oneditprepare() {
$("#websocket-client-row").hide();
$("#node-input-mode").change(function(){
if( $("#node-input-mode").val() === 'client') {
$("#websocket-server-row").hide();
$("#websocket-client-row").show();
}
else {
$("#websocket-server-row").show();
$("#websocket-client-row").hide();
}
});
if(this.client) {
$("#node-input-mode").val('client').change();
}
else {
$("#node-input-mode").val('server').change();
}
}
function ws_oneditsave() {
if($("#node-input-mode").val() === 'client') {
$("#node-input-server").append('<option value="">Dummy</option>');
$("#node-input-server").val('');
}
else {
$("#node-input-client").append('<option value="">Dummy</option>');
$("#node-input-client").val('');
}
}
function ws_label() {
var nodeid = (this.client)?this.client:this.server;
var wsNode = RED.nodes.node(nodeid);
return this.name||(wsNode?"[ws] "+wsNode.label():"websocket");
}
function ws_validateserver() {
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
return true;
}
else {
return RED.nodes.node(this.server) != null;
}
}
function ws_validateclient() {
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
return RED.nodes.node(this.client) != null;
}
else {
return true;
}
}
</script>
<!-- WebSocket Input Node --> <!-- WebSocket Input Node -->
<script type="text/x-red" data-template-name="websocket in"> <script type="text/x-red" data-template-name="websocket in">
<div class="form-row"> <div class="form-row">
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> Type</label> <label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> <span data-i18n="websocket.label.type"></span></label>
<select id="node-input-mode"> <select id="node-input-mode">
<option value="server">Listen on</option> <option value="server" data-i18n="websocket.listenon"></option>
<option value="client">Connect to</option> <option value="client" data-i18n="websocket.connectto"></option>
</select> </select>
</div> </div>
<div class="form-row" id="websocket-server-row"> <div class="form-row" id="websocket-server-row">
<label for="node-input-server"><i class="fa fa-bookmark"></i> Path</label> <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"> <input type="text" id="node-input-server">
</div> </div>
<div class="form-row" id="websocket-client-row"> <div class="form-row" id="websocket-client-row">
<label for="node-input-client"><i class="fa fa-bookmark"></i> URL</label> <label for="node-input-client"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
<input type="text" id="node-input-client"> <input type="text" id="node-input-client">
</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>
@ -101,6 +44,65 @@ function ws_validateclient() {
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
(function() {
function ws_oneditprepare() {
$("#websocket-client-row").hide();
$("#node-input-mode").change(function(){
if( $("#node-input-mode").val() === 'client') {
$("#websocket-server-row").hide();
$("#websocket-client-row").show();
}
else {
$("#websocket-server-row").show();
$("#websocket-client-row").hide();
}
});
if(this.client) {
$("#node-input-mode").val('client').change();
}
else {
$("#node-input-mode").val('server').change();
}
}
function ws_oneditsave() {
if($("#node-input-mode").val() === 'client') {
$("#node-input-server").append('<option value="">Dummy</option>');
$("#node-input-server").val('');
}
else {
$("#node-input-client").append('<option value="">Dummy</option>');
$("#node-input-client").val('');
}
}
function ws_label() {
var nodeid = (this.client)?this.client:this.server;
var wsNode = RED.nodes.node(nodeid);
return this.name||(wsNode?"[ws] "+wsNode.label():"websocket");
}
function ws_validateserver() {
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
return true;
}
else {
return RED.nodes.node(this.server) != null;
}
}
function ws_validateclient() {
if($("#node-input-mode").val() === 'client' || (this.client && !this.server)) {
return RED.nodes.node(this.client) != null;
}
else {
return true;
}
}
RED.nodes.registerType('websocket in',{ RED.nodes.registerType('websocket in',{
category: 'input', category: 'input',
defaults: { defaults: {
@ -119,45 +121,7 @@ function ws_validateclient() {
oneditsave: ws_oneditsave, oneditsave: ws_oneditsave,
oneditprepare: ws_oneditprepare oneditprepare: ws_oneditprepare
}); });
</script>
<!-- WebSocket out Node -->
<script type="text/x-red" data-template-name="websocket out">
<div class="form-row">
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> Type</label>
<select id="node-input-mode">
<option value="server">Listen on</option>
<option value="client">Connect to</option>
</select>
</div>
<div class="form-row" id="websocket-server-row">
<label for="node-input-server"><i class="fa fa-bookmark"></i> Path</label>
<input type="text" id="node-input-server">
</div>
<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">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/x-red" data-help-name="websocket out">
<p>WebSocket out node.</p>
<p>By default, <b>msg.payload</b> will be sent over the WebSocket. The socket
can be configured to encode the entire message object as a JSON string and send that
over the WebSocket.</p>
<p>If the message arriving at this node started at a WebSocket In node, the message
will be sent back to the client that triggered the flow. Otherwise, the message
will be broadcast to all connected clients.</p>
<p>If you want to broadcast a message that started at a WebSocket In node, you
should delete the <b>msg._session</b> property within the flow</p>.
</script>
<script type="text/javascript">
RED.nodes.registerType('websocket out',{ RED.nodes.registerType('websocket out',{
category: 'output', category: 'output',
defaults: { defaults: {
@ -177,33 +141,7 @@ function ws_validateclient() {
oneditsave: ws_oneditsave, oneditsave: ws_oneditsave,
oneditprepare: ws_oneditprepare oneditprepare: ws_oneditprepare
}); });
</script>
<!-- WebSocket Server configuration node -->
<script type="text/x-red" data-template-name="websocket-listener">
<div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> Path</label>
<input type="text" id="node-config-input-path" placeholder="/ws/example">
</div>
<div class="form-row">
<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>
</div>
<div class="form-tips">
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.
<p id="node-config-ws-tip">This path will be relative to <code><span id="node-config-ws-path"></span></code>.</p>
</div>
</script>
<script type="text/x-red" data-help-name="websocket-listener">
<p>This configuration node creates a WebSocket Server using the specified path</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('websocket-listener',{ RED.nodes.registerType('websocket-listener',{
category: 'config', category: 'config',
defaults: { defaults: {
@ -237,33 +175,7 @@ function ws_validateclient() {
} }
} }
}); });
</script>
<!-- WebSocket Client configuration node -->
<script type="text/x-red" data-template-name="websocket-client">
<div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> URL</label>
<input type="text" id="node-config-input-path" placeholder="ws://example.com/ws">
</div>
<div class="form-row">
<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>
</div>
<div class="form-tips">
<p>URL should use ws:&#47;&#47; or wss:&#47;&#47; scheme and point to an existing websocket listener.</p>
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.
</div>
</script>
<script type="text/x-red" data-help-name="websocket-client">
<p>This configuration node connects a WebSocket client to the specified URL.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('websocket-client',{ RED.nodes.registerType('websocket-client',{
category: 'config', category: 'config',
defaults: { defaults: {
@ -276,4 +188,89 @@ function ws_validateclient() {
return this.path; return this.path;
} }
}); });
})();
</script> </script>
<!-- WebSocket out Node -->
<script type="text/x-red" data-template-name="websocket out">
<div class="form-row">
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> <span data-i18n="websocket.label.type"></span></label>
<select id="node-input-mode">
<option value="server" data-i18n="websocket.listenon"></option>
<option value="client" data-i18n="websocket.connectto"></option>
</select>
</div>
<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>
<input type="text" id="node-input-server">
</div>
<div class="form-row" id="websocket-client-row">
<label for="node-input-client"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
<input type="text" id="node-input-client">
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
<script type="text/x-red" data-help-name="websocket out">
<p>WebSocket out node.</p>
<p>By default, <b>msg.payload</b> will be sent over the WebSocket. The socket
can be configured to encode the entire message object as a JSON string and send that
over the WebSocket.</p>
<p>If the message arriving at this node started at a WebSocket In node, the message
will be sent back to the client that triggered the flow. Otherwise, the message
will be broadcast to all connected clients.</p>
<p>If you want to broadcast a message that started at a WebSocket In node, you
should delete the <b>msg._session</b> property within the flow</p>.
</script>
<!-- WebSocket Server configuration node -->
<script type="text/x-red" data-template-name="websocket-listener">
<div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.path"></span></label>
<input type="text" id="node-config-input-path" placeholder="/ws/example">
</div>
<div class="form-row">
<label for="node-config-input-wholemsg">&nbsp;</label>
<select type="text" id="node-config-input-wholemsg" style="width: 70%;">
<option value="false" data-i18n="websocket.payload"></option>
<option value="true" data-i18n="websocket.message"></option>
</select>
</div>
<div class="form-tips">
<span data-i18n="[html]websocket.tip.path1"></span>
<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>
</script>
<script type="text/x-red" data-help-name="websocket-listener">
<p>This configuration node creates a WebSocket Server using the specified path</p>
</script>
<!-- WebSocket Client configuration node -->
<script type="text/x-red" data-template-name="websocket-client">
<div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
<input type="text" id="node-config-input-path" placeholder="ws://example.com/ws">
</div>
<div class="form-row">
<label for="node-config-input-wholemsg">&nbsp;</label>
<select type="text" id="node-config-input-wholemsg" style="width: 70%;">
<option value="false" data-i18n="websocket.payload"></option>
<option value="true" data-i18n="websocket.message"></option>
</select>
</div>
<div class="form-tips">
<p><span data-i18n="[html]websocket.tip.url1"></span></p>
<span data-i18n="[html]websocket.tip.url2"></span>
</div>
</script>
<script type="text/x-red" data-help-name="websocket-client">
<p>This configuration node connects a WebSocket client to the specified URL.</p>
</script>

View File

@ -180,11 +180,12 @@ module.exports = function(RED) {
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);
// TODO: nls
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:"connected "+n}); }); 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.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"error"}); });
this.serverConfig.on('closed', function() { node.status({fill:"red",shape:"ring",text:"disconnected"}); }); this.serverConfig.on('closed', function() { node.status({fill:"red",shape:"ring",text:"disconnected"}); });
} else { } else {
this.error("Missing server configuration"); this.error(RED._("websocket.errors.missing-conf"));
} }
} }
RED.nodes.registerType("websocket in",WebSocketInNode); RED.nodes.registerType("websocket in",WebSocketInNode);
@ -195,9 +196,10 @@ module.exports = function(RED) {
this.server = (n.client)?n.client:n.server; this.server = (n.client)?n.client:n.server;
this.serverConfig = RED.nodes.getNode(this.server); this.serverConfig = RED.nodes.getNode(this.server);
if (!this.serverConfig) { if (!this.serverConfig) {
this.error("Missing server configuration"); this.error(RED._("websocket.errors.missing-conf"));
} }
else { else {
// TODO: nls
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:"connected "+n}); }); 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.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"error"}); });
this.serverConfig.on('closed', function() { node.status({fill:"red",shape:"ring",text:"disconnected"}); }); this.serverConfig.on('closed', function() { node.status({fill:"red",shape:"ring",text:"disconnected"}); });
@ -221,7 +223,7 @@ module.exports = function(RED) {
} else { } else {
node.serverConfig.broadcast(payload,function(error){ node.serverConfig.broadcast(payload,function(error){
if (!!error) { if (!!error) {
node.warn("An error occurred while sending:" + inspect(error)); node.warn(RED._("websocket.errors.send-error")+inspect(error));
} }
}); });
} }

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

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