mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
commit
b2d7f079b7
2
.gitignore
vendored
2
.gitignore
vendored
@ -15,3 +15,5 @@ flows*.json
|
||||
nodes/node-red-nodes/
|
||||
node_modules
|
||||
public
|
||||
locales/zz-ZZ
|
||||
nodes/core/locales/zz-ZZ
|
||||
|
26
Gruntfile.js
26
Gruntfile.js
@ -93,6 +93,7 @@ module.exports = function(grunt) {
|
||||
// Ensure editor source files are concatenated in
|
||||
// the right order
|
||||
"editor/js/main.js",
|
||||
"editor/js/i18n.js",
|
||||
"editor/js/settings.js",
|
||||
"editor/js/user.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/marked/marked.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": [
|
||||
"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: {
|
||||
js: {
|
||||
src: [
|
||||
@ -196,6 +207,14 @@ module.exports = function(grunt) {
|
||||
'editor/sass/**/*.scss'
|
||||
],
|
||||
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',
|
||||
options: {
|
||||
args:['-v'],
|
||||
ext: 'js,html',
|
||||
ext: 'js,html,json',
|
||||
watch: [
|
||||
'red','nodes'
|
||||
]
|
||||
@ -312,6 +331,7 @@ module.exports = function(grunt) {
|
||||
grunt.loadNpmTasks('grunt-contrib-compress');
|
||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||
grunt.loadNpmTasks('grunt-chmod');
|
||||
grunt.loadNpmTasks('grunt-jsonlint');
|
||||
|
||||
grunt.registerMultiTask('attachCopyright', function() {
|
||||
var files = this.data.src;
|
||||
@ -377,7 +397,7 @@ module.exports = function(grunt) {
|
||||
|
||||
grunt.registerTask('build',
|
||||
'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',
|
||||
'Developer mode: run node-red, watch for source changes and build/restart',
|
||||
|
@ -13,16 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
|
||||
RED.comms = (function() {
|
||||
|
||||
|
||||
var errornotification = null;
|
||||
var clearErrorTimer = null;
|
||||
|
||||
|
||||
var subscriptions = {};
|
||||
var ws;
|
||||
var pendingAuth = false;
|
||||
|
||||
|
||||
function connectWS() {
|
||||
var path = location.hostname;
|
||||
var port = location.port;
|
||||
@ -32,10 +32,10 @@ RED.comms = (function() {
|
||||
path = path+document.location.pathname;
|
||||
path = path+(path.slice(-1) == "/"?"":"/")+"comms";
|
||||
path = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path;
|
||||
|
||||
|
||||
var auth_tokens = RED.settings.get("auth-tokens");
|
||||
pendingAuth = (auth_tokens!=null);
|
||||
|
||||
|
||||
function completeConnection() {
|
||||
for (var t in subscriptions) {
|
||||
if (subscriptions.hasOwnProperty(t)) {
|
||||
@ -43,7 +43,7 @@ RED.comms = (function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ws = new WebSocket(path);
|
||||
ws.onopen = function() {
|
||||
if (errornotification) {
|
||||
@ -81,7 +81,7 @@ RED.comms = (function() {
|
||||
};
|
||||
ws.onclose = function() {
|
||||
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) {
|
||||
clearTimeout(clearErrorTimer);
|
||||
clearErrorTimer = null;
|
||||
@ -89,7 +89,7 @@ RED.comms = (function() {
|
||||
setTimeout(connectWS,1000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function subscribe(topic,callback) {
|
||||
if (subscriptions[topic] == null) {
|
||||
subscriptions[topic] = [];
|
||||
@ -99,7 +99,7 @@ RED.comms = (function() {
|
||||
ws.send(JSON.stringify({subscribe:topic}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function unsubscribe(topic,callback) {
|
||||
if (subscriptions[topic]) {
|
||||
for (var i=0;i<subscriptions[topic].length;i++) {
|
||||
@ -113,7 +113,7 @@ RED.comms = (function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
connect: connectWS,
|
||||
subscribe: subscribe,
|
||||
|
45
editor/js/i18n.js
Normal file
45
editor/js/i18n.js
Normal 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);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
|
@ -16,8 +16,8 @@
|
||||
var RED = (function() {
|
||||
|
||||
|
||||
function loadSettings() {
|
||||
RED.settings.init(loadNodeList);
|
||||
function loadLocales() {
|
||||
RED.i18n.init(loadEditor);
|
||||
}
|
||||
|
||||
function loadNodeList() {
|
||||
@ -29,7 +29,23 @@ var RED = (function() {
|
||||
url: 'nodes',
|
||||
success: function(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',
|
||||
success: function(data) {
|
||||
$("body").append(data);
|
||||
$("body").i18n();
|
||||
|
||||
|
||||
$(".palette-spinner").hide();
|
||||
$(".palette-scroll").show();
|
||||
$("#palette-search").show();
|
||||
@ -66,6 +85,9 @@ var RED = (function() {
|
||||
var parts = topic.split("/");
|
||||
var node = RED.nodes.node(parts[1]);
|
||||
if (node) {
|
||||
if (msg.text) {
|
||||
msg.text = node._(msg.text,{defaultValue:msg.text});
|
||||
}
|
||||
node.status = msg;
|
||||
if (statusEnabled) {
|
||||
node.dirty = true;
|
||||
@ -91,7 +113,7 @@ var RED = (function() {
|
||||
}
|
||||
if (addedTypes.length) {
|
||||
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") {
|
||||
for (i=0;i<msg.length;i++) {
|
||||
@ -99,7 +121,7 @@ var RED = (function() {
|
||||
info = RED.nodes.removeNodeSet(m.id);
|
||||
if (info.added) {
|
||||
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") {
|
||||
@ -108,12 +130,12 @@ var RED = (function() {
|
||||
if (info.added) {
|
||||
RED.nodes.enableNodeSet(msg.id);
|
||||
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 {
|
||||
$.get('nodes/'+msg.id, function(data) {
|
||||
$("body").append(data);
|
||||
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) {
|
||||
RED.nodes.disableNodeSet(msg.id);
|
||||
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||
RED.notify("Node"+(msg.types.length!=1 ? "s":"")+" disabled:"+typeList,"success");
|
||||
RED.notify(RED._("palette.event.nodeDisabled", {count:msg.types.length})+typeList,"success");
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -138,42 +160,42 @@ var RED = (function() {
|
||||
function loadEditor() {
|
||||
RED.menu.init({id:"btn-sidemenu",
|
||||
options: [
|
||||
{id:"menu-item-sidebar",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-sidebar",label:RED._("menu.label.sidebar"),toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true},
|
||||
{id:"menu-item-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:toggleStatus, selected: true},
|
||||
null,
|
||||
{id:"menu-item-import",label:"Import",options:[
|
||||
{id:"menu-item-import-clipboard",label:"Clipboard",onselect:RED.clipboard.import},
|
||||
{id:"menu-item-import-library",label:"Library",options:[]}
|
||||
{id:"menu-item-import",label:RED._("menu.label.import"),options:[
|
||||
{id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:RED.clipboard.import},
|
||||
{id:"menu-item-import-library",label:RED._("menu.label.library"),options:[]}
|
||||
]},
|
||||
{id:"menu-item-export",label:"Export",disabled:true,options:[
|
||||
{id:"menu-item-export-clipboard",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",label:RED._("menu.label.export"),disabled:true,options:[
|
||||
{id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),disabled:true,onselect:RED.clipboard.export},
|
||||
{id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:RED.library.export}
|
||||
]},
|
||||
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,
|
||||
{id:"menu-item-subflow",label:"Subflows", options: [
|
||||
{id:"menu-item-subflow-create",label:"Create subflow",onselect:RED.subflow.createSubflow},
|
||||
{id:"menu-item-subflow-convert",label:"Selection to subflow",disabled:true,onselect:RED.subflow.convertToSubflow},
|
||||
{id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [
|
||||
{id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:RED.subflow.createSubflow},
|
||||
{id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:RED.subflow.convertToSubflow},
|
||||
]},
|
||||
null,
|
||||
{id:"menu-item-workspace",label:"Workspaces",options:[
|
||||
{id:"menu-item-workspace-add",label:"Add",onselect:RED.workspaces.add},
|
||||
{id:"menu-item-workspace-edit",label:"Rename",onselect:RED.workspaces.edit},
|
||||
{id:"menu-item-workspace-delete",label:"Delete",onselect:RED.workspaces.remove},
|
||||
{id:"menu-item-workspace",label:RED._("menu.label.workspaces"),options:[
|
||||
{id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:RED.workspaces.add},
|
||||
{id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:RED.workspaces.edit},
|
||||
{id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:RED.workspaces.remove},
|
||||
null
|
||||
]},
|
||||
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",
|
||||
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")
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
RED.user.init();
|
||||
|
||||
|
||||
RED.library.init();
|
||||
RED.palette.init();
|
||||
RED.sidebar.init();
|
||||
@ -181,9 +203,10 @@ var RED = (function() {
|
||||
RED.workspaces.init();
|
||||
RED.clipboard.init();
|
||||
RED.view.init();
|
||||
|
||||
RED.editor.init();
|
||||
|
||||
RED.deploy.init(RED.settings.theme("deployButton",null));
|
||||
|
||||
|
||||
RED.keyboard.add(/* ? */ 191,{shift:true},function(){RED.keyboard.showHelp();d3.event.preventDefault();});
|
||||
RED.comms.connect();
|
||||
|
||||
@ -194,14 +217,14 @@ var RED = (function() {
|
||||
}
|
||||
|
||||
$(function() {
|
||||
|
||||
|
||||
if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) {
|
||||
document.title = document.title+" : "+window.location.hostname;
|
||||
}
|
||||
|
||||
|
||||
ace.require("ace/ext/language_tools");
|
||||
|
||||
RED.settings.init(loadEditor);
|
||||
RED.settings.init(loadLocales);
|
||||
});
|
||||
|
||||
|
||||
|
@ -22,20 +22,20 @@ RED.nodes = (function() {
|
||||
var defaultWorkspace;
|
||||
var workspaces = {};
|
||||
var subflows = {};
|
||||
|
||||
|
||||
var dirty = false;
|
||||
|
||||
|
||||
function setDirty(d) {
|
||||
dirty = d;
|
||||
eventHandler.emit("change",{dirty:dirty});
|
||||
}
|
||||
|
||||
|
||||
var registry = (function() {
|
||||
var nodeList = [];
|
||||
var nodeSets = {};
|
||||
var typeToId = {};
|
||||
var nodeDefinitions = {};
|
||||
|
||||
|
||||
var exports = {
|
||||
getNodeList: function() {
|
||||
return nodeList;
|
||||
@ -107,7 +107,23 @@ RED.nodes = (function() {
|
||||
registerNodeType: function(nt,def) {
|
||||
nodeDefinitions[nt] = def;
|
||||
if (def.category != "subflows") {
|
||||
def.set = nodeSets[typeToId[nt]];
|
||||
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
|
||||
}
|
||||
RED.palette.add(nt,def);
|
||||
@ -117,6 +133,7 @@ RED.nodes = (function() {
|
||||
},
|
||||
removeNodeType: function(nt) {
|
||||
if (nt.substring(0,8) != "subflow:") {
|
||||
// NON-NLS - internal debug message
|
||||
throw new Error("this api is subflow only. called with:",nt);
|
||||
}
|
||||
delete nodeDefinitions[nt];
|
||||
@ -128,12 +145,15 @@ RED.nodes = (function() {
|
||||
};
|
||||
return exports;
|
||||
})();
|
||||
|
||||
|
||||
function getID() {
|
||||
return (1+Math.random()*4294967295).toString(16);
|
||||
}
|
||||
|
||||
function addNode(n) {
|
||||
if (n.type.indexOf("subflow") !== 0) {
|
||||
n["_"] = n._def._;
|
||||
}
|
||||
if (n._def.category == "config") {
|
||||
configNodes[n.id] = n;
|
||||
RED.sidebar.config.refresh();
|
||||
@ -265,7 +285,7 @@ RED.nodes = (function() {
|
||||
var subflowNames = Object.keys(subflows).map(function(sfid) {
|
||||
return subflows[sfid].name;
|
||||
});
|
||||
|
||||
|
||||
subflowNames.sort();
|
||||
var copyNumber = 1;
|
||||
var subflowName = sf.name;
|
||||
@ -277,7 +297,7 @@ RED.nodes = (function() {
|
||||
});
|
||||
sf.name = subflowName;
|
||||
}
|
||||
|
||||
|
||||
subflows[sf.id] = sf;
|
||||
RED.nodes.registerType("subflow:"+sf.id, {
|
||||
defaults:{name:{value:""}},
|
||||
@ -288,10 +308,13 @@ RED.nodes = (function() {
|
||||
color: "#da9",
|
||||
label: function() { return this.name||RED.nodes.subflow(sf.id).name },
|
||||
labelStyle: function() { return this.name?"node_label_italic":""; },
|
||||
paletteLabel: function() { return RED.nodes.subflow(sf.id).name }
|
||||
paletteLabel: function() { return RED.nodes.subflow(sf.id).name },
|
||||
set:{
|
||||
module: "node-red"
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
function getSubflow(id) {
|
||||
return subflows[id];
|
||||
@ -300,7 +323,7 @@ RED.nodes = (function() {
|
||||
delete subflows[sf.id];
|
||||
registry.removeNodeType("subflow:"+sf.id);
|
||||
}
|
||||
|
||||
|
||||
function subflowContains(sfid,nodeid) {
|
||||
for (var i=0;i<nodes.length;i++) {
|
||||
var node = nodes[i];
|
||||
@ -320,7 +343,7 @@ RED.nodes = (function() {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function getAllFlowNodes(node) {
|
||||
var visited = {};
|
||||
visited[node.id] = true;
|
||||
@ -411,7 +434,7 @@ RED.nodes = (function() {
|
||||
node.name = n.name;
|
||||
node.in = [];
|
||||
node.out = [];
|
||||
|
||||
|
||||
n.in.forEach(function(p) {
|
||||
var nIn = {x:p.x,y:p.y,wires:[]};
|
||||
var wires = links.filter(function(d) { return d.source === p });
|
||||
@ -435,8 +458,8 @@ RED.nodes = (function() {
|
||||
}
|
||||
node.out.push(nOut);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
return node;
|
||||
}
|
||||
/**
|
||||
@ -527,7 +550,7 @@ RED.nodes = (function() {
|
||||
try {
|
||||
newNodes = JSON.parse(newNodesObj);
|
||||
} catch(err) {
|
||||
var e = new Error("Invalid flow: "+err.message);
|
||||
var e = new Error(RED._("clipboard.invalidFlow",{message:err.message}));
|
||||
e.code = "NODE_RED";
|
||||
throw e;
|
||||
}
|
||||
@ -542,21 +565,19 @@ RED.nodes = (function() {
|
||||
for (i=0;i<newNodes.length;i++) {
|
||||
n = newNodes[i];
|
||||
// TODO: remove workspace in next release+1
|
||||
if (n.type != "workspace" &&
|
||||
n.type != "tab" &&
|
||||
if (n.type != "workspace" &&
|
||||
n.type != "tab" &&
|
||||
n.type != "subflow" &&
|
||||
!registry.getNodeType(n.type) &&
|
||||
n.type.substring(0,8) != "subflow:" &&
|
||||
unknownTypes.indexOf(n.type)==-1) {
|
||||
|
||||
unknownTypes.push(n.type);
|
||||
}
|
||||
}
|
||||
if (unknownTypes.length > 0) {
|
||||
var typeList = "<ul><li>"+unknownTypes.join("</li><li>")+"</li></ul>";
|
||||
var type = "type"+(unknownTypes.length > 1?"s":"");
|
||||
RED.notify("<strong>Imported unrecognised "+type+":</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");
|
||||
RED.notify("<strong>"+RED._("clipboard.importUnrecognised",{count:unknownTypes.length})+"</strong>"+typeList,"error",false,10000);
|
||||
}
|
||||
|
||||
var activeWorkspace = RED.workspaces.active();
|
||||
@ -568,21 +589,20 @@ RED.nodes = (function() {
|
||||
var subflowId = m[1];
|
||||
var err;
|
||||
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)) {
|
||||
err = new Error("Cannot add subflow - circular reference detected");
|
||||
err = new Error(RED._("notification.errors.cannotAddCircularReference"));
|
||||
}
|
||||
if (err) {
|
||||
// TODO: standardise error codes
|
||||
err.code = "NODE_RED";
|
||||
throw err;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var new_workspaces = [];
|
||||
var workspace_map = {};
|
||||
var new_subflows = [];
|
||||
@ -704,11 +724,13 @@ RED.nodes = (function() {
|
||||
defaults: {},
|
||||
label: "unknown: "+n.type,
|
||||
labelStyle: "node_label_italic",
|
||||
outputs: n.outputs||n.wires.length
|
||||
outputs: n.outputs||n.wires.length,
|
||||
set: registry.getNodeSet("node-red/unknown")
|
||||
}
|
||||
} else {
|
||||
node._def = {
|
||||
category:"config"
|
||||
category:"config",
|
||||
set: registry.getNodeSet("node-red/unknown")
|
||||
};
|
||||
node.users = [];
|
||||
}
|
||||
@ -779,14 +801,14 @@ RED.nodes = (function() {
|
||||
delete output.wires;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return [new_nodes,new_links,new_workspaces,new_subflows];
|
||||
}
|
||||
|
||||
|
||||
// TODO: supports filter.z|type
|
||||
function filterNodes(filter) {
|
||||
var result = [];
|
||||
|
||||
|
||||
for (var n=0;n<nodes.length;n++) {
|
||||
var node = nodes[n];
|
||||
if (filter.hasOwnProperty("z") && node.z !== filter.z) {
|
||||
@ -801,7 +823,7 @@ RED.nodes = (function() {
|
||||
}
|
||||
function filterLinks(filter) {
|
||||
var result = [];
|
||||
|
||||
|
||||
for (var n=0;n<links.length;n++) {
|
||||
var link = links[n];
|
||||
if (filter.source) {
|
||||
@ -827,11 +849,11 @@ RED.nodes = (function() {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// TODO: DRY
|
||||
var eventHandler = (function() {
|
||||
var handlers = {};
|
||||
|
||||
|
||||
return {
|
||||
on: function(evt,func) {
|
||||
handlers[evt] = handlers[evt]||[];
|
||||
@ -842,43 +864,43 @@ RED.nodes = (function() {
|
||||
for (var i=0;i<handlers[evt].length;i++) {
|
||||
handlers[evt][i](arg);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
return {
|
||||
on: eventHandler.on,
|
||||
|
||||
|
||||
registry:registry,
|
||||
setNodeList: registry.setNodeList,
|
||||
|
||||
|
||||
getNodeSet: registry.getNodeSet,
|
||||
addNodeSet: registry.addNodeSet,
|
||||
removeNodeSet: registry.removeNodeSet,
|
||||
enableNodeSet: registry.enableNodeSet,
|
||||
disableNodeSet: registry.disableNodeSet,
|
||||
|
||||
|
||||
registerType: registry.registerNodeType,
|
||||
getType: registry.getNodeType,
|
||||
convertNode: convertNode,
|
||||
|
||||
|
||||
add: addNode,
|
||||
remove: removeNode,
|
||||
|
||||
|
||||
addLink: addLink,
|
||||
removeLink: removeLink,
|
||||
|
||||
|
||||
addWorkspace: addWorkspace,
|
||||
removeWorkspace: removeWorkspace,
|
||||
workspace: getWorkspace,
|
||||
|
||||
|
||||
addSubflow: addSubflow,
|
||||
removeSubflow: removeSubflow,
|
||||
subflow: getSubflow,
|
||||
subflowContains: subflowContains,
|
||||
|
||||
|
||||
eachNode: function(cb) {
|
||||
for (var n=0;n<nodes.length;n++) {
|
||||
cb(nodes[n]);
|
||||
@ -903,14 +925,14 @@ RED.nodes = (function() {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
node: getNode,
|
||||
|
||||
|
||||
filterNodes: filterNodes,
|
||||
filterLinks: filterLinks,
|
||||
|
||||
|
||||
import: importNodes,
|
||||
|
||||
|
||||
getAllFlowNodes: getAllFlowNodes,
|
||||
createExportableNodeSet: createExportableNodeSet,
|
||||
createCompleteNodeSet: createCompleteNodeSet,
|
||||
|
@ -16,62 +16,71 @@
|
||||
|
||||
|
||||
RED.clipboard = (function() {
|
||||
|
||||
var dialog = $('<div id="clipboard-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
|
||||
.appendTo("body")
|
||||
.dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 500,
|
||||
resizable: false,
|
||||
buttons: [
|
||||
{
|
||||
id: "clipboard-dialog-ok",
|
||||
text: "Ok",
|
||||
click: function() {
|
||||
if (/Import/.test(dialog.dialog("option","title"))) {
|
||||
RED.view.importNodes($("#clipboard-import").val());
|
||||
}
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "clipboard-dialog-cancel",
|
||||
text: "Cancel",
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "clipboard-dialog-close",
|
||||
text: "Close",
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
],
|
||||
open: function(e) {
|
||||
$(this).parent().find(".ui-dialog-titlebar-close").hide();
|
||||
RED.keyboard.disable();
|
||||
},
|
||||
close: function(e) {
|
||||
RED.keyboard.enable();
|
||||
}
|
||||
});
|
||||
|
||||
var dialogContainer = dialog.children(".dialog-form");
|
||||
|
||||
var exportNodesDialog = '<div class="form-row">'+
|
||||
'<label for="node-input-export" style="display: block; width:100%;"><i class="fa fa-clipboard"></i> Nodes:</label>'+
|
||||
'<textarea readonly style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-export" rows="5"></textarea>'+
|
||||
'</div>'+
|
||||
'<div class="form-tips">'+
|
||||
'Select the text above and copy to the clipboard with Ctrl-C.'+
|
||||
'</div>';
|
||||
|
||||
var 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>'+
|
||||
'</div>';
|
||||
var dialog;
|
||||
var dialogContainer;
|
||||
var exportNodesDialog;
|
||||
var importNodesDialog;
|
||||
|
||||
function setupDialogs(){
|
||||
dialog = $('<div id="clipboard-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
|
||||
.appendTo("body")
|
||||
.dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 500,
|
||||
resizable: false,
|
||||
buttons: [
|
||||
{
|
||||
id: "clipboard-dialog-ok",
|
||||
text: RED._("common.label.ok"),
|
||||
click: function() {
|
||||
if (/Import/.test(dialog.dialog("option","title"))) {
|
||||
RED.view.importNodes($("#clipboard-import").val());
|
||||
}
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "clipboard-dialog-cancel",
|
||||
text: RED._("common.label.cancel"),
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "clipboard-dialog-close",
|
||||
text: RED._("common.label.close"),
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
],
|
||||
open: function(e) {
|
||||
$(this).parent().find(".ui-dialog-titlebar-close").hide();
|
||||
RED.keyboard.disable();
|
||||
},
|
||||
close: function(e) {
|
||||
RED.keyboard.enable();
|
||||
}
|
||||
});
|
||||
|
||||
dialogContainer = dialog.children(".dialog-form");
|
||||
|
||||
exportNodesDialog = '<div class="form-row">'+
|
||||
'<label for="node-input-export" style="display: block; width:100%;"><i class="fa fa-clipboard"></i> '+RED._("clipboard.nodes")+'</label>'+
|
||||
'<textarea readonly style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-export" rows="5"></textarea>'+
|
||||
'</div>'+
|
||||
'<div class="form-tips">'+
|
||||
RED._("clipboard.selectNodes")+
|
||||
'</div>';
|
||||
|
||||
importNodesDialog = '<div class="form-row">'+
|
||||
'<textarea style="resize: none; width: 100%; border-radius: 0px;font-family: monospace; font-size: 12px; background:#eee; padding-left: 0.5em; box-sizing:border-box;" id="clipboard-import" rows="5" placeholder="'+
|
||||
RED._("clipboard.pasteNodes")+
|
||||
'"></textarea>'+
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function validateImport() {
|
||||
var importInput = $("#clipboard-import");
|
||||
@ -87,7 +96,7 @@ RED.clipboard = (function() {
|
||||
$("#clipboard-dialog-ok").button("disable");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function importNodes() {
|
||||
dialogContainer.empty();
|
||||
dialogContainer.append($(importNodesDialog));
|
||||
@ -97,8 +106,8 @@ RED.clipboard = (function() {
|
||||
$("#clipboard-dialog-ok").button("disable");
|
||||
$("#clipboard-import").keyup(validateImport);
|
||||
$("#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() {
|
||||
@ -120,17 +129,18 @@ RED.clipboard = (function() {
|
||||
return false;
|
||||
})
|
||||
});
|
||||
dialog.dialog("option","title","Export nodes to clipboard").dialog( "open" );
|
||||
dialog.dialog("option","title",RED._("clipboard.exportNodes")).dialog( "open" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function hideDropTarget() {
|
||||
$("#dropTarget").hide();
|
||||
RED.keyboard.remove(/* ESCAPE */ 27);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
setupDialogs();
|
||||
RED.view.on("selection-changed",function(selection) {
|
||||
if (!selection.nodes) {
|
||||
RED.menu.setDisabled("menu-item-export",true);
|
||||
@ -144,16 +154,16 @@ RED.clipboard = (function() {
|
||||
});
|
||||
RED.keyboard.add(/* e */ 69,{ctrl:true},function(){exportNodes();d3.event.preventDefault();});
|
||||
RED.keyboard.add(/* i */ 73,{ctrl:true},function(){importNodes();d3.event.preventDefault();});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
$('#chart').on("dragenter",function(event) {
|
||||
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
|
||||
$("#dropTarget").css({display:'table'});
|
||||
RED.keyboard.add(/* ESCAPE */ 27,hideDropTarget);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$('#dropTarget').on("dragover",function(event) {
|
||||
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
|
||||
event.preventDefault();
|
||||
@ -168,15 +178,15 @@ RED.clipboard = (function() {
|
||||
RED.view.importNodes(data);
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
},
|
||||
import: importNodes,
|
||||
export: exportNodes
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
})();
|
||||
|
@ -13,29 +13,29 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
|
||||
RED.deploy = (function() {
|
||||
|
||||
|
||||
var deploymentTypes = {
|
||||
"full":{img:"red/images/deploy-full-o.png"},
|
||||
"nodes":{img:"red/images/deploy-nodes-o.png"},
|
||||
"flows":{img:"red/images/deploy-flows-o.png"}
|
||||
}
|
||||
|
||||
|
||||
var ignoreDeployWarnings = {
|
||||
unknown: false,
|
||||
unusedConfig: false,
|
||||
invalid: false
|
||||
}
|
||||
|
||||
|
||||
var deploymentType = "full";
|
||||
|
||||
|
||||
function changeDeploymentType(type) {
|
||||
deploymentType = type;
|
||||
$("#btn-deploy img").attr("src",deploymentTypes[type].img);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* options:
|
||||
* type: "default" - Button with drop-down options - no further customisation available
|
||||
@ -46,35 +46,35 @@ RED.deploy = (function() {
|
||||
function init(options) {
|
||||
options = options || {};
|
||||
var type = options.type || "default";
|
||||
|
||||
|
||||
if (type == "default") {
|
||||
$('<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>'+
|
||||
'</span></li>').prependTo(".header-toolbar");
|
||||
RED.menu.init({id:"btn-deploy-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-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-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-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: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:RED._("deploy.modifiedNodes"),sublabel:RED._("deploy.modifiedNodesDesc"),onselect:function(s) { if(s){changeDeploymentType("nodes")}}}
|
||||
]
|
||||
});
|
||||
} else if (type == "simple") {
|
||||
var label = options.label || "Deploy";
|
||||
var label = options.label || RED._("deploy.deploy");
|
||||
var icon = 'red/images/deploy-full-o.png';
|
||||
if (options.hasOwnProperty('icon')) {
|
||||
icon = options.icon;
|
||||
}
|
||||
|
||||
|
||||
$('<li><span class="deploy-button-group button-group">'+
|
||||
'<a id="btn-deploy" class="deploy-button disabled" href="#">'+
|
||||
(icon?'<img id="btn-deploy-icon" src="'+icon+'"> ':'')+
|
||||
'<span>'+label+'</span></a>'+
|
||||
'</span></li>').prependTo(".header-toolbar");
|
||||
}
|
||||
|
||||
|
||||
$('#btn-deploy').click(function() { save(); });
|
||||
|
||||
|
||||
$( "#node-dialog-confirm-deploy" ).dialog({
|
||||
title: "Confirm deploy",
|
||||
modal: true,
|
||||
@ -83,9 +83,9 @@ RED.deploy = (function() {
|
||||
height: "auto",
|
||||
buttons: [
|
||||
{
|
||||
text: "Confirm deploy",
|
||||
text: RED._("deploy.confirm.button.confirm"),
|
||||
click: function() {
|
||||
|
||||
|
||||
var ignoreChecked = $( "#node-dialog-confirm-deploy-hide" ).prop("checked");
|
||||
if (ignoreChecked) {
|
||||
ignoreDeployWarnings[$( "#node-dialog-confirm-deploy-type" ).val()] = true;
|
||||
@ -95,7 +95,7 @@ RED.deploy = (function() {
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "Cancel",
|
||||
text: RED._("deploy.confirm.button.cancel"),
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
@ -114,7 +114,7 @@ RED.deploy = (function() {
|
||||
RED.nodes.on('change',function(state) {
|
||||
if (state.dirty) {
|
||||
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");
|
||||
} else {
|
||||
@ -132,7 +132,7 @@ RED.deploy = (function() {
|
||||
var hasUnknown = false;
|
||||
var hasInvalid = false;
|
||||
var hasUnusedConfig = false;
|
||||
|
||||
|
||||
var unknownNodes = [];
|
||||
RED.nodes.eachNode(function(node) {
|
||||
hasInvalid = hasInvalid || !node.valid;
|
||||
@ -143,7 +143,7 @@ RED.deploy = (function() {
|
||||
}
|
||||
});
|
||||
hasUnknown = unknownNodes.length > 0;
|
||||
|
||||
|
||||
var unusedConfigNodes = {};
|
||||
RED.nodes.eachConfig(function(node) {
|
||||
if (node.users.length === 0) {
|
||||
@ -159,13 +159,13 @@ RED.deploy = (function() {
|
||||
hasUnusedConfig = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$( "#node-dialog-confirm-deploy-config" ).hide();
|
||||
$( "#node-dialog-confirm-deploy-unknown" ).hide();
|
||||
$( "#node-dialog-confirm-deploy-unused" ).hide();
|
||||
|
||||
|
||||
var showWarning = false;
|
||||
|
||||
|
||||
if (hasUnknown && !ignoreDeployWarnings.unknown) {
|
||||
showWarning = true;
|
||||
$( "#node-dialog-confirm-deploy-type" ).val("unknown");
|
||||
@ -186,7 +186,7 @@ RED.deploy = (function() {
|
||||
unusedConfigNodes[type].forEach(function(label) {
|
||||
unusedNodeLabels.push(type+": "+label);
|
||||
});
|
||||
});
|
||||
});
|
||||
$( "#node-dialog-confirm-deploy-unused-list" )
|
||||
.html("<li>"+unusedNodeLabels.join("</li><li>")+"</li>");
|
||||
}
|
||||
@ -196,10 +196,10 @@ RED.deploy = (function() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var nns = RED.nodes.createCompleteNodeSet();
|
||||
|
||||
$("#btn-deploy-icon").removeClass('fa-download');
|
||||
@ -215,7 +215,7 @@ RED.deploy = (function() {
|
||||
"Node-RED-Deployment-Type":deploymentType
|
||||
}
|
||||
}).done(function(data,textStatus,xhr) {
|
||||
RED.notify("Successfully deployed","success");
|
||||
RED.notify(RED._("deploy.successfulDeploy"),"success");
|
||||
RED.nodes.eachNode(function(node) {
|
||||
if (node.changed) {
|
||||
node.dirty = true;
|
||||
@ -236,9 +236,9 @@ RED.deploy = (function() {
|
||||
}).fail(function(xhr,textStatus,err) {
|
||||
RED.nodes.dirty(true);
|
||||
if (xhr.responseText) {
|
||||
RED.notify("<strong>Error</strong>: "+xhr.responseJSON.message,"error");
|
||||
RED.notify(RED._("notification.error",{message:xhr.responseJSON.message}),"error");
|
||||
} else {
|
||||
RED.notify("<strong>Error</strong>: no response from server","error");
|
||||
RED.notify(RED._("notification.error",{message:RED._("deploy.errors.noResponse")}),"error");
|
||||
}
|
||||
}).always(function() {
|
||||
$("#btn-deploy-icon").removeClass('spinner');
|
||||
|
@ -168,184 +168,186 @@ RED.editor = (function() {
|
||||
return removedLinks;
|
||||
}
|
||||
|
||||
$( "#dialog" ).dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
dialogClass: "ui-dialog-no-close",
|
||||
closeOnEscape: false,
|
||||
minWidth: 500,
|
||||
width: 'auto',
|
||||
buttons: [
|
||||
{
|
||||
id: "node-dialog-ok",
|
||||
text: "Ok",
|
||||
click: function() {
|
||||
if (editing_node) {
|
||||
var changes = {};
|
||||
var changed = false;
|
||||
var wasDirty = RED.nodes.dirty();
|
||||
var d;
|
||||
function createDialog(){
|
||||
$( "#dialog" ).dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
dialogClass: "ui-dialog-no-close",
|
||||
closeOnEscape: false,
|
||||
minWidth: 500,
|
||||
width: 'auto',
|
||||
buttons: [
|
||||
{
|
||||
id: "node-dialog-ok",
|
||||
text: RED._("common.label.ok"),
|
||||
click: function() {
|
||||
if (editing_node) {
|
||||
var changes = {};
|
||||
var changed = false;
|
||||
var wasDirty = RED.nodes.dirty();
|
||||
var d;
|
||||
|
||||
if (editing_node._def.oneditsave) {
|
||||
var oldValues = {};
|
||||
for (d in editing_node._def.defaults) {
|
||||
if (editing_node._def.defaults.hasOwnProperty(d)) {
|
||||
if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") {
|
||||
oldValues[d] = editing_node[d];
|
||||
} else {
|
||||
oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
|
||||
if (editing_node._def.oneditsave) {
|
||||
var oldValues = {};
|
||||
for (d in editing_node._def.defaults) {
|
||||
if (editing_node._def.defaults.hasOwnProperty(d)) {
|
||||
if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") {
|
||||
oldValues[d] = editing_node[d];
|
||||
} else {
|
||||
oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var rc = editing_node._def.oneditsave.call(editing_node);
|
||||
if (rc === true) {
|
||||
changed = true;
|
||||
}
|
||||
var rc = editing_node._def.oneditsave.call(editing_node);
|
||||
if (rc === true) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
for (d in editing_node._def.defaults) {
|
||||
if (editing_node._def.defaults.hasOwnProperty(d)) {
|
||||
if (oldValues[d] === null || typeof oldValues[d] === "string" || typeof oldValues[d] === "number") {
|
||||
if (oldValues[d] !== editing_node[d]) {
|
||||
changes[d] = oldValues[d];
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
if (JSON.stringify(oldValues[d]) !== JSON.stringify(editing_node[d])) {
|
||||
changes[d] = oldValues[d];
|
||||
changed = true;
|
||||
for (d in editing_node._def.defaults) {
|
||||
if (editing_node._def.defaults.hasOwnProperty(d)) {
|
||||
if (oldValues[d] === null || typeof oldValues[d] === "string" || typeof oldValues[d] === "number") {
|
||||
if (oldValues[d] !== editing_node[d]) {
|
||||
changes[d] = oldValues[d];
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
if (JSON.stringify(oldValues[d]) !== JSON.stringify(editing_node[d])) {
|
||||
changes[d] = oldValues[d];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (editing_node._def.defaults) {
|
||||
for (d in editing_node._def.defaults) {
|
||||
if (editing_node._def.defaults.hasOwnProperty(d)) {
|
||||
var input = $("#node-input-"+d);
|
||||
var newValue;
|
||||
if (input.attr('type') === "checkbox") {
|
||||
newValue = input.prop('checked');
|
||||
} else {
|
||||
newValue = input.val();
|
||||
}
|
||||
if (newValue != null) {
|
||||
if (editing_node[d] != newValue) {
|
||||
if (editing_node._def.defaults) {
|
||||
for (d in editing_node._def.defaults) {
|
||||
if (editing_node._def.defaults.hasOwnProperty(d)) {
|
||||
var input = $("#node-input-"+d);
|
||||
var newValue;
|
||||
if (input.attr('type') === "checkbox") {
|
||||
newValue = input.prop('checked');
|
||||
} else {
|
||||
newValue = input.val();
|
||||
}
|
||||
if (newValue != null) {
|
||||
if (d === "outputs" && (newValue.trim() === "" || isNaN(newValue))) {
|
||||
continue;
|
||||
}
|
||||
if (editing_node._def.defaults[d].type) {
|
||||
if (newValue == "_ADD_") {
|
||||
newValue = "";
|
||||
}
|
||||
// Change to a related config node
|
||||
var configNode = RED.nodes.node(editing_node[d]);
|
||||
if (configNode) {
|
||||
var users = configNode.users;
|
||||
users.splice(users.indexOf(editing_node),1);
|
||||
}
|
||||
configNode = RED.nodes.node(newValue);
|
||||
if (configNode) {
|
||||
configNode.users.push(editing_node);
|
||||
if (editing_node[d] != newValue) {
|
||||
if (editing_node._def.defaults[d].type) {
|
||||
if (newValue == "_ADD_") {
|
||||
newValue = "";
|
||||
}
|
||||
// Change to a related config node
|
||||
var configNode = RED.nodes.node(editing_node[d]);
|
||||
if (configNode) {
|
||||
var users = configNode.users;
|
||||
users.splice(users.indexOf(editing_node),1);
|
||||
}
|
||||
configNode = RED.nodes.node(newValue);
|
||||
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) {
|
||||
var prefix = 'node-input';
|
||||
var credDefinition = editing_node._def.credentials;
|
||||
var credsChanged = updateNodeCredentials(editing_node,credDefinition,prefix);
|
||||
changed = changed || credsChanged;
|
||||
}
|
||||
if (editing_node._def.credentials) {
|
||||
var prefix = 'node-input';
|
||||
var credDefinition = editing_node._def.credentials;
|
||||
var credsChanged = updateNodeCredentials(editing_node,credDefinition,prefix);
|
||||
changed = changed || credsChanged;
|
||||
}
|
||||
|
||||
var removedLinks = updateNodeProperties(editing_node);
|
||||
if (changed) {
|
||||
var wasChanged = editing_node.changed;
|
||||
editing_node.changed = true;
|
||||
RED.nodes.dirty(true);
|
||||
RED.history.push({t:'edit',node:editing_node,changes:changes,links:removedLinks,dirty:wasDirty,changed:wasChanged});
|
||||
}
|
||||
editing_node.dirty = true;
|
||||
validateNode(editing_node);
|
||||
RED.view.redraw();
|
||||
} else if (/Export nodes to library/.test($( "#dialog" ).dialog("option","title"))) {
|
||||
//TODO: move this to RED.library
|
||||
var flowName = $("#node-input-filename").val();
|
||||
if (!/^\s*$/.test(flowName)) {
|
||||
$.ajax({
|
||||
url:'library/flows/'+flowName,
|
||||
type: "POST",
|
||||
data: $("#node-input-filename").attr('nodes'),
|
||||
contentType: "application/json; charset=utf-8"
|
||||
}).done(function() {
|
||||
RED.library.loadFlowLibrary();
|
||||
RED.notify("Saved nodes","success");
|
||||
});
|
||||
var removedLinks = updateNodeProperties(editing_node);
|
||||
if (changed) {
|
||||
var wasChanged = editing_node.changed;
|
||||
editing_node.changed = true;
|
||||
RED.nodes.dirty(true);
|
||||
RED.history.push({t:'edit',node:editing_node,changes:changes,links:removedLinks,dirty:wasDirty,changed:wasChanged});
|
||||
}
|
||||
editing_node.dirty = true;
|
||||
validateNode(editing_node);
|
||||
RED.view.redraw();
|
||||
} else if (/Export nodes to library/.test($( "#dialog" ).dialog("option","title"))) {
|
||||
//TODO: move this to RED.library
|
||||
var flowName = $("#node-input-filename").val();
|
||||
if (!/^\s*$/.test(flowName)) {
|
||||
$.ajax({
|
||||
url:'library/flows/'+flowName,
|
||||
type: "POST",
|
||||
data: $("#node-input-filename").attr('nodes'),
|
||||
contentType: "application/json; charset=utf-8"
|
||||
}).done(function() {
|
||||
RED.library.loadFlowLibrary();
|
||||
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);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "node-dialog-cancel",
|
||||
text: "Cancel",
|
||||
click: function() {
|
||||
if (editing_node && editing_node._def) {
|
||||
if (editing_node._def.oneditcancel) {
|
||||
editing_node._def.oneditcancel.call(editing_node);
|
||||
}
|
||||
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( "close" );
|
||||
}
|
||||
}
|
||||
],
|
||||
resize: function(e,ui) {
|
||||
if (editing_node) {
|
||||
$(this).dialog('option',"sizeCache-"+editing_node.type,ui.size);
|
||||
}
|
||||
},
|
||||
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);
|
||||
},
|
||||
close: function(e) {
|
||||
RED.keyboard.enable();
|
||||
|
||||
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
|
||||
RED.view.state(RED.state.DEFAULT);
|
||||
}
|
||||
}
|
||||
},
|
||||
close: function(e) {
|
||||
RED.keyboard.enable();
|
||||
$( this ).dialog('option','height','auto');
|
||||
$( this ).dialog('option','width','auto');
|
||||
if (editing_node) {
|
||||
RED.sidebar.info.refresh(editing_node);
|
||||
}
|
||||
RED.sidebar.config.refresh();
|
||||
|
||||
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
|
||||
RED.view.state(RED.state.DEFAULT);
|
||||
var buttons = $( this ).dialog("option","buttons");
|
||||
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
|
||||
@ -392,9 +394,9 @@ RED.editor = (function() {
|
||||
input.after(button);
|
||||
|
||||
if (node[property]) {
|
||||
button.text("edit");
|
||||
button.text(RED._("editor.configEdit"));
|
||||
} else {
|
||||
button.text("add");
|
||||
button.text(RED._("editor.configAdd"));
|
||||
}
|
||||
|
||||
button.click(function(e) {
|
||||
@ -565,7 +567,7 @@ RED.editor = (function() {
|
||||
var buttons = $( "#dialog" ).dialog("option","buttons");
|
||||
buttons.unshift({
|
||||
class: 'leftButton',
|
||||
text: "Edit flow",
|
||||
text: RED._("subflow.edit"),
|
||||
click: function() {
|
||||
RED.workspaces.show(id);
|
||||
$("#node-dialog-ok").click();
|
||||
@ -574,21 +576,44 @@ RED.editor = (function() {
|
||||
$( "#dialog" ).dialog("option","buttons",buttons);
|
||||
}
|
||||
$("#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");
|
||||
prepareEditDialog(node,node._def,"node-input");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
$("#dialog").i18n();
|
||||
$( "#dialog" ).dialog("option","title","Edit "+type+" node").dialog( "open" );
|
||||
}
|
||||
|
||||
function showEditConfigNodeDialog(name,type,id) {
|
||||
var adding = (id == "_ADD_");
|
||||
var node_def = RED.nodes.getType(type);
|
||||
|
||||
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) {
|
||||
configNode = {
|
||||
id: (1+Math.random()*4294967295).toString(16),
|
||||
@ -600,9 +625,25 @@ RED.editor = (function() {
|
||||
configNode[d] = node_def.defaults[d].value;
|
||||
}
|
||||
}
|
||||
configNode["_"] = node_def._;
|
||||
}
|
||||
|
||||
$("#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");
|
||||
|
||||
var buttons = $( "#node-config-dialog" ).dialog("option","buttons");
|
||||
@ -616,7 +657,7 @@ RED.editor = (function() {
|
||||
if (buttons.length == 2) {
|
||||
buttons.unshift({
|
||||
class: 'leftButton',
|
||||
text: "Delete",
|
||||
text: RED._("editor.configDelete"),
|
||||
click: function() {
|
||||
var configProperty = $(this).dialog('option','node-property');
|
||||
var configId = $(this).dialog('option','node-id');
|
||||
@ -648,16 +689,18 @@ RED.editor = (function() {
|
||||
});
|
||||
}
|
||||
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").i18n();
|
||||
|
||||
$( "#node-config-dialog" )
|
||||
.dialog("option","node-adding",adding)
|
||||
.dialog("option","node-property",name)
|
||||
.dialog("option","node-id",configNode.id)
|
||||
.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" );
|
||||
}
|
||||
|
||||
@ -665,9 +708,9 @@ RED.editor = (function() {
|
||||
var button = $("#node-input-edit-"+name);
|
||||
if (button.length) {
|
||||
if (value) {
|
||||
button.text("edit");
|
||||
button.text(RED._("editor.configEdit"));
|
||||
} else {
|
||||
button.text("add");
|
||||
button.text(RED._("editor.configAdd"));
|
||||
}
|
||||
$("#node-input-"+name).val(value);
|
||||
} else {
|
||||
@ -686,208 +729,211 @@ RED.editor = (function() {
|
||||
select.append('<option value="'+config.id+'"'+(value==config.id?" selected":"")+'>'+label+'</option>');
|
||||
}
|
||||
});
|
||||
|
||||
select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>Add new '+type+'...</option>');
|
||||
select.append('<option value="_ADD_"'+(value===""?" selected":"")+'>'+RED._("editor.addNewType", {type:type})+'</option>');
|
||||
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,
|
||||
autoOpen: false,
|
||||
dialogClass: "ui-dialog-no-close",
|
||||
closeOnEscape: false,
|
||||
minWidth: 500,
|
||||
width: 'auto',
|
||||
closeOnEscape: false,
|
||||
buttons: [
|
||||
{
|
||||
id: "node-config-dialog-ok",
|
||||
text: "Ok",
|
||||
id: "subflow-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 (editing_node) {
|
||||
var i;
|
||||
var changes = {};
|
||||
var changed = false;
|
||||
var wasDirty = RED.nodes.dirty();
|
||||
|
||||
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();
|
||||
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(RED._("subflow.tabLabel",{name:newName}));
|
||||
}
|
||||
|
||||
RED.palette.refresh();
|
||||
|
||||
if (changed) {
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.type == "subflow:"+editing_node.id) {
|
||||
n.changed = true;
|
||||
updateNodeProperties(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
});
|
||||
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.nodes.dirty(true);
|
||||
$(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);
|
||||
}
|
||||
RED.history.push(historyEvent);
|
||||
}
|
||||
editing_node.dirty = true;
|
||||
RED.view.redraw();
|
||||
}
|
||||
$( 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) {
|
||||
RED.keyboard.disable();
|
||||
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.keyboard.enable();
|
||||
|
||||
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
|
||||
RED.view.state(RED.state.DEFAULT);
|
||||
}
|
||||
RED.sidebar.config.refresh();
|
||||
RED.sidebar.info.refresh(editing_node);
|
||||
editing_node = null;
|
||||
}
|
||||
});
|
||||
|
||||
$( "#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();});
|
||||
});
|
||||
$("#subflow-dialog form" ).submit(function(e) { e.preventDefault();});
|
||||
}
|
||||
|
||||
|
||||
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").dialog("option","title","Edit flow "+subflow.name).dialog( "open" );
|
||||
$("#subflow-dialog-user-count").html(RED._("subflow.subflowInstances", {count:userCount})).show();
|
||||
$("#subflow-dialog").dialog("option","title",RED._("subflow.editSubflow",{name:subflow.name})).dialog( "open" );
|
||||
}
|
||||
|
||||
|
||||
|
||||
return {
|
||||
init: function(){
|
||||
createDialog();
|
||||
createNodeConfigDialog();
|
||||
createSubflowDialog();
|
||||
},
|
||||
edit: showEditDialog,
|
||||
editConfig: showEditConfigNodeDialog,
|
||||
editSubflow: showEditSubflowDialog,
|
||||
|
@ -69,24 +69,24 @@ RED.keyboard = (function() {
|
||||
dialog = $('<div id="keyboard-help-dialog" class="hide">'+
|
||||
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
|
||||
'<table class="keyboard-shortcuts">'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">a</span></td><td>Select all nodes</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">Ctrl/⌘</span> + <span class="help-key">Click</span></td><td>Add/remove node from selection</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">Ctrl/⌘</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>'+RED._("keyboard.selectAllConnected")+'</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">Click</span></td><td>'+RED._("keyboard.addRemoveNode")+'</td></tr>'+
|
||||
'<tr><td><span class="help-key">Delete</span></td><td>'+RED._("keyboard.deleteSelected")+'</td></tr>'+
|
||||
'<tr><td> </td><td></td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">i</span></td><td>Import nodes</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">e</span></td><td>Export selected nodes</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">i</span></td><td>'+RED._("keyboard.importNode")+'</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">e</span></td><td>'+RED._("keyboard.exportNode")+'</td></tr>'+
|
||||
'</table>'+
|
||||
'</div>'+
|
||||
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
|
||||
'<table class="keyboard-shortcuts">'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">Space</span></td><td>Toggle sidebar</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">Space</span></td><td>'+RED._("keyboard.toggleSidebar")+'</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><span class="help-key">Ctrl/⌘</span> + <span class="help-key">c</span></td><td>Copy selected nodes</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">x</span></td><td>Cut selected nodes</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">v</span></td><td>Paste nodes</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">c</span></td><td>'+RED._("keyboard.copyNode")+'</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">x</span></td><td>'+RED._("keyboard.cutNode")+'</td></tr>'+
|
||||
'<tr><td><span class="help-key">Ctrl/⌘</span> + <span class="help-key">v</span></td><td>'+RED._("keyboard.pasteNode")+'</td></tr>'+
|
||||
'</table>'+
|
||||
'</div>'+
|
||||
'</div>')
|
||||
|
@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
RED.library = (function() {
|
||||
|
||||
|
||||
|
||||
|
||||
function loadFlowLibrary() {
|
||||
$.getJSON("library/flows",function(data) {
|
||||
//console.log(data);
|
||||
@ -66,12 +66,12 @@ RED.library = (function() {
|
||||
$("#menu-item-import-library-submenu").replaceWith(menu);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function createUI(options) {
|
||||
var libraryData = {};
|
||||
var selectedLibraryItem = null;
|
||||
var libraryEditor = null;
|
||||
|
||||
|
||||
// Orion editor has set/getText
|
||||
// ACE editor has set/getValue
|
||||
// normalise to set/getValue
|
||||
@ -84,14 +84,14 @@ RED.library = (function() {
|
||||
if (options.editor.getText) {
|
||||
options.editor.getValue = options.editor.getText;
|
||||
}
|
||||
|
||||
|
||||
function buildFileListItem(item) {
|
||||
var li = document.createElement("li");
|
||||
li.onmouseover = function(e) { $(this).addClass("list-hover"); };
|
||||
li.onmouseout = function(e) { $(this).removeClass("list-hover"); };
|
||||
return li;
|
||||
}
|
||||
|
||||
|
||||
function buildFileList(root,data) {
|
||||
var ul = document.createElement("ul");
|
||||
var li;
|
||||
@ -104,7 +104,7 @@ RED.library = (function() {
|
||||
var dirName = v;
|
||||
return function(e) {
|
||||
var bcli = $('<li class="active"><span class="divider">/</span> <a href="#">'+dirName+'</a></li>');
|
||||
$("a",bcli).click(function(e) {
|
||||
$("a",bcli).click(function(e) {
|
||||
$(this).parent().nextAll().remove();
|
||||
$.getJSON("library/"+options.url+root+dirName,function(data) {
|
||||
$("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data));
|
||||
@ -141,24 +141,24 @@ RED.library = (function() {
|
||||
}
|
||||
return ul;
|
||||
}
|
||||
|
||||
|
||||
$('#node-input-name').addClass('input-append-left').css("width","65%").after(
|
||||
'<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>'+
|
||||
'<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-save-library" tabindex="-1" href="#">Save to 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="#">'+RED._("library.saveToLibrary")+'</a></li>'+
|
||||
'</ul></div>'
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
$('#node-input-'+options.type+'-menu-open-library').click(function(e) {
|
||||
$("#node-select-library").children().remove();
|
||||
var bc = $("#node-dialog-library-breadcrumbs");
|
||||
bc.children().first().nextAll().remove();
|
||||
libraryEditor.setValue('',-1);
|
||||
|
||||
|
||||
$.getJSON("library/"+options.url,function(data) {
|
||||
$("#node-select-library").append(buildFileList("/",data));
|
||||
$("#node-dialog-library-breadcrumbs a").click(function(e) {
|
||||
@ -168,10 +168,10 @@ RED.library = (function() {
|
||||
});
|
||||
$( "#node-dialog-library-lookup" ).dialog( "open" );
|
||||
});
|
||||
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
|
||||
$('#node-input-'+options.type+'-menu-save-library').click(function(e) {
|
||||
//var found = false;
|
||||
var name = $("#node-input-name").val().replace(/(^\s*)|(\s*$)/g,"");
|
||||
@ -217,7 +217,7 @@ RED.library = (function() {
|
||||
$( "#node-dialog-library-save" ).dialog( "open" );
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
|
||||
libraryEditor = ace.edit('node-select-library-text');
|
||||
libraryEditor.setTheme("ace/theme/tomorrow");
|
||||
if (options.mode) {
|
||||
@ -230,16 +230,16 @@ RED.library = (function() {
|
||||
});
|
||||
libraryEditor.renderer.$cursorLayer.element.style.opacity=0;
|
||||
libraryEditor.$blockScrolling = Infinity;
|
||||
|
||||
|
||||
$( "#node-dialog-library-lookup" ).dialog({
|
||||
title: options.type+" library",
|
||||
title: RED._("library.typeLibrary", {type:options.type}),
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 800,
|
||||
height: 450,
|
||||
buttons: [
|
||||
{
|
||||
text: "Ok",
|
||||
text: RED._("common.label.ok"),
|
||||
click: function() {
|
||||
if (selectedLibraryItem) {
|
||||
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() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
@ -270,16 +270,16 @@ RED.library = (function() {
|
||||
$(".form-row:last-child",form).children().height(form.height()-60);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function saveToLibrary(overwrite) {
|
||||
var name = $("#node-input-name").val().replace(/(^\s*)|(\s*$)/g,"");
|
||||
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 pathname = $("#node-dialog-library-save-folder").val().replace(/(^\s*)|(\s*$)/g,"");
|
||||
if (filename === "" || !/.+\.js$/.test(filename)) {
|
||||
RED.notify("Invalid filename","warning");
|
||||
RED.notify(RED._("library.invalidFilename"),"warning");
|
||||
return;
|
||||
}
|
||||
var fullpath = pathname+(pathname===""?"":"/")+filename;
|
||||
@ -304,8 +304,7 @@ RED.library = (function() {
|
||||
// }
|
||||
//}
|
||||
//if (exists) {
|
||||
// $("#node-dialog-library-save-type").html(options.type);
|
||||
// $("#node-dialog-library-save-name").html(fullpath);
|
||||
// $("#node-dialog-library-save-content").html(RED._("library.dialogSaveOverwrite",{libraryType:options.type,libraryName:fullpath}));
|
||||
// $("#node-dialog-library-save-confirm").dialog( "open" );
|
||||
// return;
|
||||
//}
|
||||
@ -320,7 +319,7 @@ RED.library = (function() {
|
||||
data[field] = $("#node-input-"+field).val();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data.text = options.editor.getValue();
|
||||
$.ajax({
|
||||
url:"library/"+options.url+'/'+fullpath,
|
||||
@ -328,27 +327,27 @@ RED.library = (function() {
|
||||
data: JSON.stringify(data),
|
||||
contentType: "application/json; charset=utf-8"
|
||||
}).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) {
|
||||
RED.notify("Saved failed: "+xhr.responseJSON.message,"error");
|
||||
RED.notify(RED._("library.saveFailed",{message:xhr.responseJSON.message}),"error");
|
||||
});
|
||||
}
|
||||
$( "#node-dialog-library-save-confirm" ).dialog({
|
||||
title: "Save to library",
|
||||
title: RED._("library.saveToLibrary"),
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 530,
|
||||
height: 230,
|
||||
buttons: [
|
||||
{
|
||||
text: "Ok",
|
||||
text: RED._("common.label.ok"),
|
||||
click: function() {
|
||||
saveToLibrary(true);
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "Cancel",
|
||||
text: RED._("common.label.cancel"),
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
@ -356,21 +355,21 @@ RED.library = (function() {
|
||||
]
|
||||
});
|
||||
$( "#node-dialog-library-save" ).dialog({
|
||||
title: "Save to library",
|
||||
title: RED._("library.saveToLibrary"),
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 530,
|
||||
height: 230,
|
||||
buttons: [
|
||||
{
|
||||
text: "Ok",
|
||||
text: RED._("common.label.ok"),
|
||||
click: function() {
|
||||
saveToLibrary(false);
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "Cancel",
|
||||
text: RED._("common.label.cancel"),
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
@ -379,15 +378,16 @@ RED.library = (function() {
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
function exportFlow() {
|
||||
//TODO: don't rely on the main dialog
|
||||
var nns = RED.nodes.createExportableNodeSet(RED.view.selection().nodes);
|
||||
$("#dialog-form").html($("script[data-template-name='export-library-dialog']").html());
|
||||
$("#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 {
|
||||
init: function() {
|
||||
RED.view.on("selection-changed",function(selection) {
|
||||
@ -401,16 +401,14 @@ RED.library = (function() {
|
||||
RED.menu.setDisabled("menu-item-export-library",false);
|
||||
}
|
||||
});
|
||||
|
||||
if (RED.settings.theme("menu.menu-item-import-library") !== false) {
|
||||
|
||||
if (RED.settings.theme("menu.menu-item-import-library") !== false) {
|
||||
loadFlowLibrary();
|
||||
}
|
||||
},
|
||||
create: createUI,
|
||||
loadFlowLibrary: loadFlowLibrary,
|
||||
|
||||
|
||||
export: exportFlow
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
|
@ -19,10 +19,10 @@ RED.palette = (function() {
|
||||
var exclusion = ['config','unknown','deprecated'];
|
||||
var core = ['subflows', 'input', 'output', 'function', 'social', 'storage', 'analysis', 'advanced'];
|
||||
|
||||
function createCategoryContainer(category){
|
||||
var escapedCategory = category.replace(" ","_");
|
||||
function createCategoryContainer(category, label){
|
||||
label = label || category.replace("_", " ");
|
||||
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 id="palette-'+category+'-input"></div>'+
|
||||
'<div id="palette-'+category+'-output"></div>'+
|
||||
@ -77,16 +77,17 @@ RED.palette = (function() {
|
||||
if (label != type) {
|
||||
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) {
|
||||
return this.nodeType == 1 || (this.nodeType == 3 && this.textContent.trim().length > 0)
|
||||
}).slice(0,2);
|
||||
} catch(err) {
|
||||
// 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());
|
||||
popOverContent = "<p><b>"+label+"</b></p><p>no information available</p>";
|
||||
popOverContent = "<p><b>"+label+"</b></p><p>"+RED._("palette.noInfo")+"</p>";
|
||||
}
|
||||
|
||||
|
||||
@ -120,12 +121,12 @@ RED.palette = (function() {
|
||||
label = (typeof def.paletteLabel === "function" ? def.paletteLabel.call(def) : def.paletteLabel)||"";
|
||||
}
|
||||
|
||||
|
||||
|
||||
$('<div/>',{class:"palette_label"+(def.align=="right"?" palette_label_right":"")}).appendTo(d);
|
||||
|
||||
d.className="palette_node";
|
||||
|
||||
|
||||
|
||||
|
||||
if (def.icon) {
|
||||
var icon_url = (typeof def.icon === "function" ? def.icon.call({}) : def.icon);
|
||||
var iconContainer = $('<div/>',{class:"palette_icon_container"+(def.align=="right"?" palette_icon_container_right":"")}).appendTo(d);
|
||||
@ -147,7 +148,12 @@ RED.palette = (function() {
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@ -178,7 +184,7 @@ RED.palette = (function() {
|
||||
revertDuration: 50,
|
||||
start: function() {RED.view.focus();}
|
||||
});
|
||||
|
||||
|
||||
if (def.category == "subflows") {
|
||||
$(d).dblclick(function(e) {
|
||||
RED.workspaces.show(nt.substring(8));
|
||||
@ -187,7 +193,7 @@ RED.palette = (function() {
|
||||
}
|
||||
|
||||
setLabel(nt,$(d),label);
|
||||
|
||||
|
||||
var categoryNode = $("#palette-container-"+category);
|
||||
if (categoryNode.find(".palette_node").length === 1) {
|
||||
if (!categoryNode.find("i").hasClass("expanded")) {
|
||||
@ -195,7 +201,7 @@ RED.palette = (function() {
|
||||
categoryNode.find("i").toggleClass("expanded");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,30 +274,34 @@ RED.palette = (function() {
|
||||
function init() {
|
||||
$(".palette-spinner").show();
|
||||
if (RED.settings.paletteCategories) {
|
||||
RED.settings.paletteCategories.forEach(createCategoryContainer);
|
||||
RED.settings.paletteCategories.forEach(function(category){
|
||||
createCategoryContainer(category, RED._("palette.label."+category,{defaultValue:category}));
|
||||
});
|
||||
} else {
|
||||
core.forEach(createCategoryContainer);
|
||||
core.forEach(function(category){
|
||||
createCategoryContainer(category, RED._("palette.label."+category,{defaultValue:category}));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$("#palette-search-input").focus(function(e) {
|
||||
RED.keyboard.disable();
|
||||
});
|
||||
$("#palette-search-input").blur(function(e) {
|
||||
RED.keyboard.enable();
|
||||
});
|
||||
|
||||
|
||||
$("#palette-search-clear").on("click",function(e) {
|
||||
e.preventDefault();
|
||||
$("#palette-search-input").val("");
|
||||
filterChange();
|
||||
$("#palette-search-input").focus();
|
||||
});
|
||||
|
||||
|
||||
$("#palette-search-input").val("");
|
||||
$("#palette-search-input").on("keyup",function() {
|
||||
filterChange();
|
||||
});
|
||||
|
||||
|
||||
$("#palette-search-input").on("focus",function() {
|
||||
$("body").one("mousedown",function() {
|
||||
$("#palette-search-input").blur();
|
||||
|
@ -30,7 +30,8 @@ RED.sidebar = (function() {
|
||||
function addTab(title,content,closeable) {
|
||||
$("#sidebar-content").append(content);
|
||||
$(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";
|
||||
//$('#sidebar').tabs("refresh");
|
||||
}
|
||||
|
@ -15,12 +15,12 @@
|
||||
**/
|
||||
|
||||
RED.subflow = (function() {
|
||||
|
||||
|
||||
|
||||
function getSubflow() {
|
||||
return RED.nodes.subflow(RED.workspaces.active());
|
||||
}
|
||||
|
||||
|
||||
function findAvailableSubflowIOPosition(subflow) {
|
||||
var pos = {x:70,y:70};
|
||||
for (var i=0;i<subflow.out.length+subflow.in.length;i++) {
|
||||
@ -37,7 +37,7 @@ RED.subflow = (function() {
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
function addSubflowInput() {
|
||||
var subflow = RED.nodes.subflow(RED.workspaces.active());
|
||||
var position = findAvailableSubflowIOPosition(subflow);
|
||||
@ -56,7 +56,7 @@ RED.subflow = (function() {
|
||||
var wasDirty = RED.nodes.dirty();
|
||||
var wasChanged = subflow.changed;
|
||||
subflow.changed = true;
|
||||
|
||||
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.type == "subflow:"+subflow.id) {
|
||||
n.changed = true;
|
||||
@ -77,11 +77,11 @@ RED.subflow = (function() {
|
||||
$("#workspace-subflow-add-input").toggleClass("disabled",true);
|
||||
RED.view.select();
|
||||
}
|
||||
|
||||
|
||||
function addSubflowOutput(id) {
|
||||
var subflow = RED.nodes.subflow(RED.workspaces.active());
|
||||
var position = findAvailableSubflowIOPosition(subflow);
|
||||
|
||||
|
||||
var newOutput = {
|
||||
type:"subflow",
|
||||
direction:"out",
|
||||
@ -97,7 +97,7 @@ RED.subflow = (function() {
|
||||
var wasDirty = RED.nodes.dirty();
|
||||
var wasChanged = subflow.changed;
|
||||
subflow.changed = true;
|
||||
|
||||
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.type == "subflow:"+subflow.id) {
|
||||
n.changed = true;
|
||||
@ -117,7 +117,7 @@ RED.subflow = (function() {
|
||||
RED.history.push(historyEvent);
|
||||
RED.view.select();
|
||||
}
|
||||
|
||||
|
||||
function init() {
|
||||
$("#workspace-subflow-edit").click(function(event) {
|
||||
RED.editor.editSubflow(RED.nodes.subflow(RED.workspaces.active()));
|
||||
@ -137,13 +137,13 @@ RED.subflow = (function() {
|
||||
}
|
||||
addSubflowOutput();
|
||||
});
|
||||
|
||||
|
||||
$("#workspace-subflow-delete").click(function(event) {
|
||||
event.preventDefault();
|
||||
var removedNodes = [];
|
||||
var removedLinks = [];
|
||||
var startDirty = RED.nodes.dirty();
|
||||
|
||||
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.type == "subflow:"+getSubflow().id) {
|
||||
removedNodes.push(n);
|
||||
@ -152,16 +152,16 @@ RED.subflow = (function() {
|
||||
removedNodes.push(n);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
for (var i=0;i<removedNodes.length;i++) {
|
||||
var rmlinks = RED.nodes.remove(removedNodes[i].id);
|
||||
removedLinks = removedLinks.concat(rmlinks);
|
||||
}
|
||||
|
||||
|
||||
var activeSubflow = getSubflow();
|
||||
|
||||
|
||||
RED.nodes.removeSubflow(activeSubflow);
|
||||
|
||||
|
||||
RED.history.push({
|
||||
t:'delete',
|
||||
nodes:removedNodes,
|
||||
@ -169,12 +169,12 @@ RED.subflow = (function() {
|
||||
subflow: activeSubflow,
|
||||
dirty:startDirty
|
||||
});
|
||||
|
||||
|
||||
RED.workspaces.remove(activeSubflow);
|
||||
RED.nodes.dirty(true);
|
||||
RED.view.redraw();
|
||||
});
|
||||
|
||||
|
||||
RED.view.on("selection-changed",function(selection) {
|
||||
if (!selection.nodes) {
|
||||
RED.menu.setDisabled("menu-item-subflow-convert",true);
|
||||
@ -182,9 +182,9 @@ RED.subflow = (function() {
|
||||
RED.menu.setDisabled("menu-item-subflow-convert",false);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
function createSubflow() {
|
||||
var lastIndex = 0;
|
||||
RED.nodes.eachSubflow(function(sf) {
|
||||
@ -193,9 +193,9 @@ RED.subflow = (function() {
|
||||
lastIndex = Math.max(lastIndex,m[1]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var name = "Subflow "+(lastIndex+1);
|
||||
|
||||
|
||||
var subflowId = RED.nodes.id();
|
||||
var subflow = {
|
||||
type:"subflow",
|
||||
@ -212,26 +212,26 @@ RED.subflow = (function() {
|
||||
});
|
||||
RED.workspaces.show(subflowId);
|
||||
}
|
||||
|
||||
|
||||
function convertToSubflow() {
|
||||
var selection = RED.view.selection();
|
||||
if (!selection.nodes) {
|
||||
RED.notify("<strong>Cannot create subflow</strong>: no nodes selected","error");
|
||||
RED.notify(RED._("subflow.errors.noNodesSelected"),"error");
|
||||
return;
|
||||
}
|
||||
var i;
|
||||
var nodes = {};
|
||||
var new_links = [];
|
||||
var removedLinks = [];
|
||||
|
||||
|
||||
var candidateInputs = [];
|
||||
var candidateOutputs = [];
|
||||
|
||||
|
||||
var boundingBox = [selection.nodes[0].x,
|
||||
selection.nodes[0].y,
|
||||
selection.nodes[0].x,
|
||||
selection.nodes[0].y];
|
||||
|
||||
|
||||
for (i=0;i<selection.nodes.length;i++) {
|
||||
var n = selection.nodes[i];
|
||||
nodes[n.id] = {n:n,outputs:{}};
|
||||
@ -242,14 +242,14 @@ RED.subflow = (function() {
|
||||
Math.max(boundingBox[3],n.y)
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
var center = [(boundingBox[2]+boundingBox[0]) / 2,(boundingBox[3]+boundingBox[1]) / 2];
|
||||
|
||||
|
||||
RED.nodes.eachLink(function(link) {
|
||||
if (nodes[link.source.id] && nodes[link.target.id]) {
|
||||
// A link wholely within the selection
|
||||
}
|
||||
|
||||
|
||||
if (nodes[link.source.id] && !nodes[link.target.id]) {
|
||||
// An outbound link from the selection
|
||||
candidateOutputs.push(link);
|
||||
@ -261,7 +261,7 @@ RED.subflow = (function() {
|
||||
removedLinks.push(link);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var outputs = {};
|
||||
candidateOutputs = candidateOutputs.filter(function(v) {
|
||||
if (outputs[v.source.id+":"+v.sourcePort]) {
|
||||
@ -274,17 +274,17 @@ RED.subflow = (function() {
|
||||
return true;
|
||||
});
|
||||
candidateOutputs.sort(function(a,b) { return a.source.y-b.source.y});
|
||||
|
||||
|
||||
if (candidateInputs.length > 1) {
|
||||
RED.notify("<strong>Cannot create subflow</strong>: multiple inputs to selection","error");
|
||||
RED.notify(RED._("subflow.errors.multipleInputsToSelection"),"error");
|
||||
return;
|
||||
}
|
||||
//if (candidateInputs.length == 0) {
|
||||
// RED.notify("<strong>Cannot create subflow</strong>: no input to selection","error");
|
||||
// return;
|
||||
//}
|
||||
|
||||
|
||||
|
||||
|
||||
var lastIndex = 0;
|
||||
RED.nodes.eachSubflow(function(sf) {
|
||||
var m = (new RegExp("^Subflow (\\d+)$")).exec(sf.name);
|
||||
@ -292,9 +292,9 @@ RED.subflow = (function() {
|
||||
lastIndex = Math.max(lastIndex,m[1]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var name = "Subflow "+(lastIndex+1);
|
||||
|
||||
|
||||
var subflowId = RED.nodes.id();
|
||||
var subflow = {
|
||||
type:"subflow",
|
||||
@ -322,7 +322,7 @@ RED.subflow = (function() {
|
||||
}})
|
||||
};
|
||||
RED.nodes.addSubflow(subflow);
|
||||
|
||||
|
||||
var subflowInstance = {
|
||||
id:RED.nodes.id(),
|
||||
type:"subflow:"+subflow.id,
|
||||
@ -337,13 +337,13 @@ RED.subflow = (function() {
|
||||
subflowInstance._def = RED.nodes.getType(subflowInstance.type);
|
||||
RED.editor.validateNode(subflowInstance);
|
||||
RED.nodes.add(subflowInstance);
|
||||
|
||||
|
||||
candidateInputs.forEach(function(l) {
|
||||
var link = {source:l.source, sourcePort:l.sourcePort, target: subflowInstance};
|
||||
new_links.push(link);
|
||||
RED.nodes.addLink(link);
|
||||
});
|
||||
|
||||
|
||||
candidateOutputs.forEach(function(output,i) {
|
||||
output.targets.forEach(function(target) {
|
||||
var link = {source:subflowInstance, sourcePort:i, target: target};
|
||||
@ -351,7 +351,7 @@ RED.subflow = (function() {
|
||||
RED.nodes.addLink(link);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
subflow.in.forEach(function(input) {
|
||||
input.wires.forEach(function(wire) {
|
||||
var link = {source: input, sourcePort: 0, target: RED.nodes.node(wire.id) }
|
||||
@ -366,34 +366,34 @@ RED.subflow = (function() {
|
||||
RED.nodes.addLink(link);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
for (i=0;i<removedLinks.length;i++) {
|
||||
RED.nodes.removeLink(removedLinks[i]);
|
||||
}
|
||||
|
||||
|
||||
for (i=0;i<selection.nodes.length;i++) {
|
||||
selection.nodes[i].z = subflow.id;
|
||||
}
|
||||
|
||||
|
||||
RED.history.push({
|
||||
t:'createSubflow',
|
||||
nodes:[subflowInstance.id],
|
||||
links:new_links,
|
||||
subflow: subflow,
|
||||
|
||||
|
||||
activeWorkspace: RED.workspaces.active(),
|
||||
removedLinks: removedLinks,
|
||||
|
||||
|
||||
dirty:RED.nodes.dirty()
|
||||
});
|
||||
|
||||
|
||||
RED.editor.validateNode(subflow);
|
||||
RED.nodes.dirty(true);
|
||||
RED.view.redraw(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return {
|
||||
init: init,
|
||||
createSubflow: createSubflow,
|
||||
|
@ -14,23 +14,23 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
RED.sidebar.config = (function() {
|
||||
|
||||
|
||||
var content = document.createElement("div");
|
||||
content.id = "tab-config";
|
||||
content.style.paddingTop = "4px";
|
||||
content.style.paddingLeft = "4px";
|
||||
content.style.paddingRight = "4px";
|
||||
|
||||
|
||||
var list = $("<ul>",{class:"tab-config-list"}).appendTo(content);
|
||||
|
||||
|
||||
function show() {
|
||||
if (!RED.sidebar.containsTab("config")) {
|
||||
RED.sidebar.addTab("config",content,true);
|
||||
RED.sidebar.addTab(RED._("sidebar.config.title"),content,true);
|
||||
}
|
||||
refresh();
|
||||
RED.sidebar.show("config");
|
||||
}
|
||||
|
||||
|
||||
function refresh() {
|
||||
list.empty();
|
||||
RED.nodes.eachConfig(function(node) {
|
||||
@ -46,12 +46,12 @@ RED.sidebar.config = (function() {
|
||||
label = node._def.label;
|
||||
}
|
||||
label = label || " ";
|
||||
|
||||
|
||||
var entry = $('<div class="tab-config-list-entry"></div>').appendTo(li);
|
||||
entry.on('dblclick',function(e) {
|
||||
RED.editor.editConfig("", node.type, node.id);
|
||||
});
|
||||
|
||||
|
||||
var userArray = node.users.map(function(n) { return n.id });
|
||||
entry.on('mouseover',function(e) {
|
||||
RED.nodes.eachNode(function(node) {
|
||||
@ -72,7 +72,7 @@ RED.sidebar.config = (function() {
|
||||
});
|
||||
RED.view.redraw();
|
||||
});
|
||||
|
||||
|
||||
$('<div class="tab-config-list-label">'+label+'</div>').appendTo(entry);
|
||||
$('<div class="tab-config-list-users">'+node.users.length+'</div>').appendTo(entry);
|
||||
});
|
||||
|
@ -33,10 +33,10 @@ RED.sidebar.info = (function() {
|
||||
content.style.paddingRight = "4px";
|
||||
|
||||
var propertiesExpanded = false;
|
||||
|
||||
|
||||
function show() {
|
||||
if (!RED.sidebar.containsTab("info")) {
|
||||
RED.sidebar.addTab("info",content,false);
|
||||
RED.sidebar.addTab(RED._("sidebar.info.title"),content,false);
|
||||
}
|
||||
RED.sidebar.show("info");
|
||||
}
|
||||
@ -60,13 +60,13 @@ RED.sidebar.info = (function() {
|
||||
|
||||
function refresh(node) {
|
||||
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) {
|
||||
table += "<tr><td>Name</td><td> "+node.name+"</td></tr>";
|
||||
table += "<tr><td>"+RED._("common.label.name")+"</td><td> "+node.name+"</td></tr>";
|
||||
}
|
||||
table += "<tr><td>Type</td><td> "+node.type+"</td></tr>";
|
||||
table += "<tr><td>ID</td><td> "+node.id+"</td></tr>";
|
||||
|
||||
table += "<tr><td>"+RED._("sidebar.info.type")+"</td><td> "+node.type+"</td></tr>";
|
||||
table += "<tr><td>"+RED._("sidebar.info.id")+"</td><td> "+node.id+"</td></tr>";
|
||||
|
||||
var m = /^subflow(:(.+))?$/.exec(node.type);
|
||||
if (m) {
|
||||
var subflowNode;
|
||||
@ -75,9 +75,9 @@ RED.sidebar.info = (function() {
|
||||
} else {
|
||||
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 subflowType = "subflow:"+subflowNode.id;
|
||||
RED.nodes.eachNode(function(n) {
|
||||
@ -85,12 +85,12 @@ RED.sidebar.info = (function() {
|
||||
userCount++;
|
||||
}
|
||||
});
|
||||
table += "<tr><td>name</td><td>"+subflowNode.name+"</td></tr>";
|
||||
table += "<tr><td>instances</td><td>"+userCount+"</td></tr>";
|
||||
table += "<tr><td>"+RED._("common.label.name")+"</td><td>"+subflowNode.name+"</td></tr>";
|
||||
table += "<tr><td>"+RED._("sidebar.info.instances")+"</td><td>"+userCount+"</td></tr>";
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
for (var n in node._def.defaults) {
|
||||
if (n != "name" && node._def.defaults.hasOwnProperty(n)) {
|
||||
@ -98,7 +98,7 @@ RED.sidebar.info = (function() {
|
||||
var type = typeof val;
|
||||
if (type === "string") {
|
||||
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 {
|
||||
if (val.length > 30) {
|
||||
val = val.substring(0,30)+" ...";
|
||||
@ -114,14 +114,14 @@ RED.sidebar.info = (function() {
|
||||
val += " "+i+": "+vv+"<br/>";
|
||||
}
|
||||
if (node[n].length > 10) {
|
||||
val += " ... "+node[n].length+" items<br/>";
|
||||
val += " ... "+RED._("sidebar.info.arrayItems",{count:node[n].length})+"<br/>";
|
||||
}
|
||||
val += "]";
|
||||
} else {
|
||||
val = JSON.stringify(val,jsonFilter," ");
|
||||
val = val.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
}
|
||||
|
||||
|
||||
table += '<tr class="node-info-property-row'+(propertiesExpanded?"":" hide")+'"><td>'+n+"</td><td>"+val+"</td></tr>";
|
||||
}
|
||||
}
|
||||
@ -140,7 +140,7 @@ RED.sidebar.info = (function() {
|
||||
}
|
||||
|
||||
$("#tab-info").html(table);
|
||||
|
||||
|
||||
$(".node-info-property-header").click(function(e) {
|
||||
var icon = $(this).find("i");
|
||||
if (icon.hasClass("fa-caret-right")) {
|
||||
@ -154,15 +154,15 @@ RED.sidebar.info = (function() {
|
||||
$(".node-info-property-row").hide();
|
||||
propertiesExpanded = false;
|
||||
}
|
||||
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function clear() {
|
||||
$("#tab-info").html("");
|
||||
}
|
||||
|
||||
|
||||
RED.view.on("selection-changed",function(selection) {
|
||||
if (selection.nodes) {
|
||||
if (selection.nodes.length == 1) {
|
||||
|
@ -35,7 +35,7 @@ RED.view = (function() {
|
||||
var activeSubflow = null;
|
||||
var activeNodes = [];
|
||||
var activeLinks = [];
|
||||
|
||||
|
||||
var selected_link = null,
|
||||
mousedown_link = null,
|
||||
mousedown_node = null,
|
||||
@ -73,7 +73,7 @@ RED.view = (function() {
|
||||
.on("mousedown", function() {
|
||||
$(this).focus();
|
||||
});
|
||||
|
||||
|
||||
var vis = outer
|
||||
.append('svg:g')
|
||||
.on("dblclick.zoom", null)
|
||||
@ -233,9 +233,9 @@ RED.view = (function() {
|
||||
|
||||
function updateActiveNodes() {
|
||||
var activeWorkspace = RED.workspaces.active();
|
||||
|
||||
|
||||
activeNodes = RED.nodes.filterNodes({z:activeWorkspace});
|
||||
|
||||
|
||||
activeLinks = RED.nodes.filterLinks({
|
||||
source:{z:activeWorkspace},
|
||||
target:{z:activeWorkspace}
|
||||
@ -253,15 +253,15 @@ RED.view = (function() {
|
||||
}
|
||||
var scrollStartLeft = chart.scrollLeft();
|
||||
var scrollStartTop = chart.scrollTop();
|
||||
|
||||
|
||||
activeSubflow = RED.nodes.subflow(event.workspace);
|
||||
if (activeSubflow) {
|
||||
$("#workspace-subflow-add-input").toggleClass("disabled",activeSubflow.in.length > 0);
|
||||
}
|
||||
|
||||
|
||||
RED.menu.setDisabled("menu-item-workspace-edit", activeSubflow);
|
||||
RED.menu.setDisabled("menu-item-workspace-delete",RED.workspaces.count() == 1 || activeSubflow);
|
||||
|
||||
|
||||
if (workspaceScrollPositions[event.workspace]) {
|
||||
chart.scrollLeft(workspaceScrollPositions[event.workspace].left);
|
||||
chart.scrollTop(workspaceScrollPositions[event.workspace].top);
|
||||
@ -283,7 +283,7 @@ RED.view = (function() {
|
||||
updateActiveNodes();
|
||||
redraw();
|
||||
});
|
||||
|
||||
|
||||
$('#btn-zoom-out').click(function() {zoomOut();});
|
||||
$('#btn-zoom-zero').click(function() {zoomZero();});
|
||||
$('#btn-zoom-in').click(function() {zoomIn();});
|
||||
@ -296,50 +296,50 @@ RED.view = (function() {
|
||||
else { zoomIn(); }
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Handle nodes dragged from the palette
|
||||
$("#chart").droppable({
|
||||
accept:".palette_node",
|
||||
drop: function( event, ui ) {
|
||||
d3.event = event;
|
||||
var selected_tool = ui.draggable[0].type;
|
||||
|
||||
|
||||
var m = /^subflow:(.+)$/.exec(selected_tool);
|
||||
|
||||
|
||||
if (activeSubflow && m) {
|
||||
var subflowId = m[1];
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
var mousePos = d3.touches(this)[0]||d3.mouse(this);
|
||||
mousePos[1] += this.scrollTop;
|
||||
mousePos[0] += this.scrollLeft;
|
||||
mousePos[1] /= scaleFactor;
|
||||
mousePos[0] /= scaleFactor;
|
||||
|
||||
|
||||
var nn = { id:(1+Math.random()*4294967295).toString(16),x: mousePos[0],y:mousePos[1],w:node_width,z:RED.workspaces.active()};
|
||||
|
||||
|
||||
nn.type = selected_tool;
|
||||
nn._def = RED.nodes.getType(nn.type);
|
||||
|
||||
|
||||
if (!m) {
|
||||
nn.inputs = nn._def.inputs || 0;
|
||||
nn.outputs = nn._def.outputs;
|
||||
|
||||
|
||||
for (var d in nn._def.defaults) {
|
||||
if (nn._def.defaults.hasOwnProperty(d)) {
|
||||
nn[d] = nn._def.defaults[d].value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (nn._def.onadd) {
|
||||
nn._def.onadd.call(nn);
|
||||
}
|
||||
@ -348,7 +348,7 @@ RED.view = (function() {
|
||||
nn.inputs = subflow.in.length;
|
||||
nn.outputs = subflow.out.length;
|
||||
}
|
||||
|
||||
|
||||
nn.changed = true;
|
||||
nn.h = Math.max(node_height,(nn.outputs||0) * 15);
|
||||
RED.history.push({t:'add',nodes:[nn.id],dirty:RED.nodes.dirty()});
|
||||
@ -362,13 +362,13 @@ RED.view = (function() {
|
||||
updateActiveNodes();
|
||||
updateSelection();
|
||||
redraw();
|
||||
|
||||
|
||||
if (nn._def.autoedit) {
|
||||
RED.editor.edit(nn);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
RED.keyboard.add(/* z */ 90,{ctrl:true},function(){RED.history.pop();});
|
||||
RED.keyboard.add(/* a */ 65,{ctrl:true},function(){selectAll();d3.event.preventDefault();});
|
||||
RED.keyboard.add(/* = */ 187,{ctrl:true},function(){zoomIn();d3.event.preventDefault();});
|
||||
@ -652,7 +652,7 @@ RED.view = (function() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
selected_link = null;
|
||||
updateSelection();
|
||||
redraw();
|
||||
@ -693,17 +693,17 @@ RED.view = (function() {
|
||||
}
|
||||
|
||||
var selection = {};
|
||||
|
||||
|
||||
if (moving_set.length > 0) {
|
||||
selection.nodes = moving_set.map(function(n) { return n.n;});
|
||||
}
|
||||
if (selected_link != null) {
|
||||
selection.link = selected_link;
|
||||
}
|
||||
|
||||
|
||||
eventHandler.emit("selection-changed",selection);
|
||||
}
|
||||
|
||||
|
||||
function endKeyboardMove() {
|
||||
var ns = [];
|
||||
for (var i=0;i<moving_set.length;i++) {
|
||||
@ -746,7 +746,7 @@ RED.view = (function() {
|
||||
var removedLinks = [];
|
||||
var removedSubflowOutputs = [];
|
||||
var removedSubflowInputs = [];
|
||||
|
||||
|
||||
var startDirty = RED.nodes.dirty();
|
||||
if (moving_set.length > 0) {
|
||||
for (var i=0;i<moving_set.length;i++) {
|
||||
@ -789,7 +789,7 @@ RED.view = (function() {
|
||||
});
|
||||
subflowRemovedLinks.forEach(function(l) { RED.nodes.removeLink(l)});
|
||||
subflowMovedLinks.forEach(function(l) { l.sourcePort--; });
|
||||
|
||||
|
||||
removedLinks = removedLinks.concat(subflowRemovedLinks);
|
||||
for (var j=output.i;j<activeSubflow.out.length;j++) {
|
||||
activeSubflow.out[j].i--;
|
||||
@ -813,7 +813,7 @@ RED.view = (function() {
|
||||
activeSubflow.in = [];
|
||||
$("#workspace-subflow-add-input").toggleClass("disabled",false);
|
||||
}
|
||||
|
||||
|
||||
if (activeSubflow) {
|
||||
RED.nodes.filterNodes({type:"subflow:"+activeSubflow.id}).forEach(function(n) {
|
||||
n.changed = true;
|
||||
@ -827,7 +827,7 @@ RED.view = (function() {
|
||||
});
|
||||
RED.editor.validateNode(activeSubflow);
|
||||
}
|
||||
|
||||
|
||||
moving_set = [];
|
||||
if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0) {
|
||||
RED.nodes.dirty(true);
|
||||
@ -856,7 +856,7 @@ RED.view = (function() {
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
} 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 {
|
||||
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();
|
||||
}
|
||||
@ -1070,9 +1070,9 @@ RED.view = (function() {
|
||||
|
||||
// Don't bother redrawing nodes if we're drawing links
|
||||
if (mouse_mode != RED.state.JOINING) {
|
||||
|
||||
|
||||
var dirtyNodes = {};
|
||||
|
||||
|
||||
if (activeSubflow) {
|
||||
var subflowOutputs = vis.selectAll(".subflowoutput").data(activeSubflow.out,function(d,i){ return d.id;});
|
||||
subflowOutputs.exit().remove();
|
||||
@ -1082,7 +1082,7 @@ RED.view = (function() {
|
||||
d.h=40;
|
||||
});
|
||||
outGroup.append("rect").attr("class","subflowport").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40)
|
||||
// TODO: This is exactly the same set of handlers used for regular nodes - DRY
|
||||
// TODO: This is exactly the same set of handlers used for regular nodes - DRY
|
||||
.on("mouseup",nodeMouseUp)
|
||||
.on("mousedown",nodeMouseDown)
|
||||
.on("touchstart",function(d) {
|
||||
@ -1094,7 +1094,7 @@ RED.view = (function() {
|
||||
touchStartTime = setTimeout(function() {
|
||||
showTouchMenu(obj,pos);
|
||||
},touchLongPressTimeout);
|
||||
nodeMouseDown.call(this,d)
|
||||
nodeMouseDown.call(this,d)
|
||||
})
|
||||
.on("touchend", function(d) {
|
||||
clearTimeout(touchStartTime);
|
||||
@ -1105,7 +1105,7 @@ RED.view = (function() {
|
||||
}
|
||||
nodeMouseUp.call(this,d);
|
||||
});
|
||||
|
||||
|
||||
outGroup.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10).attr("x",-5).attr("y",15)
|
||||
.on("mousedown", function(d,i){portMouseDown(d,1,0);} )
|
||||
.on("touchstart", function(d,i){portMouseDown(d,1,0);} )
|
||||
@ -1125,7 +1125,7 @@ RED.view = (function() {
|
||||
d.h=40;
|
||||
});
|
||||
inGroup.append("rect").attr("class","subflowport").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40)
|
||||
// TODO: This is exactly the same set of handlers used for regular nodes - DRY
|
||||
// TODO: This is exactly the same set of handlers used for regular nodes - DRY
|
||||
.on("mouseup",nodeMouseUp)
|
||||
.on("mousedown",nodeMouseDown)
|
||||
.on("touchstart",function(d) {
|
||||
@ -1137,7 +1137,7 @@ RED.view = (function() {
|
||||
touchStartTime = setTimeout(function() {
|
||||
showTouchMenu(obj,pos);
|
||||
},touchLongPressTimeout);
|
||||
nodeMouseDown.call(this,d)
|
||||
nodeMouseDown.call(this,d)
|
||||
})
|
||||
.on("touchend", function(d) {
|
||||
clearTimeout(touchStartTime);
|
||||
@ -1148,7 +1148,7 @@ RED.view = (function() {
|
||||
}
|
||||
nodeMouseUp.call(this,d);
|
||||
});
|
||||
|
||||
|
||||
inGroup.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10).attr("x",35).attr("y",15)
|
||||
.on("mousedown", function(d,i){portMouseDown(d,0,i);} )
|
||||
.on("touchstart", function(d,i){portMouseDown(d,0,i);} )
|
||||
@ -1157,9 +1157,9 @@ RED.view = (function() {
|
||||
.on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type !== 0 ));})
|
||||
.on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);});
|
||||
inGroup.append("svg:text").attr('class','port_label').attr('x',18).attr('y',20).style("font-size","10px").text("input");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
subflowOutputs.each(function(d,i) {
|
||||
if (d.dirty) {
|
||||
var output = d3.select(this);
|
||||
@ -1183,7 +1183,7 @@ RED.view = (function() {
|
||||
vis.selectAll(".subflowoutput").remove();
|
||||
vis.selectAll(".subflowinput").remove();
|
||||
}
|
||||
|
||||
|
||||
var node = vis.selectAll(".nodegroup").data(activeNodes,function(d){return d.id});
|
||||
node.exit().remove();
|
||||
|
||||
@ -1318,7 +1318,7 @@ RED.view = (function() {
|
||||
//icon.attr('class','node_icon_shade node_icon_shade_'+d._def.align);
|
||||
//icon.attr('class','node_icon_shade_border node_icon_shade_border_'+d._def.align);
|
||||
}
|
||||
|
||||
|
||||
//if (d.inputs > 0 && d._def.align == null) {
|
||||
// icon_shade.attr("width",35);
|
||||
// icon.attr("transform","translate(5,0)");
|
||||
@ -1388,7 +1388,7 @@ RED.view = (function() {
|
||||
var thisNode = d3.select(this);
|
||||
//thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}});
|
||||
thisNode.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; });
|
||||
|
||||
|
||||
if (mouse_mode != RED.state.MOVING_ACTIVE) {
|
||||
thisNode.selectAll(".node")
|
||||
.attr("width",function(d){return d.w})
|
||||
@ -1398,13 +1398,13 @@ RED.view = (function() {
|
||||
;
|
||||
//thisNode.selectAll(".node-gradient-top").attr("width",function(d){return d.w});
|
||||
//thisNode.selectAll(".node-gradient-bottom").attr("width",function(d){return d.w}).attr("y",function(d){return d.h-30});
|
||||
|
||||
|
||||
thisNode.selectAll(".node_icon_group_right").attr('transform', function(d){return "translate("+(d.w-30)+",0)"});
|
||||
thisNode.selectAll(".node_label_right").attr('x', function(d){return d.w-38});
|
||||
//thisNode.selectAll(".node_icon_right").attr("x",function(d){return d.w-d3.select(this).attr("width")-1-(d.outputs>0?5:0);});
|
||||
//thisNode.selectAll(".node_icon_shade_right").attr("x",function(d){return d.w-30;});
|
||||
//thisNode.selectAll(".node_icon_shade_border_right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)});
|
||||
|
||||
|
||||
var inputPorts = thisNode.selectAll(".port_input");
|
||||
if (d.inputs === 0 && !inputPorts.empty()) {
|
||||
inputPorts.remove();
|
||||
@ -1419,13 +1419,13 @@ RED.view = (function() {
|
||||
.on("mouseover",function(d) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type != 1 ));})
|
||||
.on("mouseout",function(d) { var port = d3.select(this); port.classed("port_hovered",false);})
|
||||
}
|
||||
|
||||
|
||||
var numOutputs = d.outputs;
|
||||
var y = (d.h/2)-((numOutputs-1)/2)*13;
|
||||
d.ports = d.ports || d3.range(numOutputs);
|
||||
d._ports = thisNode.selectAll(".port_output").data(d.ports);
|
||||
var output_group = d._ports.enter().append("g").attr("class","port_output");
|
||||
|
||||
|
||||
output_group.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10)
|
||||
.on("mousedown",(function(){var node = d; return function(d,i){portMouseDown(node,0,i);}})() )
|
||||
.on("touchstart",(function(){var node = d; return function(d,i){portMouseDown(node,0,i);}})() )
|
||||
@ -1433,7 +1433,7 @@ RED.view = (function() {
|
||||
.on("touchend",(function(){var node = d; return function(d,i){portMouseUp(node,0,i);}})() )
|
||||
.on("mouseover",function(d,i) { var port = d3.select(this); port.classed("port_hovered",(mouse_mode!=RED.state.JOINING || mousedown_port_type !== 0 ));})
|
||||
.on("mouseout",function(d,i) { var port = d3.select(this); port.classed("port_hovered",false);});
|
||||
|
||||
|
||||
d._ports.exit().remove();
|
||||
if (d._ports) {
|
||||
numOutputs = d.outputs || 1;
|
||||
@ -1461,7 +1461,7 @@ RED.view = (function() {
|
||||
(d._def.align?' node_label_'+d._def.align:'')+
|
||||
(d._def.labelStyle?' '+(typeof d._def.labelStyle == "function" ? d._def.labelStyle.call(d):d._def.labelStyle):'') ;
|
||||
});
|
||||
|
||||
|
||||
if (d._def.icon) {
|
||||
icon = thisNode.select(".node_icon");
|
||||
var current_url = icon.attr("xlink:href");
|
||||
@ -1482,27 +1482,27 @@ RED.view = (function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
thisNode.selectAll(".node_tools").attr("x",function(d){return d.w-35;}).attr("y",function(d){return d.h-20;});
|
||||
|
||||
|
||||
thisNode.selectAll(".node_changed")
|
||||
.attr("x",function(d){return d.w-10})
|
||||
.classed("hidden",function(d) { return !d.changed; });
|
||||
|
||||
|
||||
thisNode.selectAll(".node_error")
|
||||
.attr("x",function(d){return d.w-10-(d.changed?13:0)})
|
||||
.classed("hidden",function(d) { return d.valid; });
|
||||
|
||||
|
||||
thisNode.selectAll(".port_input").each(function(d,i) {
|
||||
var port = d3.select(this);
|
||||
port.attr("transform",function(d){return "translate(-5,"+((d.h/2)-5)+")";})
|
||||
});
|
||||
|
||||
|
||||
thisNode.selectAll(".node_icon").attr("y",function(d){return (d.h-d3.select(this).attr("height"))/2;});
|
||||
thisNode.selectAll(".node_icon_shade").attr("height",function(d){return d.h;});
|
||||
thisNode.selectAll(".node_icon_shade_border").attr("d",function(d){ return "M "+(("right" == d._def.align) ?0:30)+" 1 l 0 "+(d.h-2)});
|
||||
|
||||
|
||||
thisNode.selectAll('.node_button').attr("opacity",function(d) {
|
||||
return (activeSubflow||d.changed)?0.4:1
|
||||
});
|
||||
@ -1522,11 +1522,11 @@ RED.view = (function() {
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
|
||||
|
||||
//thisNode.selectAll('.node_right_button').attr("transform",function(d){return "translate("+(d.w - d._def.button.width.call(d))+","+0+")";}).attr("fill",function(d) {
|
||||
// return typeof d._def.button.color === "function" ? d._def.button.color.call(d):(d._def.button.color != null ? d._def.button.color : d._def.color)
|
||||
//});
|
||||
|
||||
|
||||
thisNode.selectAll('.node_badge_group').attr("transform",function(d){return "translate("+(d.w-40)+","+(d.h+3)+")";});
|
||||
thisNode.selectAll('text.node_badge_label').text(function(d,i) {
|
||||
if (d._def.badge) {
|
||||
@ -1581,7 +1581,7 @@ RED.view = (function() {
|
||||
}
|
||||
);
|
||||
var linkEnter = link.enter().insert("g",".node").attr("class","link");
|
||||
|
||||
|
||||
linkEnter.each(function(d,i) {
|
||||
var l = d3.select(this);
|
||||
d.added = true;
|
||||
@ -1608,7 +1608,7 @@ RED.view = (function() {
|
||||
l.append("svg:path").attr("class","link_line link_path")
|
||||
.classed("link_subflow", function(d) { return activeSubflow && (d.source.type === "subflow" || d.target.type === "subflow") });
|
||||
});
|
||||
|
||||
|
||||
link.exit().remove();
|
||||
var links = vis.selectAll(".link_path");
|
||||
links.each(function(d) {
|
||||
@ -1618,7 +1618,7 @@ RED.view = (function() {
|
||||
var numOutputs = d.source.outputs || 1;
|
||||
var sourcePort = d.sourcePort || 0;
|
||||
var y = -((numOutputs-1)/2)*13 +13*sourcePort;
|
||||
|
||||
|
||||
var dy = d.target.y-(d.source.y+y);
|
||||
var dx = (d.target.x-d.target.w/2)-(d.source.x+d.source.w/2);
|
||||
var delta = Math.sqrt(dy*dy+dx*dx);
|
||||
@ -1627,19 +1627,19 @@ RED.view = (function() {
|
||||
if (delta < node_width) {
|
||||
scale = 0.75-0.75*((node_width-delta)/node_width);
|
||||
}
|
||||
|
||||
|
||||
if (dx < 0) {
|
||||
scale += 2*(Math.min(5*node_width,Math.abs(dx))/(5*node_width));
|
||||
if (Math.abs(dy) < 3*node_height) {
|
||||
scaleY = ((dy>0)?0.5:-0.5)*(((3*node_height)-Math.abs(dy))/(3*node_height))*(Math.min(node_width,Math.abs(dx))/(node_width)) ;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
d.x1 = d.source.x+d.source.w/2;
|
||||
d.y1 = d.source.y+y;
|
||||
d.x2 = d.target.x-d.target.w/2;
|
||||
d.y2 = d.target.y;
|
||||
|
||||
|
||||
return "M "+(d.source.x+d.source.w/2)+" "+(d.source.y+y)+
|
||||
" C "+(d.source.x+d.source.w/2+scale*node_width)+" "+(d.source.y+y+scaleY*node_height)+" "+
|
||||
(d.target.x-d.target.w/2-scale*node_width)+" "+(d.target.y-scaleY*node_height)+" "+
|
||||
@ -1647,9 +1647,9 @@ RED.view = (function() {
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
link.classed("link_selected", function(d) { return d === selected_link || d.selected; });
|
||||
link.classed("link_unknown",function(d) {
|
||||
link.classed("link_unknown",function(d) {
|
||||
delete d.added;
|
||||
return d.target.type == "unknown" || d.source.type == "unknown"
|
||||
});
|
||||
@ -1662,12 +1662,12 @@ RED.view = (function() {
|
||||
}
|
||||
).classed("link_selected", false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (d3.event) {
|
||||
d3.event.preventDefault();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
function focusView() {
|
||||
@ -1688,7 +1688,7 @@ RED.view = (function() {
|
||||
var new_links = result[1];
|
||||
var new_workspaces = result[2];
|
||||
var new_subflows = result[3];
|
||||
|
||||
|
||||
var new_ms = new_nodes.filter(function(n) { return n.z == RED.workspaces.active() }).map(function(n) { return {n:n};});
|
||||
var new_node_ids = new_nodes.map(function(n){ return n.id; });
|
||||
|
||||
@ -1754,9 +1754,9 @@ RED.view = (function() {
|
||||
} catch(error) {
|
||||
if (error.code != "NODE_RED") {
|
||||
console.log(error.stack);
|
||||
RED.notify("<strong>Error</strong>: "+error,"error");
|
||||
RED.notify(RED._("notification.error")+error,"error");
|
||||
} else {
|
||||
RED.notify("<strong>Error</strong>: "+error.message,"error");
|
||||
RED.notify(RED._("notification.error")+error.message,"error");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1764,7 +1764,7 @@ RED.view = (function() {
|
||||
// TODO: DRY
|
||||
var eventHandler = (function() {
|
||||
var handlers = {};
|
||||
|
||||
|
||||
return {
|
||||
on: function(evt,func) {
|
||||
handlers[evt] = handlers[evt]||[];
|
||||
@ -1775,12 +1775,12 @@ RED.view = (function() {
|
||||
for (var i=0;i<handlers[evt].length;i++) {
|
||||
handlers[evt][i](arg);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
return {
|
||||
init: init,
|
||||
on: eventHandler.on,
|
||||
@ -1791,13 +1791,13 @@ RED.view = (function() {
|
||||
mouse_mode = state;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
redraw: function(updateActive) {
|
||||
if (updateActive) {
|
||||
updateActiveNodes();
|
||||
}
|
||||
RED.workspaces.refresh();
|
||||
redraw();
|
||||
redraw();
|
||||
},
|
||||
focus: focusView,
|
||||
importNodes: importNodes,
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
|
||||
RED.workspaces = (function() {
|
||||
|
||||
|
||||
var activeWorkspace = 0;
|
||||
var workspaceIndex = 0;
|
||||
|
||||
@ -29,7 +29,7 @@ RED.workspaces = (function() {
|
||||
do {
|
||||
workspaceIndex += 1;
|
||||
} while($("#workspace-tabs a[title='Sheet "+workspaceIndex+"']").size() !== 0);
|
||||
|
||||
|
||||
ws = {type:"tab",id:tabId,label:"Sheet "+workspaceIndex};
|
||||
RED.nodes.addWorkspace(ws);
|
||||
workspace_tabs.addTab(ws);
|
||||
@ -56,10 +56,10 @@ RED.workspaces = (function() {
|
||||
RED.nodes.dirty(true);
|
||||
} else {
|
||||
$( "#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');
|
||||
}
|
||||
}
|
||||
}
|
||||
function showRenameWorkspaceDialog(id) {
|
||||
var ws = RED.nodes.workspace(id);
|
||||
$( "#node-dialog-rename-workspace" ).dialog("option","workspace",ws);
|
||||
@ -77,133 +77,138 @@ RED.workspaces = (function() {
|
||||
$( "#node-input-workspace-name" ).val(ws.label);
|
||||
$( "#node-dialog-rename-workspace" ).dialog("open");
|
||||
}
|
||||
|
||||
var workspace_tabs = RED.tabs.create({
|
||||
id: "workspace-tabs",
|
||||
onchange: function(tab) {
|
||||
if (tab.type == "subflow") {
|
||||
$("#workspace-toolbar").show();
|
||||
} else {
|
||||
$("#workspace-toolbar").hide();
|
||||
}
|
||||
var event = {
|
||||
old: activeWorkspace
|
||||
}
|
||||
activeWorkspace = tab.id;
|
||||
event.workspace = activeWorkspace;
|
||||
|
||||
eventHandler.emit("change",event);
|
||||
},
|
||||
ondblclick: function(tab) {
|
||||
if (tab.type != "subflow") {
|
||||
showRenameWorkspaceDialog(tab.id);
|
||||
} else {
|
||||
RED.editor.editSubflow(RED.nodes.subflow(tab.id));
|
||||
}
|
||||
},
|
||||
onadd: function(tab) {
|
||||
RED.menu.addItem("menu-item-workspace",{
|
||||
id:"menu-item-workspace-menu-"+tab.id.replace(".","-"),
|
||||
label:tab.label,
|
||||
onselect:function() {
|
||||
workspace_tabs.activateTab(tab.id);
|
||||
}
|
||||
});
|
||||
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
|
||||
},
|
||||
onremove: function(tab) {
|
||||
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
|
||||
RED.menu.removeItem("menu-item-workspace-menu-"+tab.id.replace(".","-"));
|
||||
}
|
||||
});
|
||||
|
||||
$("#node-dialog-rename-workspace form" ).submit(function(e) { e.preventDefault();});
|
||||
$( "#node-dialog-rename-workspace" ).dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 500,
|
||||
title: "Rename sheet",
|
||||
buttons: [
|
||||
{
|
||||
class: 'leftButton',
|
||||
text: "Delete",
|
||||
click: function() {
|
||||
var workspace = $(this).dialog('option','workspace');
|
||||
$( this ).dialog( "close" );
|
||||
deleteWorkspace(workspace);
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "Ok",
|
||||
click: function() {
|
||||
var workspace = $(this).dialog('option','workspace');
|
||||
var label = $( "#node-input-workspace-name" ).val();
|
||||
if (workspace.label != label) {
|
||||
workspace_tabs.renameTab(workspace.id,label);
|
||||
RED.nodes.dirty(true);
|
||||
$("#menu-item-workspace-menu-"+workspace.id.replace(".","-")).text(label);
|
||||
// TODO: update entry in menu
|
||||
}
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "Cancel",
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
],
|
||||
open: function(e) {
|
||||
RED.keyboard.disable();
|
||||
},
|
||||
close: function(e) {
|
||||
RED.keyboard.enable();
|
||||
}
|
||||
});
|
||||
$( "#node-dialog-delete-workspace" ).dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 500,
|
||||
title: "Confirm delete",
|
||||
buttons: [
|
||||
{
|
||||
text: "Ok",
|
||||
click: function() {
|
||||
var workspace = $(this).dialog('option','workspace');
|
||||
deleteWorkspace(workspace,true);
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "Cancel",
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
],
|
||||
open: function(e) {
|
||||
RED.keyboard.disable();
|
||||
},
|
||||
close: function(e) {
|
||||
RED.keyboard.enable();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
var workspace_tabs;
|
||||
function createWorkspaceTabs(){
|
||||
workspace_tabs = RED.tabs.create({
|
||||
id: "workspace-tabs",
|
||||
onchange: function(tab) {
|
||||
if (tab.type == "subflow") {
|
||||
$("#workspace-toolbar").show();
|
||||
} else {
|
||||
$("#workspace-toolbar").hide();
|
||||
}
|
||||
var event = {
|
||||
old: activeWorkspace
|
||||
}
|
||||
activeWorkspace = tab.id;
|
||||
event.workspace = activeWorkspace;
|
||||
|
||||
eventHandler.emit("change",event);
|
||||
},
|
||||
ondblclick: function(tab) {
|
||||
if (tab.type != "subflow") {
|
||||
showRenameWorkspaceDialog(tab.id);
|
||||
} else {
|
||||
RED.editor.editSubflow(RED.nodes.subflow(tab.id));
|
||||
}
|
||||
},
|
||||
onadd: function(tab) {
|
||||
RED.menu.addItem("menu-item-workspace",{
|
||||
id:"menu-item-workspace-menu-"+tab.id.replace(".","-"),
|
||||
label:tab.label,
|
||||
onselect:function() {
|
||||
workspace_tabs.activateTab(tab.id);
|
||||
}
|
||||
});
|
||||
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
|
||||
},
|
||||
onremove: function(tab) {
|
||||
RED.menu.setDisabled("menu-item-workspace-delete",workspace_tabs.count() == 1);
|
||||
RED.menu.removeItem("menu-item-workspace-menu-"+tab.id.replace(".","-"));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$("#node-dialog-rename-workspace form" ).submit(function(e) { e.preventDefault();});
|
||||
$( "#node-dialog-rename-workspace" ).dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 500,
|
||||
title: RED._("workspace.renameSheet"),
|
||||
buttons: [
|
||||
{
|
||||
class: 'leftButton',
|
||||
text: RED._("common.label.delete"),
|
||||
click: function() {
|
||||
var workspace = $(this).dialog('option','workspace');
|
||||
$( this ).dialog( "close" );
|
||||
deleteWorkspace(workspace);
|
||||
}
|
||||
},
|
||||
{
|
||||
text: RED._("common.label.ok"),
|
||||
click: function() {
|
||||
var workspace = $(this).dialog('option','workspace');
|
||||
var label = $( "#node-input-workspace-name" ).val();
|
||||
if (workspace.label != label) {
|
||||
workspace_tabs.renameTab(workspace.id,label);
|
||||
RED.nodes.dirty(true);
|
||||
$("#menu-item-workspace-menu-"+workspace.id.replace(".","-")).text(label);
|
||||
// TODO: update entry in menu
|
||||
}
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
text: RED._("common.label.cancel"),
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
],
|
||||
open: function(e) {
|
||||
RED.keyboard.disable();
|
||||
},
|
||||
close: function(e) {
|
||||
RED.keyboard.enable();
|
||||
}
|
||||
});
|
||||
$( "#node-dialog-delete-workspace" ).dialog({
|
||||
modal: true,
|
||||
autoOpen: false,
|
||||
width: 500,
|
||||
title: RED._("workspace.confirmDelete"),
|
||||
buttons: [
|
||||
{
|
||||
text: RED._("common.label.ok"),
|
||||
click: function() {
|
||||
var workspace = $(this).dialog('option','workspace');
|
||||
deleteWorkspace(workspace,true);
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
},
|
||||
{
|
||||
text: RED._("common.label.cancel"),
|
||||
click: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
],
|
||||
open: function(e) {
|
||||
RED.keyboard.disable();
|
||||
},
|
||||
close: function(e) {
|
||||
RED.keyboard.enable();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
createWorkspaceTabs();
|
||||
$('#btn-workspace-add-tab').on("click",function(e) {addWorkspace(); e.preventDefault()});
|
||||
RED.sidebar.on("resize",workspace_tabs.resize);
|
||||
|
||||
|
||||
RED.menu.setAction('menu-item-workspace-delete',function() {
|
||||
deleteWorkspace(RED.nodes.workspace(activeWorkspace));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// TODO: DRY
|
||||
var eventHandler = (function() {
|
||||
var handlers = {};
|
||||
|
||||
|
||||
return {
|
||||
on: function(evt,func) {
|
||||
handlers[evt] = handlers[evt]||[];
|
||||
@ -214,12 +219,12 @@ RED.workspaces = (function() {
|
||||
for (var i=0;i<handlers[evt].length;i++) {
|
||||
handlers[evt][i](arg);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
function removeWorkspace(ws) {
|
||||
if (!ws) {
|
||||
deleteWorkspace(RED.nodes.workspace(activeWorkspace));
|
||||
@ -234,7 +239,7 @@ RED.workspaces = (function() {
|
||||
on: eventHandler.on,
|
||||
add: addWorkspace,
|
||||
remove: removeWorkspace,
|
||||
|
||||
|
||||
edit: function(id) {
|
||||
showRenameWorkspaceDialog(id||activeWorkspace);
|
||||
},
|
||||
@ -251,15 +256,15 @@ RED.workspaces = (function() {
|
||||
if (!workspace_tabs.contains(id)) {
|
||||
var sf = RED.nodes.subflow(id);
|
||||
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);
|
||||
},
|
||||
refresh: function() {
|
||||
RED.nodes.eachSubflow(function(sf) {
|
||||
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}));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -14,13 +14,13 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
RED.user = (function() {
|
||||
|
||||
|
||||
function login(opts,done) {
|
||||
if (typeof opts == 'function') {
|
||||
done = opts;
|
||||
opts = {};
|
||||
}
|
||||
|
||||
|
||||
var dialog = $('<div id="node-dialog-login" class="hide">'+
|
||||
'<div style="display: inline-block;width: 250px; vertical-align: top; margin-right: 10px; margin-bottom: 20px;"><img id="node-dialog-login-image" src=""/></div>'+
|
||||
'<div style="display: inline-block; width: 250px; vertical-align: bottom; margin-left: 10px; margin-bottom: 20px;">'+
|
||||
@ -37,7 +37,7 @@ RED.user = (function() {
|
||||
resizable: false,
|
||||
draggable: false
|
||||
});
|
||||
|
||||
|
||||
$("#node-dialog-login-fields").empty();
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
@ -45,7 +45,7 @@ RED.user = (function() {
|
||||
success: function(data) {
|
||||
if (data.type == "credentials") {
|
||||
var i=0;
|
||||
|
||||
|
||||
if (data.image) {
|
||||
$("#node-dialog-login-image").attr("src",data.image);
|
||||
} else {
|
||||
@ -56,7 +56,7 @@ RED.user = (function() {
|
||||
var row = $("<div/>",{id:"rrr"+i,class:"form-row"});
|
||||
$('<label for="node-dialog-login-'+field.id+'">'+field.label+':</label><br/>').appendTo(row);
|
||||
var input = $('<input style="width: 100%" id="node-dialog-login-'+field.id+'" type="'+field.type+'" tabIndex="'+(i+1)+'"/>').appendTo(row);
|
||||
|
||||
|
||||
if (i<data.prompts.length-1) {
|
||||
input.keypress(
|
||||
(function() {
|
||||
@ -72,17 +72,17 @@ RED.user = (function() {
|
||||
}
|
||||
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"/>'+
|
||||
(opts.cancelable?'<a href="#" id="node-dialog-login-cancel" style="margin-right: 20px;" tabIndex="'+(i+1)+'">Cancel</a>':'')+
|
||||
'<input type="submit" id="node-dialog-login-submit" style="width: auto;" tabIndex="'+(i+2)+'" value="Login"></div>').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">'+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)+'">'+RED._("common.label.cancel")+'</a>':'')+
|
||||
'<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-fields").submit(function(event) {
|
||||
$("#node-dialog-login-submit").button("option","disabled",true);
|
||||
$("#node-dialog-login-failed").hide();
|
||||
$(".login-spinner").show();
|
||||
|
||||
|
||||
var body = {
|
||||
client_id: "node-red-editor",
|
||||
grant_type: "password",
|
||||
@ -116,7 +116,7 @@ RED.user = (function() {
|
||||
}
|
||||
}
|
||||
dialog.dialog("open");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -131,17 +131,17 @@ RED.user = (function() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function updateUserMenu() {
|
||||
$("#usermenu-submenu li").remove();
|
||||
if (RED.settings.user.anonymous) {
|
||||
RED.menu.addItem("btn-usermenu",{
|
||||
id:"usermenu-item-login",
|
||||
label:"Login",
|
||||
label:RED._("menu.label.login"),
|
||||
onselect: function() {
|
||||
RED.user.login({cancelable:true},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();
|
||||
});
|
||||
});
|
||||
@ -154,31 +154,31 @@ RED.user = (function() {
|
||||
});
|
||||
RED.menu.addItem("btn-usermenu",{
|
||||
id:"usermenu-item-logout",
|
||||
label:"Logout",
|
||||
label:RED._("menu.label.logout"),
|
||||
onselect: function() {
|
||||
RED.user.logout();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function init() {
|
||||
if (RED.settings.user) {
|
||||
if (!RED.settings.editorTheme || !RED.settings.editorTheme.hasOwnProperty("userMenu")) {
|
||||
|
||||
|
||||
$('<li><a id="btn-usermenu" class="button hide" data-toggle="dropdown" href="#"><i class="fa fa-user"></i></a></li>')
|
||||
.prependTo(".header-toolbar");
|
||||
|
||||
|
||||
RED.menu.init({id:"btn-usermenu",
|
||||
options: []
|
||||
});
|
||||
updateUserMenu();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return {
|
||||
init: init,
|
||||
|
@ -46,7 +46,7 @@
|
||||
<div id="palette-container" class="palette-scroll">
|
||||
</div>
|
||||
<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><!-- /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="chart"></div>
|
||||
<div id="workspace-toolbar">
|
||||
<a class="button" id="workspace-subflow-edit" href="#"><i class="fa fa-pencil"></i> edit name</a>
|
||||
<a class="button disabled" id="workspace-subflow-add-input" href="#"><i class="fa fa-plus"></i> input</a>
|
||||
<a class="button" id="workspace-subflow-add-output" href="#"><i class="fa fa-plus"></i> output</a>
|
||||
<a class="button" id="workspace-subflow-delete" href="#"><i class="fa fa-trash"></i> delete subflow</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="#" data-i18n="[append]subflow.input"><i class="fa fa-plus"></i> </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="#" data-i18n="[append]subflow.deleteSubflow"><i class="fa fa-trash"></i> </a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -80,14 +80,14 @@
|
||||
</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="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">
|
||||
<form class="form-horizontal">
|
||||
<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>
|
||||
</form>
|
||||
<div class="form-tips" id="subflow-dialog-user-count"></div>
|
||||
@ -95,26 +95,19 @@
|
||||
|
||||
<div id="node-dialog-confirm-deploy" class="hide">
|
||||
<form class="form-horizontal">
|
||||
<div id="node-dialog-confirm-deploy-config" style="text-align: left; padding-top: 30px;">
|
||||
Some of the nodes are not properly configured. Are you sure you want to deploy?
|
||||
</div>
|
||||
<div id="node-dialog-confirm-deploy-unknown" style="text-align: left; padding-top: 10px;">
|
||||
The workspace contains some unknown node types:
|
||||
<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>
|
||||
<div id="node-dialog-confirm-deploy-unknown" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.unknown;[append]deploy.confirm.confirm">
|
||||
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-unknown-list"></ul>
|
||||
Are you sure you want to deploy?
|
||||
</div>
|
||||
<div id="node-dialog-confirm-deploy-unused" style="text-align: left; padding-top: 10px;">
|
||||
The workspace contains some unused configuration nodes:
|
||||
<div id="node-dialog-confirm-deploy-unused" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.unusedConfig;[append]deploy.confirm.confirm">
|
||||
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-unused-list"></ul>
|
||||
Are you sure you want to deploy?
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="node-dialog-library-save-confirm" class="hide">
|
||||
<form class="form-horizontal">
|
||||
<div style="text-align: center; padding-top: 30px;">
|
||||
A <span id="node-dialog-library-save-type"></span> called <span id="node-dialog-library-save-name"></span> already exists. Overwrite?
|
||||
<div style="text-align: center; padding-top: 30px;" id="node-dialog-library-save-content">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -122,12 +115,12 @@
|
||||
<div id="node-dialog-library-save" class="hide">
|
||||
<form class="form-horizontal">
|
||||
<div class="form-row">
|
||||
<label for="node-dialog-library-save-folder"><i class="fa fa-folder-open"></i> Folder</label>
|
||||
<input type="text" id="node-dialog-library-save-folder" placeholder="Folder">
|
||||
<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" data-i18n="[placeholder]library.folderPlaceholder">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-dialog-library-save-filename"><i class="fa fa-file"></i> Filename</label>
|
||||
<input type="text" id="node-dialog-library-save-filename" placeholder="Filename">
|
||||
<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" data-i18n="[placeholder]library.filenamePlaceholder">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -136,7 +129,7 @@
|
||||
<form class="form-horizontal">
|
||||
<div class="form-row">
|
||||
<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>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
@ -152,30 +145,29 @@
|
||||
<div id="node-dialog-rename-workspace" class="hide">
|
||||
<form class="form-horizontal">
|
||||
<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">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div id="node-dialog-delete-workspace" class="hide">
|
||||
<form class="form-horizontal">
|
||||
<div style="text-align: center; padding-top: 30px;">
|
||||
Are you sure you want to delete '<span id="node-dialog-delete-workspace-name"></span>'?
|
||||
<div style="text-align: center; padding-top: 30px;" id="node-dialog-delete-workspace-content">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script type="text/x-red" data-template-name="export-library-dialog">
|
||||
<div class="form-row">
|
||||
<label for="node-input-filename" ><i class="fa fa-file"></i> Filename:</label>
|
||||
<input type="text" id="node-input-filename" placeholder="Filename">
|
||||
<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" data-i18n="[placeholder]editor:library.fullFilenamePlaceholder">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="subflow">
|
||||
<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">
|
||||
<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">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
|
5
editor/vendor/i18next/i18next.min.js
vendored
Normal file
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
192
locales/en-US/editor.json
Normal 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
123
locales/en-US/runtime.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
@ -16,8 +16,8 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="sentiment">
|
||||
<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">
|
||||
<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>
|
||||
|
||||
|
@ -16,38 +16,48 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="inject">
|
||||
<div class="form-row node-input-payload">
|
||||
<label for="node-input-payloadType"><i class="fa fa-envelope"></i> 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%">
|
||||
<option value="date">timestamp</option>
|
||||
<option value="string">string</option>
|
||||
<option value="none">blank</option>
|
||||
<option value="date" data-i18n="inject.timestamp"></option>
|
||||
<option value="string" data-i18n="inject.string"></option>
|
||||
<option value="none" data-i18n="inject.blank"></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-row" id="node-input-row-payload">
|
||||
<label for="node-input-payload"></label>
|
||||
<input type="text" id="node-input-payload" placeholder="payload" style="width:70%">
|
||||
<input type="text" id="node-input-payload" style="width:70%">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
|
||||
<input type="text" id="node-input-topic" placeholder="topic" style="width: 70%x">
|
||||
<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" style="width: 70%x">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for=""><i class="fa fa-repeat"></i> Repeat</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>
|
||||
<input type="hidden" id="node-input-repeat" placeholder="payload">
|
||||
<input type="hidden" id="node-input-crontab" placeholder="payload">
|
||||
<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" data-i18n="inject.none"></option>
|
||||
<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 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>
|
||||
<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/>
|
||||
<span data-i18n="inject.every"></span>
|
||||
<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 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="2">2</option>
|
||||
<option value="3">3</option>
|
||||
@ -60,46 +70,46 @@
|
||||
<option value="20">20</option>
|
||||
<option value="30">30</option>
|
||||
<option value="0">60</option>
|
||||
</select> minutes<br/>
|
||||
between <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/>
|
||||
</select> <span data-i18n="inject.minutes"></span><br/>
|
||||
<span data-i18n="inject.between"></span> <select id="inject-time-interval-time-start" class="inject-time-times"></select>
|
||||
<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 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>
|
||||
<label><input type='checkbox' checked value='1'/> Monday</label>
|
||||
<label><input type='checkbox' checked value='2'/> Tuesday</label>
|
||||
<label><input type='checkbox' checked value='3'/> Wednesday</label>
|
||||
<label><input type='checkbox' checked value='1'/> <span data-i18n="inject.days.0"></span></label>
|
||||
<label><input type='checkbox' checked value='2'/> <span data-i18n="inject.days.1"></span></label>
|
||||
<label><input type='checkbox' checked value='3'/> <span data-i18n="inject.days.2"></span></label>
|
||||
</div>
|
||||
<div>
|
||||
<label><input type='checkbox' checked value='4'/> Thursday</label>
|
||||
<label><input type='checkbox' checked value='5'/> Friday</label>
|
||||
<label><input type='checkbox' checked value='6'/> Saturday</label>
|
||||
<label><input type='checkbox' checked value='4'/> <span data-i18n="inject.days.3"></span></label>
|
||||
<label><input type='checkbox' checked value='5'/> <span data-i18n="inject.days.4"></span></label>
|
||||
<label><input type='checkbox' checked value='6'/> <span data-i18n="inject.days.5"></span></label>
|
||||
</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 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 style="display: inline-block; vertical-align: top;margin-right: 5px;">on </div>
|
||||
<div style="display:inline-block;">
|
||||
<div>
|
||||
<label><input type='checkbox' checked value='1'/> Monday</label>
|
||||
<label><input type='checkbox' checked value='2'/> Tuesday</label>
|
||||
<label><input type='checkbox' checked value='3'/> Wednesday</label>
|
||||
<label><input type='checkbox' checked value='1'/> <span data-i18n="inject.days.0"></span></label>
|
||||
<label><input type='checkbox' checked value='2'/> <span data-i18n="inject.days.1"></span></label>
|
||||
<label><input type='checkbox' checked value='3'/> <span data-i18n="inject.days.2"></span></label>
|
||||
</div>
|
||||
<div>
|
||||
<label><input type='checkbox' checked value='4'/> Thursday</label>
|
||||
<label><input type='checkbox' checked value='5'/> Friday</label>
|
||||
<label><input type='checkbox' checked value='6'/> Saturday</label>
|
||||
<label><input type='checkbox' checked value='4'/> <span data-i18n="inject.days.3"></span></label>
|
||||
<label><input type='checkbox' checked value='5'/> <span data-i18n="inject.days.4"></span></label>
|
||||
<label><input type='checkbox' checked value='6'/> <span data-i18n="inject.days.5"></span></label>
|
||||
</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>
|
||||
@ -108,15 +118,15 @@
|
||||
<div class="form-row" id="node-once">
|
||||
<label> </label>
|
||||
<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 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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
|
||||
<div class="form-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>
|
||||
<style>
|
||||
.inject-time-row {
|
||||
@ -197,7 +207,7 @@
|
||||
if ((this.topic.length < 24) && (this.topic.length > 0)) {
|
||||
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() {
|
||||
return this.name?"node_label_italic":"";
|
||||
@ -474,24 +484,24 @@
|
||||
button: {
|
||||
onclick: function() {
|
||||
var label = (this.name||this.payload).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
if (this.payloadType === "date") { label = "timestamp"; }
|
||||
if (this.payloadType === "none") { label = "blank"; }
|
||||
|
||||
if (this.payloadType === "date") { label = this._("inject.timestamp"); }
|
||||
if (this.payloadType === "none") { label = this._("inject.blank"); }
|
||||
var node = this;
|
||||
$.ajax({
|
||||
url: "inject/"+this.id,
|
||||
type:"POST",
|
||||
success: function(resp) {
|
||||
RED.notify("Successfully injected: "+label,"success");
|
||||
RED.notify(node._("inject.success",{label:label}),"success");
|
||||
},
|
||||
error: function(jqXHR,textStatus,errorThrown) {
|
||||
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) {
|
||||
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) {
|
||||
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 {
|
||||
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");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2013, 2014 IBM Corp.
|
||||
* Copyright 2013, 2015 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -32,12 +32,12 @@ module.exports = function(RED) {
|
||||
|
||||
if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
|
||||
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() {
|
||||
node.emit("input",{});
|
||||
}, this.repeat );
|
||||
} 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,
|
||||
function() {
|
||||
node.emit("input",{});
|
||||
@ -68,10 +68,10 @@ module.exports = function(RED) {
|
||||
InjectNode.prototype.close = function() {
|
||||
if (this.interval_id != null) {
|
||||
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) {
|
||||
this.cronjob.stop();
|
||||
if (RED.settings.verbose) { this.log("inject: cronjob stopped"); }
|
||||
if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
|
||||
delete this.cronjob;
|
||||
}
|
||||
}
|
||||
@ -84,7 +84,7 @@ module.exports = function(RED) {
|
||||
res.send(200);
|
||||
} catch(err) {
|
||||
res.send(500);
|
||||
node.error("Inject failed:"+err);
|
||||
node.error(RED._("inject.failed",{error:err.toString()}));
|
||||
}
|
||||
} else {
|
||||
res.send(404);
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="catch">
|
||||
<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">
|
||||
<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="catch">
|
||||
@ -53,7 +53,7 @@
|
||||
outputs:1,
|
||||
icon: "alert.png",
|
||||
label: function() {
|
||||
return this.name||"catch";
|
||||
return this.name||this._("catch.catch");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
|
@ -16,25 +16,25 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="debug">
|
||||
<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;">
|
||||
<option value="false">message property</option>
|
||||
<option value="true">complete msg object</option>
|
||||
<option value="false" data-i18n="debug.msgprop"></option>
|
||||
<option value="true" data-i18n="debug.msgobj"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="node-prop-row">
|
||||
<label for="node-input-complete"> </label>msg.<input type="text" style="width:208px" id="node-input-complete">
|
||||
</div>
|
||||
<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;">
|
||||
<option value="false">debug tab</option>
|
||||
<option value="true">debug tab and console</option>
|
||||
<option value="false" data-i18n="debug.debtab"></option>
|
||||
<option value="true" data-i18n="debug.tabcon"></option>
|
||||
</select>
|
||||
</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">
|
||||
<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>
|
||||
|
||||
@ -103,23 +103,24 @@
|
||||
toggle: "active",
|
||||
onclick: function() {
|
||||
var label = this.name||"debug";
|
||||
var node = this;
|
||||
$.ajax({
|
||||
url: "debug/"+this.id+"/"+(this.active?"enable":"disable"),
|
||||
type: "POST",
|
||||
success: function(resp, textStatus, xhr) {
|
||||
if (xhr.status == 200) {
|
||||
RED.notify("Successfully activated: "+label,"success");
|
||||
RED.notify(node._("debug.notification.activated",{label:label}),"success");
|
||||
} 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) {
|
||||
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) {
|
||||
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 {
|
||||
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";
|
||||
content.appendChild(messages);
|
||||
|
||||
RED.sidebar.addTab("debug",content);
|
||||
RED.sidebar.addTab(this._("debug.sidebarTitle"),content);
|
||||
|
||||
function getTimestamp() {
|
||||
var d = new Date();
|
||||
|
@ -16,28 +16,28 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="exec">
|
||||
<div class="form-row">
|
||||
<label for="node-input-command"><i class="fa fa-file"></i> Command</label>
|
||||
<input type="text" id="node-input-command" placeholder="command">
|
||||
<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" data-i18n="[placeholder]exec.label.command">
|
||||
</div>
|
||||
<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;">
|
||||
msg.payload
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<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 class="form-row">
|
||||
<label> </label>
|
||||
<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 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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-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 type="text/x-red" data-help-name="exec">
|
||||
|
@ -63,7 +63,7 @@ module.exports = function(RED) {
|
||||
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 {
|
||||
var cl = node.cmd;
|
||||
@ -75,7 +75,7 @@ module.exports = function(RED) {
|
||||
try {
|
||||
if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); }
|
||||
} catch(e) {
|
||||
node.log("Bad STDOUT");
|
||||
node.log(RED._("exec.badstdout"));
|
||||
}
|
||||
var msg2 = {payload:stderr};
|
||||
var msg3 = null;
|
||||
|
@ -16,11 +16,11 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="function">
|
||||
<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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-row" 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-noerr">
|
||||
</div>
|
||||
@ -28,10 +28,10 @@
|
||||
<div style="height: 250px;" class="node-text-editor" id="node-input-func-editor" ></div>
|
||||
</div>
|
||||
<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">
|
||||
</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 type="text/x-red" data-help-name="function">
|
||||
|
@ -16,11 +16,11 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="template">
|
||||
<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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-row" 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">
|
||||
<select id="node-input-format" style=" font-size: 0.8em; margin-bottom: 3px; width:110px; float:right;">
|
||||
<option value="handlebars">mustache</option>
|
||||
@ -34,7 +34,7 @@
|
||||
<div style="height: 250px;" class="node-text-editor" id="node-input-template-editor" ></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-field"><i class="fa fa-edit"></i> 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;">
|
||||
</div>
|
||||
</script>
|
||||
|
@ -18,58 +18,58 @@
|
||||
<script type="text/x-red" data-template-name="delay">
|
||||
|
||||
<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">
|
||||
<option value="delay">Delay message</option>
|
||||
<option value="random">Random delay</option>
|
||||
<option value="rate">Limit rate to</option>
|
||||
<option value="queue">Topic based fair queue</option>
|
||||
<option value="delay" data-i18n="delay.delaymsg"></option>
|
||||
<option value="random" data-i18n="delay.ramdomdelay"></option>
|
||||
<option value="rate" data-i18n="delay.limitrate"></option>
|
||||
<option value="queue" data-i18n="delay.fairqueue"></option>
|
||||
</select>
|
||||
</div>
|
||||
<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">
|
||||
<select id="node-input-timeoutUnits" style="width:200px !important">
|
||||
<option value="milliseconds">Milliseconds</option>
|
||||
<option value="seconds">Seconds</option>
|
||||
<option value="minutes">Minutes</option>
|
||||
<option value="hours">Hours</option>
|
||||
<option value="days">Days</option>
|
||||
<option value="milliseconds" data-i18n="delay.milisecs"></option>
|
||||
<option value="seconds" data-i18n="delay.secs"></option>
|
||||
<option value="minutes" data-i18n="delay.mins"></option>
|
||||
<option value="hours" data-i18n="delay.hours"></option>
|
||||
<option value="days" data-i18n="delay.days"></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<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">
|
||||
<option value="second">Second</option>
|
||||
<option value="minute">Minute</option>
|
||||
<option value="hour">Hour</option>
|
||||
<option value="day">Day</option>
|
||||
<option value="second" data-i18n="delay.sec"></option>
|
||||
<option value="minute" data-i18n="delay.min"></option>
|
||||
<option value="hour" data-i18n="delay.hour"></option>
|
||||
<option value="day" data-i18n="delay.day"></option>
|
||||
</select>
|
||||
<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 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">
|
||||
<label for="node-input-randomLast" style="width:20px"> & </label>
|
||||
<input type="text" id="node-input-randomLast" placeholder="" style="directon:rtl; width:30px !important">
|
||||
<select id="node-input-randomUnits" style="width:140px !important">
|
||||
<option value="milliseconds">Milliseconds</option>
|
||||
<option value="seconds">Seconds</option>
|
||||
<option value="minutes">Minutes</option>
|
||||
<option value="hours">Hours</option>
|
||||
<option value="days">Days</option>
|
||||
<option value="milliseconds" data-i18n="delay.milisecs"></option>
|
||||
<option value="seconds" data-i18n="delay.secs"></option>
|
||||
<option value="minutes" data-i18n="delay.mins"></option>
|
||||
<option value="hours" data-i18n="delay.hours"></option>
|
||||
<option value="days" data-i18n="delay.days"></option>
|
||||
</select>
|
||||
</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">
|
||||
<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>
|
||||
@ -109,16 +109,16 @@
|
||||
if (this.pauseType == "delay") {
|
||||
var units = this.timeoutUnits ? this.timeoutUnits.charAt(0) : "s";
|
||||
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") {
|
||||
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") {
|
||||
return this.name || "random";
|
||||
return this.name || this._("delay.label.random");
|
||||
}
|
||||
else {
|
||||
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
|
||||
|
@ -104,7 +104,7 @@ module.exports = function(RED) {
|
||||
node.status({text:node.buffer.length});
|
||||
}
|
||||
if (node.buffer.length > 1000) {
|
||||
node.warn(this.name + " buffer exceeded 1000 messages");
|
||||
node.warn(this.name + " " + RED._("delay.error.buffer"));
|
||||
}
|
||||
} else {
|
||||
node.send(msg);
|
||||
|
@ -16,48 +16,48 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="trigger">
|
||||
<div class="form-row">
|
||||
Send
|
||||
<span data-i18n="trigger.send"></span>
|
||||
<select id="node-input-op1type" style="width:200px !important">
|
||||
<option value="val">the string payload</option>
|
||||
<option value="pay">the existing message</option>
|
||||
<option value="nul">nothing</option>
|
||||
<option value="val" data-i18n="trigger.output.string"></option>
|
||||
<option value="pay" data-i18n="trigger.output.existing"></option>
|
||||
<option value="nul" data-i18n="trigger.output.nothing"></option>
|
||||
</select>
|
||||
<input style="width: 180px !important" type="text" id="node-input-op1">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
then
|
||||
<span data-i18n="trigger.then"></span>
|
||||
<select id="node-then-type" style="width:150px;">
|
||||
<option value="block">wait to be reset</option>
|
||||
<option value="wait">wait for</option>
|
||||
<option value="block" data-i18n="trigger.wait-reset"></option>
|
||||
<option value="wait" data-i18n="trigger.wait-for"></option>
|
||||
</select>
|
||||
<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">
|
||||
<option value="ms">Milliseconds</option>
|
||||
<option value="s">Seconds</option>
|
||||
<option value="min">Minutes</option>
|
||||
<option value="hr">Hours</option>
|
||||
<option value="ms" data-i18n="trigger.duration.ms"></option>
|
||||
<option value="s" data-i18n="trigger.duration.s"></option>
|
||||
<option value="min" data-i18n="trigger.duration.m"></option>
|
||||
<option value="hr" data-i18n="trigger.duration.h"></option>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
<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">
|
||||
<option value="val">the string payload</option>
|
||||
<option value="pay">the existing message</option>
|
||||
<option value="nul">nothing</option>
|
||||
<option value="val" data-i18n="trigger.output.string"></option>
|
||||
<option value="pay" data-i18n="trigger.output.existing"></option>
|
||||
<option value="nul" data-i18n="trigger.output.nothing"></option>
|
||||
</select>
|
||||
<input style="width: 145px !important" type="text" id="node-input-op2">
|
||||
</div>
|
||||
<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>
|
||||
<br/>
|
||||
<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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-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 type="text/x-red" data-help-name="trigger">
|
||||
@ -95,10 +95,10 @@
|
||||
icon: "trigger.png",
|
||||
label: function() {
|
||||
if (this.duration > 0) {
|
||||
return this.name|| "trigger"+" "+this.duration+this.units;
|
||||
return this.name|| this._("trigger.label.trigger")+" "+this.duration+this.units;
|
||||
}
|
||||
else {
|
||||
return this.name|| "trigger & block";
|
||||
return this.name|| this._("trigger.label.trigger-block");
|
||||
}
|
||||
},
|
||||
labelStyle: function() {
|
||||
@ -136,19 +136,19 @@
|
||||
$("#node-then-type").change();
|
||||
$("#node-input-op1type").change();
|
||||
$("#node-input-op2type").change();
|
||||
|
||||
|
||||
if (this.extend === "true" || this.extend === true) {
|
||||
$("#node-input-extend").prop("checked",true);
|
||||
} else {
|
||||
$("#node-input-extend").prop("checked",false);
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
oneditsave: function() {
|
||||
if ($("#node-then-type").val() == "block") {
|
||||
$("#node-input-duration").val("0");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -16,17 +16,17 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="comment">
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-comment"></i> Title</label>
|
||||
<input type="text" id="node-input-name" placeholder="Comment">
|
||||
<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">
|
||||
</div>
|
||||
<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">
|
||||
</div>
|
||||
<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 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 type="text/x-red" data-help-name="comment">
|
||||
@ -51,8 +51,8 @@
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
info: function() {
|
||||
var t = this.name || "Comment node";
|
||||
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 t = this.name || this._("comment.defaulttitle");
|
||||
var b = this.info || this._("comment.defaultinfo");
|
||||
return "### "+t+"\n"+b;
|
||||
},
|
||||
oneditprepare: function() {
|
||||
|
@ -15,10 +15,7 @@
|
||||
-->
|
||||
|
||||
<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>
|
||||
<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>
|
||||
<div class="form-tips"><span data-i18n="[html]unknown.tip"></span></div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="unknown">
|
||||
@ -42,7 +39,7 @@
|
||||
outputs:1,
|
||||
icon: "",
|
||||
label: function() {
|
||||
return "("+this.name+")"||"unknown";
|
||||
return "("+this.name+")"||this._("unknown.label.unknown");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return "node_label_unknown";
|
||||
|
@ -16,9 +16,9 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="rpi-gpio in">
|
||||
<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;">
|
||||
<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="5">5 - SCL1 - BCM3</option>
|
||||
<option value="7">7 - GPIO7 - BCM4</option>
|
||||
@ -40,25 +40,25 @@
|
||||
<span id="pitype"></span>
|
||||
</div>
|
||||
<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;">
|
||||
<option value="tri">none</option>
|
||||
<option value="up">pullup</option>
|
||||
<option value="down">pulldown</option>
|
||||
<option value="tri" data-i18n="rpi-gpio.resistor.none"></option>
|
||||
<option value="up" data-i18n="rpi-gpio.resistor.pullup"></option>
|
||||
<option value="down" data-i18n="rpi-gpio.resistor.pulldown"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label> </label>
|
||||
<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>
|
||||
<br/>
|
||||
<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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-tips" id="pin-tip"><b>Pins in Use</b>: </div>
|
||||
<div class="form-tips">Tip: Only Digital Input is supported - input must be 0 or 1.</div>
|
||||
<div class="form-tips" id="pin-tip"><span data-i18n="[html]rpi-gpio.tip.pin"></span></div>
|
||||
<div class="form-tips"><span data-i18n="[html]rpi-gpio.tip.in"></span></div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="rpi-gpio in">
|
||||
@ -97,6 +97,10 @@
|
||||
},
|
||||
oneditprepare: function() {
|
||||
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) {
|
||||
$('#pitype').text(data.type);
|
||||
if ((data.type === "Model B+") || (data.type === "Model A+")) {
|
||||
@ -117,14 +121,14 @@
|
||||
|
||||
$.getJSON('rpi-pins/'+this.id,function(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() {
|
||||
var pinnew = $("#node-input-pin").val();
|
||||
if ((pinnew) && (pinnew !== pinnow)) {
|
||||
if (pinsInUse.hasOwnProperty(pinnew)) {
|
||||
RED.notify("Pin "+pinnew+" already in use.","warn");
|
||||
RED.notify(pinname+" "+pinnew+" "+alreadyuse,"warn");
|
||||
}
|
||||
pinnow = pinnew;
|
||||
}
|
||||
@ -133,7 +137,7 @@
|
||||
$("#node-input-intype").change(function() {
|
||||
var newtype = $("#node-input-intype option:selected").val();
|
||||
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">
|
||||
<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;">
|
||||
<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="5">5 - SCL1 - BCM3</option>
|
||||
<option value="7">7 - GPIO7 - BCM4</option>
|
||||
@ -166,32 +170,32 @@
|
||||
<span id="pitype"></span>
|
||||
</div>
|
||||
<div class="form-row" id="node-set-pwm">
|
||||
<label> Type</label>
|
||||
<label> <span data-i18n="rpi-gpio.label.type"></span></label>
|
||||
<select id="node-input-out" style="width: 250px;">
|
||||
<option value="out">Digital output</option>
|
||||
<option value="pwm">PWM output</option>
|
||||
<option value="out" data-i18n="rpi-gpio.digout"></option>
|
||||
<option value="pwm" data-i18n="rpi-gpio.pwmout"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="node-set-tick">
|
||||
<label> </label>
|
||||
<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 class="form-row" id="node-set-state">
|
||||
<label for="node-input-level"> </label>
|
||||
<select id="node-input-level" style="width: 250px;">
|
||||
<option value="0">initial level of pin - low (0)</option>
|
||||
<option value="1">initial level of pin - high (1)</option>
|
||||
<option value="0" data-i18n="rpi-gpio.initpin0"></option>
|
||||
<option value="1" data-i18n="rpi-gpio.initpin1"></option>
|
||||
</select>
|
||||
</div>
|
||||
<br/>
|
||||
<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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-tips" id="pin-tip"><b>Pins in Use</b>: </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="pwm-tip"><b>Tip</b>: For PWM output - input must be between 0 to 100.</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"><span data-i18n="[html]rpi-gpio.tip.dig"></span></div>
|
||||
<div class="form-tips" id="pwm-tip"><span data-i18n="[html]rpi-gpio.tip.pwm"></span></div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="rpi-gpio out">
|
||||
@ -234,6 +238,10 @@
|
||||
},
|
||||
oneditprepare: function() {
|
||||
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"); }
|
||||
$.getJSON('rpi-gpio/'+this.id,function(data) {
|
||||
$('#pitype').text(data.type);
|
||||
@ -255,14 +263,14 @@
|
||||
|
||||
$.getJSON('rpi-pins/'+this.id,function(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() {
|
||||
var pinnew = $("#node-input-pin").val();
|
||||
if ((pinnew) && (pinnew !== pinnow)) {
|
||||
if (pinsInUse.hasOwnProperty(pinnew)) {
|
||||
RED.notify("Pin "+pinnew+" already in use.","warn");
|
||||
RED.notify(pinname+" "+pinnew+" "+alreadyuse,"warn");
|
||||
}
|
||||
pinnow = pinnew;
|
||||
}
|
||||
@ -271,7 +279,7 @@
|
||||
$("#node-input-out").change(function() {
|
||||
var newtype = $("#node-input-out option:selected").val();
|
||||
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">
|
||||
<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;">
|
||||
<option value="1">left</option>
|
||||
<option value="2">right</option>
|
||||
<option value="4">middle</option>
|
||||
<option value="7">any</option>
|
||||
<option value="1" data-i18n="rpi-gpio.left"></option>
|
||||
<option value="2" data-i18n="rpi-gpio.right"></option>
|
||||
<option value="4" data-i18n="rpi-gpio.middle"></option>
|
||||
<option value="7" data-i18n="rpi-gpio.any"></option>
|
||||
</select>
|
||||
</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">
|
||||
<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>
|
||||
|
||||
@ -341,10 +349,10 @@
|
||||
outputs:1,
|
||||
icon: "rpi.png",
|
||||
label: function() {
|
||||
var na = "Pi Mouse";
|
||||
if (this.butt === "1") { na += " Left"; }
|
||||
if (this.butt === "2") { na += " Right"; }
|
||||
if (this.butt === "4") { na += " Middle"; }
|
||||
var na = this._("rpi-gpio.label.pimouse");
|
||||
if (this.butt === "1") { na += " "+this._("rpi-gpio.label.left"); }
|
||||
if (this.butt === "2") { na += " "+this._("rpi-gpio.label.right"); }
|
||||
if (this.butt === "4") { na += " "+this._("rpi-gpio.label.middle"); }
|
||||
return this.name||na;
|
||||
},
|
||||
labelStyle: function() {
|
||||
|
@ -23,25 +23,25 @@ module.exports = function(RED) {
|
||||
var gpioCommand = __dirname+'/nrgpio';
|
||||
|
||||
if (!fs.existsSync("/dev/ttyAMA0")) { // unlikely if not on a Pi
|
||||
//RED.log.info("Ignoring Raspberry Pi specific node.");
|
||||
throw "Info : Ignoring Raspberry Pi specific node.";
|
||||
//RED.log.info(RED._("rpi-gpio.errors.ignorenode"));
|
||||
throw "Info : "+RED._("rpi-gpio.errors.ignorenode");
|
||||
}
|
||||
|
||||
if (!fs.existsSync("/usr/share/doc/python-rpi.gpio")) {
|
||||
RED.log.warn("Can't find Pi RPi.GPIO python library.");
|
||||
throw "Warning : Can't find Pi RPi.GPIO python library.";
|
||||
RED.log.warn(RED._("rpi-gpio.errors.libnotfound"));
|
||||
throw "Warning : "+RED._("rpi-gpio.errors.libnotfound");
|
||||
}
|
||||
|
||||
if ( !(1 & parseInt ((fs.statSync(gpioCommand).mode & parseInt ("777", 8)).toString (8)[0]) )) {
|
||||
RED.log.error(gpioCommand+" needs to be executable.");
|
||||
throw "Error : nrgpio must to be executable.";
|
||||
RED.log.error(RED._("rpi-gpio.errors.needtobeexecutable",{command:gpioCommand}));
|
||||
throw "Error : "+RED._("rpi-gpio.errors.mustbeexecutable");
|
||||
}
|
||||
|
||||
// the magic to make python print stuff immediately
|
||||
process.env.PYTHONUNBUFFERED = 1;
|
||||
|
||||
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) {
|
||||
RED.nodes.createNode(this,n);
|
||||
@ -56,7 +56,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
else {
|
||||
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.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) {
|
||||
data = data.toString().trim();
|
||||
@ -88,27 +88,27 @@ module.exports = function(RED) {
|
||||
node.child.on('close', function (code) {
|
||||
node.child = null;
|
||||
node.running = false;
|
||||
if (RED.settings.verbose) { node.log("closed"); }
|
||||
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
|
||||
if (node.done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"closed"});
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
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) {
|
||||
if (err.errno === "ENOENT") { node.error('nrgpio command not found'); }
|
||||
else if (err.errno === "EACCES") { node.error('nrgpio command not executable'); }
|
||||
else { node.error('error: ' + err.errno); }
|
||||
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
|
||||
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
|
||||
else { node.error(RED._("rpi-gpio.errors.error",{error:err.errno})) }
|
||||
});
|
||||
|
||||
}
|
||||
else {
|
||||
node.warn("Invalid GPIO pin: "+node.pin);
|
||||
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
|
||||
}
|
||||
|
||||
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];
|
||||
if (node.child != null) {
|
||||
node.done = done;
|
||||
@ -133,7 +133,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
else {
|
||||
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()});
|
||||
}
|
||||
else {
|
||||
node.error("nrpgio python command not running",msg);
|
||||
node.status({fill:"red",shape:"ring",text:"not running"});
|
||||
node.error(RED._("rpi-gpio.errors.pythoncommandnotfound"),msg);
|
||||
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) {
|
||||
@ -164,7 +164,7 @@ module.exports = function(RED) {
|
||||
node.child = spawn(gpioCommand, [node.out,node.pin]);
|
||||
}
|
||||
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);
|
||||
|
||||
@ -179,27 +179,27 @@ module.exports = function(RED) {
|
||||
node.child.on('close', function (code) {
|
||||
node.child = null;
|
||||
node.running = false;
|
||||
if (RED.settings.verbose) { node.log("closed"); }
|
||||
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
|
||||
if (node.done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"closed"});
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
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) {
|
||||
if (err.errno === "ENOENT") { node.error('nrgpio command not found'); }
|
||||
else if (err.errno === "EACCES") { node.error('nrgpio command not executable'); }
|
||||
else { node.error('error: ' + err.errno); }
|
||||
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
|
||||
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
|
||||
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
|
||||
});
|
||||
|
||||
}
|
||||
else {
|
||||
node.warn("Invalid GPIO pin: "+node.pin);
|
||||
node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
|
||||
}
|
||||
|
||||
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];
|
||||
if (node.child != null) {
|
||||
node.done = done;
|
||||
@ -214,14 +214,14 @@ module.exports = function(RED) {
|
||||
var pitype = { type:"" };
|
||||
exec(gpioCommand+" rev 0", function(err,stdout,stderr) {
|
||||
if (err) {
|
||||
RED.log.info('Version command failed for some reason.');
|
||||
RED.log.info(RED._("rpi-gpio.errors.version"));
|
||||
}
|
||||
else {
|
||||
if (stdout.trim() == "0") { pitype = { type:"Compute" }; }
|
||||
else if (stdout.trim() == "1") { pitype = { type:"A/B v1" }; }
|
||||
else if (stdout.trim() == "2") { pitype = { type:"A/B v2" }; }
|
||||
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);
|
||||
@ -232,7 +232,7 @@ module.exports = function(RED) {
|
||||
var node = this;
|
||||
|
||||
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) {
|
||||
data = Number(data);
|
||||
@ -247,22 +247,22 @@ module.exports = function(RED) {
|
||||
node.child.on('close', function (code) {
|
||||
node.child = null;
|
||||
node.running = false;
|
||||
if (RED.settings.verbose) { node.log("closed"); }
|
||||
if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
|
||||
if (node.done) {
|
||||
node.status({fill:"grey",shape:"ring",text:"closed"});
|
||||
node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
|
||||
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) {
|
||||
if (err.errno === "ENOENT") { node.error('nrgpio command not found'); }
|
||||
else if (err.errno === "EACCES") { node.error('nrgpio ommand not executable'); }
|
||||
else { node.error('error: ' + err.errno); }
|
||||
if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
|
||||
else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
|
||||
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
|
||||
});
|
||||
|
||||
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) {
|
||||
node.done = done;
|
||||
node.child.kill('SIGINT');
|
||||
|
@ -16,16 +16,16 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="mqtt in">
|
||||
<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">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
|
||||
<input type="text" id="node-input-topic" placeholder="Topic">
|
||||
<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" data-i18n="[placeholder]common.label.topic">
|
||||
</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">
|
||||
<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>
|
||||
|
||||
@ -58,32 +58,32 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="mqtt out">
|
||||
<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">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
|
||||
<input type="text" id="node-input-topic" placeholder="Topic">
|
||||
<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" data-i18n="[placeholder]common.label.topic">
|
||||
</div>
|
||||
<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">
|
||||
<option value=""></option>
|
||||
<option value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
</select>
|
||||
<i class="fa fa-history"></i> Retain <select id="node-input-retain" style="width:125px !important">
|
||||
<i class="fa fa-history"></i> <span data-i18n="mqtt.retain"></span> <select id="node-input-retain" style="width:125px !important">
|
||||
<option value=""></option>
|
||||
<option value="false">false</option>
|
||||
<option value="true">true</option>
|
||||
<option value="false" data-i18n="mqtt.false"></option>
|
||||
<option value="true" data-i18n="mqtt.true"></option>
|
||||
</select>
|
||||
</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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-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 type="text/x-red" data-help-name="mqtt out">
|
||||
@ -118,21 +118,21 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="mqtt-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%;" >
|
||||
<label for="node-config-input-port" style="margin-left: 10px; width: 35px; "> Port</label>
|
||||
<input type="text" id="node-config-input-port" placeholder="Port" style="width:45px">
|
||||
<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" data-i18n="[placeholder]mqtt.label.port" style="width:45px">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-clientid"><i class="fa fa-tag"></i> Client ID</label>
|
||||
<input type="text" id="node-config-input-clientid" placeholder="Leave blank for auto generated">
|
||||
<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" data-i18n="[placeholder]mqtt.placeholder.clientid">
|
||||
</div>
|
||||
<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">
|
||||
</div>
|
||||
<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">
|
||||
</div>
|
||||
</script>
|
||||
|
@ -42,7 +42,7 @@ module.exports = function(RED) {
|
||||
this.broker = n.broker;
|
||||
this.brokerConfig = RED.nodes.getNode(this.broker);
|
||||
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);
|
||||
var node = this;
|
||||
if (this.topic) {
|
||||
@ -55,22 +55,22 @@ module.exports = function(RED) {
|
||||
node.send(msg);
|
||||
}, this.id);
|
||||
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() {
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||
});
|
||||
if (this.client.isConnected()) {
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||
} else {
|
||||
this.client.connect();
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.error("topic not defined");
|
||||
this.error(RED._("mqtt.errors.not-defined"));
|
||||
}
|
||||
} else {
|
||||
this.error("missing broker configuration");
|
||||
this.error(RED._("mqtt.errors.missing-config"));
|
||||
}
|
||||
this.on('close', function() {
|
||||
if (this.client) {
|
||||
@ -90,7 +90,7 @@ module.exports = function(RED) {
|
||||
this.brokerConfig = RED.nodes.getNode(this.broker);
|
||||
|
||||
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);
|
||||
var node = this;
|
||||
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
|
||||
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() {
|
||||
node.status({fill:"red",shape:"ring",text:"disconnected"});
|
||||
node.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
|
||||
});
|
||||
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()) {
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||
} else {
|
||||
this.client.connect();
|
||||
}
|
||||
} else {
|
||||
this.error("missing broker configuration");
|
||||
this.error(RED._("mqtt.errors.missing-config"));
|
||||
}
|
||||
this.on('close', function() {
|
||||
if (this.client) {
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="http in">
|
||||
<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%;">
|
||||
<option value="get">GET</option>
|
||||
<option value="post">POST</option>
|
||||
@ -25,18 +25,18 @@
|
||||
</select>
|
||||
</div>
|
||||
<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">
|
||||
</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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-row 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">
|
||||
</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 type="text/x-red" data-help-name="http in">
|
||||
@ -67,10 +67,10 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="http response">
|
||||
<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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-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 type="text/x-red" data-help-name="http response">
|
||||
@ -86,45 +86,45 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="http request">
|
||||
<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%;">
|
||||
<option value="GET">GET</option>
|
||||
<option value="POST">POST</option>
|
||||
<option value="PUT">PUT</option>
|
||||
<option value="DELETE">DELETE</option>
|
||||
<option value="use">- set by msg.method -</option>
|
||||
<option value="use" data-i18n="httpin.setby"></option>
|
||||
</select>
|
||||
</div>
|
||||
<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://">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label> </label>
|
||||
<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 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">
|
||||
</div>
|
||||
<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">
|
||||
</div>
|
||||
<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%;">
|
||||
<option value="txt">a UTF-8 string</option>
|
||||
<option value="bin">a binary buffer</option>
|
||||
<option value="obj">a parsed JSON object</option>
|
||||
<option value="txt" data-i18n="httpin.utf8"></option>
|
||||
<option value="bin" data-i18n="httpin.binary"></option>
|
||||
<option value="obj" data-i18n="httpin.json"></option>
|
||||
</select>
|
||||
</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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-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 type="text/x-red" data-help-name="http request">
|
||||
@ -241,7 +241,7 @@
|
||||
outputs:1,
|
||||
icon: "white-globe.png",
|
||||
label: function() {
|
||||
return this.name||"http request";
|
||||
return this.name||this._("httpin.httpreq");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
|
@ -137,7 +137,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
});
|
||||
} 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);
|
||||
@ -172,7 +172,7 @@ module.exports = function(RED) {
|
||||
msg.res.send(statusCode,msg.payload);
|
||||
}
|
||||
} 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) {
|
||||
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;
|
||||
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) {
|
||||
url = mustache.render(nodeUrl,msg);
|
||||
}
|
||||
if (!url) {
|
||||
node.error("No url specified",msg);
|
||||
node.error(RED._("httpin.errors.no-url"),msg);
|
||||
return;
|
||||
}
|
||||
// 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";
|
||||
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")) {
|
||||
method = msg.method.toUpperCase(); // use the msg parameter
|
||||
@ -312,7 +312,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
else if (node.ret === "obj") {
|
||||
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.status({});
|
||||
|
@ -1,279 +1,276 @@
|
||||
<!--
|
||||
Copyright 2013 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.
|
||||
-->
|
||||
<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 -->
|
||||
<script type="text/x-red" data-template-name="websocket in">
|
||||
<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 in">
|
||||
<p>WebSocket input node.</p>
|
||||
<p>By default, the data received from the WebSocket will be in <b>msg.payload</b>.
|
||||
The socket can be configured to expect a properly formed JSON string, in which
|
||||
case it will parse the JSON and send on the resulting object as the entire message.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('websocket in',{
|
||||
category: 'input',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
server: {type:"websocket-listener", validate: ws_validateserver},
|
||||
client: {type:"websocket-client", validate: ws_validateclient}
|
||||
},
|
||||
color:"rgb(215, 215, 160)",
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "white-globe.png",
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
label: ws_label,
|
||||
oneditsave: ws_oneditsave,
|
||||
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',{
|
||||
category: 'output',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
server: {type:"websocket-listener", validate: ws_validateserver},
|
||||
client: {type:"websocket-client", validate: ws_validateclient}
|
||||
},
|
||||
color:"rgb(215, 215, 160)",
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "white-globe.png",
|
||||
align: "right",
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
label: ws_label,
|
||||
oneditsave: ws_oneditsave,
|
||||
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"> </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',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
|
||||
wholemsg: {value:"false"}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:0,
|
||||
label: function() {
|
||||
var root = RED.settings.httpNodeRoot;
|
||||
if (root.slice(-1) != "/") {
|
||||
root = root+"/";
|
||||
}
|
||||
if (this.path.charAt(0) == "/") {
|
||||
root += this.path.slice(1);
|
||||
} else {
|
||||
root += this.path;
|
||||
}
|
||||
return root;
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var root = RED.settings.httpNodeRoot;
|
||||
if (root.slice(-1) == "/") {
|
||||
root = root.slice(0,-1);
|
||||
}
|
||||
if (root == "") {
|
||||
$("#node-config-ws-tip").hide();
|
||||
} else {
|
||||
$("#node-config-ws-path").html(root);
|
||||
$("#node-config-ws-tip").show();
|
||||
}
|
||||
}
|
||||
});
|
||||
</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"> </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:// or wss:// 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',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
|
||||
wholemsg: {value:"false"}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:0,
|
||||
label: function() {
|
||||
return this.path;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<!--
|
||||
Copyright 2013 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.
|
||||
-->
|
||||
<!-- WebSocket Input Node -->
|
||||
<script type="text/x-red" data-template-name="websocket in">
|
||||
<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 in">
|
||||
<p>WebSocket input node.</p>
|
||||
<p>By default, the data received from the WebSocket will be in <b>msg.payload</b>.
|
||||
The socket can be configured to expect a properly formed JSON string, in which
|
||||
case it will parse the JSON and send on the resulting object as the entire message.</p>
|
||||
</script>
|
||||
|
||||
<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',{
|
||||
category: 'input',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
server: {type:"websocket-listener", validate: ws_validateserver},
|
||||
client: {type:"websocket-client", validate: ws_validateclient}
|
||||
},
|
||||
color:"rgb(215, 215, 160)",
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "white-globe.png",
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
label: ws_label,
|
||||
oneditsave: ws_oneditsave,
|
||||
oneditprepare: ws_oneditprepare
|
||||
});
|
||||
|
||||
RED.nodes.registerType('websocket out',{
|
||||
category: 'output',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
server: {type:"websocket-listener", validate: ws_validateserver},
|
||||
client: {type:"websocket-client", validate: ws_validateclient}
|
||||
},
|
||||
color:"rgb(215, 215, 160)",
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "white-globe.png",
|
||||
align: "right",
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
label: ws_label,
|
||||
oneditsave: ws_oneditsave,
|
||||
oneditprepare: ws_oneditprepare
|
||||
});
|
||||
|
||||
RED.nodes.registerType('websocket-listener',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
|
||||
wholemsg: {value:"false"}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:0,
|
||||
label: function() {
|
||||
var root = RED.settings.httpNodeRoot;
|
||||
if (root.slice(-1) != "/") {
|
||||
root = root+"/";
|
||||
}
|
||||
if (this.path.charAt(0) == "/") {
|
||||
root += this.path.slice(1);
|
||||
} else {
|
||||
root += this.path;
|
||||
}
|
||||
return root;
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var root = RED.settings.httpNodeRoot;
|
||||
if (root.slice(-1) == "/") {
|
||||
root = root.slice(0,-1);
|
||||
}
|
||||
if (root == "") {
|
||||
$("#node-config-ws-tip").hide();
|
||||
} else {
|
||||
$("#node-config-ws-path").html(root);
|
||||
$("#node-config-ws-tip").show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
RED.nodes.registerType('websocket-client',{
|
||||
category: 'config',
|
||||
defaults: {
|
||||
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/) },
|
||||
wholemsg: {value:"false"}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:0,
|
||||
label: function() {
|
||||
return this.path;
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
</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"> </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"> </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>
|
||||
|
||||
|
@ -1,232 +1,234 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var ws = require("ws");
|
||||
var inspect = require("sys").inspect;
|
||||
|
||||
// A node red node that sets up a local websocket server
|
||||
function WebSocketListenerNode(n) {
|
||||
// Create a RED node
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
|
||||
// Store local copies of the node configuration (as defined in the .html)
|
||||
node.path = n.path;
|
||||
node.wholemsg = (n.wholemsg === "true");
|
||||
|
||||
node._inputNodes = []; // collection of nodes that want to receive events
|
||||
node._clients = {};
|
||||
// match absolute url
|
||||
node.isServer = !/^ws{1,2}:\/\//i.test(node.path);
|
||||
node.closing = false;
|
||||
|
||||
function startconn() { // Connect to remote endpoint
|
||||
var socket = new ws(node.path);
|
||||
node.server = socket; // keep for closing
|
||||
handleConnection(socket);
|
||||
}
|
||||
|
||||
function handleConnection(/*socket*/socket) {
|
||||
var id = (1+Math.random()*4294967295).toString(16);
|
||||
if (node.isServer) { node._clients[id] = socket; node.emit('opened',Object.keys(node._clients).length); }
|
||||
socket.on('open',function() {
|
||||
if (!node.isServer) { node.emit('opened',''); }
|
||||
});
|
||||
socket.on('close',function() {
|
||||
if (node.isServer) { delete node._clients[id]; node.emit('opened',Object.keys(node._clients).length); }
|
||||
else { node.emit('closed'); }
|
||||
if (!node.closing && !node.isServer) {
|
||||
node.tout = setTimeout(function(){ startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
|
||||
}
|
||||
});
|
||||
socket.on('message',function(data,flags){
|
||||
node.handleEvent(id,socket,'message',data,flags);
|
||||
});
|
||||
socket.on('error', function(err) {
|
||||
node.emit('erro');
|
||||
if (!node.closing && !node.isServer) {
|
||||
node.tout = setTimeout(function(){ startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (node.isServer) {
|
||||
var path = RED.settings.httpNodeRoot || "/";
|
||||
path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path);
|
||||
|
||||
// Workaround https://github.com/einaros/ws/pull/253
|
||||
// Listen for 'newListener' events from RED.server
|
||||
node._serverListeners = {};
|
||||
|
||||
var storeListener = function(/*String*/event,/*function*/listener){
|
||||
if(event == "error" || event == "upgrade" || event == "listening"){
|
||||
node._serverListeners[event] = listener;
|
||||
}
|
||||
}
|
||||
|
||||
RED.server.addListener('newListener',storeListener);
|
||||
|
||||
// Create a WebSocket Server
|
||||
node.server = new ws.Server({server:RED.server,path:path});
|
||||
|
||||
// Workaround https://github.com/einaros/ws/pull/253
|
||||
// Stop listening for new listener events
|
||||
RED.server.removeListener('newListener',storeListener);
|
||||
|
||||
node.server.on('connection', handleConnection);
|
||||
}
|
||||
else {
|
||||
node.closing = false;
|
||||
startconn(); // start outbound connection
|
||||
}
|
||||
|
||||
node.on("close", function() {
|
||||
// Workaround https://github.com/einaros/ws/pull/253
|
||||
// Remove listeners from RED.server
|
||||
if (node.isServer) {
|
||||
var listener = null;
|
||||
for (var event in node._serverListeners) {
|
||||
if (node._serverListeners.hasOwnProperty(event)) {
|
||||
listener = node._serverListeners[event];
|
||||
if (typeof listener === "function") {
|
||||
RED.server.removeListener(event,listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
node._serverListeners = {};
|
||||
node.server.close();
|
||||
node._inputNodes = [];
|
||||
}
|
||||
else {
|
||||
node.closing = true;
|
||||
node.server.close();
|
||||
if (node.tout) { clearTimeout(tout); }
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("websocket-listener",WebSocketListenerNode);
|
||||
RED.nodes.registerType("websocket-client",WebSocketListenerNode);
|
||||
|
||||
WebSocketListenerNode.prototype.registerInputNode = function(/*Node*/handler) {
|
||||
this._inputNodes.push(handler);
|
||||
}
|
||||
|
||||
WebSocketListenerNode.prototype.handleEvent = function(id,/*socket*/socket,/*String*/event,/*Object*/data,/*Object*/flags){
|
||||
var msg;
|
||||
if (this.wholemsg) {
|
||||
try {
|
||||
msg = JSON.parse(data);
|
||||
}
|
||||
catch(err) {
|
||||
msg = { payload:data };
|
||||
}
|
||||
} else {
|
||||
msg = {
|
||||
payload:data
|
||||
};
|
||||
}
|
||||
msg._session = {type:"websocket",id:id};
|
||||
for (var i = 0; i < this._inputNodes.length; i++) {
|
||||
this._inputNodes[i].send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketListenerNode.prototype.broadcast = function(data) {
|
||||
try {
|
||||
if(this.isServer) {
|
||||
for (var i = 0; i < this.server.clients.length; i++) {
|
||||
this.server.clients[i].send(data);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.server.send(data);
|
||||
}
|
||||
}
|
||||
catch(e) { // swallow any errors
|
||||
this.warn("ws:"+i+" : "+e);
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketListenerNode.prototype.reply = function(id,data) {
|
||||
var session = this._clients[id];
|
||||
if (session) {
|
||||
try {
|
||||
session.send(data);
|
||||
}
|
||||
catch(e) { // swallow any errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function WebSocketInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.server = (n.client)?n.client:n.server;
|
||||
var node = this;
|
||||
this.serverConfig = RED.nodes.getNode(this.server);
|
||||
if (this.serverConfig) {
|
||||
this.serverConfig.registerInputNode(this);
|
||||
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('closed', function() { node.status({fill:"red",shape:"ring",text:"disconnected"}); });
|
||||
} else {
|
||||
this.error("Missing server configuration");
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("websocket in",WebSocketInNode);
|
||||
|
||||
function WebSocketOutNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
this.server = (n.client)?n.client:n.server;
|
||||
this.serverConfig = RED.nodes.getNode(this.server);
|
||||
if (!this.serverConfig) {
|
||||
this.error("Missing server configuration");
|
||||
}
|
||||
else {
|
||||
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('closed', function() { node.status({fill:"red",shape:"ring",text:"disconnected"}); });
|
||||
}
|
||||
this.on("input", function(msg) {
|
||||
var payload;
|
||||
if (this.serverConfig.wholemsg) {
|
||||
delete msg._session;
|
||||
payload = JSON.stringify(msg);
|
||||
} else if (msg.hasOwnProperty("payload")) {
|
||||
if (!Buffer.isBuffer(msg.payload)) { // if it's not a buffer make sure it's a string.
|
||||
payload = RED.util.ensureString(msg.payload);
|
||||
}
|
||||
else {
|
||||
payload = msg.payload;
|
||||
}
|
||||
}
|
||||
if (payload) {
|
||||
if (msg._session && msg._session.type == "websocket") {
|
||||
node.serverConfig.reply(msg._session.id,payload);
|
||||
} else {
|
||||
node.serverConfig.broadcast(payload,function(error){
|
||||
if (!!error) {
|
||||
node.warn("An error occurred while sending:" + inspect(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("websocket out",WebSocketOutNode);
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var ws = require("ws");
|
||||
var inspect = require("sys").inspect;
|
||||
|
||||
// A node red node that sets up a local websocket server
|
||||
function WebSocketListenerNode(n) {
|
||||
// Create a RED node
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
|
||||
// Store local copies of the node configuration (as defined in the .html)
|
||||
node.path = n.path;
|
||||
node.wholemsg = (n.wholemsg === "true");
|
||||
|
||||
node._inputNodes = []; // collection of nodes that want to receive events
|
||||
node._clients = {};
|
||||
// match absolute url
|
||||
node.isServer = !/^ws{1,2}:\/\//i.test(node.path);
|
||||
node.closing = false;
|
||||
|
||||
function startconn() { // Connect to remote endpoint
|
||||
var socket = new ws(node.path);
|
||||
node.server = socket; // keep for closing
|
||||
handleConnection(socket);
|
||||
}
|
||||
|
||||
function handleConnection(/*socket*/socket) {
|
||||
var id = (1+Math.random()*4294967295).toString(16);
|
||||
if (node.isServer) { node._clients[id] = socket; node.emit('opened',Object.keys(node._clients).length); }
|
||||
socket.on('open',function() {
|
||||
if (!node.isServer) { node.emit('opened',''); }
|
||||
});
|
||||
socket.on('close',function() {
|
||||
if (node.isServer) { delete node._clients[id]; node.emit('opened',Object.keys(node._clients).length); }
|
||||
else { node.emit('closed'); }
|
||||
if (!node.closing && !node.isServer) {
|
||||
node.tout = setTimeout(function(){ startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
|
||||
}
|
||||
});
|
||||
socket.on('message',function(data,flags){
|
||||
node.handleEvent(id,socket,'message',data,flags);
|
||||
});
|
||||
socket.on('error', function(err) {
|
||||
node.emit('erro');
|
||||
if (!node.closing && !node.isServer) {
|
||||
node.tout = setTimeout(function(){ startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (node.isServer) {
|
||||
var path = RED.settings.httpNodeRoot || "/";
|
||||
path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path);
|
||||
|
||||
// Workaround https://github.com/einaros/ws/pull/253
|
||||
// Listen for 'newListener' events from RED.server
|
||||
node._serverListeners = {};
|
||||
|
||||
var storeListener = function(/*String*/event,/*function*/listener){
|
||||
if(event == "error" || event == "upgrade" || event == "listening"){
|
||||
node._serverListeners[event] = listener;
|
||||
}
|
||||
}
|
||||
|
||||
RED.server.addListener('newListener',storeListener);
|
||||
|
||||
// Create a WebSocket Server
|
||||
node.server = new ws.Server({server:RED.server,path:path});
|
||||
|
||||
// Workaround https://github.com/einaros/ws/pull/253
|
||||
// Stop listening for new listener events
|
||||
RED.server.removeListener('newListener',storeListener);
|
||||
|
||||
node.server.on('connection', handleConnection);
|
||||
}
|
||||
else {
|
||||
node.closing = false;
|
||||
startconn(); // start outbound connection
|
||||
}
|
||||
|
||||
node.on("close", function() {
|
||||
// Workaround https://github.com/einaros/ws/pull/253
|
||||
// Remove listeners from RED.server
|
||||
if (node.isServer) {
|
||||
var listener = null;
|
||||
for (var event in node._serverListeners) {
|
||||
if (node._serverListeners.hasOwnProperty(event)) {
|
||||
listener = node._serverListeners[event];
|
||||
if (typeof listener === "function") {
|
||||
RED.server.removeListener(event,listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
node._serverListeners = {};
|
||||
node.server.close();
|
||||
node._inputNodes = [];
|
||||
}
|
||||
else {
|
||||
node.closing = true;
|
||||
node.server.close();
|
||||
if (node.tout) { clearTimeout(tout); }
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("websocket-listener",WebSocketListenerNode);
|
||||
RED.nodes.registerType("websocket-client",WebSocketListenerNode);
|
||||
|
||||
WebSocketListenerNode.prototype.registerInputNode = function(/*Node*/handler) {
|
||||
this._inputNodes.push(handler);
|
||||
}
|
||||
|
||||
WebSocketListenerNode.prototype.handleEvent = function(id,/*socket*/socket,/*String*/event,/*Object*/data,/*Object*/flags){
|
||||
var msg;
|
||||
if (this.wholemsg) {
|
||||
try {
|
||||
msg = JSON.parse(data);
|
||||
}
|
||||
catch(err) {
|
||||
msg = { payload:data };
|
||||
}
|
||||
} else {
|
||||
msg = {
|
||||
payload:data
|
||||
};
|
||||
}
|
||||
msg._session = {type:"websocket",id:id};
|
||||
for (var i = 0; i < this._inputNodes.length; i++) {
|
||||
this._inputNodes[i].send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketListenerNode.prototype.broadcast = function(data) {
|
||||
try {
|
||||
if(this.isServer) {
|
||||
for (var i = 0; i < this.server.clients.length; i++) {
|
||||
this.server.clients[i].send(data);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.server.send(data);
|
||||
}
|
||||
}
|
||||
catch(e) { // swallow any errors
|
||||
this.warn("ws:"+i+" : "+e);
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketListenerNode.prototype.reply = function(id,data) {
|
||||
var session = this._clients[id];
|
||||
if (session) {
|
||||
try {
|
||||
session.send(data);
|
||||
}
|
||||
catch(e) { // swallow any errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function WebSocketInNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
this.server = (n.client)?n.client:n.server;
|
||||
var node = this;
|
||||
this.serverConfig = RED.nodes.getNode(this.server);
|
||||
if (this.serverConfig) {
|
||||
this.serverConfig.registerInputNode(this);
|
||||
// TODO: nls
|
||||
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('closed', function() { node.status({fill:"red",shape:"ring",text:"disconnected"}); });
|
||||
} else {
|
||||
this.error(RED._("websocket.errors.missing-conf"));
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType("websocket in",WebSocketInNode);
|
||||
|
||||
function WebSocketOutNode(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
var node = this;
|
||||
this.server = (n.client)?n.client:n.server;
|
||||
this.serverConfig = RED.nodes.getNode(this.server);
|
||||
if (!this.serverConfig) {
|
||||
this.error(RED._("websocket.errors.missing-conf"));
|
||||
}
|
||||
else {
|
||||
// TODO: nls
|
||||
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('closed', function() { node.status({fill:"red",shape:"ring",text:"disconnected"}); });
|
||||
}
|
||||
this.on("input", function(msg) {
|
||||
var payload;
|
||||
if (this.serverConfig.wholemsg) {
|
||||
delete msg._session;
|
||||
payload = JSON.stringify(msg);
|
||||
} else if (msg.hasOwnProperty("payload")) {
|
||||
if (!Buffer.isBuffer(msg.payload)) { // if it's not a buffer make sure it's a string.
|
||||
payload = RED.util.ensureString(msg.payload);
|
||||
}
|
||||
else {
|
||||
payload = msg.payload;
|
||||
}
|
||||
}
|
||||
if (payload) {
|
||||
if (msg._session && msg._session.type == "websocket") {
|
||||
node.serverConfig.reply(msg._session.id,payload);
|
||||
} else {
|
||||
node.serverConfig.broadcast(payload,function(error){
|
||||
if (!!error) {
|
||||
node.warn(RED._("websocket.errors.send-error")+inspect(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
RED.nodes.registerType("websocket out",WebSocketOutNode);
|
||||
}
|
||||
|
@ -16,14 +16,14 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="watch">
|
||||
<div class="form-row node-input-filename">
|
||||
<label for="node-input-files"><i class="fa fa-file"></i> File(s)</label>
|
||||
<input type="text" id="node-input-files" placeholder="File(s) or Directory">
|
||||
<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" data-i18n="[placeholder]watch.placeholder.files">
|
||||
</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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div 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 type="text/x-red" data-help-name="watch">
|
||||
|
@ -16,43 +16,42 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="tcp in">
|
||||
<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;">
|
||||
<option value="server">Listen on</option>
|
||||
<option value="client">Connect to</option>
|
||||
<option value="server" data-i18n="tcpin.type.listen"></option>
|
||||
<option value="client" data-i18n="tcpin.type.connect"></option>
|
||||
</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 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 class="form-row">
|
||||
<label><i class="fa fa-sign-out"></i> Output</label>
|
||||
a
|
||||
<label><i class="fa fa-sign-out"></i> <span data-i18n="tcpin.label.output"></span></label>
|
||||
<select id="node-input-datamode" style="width:110px;">
|
||||
<option value="stream">stream of</option>
|
||||
<option value="single">single</option>
|
||||
<option value="stream" data-i18n="tcpin.output.stream"></option>
|
||||
<option value="single" data-i18n="tcpin.output.single"></option>
|
||||
</select>
|
||||
<select id="node-input-datatype" style="width:140px;">
|
||||
<option value="buffer">Buffer</option>
|
||||
<option value="utf8">String</option>
|
||||
<option value="base64">Base64 String</option>
|
||||
<option value="buffer" data-i18n="tcpin.output.buffer"></option>
|
||||
<option value="utf8" data-i18n="tcpin.output.string"></option>
|
||||
<option value="base64" data-i18n="tcpin.output.base64"></option>
|
||||
</select>
|
||||
payload<span id="node-input-datamode-plural">s</span>
|
||||
<span data-i18n="tcpin.label.payload"></span>
|
||||
</div>
|
||||
|
||||
<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 class="form-row">
|
||||
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
|
||||
<input type="text" id="node-input-topic" placeholder="Topic">
|
||||
<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" data-i18n="[placeholder]common.label.topic">
|
||||
</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">
|
||||
<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>
|
||||
|
||||
@ -96,14 +95,12 @@
|
||||
var datamode = $("#node-input-datamode option:selected").val();
|
||||
var datatype = $("#node-input-datatype option:selected").val();
|
||||
if (datamode == "stream") {
|
||||
$("#node-input-datamode-plural").show();
|
||||
if (datatype == "utf8") {
|
||||
$("#node-row-newline").show();
|
||||
} else {
|
||||
$("#node-row-newline").hide();
|
||||
}
|
||||
} else {
|
||||
$("#node-input-datamode-plural").hide();
|
||||
$("#node-row-newline").hide();
|
||||
}
|
||||
};
|
||||
@ -118,41 +115,34 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="tcp out">
|
||||
<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;">
|
||||
<option value="server">Listen on</option>
|
||||
<option value="client">Connect to</option>
|
||||
<option value="reply">Reply to TCP</option>
|
||||
<option value="server" data-i18n="tcpin.type.listen"></option>
|
||||
<option value="client" data-i18n="tcpin.type.connect"></option>
|
||||
<option value="reply" data-i18n="tcpin.type.reply"></option>
|
||||
</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 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 class="form-row hidden" id="node-input-end-row">
|
||||
<label> </label>
|
||||
<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 class="form-row">
|
||||
<label> </label>
|
||||
<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 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>
|
||||
|
||||
<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.
|
||||
<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>
|
||||
|
||||
@ -201,17 +191,8 @@
|
||||
|
||||
if (sockettype == "client") {
|
||||
$("#node-input-host-row").show();
|
||||
$("#fin-tip").show();
|
||||
} else {
|
||||
$("#node-input-host-row").hide();
|
||||
$("#fin-tip").hide();
|
||||
}
|
||||
|
||||
if (sockettype == "server") {
|
||||
$("#fin-tip2").show();
|
||||
}
|
||||
else {
|
||||
$("#fin-tip2").hide();
|
||||
}
|
||||
};
|
||||
updateOptions();
|
||||
@ -223,27 +204,25 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="tcp request">
|
||||
<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%">
|
||||
port <input type="text" id="node-input-port" placeholder="number" style="width:60px">
|
||||
port <input type="text" id="node-input-port" style="width:60px">
|
||||
</div>
|
||||
<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%;">
|
||||
<option value="time">after a fixed timeout of</option>
|
||||
<option value="char">when character received is</option>
|
||||
<option value="count">a fixed number of characters</option>
|
||||
<option value="sit">never. Keep connection open</option>
|
||||
<option value="time" data-i18n="tcpin.return.timeout"></option>
|
||||
<option value="char" data-i18n="tcpin.return.character"></option>
|
||||
<option value="count" data-i18n="tcpin.return.number"></option>
|
||||
<option value="sit" data-i18n="tcpin.return.never"></option>
|
||||
</select>
|
||||
<input type="text" id="node-input-splitc" style="width:50px;">
|
||||
<span id="node-units"></span>
|
||||
</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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-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>
|
||||
var previous = null;
|
||||
$("#node-input-out").on('focus', function () { previous = this.value; }).change(function() {
|
||||
|
@ -43,14 +43,14 @@ module.exports = function(RED) {
|
||||
var reconnectTimeout;
|
||||
var end = false;
|
||||
var setupTcpClient = function() {
|
||||
node.log("connecting to "+node.host+":"+node.port);
|
||||
node.status({fill:"grey",shape:"dot",text:"connecting"});
|
||||
node.log(RED._("tcpin.status.connecting",{host:node.host,port:node.port}));
|
||||
node.status({fill:"grey",shape:"dot",text:"common.status.connecting"});
|
||||
var id = (1+Math.random()*4294967295).toString(16);
|
||||
client = net.connect(node.port, node.host, function() {
|
||||
buffer = (node.datatype == 'buffer')? new Buffer(0):"";
|
||||
node.connected = true;
|
||||
node.log("connected to "+node.host+":"+node.port);
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port}));
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||
});
|
||||
connectionPool[id] = client;
|
||||
|
||||
@ -96,14 +96,14 @@ module.exports = function(RED) {
|
||||
client.on('close', function() {
|
||||
delete connectionPool[id];
|
||||
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 (end) { // if we were asked to close then try to reconnect once very quick.
|
||||
end = false;
|
||||
reconnectTimeout = setTimeout(setupTcpClient, 20);
|
||||
}
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
@ -128,7 +128,7 @@ module.exports = function(RED) {
|
||||
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
|
||||
var id = (1+Math.random()*4294967295).toString(16);
|
||||
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):"";
|
||||
socket.on('data', function (data) {
|
||||
@ -170,12 +170,12 @@ module.exports = function(RED) {
|
||||
}
|
||||
});
|
||||
socket.on('timeout', function() {
|
||||
node.log('timeout closed socket port '+node.port);
|
||||
node.log(RED._("tcpin.errors.timeout",{port:node.port}));
|
||||
socket.end();
|
||||
});
|
||||
socket.on('close', function() {
|
||||
delete connectionPool[id];
|
||||
node.status({text:--count+" connections"});
|
||||
node.status({text:RED._("tcpin.status.connections",{count:--count})});
|
||||
});
|
||||
socket.on('error',function(err) {
|
||||
node.log(err);
|
||||
@ -183,15 +183,15 @@ module.exports = function(RED) {
|
||||
});
|
||||
server.on('error', function(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) {
|
||||
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 {
|
||||
node.log('listening on port '+node.port);
|
||||
node.log(RED._("tcpin.status.listening-port",{port:node.port}));
|
||||
node.on('close', function() {
|
||||
for (var c in connectionPool) {
|
||||
if (connectionPool.hasOwnProperty(c)) {
|
||||
@ -201,7 +201,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
node.closing = true;
|
||||
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 setupTcpClient = function() {
|
||||
node.log("connecting to "+node.host+":"+node.port);
|
||||
node.status({fill:"grey",shape:"dot",text:"connecting"});
|
||||
node.log(RED._("tcpin.status.connecting",{host:node.host,port:node.port}));
|
||||
node.status({fill:"grey",shape:"dot",text:"common.status.connecting"});
|
||||
client = net.connect(node.port, node.host, function() {
|
||||
node.connected = true;
|
||||
node.log("connected to "+node.host+":"+node.port);
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port}));
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||
});
|
||||
client.on('error', function (err) {
|
||||
node.log(err);
|
||||
node.log(RED._("tcpin.errors.error",{error:err.toString()}));
|
||||
});
|
||||
client.on('end', function (err) {
|
||||
});
|
||||
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;
|
||||
client.destroy();
|
||||
if (!node.closing) {
|
||||
@ -250,7 +250,7 @@ module.exports = function(RED) {
|
||||
reconnectTimeout = setTimeout(setupTcpClient,20);
|
||||
}
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
@ -301,26 +301,26 @@ module.exports = function(RED) {
|
||||
});
|
||||
} else {
|
||||
var connectedSockets = [];
|
||||
node.status({text:"0 connections"});
|
||||
node.status({text:RED._("tcpin.status.connections",{count:0})});
|
||||
var server = net.createServer(function (socket) {
|
||||
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
|
||||
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);
|
||||
node.status({text:connectedSockets.length+" connections"});
|
||||
node.status({text:connectedSockets.length+" "+"tcpin.status.connections")});
|
||||
socket.on('timeout', function() {
|
||||
node.log('timeout closed socket port '+node.port);
|
||||
node.log(RED._("tcpin.errors.timeout",{port:node.port}));
|
||||
socket.end();
|
||||
});
|
||||
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);
|
||||
node.status({text:connectedSockets.length+" connections"});
|
||||
node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
|
||||
});
|
||||
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);
|
||||
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) {
|
||||
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) {
|
||||
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 {
|
||||
node.log('listening on port '+node.port);
|
||||
node.log(RED._("tcpin.status.listening-port",{port:node.port}));
|
||||
node.on('close', function() {
|
||||
for (var c in connectedSockets) {
|
||||
if (connectedSockets.hasOwnProperty(c)) {
|
||||
@ -360,7 +360,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
}
|
||||
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) {
|
||||
client.connect(port, host, function() {
|
||||
//node.log('client connected');
|
||||
node.status({fill:"green",shape:"dot",text:"connected"});
|
||||
//node.log(RED._("tcpin.errors.client-connected"));
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||
node.connected = true;
|
||||
client.write(msg.payload);
|
||||
});
|
||||
}
|
||||
else {
|
||||
node.warn("Host and/or port not set");
|
||||
node.warn(RED._("tcpin.errors.no-host"));
|
||||
}
|
||||
|
||||
client.on('data', function(data) {
|
||||
//node.log("data:"+ data.length+":"+ data);
|
||||
if (node.out == "sit") { // if we are staying connected just send the buffer
|
||||
node.send({"payload": data});
|
||||
}
|
||||
@ -477,18 +476,17 @@ module.exports = function(RED) {
|
||||
});
|
||||
|
||||
client.on('error', function() {
|
||||
node.error('connect failed',msg);
|
||||
node.status({fill:"red",shape:"ring",text:"error"});
|
||||
node.error(RED._("tcpin.errors.connect-fail"),msg);
|
||||
node.status({fill:"red",shape:"ring",text:"common.status.error"});
|
||||
if (client) { client.end(); }
|
||||
});
|
||||
|
||||
client.on('timeout',function() {
|
||||
node.warn('connect timeout');
|
||||
node.warn(RED._("tcpin.errors.connect-timeout"));
|
||||
if (client) {
|
||||
client.end();
|
||||
setTimeout(function() {
|
||||
client.connect(port, host, function() {
|
||||
//node.log('client connected');
|
||||
node.connected = true;
|
||||
client.write(msg.payload);
|
||||
});
|
||||
|
@ -17,54 +17,41 @@
|
||||
<!-- The Input Node -->
|
||||
<script type="text/x-red" data-template-name="udp in">
|
||||
<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%'>
|
||||
<option value="false">udp messages</option>
|
||||
<option value="true">multicast messages</option>
|
||||
<option value="false" data-i18n="udp.udpmsgs"></option>
|
||||
<option value="true" data-i18n="udp.mcmsgs"></option>
|
||||
</select>
|
||||
</div>
|
||||
<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">
|
||||
</div>
|
||||
<div class="form-row node-input-iface">
|
||||
<label for="node-input-iface"><i class="fa fa-random"></i> Interface</label>
|
||||
<input type="text" id="node-input-iface" placeholder="(optional) ip address of eth0">
|
||||
<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" data-i18n="[placeholder]udp.label.interface">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-port"><i class="fa fa-sign-in"></i> on Port</label>
|
||||
<input type="text" id="node-input-port" placeholder="Port" style="width: 80px">
|
||||
using <select id="node-input-ipv" style="width:80px">
|
||||
<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" style="width: 80px">
|
||||
<span data-i18n="udp.label.using"></span> <select id="node-input-ipv" style="width:80px">
|
||||
<option value="udp4">ipv4</option>
|
||||
<option value="udp6">ipv6</option>
|
||||
</select>
|
||||
</div>
|
||||
<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%;">
|
||||
<option value="buffer">a Buffer</option>
|
||||
<option value="utf8">a String</option>
|
||||
<option value="base64">a Base64 encoded string</option>
|
||||
<option value="buffer" data-i18n="udp.output.buffer"></option>
|
||||
<option value="utf8" data-i18n="udp.output.string"></option>
|
||||
<option value="base64" data-i18n="udp.output.base64"></option>
|
||||
</select>
|
||||
</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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-tips">Tip: Make sure your firewall will allow the data in.</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>
|
||||
<div class="form-tips"><span data-i18n="udp.tip.in"></span></div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="udp in">
|
||||
@ -97,6 +84,20 @@
|
||||
},
|
||||
labelStyle: function() {
|
||||
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>
|
||||
@ -105,62 +106,44 @@
|
||||
<!-- The Output Node -->
|
||||
<script type="text/x-red" data-template-name="udp out">
|
||||
<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%">
|
||||
<option value="false">udp message</option>
|
||||
<option value="broad">broadcast message</option>
|
||||
<option value="multi">multicast message</option>
|
||||
<option value="false" data-i18n="udp.udpmsg"></option>
|
||||
<option value="broad" data-i18n="udp.bcmsg"></option>
|
||||
<option value="multi" data-i18n="udp.mcmsg"></option>
|
||||
</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 class="form-row node-input-addr">
|
||||
<label for="node-input-addr" id="node-input-addr-label"><i class="fa fa-list"></i> Address</label>
|
||||
<input type="text" id="node-input-addr" placeholder="destination ip" style="width: 50%;">
|
||||
<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" data-i18n="[placeholder]udp.placeholder.address" style="width: 50%;">
|
||||
<select id="node-input-ipv" style="width:70px">
|
||||
<option value="udp4">ipv4</option>
|
||||
<option value="udp6">ipv6</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row node-input-iface">
|
||||
<label for="node-input-iface"><i class="fa fa-random"></i> Interface</label>
|
||||
<input type="text" id="node-input-iface" placeholder="(optional) ip address of eth0">
|
||||
<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" data-i18n="[placeholder]udp.placeholder.interface">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-outport-type"> </label>
|
||||
<select id="node-input-outport-type">
|
||||
<option id="node-input-outport-type-random" value="random">use random local port</option>
|
||||
<option value="fixed">bind to local port</option>
|
||||
<option id="node-input-outport-type-random" value="random" data-i18n="udp.bind.random"></option>
|
||||
<option value="fixed" data-i18n="udp.bind.local"></option>
|
||||
</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 class="form-row">
|
||||
<label> </label>
|
||||
<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 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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-tips">Tip: leave address and port blank if you want to set using <b>msg.ip</b> and <b>msg.port</b>.</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>
|
||||
<div class="form-tips"><span data-i18n="[html]udp.tip.out"></span></div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="udp out">
|
||||
@ -195,6 +178,12 @@
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
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";
|
||||
$("#node-input-outport-type option").filter(function() {
|
||||
return $(this).val() == type;
|
||||
@ -208,15 +197,30 @@
|
||||
$("#node-input-outport").show();
|
||||
}
|
||||
});
|
||||
|
||||
$("#node-input-outport-type").change();
|
||||
|
||||
$("#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();
|
||||
if (type == "false") {
|
||||
$("#node-input-outport-type-random").html("bind to random local port");
|
||||
$("#node-input-outport-type-random").html(bindrandom);
|
||||
} else {
|
||||
$("#node-input-outport-type-random").html("bind to target port");
|
||||
$("#node-input-outport-type-random").html(bindtarget);
|
||||
}
|
||||
});
|
||||
$("#node-input-multicast").change();
|
||||
|
@ -33,9 +33,9 @@ module.exports = function(RED) {
|
||||
|
||||
server.on("error", function (err) {
|
||||
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 {
|
||||
node.error("UDP error : "+err.code);
|
||||
node.error(RED._("udp.errors.error",{error:err.code}));
|
||||
}
|
||||
server.close();
|
||||
});
|
||||
@ -54,20 +54,20 @@ module.exports = function(RED) {
|
||||
|
||||
server.on('listening', function () {
|
||||
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") {
|
||||
server.setBroadcast(true);
|
||||
try {
|
||||
server.setMulticastTTL(128);
|
||||
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) {
|
||||
if (e.errno == "EINVAL") {
|
||||
node.error("Bad Multicast Address");
|
||||
node.error(RED._("udp.errors.bad-mcaddress"));
|
||||
} else if (e.errno == "ENODEV") {
|
||||
node.error("Must be ip address of the required interface");
|
||||
node.error(RED._("udp.errors.interface"));
|
||||
} 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() {
|
||||
try {
|
||||
server.close();
|
||||
node.log('udp listener stopped');
|
||||
node.log(RED._("udp.status.listener-stopped"));
|
||||
} catch (err) {
|
||||
node.error(err);
|
||||
}
|
||||
@ -118,25 +118,25 @@ module.exports = function(RED) {
|
||||
try {
|
||||
sock.setMulticastTTL(128);
|
||||
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) {
|
||||
if (e.errno == "EINVAL") {
|
||||
node.error("Bad Multicast Address");
|
||||
node.error(RED._("udp.errors.bad-mcaddress"));
|
||||
} else if (e.errno == "ENODEV") {
|
||||
node.error("Must be ip address of the required interface");
|
||||
node.error(RED._("udp.errors.interface"));
|
||||
} else {
|
||||
node.error("Error :"+e.errno);
|
||||
node.error(RED._("udp.errors.error",{error:e.errno}));
|
||||
}
|
||||
}
|
||||
} 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 != "") {
|
||||
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 {
|
||||
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) {
|
||||
@ -144,11 +144,11 @@ module.exports = function(RED) {
|
||||
var add = node.addr || msg.ip || "";
|
||||
var por = node.port || msg.port || 0;
|
||||
if (add == "") {
|
||||
node.warn("udp: ip address not set");
|
||||
node.warn(RED._("udp.errors.ip-notset"));
|
||||
} 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)) {
|
||||
node.warn("udp: port number not valid");
|
||||
node.warn(RED._("udp.errors.port-invalid"));
|
||||
} else {
|
||||
var message;
|
||||
if (node.base64) {
|
||||
@ -171,7 +171,7 @@ module.exports = function(RED) {
|
||||
node.on("close", function() {
|
||||
try {
|
||||
sock.close();
|
||||
node.log('udp output stopped');
|
||||
node.log(RED._("udp.status.output-stopped"));
|
||||
} catch (err) {
|
||||
node.error(err);
|
||||
}
|
||||
|
608
nodes/core/locales/en-US/messages.json
Normal file
608
nodes/core/locales/en-US/messages.json
Normal 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:// or wss:// 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__"
|
||||
}
|
||||
}
|
||||
}
|
@ -16,11 +16,11 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="switch">
|
||||
<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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
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 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;">
|
||||
@ -28,12 +28,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<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 class="form-row">
|
||||
<select id="node-input-checkall" style="width:100%; margin-right:5px;">
|
||||
<option value="true">checking all rules</option>
|
||||
<option value="false">stopping after first match</option>
|
||||
<option value="true" data-i18n="switch.checkall"></option>
|
||||
<option value="false" data-i18n="switch.stopfirst"></option>
|
||||
</select>
|
||||
</div>
|
||||
</script>
|
||||
@ -72,14 +72,14 @@
|
||||
{v:"lte",t:"<="},
|
||||
{v:"gt",t:">"},
|
||||
{v:"gte",t:">="},
|
||||
{v:"btwn",t:"is between"},
|
||||
{v:"cont",t:"contains"},
|
||||
{v:"regex",t:"matches regex"},
|
||||
{v:"true",t:"is true"},
|
||||
{v:"false",t:"is false"},
|
||||
{v:"null",t:"is null"},
|
||||
{v:"nnull",t:"is not null"},
|
||||
{v:"else",t:"otherwise"}
|
||||
{v:"btwn",t:this._("switch.rules.btwn")},
|
||||
{v:"cont",t:this._("switch.rules.cont")},
|
||||
{v:"regex",t:this._("switch.rules.regex")},
|
||||
{v:"true",t:this._("switch.rules.true")},
|
||||
{v:"false",t:this._("switch.rules.false")},
|
||||
{v:"null",t:this._("switch.rules.null")},
|
||||
{v:"nnull",t:this._("switch.rules.nnull")},
|
||||
{v:"else",t:this._("switch.rules.else")}
|
||||
];
|
||||
|
||||
function generateRule(i,rule) {
|
||||
|
@ -16,11 +16,11 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="change">
|
||||
<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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-row" 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 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;">
|
||||
@ -28,7 +28,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</script>
|
||||
|
||||
@ -68,23 +68,23 @@
|
||||
}
|
||||
if (!this.rules) {
|
||||
if (this.action === "replace") {
|
||||
return "set msg."+this.property;
|
||||
return this._("change.label.set",{property:"msg."+this.property});
|
||||
} else if (this.action === "change") {
|
||||
return "change msg."+this.property;
|
||||
return this._("change.label.change",{property:"msg."+this.property});
|
||||
} else {
|
||||
return this.action+" msg."+this.property
|
||||
return this._("change.label.delete",{property:"msg."+this.property});
|
||||
}
|
||||
} else {
|
||||
if (this.rules.length == 1) {
|
||||
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") {
|
||||
return "change msg."+this.rules[0].p;
|
||||
return this._("change.label.change",{property:"msg."+this.rules[0].p});
|
||||
} else {
|
||||
return "delete msg."+this.rules[0].p;
|
||||
return this._("change.label.delete",{property:"msg."+this.rules[0].p});
|
||||
}
|
||||
} 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" : "";
|
||||
},
|
||||
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); }
|
||||
$("#node-input-action").change();
|
||||
|
||||
@ -104,7 +111,7 @@
|
||||
var row3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(container);
|
||||
|
||||
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++) {
|
||||
selectField.append($("<option></option>").val(selectOptions[i].v).text(selectOptions[i].l));
|
||||
}
|
||||
@ -118,21 +125,21 @@
|
||||
$('<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 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 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 row3_3 = $('<div/>',{style:"margin-top:8px;"}).appendTo(row3);
|
||||
var id = "node-input-rule-property-regex-"+Math.floor(Math.random()*10000);
|
||||
var useRegExp = $('<input/>',{id:id,class:"node-input-rule-property-re",type:"checkbox", style:"margin-left: 150px; margin-right: 10px; display: inline-block; width: auto; vertical-align: top;"}).appendTo(row3_3);
|
||||
$('<label/>',{for:id,style:"width: auto;"}).text("Use regular expressions").appendTo(row3_3);
|
||||
$('<label/>',{for:id,style:"width: auto;"}).text(regex).appendTo(row3_3);
|
||||
|
||||
|
||||
selectField.change(function() {
|
||||
@ -147,7 +154,6 @@
|
||||
} else if (type == "delete") {
|
||||
row2.hide();
|
||||
row3.hide();
|
||||
$("#node-tip").hide();
|
||||
}
|
||||
});
|
||||
deleteButton.click(function() {
|
||||
|
@ -52,7 +52,7 @@ module.exports = function(RED) {
|
||||
rule.from = new RegExp(rule.from, "g");
|
||||
} catch (e) {
|
||||
valid = false;
|
||||
this.error("Invalid 'from' property: "+e.message);
|
||||
this.error(RED._("change.errors.invalid-from",{error:e.message}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,35 +16,35 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="range">
|
||||
<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;">
|
||||
<option value="scale">Scale msg.payload</option>
|
||||
<option value="clamp">Scale and limit to the target range</option>
|
||||
<option value="roll">Scale and wrap within the target range</option>
|
||||
<option value="scale" data-i18n="range.scale.payload"></option>
|
||||
<option value="clamp" data-i18n="range.scale.limit"></option>
|
||||
<option value="roll" data-i18n="range.scale.wrap"></option>
|
||||
</select>
|
||||
</div>
|
||||
<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>
|
||||
from: <input type="text" id="node-input-minin" placeholder="e.g. 0" style="width:100px;"/>
|
||||
to: <input type="text" id="node-input-maxin" placeholder="e.g. 99" 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;"/>
|
||||
<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 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>
|
||||
from: <input type="text" id="node-input-minout" placeholder="e.g. 0" style="width:100px;"/>
|
||||
to: <input type="text" id="node-input-maxout" placeholder="e.g. 255" 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;"/>
|
||||
<span data-i18n="range.label.to"></span>: <input type="text" id="node-input-maxout" data-i18n="[placeholder]range.placeholder.maxout" style="width:100px;"/>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="form-row"><label></label>
|
||||
<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>
|
||||
<br/>
|
||||
<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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-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 type="text/x-red" data-help-name="range">
|
||||
|
@ -42,7 +42,7 @@ module.exports = function(RED) {
|
||||
if (node.round) { msg.payload = Math.round(msg.payload); }
|
||||
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.
|
||||
});
|
||||
|
@ -17,52 +17,52 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="csv">
|
||||
<div class="form-row">
|
||||
<label for="node-input-temp"><i class="fa fa-list"></i> Columns</label>
|
||||
<input type="text" id="node-input-temp" placeholder="comma-separated column names">
|
||||
<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" data-i18n="[placeholder]csv.placeholder.columns">
|
||||
</div>
|
||||
<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">
|
||||
<option value=",">comma</option>
|
||||
<option value="\t">tab</option>
|
||||
<option value=" ">space</option>
|
||||
<option value=";">semicolon</option>
|
||||
<option value=":">colon</option>
|
||||
<option value="#">hashtag</option>
|
||||
<option value="">other...</option>
|
||||
<option value="," data-i18n="csv.separator.comma"></option>
|
||||
<option value="\t" data-i18n="csv.separator.tab"></option>
|
||||
<option value=" " data-i18n="csv.separator.space"></option>
|
||||
<option value=";" data-i18n="csv.separator.semicolon"></option>
|
||||
<option value=":" data-i18n="csv.separator.colon"></option>
|
||||
<option value="#" data-i18n="csv.separator.hashtag"></option>
|
||||
<option value="" data-i18n="csv.separator.other"></option>
|
||||
</select>
|
||||
<input style="width: 40px;" type="text" id="node-input-sep" pattern=".">
|
||||
</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">
|
||||
<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>
|
||||
<hr align="middle"/>
|
||||
<div class="form-row">
|
||||
<label style="width: 100%;"><i class="fa fa-gears"></i> CSV-to-Object options</label>
|
||||
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-in"></i> Input</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>
|
||||
<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> <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"><span data-i18n="csv.label.firstrow"></span></span>
|
||||
</div>
|
||||
<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;">
|
||||
<option value="one">a message per row</option>
|
||||
<option value="mult">a single message [array]</option>
|
||||
<option value="one" data-i18n="csv.output.row"></option>
|
||||
<option value="mult" data-i18n="csv.output.array"></option>
|
||||
</select>
|
||||
</div>
|
||||
<hr align="middle"/>
|
||||
<div class="form-row">
|
||||
<label style="width: 100%;"><i class="fa fa-gears"></i> Object-to-CSV options</label>
|
||||
<label style="margin-left: 10px; margin-right: -10px;"><i class="fa fa-sign-in"></i> Output</label>
|
||||
<input style="width: 30px" type="checkbox" id="node-input-hdrout"><label style="width: auto;" for="node-input-hdrout">include column name row</span>
|
||||
<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> <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"><span data-i18n="csv.label.includerow"></span></span>
|
||||
</div>
|
||||
<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">
|
||||
<option value='\n'>Linux (\n)</option>
|
||||
<option value='\r'>Mac (\r)</option>
|
||||
<option value='\r\n'>Windows (\r\n)</option>
|
||||
<option value='\n' data-i18n="csv.newline.linux"></option>
|
||||
<option value='\r' data-i18n="csv.newline.mac"></option>
|
||||
<option value='\r\n' data-i18n="csv.newline.windows"></option>
|
||||
</select>
|
||||
</div>
|
||||
</script>
|
||||
|
@ -163,7 +163,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
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.
|
||||
});
|
||||
|
@ -16,31 +16,31 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="html">
|
||||
<div class="form-row">
|
||||
<label for="node-input-tag"><i class="fa fa-filter"></i> Select</label>
|
||||
<input type="text" id="node-input-tag" placeholder="h1">
|
||||
<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" style="width:73% !important" placeholder="h1">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-ret"><i class="fa fa-sign-out"></i> Output</label>
|
||||
<select id="node-input-ret" style="width:73% !important">
|
||||
<option value="html">the html content of the elements</option>
|
||||
<option value="text">only the text content of the elements</option>
|
||||
<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:76% !important">
|
||||
<option value="html" data-i18n="html.output.html"></option>
|
||||
<option value="text"data-i18n="html.output.text"></option>
|
||||
<!-- <option value="attr">an object of any attributes</option> -->
|
||||
<!-- <option value="val">return the value from a form element</option> -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-as"> </label>
|
||||
<select id="node-input-as" style="width:73% !important">
|
||||
<option value="single">as a single message containing an array</option>
|
||||
<option value="multi">as multiple messages, one for each element</option>
|
||||
<select id="node-input-as" style="width:76% !important">
|
||||
<option value="single" data-i18n="html.format.single"></option>
|
||||
<option value="multi" data-i18n="html.format.multi"></option>
|
||||
</select>
|
||||
</div>
|
||||
<br/>
|
||||
<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">
|
||||
<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" style="width:73% !important" data-i18n="[placeholder]common.label.name">
|
||||
</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 type="text/x-red" data-help-name="html">
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="json">
|
||||
<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">
|
||||
<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>
|
||||
|
||||
|
@ -35,9 +35,9 @@ module.exports = function(RED) {
|
||||
msg.payload = JSON.stringify(msg.payload);
|
||||
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.
|
||||
});
|
||||
|
@ -16,37 +16,20 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="xml">
|
||||
<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" style="width:280px !important">
|
||||
<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" style="width:280px !important">
|
||||
</div>
|
||||
<div class="form-row" id="advanced">
|
||||
</div>
|
||||
<div id="advanced-options">
|
||||
<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 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 class="form-tips">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.</div>
|
||||
<div class="form-tips"><span data-i18n="xml.tip"></span></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 type="text/x-red" data-help-name="xml">
|
||||
@ -73,6 +56,23 @@
|
||||
},
|
||||
labelStyle: function() {
|
||||
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>
|
||||
|
@ -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.
|
||||
});
|
||||
|
@ -16,17 +16,17 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="tail">
|
||||
<div class="form-row node-input-filename">
|
||||
<label for="node-input-filename"><i class="fa fa-file"></i> Filename</label>
|
||||
<input type="text" id="node-input-filename" placeholder="Filename">
|
||||
<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">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label> </label>
|
||||
<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 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">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<!-- <div class="form-tips">WON'T work on Windows.</div> -->
|
||||
</script>
|
||||
|
@ -20,7 +20,7 @@ module.exports = function(RED) {
|
||||
var plat = require('os').platform();
|
||||
|
||||
if (plat.match(/^win/)) {
|
||||
throw "Info : Currently not supported on Windows.";
|
||||
throw RED._("tail.errors.windowsnotsupport");
|
||||
}
|
||||
|
||||
function TailNode(n) {
|
||||
|
@ -16,25 +16,25 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="file">
|
||||
<div class="form-row node-input-filename">
|
||||
<label for="node-input-filename"><i class="fa fa-file"></i> Filename</label>
|
||||
<input type="text" id="node-input-filename" placeholder="Filename">
|
||||
<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">
|
||||
</div>
|
||||
<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;">
|
||||
<option value="false">append to file</option>
|
||||
<option value="true">overwrite file</option>
|
||||
<option value="delete">delete file</option>
|
||||
<option value="false" data-i18n="file.action.append"></option>
|
||||
<option value="true" data-i18n="file.action.overwrite"></option>
|
||||
<option value="delete" data-i18n="file.action.delete"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="node-appline">
|
||||
<label> </label>
|
||||
<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 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">
|
||||
<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>
|
||||
|
||||
@ -48,19 +48,19 @@
|
||||
|
||||
<script type="text/x-red" data-template-name="file in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-filename"><i class="fa fa-file"></i> Filename</label>
|
||||
<input type="text" id="node-input-filename" placeholder="Filename">
|
||||
<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" data-i18n="[placeholder]file.label.filename">
|
||||
</div>
|
||||
<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">
|
||||
<option value="utf8">a utf8 string</option>
|
||||
<option value="">a Buffer</option>
|
||||
<option value="utf8" data-i18n="file.output.utf8"></option>
|
||||
<option value="" data-i18n="file.output.buffer"></option>
|
||||
</select>
|
||||
</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">
|
||||
<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>
|
||||
|
||||
@ -84,8 +84,11 @@
|
||||
icon: "file.png",
|
||||
align: "right",
|
||||
label: function() {
|
||||
if (this.overwriteFile === "delete") { return this.name||"delete "+this.filename; }
|
||||
else { return this.name||this.filename||"file"; }
|
||||
if (this.overwriteFile === "delete") {
|
||||
return this.name||this._("file.label.deletelabel",{file:this.filename})
|
||||
} else {
|
||||
return this.name||this.filename||this._("file.label.filelabel");
|
||||
}
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
@ -110,7 +113,7 @@
|
||||
outputs:1,
|
||||
icon: "file.png",
|
||||
label: function() {
|
||||
return this.name||this.filename||"file";
|
||||
return this.name||this.filename||this._("file.label.filelabel");
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
|
@ -27,18 +27,15 @@ module.exports = function(RED) {
|
||||
this.on("input",function(msg) {
|
||||
var filename = this.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) {
|
||||
node.status({fill:"grey",shape:"dot",text: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
|
||||
node.warn("Warning: Invalid delete. Please use specific delete option in config dialog.");
|
||||
//fs.unlink(filename, function (err) {
|
||||
//if (err) { node.error('failed to delete file : '+err,msg); }
|
||||
//});
|
||||
node.warn(RED._("file.errors.invaliddelete"));
|
||||
} else if (msg.payload && (typeof msg.payload != "undefined")) {
|
||||
var data = msg.payload;
|
||||
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
|
||||
//fs.writeFile(filename, data, {encoding:"binary"}, function (err) {
|
||||
fs.writeFile(filename, data, "binary", function (err) {
|
||||
if (err) { node.error('failed to write to file : '+err,msg); }
|
||||
else if (RED.settings.verbose) { node.log('wrote to file: '+filename); }
|
||||
if (err) { node.error(RED._("file.errors.writefail",{error:err.toString()}),msg); }
|
||||
else if (RED.settings.verbose) { node.log(RED._("file.status.wrotefile",{file:filename})); }
|
||||
});
|
||||
}
|
||||
else if (this.overwriteFile === "delete") {
|
||||
fs.unlink(filename, function (err) {
|
||||
if (err) { node.error('failed to delete file : '+err,msg); }
|
||||
else if (RED.settings.verbose) { node.log("deleted file: "+filename); }
|
||||
if (err) { node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg); }
|
||||
else if (RED.settings.verbose) { node.log(RED._("file.status.deletedfile",{file:filename})); }
|
||||
});
|
||||
}
|
||||
else {
|
||||
// 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, "binary", function (err) {
|
||||
if (err) { node.error('failed to append to file : '+err,msg); }
|
||||
else if (RED.settings.verbose) { node.log('appended to file: '+filename); }
|
||||
if (err) { node.error(RED._("file.errors.appendfail",{error:err.toString()}),msg); }
|
||||
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) {
|
||||
var filename = this.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) {
|
||||
node.status({fill:"grey",shape:"dot",text:filename});
|
||||
}
|
||||
if (filename === "") {
|
||||
node.warn('No filename specified');
|
||||
node.warn(RED._("file.errors.nofilename"));
|
||||
} else {
|
||||
msg.filename = filename;
|
||||
fs.readFile(filename,options,function(err,data) {
|
||||
|
@ -49,6 +49,7 @@
|
||||
"passport-http-bearer":"1.0.1",
|
||||
"passport-oauth2-client-password":"0.1.2",
|
||||
"oauth2orize":"1.0.1",
|
||||
"i18next":"1.7.10",
|
||||
"node-red-node-feedparser":"0.0.*",
|
||||
"node-red-node-email":"0.0.*",
|
||||
"node-red-node-twitter":"0.0.*"
|
||||
@ -69,6 +70,7 @@
|
||||
"grunt-contrib-jshint": "0.11.2",
|
||||
"grunt-contrib-uglify": "0.9.1",
|
||||
"grunt-contrib-watch":"0.6.1",
|
||||
"grunt-jsonlint":"1.0.4",
|
||||
"grunt-nodemon":"0.4.0",
|
||||
"grunt-sass":"1.0.0",
|
||||
"grunt-simple-mocha": "0.4.0",
|
||||
|
17
red.js
17
red.js
@ -23,6 +23,7 @@ var nopt = require("nopt");
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var RED = require("./red/red.js");
|
||||
var log = require("./red/log");
|
||||
|
||||
var server;
|
||||
var app = express();
|
||||
@ -166,7 +167,7 @@ try {
|
||||
}
|
||||
|
||||
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,
|
||||
express.basicAuth(function(user, 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) {
|
||||
server.on('error', function(err) {
|
||||
if (err.errno === "EADDRINUSE") {
|
||||
RED.log.error('Unable to listen on '+getListenPath());
|
||||
RED.log.error('Error: port in use');
|
||||
RED.log.error(log._("server.unable-to-listen", {listenpath:getListenPath()}));
|
||||
RED.log.error(log._("server.port-in-use"));
|
||||
} else {
|
||||
RED.log.error('Uncaught Exception:');
|
||||
RED.log.error(log._("server.uncaught-exception"));
|
||||
if (err.stack) {
|
||||
RED.log.error(err.stack);
|
||||
} else {
|
||||
@ -230,16 +231,16 @@ RED.start().then(function() {
|
||||
});
|
||||
server.listen(settings.uiPort,settings.uiHost,function() {
|
||||
if (settings.httpAdminRoot === false) {
|
||||
RED.log.info('Admin UI disabled');
|
||||
RED.log.info(log._("server.admin-ui-disabled"));
|
||||
}
|
||||
process.title = 'node-red';
|
||||
RED.log.info('Server now running at '+getListenPath());
|
||||
RED.log.info(log._("server.now-running", {listenpath:getListenPath()}));
|
||||
});
|
||||
} else {
|
||||
util.log('[red] Running in headless mode');
|
||||
RED.log.info(log._("server.headless-mode"));
|
||||
}
|
||||
}).otherwise(function(err) {
|
||||
RED.log.error("Failed to start server:");
|
||||
RED.log.error(log._("server.failed-to-start"));
|
||||
if (err.stack) {
|
||||
RED.log.error(err.stack);
|
||||
} else {
|
||||
|
@ -13,12 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* 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 redNodes = require("../nodes");
|
||||
var settings = require("../settings");
|
||||
|
||||
@ -34,7 +31,7 @@ module.exports = {
|
||||
redNodes.setFlows(flows,deploymentType).then(function() {
|
||||
res.send(204);
|
||||
}).otherwise(function(err) {
|
||||
log.warn("Error saving flows : "+err.message);
|
||||
log.warn(log._("api.flows.error-save",{message:err.message}));
|
||||
log.warn(err.stack);
|
||||
res.json(500,{error:"unexpected_error", message:err.message});
|
||||
});
|
||||
|
@ -25,6 +25,7 @@ var flows = require("./flows");
|
||||
var library = require("./library");
|
||||
var info = require("./info");
|
||||
var theme = require("./theme");
|
||||
var locales = require("./locales");
|
||||
|
||||
var auth = require("./auth");
|
||||
var needsPermission = auth.needsPermission;
|
||||
@ -85,6 +86,8 @@ function init(adminApp,storage) {
|
||||
adminApp.get("/nodes/:mod/:set",needsPermission("nodes.read"),nodes.getSet);
|
||||
adminApp.put("/nodes/:mod/:set",needsPermission("nodes.write"),nodes.putSet);
|
||||
|
||||
adminApp.get(/locales\/(.+)\/?$/,needsPermission("nodes.read"),locales.get);
|
||||
|
||||
// Library
|
||||
library.init(adminApp);
|
||||
adminApp.post(new RegExp("/library/flows\/(.*)"),needsPermission("library.write"),library.post);
|
||||
|
@ -17,6 +17,7 @@
|
||||
var redApp = null;
|
||||
var storage = require("../storage");
|
||||
var log = require("../log");
|
||||
|
||||
var needsPermission = require("./auth").needsPermission;
|
||||
|
||||
function createLibrary(type) {
|
||||
@ -34,8 +35,8 @@ function createLibrary(type) {
|
||||
}
|
||||
}).otherwise(function(err) {
|
||||
if (err) {
|
||||
log.warn("Error loading library entry '"+path+"' : "+err);
|
||||
if (err.message.indexOf('forbidden') === 0) {
|
||||
log.warn(log._("api.library.error-load-entry",{path:path,message:err.toString()}));
|
||||
if (err.code === 'forbidden') {
|
||||
log.audit({event: "library.get",type:type,error:"forbidden"},req);
|
||||
res.send(403);
|
||||
return;
|
||||
@ -56,8 +57,8 @@ function createLibrary(type) {
|
||||
log.audit({event: "library.set",type:type},req);
|
||||
res.send(204);
|
||||
}).otherwise(function(err) {
|
||||
log.warn("Error saving library entry '"+path+"' : "+err);
|
||||
if (err.message.indexOf('forbidden') === 0) {
|
||||
log.warn(log._("api.library.error-save-entry",{path:path,message:err.toString()}));
|
||||
if (err.code === 'forbidden') {
|
||||
log.audit({event: "library.set",type:type,error:"forbidden"},req);
|
||||
res.send(403);
|
||||
return;
|
||||
@ -88,8 +89,8 @@ module.exports = {
|
||||
res.send(data);
|
||||
}).otherwise(function(err) {
|
||||
if (err) {
|
||||
log.warn("Error loading flow '"+req.params[0]+"' : "+err);
|
||||
if (err.message.indexOf('forbidden') === 0) {
|
||||
log.warn(log._("api.library.error-load-flow",{path:req.params[0],message:err.toString()}));
|
||||
if (err.code === 'forbidden') {
|
||||
log.audit({event: "library.get",type:"flow",path:req.params[0],error:"forbidden"},req);
|
||||
res.send(403);
|
||||
return;
|
||||
@ -105,8 +106,8 @@ module.exports = {
|
||||
log.audit({event: "library.set",type:"flow",path:req.params[0]},req);
|
||||
res.send(204);
|
||||
}).otherwise(function(err) {
|
||||
log.warn("Error loading flow '"+req.params[0]+"' : "+err);
|
||||
if (err.message.indexOf('forbidden') === 0) {
|
||||
log.warn(log._("api.library.error-save-flow",{path:req.params[0],message:err.toString()}));
|
||||
if (err.code === 'forbidden') {
|
||||
log.audit({event: "library.set",type:"flow",path:req.params[0],error:"forbidden"},req);
|
||||
res.send(403);
|
||||
return;
|
||||
|
30
red/api/locales.js
Normal file
30
red/api/locales.js
Normal 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);
|
||||
}
|
||||
}
|
@ -13,16 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* 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 comms = require("../comms");
|
||||
var server = require("../server");
|
||||
var log = require("../log");
|
||||
var i18n = require("../i18n");
|
||||
|
||||
var when = require("when");
|
||||
|
||||
var settings = require("../settings");
|
||||
|
||||
@ -32,8 +29,9 @@ module.exports = {
|
||||
log.audit({event: "nodes.list.get"},req);
|
||||
res.json(redNodes.getNodeList());
|
||||
} else {
|
||||
var lang = i18n.determineLangFromHeaders(req.acceptedLanguages);
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
result = redNodes.getNodeConfig(id);
|
||||
var lang = i18n.determineLangFromHeaders(req.acceptedLanguages);
|
||||
result = redNodes.getNodeConfig(id,lang);
|
||||
if (result) {
|
||||
log.audit({event: "nodes.config.get",id:id},req);
|
||||
res.send(result);
|
||||
@ -232,12 +231,12 @@ function putNode(node, enabled) {
|
||||
return promise.then(function(info) {
|
||||
if (info.enabled === enabled && !info.err) {
|
||||
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++) {
|
||||
log.info(" - "+info.types[i]);
|
||||
}
|
||||
} else if (enabled && info.err) {
|
||||
log.warn("Failed to enable node:");
|
||||
log.warn(log._("api.nodes.error-enable"));
|
||||
log.warn(" - "+info.name+" : "+info.err);
|
||||
}
|
||||
return info;
|
||||
|
@ -65,7 +65,7 @@ function start() {
|
||||
try {
|
||||
msg = JSON.parse(data);
|
||||
} catch(err) {
|
||||
log.warn("comms received malformed message : "+err.toString());
|
||||
log.trace("comms received malformed message : "+err.toString());
|
||||
return;
|
||||
}
|
||||
if (!pendingAuth) {
|
||||
@ -119,12 +119,12 @@ function start() {
|
||||
}
|
||||
});
|
||||
ws.on('error', function(err) {
|
||||
log.warn("comms error : "+err.toString());
|
||||
log.warn(log._("comms.error",{message:err.toString()}));
|
||||
});
|
||||
});
|
||||
|
||||
wsServer.on('error', function(err) {
|
||||
log.warn("comms server error : "+err.toString());
|
||||
log.warn(log._("comms.error-server",{message:err.toString()}));
|
||||
});
|
||||
|
||||
lastSentTime = Date.now();
|
||||
@ -167,7 +167,7 @@ function publishTo(ws,topic,data) {
|
||||
try {
|
||||
ws.send(msg);
|
||||
} catch(err) {
|
||||
log.warn("comms send error : "+err.toString());
|
||||
log.warn(log._("comms.error-send",{message:err.toString()}));
|
||||
}
|
||||
}
|
||||
|
||||
|
151
red/i18n.js
Normal file
151
red/i18n.js
Normal 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);
|
||||
}
|
@ -17,6 +17,8 @@
|
||||
var util = require("util");
|
||||
var EventEmitter = require("events").EventEmitter;
|
||||
|
||||
var i18n = require("./i18n");
|
||||
|
||||
var levels = {
|
||||
off: 1,
|
||||
fatal: 10,
|
||||
@ -143,3 +145,5 @@ var log = module.exports = {
|
||||
log.log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
log["_"] = i18n._;
|
||||
|
@ -43,7 +43,7 @@ function createNode(type,config) {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Log.error("Unknown type: "+type);
|
||||
Log.error(Log._("nodes.flow.unknown-type", {type:type}));
|
||||
}
|
||||
return nn;
|
||||
}
|
||||
@ -368,7 +368,7 @@ Flow.prototype.start = function(configDiff) {
|
||||
|
||||
this.started = true;
|
||||
if (this.missingTypes.length > 0) {
|
||||
throw new Error("missing types");
|
||||
throw new Error(Log._("nodes.flow.missing-types"));
|
||||
}
|
||||
events.emit("nodes-starting");
|
||||
|
||||
@ -758,7 +758,7 @@ Flow.prototype.handleError = function(node,logMessage,msg) {
|
||||
if (msg.error.source.id === node.id) {
|
||||
count = msg.error.source.count+1;
|
||||
if (count === 10) {
|
||||
node.warn("Message exceeded maximum number of catches");
|
||||
node.warn(Log._("nodes.flow.error-loop"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
var when = require("when");
|
||||
|
||||
var log = require("../log");
|
||||
|
||||
var needsPermission = require("../api/auth").needsPermission;
|
||||
|
||||
var credentialCache = {};
|
||||
@ -75,7 +76,7 @@ module.exports = {
|
||||
return storage.getCredentials().then(function (creds) {
|
||||
credentialCache = creds;
|
||||
}).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 definition = credentialsDef[dashedType];
|
||||
if (!definition) {
|
||||
log.warn('Credential Type ' + nodeType + ' is not registered.');
|
||||
log.warn(log._("nodes.credentials.not-registered",{type:nodeType}));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ var typeRegistry = require("./registry");
|
||||
var credentials = require("./credentials");
|
||||
var Flow = require("./Flow");
|
||||
var log = require("../log");
|
||||
|
||||
var events = require("../events");
|
||||
var redUtil = require("../util");
|
||||
var storage = null;
|
||||
@ -36,7 +37,7 @@ var activeConfigNodes = {};
|
||||
|
||||
events.on('type-registered',function(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();
|
||||
});
|
||||
}).otherwise(function(err) {
|
||||
log.warn("Error loading flows : "+err);
|
||||
log.warn(log._("nodes.flows.error",{message:err.toString()}));
|
||||
console.log(err.stack);
|
||||
});
|
||||
},
|
||||
@ -132,36 +133,36 @@ var flowNodes = module.exports = {
|
||||
},
|
||||
startFlows: function(configDiff) {
|
||||
if (configDiff) {
|
||||
log.info("Starting modified "+configDiff.type);
|
||||
log.info(log._("nodes.flows.starting-modified-"+configDiff.type));
|
||||
} else {
|
||||
log.info("Starting flows");
|
||||
log.info(log._("nodes.flows.starting-flows"));
|
||||
}
|
||||
try {
|
||||
activeFlow.start(configDiff);
|
||||
if (configDiff) {
|
||||
log.info("Started modified "+configDiff.type);
|
||||
log.info(log._("nodes.flows.started-modified-"+configDiff.type));
|
||||
} else {
|
||||
log.info("Started flows");
|
||||
log.info(log._("nodes.flows.started-flows"));
|
||||
}
|
||||
} catch(err) {
|
||||
var missingTypes = activeFlow.getMissingTypes();
|
||||
if (missingTypes.length > 0) {
|
||||
log.info("Waiting for missing types to be registered:");
|
||||
log.info(log._("nodes.flows.missing-types"));
|
||||
var knownUnknowns = 0;
|
||||
for (var i=0;i<missingTypes.length;i++) {
|
||||
var type = missingTypes[i];
|
||||
var info = deprecated.get(type);
|
||||
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;
|
||||
} else {
|
||||
log.info(" - "+missingTypes[i]);
|
||||
}
|
||||
}
|
||||
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("in the directory:");
|
||||
log.info(log._("nodes.flows.missing-type-install-2"));
|
||||
log.info(" "+settings.userDir);
|
||||
}
|
||||
}
|
||||
@ -169,21 +170,21 @@ var flowNodes = module.exports = {
|
||||
},
|
||||
stopFlows: function(configDiff) {
|
||||
if (configDiff) {
|
||||
log.info("Stopping modified "+configDiff.type);
|
||||
log.info(log._("nodes.flows.stopping-modified-"+configDiff.type));
|
||||
} else {
|
||||
log.info("Stopping flows");
|
||||
log.info(log._("nodes.flows.stopping-flows"));
|
||||
}
|
||||
if (activeFlow) {
|
||||
return activeFlow.stop(configDiff).then(function() {
|
||||
if (configDiff) {
|
||||
log.info("Stopped modified "+configDiff.type);
|
||||
log.info(log._("nodes.flows.stopped-modified-"+configDiff.type));
|
||||
} else {
|
||||
log.info("Stopped flows");
|
||||
log.info(log._("nodes.flows.stopped-flows"));
|
||||
}
|
||||
return;
|
||||
});
|
||||
} else {
|
||||
log.info("Stopped");
|
||||
log.info(log._("nodes.flows.stopped"));
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
@ -17,6 +17,7 @@ var registry = require("./registry");
|
||||
var credentials = require("./credentials");
|
||||
var flows = require("./flows");
|
||||
var Node = require("./Node");
|
||||
var log = require("../log");
|
||||
|
||||
/**
|
||||
* Registers a node constructor
|
||||
@ -61,7 +62,7 @@ function init(_settings,storage,app) {
|
||||
function checkTypeInUse(id) {
|
||||
var nodeInfo = registry.getNodeInfo(id);
|
||||
if (!nodeInfo) {
|
||||
throw new Error("Unrecognised id: "+id);
|
||||
throw new Error(log._("nodes.index.unrecognised-id", {id:id}));
|
||||
} else {
|
||||
var inUse = {};
|
||||
var config = flows.getFlows();
|
||||
@ -76,7 +77,7 @@ function checkTypeInUse(id) {
|
||||
});
|
||||
if (nodesInUse.length > 0) {
|
||||
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";
|
||||
throw err;
|
||||
}
|
||||
@ -91,7 +92,7 @@ function removeNode(id) {
|
||||
function removeModule(module) {
|
||||
var info = registry.getModuleInfo(module);
|
||||
if (!info) {
|
||||
throw new Error("Unrecognised module: "+module);
|
||||
throw new Error(log._("nodes.index.unrecognised-module", {module:module}));
|
||||
} else {
|
||||
for (var i=0;i<info.nodes.length;i++) {
|
||||
checkTypeInUse(module+"/"+info.nodes[i].name);
|
||||
|
@ -26,8 +26,8 @@ var settings;
|
||||
|
||||
function init(_settings) {
|
||||
settings = _settings;
|
||||
registry.init(settings);
|
||||
loader.init(settings);
|
||||
registry.init(settings,loader);
|
||||
}
|
||||
//TODO: defaultNodesDir/disableNodePathScan are to make testing easier.
|
||||
// When the tests are componentized to match the new registry structure,
|
||||
|
@ -18,16 +18,26 @@ var when = require("when");
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
|
||||
var events = require("../../events");
|
||||
|
||||
var localfilesystem = require("./localfilesystem");
|
||||
var registry = require("./registry");
|
||||
|
||||
var RED;
|
||||
var settings;
|
||||
|
||||
var i18n = require("../../i18n");
|
||||
|
||||
events.on("node-locales-dir", function(info) {
|
||||
i18n.registerMessageCatalog(info.namespace,info.dir,info.file);
|
||||
});
|
||||
|
||||
function init(_settings) {
|
||||
settings = _settings;
|
||||
localfilesystem.init(settings);
|
||||
|
||||
RED = require('../../red');
|
||||
|
||||
}
|
||||
|
||||
function load(defaultNodesDir,disableNodePathScan) {
|
||||
@ -133,6 +143,7 @@ function loadNodeConfig(fileInfo) {
|
||||
node.types = [];
|
||||
node.err = err.toString();
|
||||
}
|
||||
resolve(node);
|
||||
} else {
|
||||
var types = [];
|
||||
|
||||
@ -143,8 +154,32 @@ function loadNodeConfig(fileInfo) {
|
||||
types.push(match[2]);
|
||||
}
|
||||
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
|
||||
//node.script = "";
|
||||
for (var i=0;i<node.types.length;i++) {
|
||||
@ -153,13 +188,38 @@ function loadNodeConfig(fileInfo) {
|
||||
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
|
||||
@ -180,7 +240,20 @@ function loadNodeSet(node) {
|
||||
var loadPromise = null;
|
||||
var r = require(node.file);
|
||||
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") {
|
||||
loadPromise = promise.then(function() {
|
||||
node.enabled = true;
|
||||
@ -229,7 +302,8 @@ function addModule(module) {
|
||||
}
|
||||
var nodes = [];
|
||||
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";
|
||||
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 = {
|
||||
init: init,
|
||||
load: load,
|
||||
addModule: addModule,
|
||||
addFile: addFile,
|
||||
loadNodeSet: loadNodeSet
|
||||
loadNodeSet: loadNodeSet,
|
||||
getNodeHelp: getNodeHelp
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ var fs = require("fs");
|
||||
var path = require("path");
|
||||
|
||||
var events = require("../../events");
|
||||
var log = require("../../log");
|
||||
|
||||
var settings;
|
||||
var defaultNodesDir = path.resolve(path.join(__dirname,"..","..","..","nodes"));
|
||||
@ -87,7 +88,7 @@ function getLocalNodeFiles(dir) {
|
||||
}
|
||||
} else if (stats.isDirectory()) {
|
||||
// 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)));
|
||||
} else if (fn === "icons") {
|
||||
events.emit("node-icon-dir",path.join(dir,fn));
|
||||
@ -196,6 +197,13 @@ function getNodeFiles(_defaultNodesDir,disableNodePathScan) {
|
||||
var nodeFiles = getLocalNodeFiles(path.resolve(defaultNodesDir));
|
||||
//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) {
|
||||
dir = path.join(settings.userDir,"nodes");
|
||||
nodeFiles = nodeFiles.concat(getLocalNodeFiles(dir));
|
||||
@ -244,7 +252,7 @@ function getModuleFiles(module) {
|
||||
|
||||
var moduleFiles = scanTreeForNodesModules(module);
|
||||
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';
|
||||
throw err;
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ var settings;
|
||||
|
||||
var Node;
|
||||
|
||||
var loader;
|
||||
|
||||
var nodeConfigCache = null;
|
||||
var moduleConfigs = {};
|
||||
var nodeList = [];
|
||||
@ -30,8 +32,9 @@ var nodeConstructors = {};
|
||||
var nodeTypeToId = {};
|
||||
var moduleNodes = {};
|
||||
|
||||
function init(_settings) {
|
||||
function init(_settings,_loader) {
|
||||
settings = _settings;
|
||||
loader = _loader;
|
||||
if (settings.available()) {
|
||||
moduleConfigs = loadNodeConfigs();
|
||||
} else {
|
||||
@ -323,7 +326,7 @@ function registerNodeConstructor(type,constructor) {
|
||||
events.emit("type-registered",type);
|
||||
}
|
||||
|
||||
function getAllNodeConfigs() {
|
||||
function getAllNodeConfigs(lang) {
|
||||
if (!nodeConfigCache) {
|
||||
var result = "";
|
||||
var script = "";
|
||||
@ -332,6 +335,7 @@ function getAllNodeConfigs() {
|
||||
var config = moduleConfigs[getModule(id)].nodes[getNode(id)];
|
||||
if (config.enabled && !config.err) {
|
||||
result += config.config;
|
||||
result += loader.getNodeHelp(config,lang||"en-US")||"";
|
||||
//script += config.script;
|
||||
}
|
||||
}
|
||||
@ -345,7 +349,7 @@ function getAllNodeConfigs() {
|
||||
return nodeConfigCache;
|
||||
}
|
||||
|
||||
function getNodeConfig(id) {
|
||||
function getNodeConfig(id,lang) {
|
||||
var config = moduleConfigs[getModule(id)];
|
||||
if (!config) {
|
||||
return null;
|
||||
@ -353,6 +357,8 @@ function getNodeConfig(id) {
|
||||
config = config.nodes[getNode(id)];
|
||||
if (config) {
|
||||
var result = config.config;
|
||||
result += loader.getNodeHelp(config,lang||"en-US")
|
||||
|
||||
//if (config.script) {
|
||||
// result += '<script type="text/javascript">'+config.script+'</script>';
|
||||
//}
|
||||
|
@ -20,6 +20,7 @@ var library = require("./api/library");
|
||||
var comms = require("./comms");
|
||||
var log = require("./log");
|
||||
var util = require("./util");
|
||||
var i18n = require("./i18n");
|
||||
var fs = require("fs");
|
||||
var settings = require("./settings");
|
||||
var credentials = require("./nodes/credentials");
|
||||
@ -75,4 +76,5 @@ var RED = {
|
||||
get httpNode() { return server.nodeApp },
|
||||
get server() { return server.server }
|
||||
};
|
||||
|
||||
module.exports = RED;
|
||||
|
@ -24,6 +24,7 @@ var redNodes = require("./nodes");
|
||||
var comms = require("./comms");
|
||||
var storage = require("./storage");
|
||||
var log = require("./log");
|
||||
var i18n = require("./i18n");
|
||||
|
||||
var app = null;
|
||||
var nodeApp = null;
|
||||
@ -44,7 +45,8 @@ function init(_server,_settings) {
|
||||
}
|
||||
|
||||
function start() {
|
||||
return storage.init(settings)
|
||||
return i18n.init()
|
||||
.then(function() { return storage.init(settings)})
|
||||
.then(function() { return settings.load(storage)})
|
||||
.then(function() {
|
||||
if (settings.httpAdminRoot !== false) {
|
||||
@ -56,12 +58,12 @@ function start() {
|
||||
reportMetrics();
|
||||
}, settings.runtimeMetricInterval||15000);
|
||||
}
|
||||
console.log("\n\nWelcome to Node-RED\n===================\n");
|
||||
console.log("\n\n"+log._("runtime.welcome")+"\n===================\n");
|
||||
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("Loading palette nodes");
|
||||
log.info(log._("runtime.version",{component:"Node.js ",version:process.version}));
|
||||
log.info(log._("server.loading"));
|
||||
redNodes.init(settings,storage,app);
|
||||
return redNodes.load().then(function() {
|
||||
|
||||
@ -75,13 +77,13 @@ function start() {
|
||||
log.warn("["+nodeErrors[i].name+"] "+nodeErrors[i].err);
|
||||
}
|
||||
} else {
|
||||
log.warn("Failed to register "+nodeErrors.length+" node type"+(nodeErrors.length==1?"":"s"));
|
||||
log.warn("Run with -v for details");
|
||||
log.warn(log._("server.errors",{count:nodeErrors.length}));
|
||||
log.warn(log._("server.errors-help"));
|
||||
}
|
||||
log.warn("------------------------------------------");
|
||||
}
|
||||
if (nodeMissing.length > 0) {
|
||||
log.warn("Missing node modules:");
|
||||
log.warn(log._("server.missing-modules"));
|
||||
var missingModules = {};
|
||||
for (i=0;i<nodeMissing.length;i++) {
|
||||
var missing = nodeMissing[i];
|
||||
@ -100,11 +102,11 @@ function start() {
|
||||
}
|
||||
}
|
||||
if (!settings.autoInstallModules) {
|
||||
log.info("Removing modules from config");
|
||||
log.info(log._("server.removing-modules"));
|
||||
redNodes.cleanModuleList();
|
||||
}
|
||||
}
|
||||
log.info("Settings file : "+settings.settingsFile);
|
||||
log.info(log._("runtime.paths.settings",{path:settings.settingsFile}));
|
||||
redNodes.loadFlows();
|
||||
comms.start();
|
||||
}).otherwise(function(err) {
|
||||
@ -117,7 +119,7 @@ function start() {
|
||||
function reportAddedModules(info) {
|
||||
comms.publish("node/added",info.nodes,false);
|
||||
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 j=0;j<info.nodes[i].types.length;j++) {
|
||||
log.info(" - "+
|
||||
@ -133,7 +135,7 @@ function reportAddedModules(info) {
|
||||
|
||||
function reportRemovedModules(removedNodes) {
|
||||
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 i=0;i<removedNodes[j].types.length;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'
|
||||
return when.promise(function(resolve,reject) {
|
||||
if (/[\s;]/.test(module)) {
|
||||
reject(new Error("Invalid module name"));
|
||||
reject(new Error(log._("server.install.invalid")));
|
||||
return;
|
||||
}
|
||||
if (redNodes.getModuleInfo(module)) {
|
||||
// TODO: nls
|
||||
var err = new Error("Module already loaded");
|
||||
err.code = "module_already_loaded";
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
log.info(i18n._("nodes.install.installing",{name: module}));
|
||||
|
||||
|
||||
log.info("Installing module: "+module);
|
||||
var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
|
||||
var child = child_process.exec('npm install --production '+module,
|
||||
{
|
||||
@ -180,19 +182,19 @@ function installModule(module) {
|
||||
if (err) {
|
||||
var lookFor404 = new RegExp(" 404 .*"+module+"$","m");
|
||||
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();
|
||||
e.code = 404;
|
||||
reject(e);
|
||||
} else {
|
||||
log.warn("Installation of module "+module+" failed:");
|
||||
log.warn(log._("server.install.install-failed-long",{name:module}));
|
||||
log.warn("------------------------------------------");
|
||||
log.warn(err.toString());
|
||||
log.warn("------------------------------------------");
|
||||
reject(new Error("Install failed"));
|
||||
reject(new Error(log._("server.install.install-failed")));
|
||||
}
|
||||
} else {
|
||||
log.info("Installed module: "+module);
|
||||
log.info(log._("server.install.installed",{name:module}));
|
||||
resolve(redNodes.addModule(module).then(reportAddedModules));
|
||||
}
|
||||
}
|
||||
@ -203,30 +205,30 @@ function installModule(module) {
|
||||
function uninstallModule(module) {
|
||||
return when.promise(function(resolve,reject) {
|
||||
if (/[\s;]/.test(module)) {
|
||||
reject(new Error("Invalid module name"));
|
||||
reject(new Error(log._("server.install.invalid")));
|
||||
return;
|
||||
}
|
||||
var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
|
||||
var moduleDir = path.join(installDir,"node_modules",module);
|
||||
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);
|
||||
log.info("Removing module: "+module);
|
||||
log.info(log._("server.install.uninstalling",{name:module}));
|
||||
var child = child_process.exec('npm remove '+module,
|
||||
{
|
||||
cwd: installDir
|
||||
},
|
||||
function(err, stdin, stdout) {
|
||||
if (err) {
|
||||
log.warn("Removal of module "+module+" failed:");
|
||||
log.warn(log._("server.install.uninstall-failed-long",{name:module}));
|
||||
log.warn("------------------------------------------");
|
||||
log.warn(err.toString());
|
||||
log.warn("------------------------------------------");
|
||||
reject(new Error("Removal failed"));
|
||||
reject(new Error(log._("server.install.uninstall-failed",{name:module})));
|
||||
} else {
|
||||
log.info("Removed module: "+module);
|
||||
log.info(log._("server.install.uninstalled",{name:module}));
|
||||
reportRemovedModules(list);
|
||||
resolve(list);
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
var when = require("when");
|
||||
var clone = require("clone");
|
||||
var assert = require("assert");
|
||||
var log = require("./log");
|
||||
|
||||
var userSettings = null;
|
||||
var globalSettings = null;
|
||||
@ -48,17 +49,17 @@ var persistentSettings = {
|
||||
return clone(userSettings[prop]);
|
||||
}
|
||||
if (globalSettings === null) {
|
||||
throw new Error("Settings not available");
|
||||
throw new Error(log._("settings.not-available"));
|
||||
}
|
||||
return clone(globalSettings[prop]);
|
||||
},
|
||||
|
||||
set: function(prop,value) {
|
||||
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) {
|
||||
throw new Error("Settings not available");
|
||||
throw new Error(log._("settings.not-available"));
|
||||
}
|
||||
var current = globalSettings[prop];
|
||||
globalSettings[prop] = value;
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
var when = require('when');
|
||||
var Path = require('path');
|
||||
var log = require("../log");
|
||||
|
||||
var storageModule;
|
||||
var settingsAvailable;
|
||||
@ -96,13 +97,17 @@ var storageModuleInterface = {
|
||||
|
||||
getLibraryEntry: function(type, 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);
|
||||
},
|
||||
saveLibraryEntry: function(type, path, meta, body) {
|
||||
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);
|
||||
},
|
||||
@ -117,7 +122,9 @@ var storageModuleInterface = {
|
||||
},
|
||||
getFlow: function(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")) {
|
||||
return storageModule.getFlow(fn);
|
||||
@ -128,7 +135,9 @@ var storageModuleInterface = {
|
||||
},
|
||||
saveFlow: function(fn, data) {
|
||||
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")) {
|
||||
return storageModule.saveFlow(fn, data);
|
||||
|
@ -185,15 +185,15 @@ var localfilesystem = {
|
||||
|
||||
getFlows: function() {
|
||||
return when.promise(function(resolve) {
|
||||
log.info("User Directory : "+settings.userDir);
|
||||
log.info("Flows file : "+flowsFullPath);
|
||||
log.info(log._("storage.localfilesystem.user-dir",{path:settings.userDir}));
|
||||
log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath}));
|
||||
fs.exists(flowsFullPath, function(exists) {
|
||||
if (exists) {
|
||||
resolve(nodeFn.call(fs.readFile,flowsFullPath,'utf8').then(function(data) {
|
||||
return JSON.parse(data);
|
||||
}));
|
||||
} else {
|
||||
log.info("Creating new flows file");
|
||||
log.info(log._("storage.localfilesystem.create"));
|
||||
resolve([]);
|
||||
}
|
||||
});
|
||||
@ -257,7 +257,7 @@ var localfilesystem = {
|
||||
try {
|
||||
return JSON.parse(data);
|
||||
} catch(err) {
|
||||
log.info("Corrupted config detected - resetting");
|
||||
log.trace("Corrupted config detected - resetting");
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
@ -277,7 +277,7 @@ var localfilesystem = {
|
||||
try {
|
||||
return JSON.parse(data);
|
||||
} catch(err) {
|
||||
log.info("Corrupted sessions file - resetting");
|
||||
log.trace("Corrupted sessions file - resetting");
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
|
@ -489,7 +489,7 @@ describe('websocket Node', function() {
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
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();
|
||||
});
|
||||
});
|
||||
@ -505,7 +505,7 @@ describe('websocket Node', function() {
|
||||
//console.log(logEvents);
|
||||
logEvents.should.have.length(1);
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
@ -134,7 +134,7 @@ describe('range Node', function() {
|
||||
|
||||
var sinon = require('sinon');
|
||||
sinon.stub(rangeNode1, 'log', function(log) {
|
||||
if(log.indexOf("Not a number") > -1) {
|
||||
if(log.indexOf("notnumber") > -1) {
|
||||
done();
|
||||
} else {
|
||||
try {
|
||||
|
@ -254,9 +254,9 @@ describe('CSV node', function() {
|
||||
});
|
||||
logEvents.should.have.length(2);
|
||||
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].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();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
|
@ -105,13 +105,13 @@ describe('JSON node', function() {
|
||||
//console.log(logEvents);
|
||||
logEvents.should.have.length(4);
|
||||
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].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].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].msg.toString().should.startWith('Dropped: ');
|
||||
logEvents[3][0].msg.toString().should.eql('json.errors.dropped-object');
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
|
@ -111,7 +111,7 @@ describe('XML node', function() {
|
||||
return evt[0].type == "xml";
|
||||
});
|
||||
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();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
|
@ -133,7 +133,7 @@ describe('file Nodes', function() {
|
||||
//console.log(logEvents);
|
||||
logEvents.should.have.length(1);
|
||||
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();
|
||||
}
|
||||
},wait);
|
||||
@ -180,7 +180,7 @@ describe('file Nodes', function() {
|
||||
//console.log(logEvents);
|
||||
logEvents.should.have.length(1);
|
||||
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();
|
||||
}
|
||||
catch(e) { done(e); }
|
||||
@ -205,7 +205,7 @@ describe('file Nodes', function() {
|
||||
//console.log(logEvents);
|
||||
logEvents.should.have.length(1);
|
||||
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();
|
||||
}
|
||||
catch(e) { done(e); }
|
||||
@ -230,7 +230,7 @@ describe('file Nodes', function() {
|
||||
//console.log(logEvents);
|
||||
logEvents.should.have.length(1);
|
||||
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();
|
||||
}
|
||||
catch(e) { done(e); }
|
||||
@ -318,7 +318,7 @@ describe('file Nodes', function() {
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
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();
|
||||
});
|
||||
n1.receive({payload:"",filename:"foo.txt"});
|
||||
@ -335,7 +335,7 @@ describe('file Nodes', function() {
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
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();
|
||||
},wait);
|
||||
n1.receive({});
|
||||
@ -352,7 +352,8 @@ describe('file Nodes', function() {
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
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();
|
||||
},wait);
|
||||
n1.receive({payload:""});
|
||||
|
@ -86,15 +86,28 @@ module.exports = {
|
||||
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);
|
||||
credentials.init(storage,express());
|
||||
RED.nodes.registerType("helper", helperNode);
|
||||
if (Array.isArray(testNode)) {
|
||||
for (var i = 0; i < testNode.length; i++) {
|
||||
testNode[i](RED);
|
||||
testNode[i](red);
|
||||
}
|
||||
} else {
|
||||
testNode(RED);
|
||||
testNode(red);
|
||||
}
|
||||
flows.load().then(function() {
|
||||
should.deepEqual(testFlows, flows.getFlows());
|
||||
|
0
test/red/api/locales_spec.js
Normal file
0
test/red/api/locales_spec.js
Normal file
0
test/red/i18n_spec.js
Normal file
0
test/red/i18n_spec.js
Normal file
@ -29,7 +29,7 @@ var auth = require("../../../red/api/auth");
|
||||
|
||||
|
||||
describe('Credentials', function() {
|
||||
|
||||
|
||||
afterEach(function() {
|
||||
index.clearRegistry();
|
||||
});
|
||||
@ -156,7 +156,7 @@ describe('Credentials', function() {
|
||||
|
||||
credentials.init(storage);
|
||||
credentials.load().then(function() {
|
||||
logmsg.should.equal("Error loading credentials : test forcing failure");
|
||||
log.warn.calledOnce.should.be.true;
|
||||
log.warn.restore();
|
||||
done();
|
||||
}).otherwise(function(err){
|
||||
@ -218,7 +218,7 @@ describe('Credentials', function() {
|
||||
index.loadFlows().then(function() {
|
||||
var testnode = new TestNode({id:'tab1',type:'test',name:'barney'});
|
||||
credentials.extract(testnode);
|
||||
should.equal(logmsg, 'Credential Type test is not registered.');
|
||||
log.warn.calledOnce.should.be.true;
|
||||
log.warn.restore();
|
||||
done();
|
||||
}).otherwise(function(err){
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user