Merge branch '0.18' into projects

This commit is contained in:
Nick O'Leary 2018-01-16 11:21:54 +00:00
commit 25f4a018d9
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
146 changed files with 6584 additions and 1498 deletions

30
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,30 @@
## Before you hit that Submit button....
This issue tracker is for problems with the Node-RED runtime, the editor or the core nodes.
If your issue is:
- a general 'how-to' type question,
- a feature request or suggestion for a change,
- or problems with 3rd party (`node-red-contrib-`) nodes
please use the [mailing list](https://groups.google.com/forum/#!forum/node-red), [slack team](https://nodered.org/slack) or ask a question on [Stack Overflow](https://stackoverflow.com/questions/tagged/node-red) and tag it `node-red`.
That way the whole Node-RED user community can help, rather than rely on the core development team.
## So you have a real issue to raise...
To help us understand the issue, please fill-in as much of the following information as you can:
### What are the steps to reproduce?
### What happens?
### What do you expect to happen?
### Please tell us about your environment:
- [ ] Node-RED version:
- [ ] node.js version:
- [ ] npm version:
- [ ] Platform/OS:
- [ ] Browser:

30
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,30 @@
## Before you hit that Submit button....
Please read our [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
before submitting a pull-request.
## Types of changes
What types of changes does your code introduce?
_Put an `x` in the boxes that apply_
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
If you want to raise a pull-request with a new feature, or a refactoring
of existing code, it **may well get rejected** if it hasn't been discussed on
the [mailing list](https://groups.google.com/forum/#!forum/node-red) or
[slack team](https://nodered.org/slack) first.
## Proposed changes
Describe the nature of this change. What problem does it address?
## Checklist
_Put an `x` in the boxes that apply_
- [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
- [ ] For non-bugfix PRs, I have discussed this change on the mailing list/slack team.
- [ ] I have run `grunt` to verify the unit tests pass
- [ ] I have added suitable unit tests to cover the new/changed functionality

View File

@ -11,7 +11,6 @@ addons:
- gcc-4.8
node_js:
- "8"
- "7"
- "6"
- "4"
script:

View File

@ -41,6 +41,11 @@ module.exports = function(grunt) {
core: { src: ["test/_spec.js","test/red/**/*_spec.js"]},
nodes: { src: ["test/nodes/**/*_spec.js"]}
},
webdriver: {
all: {
configFile: 'test/editor/wdio.conf.js'
}
},
mocha_istanbul: {
options: {
globals: ['expect'],
@ -419,6 +424,7 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-chmod');
grunt.loadNpmTasks('grunt-jsonlint');
grunt.loadNpmTasks('grunt-mocha-istanbul');
grunt.loadNpmTasks('grunt-webdriver');
grunt.registerMultiTask('attachCopyright', function() {
var files = this.data.src;
@ -478,6 +484,10 @@ module.exports = function(grunt) {
'Runs code style check on editor code',
['jshint:editor']);
grunt.registerTask('test-ui',
'Builds editor content then runs unit tests on editor ui',
['build','jshint:editor','webdriver:all']);
grunt.registerTask('test-nodes',
'Runs unit tests on core nodes',
['build','mocha_istanbul:nodes']);

View File

@ -14,7 +14,7 @@ A visual tool for wiring the Internet of Things.
Check out http://nodered.org/docs/getting-started/ for full instructions on getting
started.
1. `sudo npm install -g node-red`
1. `sudo npm install -g --unsafe-perm node-red`
2. `node-red`
3. Open <http://localhost:1880>

BIN
editor/icons/sort.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

View File

@ -64,27 +64,31 @@ RED.comms = (function() {
}
}
ws.onmessage = function(event) {
var msg = JSON.parse(event.data);
if (pendingAuth && msg.auth) {
if (msg.auth === "ok") {
pendingAuth = false;
completeConnection();
} else if (msg.auth === "fail") {
// anything else is an error...
active = false;
RED.user.login({updateMenu:true},function() {
connectWS();
})
var message = JSON.parse(event.data);
for (var m = 0; m < message.length; m++) {
var msg = message[m];
if (pendingAuth && msg.auth) {
if (msg.auth === "ok") {
pendingAuth = false;
completeConnection();
} else if (msg.auth === "fail") {
// anything else is an error...
active = false;
RED.user.login({updateMenu:true},function() {
connectWS();
})
}
}
} else if (msg.topic) {
for (var t in subscriptions) {
if (subscriptions.hasOwnProperty(t)) {
var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
if (re.test(msg.topic)) {
var subscribers = subscriptions[t];
if (subscribers) {
for (var i=0;i<subscribers.length;i++) {
subscribers[i](msg.topic,msg.data);
else if (msg.topic) {
for (var t in subscriptions) {
if (subscriptions.hasOwnProperty(t)) {
var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
if (re.test(msg.topic)) {
var subscribers = subscriptions[t];
if (subscribers) {
for (var i=0;i<subscribers.length;i++) {
subscribers[i](msg.topic,msg.data);
}
}
}
}

View File

@ -24,7 +24,25 @@
url: 'nodes',
success: function(data) {
RED.nodes.setNodeList(data);
RED.i18n.loadNodeCatalogs(loadNodes);
RED.i18n.loadNodeCatalogs(function() {
loadIconList(loadNodes);
});
}
});
}
function loadIconList(done) {
$.ajax({
headers: {
"Accept":"application/json"
},
cache: false,
url: 'icons',
success: function(data) {
RED.nodes.setIconSets(data);
if (done) {
done();
}
}
});
}
@ -95,6 +113,7 @@
});
return;
}
if (msg.text) {
var text = RED._(msg.text,{default:msg.text});
var options = {
@ -192,6 +211,7 @@
typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>";
RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
}
loadIconList();
} else if (topic == "notification/node/removed") {
for (i=0;i<msg.length;i++) {
m = msg[i];
@ -201,6 +221,7 @@
RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success");
}
}
loadIconList();
} else if (topic == "notification/node/enabled") {
if (msg.types) {
info = RED.nodes.getNodeSet(msg.id);

View File

@ -40,6 +40,7 @@ RED.nodes = (function() {
var nodeSets = {};
var typeToId = {};
var nodeDefinitions = {};
var iconSets = {};
nodeDefinitions['tab'] = {
defaults: {
@ -170,6 +171,12 @@ RED.nodes = (function() {
},
getNodeType: function(nt) {
return nodeDefinitions[nt];
},
setIconSets: function(sets) {
iconSets = sets;
},
getIconSets: function() {
return iconSets;
}
};
return exports;
@ -485,6 +492,12 @@ RED.nodes = (function() {
if (n.outputs > 0 && n.outputLabels && !/^\s*$/.test(n.outputLabels.join(""))) {
node.outputLabels = n.outputLabels.slice();
}
if (!n._def.defaults.hasOwnProperty("icon") && n.icon) {
var defIcon = RED.utils.getDefaultNodeIcon(n._def, n);
if (n.icon !== defIcon.module+"/"+defIcon.file) {
node.icon = n.icon;
}
}
}
return node;
}
@ -915,6 +928,7 @@ RED.nodes = (function() {
wires:n.wires,
inputLabels: n.inputLabels,
outputLabels: n.outputLabels,
icon: n.icon,
changed:false,
_config:{}
};
@ -1273,6 +1287,9 @@ RED.nodes = (function() {
enableNodeSet: registry.enableNodeSet,
disableNodeSet: registry.disableNodeSet,
setIconSets: registry.setIconSets,
getIconSets: registry.getIconSets,
registerType: registry.registerNodeType,
getType: registry.getNodeType,
convertNode: convertNode,

View File

@ -99,7 +99,7 @@
this.uiSelect = this.elementDiv.wrap( "<div>" ).parent();
var attrStyle = this.element.attr('style');
var m;
if ((m = /width\s*:\s*(\d+(%|px))/i.exec(attrStyle)) !== null) {
if ((m = /width\s*:\s*(calc\s*\(.*\)|\d+(%|px))/i.exec(attrStyle)) !== null) {
this.element.css('width','100%');
this.uiSelect.width(m[1]);
this.uiWidth = null;
@ -354,10 +354,27 @@
return this.element.val();
} else {
if (this.typeMap[this.propertyType].options) {
if (this.typeMap[this.propertyType].options.indexOf(value) === -1) {
value = "";
var validValue = false;
var label;
for (var i=0;i<this.typeMap[this.propertyType].options.length;i++) {
var op = this.typeMap[this.propertyType].options[i];
if (typeof op === "string") {
if (op === value) {
label = value;
validValue = true;
break;
}
} else if (op.value === value) {
label = op.label||op.value;
validValue = true;
break;
}
}
this.optionSelectLabel.text(value);
if (!validValue) {
value = "";
label = "";
}
this.optionSelectLabel.text(label);
}
this.element.val(value);
this.element.trigger('change',this.type(),value);
@ -394,11 +411,31 @@
that.value(v);
});
var currentVal = this.element.val();
if (opt.options.indexOf(currentVal) !== -1) {
this.optionSelectLabel.text(currentVal);
} else {
this.value(opt.options[0]);
var validValue = false;
var op;
for (var i=0;i<opt.options.length;i++) {
op = opt.options[i];
if (typeof op === "string") {
if (op === currentVal) {
this.optionSelectLabel.text(currentVal);
validValue = true;
break;
}
} else if (op.value === currentVal) {
this.optionSelectLabel.text(op.label||op.value);
validValue = true;
break;
}
}
if (!validValue) {
op = opt.options[0];
if (typeof op === "string") {
this.value(op);
} else {
this.value(op.value);
}
}
console.log(validValue);
}
} else {
if (this.optionMenu) {

View File

@ -108,6 +108,17 @@ RED.editor = (function() {
}
}
}
if (node.icon) {
var iconPath = RED.utils.separateIconPath(node.icon);
if (!iconPath.module) {
return isValid;
}
var iconSets = RED.nodes.getIconSets();
var iconFileList = iconSets[iconPath.module];
if (!iconFileList || iconFileList.indexOf(iconPath.file) === -1) {
isValid = false;
}
}
return isValid;
}
@ -159,6 +170,23 @@ RED.editor = (function() {
}
}
}
if (!node._def.defaults.hasOwnProperty("icon") && node.icon) {
var iconPath = RED.utils.separateIconPath(node.icon);
var iconSets = RED.nodes.getIconSets();
var iconFileList = iconSets[iconPath.module];
var iconModule = $("#node-settings-icon-module");
var iconFile = $("#node-settings-icon-file");
if (!iconFileList) {
iconModule.addClass("input-error");
iconFile.removeClass("input-error");
} else if (iconFileList.indexOf(iconPath.file) === -1) {
iconModule.removeClass("input-error");
iconFile.addClass("input-error");
} else {
iconModule.removeClass("input-error");
iconFile.removeClass("input-error");
}
}
}
function validateNodeEditorProperty(node,defaults,property,prefix) {
var input = $("#"+prefix+"-"+property);
@ -713,10 +741,78 @@ RED.editor = (function() {
} else {
buildLabelRow().appendTo(outputsDiv);
}
if (!node._def.defaults.hasOwnProperty("icon")) {
$('<div class="form-row"><div id="node-settings-icon"></div></div>').appendTo(dialogForm);
var iconDiv = $("#node-settings-icon");
$('<label data-i18n="editor.settingIcon">').appendTo(iconDiv);
var iconForm = $('<div>',{class:"node-label-form-row"});
iconForm.appendTo(iconDiv);
$('<label>').appendTo(iconForm);
var selectIconModule = $('<select id="node-settings-icon-module"><option value=""></option></select>').appendTo(iconForm);
var iconPath;
if (node.icon) {
iconPath = RED.utils.separateIconPath(node.icon);
} else {
iconPath = RED.utils.getDefaultNodeIcon(node._def, node);
}
var iconSets = RED.nodes.getIconSets();
Object.keys(iconSets).forEach(function(moduleName) {
selectIconModule.append($("<option></option>").val(moduleName).text(moduleName));
});
if (iconPath.module && !iconSets[iconPath.module]) {
selectIconModule.append($("<option disabled></option>").val(iconPath.module).text(iconPath.module));
}
selectIconModule.val(iconPath.module);
var iconModuleHidden = $('<input type="hidden" id="node-settings-icon-module-hidden"></input>').appendTo(iconForm);
iconModuleHidden.val(iconPath.module);
var selectIconFile = $('<select id="node-settings-icon-file"><option value=""></option></select>').appendTo(iconForm);
selectIconModule.change(function() {
moduleChange(selectIconModule, selectIconFile, iconModuleHidden, iconFileHidden, iconSets, true);
});
var iconFileHidden = $('<input type="hidden" id="node-settings-icon-file-hidden"></input>').appendTo(iconForm);
iconFileHidden.val(iconPath.file);
selectIconFile.change(function() {
selectIconFile.removeClass("input-error");
var fileName = selectIconFile.val();
iconFileHidden.val(fileName);
});
moduleChange(selectIconModule, selectIconFile, iconModuleHidden, iconFileHidden, iconSets, false);
var iconFileList = iconSets[selectIconModule.val()];
if (!iconFileList || iconFileList.indexOf(iconPath.file) === -1) {
selectIconFile.append($("<option disabled></option>").val(iconPath.file).text(iconPath.file));
}
selectIconFile.val(iconPath.file);
}
}
function moduleChange(selectIconModule, selectIconFile, iconModuleHidden, iconFileHidden, iconSets, updateIconFile) {
selectIconFile.children().remove();
var moduleName = selectIconModule.val();
if (moduleName !== null) {
iconModuleHidden.val(moduleName);
}
var iconFileList = iconSets[moduleName];
if (iconFileList) {
iconFileList.forEach(function(fileName) {
if (updateIconFile) {
updateIconFile = false;
iconFileHidden.val(fileName);
}
selectIconFile.append($("<option></option>").val(fileName).text(fileName));
});
}
selectIconFile.prop("disabled", !iconFileList);
selectIconModule.removeClass("input-error");
}
function showEditDialog(node) {
var editing_node = node;
var isDefaultIcon;
var defaultIcon;
editStack.push(node);
RED.view.state(RED.state.EDITING);
var type = node.type;
@ -885,6 +981,8 @@ RED.editor = (function() {
if (outputsChanged) {
changed = true;
}
} else {
newValue = parseInt(newValue);
}
}
if (editing_node[d] != newValue) {
@ -963,6 +1061,33 @@ RED.editor = (function() {
changed = true;
}
if (!editing_node._def.defaults.hasOwnProperty("icon")) {
var iconModule = $("#node-settings-icon-module-hidden").val();
var iconFile = $("#node-settings-icon-file-hidden").val();
var icon = (iconModule && iconFile) ? iconModule+"/"+iconFile : "";
if (!isDefaultIcon) {
if (icon !== editing_node.icon) {
changes.icon = editing_node.icon;
editing_node.icon = icon;
changed = true;
}
} else {
if (icon !== defaultIcon) {
changes.icon = editing_node.icon;
editing_node.icon = icon;
changed = true;
} else {
var iconPath = RED.utils.getDefaultNodeIcon(editing_node._def, editing_node);
var currentDefaultIcon = iconPath.module+"/"+iconPath.file;
if (defaultIcon !== currentDefaultIcon) {
changes.icon = editing_node.icon;
editing_node.icon = currentDefaultIcon;
changed = true;
}
}
}
}
if (changed) {
var wasChanged = editing_node.changed;
editing_node.changed = true;
@ -1054,6 +1179,13 @@ RED.editor = (function() {
} else {
ns = node._def.set.id;
}
var iconPath = RED.utils.getDefaultNodeIcon(node._def,node);
defaultIcon = iconPath.module+"/"+iconPath.file;
if (node.icon && node.icon !== defaultIcon) {
isDefaultIcon = false;
} else {
isDefaultIcon = true;
}
buildEditForm(nodeProperties.content,"dialog-form",type,ns);
buildLabelForm(portLabels.content,node);
@ -1841,12 +1973,12 @@ RED.editor = (function() {
tabs.addTab({
id: 'expression-help',
label: 'Function reference',
label: RED._('expressionEditor.functionReference'),
content: $("#node-input-expression-tab-help")
});
tabs.addTab({
id: 'expression-tests',
label: 'Test',
label: RED._('expressionEditor.test'),
content: $("#node-input-expression-tab-test")
});
testDataEditor = RED.editor.createEditor({

View File

@ -14,6 +14,31 @@
* limitations under the License.
**/
RED.notify = (function() {
/*
// Example usage for a modal dialog with buttons
var myNotification = RED.notify("This is the message to display",{
modal: true,
fixed: true,
type: 'warning',
buttons: [
{
text: "cancel",
click: function(e) {
myNotification.close();
}
},
{
text: "okay",
class:"primary",
click: function(e) {
myNotification.close();
}
}
]
});
*/
var currentNotifications = [];
var c = 0;
return function(msg,type,fixed,timeout) {

View File

@ -21,7 +21,7 @@ RED.palette = (function() {
var categoryContainers = {};
function createCategoryContainer(category, label){
function createCategoryContainer(category, label) {
label = (label || category).replace(/_/g, " ");
var catDiv = $('<div id="palette-container-'+category+'" class="palette-category palette-close hide">'+
'<div id="palette-header-'+category+'" class="palette-header"><i class="expanded fa fa-angle-down"></i><span>'+label+'</span></div>'+
@ -325,14 +325,26 @@ RED.palette = (function() {
}
}
}
function hideNodeType(nt) {
var nodeTypeId = escapeNodeType(nt);
$("#palette_node_"+nodeTypeId).hide();
var paletteNode = $("#palette_node_"+nodeTypeId);
paletteNode.hide();
var categoryNode = paletteNode.closest(".palette-category");
var cl = categoryNode.find(".palette_node");
var c = 0;
for (var i = 0; i < cl.length; i++) {
if ($(cl[i]).css('display') === 'none') { c += 1; }
}
if (c === cl.length) { categoryNode.hide(); }
}
function showNodeType(nt) {
var nodeTypeId = escapeNodeType(nt);
$("#palette_node_"+nodeTypeId).show();
var paletteNode = $("#palette_node_"+nodeTypeId);
var categoryNode = paletteNode.closest(".palette-category");
categoryNode.show();
paletteNode.show();
}
function refreshNodeTypes() {
@ -396,7 +408,6 @@ RED.palette = (function() {
RED.events.on('registry:node-type-removed', function(nodeType) {
removeNodeType(nodeType);
});
RED.events.on('registry:node-set-enabled', function(nodeSet) {
for (var j=0;j<nodeSet.types.length;j++) {
showNodeType(nodeSet.types[j]);
@ -427,7 +438,6 @@ RED.palette = (function() {
}
});
$("#palette > .palette-spinner").show();
$("#palette-search input").searchBox({

View File

@ -12,6 +12,7 @@ RED.typeSearch = (function() {
var activeFilter = "";
var addCallback;
var cancelCallback;
var typesUsed = {};
@ -155,11 +156,19 @@ RED.typeSearch = (function() {
t = t.parent();
}
hide(true);
if (cancelCallback) {
cancelCallback();
}
}
}
function show(opts) {
if (!visible) {
RED.keyboard.add("*","escape",function(){hide()});
RED.keyboard.add("*","escape",function(){
hide();
if (cancelCallback) {
cancelCallback();
}
});
if (dialog === null) {
createDialog();
}
@ -175,6 +184,7 @@ RED.typeSearch = (function() {
}
refreshTypeList();
addCallback = opts.add;
closeCallback = opts.close;
RED.events.emit("type-search:open");
//shade.show();
dialog.css({left:opts.x+"px",top:opts.y+"px"}).show();
@ -255,16 +265,19 @@ RED.typeSearch = (function() {
var commonCount = 0;
var item;
for(i=0;i<common.length;i++) {
item = {
type: common[i],
common: true,
def: RED.nodes.getType(common[i])
};
item.label = getTypeLabel(item.type,item.def);
if (i === common.length-1) {
item.separator = true;
var itemDef = RED.nodes.getType(common[i]);
if (itemDef) {
item = {
type: common[i],
common: true,
def: itemDef
};
item.label = getTypeLabel(item.type,item.def);
if (i === common.length-1) {
item.separator = true;
}
searchResults.editableList('addItem', item);
}
searchResults.editableList('addItem', item);
}
for(i=0;i<Math.min(5,recentlyUsed.length);i++) {
item = {

View File

@ -171,7 +171,7 @@ RED.utils = (function() {
}
function formatNumber(element,obj,sourceId,path,cycle,initialFormat) {
var format = (formattedPaths[sourceId] && formattedPaths[sourceId][path]) || initialFormat || "dec";
var format = (formattedPaths[sourceId] && formattedPaths[sourceId][path] && formattedPaths[sourceId][path]['number']) || initialFormat || "dec";
if (cycle) {
if (format === 'dec') {
if ((obj.toString().length===13) && (obj<=2147483647000)) {
@ -187,10 +187,12 @@ RED.utils = (function() {
format = 'dec';
}
formattedPaths[sourceId] = formattedPaths[sourceId]||{};
formattedPaths[sourceId][path] = format;
formattedPaths[sourceId][path] = formattedPaths[sourceId][path]||{};
formattedPaths[sourceId][path]['number'] = format;
} else if (initialFormat !== undefined){
formattedPaths[sourceId] = formattedPaths[sourceId]||{};
formattedPaths[sourceId][path] = format;
formattedPaths[sourceId][path] = formattedPaths[sourceId][path]||{};
formattedPaths[sourceId][path]['number'] = format;
}
if (format === 'dec') {
element.text(""+obj);
@ -204,7 +206,7 @@ RED.utils = (function() {
}
function formatBuffer(element,button,sourceId,path,cycle) {
var format = (formattedPaths[sourceId] && formattedPaths[sourceId][path]) || "raw";
var format = (formattedPaths[sourceId] && formattedPaths[sourceId][path] && formattedPaths[sourceId][path]['buffer']) || "raw";
if (cycle) {
if (format === 'raw') {
format = 'string';
@ -212,7 +214,8 @@ RED.utils = (function() {
format = 'raw';
}
formattedPaths[sourceId] = formattedPaths[sourceId]||{};
formattedPaths[sourceId][path] = format;
formattedPaths[sourceId][path] = formattedPaths[sourceId][path]||{};
formattedPaths[sourceId][path]['buffer'] = format;
}
if (format === 'raw') {
button.text('raw');
@ -689,16 +692,21 @@ RED.utils = (function() {
return result;
}
function getNodeIcon(def,node) {
if (def.category === 'config') {
return "icons/node-red/cog.png"
} else if (node && node.type === 'tab') {
return "icons/node-red/subflow.png"
} else if (node && node.type === 'unknown') {
return "icons/node-red/alert.png"
} else if (node && node.type === 'subflow') {
return "icons/node-red/subflow.png"
function separateIconPath(icon) {
var result = {module: "", file: ""};
if (icon) {
var index = icon.indexOf('/');
if (index !== -1) {
result.module = icon.slice(0, index);
result.file = icon.slice(index + 1);
} else {
result.file = icon;
}
}
return result;
}
function getDefaultNodeIcon(def,node) {
var icon_url;
if (typeof def.icon === "function") {
try {
@ -710,7 +718,34 @@ RED.utils = (function() {
} else {
icon_url = def.icon;
}
return "icons/"+def.set.module+"/"+icon_url;
var iconPath = separateIconPath(icon_url);
if (!iconPath.module) {
iconPath.module = def.set.module;
}
return iconPath;
}
function getNodeIcon(def,node) {
if (def.category === 'config') {
return "icons/node-red/cog.png"
} else if (node && node.type === 'tab') {
return "icons/node-red/subflow.png"
} else if (node && node.type === 'unknown') {
return "icons/node-red/alert.png"
} else if (node && node.type === 'subflow') {
return "icons/node-red/subflow.png"
} else if (node && node.icon) {
var iconPath = separateIconPath(node.icon);
var iconSets = RED.nodes.getIconSets();
var iconFileList = iconSets[iconPath.module];
if (iconFileList && iconFileList.indexOf(iconPath.file) !== -1) {
return "icons/" + node.icon;
}
}
var iconPath = getDefaultNodeIcon(def, node);
return "icons/"+iconPath.module+"/"+iconPath.file;
}
function getNodeLabel(node,defaultLabel) {
@ -735,6 +770,8 @@ RED.utils = (function() {
getMessageProperty: getMessageProperty,
normalisePropertyExpression: normalisePropertyExpression,
validatePropertyExpression: validatePropertyExpression,
separateIconPath: separateIconPath,
getDefaultNodeIcon: getDefaultNodeIcon,
getNodeIcon: getNodeIcon,
getNodeLabel: getNodeLabel,
}

View File

@ -546,6 +546,9 @@ RED.view = (function() {
RED.typeSearch.show({
x:d3.event.clientX-mainPos.left-node_width/2,
y:d3.event.clientY-mainPos.top-node_height/2,
cancel: function() {
resetMouseVars();
},
add: function(type) {
var result = addNode(type);
if (!result) {
@ -683,7 +686,7 @@ RED.view = (function() {
var mousePos;
if (mouse_mode == RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) {
// update drag line
if (drag_lines.length === 0) {
if (drag_lines.length === 0 && mousedown_port_type !== null) {
if (d3.event.shiftKey) {
// Get all the wires we need to detach.
var links = [];
@ -1347,7 +1350,7 @@ RED.view = (function() {
mouseup_node = null;
mousedown_link = null;
mouse_mode = 0;
mousedown_port_type = PORT_TYPE_OUTPUT;
mousedown_port_type = null;
activeSpliceLink = null;
spliceActive = false;
d3.select(".link_splice").classed("link_splice",false);

View File

@ -208,7 +208,7 @@
border-radius:5px;
overflow: hidden;
font-size: 14px !important;
font-family: monospace !important;
font-family: Menlo, Consolas, 'DejaVu Sans Mono', Courier, monospace !important;
}
.editor-button {
@ -330,6 +330,13 @@
input {
width: calc(100% - 100px);
}
#node-settings-icon-module {
width: calc(55% - 50px);
}
#node-settings-icon-file {
width: calc(45% - 55px);
margin-left: 5px;
}
}
.node-label-form-none {
span {

2
editor/vendor/ace/ace.js vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

2
editor/vendor/ace/ext-language_tools.js vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

2
editor/vendor/ace/ext-searchbox.js vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

2
editor/vendor/ace/mode-css.js vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

2
editor/vendor/ace/mode-handlebars.js vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

2
editor/vendor/ace/mode-html.js vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

2
editor/vendor/ace/mode-javascript.js vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

2
editor/vendor/ace/mode-json.js vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

2
editor/vendor/ace/mode-markdown.js vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

0
editor/vendor/ace/mode-properties.js vendored Executable file → Normal file
View File

1
editor/vendor/ace/mode-python.js vendored Normal file
View File

@ -0,0 +1 @@
ace.define("ace/mode/python_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){var e="and|as|assert|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|not|or|pass|print|raise|return|try|while|with|yield|async|await",t="True|False|None|NotImplemented|Ellipsis|__debug__",n="abs|divmod|input|open|staticmethod|all|enumerate|int|ord|str|any|eval|isinstance|pow|sum|basestring|execfile|issubclass|print|super|binfile|iter|property|tuple|bool|filter|len|range|type|bytearray|float|list|raw_input|unichr|callable|format|locals|reduce|unicode|chr|frozenset|long|reload|vars|classmethod|getattr|map|repr|xrange|cmp|globals|max|reversed|zip|compile|hasattr|memoryview|round|__import__|complex|hash|min|set|apply|delattr|help|next|setattr|buffer|dict|hex|object|slice|coerce|dir|id|oct|sorted|intern",r=this.createKeywordMapper({"invalid.deprecated":"debugger","support.function":n,"constant.language":t,keyword:e},"identifier"),i="(?:r|u|ur|R|U|UR|Ur|uR)?",s="(?:(?:[1-9]\\d*)|(?:0))",o="(?:0[oO]?[0-7]+)",u="(?:0[xX][\\dA-Fa-f]+)",a="(?:0[bB][01]+)",f="(?:"+s+"|"+o+"|"+u+"|"+a+")",l="(?:[eE][+-]?\\d+)",c="(?:\\.\\d+)",h="(?:\\d+)",p="(?:(?:"+h+"?"+c+")|(?:"+h+"\\.))",d="(?:(?:"+p+"|"+h+")"+l+")",v="(?:"+d+"|"+p+")",m="\\\\(x[0-9A-Fa-f]{2}|[0-7]{3}|[\\\\abfnrtv'\"]|U[0-9A-Fa-f]{8}|u[0-9A-Fa-f]{4})";this.$rules={start:[{token:"comment",regex:"#.*$"},{token:"string",regex:i+'"{3}',next:"qqstring3"},{token:"string",regex:i+'"(?=.)',next:"qqstring"},{token:"string",regex:i+"'{3}",next:"qstring3"},{token:"string",regex:i+"'(?=.)",next:"qstring"},{token:"constant.numeric",regex:"(?:"+v+"|\\d+)[jJ]\\b"},{token:"constant.numeric",regex:v},{token:"constant.numeric",regex:f+"[lL]\\b"},{token:"constant.numeric",regex:f+"\\b"},{token:r,regex:"[a-zA-Z_$][a-zA-Z0-9_$]*\\b"},{token:"keyword.operator",regex:"\\+|\\-|\\*|\\*\\*|\\/|\\/\\/|%|<<|>>|&|\\||\\^|~|<|>|<=|=>|==|!=|<>|="},{token:"paren.lparen",regex:"[\\[\\(\\{]"},{token:"paren.rparen",regex:"[\\]\\)\\}]"},{token:"text",regex:"\\s+"}],qqstring3:[{token:"constant.language.escape",regex:m},{token:"string",regex:'"{3}',next:"start"},{defaultToken:"string"}],qstring3:[{token:"constant.language.escape",regex:m},{token:"string",regex:"'{3}",next:"start"},{defaultToken:"string"}],qqstring:[{token:"constant.language.escape",regex:m},{token:"string",regex:"\\\\$",next:"qqstring"},{token:"string",regex:'"|$',next:"start"},{defaultToken:"string"}],qstring:[{token:"constant.language.escape",regex:m},{token:"string",regex:"\\\\$",next:"qstring"},{token:"string",regex:"'|$",next:"start"},{defaultToken:"string"}]}};r.inherits(s,i),t.PythonHighlightRules=s}),ace.define("ace/mode/folding/pythonic",["require","exports","module","ace/lib/oop","ace/mode/folding/fold_mode"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("./fold_mode").FoldMode,s=t.FoldMode=function(e){this.foldingStartMarker=new RegExp("([\\[{])(?:\\s*)$|("+e+")(?:\\s*)(?:#.*)?$")};r.inherits(s,i),function(){this.getFoldWidgetRange=function(e,t,n){var r=e.getLine(n),i=r.match(this.foldingStartMarker);if(i)return i[1]?this.openingBracketBlock(e,i[1],n,i.index):i[2]?this.indentationBlock(e,n,i.index+i[2].length):this.indentationBlock(e,n)}}.call(s.prototype)}),ace.define("ace/mode/python",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/python_highlight_rules","ace/mode/folding/pythonic","ace/range"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./python_highlight_rules").PythonHighlightRules,o=e("./folding/pythonic").FoldMode,u=e("../range").Range,a=function(){this.HighlightRules=s,this.foldingRules=new o("\\:"),this.$behaviour=this.$defaultBehaviour};r.inherits(a,i),function(){this.lineCommentStart="#",this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t),i=this.getTokenizer().getLineTokens(t,e),s=i.tokens;if(s.length&&s[s.length-1].type=="comment")return r;if(e=="start"){var o=t.match(/^.*[\{\(\[:]\s*$/);o&&(r+=n)}return r};var e={pass:1,"return":1,raise:1,"break":1,"continue":1};this.checkOutdent=function(t,n,r){if(r!=="\r\n"&&r!=="\r"&&r!=="\n")return!1;var i=this.getTokenizer().getLineTokens(n.trim(),t).tokens;if(!i)return!1;do var s=i.pop();while(s&&(s.type=="comment"||s.type=="text"&&s.value.match(/^\s+$/)));return s?s.type=="keyword"&&e[s.value]:!1},this.autoOutdent=function(e,t,n){n+=1;var r=this.$getIndent(t.getLine(n)),i=t.getTabString();r.slice(-i.length)==i&&t.remove(new u(n,r.length-i.length,n,r.length))},this.$id="ace/mode/python"}.call(a.prototype),t.Mode=a})

0
editor/vendor/ace/mode-sql.js vendored Executable file → Normal file
View File

2
editor/vendor/ace/mode-swift.js vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

2
editor/vendor/ace/mode-yaml.js vendored Executable file → Normal file
View File

@ -1 +1 @@
ace.define("ace/mode/yaml_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"comment",regex:"#.*$"},{token:"list.markup",regex:/^(?:-{3}|\.{3})\s*(?=#|$)/},{token:"list.markup",regex:/^\s*[\-?](?:$|\s)/},{token:"constant",regex:"!![\\w//]+"},{token:"constant.language",regex:"[&\\*][a-zA-Z0-9-_]+"},{token:["meta.tag","keyword"],regex:/^(\s*\w.*?)(:(?:\s+|$))/},{token:["meta.tag","keyword"],regex:/(\w+?)(\s*:(?:\s+|$))/},{token:"keyword.operator",regex:"<<\\w*:\\w*"},{token:"keyword.operator",regex:"-\\s*(?=[{])"},{token:"string",regex:'["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'},{token:"string",regex:/[|>][-+\d\s]*$/,onMatch:function(e,t,n,r){var i=/^\s*/.exec(r)[0];return n.length<1?n.push(this.next):n[0]="mlString",n.length<2?n.push(i.length):n[1]=i.length,this.token},next:"mlString"},{token:"string",regex:"['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"},{token:"constant.numeric",regex:/(\b|[+\-\.])[\d_]+(?:(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)/},{token:"constant.numeric",regex:/[+\-]?\.inf\b|NaN\b|0x[\dA-Fa-f_]+|0b[10_]+/},{token:"constant.language.boolean",regex:"\\b(?:true|false|TRUE|FALSE|True|False|yes|no)\\b"},{token:"paren.lparen",regex:"[[({]"},{token:"paren.rparen",regex:"[\\])}]"}],mlString:[{token:"indent",regex:/^\s*$/},{token:"indent",regex:/^\s*/,onMatch:function(e,t,n){var r=n[1];return r>=e.length?(this.next="start",n.splice(0)):this.next="mlString",this.token},next:"mlString"},{token:"string",regex:".+"}]},this.normalizeRules()};r.inherits(s,i),t.YamlHighlightRules=s}),ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),ace.define("ace/mode/folding/coffee",["require","exports","module","ace/lib/oop","ace/mode/folding/fold_mode","ace/range"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("./fold_mode").FoldMode,s=e("../../range").Range,o=t.FoldMode=function(){};r.inherits(o,i),function(){this.getFoldWidgetRange=function(e,t,n){var r=this.indentationBlock(e,n);if(r)return r;var i=/\S/,o=e.getLine(n),u=o.search(i);if(u==-1||o[u]!="#")return;var a=o.length,f=e.getLength(),l=n,c=n;while(++n<f){o=e.getLine(n);var h=o.search(i);if(h==-1)continue;if(o[h]!="#")break;c=n}if(c>l){var p=e.getLine(c).length;return new s(l,a,c,p)}},this.getFoldWidget=function(e,t,n){var r=e.getLine(n),i=r.search(/\S/),s=e.getLine(n+1),o=e.getLine(n-1),u=o.search(/\S/),a=s.search(/\S/);if(i==-1)return e.foldWidgets[n-1]=u!=-1&&u<a?"start":"","";if(u==-1){if(i==a&&r[i]=="#"&&s[i]=="#")return e.foldWidgets[n-1]="",e.foldWidgets[n+1]="","start"}else if(u==i&&r[i]=="#"&&o[i]=="#"&&e.getLine(n-2).search(/\S/)==-1)return e.foldWidgets[n-1]="start",e.foldWidgets[n+1]="","";return u!=-1&&u<i?e.foldWidgets[n-1]="start":e.foldWidgets[n-1]="",i<a?"start":""}}.call(o.prototype)}),ace.define("ace/mode/yaml",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/yaml_highlight_rules","ace/mode/matching_brace_outdent","ace/mode/folding/coffee"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./yaml_highlight_rules").YamlHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("./folding/coffee").FoldMode,a=function(){this.HighlightRules=s,this.$outdent=new o,this.foldingRules=new u,this.$behaviour=this.$defaultBehaviour};r.inherits(a,i),function(){this.lineCommentStart="#",this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t);if(e=="start"){var i=t.match(/^.*[\{\(\[]\s*$/);i&&(r+=n)}return r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.$id="ace/mode/yaml"}.call(a.prototype),t.Mode=a})
ace.define("ace/mode/yaml_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){this.$rules={start:[{token:"comment",regex:"#.*$"},{token:"list.markup",regex:/^(?:-{3}|\.{3})\s*(?=#|$)/},{token:"list.markup",regex:/^\s*[\-?](?:$|\s)/},{token:"constant",regex:"!![\\w//]+"},{token:"constant.language",regex:"[&\\*][a-zA-Z0-9-_]+"},{token:["meta.tag","keyword"],regex:/^(\s*\w.*?)(:(?=\s|$))/},{token:["meta.tag","keyword"],regex:/(\w+?)(\s*:(?=\s|$))/},{token:"keyword.operator",regex:"<<\\w*:\\w*"},{token:"keyword.operator",regex:"-\\s*(?=[{])"},{token:"string",regex:'["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'},{token:"string",regex:/[|>][-+\d\s]*$/,onMatch:function(e,t,n,r){var i=/^\s*/.exec(r)[0];return n.length<1?n.push(this.next):n[0]="mlString",n.length<2?n.push(i.length):n[1]=i.length,this.token},next:"mlString"},{token:"string",regex:"['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"},{token:"constant.numeric",regex:/(\b|[+\-\.])[\d_]+(?:(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)(?=[^\d-\w]|$)/},{token:"constant.numeric",regex:/[+\-]?\.inf\b|NaN\b|0x[\dA-Fa-f_]+|0b[10_]+/},{token:"constant.language.boolean",regex:"\\b(?:true|false|TRUE|FALSE|True|False|yes|no)\\b"},{token:"paren.lparen",regex:"[[({]"},{token:"paren.rparen",regex:"[\\])}]"},{token:"text",regex:/[^\s,:\[\]\{\}]+/}],mlString:[{token:"indent",regex:/^\s*$/},{token:"indent",regex:/^\s*/,onMatch:function(e,t,n){var r=n[1];return r>=e.length?(this.next="start",n.splice(0)):this.next="mlString",this.token},next:"mlString"},{token:"string",regex:".+"}]},this.normalizeRules()};r.inherits(s,i),t.YamlHighlightRules=s}),ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"],function(e,t,n){"use strict";var r=e("../range").Range,i=function(){};(function(){this.checkOutdent=function(e,t){return/^\s+$/.test(e)?/^\s*\}/.test(t):!1},this.autoOutdent=function(e,t){var n=e.getLine(t),i=n.match(/^(\s*\})/);if(!i)return 0;var s=i[1].length,o=e.findMatchingBracket({row:t,column:s});if(!o||o.row==t)return 0;var u=this.$getIndent(e.getLine(o.row));e.replace(new r(t,0,t,s-1),u)},this.$getIndent=function(e){return e.match(/^\s*/)[0]}}).call(i.prototype),t.MatchingBraceOutdent=i}),ace.define("ace/mode/folding/coffee",["require","exports","module","ace/lib/oop","ace/mode/folding/fold_mode","ace/range"],function(e,t,n){"use strict";var r=e("../../lib/oop"),i=e("./fold_mode").FoldMode,s=e("../../range").Range,o=t.FoldMode=function(){};r.inherits(o,i),function(){this.getFoldWidgetRange=function(e,t,n){var r=this.indentationBlock(e,n);if(r)return r;var i=/\S/,o=e.getLine(n),u=o.search(i);if(u==-1||o[u]!="#")return;var a=o.length,f=e.getLength(),l=n,c=n;while(++n<f){o=e.getLine(n);var h=o.search(i);if(h==-1)continue;if(o[h]!="#")break;c=n}if(c>l){var p=e.getLine(c).length;return new s(l,a,c,p)}},this.getFoldWidget=function(e,t,n){var r=e.getLine(n),i=r.search(/\S/),s=e.getLine(n+1),o=e.getLine(n-1),u=o.search(/\S/),a=s.search(/\S/);if(i==-1)return e.foldWidgets[n-1]=u!=-1&&u<a?"start":"","";if(u==-1){if(i==a&&r[i]=="#"&&s[i]=="#")return e.foldWidgets[n-1]="",e.foldWidgets[n+1]="","start"}else if(u==i&&r[i]=="#"&&o[i]=="#"&&e.getLine(n-2).search(/\S/)==-1)return e.foldWidgets[n-1]="start",e.foldWidgets[n+1]="","";return u!=-1&&u<i?e.foldWidgets[n-1]="start":e.foldWidgets[n-1]="",i<a?"start":""}}.call(o.prototype)}),ace.define("ace/mode/yaml",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/yaml_highlight_rules","ace/mode/matching_brace_outdent","ace/mode/folding/coffee"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./yaml_highlight_rules").YamlHighlightRules,o=e("./matching_brace_outdent").MatchingBraceOutdent,u=e("./folding/coffee").FoldMode,a=function(){this.HighlightRules=s,this.$outdent=new o,this.foldingRules=new u,this.$behaviour=this.$defaultBehaviour};r.inherits(a,i),function(){this.lineCommentStart=["#","//"],this.getNextLineIndent=function(e,t,n){var r=this.$getIndent(t);if(e=="start"){var i=t.match(/^.*[\{\(\[]\s*$/);i&&(r+=n)}return r},this.checkOutdent=function(e,t,n){return this.$outdent.checkOutdent(t,n)},this.autoOutdent=function(e,t,n){this.$outdent.autoOutdent(t,n)},this.$id="ace/mode/yaml"}.call(a.prototype),t.Mode=a})

0
editor/vendor/ace/snippets/css.js vendored Executable file → Normal file
View File

0
editor/vendor/ace/snippets/handlebars.js vendored Executable file → Normal file
View File

836
editor/vendor/ace/snippets/html.js vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

0
editor/vendor/ace/snippets/javascript.js vendored Executable file → Normal file
View File

0
editor/vendor/ace/snippets/markdown.js vendored Executable file → Normal file
View File

0
editor/vendor/ace/snippets/properties.js vendored Executable file → Normal file
View File

1
editor/vendor/ace/snippets/python.js vendored Normal file
View File

@ -0,0 +1 @@
ace.define("ace/snippets/python",["require","exports","module"],function(e,t,n){"use strict";t.snippetText='snippet #!\n #!/usr/bin/env python\nsnippet imp\n import ${1:module}\nsnippet from\n from ${1:package} import ${2:module}\n# Module Docstring\nsnippet docs\n \'\'\'\n File: ${1:FILENAME:file_name}\n Author: ${2:author}\n Description: ${3}\n \'\'\'\nsnippet wh\n while ${1:condition}:\n ${2:# TODO: write code...}\n# dowh - does the same as do...while in other languages\nsnippet dowh\n while True:\n ${1:# TODO: write code...}\n if ${2:condition}:\n break\nsnippet with\n with ${1:expr} as ${2:var}:\n ${3:# TODO: write code...}\n# New Class\nsnippet cl\n class ${1:ClassName}(${2:object}):\n """${3:docstring for $1}"""\n def __init__(self, ${4:arg}):\n ${5:super($1, self).__init__()}\n self.$4 = $4\n ${6}\n# New Function\nsnippet def\n def ${1:fname}(${2:`indent(\'.\') ? \'self\' : \'\'`}):\n """${3:docstring for $1}"""\n ${4:# TODO: write code...}\nsnippet deff\n def ${1:fname}(${2:`indent(\'.\') ? \'self\' : \'\'`}):\n ${3:# TODO: write code...}\n# New Method\nsnippet defs\n def ${1:mname}(self, ${2:arg}):\n ${3:# TODO: write code...}\n# New Property\nsnippet property\n def ${1:foo}():\n doc = "${2:The $1 property.}"\n def fget(self):\n ${3:return self._$1}\n def fset(self, value):\n ${4:self._$1 = value}\n# Ifs\nsnippet if\n if ${1:condition}:\n ${2:# TODO: write code...}\nsnippet el\n else:\n ${1:# TODO: write code...}\nsnippet ei\n elif ${1:condition}:\n ${2:# TODO: write code...}\n# For\nsnippet for\n for ${1:item} in ${2:items}:\n ${3:# TODO: write code...}\n# Encodes\nsnippet cutf8\n # -*- coding: utf-8 -*-\nsnippet clatin1\n # -*- coding: latin-1 -*-\nsnippet cascii\n # -*- coding: ascii -*-\n# Lambda\nsnippet ld\n ${1:var} = lambda ${2:vars} : ${3:action}\nsnippet .\n self.\nsnippet try Try/Except\n try:\n ${1:# TODO: write code...}\n except ${2:Exception}, ${3:e}:\n ${4:raise $3}\nsnippet try Try/Except/Else\n try:\n ${1:# TODO: write code...}\n except ${2:Exception}, ${3:e}:\n ${4:raise $3}\n else:\n ${5:# TODO: write code...}\nsnippet try Try/Except/Finally\n try:\n ${1:# TODO: write code...}\n except ${2:Exception}, ${3:e}:\n ${4:raise $3}\n finally:\n ${5:# TODO: write code...}\nsnippet try Try/Except/Else/Finally\n try:\n ${1:# TODO: write code...}\n except ${2:Exception}, ${3:e}:\n ${4:raise $3}\n else:\n ${5:# TODO: write code...}\n finally:\n ${6:# TODO: write code...}\n# if __name__ == \'__main__\':\nsnippet ifmain\n if __name__ == \'__main__\':\n ${1:main()}\n# __magic__\nsnippet _\n __${1:init}__${2}\n# python debugger (pdb)\nsnippet pdb\n import pdb; pdb.set_trace()\n# ipython debugger (ipdb)\nsnippet ipdb\n import ipdb; ipdb.set_trace()\n# ipython debugger (pdbbb)\nsnippet pdbbb\n import pdbpp; pdbpp.set_trace()\nsnippet pprint\n import pprint; pprint.pprint(${1})${2}\nsnippet "\n """\n ${1:doc}\n """\n# test function/method\nsnippet test\n def test_${1:description}(${2:self}):\n ${3:# TODO: write code...}\n# test case\nsnippet testcase\n class ${1:ExampleCase}(unittest.TestCase):\n \n def test_${2:description}(self):\n ${3:# TODO: write code...}\nsnippet fut\n from __future__ import ${1}\n#getopt\nsnippet getopt\n try:\n # Short option syntax: "hv:"\n # Long option syntax: "help" or "verbose="\n opts, args = getopt.getopt(sys.argv[1:], "${1:short_options}", [${2:long_options}])\n \n except getopt.GetoptError, err:\n # Print debug info\n print str(err)\n ${3:error_action}\n\n for option, argument in opts:\n if option in ("-h", "--help"):\n ${4}\n elif option in ("-v", "--verbose"):\n verbose = argument\n',t.scope="python"})

0
editor/vendor/ace/snippets/text.js vendored Executable file → Normal file
View File

0
editor/vendor/ace/snippets/yaml.js vendored Executable file → Normal file
View File

0
editor/vendor/ace/theme-chrome.js vendored Executable file → Normal file
View File

0
editor/vendor/ace/theme-tomorrow.js vendored Executable file → Normal file
View File

2
editor/vendor/ace/worker-html.js vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

2
editor/vendor/ace/worker-javascript.js vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

2
editor/vendor/ace/worker-json.js vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

View File

@ -120,6 +120,11 @@
'$exists':{ args:[ 'arg' ]},
'$filter':{ args:[ 'array', 'function' ]},
'$floor':{ args:[ 'number' ]},
'$flowContext': {args:['string']},
'$formatBase': {args:['number','radix']},
'$formatNumber': {args:['number', 'picture', 'options']},
'$fromMillis': {args:['number']},
'$globalContext': {args:['string']},
'$join':{ args:[ 'array', 'separator' ]},
'$keys':{ args:[ 'object' ]},
'$length':{ args:[ 'str' ]},
@ -128,10 +133,13 @@
'$map':{ args:[ 'array', 'function' ]},
'$match':{ args:[ 'str', 'pattern', 'limit' ]},
'$max':{ args:[ 'array' ]},
'$merge':{ args:[ 'array' ]},
'$millis':{ args:[ ]},
'$min':{ args:[ 'array' ]},
'$not':{ args:[ 'arg' ]},
'$now':{ args:[ ]},
'$number':{ args:[ 'arg' ]},
'$pad': {args:['str', 'width','char']},
'$power':{ args:[ 'base', 'exponent' ]},
'$random':{ args:[ ]},
'$reduce':{ args:[ 'array', 'function' , 'init' ]},
@ -149,6 +157,7 @@
'$substringAfter':{ args:[ 'str', 'chars' ]},
'$substringBefore':{ args:[ 'str', 'chars' ]},
'$sum':{ args:[ 'array' ]},
'$toMillis':{args:['timestamp']}, // <-------------
'$trim':{ args:[ 'str' ]},
'$uppercase':{ args:[ 'str' ]},
'$zip':{ args:[ 'array1' ]}

View File

@ -26,6 +26,14 @@
<input type="text" id="node-input-topic">
</div>
<div class="form-row" id="node-once">
<label for="node-input-once">&nbsp;</label>
<input type="checkbox" id="node-input-once" style="display:inline-block;width:15px;vertical-align:baseline;">
<span data-i18n="inject.onstart"></span>&nbsp;
<input type="text" id="node-input-onceDelay" placeholder="0.1" style="width:45px">&nbsp;
<span data-i18n="inject.onceDelay"></span>
</div>
<div class="form-row">
<label for=""><i class="fa fa-repeat"></i> <span data-i18n="inject.label.repeat"></span></label>
<select id="inject-time-type-select">
@ -106,13 +114,6 @@
</div>
</div>
</div>
<div class="form-row" id="node-once">
<label>&nbsp;</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%;" data-i18n="inject.onstart"></label>
</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">
@ -181,9 +182,10 @@ If you want every 20 minutes from now - use the <i>"interval"</i> option.</p>
topic: {value:""},
payload: {value:"", validate: RED.validators.typedInput("payloadType")},
payloadType: {value:"date"},
repeat: {value:""},
repeat: {value:"", validate:function(v) { return ((v === "") || (RED.validators.number(v) && (v >= 0))) }},
crontab: {value:""},
once: {value:false}
once: {value:false},
onceDelay: {value:0.1}
},
icon: "inject.png",
inputs:0,

View File

@ -26,27 +26,35 @@ module.exports = function(RED) {
this.repeat = n.repeat;
this.crontab = n.crontab;
this.once = n.once;
this.onceDelay = (n.onceDelay || 0.1) * 1000;
var node = this;
this.interval_id = null;
this.cronjob = null;
if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
node.repeaterSetup = function () {
if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
this.repeat = this.repeat * 1000;
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(RED._("inject.crontab",this)); }
this.cronjob = new cron.CronJob(this.crontab,
function() {
node.emit("input",{});
},
null,true);
}
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(RED._("inject.crontab", this));
}
this.cronjob = new cron.CronJob(this.crontab, function() { node.emit("input", {}); }, null, true);
}
};
if (this.once) {
setTimeout( function() { node.emit("input",{}); }, 100 );
this.onceTimeout = setTimeout( function() {
node.emit("input",{});
node.repeaterSetup();
}, this.onceDelay);
} else {
node.repeaterSetup();
}
this.on("input",function(msg) {
@ -56,7 +64,7 @@ module.exports = function(RED) {
msg.payload = Date.now();
} else if (this.payloadType == null) {
msg.payload = this.payload;
} else if (this.payloadType == 'none') {
} else if (this.payloadType === 'none') {
msg.payload = "";
} else {
msg.payload = RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg);
@ -72,6 +80,9 @@ module.exports = function(RED) {
RED.nodes.registerType("inject",InjectNode);
InjectNode.prototype.close = function() {
if (this.onceTimeout) {
clearTimeout(this.onceTimeout);
}
if (this.interval_id != null) {
clearInterval(this.interval_id);
if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
@ -80,7 +91,7 @@ module.exports = function(RED) {
if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
delete this.cronjob;
}
}
};
RED.httpAdmin.post("/inject/:id", RED.auth.needsPermission("inject.write"), function(req,res) {
var node = RED.nodes.getNode(req.params.id);

View File

@ -6,11 +6,22 @@
<input id="node-input-complete" type="hidden">
</div>
<div class="form-row">
<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" data-i18n="debug.debtab"></option>
<option value="true" data-i18n="debug.tabcon"></option>
</select>
<label for="node-input-tosidebar"><i class="fa fa-random"></i> <span data-i18n="debug.to"></span></label>
<label for="node-input-tosidebar" style="width:70%">
<input type="checkbox" id="node-input-tosidebar" style="display:inline-block; width:22px; vertical-align:baseline;"><span data-i18n="debug.toSidebar"></span>
</label>
</div>
<div class="form-row">
<label for="node-input-console"> </label>
<label for="node-input-console" style="width:70%">
<input type="checkbox" id="node-input-console" style="display:inline-block; width:22px; vertical-align:baseline;"><span data-i18n="debug.toConsole"></span>
</label>
</div>
<div class="form-row" id="node-tostatus-line">
<label for="node-input-tostatus"> </label>
<label for="node-input-tostatus" style="width:70%">
<input type="checkbox" id="node-input-tostatus" style="display:inline-block; width:22px; vertical-align:baseline;"><span data-i18n="debug.toStatus"></span>
</label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
@ -26,7 +37,7 @@
<p>Alongside each message, the debug sidebar includes information about the time the message was received, the node that sent it and the type of the message.
Clicking on the source node id will reveal that node within the workspace.</p>
<p>The button on the node can be used to enable or disable its output. It is recommended to disable or remove any Debug nodes that are not being used.</p>
<p>The node can also be configured to send all messages to the runtime log.</p>
<p>The node can also be configured to send all messages to the runtime log, or to send short (32 characters) to the status text under the debug node.</p>
</script>
<script src="debug/view/debug-utils.js"></script>
@ -38,7 +49,9 @@
defaults: {
name: {value:""},
active: {value:true},
console: {value:"false"},
tosidebar: {value:true},
console: {value:false},
tostatus: {value:false},
complete: {value:"false", required:true}
},
label: function() {
@ -100,7 +113,6 @@
}
},
onpaletteadd: function() {
var options = {
messageMouseEnter: function(sourceId) {
if (sourceId) {
@ -148,7 +160,7 @@
var that = this;
RED._debug = function(msg) {
that.handleDebugMessage("",{
that.handleDebugMessage("", {
name:"debug",
msg:msg
});
@ -170,7 +182,6 @@
var sourceNode = RED.nodes.node(o.id) || RED.nodes.node(o.z);
if (sourceNode) {
o._source = {id:sourceNode.id,z:sourceNode.z,name:sourceNode.name};
}
RED.debug.handleDebugMessage(o);
if (subWindow) {
@ -237,6 +248,15 @@
delete RED._debug;
},
oneditprepare: function() {
if (this.tosidebar === undefined) {
this.tosidebar = true;
$("#node-input-tosidebar").prop('checked', true);
}
if (typeof this.console === "string") {
this.console = (this.console == 'true');
$("#node-input-console").prop('checked', this.console);
$("#node-input-tosidebar").prop('checked', true);
}
$("#node-input-typed-complete").typedInput({types:['msg', {value:"full",label:RED._("node-red:debug.msgobj"),hasValue:false}]});
if (this.complete === "true" || this.complete === true) {
// show complete message object
@ -252,6 +272,16 @@
) {
$("#node-input-typed-complete").typedInput('value','payload');
}
if ($("#node-input-typed-complete").typedInput('type') === 'msg') {
$("#node-tostatus-line").show();
}
else { $("#node-tostatus-line").hide(); }
});
$("#node-input-complete").on('change',function() {
if ($("#node-input-typed-complete").typedInput('type') === 'msg') {
$("#node-tostatus-line").show();
}
else { $("#node-tostatus-line").hide(); }
});
},
oneditsave: function() {
@ -262,7 +292,6 @@
$("#node-input-complete").val($("#node-input-typed-complete").typedInput('value'));
}
}
});
})();
</script>

View File

@ -5,7 +5,7 @@ module.exports = function(RED) {
var events = require("events");
var path = require("path");
var safeJSONStringify = require("json-stringify-safe");
var debuglength = RED.settings.debugMaxLength||1000;
var debuglength = RED.settings.debugMaxLength || 1000;
var useColors = RED.settings.debugUseColors || false;
util.inspect.styles.boolean = "red";
@ -13,26 +13,49 @@ module.exports = function(RED) {
RED.nodes.createNode(this,n);
this.name = n.name;
this.complete = (n.complete||"payload").toString();
if (this.complete === "false") {
this.complete = "payload";
}
this.console = n.console;
if (this.complete === "false") { this.complete = "payload"; }
this.console = ""+(n.console || false);
this.tostatus = n.tostatus || false;
this.tosidebar = n.tosidebar;
if (this.tosidebar === undefined) { this.tosidebar = true; }
this.severity = n.severity || 40;
this.active = (n.active === null || typeof n.active === "undefined") || n.active;
this.status({});
var node = this;
var levels = {
off: 1,
fatal: 10,
error: 20,
warn: 30,
info: 40,
debug: 50,
trace: 60,
audit: 98,
metric: 99
};
var colors = {
"0": "grey",
"10": "grey",
"20": "red",
"30": "yellow",
"40": "grey",
"50": "green",
"60": "blue"
};
this.on("input",function(msg) {
if (this.complete === "true") {
// debug complete msg object
// debug complete msg object
if (this.console === "true") {
node.log("\n"+util.inspect(msg, {colors:useColors, depth:10}));
}
if (this.active) {
sendDebug({id:node.id,name:node.name,topic:msg.topic,msg:msg,_path:msg._path});
if (this.active && this.tosidebar) {
sendDebug({id:node.id, name:node.name, topic:msg.topic, msg:msg, _path:msg._path});
}
} else {
// debug user defined msg property
}
else {
// debug user defined msg property
var property = "payload";
var output = msg[property];
if (this.complete !== "false" && typeof this.complete !== "undefined") {
@ -53,7 +76,15 @@ module.exports = function(RED) {
}
}
if (this.active) {
sendDebug({id:node.id,z:node.z,name:node.name,topic:msg.topic,property:property,msg:output,_path:msg._path});
if (this.tosidebar == true) {
sendDebug({id:node.id, z:node.z, name:node.name, topic:msg.topic, property:property, msg:output, _path:msg._path});
}
if (this.tostatus === true) {
var st = util.inspect(output);
if (st.length > 32) { st = st.substr(0,32) + "..."; }
node.oldStatus = {fill:colors[node.severity], shape:"dot", text:st};
node.status(node.oldStatus);
}
}
}
});
@ -138,7 +169,7 @@ module.exports = function(RED) {
value = value.substring(0,debuglength)+"...";
}
} else if (value && value.constructor) {
if (value.constructor.name === "Buffer") {
if (value.type === "Buffer") {
value.__encoded__ = true;
value.length = value.data.length;
if (value.length > debuglength) {
@ -196,9 +227,14 @@ module.exports = function(RED) {
if (state === "enable") {
node.active = true;
res.sendStatus(200);
if (node.tostatus) { node.status({}); }
} else if (state === "disable") {
node.active = false;
res.sendStatus(201);
if (node.tostatus && node.hasOwnProperty("oldStatus")) {
node.oldStatus.shape = "ring";
node.status(node.oldStatus);
}
} else {
res.sendStatus(404);
}

View File

@ -75,12 +75,20 @@
<dt>payload <span class="property-type">string</span></dt>
<dd>the standard output of the command.</dd>
</dl>
<dl class="message-properties">
<dt>rc <span class="property-type">object</span></dt>
<dd>exec mode only, a copy of the return code object (also available on port 3)</dd>
</dl>
</li>
<li>Standard error
<dl class="message-properties">
<dt>payload <span class="property-type">string</span></dt>
<dd>the standard error of the command.</dd>
</dl>
<dl class="message-properties">
<dt>rc <span class="property-type">object</span></dt>
<dd>exec mode only, a copy of the return code object (also available on port 3)</dd>
</dl>
</li>
<li>Return code
<dl class="message-properties">

View File

@ -125,14 +125,15 @@ module.exports = function(RED) {
if (node.append.trim() !== "") { cl += " "+node.append; }
/* istanbul ignore else */
if (RED.settings.verbose) { node.log(cl); }
child = exec(cl, {encoding: 'binary', maxBuffer:10000000}, function (error, stdout, stderr) {
child = exec(cl, {encoding:'binary', maxBuffer:10000000}, function (error, stdout, stderr) {
var msg2, msg3;
delete msg.payload;
if (stderr) {
msg2 = RED.util.cloneMessage(msg);
msg2.payload = stderr;
}
msg.payload = Buffer.from(stdout,"binary");
if (isUtf8(msg.payload)) { msg.payload = msg.payload.toString(); }
var msg2 = null;
if (stderr) {
msg2 = {payload: stderr};
}
var msg3 = null;
node.status({});
//console.log('[exec] stdout: ' + stdout);
//console.log('[exec] stderr: ' + stderr);
@ -142,10 +143,15 @@ module.exports = function(RED) {
if (error.code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); }
else { node.status({fill:"red",shape:"dot",text:"error:"+error.code}); }
node.log('error:' + error);
} else if (node.oldrc === "false") {
}
else if (node.oldrc === "false") {
msg3 = {payload:{code:0}};
}
if (!msg3) { node.status({}); }
else {
msg.rc = msg3.payload;
if (msg2) { msg2.rc = msg3.payload; }
}
node.send([msg,msg2,msg3]);
if (child.tout) { clearTimeout(child.tout); }
delete node.activeProcesses[child.pid];

View File

@ -70,10 +70,19 @@
label: function() {
return this.name;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var that = this;
$( "#node-input-outputs" ).spinner({
min:1
min:1,
change: function(event, ui) {
var value = this.value;
if (!value.match(/^\d+$/)) { value = 1; }
else if (value < this.min) { value = this.min; }
if (value !== this.value) { $(this).spinner("value", value); }
}
});
this.editor = RED.editor.createEditor({

View File

@ -187,6 +187,13 @@ module.exports = function(RED) {
}
}
};
if (util.hasOwnProperty('promisify')) {
sandbox.setTimeout[util.promisify.custom] = function(after, value) {
return new Promise(function(resolve, reject) {
sandbox.setTimeout(function(){ resolve(value) }, after);
});
}
}
var context = vm.createContext(sandbox);
try {
this.script = vm.createScript(functionText);

View File

@ -42,6 +42,7 @@
<select id="node-input-output" style="width:180px;">
<option value="str" data-i18n="template.label.plain"></option>
<option value="json" data-i18n="template.label.json"></option>
<option value="yaml" data-i18n="template.label.yaml"></option>
</select>
</div>
@ -53,6 +54,9 @@
<dl class="message-properties">
<dt>msg <span class="property-type">object</span></dt>
<dd>A msg object containing information to populate the template.</dd>
<dt class="optional">template <span class="property-type">string</span></dt>
<dd>A template to be populated from msg.payload. If not configured in the edit panel,
this can be set as a property of msg.</dd>
</dl>
<h3>Outputs</h3>
<dl class="message-properties">
@ -97,6 +101,9 @@
label: function() {
return this.name;
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var that = this;
if (!this.fieldType) {

View File

@ -17,14 +17,17 @@
module.exports = function(RED) {
"use strict";
var mustache = require("mustache");
var yaml = require("js-yaml");
/**
* Custom Mustache Context capable to resolve message property and node
* flow and global context
*/
function NodeContext(msg, nodeContext,parent) {
function NodeContext(msg, nodeContext, parent, escapeStrings) {
this.msgContext = new mustache.Context(msg,parent);
this.nodeContext = nodeContext;
this.escapeStrings = escapeStrings;
}
NodeContext.prototype = new mustache.Context();
@ -34,6 +37,14 @@ module.exports = function(RED) {
try {
var value = this.msgContext.lookup(name);
if (value !== undefined) {
if (this.escapeStrings && typeof value === "string") {
value = value.replace(/\\/g, "\\\\");
value = value.replace(/\n/g, "\\n");
value = value.replace(/\t/g, "\\t");
value = value.replace(/\r/g, "\\r");
value = value.replace(/\f/g, "\\f");
value = value.replace(/[\b]/g, "\\b");
}
return value;
}
@ -72,14 +83,31 @@ module.exports = function(RED) {
node.on("input", function(msg) {
try {
var value;
/***
* Allow template contents to be defined externally
* through inbound msg.template IFF node.template empty
*/
if (msg.hasOwnProperty("template")) {
if (node.template == "" || node.template === null) {
node.template = msg.template;
}
}
if (node.syntax === "mustache") {
value = mustache.render(node.template,new NodeContext(msg, node.context()));
if (node.outputFormat === "json") {
value = mustache.render(node.template,new NodeContext(msg, node.context(), null, true));
} else {
value = mustache.render(node.template,new NodeContext(msg, node.context(), null, false));
}
} else {
value = node.template;
}
if (node.outputFormat === "json") {
value = JSON.parse(value);
}
if (node.outputFormat === "yaml") {
value = yaml.load(value);
}
if (node.fieldType === 'msg') {
RED.util.setMessageProperty(msg,node.field,value);

View File

@ -102,7 +102,7 @@
<dt class="optional">delay <span class="property-type">number</span></dt>
<dd>Sets the delay, in milliseconds, to be applied to the message. This
option only applies if the node is configured to allow the message to
provide the delay interval.</dd>
override the configured default delay interval.</dd>
<dt class="optional">reset</dt>
<dd>If the received message has this property set to any value, all
outstanding messages held by the node are cleared without being sent.</dd>
@ -129,13 +129,13 @@
defaults: {
name: {value:""},
pauseType: {value:"delay", required:true},
timeout: {value:"5", required:true, validate:RED.validators.number()},
timeout: {value:"5", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
timeoutUnits: {value:"seconds"},
rate: {value:"1", required:true, validate:RED.validators.number()},
rate: {value:"1", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
nbRateUnits: {value:"1", required:false, validate:RED.validators.regex(/\d+|/)},
rateUnits: {value: "second"},
randomFirst: {value:"1", required:true, validate:RED.validators.number()},
randomLast: {value:"5", required:true, validate:RED.validators.number()},
randomFirst: {value:"1", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
randomLast: {value:"5", required:true, validate:function(v) { return RED.validators.number(v) && (v >= 0); }},
randomUnits: {value: "seconds"},
drop: {value:false}
},
@ -260,7 +260,7 @@
$("#delay-details-for").show();
$("#random-details").hide();
} else if (this.value === "delayv") {
$("#delay-details-for").hide();
$("#delay-details-for").show();
$("#random-details").hide();
} else if (this.value === "random") {
$("#delay-details-for").hide();

View File

@ -105,7 +105,10 @@ module.exports = function(RED) {
}
else if (node.pauseType === "delayv") {
node.on("input", function(msg) {
var delayvar = Number(msg.delay || 0);
var delayvar = Number(node.timeout);
if (msg.hasOwnProperty("delay") && !isNaN(parseFloat(msg.delay))) {
delayvar = parseFloat(msg.delay);
}
if (delayvar < 0) { delayvar = 0; }
var id = setTimeout(function() {
node.idList.splice(node.idList.indexOf(id),1);
@ -113,7 +116,7 @@ module.exports = function(RED) {
node.send(msg);
}, delayvar);
node.idList.push(id);
if ((delayvar >= 1) && (node.idList.length !== 0)) {
if ((delayvar >= 0) && (node.idList.length !== 0)) {
node.status({fill:"blue",shape:"dot",text:delayvar/1000+"s"});
}
if (msg.hasOwnProperty("reset")) { clearDelayList(); }

View File

@ -56,6 +56,13 @@
</ul>
</div>
<br/>
<div class="form-row">
<label data-i18n="trigger.for" for="node-input-bytopic"></label>
<select id="node-input-bytopic">
<option value="all" data-i18n="trigger.alltopics"></option>
<option value="topic" data-i18n="trigger.bytopics"></option>
</select>
</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"></input>
@ -85,7 +92,7 @@
<p>If set to a <i>string</i> type, the node supports the mustache template syntax.</p>
<p>If the node receives a message with a <code>reset</code> property, or a <code>payload</code>
that matches that configured in the node, any timeout or repeat currently in
progress will be cleared and no message triggered.</o>
progress will be cleared and no message triggered.</p>
<p>The node can be configured to resend a message at a regular interval until it
is reset by a received message.</p>
</script>
@ -103,6 +110,7 @@
extend: {value:"false"},
units: {value:"ms"},
reset: {value:""},
bytopic: {value: "all"},
name: {value:""}
},
inputs:1,

View File

@ -19,6 +19,7 @@ module.exports = function(RED) {
var mustache = require("mustache");
function TriggerNode(n) {
RED.nodes.createNode(this,n);
this.bytopic = n.bytopic || "all";
this.op1 = n.op1 || "1";
this.op2 = n.op2 || "0";
this.op1type = n.op1type || "str";
@ -47,7 +48,7 @@ module.exports = function(RED) {
this.extend = n.extend || "false";
this.units = n.units || "ms";
this.reset = n.reset || '';
this.duration = parseInt(n.duration);
this.duration = parseFloat(n.duration);
if (isNaN(this.duration)) {
this.duration = 250;
}
@ -65,29 +66,32 @@ module.exports = function(RED) {
this.op2Templated = (this.op2type === 'str' && this.op2.indexOf("{{") != -1);
if ((this.op1type === "num") && (!isNaN(this.op1))) { this.op1 = Number(this.op1); }
if ((this.op2type === "num") && (!isNaN(this.op2))) { this.op2 = Number(this.op2); }
if (this.op1 == "null") { this.op1 = null; }
if (this.op2 == "null") { this.op2 = null; }
//if (this.op1 == "null") { this.op1 = null; }
//if (this.op2 == "null") { this.op2 = null; }
//try { this.op1 = JSON.parse(this.op1); }
//catch(e) { this.op1 = this.op1; }
//try { this.op2 = JSON.parse(this.op2); }
//catch(e) { this.op2 = this.op2; }
var node = this;
var tout = null;
var m2;
node.topics = {};
this.on("input", function(msg) {
var topic = msg.topic || "_none";
if (node.bytopic === "all") { topic = "_none"; }
node.topics[topic] = node.topics[topic] || {};
if (msg.hasOwnProperty("reset") || ((node.reset !== '') && (msg.payload == node.reset)) ) {
if (node.loop === true) { clearInterval(tout); }
else { clearTimeout(tout); }
tout = null;
if (node.loop === true) { clearInterval(node.topics[topic].tout); }
else { clearTimeout(node.topics[topic].tout); }
delete node.topics[topic];
node.status({});
}
else {
if (((!tout) && (tout !== 0)) || (node.loop === true)) {
if (node.op2type === "pay" || node.op2type === "payl") { m2 = msg.payload; }
else if (node.op2Templated) { m2 = mustache.render(node.op2,msg); }
if (((!node.topics[topic].tout) && (node.topics[topic].tout !== 0)) || (node.loop === true)) {
if (node.op2type === "pay" || node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
else if (node.op2Templated) { node.topics[topic].m2 = mustache.render(node.op2,msg); }
else if (node.op2type !== "nul") {
m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg);
node.topics[topic].m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg);
}
if (node.op1type === "pay") { }
@ -96,58 +100,64 @@ module.exports = function(RED) {
msg.payload = RED.util.evaluateNodeProperty(node.op1,node.op1type,node,msg);
}
if (node.op1type !== "nul") { node.send(msg); }
if (node.op1type !== "nul") { node.send(RED.util.cloneMessage(msg)); }
if (node.duration === 0) { tout = 0; }
if (node.duration === 0) { node.topics[topic].tout = 0; }
else if (node.loop === true) {
if (tout) { clearInterval(tout); }
if (node.topics[topic].tout) { clearInterval(node.topics[topic].tout); }
if (node.op1type !== "nul") {
var msg2 = RED.util.cloneMessage(msg);
tout = setInterval(function() { node.send(msg2); },node.duration);
node.topics[topic].tout = setInterval(function() { node.send(RED.util.cloneMessage(msg2)); }, node.duration);
}
}
else {
tout = setTimeout(function() {
node.topics[topic].tout = setTimeout(function() {
var msg2 = null;
if (node.op2type !== "nul") {
var msg2 = RED.util.cloneMessage(msg);
msg2 = RED.util.cloneMessage(msg);
if (node.op2type === "flow" || node.op2type === "global") {
m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg);
node.topics[topic].m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg);
}
msg2.payload = m2;
node.send(msg2);
msg2.payload = node.topics[topic].m2;
}
tout = null;
delete node.topics[topic];
node.status({});
},node.duration);
node.send(msg2);
}, node.duration);
}
node.status({fill:"blue",shape:"dot",text:" "});
}
else if ((node.extend === "true" || node.extend === true) && (node.duration > 0)) {
if (tout) { clearTimeout(tout); }
if (node.op2type === "payl") { m2 = msg.payload; }
tout = setTimeout(function() {
if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
if (node.topics[topic].tout) { clearTimeout(node.topics[topic].tout); }
node.topics[topic].tout = setTimeout(function() {
var msg2 = null;
if (node.op2type !== "nul") {
var msg2 = RED.util.cloneMessage(msg);
if (node.op2type === "flow" || node.op2type === "global") {
m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg);
node.topics[topic].m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg);
}
if (node.topics[topic] !== undefined) {
msg2 = RED.util.cloneMessage(msg);
msg2.payload = node.topics[topic].m2;
}
msg2.payload = m2;
node.send(msg2);
}
tout = null;
delete node.topics[topic];
node.status({});
},node.duration);
node.send(msg2);
}, node.duration);
}
else {
if (node.op2type === "payl") { m2 = msg.payload; }
if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
}
}
});
this.on("close", function() {
if (tout) {
if (node.loop === true) { clearInterval(tout); }
else { clearTimeout(tout); }
tout = null;
for (var t in node.topics) {
if (node.topics[t]) {
if (node.loop === true) { clearInterval(node.topics[t].tout); }
else { clearTimeout(node.topics[t].tout); }
delete node.topics[t];
}
}
node.status({});
});

View File

@ -29,6 +29,7 @@ RED.debug = (function() {
var messagesByNode = {};
var sbc;
var activeWorkspace;
var numMessages = 100; // Hardcoded number of message to show in debug window scrollback
var filterVisible = false;
@ -367,9 +368,24 @@ RED.debug = (function() {
})
menuOptionMenu.show();
}
function handleDebugMessage(o) {
var msg = document.createElement("div");
var stack = [];
var busy = false;
function handleDebugMessage(o) {
if (o) { stack.push(o); }
if (!busy && (stack.length > 0)) {
busy = true;
processDebugMessage(stack.shift());
setTimeout(function() {
busy = false;
handleDebugMessage();
}, 15); // every 15mS = 66 times a second
if (stack.length > numMessages) { stack = stack.splice(-numMessages); }
}
}
function processDebugMessage(o) {
var msg = document.createElement("div");
var sourceNode = o._source;
msg.onmouseenter = function() {
@ -421,7 +437,9 @@ RED.debug = (function() {
$('<span class="debug-message-name">'+name+'</span>').appendTo(metaRow);
}
if (format === 'Object' || /^array/.test(format) || format === 'boolean' || format === 'number' ) {
if ((format === 'number') && (payload === "NaN")) {
payload = Number.NaN;
} else if (format === 'Object' || /^array/.test(format) || format === 'boolean' || format === 'number' ) {
payload = JSON.parse(payload);
} else if (/error/i.test(format)) {
payload = JSON.parse(payload);
@ -495,7 +513,7 @@ RED.debug = (function() {
}
}
if (messages.length === 100) {
if (messages.length === numMessages) {
m = messages.shift();
if (view === "list") {
m.el.remove();
@ -528,6 +546,6 @@ RED.debug = (function() {
init: init,
refreshMessageList:refreshMessageList,
handleDebugMessage: handleDebugMessage,
clearMessageList: clearMessageList,
clearMessageList: clearMessageList
}
})();

View File

@ -24,44 +24,48 @@
<span class="tls-config-input-data">
<label class="editor-button" for="node-config-input-certfile"><i class="fa fa-upload"></i> <span data-i18n="tls.label.upload"></span></label>
<input class="hide" type="file" id="node-config-input-certfile">
<span id="tls-config-certname" style="width: 180px; overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
<span id="tls-config-certname" style="width: calc(100% - 280px); overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
<button class="editor-button editor-button-small" id="tls-config-button-cert-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
</span>
<input type="hidden" id="node-config-input-certname">
<input type="hidden" id="node-config-input-certdata">
<input class="hide tls-config-input-path" style="width: 60%;" type="text" id="node-config-input-cert" data-i18n="[placeholder]tls.placeholder.cert">
<input class="hide tls-config-input-path" style="width: calc(100% - 170px);" type="text" id="node-config-input-cert" data-i18n="[placeholder]tls.placeholder.cert">
</div>
<div class="form-row">
<label style="width: 120px;" for="node-config-input-key"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.key"></span></label>
<span class="tls-config-input-data">
<label class="editor-button" for="node-config-input-keyfile"><i class="fa fa-upload"></i> <span data-i18n="tls.label.upload"></span></label>
<input class="hide" type="file" id="node-config-input-keyfile">
<span id="tls-config-keyname" style="width: 180px; overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
<span id="tls-config-keyname" style="width: calc(100% - 280px); overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
<button class="editor-button editor-button-small" id="tls-config-button-key-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
</span>
<input type="hidden" id="node-config-input-keyname">
<input type="hidden" id="node-config-input-keydata">
<input class="hide tls-config-input-path" style="width: 60%;" type="text" id="node-config-input-key" data-i18n="[placeholder]tls.placeholder.key">
<input class="hide tls-config-input-path" style="width: calc(100% - 170px);" type="text" id="node-config-input-key" data-i18n="[placeholder]tls.placeholder.key">
</div>
<div class="form-row">
<label style="width: 100px; margin-left: 20px;" for="node-config-input-passphrase"> <span data-i18n="tls.label.passphrase"></span></label>
<input type="password" style="width: calc(100% - 170px);" id="node-config-input-passphrase" data-i18n="[placeholder]tls.placeholder.passphrase">
</div>
<div class="form-row">
<label style="width: 120px;" for="node-config-input-ca"><i class="fa fa-file-text-o"></i> <span data-i18n="tls.label.ca"></span></label>
<span class="tls-config-input-data">
<label class="editor-button" for="node-config-input-cafile"><i class="fa fa-upload"></i> <span data-i18n="tls.label.upload"></span></label>
<input class="hide" type="file" title=" " id="node-config-input-cafile">
<span id="tls-config-caname" style="width: 180px; overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
<span id="tls-config-caname" style="width: calc(100% - 280px); overflow: hidden; line-height:34px; height:34px; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle;"> </span>
<button class="editor-button editor-button-small" id="tls-config-button-ca-clear" style="margin-left: 10px"><i class="fa fa-times"></i></button>
</span>
<input type="hidden" id="node-config-input-caname">
<input type="hidden" id="node-config-input-cadata">
<input class="hide tls-config-input-path" style="width: 60%;" type="text" id="node-config-input-ca" data-i18n="[placeholder]tls.placeholder.ca">
<input class="hide tls-config-input-path" style="width: calc(100% - 170px);" type="text" id="node-config-input-ca" data-i18n="[placeholder]tls.placeholder.ca">
</div>
<div class="form-row">
<input type="checkbox" id="node-config-input-verifyservercert" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-config-input-verifyservercert" style="width: 70%;" data-i18n="tls.label.verify-server-cert"></label>
<label for="node-config-input-verifyservercert" style="width: calc(100% - 170px);" data-i18n="tls.label.verify-server-cert"></label>
</div>
<div class="form-row">
<label style="width: 120px;" for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input style="width: 60%;" type="text" id="node-config-input-name" data-i18n="[placeholder]common.label.name">
<input style="width: calc(100% - 170px);" type="text" id="node-config-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
@ -97,7 +101,8 @@
credentials: {
certdata: {type:"text"},
keydata: {type:"text"},
cadata: {type:"text"}
cadata: {type:"text"},
passphrase: {type:"password"}
},
label: function() {
return this.name || this._("tls.tls");

View File

@ -77,7 +77,8 @@ module.exports = function(RED) {
credentials: {
certdata: {type:"text"},
keydata: {type:"text"},
cadata: {type:"text"}
cadata: {type:"text"},
passphrase: {type:"password"}
},
settings: {
tlsConfigDisableLocalFiles: {
@ -98,6 +99,9 @@ module.exports = function(RED) {
if (this.ca) {
opts.ca = this.ca;
}
if (this.credentials && this.credentials.passphrase) {
opts.passphrase = this.credentials.passphrase;
}
opts.rejectUnauthorized = this.verifyservercert;
}
return opts;

View File

@ -165,6 +165,10 @@
</script>
<script type="text/x-red" data-template-name="mqtt-broker">
<div class="form-row">
<label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-config-input-name" data-i18n="[placeholder]common.label.name">
</div>
<div class="form-row">
<ul style="background: #fff; min-width: 600px; margin-bottom: 20px;" id="node-config-mqtt-broker-tabs"></ul>
</div>
@ -256,27 +260,41 @@
</script>
<script type="text/x-red" data-help-name="mqtt-broker">
<p>A minimum MQTT broker connection requires only a broker server address to be added to the default configuration.</p>
<p>To secure the connection with SSL/TLS, a TLS Configuration must also be configured and selected.</p>
<p>If you create a Client ID it must be unique to the broker you are connecting to.</p>
<p>For more information about MQTT see the <a href="http://www.eclipse.org/paho/" target="_blank">Eclipse Paho</a> site.</p>
<p>Configuration for a connection to an MQTT broker.</p>
<p>This configuration will create a single connection to the broker which can
then be reused by <code>MQTT In</code> and <code>MQTT Out</code> nodes.</p>
<p>The node will generate a random Client ID if one is not set and the
node is configured to use a Clean Session connection. If a Client ID is set,
it must be unique to the broker you are connecting to.</p>
<h4>Birth Message</h4>
<p>This is a message that will be published on the configured topic whenever the
connection is established.</p>
<h4>Will Message</h4>
<p>This is a message that will be published by the broker in the event the node
unexpectedly loses its connection.</p>
<h4>WebSockets</h4>
<p>The node can be configured to use a WebSocket connection. To do so, the Server
field should be configured with a full URI for the connection. For example:</p>
<pre>ws://example.com:4000/mqtt</pre>
</script>
<script type="text/javascript">
RED.nodes.registerType('mqtt-broker',{
category: 'config',
defaults: {
name: {value:""},
broker: {value:"",required:true},
port: {value:1883,required:true,validate:RED.validators.number()},
port: {value:1883,required:false,validate:RED.validators.number(true)},
tls: {type:"tls-config",required: false},
clientid: {value:"", validate: function(v) {
if ($("#node-config-input-clientid").length) {
// Currently editing the node
return $("#node-config-input-cleansession").is(":checked") || (v||"").length > 0;
} else {
return (this.cleansession===undefined || this.cleansession) || (v||"").length > 0;
}
}},
if ($("#node-config-input-clientid").length) {
// Currently editing the node
return $("#node-config-input-cleansession").is(":checked") || (v||"").length > 0;
} else {
return (this.cleansession===undefined || this.cleansession) || (v||"").length > 0;
}
}},
usetls: {value: false},
verifyservercert: { value: false},
compatmode: { value: true},
@ -296,9 +314,18 @@
password: {type: "password"}
},
label: function() {
if (this.name) {
return this.name;
}
var b = this.broker;
if (b === "") { b = "undefined"; }
return (this.clientid?this.clientid+"@":"")+b+":"+this.port;
var lab = "";
lab = (this.clientid?this.clientid+"@":"")+b;
if (b.indexOf("://") === -1){
if (!this.port){ lab = lab + ":1883"; }
else { lab = lab + ":" + this.port; }
}
return lab;
},
oneditprepare: function () {
var tabs = RED.tabs.create({
@ -374,6 +401,28 @@
$("#node-config-input-cleansession").on("click",function() {
updateClientId();
});
function updatePortEntry(){
var disabled = $("#node-config-input-port").prop("disabled");
if ($("#node-config-input-broker").val().indexOf("://") === -1){
if (disabled){
$("#node-config-input-port").prop("disabled", false);
}
}
else {
if (!disabled){
$("#node-config-input-port").prop("disabled", true);
}
}
}
$("#node-config-input-broker").change(function() {
updatePortEntry();
});
$("#node-config-input-broker").on( "keyup", function() {
updatePortEntry();
});
setTimeout(updatePortEntry,50);
},
oneditsave: function() {
if (!$("#node-config-input-usetls").is(':checked')) {

View File

@ -36,6 +36,7 @@ module.exports = function(RED) {
this.port = n.port;
this.clientid = n.clientid;
this.usetls = n.usetls;
this.usews = n.usews;
this.verifyservercert = n.verifyservercert;
this.compatmode = n.compatmode;
this.keepalive = n.keepalive;
@ -69,6 +70,9 @@ module.exports = function(RED) {
if (typeof this.usetls === 'undefined') {
this.usetls = false;
}
if (typeof this.usews === 'undefined') {
this.usews = false;
}
if (typeof this.compatmode === 'undefined') {
this.compatmode = true;
}
@ -86,15 +90,27 @@ module.exports = function(RED) {
// Create the URL to pass in to the MQTT.js library
if (this.brokerurl === "") {
if (this.usetls) {
this.brokerurl="mqtts://";
// if the broker may be ws:// or wss:// or even tcp://
if (this.broker.indexOf("://") > -1) {
this.brokerurl = this.broker;
} else {
this.brokerurl="mqtt://";
}
if (this.broker !== "") {
this.brokerurl = this.brokerurl+this.broker+":"+this.port;
} else {
this.brokerurl = this.brokerurl+"localhost:1883";
// construct the std mqtt:// url
if (this.usetls) {
this.brokerurl="mqtts://";
} else {
this.brokerurl="mqtt://";
}
if (this.broker !== "") {
this.brokerurl = this.brokerurl+this.broker+":";
// port now defaults to 1883 if unset.
if (!this.port){
this.brokerurl = this.brokerurl+"1883";
} else {
this.brokerurl = this.brokerurl+this.port;
}
} else {
this.brokerurl = this.brokerurl+"localhost:1883";
}
}
}
@ -278,7 +294,9 @@ module.exports = function(RED) {
this.publish = function (msg) {
if (node.connected) {
if (!Buffer.isBuffer(msg.payload)) {
if (msg.payload === null || msg.payload === undefined) {
msg.payload = "";
} else if (!Buffer.isBuffer(msg.payload)) {
if (typeof msg.payload === "object") {
msg.payload = JSON.stringify(msg.payload);
} else if (typeof msg.payload !== "string") {

View File

@ -85,6 +85,9 @@
<dd>If set, can be used to send cookies with the request.</dd>
<dt class="optional">payload</dt>
<dd>Sent as the body of the request.</dd>
<dt class="optional">rejectUnauthorized</dt>
<dd>If set to <code>true</code>, allows requests to be made to https sites that use
self signed certificates.</dd>
</dl>
<h3>Outputs</h3>
<dl class="message-properties">

View File

@ -135,7 +135,7 @@ module.exports = function(RED) {
}
var payload = null;
if (typeof msg.payload !== "undefined" && (method == "POST" || method == "PUT" || method == "PATCH" ) ) {
if (typeof msg.payload !== "undefined") {
if (typeof msg.payload === "string" || Buffer.isBuffer(msg.payload)) {
payload = msg.payload;
} else if (typeof msg.payload == "number") {
@ -196,6 +196,10 @@ module.exports = function(RED) {
}
if (tlsNode) {
tlsNode.addTLSOptions(opts);
} else {
if (msg.hasOwnProperty('rejectUnauthorized')) {
opts.rejectUnauthorized = msg.rejectUnauthorized;
}
}
var req = ((/^https/.test(urltotest))?https:http).request(opts,function(res) {
// Force NodeJs to return a Buffer (instead of a string)

View File

@ -59,7 +59,8 @@
"Sunday"
],
"on": "on",
"onstart": "Inject once at start?",
"onstart": "Inject once after",
"onceDelay": "seconds, then",
"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": {
@ -102,9 +103,13 @@
"output": "Output",
"msgprop": "message property",
"msgobj": "complete msg object",
"to": "to",
"to": "To",
"debtab": "debug tab",
"tabcon": "debug tab and console",
"toSidebar": "debug window",
"toConsole": "system console",
"toStatus": "node status (32 characters)",
"severity": "Level",
"notification": {
"activated": "Successfully activated: __label__",
"deactivated": "Successfully deactivated: __label__"
@ -144,13 +149,15 @@
"upload": "Upload",
"cert": "Certificate",
"key": "Private Key",
"passphrase": "Passphrase",
"ca": "CA Certificate",
"verify-server-cert":"Verify server certificate"
},
"placeholder": {
"cert":"path to certificate (PEM format)",
"key":"path to private key (PEM format)",
"ca":"path to CA certificate (PEM format)"
"ca":"path to CA certificate (PEM format)",
"passphrase":"private key passphrase (optional)"
},
"error": {
"missing-file": "No certificate/key file provided"
@ -195,6 +202,7 @@
"mustache": "Mustache template",
"plain": "Plain text",
"json": "Parsed JSON",
"yaml": "Parsed YAML",
"none": "none"
},
"templatevalue": "This is the payload: {{payload}} !"
@ -204,7 +212,7 @@
"for": "For",
"delaymsg": "Delay each message",
"delayfixed": "Fixed delay",
"delayvarmsg": "Set delay with msg.delay",
"delayvarmsg": "Override delay with msg.delay",
"randomdelay": "Random delay",
"limitrate": "Rate Limit",
"limitall": "All messages",
@ -270,6 +278,9 @@
"wait-reset": "wait to be reset",
"wait-for": "wait for",
"wait-loop": "resend it every",
"for": "Handling",
"bytopics": "each msg.topic independently",
"alltopics": "all messages",
"duration": {
"ms": "Milliseconds",
"s": "Seconds",
@ -607,6 +618,8 @@
"c2o": "CSV to Object options",
"o2c": "Object to CSV options",
"input": "Input",
"skip-s": "Skip first",
"skip-e": "lines",
"firstrow": "first row contains column names",
"output": "Output",
"includerow": "include column name row",
@ -661,7 +674,14 @@
},
"label": {
"o2j": "Object to JSON options",
"pretty": "Format JSON string"
"pretty": "Format JSON string",
"action": "Action",
"property": "Property",
"actions": {
"toggle": "Convert between JSON String & Object",
"str":"Always convert to JSON String",
"obj":"Always convert to JavaScript Object"
}
}
},
"yaml": {
@ -842,5 +862,18 @@
"seconds":"seconds",
"complete":"After a message with the <code>msg.complete</code> property set",
"tip":"This mode assumes this node is either paired with a <i>split</i> node or the received messages will have a properly configured <code>msg.parts</code> property."
},
"sort" : {
"key-type" : "Key type",
"payload" : "payload or element",
"exp" : "expression",
"key-exp" : "Key exp.",
"order" : "Order",
"ascending" : "ascending",
"descending" : "descending",
"as-number" : "as number",
"invalid-exp" : "invalid JSONata expression in sort node",
"too-many" : "too many pending messages in sort node",
"clear" : "clear pending message in sort node"
}
}

View File

@ -0,0 +1,846 @@
{
"common": {
"label": {
"payload": "内容",
"topic": "主题",
"name": "名称",
"username": "用户名",
"password": "密码"
},
"status": {
"connected": "已连接",
"not-connected": "未连接",
"disconnected": "已断开",
"connecting": "连接中",
"error": "错误",
"ok": "确认"
},
"notification": {
"error": "<strong>错误</strong>: __message__",
"errors": {
"not-deployed": "节点未部署",
"no-response": "服务器无反应",
"unexpected": "发生意外错误 (__status__) __message__"
}
},
"errors": {
"nooverride": "警告: 信息的属性已经不可以改写节点的属性. 详情参考 bit.ly/nr-override-msg-props"
}
},
"inject": {
"inject": "注入",
"repeat": "重复 = __repeat__",
"crontab": "crontab = __crontab__",
"stopped": "停止",
"failed": "注入失败: __error__",
"label": {
"repeat": "重复"
},
"timestamp": "时间戳",
"none": "空白",
"interval": "间隔",
"interval-time": "特定时间内间隔",
"time": "特定时间",
"seconds": "秒",
"minutes": "分钟",
"hours": "小时",
"between": "介于",
"previous": "之前数值",
"at": "在",
"and": "之间",
"every": "每个",
"days": [
"星期一",
"星期二",
"星期三",
"星期四",
"星期五",
"星期六",
"星期天"
],
"on": "在",
"onstart": "运行时注入?",
"tip": "<b>注意:</b> \"特定时间内间隔\" 和 \"特定时间\" 会用cron系统.<br/> 详情擦看信息页.",
"success": "成功注入: __label__",
"errors": {
"failed": "注入失败, 请查看日志"
}
},
"catch": {
"catch": "检测异常",
"catchNodes": "检测到 (__number__)",
"label": {
"source": "检测错误来自",
"node": "节点",
"type": "类型",
"selectAll": "全选",
"sortByLabel": "按名称排序",
"sortByType": "按类型排序"
},
"scope": {
"all": "所有节点",
"selected": "已选节点"
}
},
"status": {
"status": "状态 (所有)",
"statusNodes": "状态显示 (__number__)",
"label": {
"source": "状态报告来自",
"node": "节点",
"type": "类型",
"selectAll": "全选",
"sortByLabel": "按名称排序",
"sortByType": "按类型排序"
},
"scope": {
"all": "所有节点",
"selected": "已选节点"
}
},
"debug": {
"output": "输出",
"msgprop": "信息属性",
"msgobj": "完整信息",
"to": "目标",
"debtab": "调试窗口",
"tabcon": "调试窗口及终端控制台",
"notification": {
"activated": "成功激活: __label__",
"deactivated": "成功取消: __label__"
},
"sidebar": {
"label": "调试窗口",
"name": "名称",
"filterAll": "所有节点",
"filterSelected": "已选节点",
"filterCurrent": "目前流程",
"debugNodes": "调试节点",
"clearLog": "清理日志",
"openWindow": "在新窗口打开"
},
"messageMenu": {
"collapseAll": "折叠所有路径",
"clearPinned": "清理已固定路径",
"filterNode": "过滤此节点",
"clearFilter": "清除已设过滤"
}
},
"link": {
"linkIn": "连接入口",
"linkOut": "连接出口",
"label": {
"event": "事件名称",
"node": "节点名称",
"type": "流程",
"sortByFlow":"根据流程排序",
"sortByLabel": "根据名称排序"
}
},
"tls": {
"tls": "TLS设置",
"label": {
"use-local-files": "使用本地密匙及证书文件",
"upload": "上传",
"cert": "证书",
"key": "私钥",
"ca": "CA证书",
"verify-server-cert":"验证服务器证书"
},
"placeholder": {
"cert":"证书路径 (PEM 格式)",
"key":"私匙路径 (PEM 格式)",
"ca":"CA证书路径 (PEM 格式)"
},
"error": {
"missing-file": "无证书/密匙文件提供"
}
},
"exec": {
"label": {
"command": "命令",
"append": "追加",
"timeout": "超时",
"timeoutplace": "可选填",
"return": "输出",
"seconds": "秒"
},
"placeholder": {
"extraparams": "额外的输入参数"
},
"opt": {
"exec": "当命令任务完成时 - exec 模式",
"spawn": "当命令任务进行时 - spawn 模式"
},
"oldrc": "使用旧式输出模式 (传统模式)"
},
"function": {
"label": {
"function": "函数",
"outputs": "输出"
},
"error": {
"inputListener":"无法在函数里面加入对‘注入’事件的监视",
"non-message-returned":"函数节点尝试返回类型为 __type__ 的信息"
},
"tip": "可从信息页面查看更多关于如何编写函数的帮助"
},
"template": {
"label": {
"template": "模版",
"property": "设定属性",
"format": "语法高亮",
"syntax": "格式",
"output": "输出为",
"mustache": "Mustache 模版",
"plain": "纯文本",
"json": "解析JSON",
"none": "无"
},
"templatevalue": "This is the payload: {{payload}} !"
},
"delay": {
"action": "行为设置",
"for": "时长",
"delaymsg": "延迟每一条信息",
"delayfixed": "固定延迟时间",
"delayvarmsg": "用 msg.delay 改写延迟时长",
"randomdelay": "随机延迟",
"limitrate": "信息速度限制",
"limitall": "所有信息",
"limittopic": "每一个 msg.topic",
"fairqueue": "轮流发每一个主题",
"timedqueue": "发所有主题",
"milisecs": "毫秒",
"secs": "秒",
"sec": "秒",
"mins": "分",
"min": "分",
"hours": "小时",
"hour": "小时",
"days": "日",
"day": "日",
"between": "介于",
"and": "和",
"rate": "速度",
"msgper": "信息 每",
"dropmsg": "不传输中间信息",
"label": {
"delay": "延迟",
"variable": "变量",
"limit": "限制",
"limitTopic": "限制主题",
"random": "随机",
"units" : {
"second": {
"plural" : "秒",
"singular": "秒"
},
"minute": {
"plural" : "分钟",
"singular": "分钟"
},
"hour": {
"plural" : "小时",
"singular": "小时"
},
"day": {
"plural" : "日",
"singular": "日"
}
}
},
"error": {
"buffer": "缓冲超过了 1000 条信息",
"buffer1": "缓冲超过了 10000 条信息"
}
},
"trigger": {
"send": "发送",
"then": "然后",
"then-send": "然后发送",
"output": {
"string": "字符串",
"number": "数字",
"existing": "现有信息对象",
"original": "原本信息对象",
"latest": "最新信息对象",
"nothing": "无"
},
"wait-reset": "等待至重置",
"wait-for": "等待",
"wait-loop": "重发每",
"duration": {
"ms": "毫秒",
"s": "秒",
"m": "分钟",
"h": "小时"
},
"extend": " 如有新信息,延长延迟",
"label": {
"trigger": "触发",
"trigger-block": "出发并阻止",
"trigger-loop": "重发每",
"reset": "重置触发节点条件 如果:",
"resetMessage":"msg.reset 已设置",
"resetPayload":"msg.payload 等于",
"resetprompt": "可选填"
}
},
"comment": {
"label": {
"title": "标题",
"body": "主体"
},
"tip": "提示: 主题内容可以添加格式化为 <a href=\"https://help.github.com/articles/markdown-basics/\" target=\"_blank\">Github 风格 Markdown</a>"
},
"unknown": {
"label": {
"unknown": "未知"
},
"tip": "<p>此节点是您安装但Node-RED所不知道的类型。</p><p><i>如果在此状态下部署节点,那么它的配置将被保留,但是流程将不会启动,直到安装缺少的类型。</i></p><p>有关更多帮助,请参阅信息侧栏</p>"
},
"mqtt": {
"label": {
"broker": "服务端",
"example": "e.g. localhost",
"qos": "QoS",
"clientid": "客户端ID",
"port": "端口",
"keepalive": "存货定时器(秒)",
"cleansession": "使用新的会话",
"use-tls": "使用安全连接 (SSL/TLS)",
"tls-config":"TLS 设置",
"verify-server-cert":"验证服务器证书",
"compatmode": "使用旧式 MQTT 3.1 支持"
},
"tabs-label": {
"connection": "连接",
"security": "安全",
"will": "终结信息",
"birth": "初始信息"
},
"placeholder": {
"clientid": "留空白将会自动生成",
"clientid-nonclean":"如非新会话必须设置客户端ID",
"will-topic": "留空白将禁止终止信息",
"birth-topic": "留空白将禁止初始信息"
},
"state": {
"connected": "已连接到服务端: __broker__",
"disconnected": "已断开与服务端 __broker__ 的链接",
"connect-failed": "与服务端 __broker__ 的连接失败"
},
"retain": "保留",
"true": "是",
"false": "否",
"tip": "提示: 如果你想用msg属性来设置主题qos 或者是否保存,请将这几个区域留空",
"errors": {
"not-defined": "主题未设置",
"missing-config": "未设置服务端",
"invalid-topic": "主题无效",
"nonclean-missingclientid": "客户端ID未设定使用新会话"
}
},
"httpin": {
"label": {
"method": "方法",
"url": "URL",
"doc": "文档",
"return": "返回",
"upload": "接受文件上传?",
"status": "状态码",
"headers": "头子段",
"other": "其他"
},
"setby": "- 用 msg.method 设定 -",
"basicauth": "基本认证",
"use-tls": "使用安全连接 (SSL/TLS) ",
"tls-config":"TLS 设置",
"utf8": "UTF-8 字符串",
"binary": "二进制缓冲模块",
"json": "解析JSON对象",
"tip": {
"in": "相对URL",
"res": "发送到此节点的消息<b>必须</b>来自 <i>http input</i> 节点",
"req": "提示如果JSON解析失败则获取的字符串将按原样返回."
},
"httpreq": "http 请求",
"errors": {
"not-created": "当httpNodeRoot为否时无法创建 http-in 节点",
"missing-path": "无路径",
"no-response": "无响应对象",
"json-error": "JSON 解析错误",
"no-url": "未设定 URL",
"deprecated-call":"__method__ 方法已弃用",
"invalid-transport":"非HTTP传输请求"
},
"status": {
"requesting": "请求中"
}
},
"websocket": {
"label": {
"type": "类型",
"path": "路径",
"url": "URL"
},
"listenon": "监听",
"connectto": "连接",
"payload": "发送/接受 有效载荷",
"message": "发送/接受 完整信息",
"tip": {
"path1": "默认情况下,<code> payload </code>将包含要发送或从Websocket接收的数据。侦听器可以配置为以JSON格式的字符串发送或接收整个消息对象.",
"path2": "这条路径将相对于 ",
"url1": "URL 应该使用 ws:&#47;&#47; 或者 wss:&#47;&#47; 方案并指向现有的websocket侦听器.",
"url2": "默认情况下,<code> payload </code>将包含要发送或从Websocket接收的数据。可以将客户端配置为以JSON格式的字符串发送或接收整个消息对象."
},
"errors": {
"connect-error": "ws连接发生了错误: ",
"send-error": "发送时发生了错误: ",
"missing-conf": "未设置服务器"
}
},
"watch": {
"label": {
"files": "文件(s)",
"recursive": "递归查看文件夹"
},
"placeholder": {
"files": "逗号分开文件或文件夹"
},
"tip": "在Windows上请务必使用双斜杠 \\\\ 来隔开文件夹名字"
},
"tcpin": {
"label": {
"type": "类型",
"output": "输出",
"port": "端口",
"host": "主服务器",
"payload": "有效载荷(s)",
"delimited": "分隔符号",
"close-connection": "是否在成功发送每条信息后断开连接?",
"decode-base64": "用 Base64 解码信息?",
"server": "服务器",
"return": "返回",
"ms": "毫秒",
"chars": "字符"
},
"type": {
"listen": "监听",
"connect": "连接",
"reply": "回应到 TCP"
},
"output": {
"stream": "字串流",
"single": "单一",
"buffer": "缓冲模块",
"string": "字符串",
"base64": "Base64 字符串"
},
"return": {
"timeout": "在固定时间超时后",
"character": "当收到某个字符时",
"number": "固定数目的字符",
"never": "永不 - 保持连接",
"immed": "马上 - 不需要等待回复"
},
"status": {
"connecting": "正在连接到 __host__:__port__",
"connected": "已经连接到 __host__:__port__",
"listening-port": "监听端口 __port__",
"stopped-listening": "已停止监听端口",
"connection-from": "连接来自 __host__:__port__",
"connection-closed": "连接已关闭 __host__:__port__",
"connections": "__count__ 段连接",
"connections_plural": "__count__ 段连接"
},
"errors": {
"connection-lost": "连接中断 __host__:__port__",
"timeout": "超时关闭套接字连接,端口 __port__",
"cannot-listen": "无法监听端口 __port__, 错误: __error__",
"error": "错误: __error__",
"socket-error": "套接字连接错误来自 __host__:__port__",
"no-host": "主服务器和/或者端口未设定",
"connect-timeout": "连接超时",
"connect-fail": "连接失败"
}
},
"udp": {
"label": {
"listen": "监听",
"onport": "端口",
"using": "使用",
"output": "输出",
"group": "组",
"interface": "本地IP",
"interfaceprompt": "(可选填)本地 IP 绑定到",
"send": "发送一个",
"toport": "到端口",
"address": "地址",
"decode-base64": "是否解码编码为Base64的信息?"
},
"placeholder": {
"interface": "可选填eth0 的 ip 地址",
"address": "目的地 ip 地址"
},
"udpmsgs": "udp 信息",
"mcmsgs": "组播信息",
"udpmsg": "udp 信息",
"bcmsg": "广播信息",
"mcmsg": "组播信息",
"output": {
"buffer": "缓冲模块",
"string": "字符串",
"base64": "Base64编码字符串"
},
"bind": {
"random": "绑定到任意本地端口",
"local": "绑定到本地端口",
"target": "绑定到目标端口"
},
"tip": {
"in": "提示:确保您的防火墙将允许数据进入",
"out": "提示:如果要使用<code> msg.ip </code>和<code> msg.port </code>设置,请将地址和端口留空",
"port": "端口已在使用: "
},
"status": {
"listener-at": "udp 监听器正在监听 __host__:__port__",
"mc-group": "udp 组播到 __group__",
"listener-stopped": "udp 监听器已停止",
"output-stopped": "udp 输出已停止",
"mc-ready": "udp 组播已准备好: __outport__ -> __host__:__port__",
"bc-ready": "udp 广播已准备好: __outport__ -> __host__:__port__",
"ready": "udp 已准备好: __outport__ -> __host__:__port__",
"ready-nolocal": "udp 已准备好: __host__:__port__"
},
"errors": {
"access-error": "UDP 访问错误, 你可能需要root权限才能接入1024以下的端口",
"error": "错误: __error__",
"bad-mcaddress": "无效的组播地址",
"interface": "必须是需要接口的 ip 地址",
"ip-notset": "udp: ip 地址未设定",
"port-notset": "udp: 端口未设定",
"port-invalid": "udp: 无效端口号码",
"alreadyused": "udp: 端口已经在使用"
}
},
"switch": {
"label": {
"property": "属性",
"rule": "规矩"
},
"and": "和",
"checkall": "全选所有规则",
"stopfirst": "接受第一条匹配信息后停止",
"ignorecase": "忽视大小写",
"rules": {
"btwn":"在之间",
"cont":"包含",
"regex":"匹配正则表达式",
"true":"为真",
"false":"为假",
"null":"为空值",
"nnull":"非空值",
"else":"除此以外"
},
"errors": {
"invalid-expr": "无效 JSONata 表达: __error__"
}
},
"change": {
"label": {
"rules": "规矩",
"rule": "规矩",
"set": "设定 __property__",
"change": "改变 __property__",
"delete": "删除 __property__",
"move": "移动 __property__",
"changeCount": "改变: __count__ 条规矩",
"regex": "用正则表达式"
},
"action": {
"set": "设定",
"change": "更改",
"delete": "删除",
"move": "转移",
"to": "到",
"search": "搜索",
"replace": "更改为"
},
"errors": {
"invalid-from": "无效来源 from 属性: __error__",
"invalid-json": "无效 to 属性",
"invalid-expr": "无效 JSONata 表示: __error__"
}
},
"range": {
"label": {
"action": "行为作用",
"inputrange": "映射输入数据范围",
"resultrange": "至目标范围",
"from": "从",
"to": "到",
"roundresult": "取最接近整数?"
},
"placeholder": {
"min": "e.g. 0",
"maxin": "e.g. 99",
"maxout": "e.g. 255"
},
"scale": {
"payload": "按比例 msg.payload",
"limit": "按比例并设定界限至目标范围",
"wrap": "按比例并包含在目标范围内"
},
"tip": "提示: 此节点仅对数字有效",
"errors": {
"notnumber": "不是一个数"
}
},
"csv": {
"label": {
"columns": "列",
"separator": "分隔符号",
"c2o": "CSV 至对象选项",
"o2c": "对象至 to CSV 选项",
"input": "输入",
"firstrow": "第一行包含列名",
"output": "输出",
"includerow": "包含列名行",
"newline": "新的一行"
},
"placeholder": {
"columns": "用逗号分割列名"
},
"separator": {
"comma": "逗号",
"tab": "Tab",
"space": "空格",
"semicolon": "分号",
"colon": "冒号",
"hashtag": "井号",
"other": "其他..."
},
"output": {
"row": "每行包含一条信息",
"array": "一条单独信息 [数组]"
},
"newline": {
"linux": "Linux (\\n)",
"mac": "Mac (\\r)",
"windows": "Windows (\\r\\n)"
},
"errors": {
"csv_js": "此节点仅处理 CSV 字符串或 js 对象",
"obj_csv": "对象 -> CSV 转换未设定列模版"
}
},
"html": {
"label": {
"select": "选取项",
"output": "输出"
},
"output": {
"html": "选定元素的 html 内容",
"text": "选定元素的纯文本内容",
"attr": "选定元素的所有属性对象"
},
"format": {
"single": "由一个单独信息包含一个数组",
"multi": "由多条信息,每一条包含一个元素"
}
},
"json": {
"errors": {
"dropped-object": "忽略非对象格式的有效负载",
"dropped": "忽略不支持格式的有效负载类型",
"dropped-error": "转换有效负载失败"
},
"label": {
"o2j": "对象至 JSON 选项",
"pretty": "格式化 JSON 字符串"
}
},
"yaml": {
"errors": {
"dropped-object": "忽略非对象格式的有效负载",
"dropped": "忽略不支持格式的有效负载类型",
"dropped-error": "转换有效负载失败"
}
},
"xml": {
"label": {
"represent": "XML标签属性的属性名称",
"prefix": "标签文本内容的属性名称",
"advanced": "高级选项",
"x2o": "XML到对象选项"
},
"errors": {
"xml_js": "此节点仅处理XML字符串或JS对象."
}
},
"rpi-gpio": {
"label": {
"gpiopin": "GPIO",
"selectpin": "选择引脚",
"resistor": "电阻?",
"readinitial": "在部署/重新启动时读取引脚的初始状态?",
"type": "类型",
"initpin": "初始化引脚状态?",
"debounce": "去抖动",
"freq": "频率",
"button": "按钮",
"pimouse": "Pi 鼠标",
"pikeyboard": "Pi 键盘",
"left": "左",
"right": "右",
"middle": "中"
},
"resistor": {
"none": "无",
"pullup": "上拉电阻",
"pulldown": "下拉电阻"
},
"digout": "数字输出",
"pwmout": "PWM 输出",
"servo": "伺服输出",
"initpin0": "初始引脚电平 - 低 (0)",
"initpin1": "初始引脚电平 - 高 (1)",
"left": "左",
"right": "右",
"middle": "中",
"any": "任何",
"pinname": "引脚",
"alreadyuse": "已经在用",
"alreadyset": "已经设定为",
"tip": {
"pin": "<b>引脚在使用</b>: ",
"in": "提示: 仅接受数字输入 - 输出必须为 0 或 1.",
"dig": "提示: 如用数字输出 - 输入必须为 0 或 1.",
"pwm": "提示: 如用PWM输出 - 输入必须为0至100之间; 如用高频率可能会比预期占用更多CPU资源.",
"ser": "<b>提示</b>: 如用伺服输出 - 输入必须为0至100之间. 50为中间值."
},
"types": {
"digout": "数字输出",
"input": "输入",
"pullup": "含有上拉电阻的输入",
"pulldown": "含有下拉电阻的输入",
"pwmout": "PWM 输出",
"servo": "伺服输出"
},
"status": {
"stopped": "已停止",
"closed": "已关闭",
"not-running": "不运行"
},
"errors": {
"ignorenode": "忽略树莓派的特定节点",
"version": "版本命令失败",
"sawpitype": "查看Pi类型",
"libnotfound": "找不到树莓派 RPi.GPIO python库",
"alreadyset": "GPIO 引脚 __pin__ 已经被设定为类型: __type__",
"invalidpin": "无效 GPIO 引脚",
"invalidinput": "无效输入",
"needtobeexecutable": "__command__ 需要为可运行命令",
"mustbeexecutable": "nrgpio 需要为可运行",
"commandnotfound": "nrgpio 命令不存在",
"commandnotexecutable": "nrgpio 命令无法运行",
"error": "错误: __error__",
"pythoncommandnotfound": "nrpgio python 命令不运行"
}
},
"tail": {
"label": {
"filename": "文件名",
"type": "文件类型",
"splitlines": "拆分线 \\n?"
},
"action": {
"text": "文本 - 返回字符串",
"binary": "二进制 - 返回缓冲区"
},
"errors": {
"windowsnotsupport": "Windows目前不支持."
}
},
"file": {
"label": {
"filename": "文件名",
"action": "行为",
"addnewline": "向每个有效载荷添加换行符(\\ n?",
"createdir": "创建目录(如果不存在)?",
"outputas": "输出",
"breakchunks": "分拆成块",
"breaklines": "分拆成行",
"filelabel": "文件",
"sendError": "发生错误时发送消息(传统模式)",
"deletelabel": "删除 __file__"
},
"action": {
"append": "追加至文件",
"overwrite": "改写文件",
"delete": "删除文件"
},
"output": {
"utf8": "一条单独 utf8 字符串",
"buffer": "一条单独缓冲区对象",
"lines": "每行一条信息",
"stream": "缓冲区流"
},
"status": {
"wrotefile": "写入至文件: __file__",
"deletedfile": "删除文件: __file__",
"appendedfile": "追加至文件: __file__"
},
"errors": {
"nofilename": "未指定文件名",
"invaliddelete": "警告:无效删除。请在配置对话框中使用特定的删除选项",
"deletefail": "无法删除文件: __error__",
"writefail": "无法写入文件: __error__",
"appendfail": "无法追加到文件: __error__",
"createfail": "文件创建失败: __error__"
},
"tip": "提示: 文件名应该是绝对路径否则它将相对于Node-RED进程的工作目录。"
},
"split": {
"intro":"分裂 <code>msg.payload</code> 基于类型:",
"object":"<b>对象</b>",
"objectSend":"发送每个键/值对的消息",
"strBuff":"<b>字符串</b> / <b>缓冲区</b>",
"array":"<b>数组</b>",
"splitUsing":"拆分使用",
"splitLength":"固定长度",
"stream":"处理为消息流",
"addname":" 复制键到 "
},
"join":{
"mode":{
"mode":"模式",
"auto":"自动",
"custom":"手动"
},
"combine":"结合每一个",
"create":"创建输出",
"type":{
"string":"字符串",
"array":"数组",
"buffer":"缓冲区",
"object":"键/值对象",
"merged":"合并对象"
},
"using":"使用数值",
"key":"当作键",
"joinedUsing":"合并符号",
"send":"发送信息:",
"afterCount":"当达到一定数目的信息部件时",
"count":"数目",
"subsequent":"和每个后续的消息",
"afterTimeout":"第一条消息的超时后",
"seconds":"秒",
"complete":"在使用<code> msg.complete </ code>属性设置的消息后",
"tip":"此模式假定此节点与 <i>split</i> 或者接收到的消息将具有正确配置的 <code>msg.parts</code> 属性."
}
}

View File

@ -114,6 +114,9 @@
label: function() {
return this.name||"switch";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
var node = this;
var previousValueType = {value:"prev",label:this._("inject.previous"),hasValue:false};
@ -199,6 +202,7 @@
} else if (type === "btwn") {
row2.hide();
row3.show();
btwnValue2Field.typedInput('show');
} else {
row2.hide();
row3.hide();

View File

@ -29,7 +29,7 @@
<dt>Delete</dt>
<dd>delete a property.</dd>
<dt>Move</dt>
<dd>move or rename a property.</dt>
<dd>move or rename a property.</dd>
</dl>
<p>The "expression" type uses the <a href="http://jsonata.org/" target="_new">JSONata</a>
query and expression language.

View File

@ -129,9 +129,9 @@ module.exports = function(RED) {
if (rule.fromt === 'msg' || rule.fromt === 'flow' || rule.fromt === 'global') {
if (rule.fromt === "msg") {
fromValue = RED.util.getMessageProperty(msg,rule.from);
} else if (rule.tot === 'flow') {
} else if (rule.fromt === 'flow') {
fromValue = node.context().flow.get(rule.from);
} else if (rule.tot === 'global') {
} else if (rule.fromt === 'global') {
fromValue = node.context().global.get(rule.from);
}
if (typeof fromValue === 'number' || fromValue instanceof Number) {
@ -201,7 +201,7 @@ module.exports = function(RED) {
} else if (rule.t === 'set') {
target.set(property,value);
} else if (rule.t === 'change') {
current = target.get(msg,property);
current = target.get(property);
if (typeof current === 'string') {
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
// str representation of exact from number/boolean

View File

@ -54,7 +54,7 @@
<dt>payload<span class="property-type">object | string | array | buffer</span></dt>
<dd>The behaviour of the node is determined by the type of <code>msg.payload</code>:
<ul>
<li><b>string</b>/<b>buffer</b> - the message is split using the specified character (default: <code>\n</code>), buffer sequence or into fixed lengths.
<li><b>string</b>/<b>buffer</b> - the message is split using the specified character (default: <code>\n</code>), buffer sequence or into fixed lengths.</li>
<li><b>array</b> - the message is split into either individual array elements, or arrays of a fixed-length.</li>
<li><b>object</b> - a message is sent for each key/value pair of the object.</li>
</ul>

View File

@ -300,6 +300,9 @@ module.exports = function(RED) {
}
RED.util.setMessageProperty(group.msg,node.property,group.payload.join(groupJoinChar));
} else {
if (node.propertyType === 'full') {
group.msg = RED.util.cloneMessage(group.msg);
}
RED.util.setMessageProperty(group.msg,node.property,group.payload);
}
if (group.msg.hasOwnProperty('parts') && group.msg.parts.hasOwnProperty('parts')) {
@ -438,7 +441,7 @@ module.exports = function(RED) {
}
} else {
for (propertyKey in property) {
if (property.hasOwnProperty(propertyKey)) {
if (property.hasOwnProperty(propertyKey) && propertyKey !== '_msgid') {
group.payload[propertyKey] = property[propertyKey];
}
}

View File

@ -0,0 +1,112 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!DOCTYPE html>
<script type="text/x-red" data-template-name="sort">
<div class="form-row">
<label><i class="fa fa-dot-circle-o"></i> <span data-i18n="sort.key-type"></span></label>
<select id="node-input-keyType" style="width:200px;">
<option value="payload" data-i18n="sort.payload"></option>
<option value="exp" data-i18n="sort.exp"></option>
</select>
</div>
<div class="node-row-sort-key">
<div class="form-row">
<label><i class="fa fa-filter"></i> <span data-i18n="sort.key-exp"></span></label>
<input type="text" id="node-input-key" style="width:70%;">
</div>
</div>
<div class="form-row">
<label><i class="fa fa-random"></i> <span data-i18n="sort.order"></span></label>
<select id="node-input-order" style="width:200px;">
<option value="ascending" data-i18n="sort.ascending"></option>
<option value="descending" data-i18n="sort.descending"></option>
</select>
</div>
<div class="form-row" id="node-as_num">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-as_num" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-as_num" style="width: 70%;" data-i18n="sort.as-number"></label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
</div>
</script>
<script type="text/x-red" data-help-name="sort">
<p>A function that sorts a sequence of messages or payload of array type.</p>
<p>When paired with the <b>split</b> node, it will reorder the
messages.</p>
<p>The sorting order can be:</p>
<ul>
<li><b>ascending</b>,</li>
<li><b>descending</b>.</li>
</ul>
<p>For numbers, numerical ordering can be specified by a checkbox.</p>
<p>Sort key can be <code>payload</code> or any JSONata expression for sorting messages, element value or any JSONata expression for sorting payload of array type.</p>
<p>The sort node relies on the received messages to have <code>msg.parts</code> set for sorting messages. The split node generates this property, but can be manually created. It has the following properties:</p>
<p>
<ul>
<li><code>id</code> - an identifier for the group of messages</li>
<li><code>index</code> - the position within the group</li>
<li><code>count</code> - the total number of messages in the group</li>
</ul>
</p>
<p><b>Note:</b> This node internally keeps messages for its operation. In order to prevent unexpected memory usage, maximum number of messages kept can be specified. Default is no limit on number of messages.
<ul>
<li><code>sortMaxKeptMsgsCount</code> property set in <b>settings.js</b>.</li>
</ul>
</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('sort',{
category: 'function',
color:"#E2D96E",
defaults: {
name: { value:"" },
order: { value:"ascending" },
as_num : { value:false },
keyType : { value:"payload" },
key : { value:"" }
},
inputs:1,
outputs:1,
icon: "sort.png",
label: function() {
return this.name || "sort";
},
labelStyle: function() {
return this.name ? "node_label_italic" : "";
},
oneditprepare: function() {
$("#node-input-key").typedInput({default:'jsonata', types:['jsonata']});
$("#node-input-keyType").change(function(e) {
var val = $(this).val();
$(".node-row-sort-key").toggle(val === 'exp');
});
$("#node-input-keyType").change();
$("#node-input-order").change();
}
});
</script>

212
nodes/core/logic/18-sort.js Normal file
View File

@ -0,0 +1,212 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
"use strict";
var _max_kept_msgs_count = undefined;
function max_kept_msgs_count(node) {
if (_max_kept_msgs_count === undefined) {
var name = "sortMaxKeptMsgsCount";
if (RED.settings.hasOwnProperty(name)) {
_max_kept_msgs_count = RED.settings[name];
}
else {
_max_kept_msgs_count = 0;
}
}
return _max_kept_msgs_count;
}
function eval_jsonata(node, code, val) {
try {
return RED.util.evaluateJSONataExpression(code, val);
}
catch (e) {
node.error(RED._("sort.invalid-exp"));
throw e;
}
}
function get_context_val(node, name, dval) {
var context = node.context();
var val = context.get(name);
if (val === undefined) {
context.set(name, dval);
return dval;
}
return val;
}
function SortNode(n) {
RED.nodes.createNode(this, n);
var node = this;
var pending = get_context_val(node, 'pending', {})
var pending_count = 0;
var pending_id = 0;
var order = n.order || "ascending";
var as_num = n.as_num || false;
var key_is_payload = (n.keyType === 'payload');
var key_exp = undefined;
if (!key_is_payload) {
try {
key_exp = RED.util.prepareJSONataExpression(n.key, this);
}
catch (e) {
node.error(RED._("sort.invalid-exp"));
return;
}
}
var dir = (order === "ascending") ? 1 : -1;
var conv = as_num
? function(x) { return Number(x); }
: function(x) { return x; };
function gen_comp(key) {
return function(x, y) {
var xp = conv(key(x));
var yp = conv(key(y));
if (xp === yp) { return 0; }
if (xp > yp) { return dir; }
return -dir;
};
}
function send_group(group) {
var key = key_is_payload
? function(msg) { return msg.payload; }
: function(msg) {
return eval_jsonata(node, key_exp, msg);
};
var comp = gen_comp(key);
var msgs = group.msgs;
try {
msgs.sort(comp);
}
catch (e) {
return; // not send when error
}
for (var i = 0; i < msgs.length; i++) {
var msg = msgs[i];
msg.parts.index = i;
node.send(msg);
}
}
function sort_payload(msg) {
var payload = msg.payload;
if (Array.isArray(payload)) {
var key = key_is_payload
? function(elem) { return elem; }
: function(elem) {
return eval_jsonata(node, key_exp, elem);
};
var comp = gen_comp(key);
try {
payload.sort(comp);
}
catch (e) {
return false;
}
return true;
}
return false;
}
function check_parts(parts) {
if (parts.hasOwnProperty("id") &&
parts.hasOwnProperty("index")) {
return true;
}
return false;
}
function clear_pending() {
for(var key in pending) {
node.log(RED._("sort.clear"), pending[key].msgs[0]);
delete pending[key];
}
pending_count = 0;
}
function remove_oldest_pending() {
var oldest = undefined;
var oldest_key = undefined;
for(var key in pending) {
var item = pending[key];
if((oldest === undefined) ||
(oldest.seq_no > item.seq_no)) {
oldest = item;
oldest_key = key;
}
}
if(oldest !== undefined) {
delete pending[oldest_key];
return oldest.msgs.length;
}
return 0;
}
function process_msg(msg) {
if (!msg.hasOwnProperty("parts")) {
if (sort_payload(msg)) {
node.send(msg);
}
return;
}
var parts = msg.parts;
if (!check_parts(parts)) {
return;
}
var gid = parts.id;
if (!pending.hasOwnProperty(gid)) {
pending[gid] = {
count: undefined,
msgs: [],
seq_no: pending_id++
};
}
var group = pending[gid];
var msgs = group.msgs;
msgs.push(msg);
if (parts.hasOwnProperty("count")) {
group.count = parts.count;
}
pending_count++;
if (group.count === msgs.length) {
delete pending[gid]
send_group(group);
pending_count -= msgs.length;
}
var max_msgs = max_kept_msgs_count(node);
if ((max_msgs > 0) && (pending_count > max_msgs)) {
pending_count -= remove_oldest_pending();
node.error(RED._("sort.too-many"), msg);
}
}
this.on("input", function(msg) {
process_msg(msg);
});
this.on("close", function() {
clear_pending();
})
}
RED.nodes.registerType("sort", SortNode);
}

View File

@ -24,29 +24,31 @@
</div>
<hr align="middle"/>
<div class="form-row">
<label style="width:100%; border-bottom: 1px solid #eee;"><span data-i18n="csv.label.c2o"></span></label>
<label style="width:100%; border-bottom:1px solid #eee;"><span data-i18n="csv.label.c2o"></span></label>
</div>
<div class="form-row" style="padding-left: 20px;">
<div class="form-row" style="padding-left:20px;">
<label><i class="fa fa-sign-in"></i> <span data-i18n="csv.label.input"></span></label>
<input style="width:20px; vertical-align:top; margin-right: 5px;" type="checkbox" id="node-input-hdrin"><label style="width: auto;" for="node-input-hdrin"><span data-i18n="csv.label.firstrow"></span>
<span data-i18n="csv.label.skip-s"></span>&nbsp;<input type="text" id="node-input-skip" style="width:30px; height:25px;"/>&nbsp;<span data-i18n="csv.label.skip-e"></span><br/>
<label>&nbsp;</label>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-hdrin"><label style="width:auto; margin-top:7px;" for="node-input-hdrin"><span data-i18n="csv.label.firstrow"></span>
</div>
<div class="form-row" style="padding-left: 20px;">
<div class="form-row" style="padding-left:20px;">
<label><i class="fa fa-sign-out"></i> <span data-i18n="csv.label.output"></span></label>
<select type="text" id="node-input-multi" style="width: 250px;">
<select type="text" id="node-input-multi" style="width:250px;">
<option value="one" data-i18n="csv.output.row"></option>
<option value="mult" data-i18n="csv.output.array"></option>
</select>
</div>
<div class="form-row" style="margin-top: 20px">
<label style="width:100%; border-bottom: 1px solid #eee;"><span data-i18n="csv.label.o2c"></span></label>
<div class="form-row" style="margin-top:20px">
<label style="width:100%; border-bottom:1px solid #eee;"><span data-i18n="csv.label.o2c"></span></label>
</div>
<div class="form-row" style="padding-left: 20px;">
<div class="form-row" style="padding-left:20px;">
<label><i class="fa fa-sign-in"></i> <span data-i18n="csv.label.output"></span></label>
<input style="width:20px; vertical-align:top; margin-right: 5px;" type="checkbox" id="node-input-hdrout"><label style="width:auto;" for="node-input-hdrout"><span data-i18n="csv.label.includerow"></span></span>
<input style="width:20px; vertical-align:top; margin-right:5px;" 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" style="padding-left: 20px;">
<div class="form-row" style="padding-left:20px;">
<label></label>
<label style="width: auto; margin-right: 10px;" for="node-input-ret"><span data-i18n="csv.label.newline"></span></label>
<label style="width:auto; margin-right:10px;" for="node-input-ret"><span data-i18n="csv.label.newline"></span></label>
<select style="width:150px;" id="node-input-ret">
<option value='\n' data-i18n="csv.newline.linux"></option>
<option value='\r' data-i18n="csv.newline.mac"></option>
@ -95,7 +97,8 @@
hdrout: {value:""},
multi: {value:"one",required:true},
ret: {value:'\\n'},
temp: {value:""}
temp: {value:""},
skip: {value:"0"}
},
inputs:1,
outputs:1,
@ -107,6 +110,9 @@
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
console.log(this.skip,$("#node-input-skip").val());
if (this.skip === undefined) { this.skip = 0; $("#node-input-skip").val("0");}
$("#node-input-skip").spinner({ min:0 });
if (this.sep == "," || this.sep == "\\t" || this.sep == ";" || this.sep == ":" || this.sep == " " || this.sep == "#") {
$("#node-input-select-sep").val(this.sep);
$("#node-input-sep").hide();

View File

@ -28,6 +28,8 @@ module.exports = function(RED) {
this.hdrin = n.hdrin || false;
this.hdrout = n.hdrout || false;
this.goodtmpl = true;
this.skip = parseInt(n.skip || 0);
this.store = [];
var tmpwarn = true;
var node = this;
@ -72,15 +74,18 @@ module.exports = function(RED) {
}
else {
if ((node.template.length === 1) && (node.template[0] === '')) {
/* istanbul ignore else */
if (tmpwarn === true) { // just warn about missing template once
node.warn(RED._("csv.errors.obj_csv"));
tmpwarn = false;
}
ou = "";
for (var p in msg.payload[0]) {
/* istanbul ignore else */
if (msg.payload[0].hasOwnProperty(p)) {
/* istanbul ignore else */
if (typeof msg.payload[0][p] !== "object") {
var q = msg.payload[0][p];
var q = "" + msg.payload[0][p];
if (q.indexOf(node.quo) !== -1) { // add double quotes if any quotes
q = q.replace(/"/g, '""');
ou += node.quo + q + node.quo + node.sep;
@ -100,9 +105,8 @@ module.exports = function(RED) {
ou += node.sep;
}
else {
// aaargh - resorting to eval here - but fairly contained front and back.
var p = RED.util.ensureString(eval("msg.payload[s]."+node.template[t]));
var p = RED.util.ensureString(RED.util.getMessageProperty(msg,"payload["+s+"]['"+node.template[t]+"']"));
/* istanbul ignore else */
if (p === "undefined") { p = ""; }
if (p.indexOf(node.quo) !== -1) { // add double quotes if any quotes
p = p.replace(/"/g, '""');
@ -132,16 +136,26 @@ module.exports = function(RED) {
var a = []; // output array is needed for multiline option
var first = true; // is this the first line
var line = msg.payload;
var linecount = 0;
var tmp = "";
var reg = /^[-]?[0-9]*\.?[0-9]+$/;
if (msg.hasOwnProperty("parts")) {
linecount = msg.parts.index;
if (msg.parts.index > node.skip) { first = false; }
}
// For now we are just going to assume that any \r or \n means an end of line...
// got to be a weird csv that has singleton \r \n in it for another reason...
// Now process the whole file/line
for (var i = 0; i < line.length; i++) {
if (first && (linecount < node.skip)) {
if (line[i] === "\n") { linecount += 1; }
continue;
}
if ((node.hdrin === true) && first) { // if the template is in the first line
if ((line[i] === "\n")||(line[i] === "\r")) { // look for first line break
if ((line[i] === "\n")||(line[i] === "\r")||(line.length - i === 1)) { // look for first line break
if (line.length - i === 1) { tmp += line[i]; }
node.template = clean(tmp.split(node.sep));
first = false;
}
@ -173,12 +187,7 @@ module.exports = function(RED) {
o[node.template[j]] = k[j];
}
if (JSON.stringify(o) !== "{}") { // don't send empty objects
if (node.multi === "one") {
var newMessage = RED.util.cloneMessage(msg);
newMessage.payload = o;
node.send(newMessage); // either send
}
else { a.push(o); } // or add to the array
a.push(o); // add to the array
}
j = 0;
k = [""];
@ -199,17 +208,50 @@ module.exports = function(RED) {
o[node.template[j]] = k[j];
}
if (JSON.stringify(o) !== "{}") { // don't send empty objects
if (node.multi === "one") {
var newMessage = RED.util.cloneMessage(msg);
newMessage.payload = o;
node.send(newMessage); // either send
}
else { a.push(o); } // or add to the aray
a.push(o); // add to the array
}
var has_parts = msg.hasOwnProperty("parts");
if (node.multi !== "one") {
msg.payload = a;
node.send(msg); // finally send the array
if (has_parts) {
if (JSON.stringify(o) !== "{}") {
node.store.push(o);
}
if (msg.parts.index + 1 === msg.parts.count) {
msg.payload = node.store;
delete msg.parts;
node.send(msg);
node.store = [];
}
}
else {
node.send(msg); // finally send the array
}
}
else {
var len = a.length;
for (var i = 0; i < len; i++) {
var newMessage = RED.util.cloneMessage(msg);
newMessage.payload = a[i];
if (!has_parts) {
newMessage.parts = {
id: msg._msgid,
index: i,
count: len
};
}
else {
newMessage.parts.index -= node.skip;
newMessage.parts.count -= node.skip;
if (node.hdrin) { // if we removed the header line then shift the counts by 1
newMessage.parts.index -= 1;
newMessage.parts.count -= 1;
}
}
node.send(newMessage);
}
}
node.linecount = 0;
}
catch(e) { node.error(e,msg); }
}

View File

@ -31,6 +31,11 @@ module.exports = function(RED) {
try {
var $ = cheerio.load(msg.payload);
var pay = [];
var count = 0;
$(tag).each(function() {
count++;
});
var index = 0;
$(tag).each(function() {
if (node.as === "multi") {
var pay2 = null;
@ -41,6 +46,13 @@ module.exports = function(RED) {
/* istanbul ignore else */
if (pay2) {
msg.payload = pay2;
msg.parts = {
id: msg._msgid,
index: index,
count: count,
type: "string",
ch: ""
};
node.send(msg);
}
}
@ -50,6 +62,7 @@ module.exports = function(RED) {
if (node.ret === "attr") { pay.push( this.attribs ); }
//if (node.ret === "val") { pay.push( $(this).val() ); }
}
index++;
});
if ((node.as === "single") && (pay.length !== 0)) {
msg.payload = pay;

View File

@ -4,11 +4,25 @@
<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 for="node-input-action"><span data-i18n="json.label.action"></span></label>
<select style="width:70%" id="node-input-action">
<option value="" data-i18n="json.label.actions.toggle"></option>
<option value="str" data-i18n="json.label.actions.str"></option>
<option value="obj" data-i18n="json.label.actions.obj"></option>
</select>
</div>
<div class="form-row">
<label data-i18n="json.label.property"></label>
<input type="text" id="node-input-property" style="width: 70%"/>
</div>
<hr align="middle"/>
<div class="form-row node-json-to-json-options">
<label style="width:100%; border-bottom: 1px solid #eee;"><span data-i18n="json.label.o2j"></span></label>
</div>
<div class="form-row" style="padding-left: 20px;">
<div class="form-row node-json-to-json-options" style="padding-left: 20px;">
<input style="width:20px; vertical-align:top; margin-right: 5px;" type="checkbox" id="node-input-pretty"><label style="width: auto;" for="node-input-pretty" data-i18n="json.label.pretty"></span>
</div>
</script>
@ -30,6 +44,18 @@
</ul>
</dd>
</dl>
<h3>Details</h3>
<p>By default, the node operates on <code>msg.payload</code>, but can be configured
to convert any message property.</p>
<p>The node can also be configured to ensure a particular encoding instead of toggling
between the two. This can be used, for example, with the <code>HTTP In</code>
node to ensure the payload is a parsed object even if an incoming request
did not set its content-type correctly for the HTTP In node to do the conversion.</p>
<p>If the node is configured to ensure the property is encoded as a String and it
receives a String, no further checks will be made of the property. It will
not check the String is valid JSON nor will it reformat it if the format option
is selected.</p>
</script>
<script type="text/javascript">
@ -38,6 +64,8 @@
color:"#DEBD5C",
defaults: {
name: {value:""},
property: { value:"payload" },
action: { value:"" },
pretty: {value:false}
},
inputs:1,
@ -48,6 +76,20 @@
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
if (this.property === undefined) {
$("#node-input-property").val("payload");
}
$("#node-input-property").typedInput({default:'msg',types:['msg']});
$("#node-input-action").change(function() {
if (this.value === "" || this.value === "str") {
$(".node-json-to-json-options").show();
} else {
$(".node-json-to-json-options").hide();
}
});
$("#node-input-action").change();
}
});
</script>

View File

@ -20,29 +20,40 @@ module.exports = function(RED) {
function JSONNode(n) {
RED.nodes.createNode(this,n);
this.indent = n.pretty ? 4 : 0;
this.action = n.action||"";
this.property = n.property||"payload";
var node = this;
this.on("input", function(msg) {
if (msg.hasOwnProperty("payload")) {
if (typeof msg.payload === "string") {
try {
msg.payload = JSON.parse(msg.payload);
node.send(msg);
}
catch(e) { node.error(e.message,msg); }
}
else if (typeof msg.payload === "object") {
if (!Buffer.isBuffer(msg.payload)) {
var value = RED.util.getMessageProperty(msg,node.property);
if (value !== undefined) {
if (typeof value === "string") {
if (node.action === "" || node.action === "obj") {
try {
msg.payload = JSON.stringify(msg.payload,null,node.indent);
RED.util.setMessageProperty(msg,node.property,JSON.parse(value));
node.send(msg);
}
catch(e) { node.error(RED._("json.errors.dropped-error")); }
catch(e) { node.error(e.message,msg); }
} else {
node.send(msg);
}
}
else if (typeof value === "object") {
if (node.action === "" || node.action === "str") {
if (!Buffer.isBuffer(value)) {
try {
RED.util.setMessageProperty(msg,node.property,JSON.stringify(value,null,node.indent));
node.send(msg);
}
catch(e) { node.error(RED._("json.errors.dropped-error")); }
}
else { node.warn(RED._("json.errors.dropped-object")); }
} else {
node.send(msg);
}
else { node.warn(RED._("json.errors.dropped-object")); }
}
else { node.warn(RED._("json.errors.dropped")); }
}
else { node.send(msg); } // If no payload - just pass it on.
else { node.send(msg); } // If no property - just pass it on.
});
}
RED.nodes.registerType("json",JSONNode);

View File

@ -49,9 +49,12 @@ module.exports = function(RED) {
else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
var dir = path.dirname(filename);
if (node.createDir) {
fs.ensureDir(dir, function(err) {
if (err) { node.error(RED._("file.errors.createfail",{error:err.toString()}),msg); }
});
try {
fs.ensureDirSync(dir);
} catch(err) {
node.error(RED._("file.errors.createfail",{error:err.toString()}),msg);
return;
}
}
var data = msg.payload;
@ -60,11 +63,11 @@ module.exports = function(RED) {
}
if (typeof data === "boolean") { data = data.toString(); }
if (typeof data === "number") { data = data.toString(); }
if ((this.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
node.data.push(Buffer.from(data));
while (node.data.length > 0) {
if (this.overwriteFile === "true") {
if (node.overwriteFile === "true") {
node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true });
node.wstream.on("error", function(err) {
node.error(RED._("file.errors.writefail",{error:err.toString()}),msg);

View File

@ -1,98 +1,112 @@
{
"name" : "node-red",
"version" : "0.17.5",
"description" : "A visual tool for wiring the Internet of Things",
"homepage" : "http://nodered.org",
"license" : "Apache-2.0",
"repository" : {
"type":"git",
"url":"https://github.com/node-red/node-red.git"
"name": "node-red",
"version": "0.18.0",
"description": "A visual tool for wiring the Internet of Things",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/node-red/node-red.git"
},
"main" : "red/red.js",
"scripts" : {
"main": "red/red.js",
"scripts": {
"start": "node red.js",
"test": "grunt",
"build": "grunt build"
},
"bin" : {
"bin": {
"node-red": "./red.js",
"node-red-pi": "bin/node-red-pi"
},
"contributors": [
{"name": "Nick O'Leary"},
{"name": "Dave Conway-Jones"}
{
"name": "Nick O'Leary"
},
{
"name": "Dave Conway-Jones"
}
],
"keywords": [
"editor", "messaging", "iot", "flow"
"editor",
"messaging",
"iot",
"flow"
],
"dependencies": {
"basic-auth": "1.1.0",
"basic-auth": "2.0.0",
"bcryptjs": "2.4.3",
"body-parser": "1.17.2",
"cheerio":"0.22.0",
"body-parser": "1.18.2",
"cheerio": "0.22.0",
"clone": "2.1.1",
"cookie": "0.3.1",
"cookie-parser": "1.4.3",
"cors":"2.8.3",
"cron":"1.2.1",
"express": "4.15.3",
"express-session": "1.15.2",
"follow-redirects":"1.2.4",
"fs-extra": "4.0.2",
"fs.notify":"0.0.4",
"hash-sum":"1.0.2",
"i18next":"1.10.6",
"is-utf8":"0.2.1",
"js-yaml": "3.8.4",
"json-stringify-safe":"5.0.1",
"jsonata":"1.2.6",
"cors": "2.8.4",
"cron": "1.3.0",
"express": "4.16.2",
"express-session": "1.15.6",
"follow-redirects": "1.3.0",
"fs-extra": "5.0.0",
"fs.notify": "0.0.4",
"hash-sum": "1.0.2",
"i18next": "1.10.6",
"is-utf8": "0.2.1",
"js-yaml": "3.10.0",
"json-stringify-safe": "5.0.1",
"jsonata": "1.4.1",
"media-typer": "0.3.0",
"mqtt": "2.9.0",
"memorystore": "1.6.0",
"mqtt": "2.15.1",
"multer": "1.3.0",
"mustache": "2.3.0",
"nopt": "3.0.6",
"oauth2orize":"1.8.0",
"on-headers":"1.0.1",
"passport":"0.3.2",
"passport-http-bearer":"1.0.1",
"passport-oauth2-client-password":"0.1.2",
"raw-body":"2.2.0",
"semver": "5.3.0",
"sentiment":"2.1.0",
"uglify-js":"3.0.20",
"nopt": "4.0.1",
"oauth2orize": "1.11.0",
"on-headers": "1.0.1",
"passport": "0.4.0",
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"raw-body": "2.3.2",
"semver": "5.4.1",
"sentiment": "2.1.0",
"uglify-js": "3.3.6",
"when": "3.7.8",
"ws": "1.1.1",
"xml2js":"0.4.17",
"node-red-node-feedparser":"0.1.*",
"node-red-node-email":"0.1.*",
"node-red-node-twitter":"0.1.*",
"node-red-node-rbe":"0.1.*"
"ws": "1.1.5",
"xml2js": "0.4.19",
"node-red-node-feedparser": "0.1.*",
"node-red-node-email": "0.1.*",
"node-red-node-twitter": "0.1.*",
"node-red-node-rbe": "0.1.*"
},
"optionalDependencies": {
"bcrypt":"~1.0.1"
"bcrypt": "~1.0.3"
},
"devDependencies": {
"chromedriver": "^2.33.2",
"grunt": "~1.0.1",
"grunt-chmod": "~1.1.1",
"grunt-cli": "~1.2.0",
"grunt-concurrent":"~2.3.1",
"grunt-contrib-clean":"~1.1.0",
"grunt-concurrent": "~2.3.1",
"grunt-contrib-clean": "~1.1.0",
"grunt-contrib-compress": "~1.4.0",
"grunt-contrib-concat":"~1.0.1",
"grunt-contrib-concat": "~1.0.1",
"grunt-contrib-copy": "~1.0.0",
"grunt-contrib-jshint": "~1.1.0",
"grunt-contrib-uglify": "~3.0.1",
"grunt-contrib-watch":"~1.0.0",
"grunt-jsonlint":"~1.1.0",
"grunt-contrib-uglify": "~3.3.0",
"grunt-contrib-watch": "~1.0.0",
"grunt-jsonlint": "~1.1.0",
"grunt-mocha-istanbul": "5.0.2",
"grunt-nodemon":"~0.4.2",
"grunt-sass":"~1.2.1",
"grunt-nodemon": "~0.4.2",
"grunt-sass": "~2.0.0",
"grunt-simple-mocha": "~0.4.1",
"grunt-webdriver": "^2.0.3",
"istanbul": "0.4.5",
"mocha": "~3.4.2",
"should": "^8.4.0",
"sinon": "1.17.7",
"supertest": "3.0.0"
"supertest": "3.0.0",
"wdio-chromedriver-service": "^0.1.1",
"wdio-mocha-framework": "^0.5.11",
"wdio-spec-reporter": "^0.1.3",
"webdriverio": "^4.9.11"
},
"engines": {
"node": ">=4"

19
red.js
View File

@ -167,7 +167,16 @@ if (settings.httpNodeRoot !== false) {
settings.httpNodeAuth = settings.httpNodeAuth || settings.httpAuth;
}
settings.uiPort = parsedArgs.port||settings.uiPort||1880;
// if we got a port from command line, use it (even if 0)
// replicate (settings.uiPort = parsedArgs.port||settings.uiPort||1880;) but allow zero
if (parsedArgs.port !== undefined){
settings.uiPort = parsedArgs.port;
} else {
if (settings.uiPort === undefined){
settings.uiPort = 1880;
}
}
settings.uiHost = settings.uiHost||"0.0.0.0";
if (flowFile) {
@ -261,9 +270,14 @@ if (settings.httpStatic) {
}
function getListenPath() {
var port = settings.serverPort;
if (port === undefined){
port = settings.uiPort;
}
var listenPath = 'http'+(settings.https?'s':'')+'://'+
(settings.uiHost == '0.0.0.0'?'127.0.0.1':settings.uiHost)+
':'+settings.uiPort;
':'+port;
if (settings.httpAdminRoot !== false) {
listenPath += settings.httpAdminRoot;
} else if (settings.httpStatic) {
@ -292,6 +306,7 @@ RED.start().then(function() {
if (settings.httpAdminRoot === false) {
RED.log.info(RED.log._("server.admin-ui-disabled"));
}
settings.serverPort = server.address().port;
process.title = parsedArgs.title || 'node-red';
RED.log.info(RED.log._("server.now-running", {listenpath:getListenPath()}));
});

View File

@ -229,6 +229,11 @@ module.exports = {
log.audit({event: "nodes.module.set",module:mod,enabled:body.enabled,error:err.code||"unexpected_error",message:err.toString()},req);
res.status(400).json({error:err.code||"unexpected_error", message:err.toString()});
}
},
getIcons: function(req,res) {
log.audit({event: "nodes.icons.get"},req);
res.json(redNodes.getNodeIcons());
}
};

View File

@ -87,9 +87,11 @@ function login(req,res) {
"prompts":[{id:"username",type:"text",label:"user.username"},{id:"password",type:"password",label:"user.password"}]
}
} else if (settings.adminAuth.type === "strategy") {
var urlPrefix = (settings.httpAdminRoot==='/')?"":settings.httpAdminRoot;
response = {
"type":"strategy",
"prompts":[{type:"button",label:settings.adminAuth.strategy.label, url:"/auth/strategy"}]
"prompts":[{type:"button",label:settings.adminAuth.strategy.label, url: urlPrefix + "auth/strategy"}]
}
if (settings.adminAuth.strategy.icon) {
response.prompts[0].icon = settings.adminAuth.strategy.icon;
@ -148,14 +150,19 @@ module.exports = {
login: login,
revoke: revoke,
genericStrategy: function(adminApp,strategy) {
var session = require('express-session');
var crypto = require("crypto");
var crypto = require("crypto")
var session = require('express-session')
var MemoryStore = require('memorystore')(session)
adminApp.use(session({
// As the session is only used across the life-span of an auth
// hand-shake, we can use a instance specific random string
secret: crypto.randomBytes(20).toString('hex'),
resave: false,
saveUninitialized:false
// As the session is only used across the life-span of an auth
// hand-shake, we can use a instance specific random string
secret: crypto.randomBytes(20).toString('hex'),
resave: false,
saveUninitialized: false,
store: new MemoryStore({
checkPeriod: 86400000 // prune expired entries every 24h
})
}));
//TODO: all passport references ought to be in ./auth
adminApp.use(passport.initialize());
@ -186,12 +193,12 @@ module.exports = {
adminApp.get('/auth/strategy', passport.authenticate(strategy.name));
adminApp.get('/auth/strategy/callback',
passport.authenticate(strategy.name, {session:false, failureRedirect: '/' }),
passport.authenticate(strategy.name, {session:false, failureRedirect: settings.httpAdminRoot }),
function(req, res) {
var tokens = req.user.tokens;
delete req.user.tokens;
// Successful authentication, redirect home.
res.redirect('/?access_token='+tokens.accessToken);
res.redirect(settings.httpAdminRoot + '?access_token='+tokens.accessToken);
}
);

View File

@ -64,7 +64,7 @@ function start() {
// https://github.com/websockets/ws/pull/632
// that is fixed in the 1.x release of the ws module
// that we cannot currently pickup as it drops node 0.10 support
perMessageDeflate: false
//perMessageDeflate: false
});
wsServer.on('connection',function(ws) {
@ -190,15 +190,27 @@ function publish(topic,data,retain) {
}
}
var stack = [];
var ok2tx = true;
function publishTo(ws,topic,data) {
var msg = JSON.stringify({topic:topic,data:data});
try {
ws.send(msg);
} catch(err) {
removeActiveConnection(ws);
removePendingConnection(ws);
log.warn(log._("comms.error-send",{message:err.toString()}));
if (topic && data) { stack.push({topic:topic,data:data}); }
if (ok2tx && (stack.length > 0)) {
ok2tx = false;
try {
ws.send(JSON.stringify(stack));
} catch(err) {
removeActiveConnection(ws);
removePendingConnection(ws);
log.warn(log._("comms.error-send",{message:err.toString()}));
}
stack = [];
var txtout = setTimeout(function() {
ok2tx = true;
publishTo(ws);
}, 50); // TODO: OK so a 50mS update rate should prob not be hard-coded
}
}
function handleRemoteSubscription(ws,topic) {

View File

@ -22,6 +22,7 @@ var library = require("./library");
var info = require("./settings");
var auth = require("../auth");
var nodes = require("../admin/nodes"); // TODO: move /icons into here
var needsPermission = auth.needsPermission;
var runtime;
var log;
@ -59,6 +60,8 @@ module.exports = {
});
}
editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor);
editorApp.get("/icons",needsPermission("nodes.read"),nodes.getIcons,errorHandler);
editorApp.get("/icons/:module/:icon",ui.icon);
editorApp.get("/icons/:scope/:module/:icon",ui.icon);
@ -75,7 +78,7 @@ module.exports = {
// Locales
var locales = require("./locales");
locales.init(runtime);
editorApp.get('/locales/nodes',locales.getAllNodes,apiUtil.errorHandler);
editorApp.get('/locales/nodes',locales.getAllNodes,apiUtil..errorHandler);
editorApp.get(/locales\/(.+)\/?$/,locales.get,apiUtil.errorHandler);
// Library

View File

@ -218,9 +218,10 @@
"editConfig": "Edit __type__ config node",
"addNewType": "Add new __type__...",
"nodeProperties": "node properties",
"portLabels": "port labels",
"portLabels": "node settings",
"labelInputs": "Inputs",
"labelOutputs": "Outputs",
"settingIcon": "Icon",
"noDefaultLabel": "none",
"defaultLabel": "use default label",
"errors": {
@ -450,8 +451,10 @@
},
"expressionEditor": {
"functions": "Functions",
"functionReference": "Function reference",
"insert": "Insert",
"title": "JSONata Expression editor",
"test": "Test",
"data": "Example message",
"result": "Result",
"format": "format expression",

View File

@ -95,6 +95,10 @@
"args":"",
"desc":"Returns a pseudo random number greater than or equal to zero and less than one."
},
"$millis": {
"args":"",
"desc":"Returns the number of milliseconds since the Unix Epoch (1 January, 1970 UTC) as a number. All invocations of `$millis()` within an evaluation of an expression will all return the same value."
},
"$sum": {
"args": "array",
"desc": "Returns the arithmetic sum of an `array` of numbers. It is an error if the input `array` contains an item which isn't a number."
@ -160,6 +164,10 @@
"args": "object",
"desc": "Splits an object containing key/value pairs into an array of objects, each of which has a single key/value pair from the input object. If the parameter is an array of objects, then the resultant array contains an object for every key/value pair in every object in the supplied array."
},
"$merge": {
"args": "array&lt;object&gt;",
"desc": "Merges an array of `objects` into a single `object` containing all the key/value pairs from each of the objects in the input array. If any of the input objects contain the same key, then the returned `object` will contain the value of the last one in the array. It is an error if the input array contains an item that is not an object."
},
"$sift": {
"args":"object, function",
"desc":"Returns an object that contains only the key/value pairs from the `object` parameter that satisfy the predicate `function` passed in as the second parameter.\n\nThe `function` that is supplied as the second parameter must have the following signature:\n\n`function(value [, key [, object]])`"
@ -187,5 +195,26 @@
"$globalContext": {
"args": "string",
"desc": "Retrieves a global context property."
},
"$pad": {
"args": "string, width [, char]",
"desc": "Returns a copy of the `string` with extra padding, if necessary, so that its total number of characters is at least the absolute value of the `width` parameter.\n\nIf `width` is a positive number, then the string is padded to the right; if negative, it is padded to the left.\n\nThe optional `char` argument specifies the padding character(s) to use. If not specified, it defaults to the space character."
},
"$fromMillis": {
"args": "number",
"desc": "Convert a number representing milliseconds since the Unix Epoch (1 January, 1970 UTC) to a timestamp string in the ISO 8601 format."
},
"$formatNumber": {
"args": "number, picture [, options]",
"desc": "Casts the number to a string and formats it to a decimal representation as specified by the picture string.\n\n The behaviour of this function is consistent with the XPath/XQuery function fn:format-number as defined in the XPath F&O 3.1 specification. The picture string parameter defines how the number is formatted and has the same syntax as fn:format-number.\n\nThe optional third argument options is used to override the default locale specific formatting characters such as the decimal separator. If supplied, this argument must be an object containing name/value pairs specified in the decimal format section of the XPath F&O 3.1 specification."
},
"$formatBase": {
"args": "number [, radix]",
"desc": "Casts the number to a string and formats it to an integer represented in the number base specified by the radix argument. If radix is not specified, then it defaults to base 10. radix can be between 2 and 36, otherwise an error is thrown."
},
"$toMillis": {
"args": "timestamp",
"desc": "Convert a timestamp string in the ISO 8601 format to the number of milliseconds since the Unix Epoch (1 January, 1970 UTC) as a number. An error is thrown if the string is not in the correct format."
}
}

View File

@ -208,9 +208,10 @@
"editConfig": "__type__ ノードの設定を編集",
"addNewType": "新規に __type__ を追加...",
"nodeProperties": "プロパティ",
"portLabels": "端子名",
"portLabels": "設定",
"labelInputs": "入力",
"labelOutputs": "出力",
"settingIcon": "アイコン",
"noDefaultLabel": "なし",
"defaultLabel": "既定の名前を使用",
"errors": {
@ -422,11 +423,13 @@
},
"expressionEditor": {
"functions": "関数",
"functionReference": "関数リファレンス",
"insert": "挿入",
"title": "JSONata式エディタ",
"test": "テスト",
"data": "メッセージ例",
"result": "結果",
"format": "",
"format": "形",
"compatMode": "互換モードが有効になっています",
"compatModeDesc": "<h3>JSONata互換モード</h3><p> 入力された式では <code>msg</code> を参照しているため、互換モードで評価します。このモードは将来廃止予定のため、式で <code>msg</code> を使わないよう修正してください。</p><p> JSONataをNode-REDで最初にサポートした際には、 <code>msg</code> オブジェクトの参照が必要でした。例えば <code>msg.payload</code> がペイロードを参照するために使われていました。</p><p> 直接メッセージに対して式を評価するようになったため、この形式は使えなくなります。ペイロードを参照するには、単に <code>payload</code> にしてください。</p>",
"noMatch": "一致した結果なし",

View File

@ -95,6 +95,10 @@
"args": "",
"desc": "0以上、1未満の疑似乱数を返します。"
},
"$millis": {
"args": "",
"desc": "Unixエポック(1 January, 1970 UTC)からの経過ミリ秒を数値として返します。評価対象式に含まれる `$millis()` の呼び出しは、全て同じ値を返します。"
},
"$sum": {
"args": "array",
"desc": "数値の配列 `array` の合計値を返します。 `array` が数値でない要素を含む場合、エラーになります。"
@ -159,6 +163,10 @@
"args": "object",
"desc": "key/valueのペアを持つオブジェクトを、各要素が1つのkey/valueのペアを持つオブジェクトの配列に分割します。引数がオブジェクトの配列の場合、結果の配列は各オブジェクトから得た各key/valueのペアのオブジェクトを持ちます。"
},
"$merge": {
"args": "array&lt;object&gt;",
"desc": "`object` の配列を1つの `object` へマージします。 マージ結果のオブジェクトは入力配列内の各オブジェクトのkey/valueペアを含みます。入力のオブジェクトが同じキーを持つ場合、戻り値の `object` には配列の最後のオブジェクトのkey/value値が格納されます。入力の配列がオブジェクトでない要素を含む場合、エラーとなります。"
},
"$sift": {
"args": "object, function",
"desc": "引数 `object` が持つkey/valueのペアのうち、関数 `function` によってふるい分けたオブジェクトのみを返します。\n\n関数 `function` は、以下の引数を持つ必要があります。\n\n`function(value [, key [, object]])`"

View File

@ -2,7 +2,7 @@
"common": {
"label": {
"name": "姓名",
"ok": "Ok",
"ok": "确认",
"done": "完成",
"cancel": "取消",
"delete": "删除",
@ -28,8 +28,8 @@
"menu": {
"label": {
"view": {
"view": "示",
"showGrid": "示网格",
"view": "示",
"showGrid": "示网格",
"snapGrid": "对齐网格",
"gridSize": "网格尺寸",
"textDir": "文本方向",
@ -42,7 +42,7 @@
"show": "显示侧边栏"
},
"userSettings": "设定",
"displayStatus": "示节点状态",
"displayStatus": "示节点状态",
"displayConfig": "配置节点设定",
"import": "导入",
"export": "导出",
@ -256,7 +256,7 @@
"social": "社交",
"storage": "存储",
"analysis": "分析",
"advanced": "高级"
"advanced": "高级"
},
"event": {
"nodeAdded": "添加到面板中的节点:",
@ -381,7 +381,7 @@
"str": "文字列",
"num": "数字",
"re": "正则表达式",
"bool": "真伪判断",
"bool": "布尔",
"json": "JSON",
"date": "时间戳"
}

View File

@ -61,7 +61,7 @@ function init(_server,_runtime) {
}
adminApp.post("/auth/revoke",auth.needsPermission(""),auth.revoke,apiUtil.errorHandler);
}
// Editor
if (!settings.disableEditor) {
editor = require("./editor");

View File

@ -0,0 +1,23 @@
{
"info": {
"tip0" : "您可以用 {{core:delete-selection}} 删除选择的节点或链接。",
"tip1" : "{{core:search}} 可以在流程内搜索节点。",
"tip2": "{{core:toggle-sidebar}} 可以显示或隐藏边栏。",
"tip3": "您可以在 {{core:manage-palette}} 中管理节点的控制面板。",
"tip4": "边栏中会列出流程中所有的配置节点。您可以通过菜单或者 {{core:show-config-tab}} 来访问这些节点。",
"tip5": "您可以在设定中选择显示或隐藏这些提示。",
"tip6": "您可以用[left] [up] [down] [right]键来移动被选中的节点。按住[shift]可以更快地移动节点。",
"tip7": "把节点拖到连接上可以向连接中插入节点。",
"tip8": "您可以用 {{core:show-export-dialog}} 来导出被选中的节点或标签页中的流程。",
"tip9": "您可以将流程的json文件拖入编辑框或 {{core:show-import-dialog}} 来导入流程。",
"tip10": "按住[shift]后单击并拖动节点可以将该节点的多个连接一并移动到其他节点的端口。",
"tip11": "{{core:show-info-tab}} 可以显示「信息」标签页。 {{core:show-debug-tab}} 可以显示「调试」标签页。",
"tip12": "按住[ctrl]的同时点击工作界面可以在节点的对话栏中快速添加节点。",
"tip13": "按住[ctrl]的同时点击节点的端口或后续节点可以快速连接多个节点。",
"tip14": "按住[shift]的同时点击节点会选中所有被连接的节点。",
"tip15": "按住[ctrl]的同时点击节点可以在选中或取消选中节点。",
"tip16": "{{core:show-previous-tab}} 和 {{core:show-next-tab}} 可以切换标签页。",
"tip17": "您可以在节点的属性配置画面中通过 {{core:confirm-edit-tray}} 来更改设置,或者用 {{core:cancel-edit-tray}} 来取消更改。",
"tip18": "您可以通过点击 {{core:edit-selected-node}} 来显示被选中节点的属性设置画面。"
}
}

View File

@ -0,0 +1,198 @@
{
"$string": {
"args": "arg",
"desc": "通过以下的类型转换规则将参数*arg*转换成字符串:\n\n - 字符串不转换。\n -函数转换成空的字符串。\n - JSON的值无法用数字表示所以用无限大或者NaN非数表示。\n - 用JSON.stringify函数将其他值转换成JSON字符串。"
},
"$length": {
"args": "str",
"desc": "输出字符串str的字数。如果str不是字符串抛出错误。"
},
"$substring": {
"args": "str, start[, length]",
"desc": "输出`start`位置后的的首次出现的包括`str`的子字符串。 如果`length`被指定,那么的字符串中将只包括前`length`个文字。如果`start`是负数则输出从`str`末尾开始的`length`个文字"
},
"$substringBefore": {
"args": "str, chars",
"desc": "输出str中首次出现的chars之前的子字符串如果str中不包括chars则输出str。"
},
"$substringAfter": {
"args": "str, chars",
"desc": "输出str中首次出现的chars之后的子字符串如果str中不包括chars则输出str。"
},
"$uppercase": {
"args": "str",
"desc": "`将str中的所有字母变为大写后输出。"
},
"$lowercase": {
"args": "str",
"desc": "将str中的所有字母变为小写后输出。"
},
"$trim": {
"args": "str",
"desc": "将以下步骤应用于`str`来去除所有空白文字并实现标准化。\n\n 将全部tab制表符、回车键、换行字符用空白代替。\n- 将连续的空白文字变成一个空白文字。\n- 消除开头和末尾的空白文字。\n\n如果`str`没有被指定(即在无输入参数的情况下调用本函数),将上下文的值作为`str`来使用。 如果`str` 不是字符串则抛出错误。"
},
"$contains": {
"args": "str, pattern",
"desc": "字符串`str` 和 `pattern`匹配的话输出`true`,不匹配的情况下输出 `false`。 不指定`str`的情况下(比如用一个参数调用本函数时)、将上下文的值作为`str`来使用。参数 `pattern`可以为字符串或正则表达。"
},
"$split": {
"args": "str[, separator][, limit]",
"desc": "将参数`str`分解成由子字符串组成的数组。 如果`str`不是字符串抛出错误。可以省略的参数 `separator`中指定字符串`str`的分隔符。分隔符可以是文字或正则表达式。在不指定`separator`的情况下、将分隔符看作空的字符串并把`str`拆分成由单个字母组成的数组。如果`separator`不是字符串则抛出错误。在可省略的参数`limit`中指定分割后的子字符串的最大个数。超出个数的子字符串将被舍弃。如果`limit`没有被指定,`str` 将不考虑子字符串的个数而将字符串完全分隔。如果`limit`是负数则抛出错误。"
},
"$join": {
"args": "array[, separator]",
"desc": "用可以省略的参数 `separator`来把多个字符串连接。如果`array`不是字符串则抛出错误。 如果没有指定`separator`,则用空字符串来连接字符(即字符串之间没有`separator`)。 如果`separator`不是字符则抛出错误。"
},
"$match": {
"args": "str, pattern [, limit]",
"desc": "对字符串`str`使用正则表达式`pattern`并输出与`str`相匹配的部分信息。"
},
"$replace": {
"args": "str, pattern, replacement [, limit]",
"desc": "在字符串`str`中搜索`pattern`并用`replacement`来替换。\n\n可选参数`limit`用来指定替换次数的上限。"
},
"$now": {
"args": "",
"desc": "生成ISO 8601互換格式的时刻并作为字符串输出。"
},
"$base64encode": {
"args": "string",
"desc": "将ASCII格式的字符串转换为Base 64格式。将字符串中的文字视作二进制形式的数据处理。包含URI编码在内的字符串文字必须在0x00到0xFF的范围内否则不会被支持。"
},
"$base64decode": {
"args": "string",
"desc": "用UTF-8代码页将Base 64形式二进制值转换为字符串。"
},
"$number": {
"args": "arg",
"desc": "用下述的规则将参数 `arg`转换为数值。:\n\n 数值不做转换。\n 将字符串中合法的JSON数値表示转换成数値。\n 其他形式的值则抛出错误。"
},
"$abs": {
"args": "number",
"desc": "输出参数`number`的绝对值。"
},
"$floor": {
"args": "number",
"desc": "输出比`number`的值小的最大整数。"
},
"$ceil": {
"args": "number",
"desc": "输出比`number`的值大的最小整数。"
},
"$round": {
"args": "number [, precision]",
"desc": "输出四舍五入后的参数`number`。可省略的参数 `precision`指定四舍五入后小数点下的位数。"
},
"$power": {
"args": "base, exponent",
"desc": "输出底数`base`的`exponent`次幂。"
},
"$sqrt": {
"args": "number",
"desc": "输出参数 `number`的平方根。"
},
"$random": {
"args": "",
"desc": "输出比0大比1小的伪随机数。"
},
"$millis": {
"args": "",
"desc": "返回从UNIX时间 (1970年1月1日 UTC/GMT的午夜开始到现在的毫秒数。在同一个表达式的测试中所有对`$millis()`的调用将会返回相同的值。"
},
"$sum": {
"args": "array",
"desc": "输出数组`array`的总和。如果`array`不是数值则抛出错误。"
},
"$max": {
"args": "array",
"desc": "输出数组`array`的最大值。如果`array`不是数值则抛出错误。"
},
"$min": {
"args": "array",
"desc": "输出数组`array`的最小值。如果`array`不是数值则抛出错误。。"
},
"$average": {
"args": "array",
"desc": "输出数组`array`的平均数。如果`array`不是数值则抛出错误。。"
},
"$boolean": {
"args": "arg",
"desc": "用下述规则将数据转换成布尔值。:\n\n - 不转换布尔值`Boolean`。\n 将空的字符串`string`转换为`false`\n 将不为空的字符串`string`转换为`true`\n 将为0的数字`number`转换成`false`\n 将不为0的数字`number`转换成`true`\n –将`null`转换成`false`\n –将空的数组`array`转换成`false`\n –如果数组`array`中含有可以转换成`true`的要素则转换成`true`\n –如果`array`中没有可转换成`true`的要素则转换成`false`\n 空的对象`object`转换成`false`\n 非空的对象`object`转换成`true`\n –将函数`function`转换成`false`"
},
"$not": {
"args": "arg",
"desc": "输出做取反运算后的布尔值。首先将`arg`转换为布尔值。"
},
"$exists": {
"args": "arg",
"desc": "如果算式`arg`的值存在则输出`true`。如果算式的值不存在(比如指向不存在区域的引用)则输出`false`。"
},
"$count": {
"args": "array",
"desc": "输出数组中的元素数。"
},
"$append": {
"args": "array, array",
"desc": "将两个数组连接。"
},
"$sort": {
"args": "array [, function]",
"desc": "输出排序后的数组`array`。\n\n如果使用了比较函数`function`,则下述两个参数需要被指定。\n\n`function(left, right)`\n\n该比较函数是为了比较left和right两个值而被排序算法调用的。如果用户希望left的值被置于right的值之后那么该函数必须输出布尔值`true`来表示位置交换。而在不需要位置交换时函数必须输出`false`。"
},
"$reverse": {
"args": "array",
"desc": "输出倒序后的数组`array`。"
},
"$shuffle": {
"args": "array",
"desc": "输出随机排序后的数组 `array`。"
},
"$zip": {
"args": "array, ...",
"desc": "将数组中的值按索引顺序打包后输出。"
},
"$keys": {
"args": "object",
"desc": "输出由对象内的键组成的数组。如果参数是对象的数组则输出由所有对象中的键去重后组成的队列。"
},
"$lookup": {
"args": "object, key",
"desc": "输出对象中与参数`key`对应的值。如果第一个参数`object`是数组,那么数组中所有的对象都将被搜索并输出这些对象中与参数`key`对应的值。"
},
"$spread": {
"args": "object",
"desc": "将对象中的键值对分隔成每个要素中只含有一个键值对的数组。如果参数`object`是数组,那么返回值的数组中包含所有对象中的键值对。"
},
"$merge": {
"args": "array&lt;object&gt;",
"desc": "将输入数组`objects`中所有的键值对合并到一个`object`中并返回。如果输入数组的要素中含有重复的键,则返回的`object`中将只包含数组中最后出现要素的值。如果输入数组中包括对象以外的元素,则抛出错误。"
},
"$sift": {
"args": "object, function",
"desc": "输出参数`object`中符合`function`的键值对。\n\n`function`必须含有下述参数。\n\n`function(value [, key [, object]])`"
},
"$each": {
"args": "object, function",
"desc": "将函数`function`应用于`object`中的所有键值对并输出由所有返回值组成的数组。"
},
"$map": {
"args": "array, function",
"desc": "将函数`function`应用于数组`array`中所有的值并输出由返回值组成的数组。\n\n`function`中必须含有下述参数。\n\n`function(value [, index [, array]])`"
},
"$filter": {
"args": "array, function",
"desc": "输出数组`array`中符合函数`function`条件的值组成的数组。\n\n`function`必须包括下述参数。\n\n`function(value [, index [, array]])`"
},
"$reduce": {
"args": "array, function [, init]",
"desc": "将`function`依次应用于数组中的各要素值。 其中,前一个要素值的计算结果将参与到下一次的函数运算中。。\n\n函数`function`接受两个参数并作为中缀表示法中的操作符。\n\n可省略的参数`init`将作为运算的初始值。"
},
"$flowContext": {
"args": "string",
"desc": "获取流上下文(流等级的上下文,可以让所有节点共享)的属性。"
},
"$globalContext": {
"args": "string",
"desc": "获取全局上下文的属性。"
}
}

View File

@ -97,6 +97,7 @@ module.exports = {
settings:runtime.settings,
util: runtime.util,
version: runtime.version,
events: runtime.events,
comms: api.comms,
library: api.library,

View File

@ -117,7 +117,11 @@ function start() {
if (nodeErrors.length > 0) {
log.warn("------------------------------------------------------");
for (i=0;i<nodeErrors.length;i+=1) {
log.warn("["+nodeErrors[i].name+"] "+nodeErrors[i].err);
if (nodeErrors[i].err.code === "type_already_registered") {
log.warn("["+nodeErrors[i].id+"] "+log._("server.type-already-registered",{type:nodeErrors[i].err.details.type,module: nodeErrors[i].err.details.moduleA}));
} else {
log.warn("["+nodeErrors[i].id+"] "+nodeErrors[i].err);
}
}
log.warn("------------------------------------------------------");
}

View File

@ -20,6 +20,7 @@
"errors-help": "Run with -v for details",
"missing-modules": "Missing node modules:",
"node-version-mismatch": "Node module cannot be loaded on this version. Requires: __version__ ",
"type-already-registered": "'__type__' already registered by module __module__",
"removing-modules": "Removing modules from config",
"added-types": "Added node types:",
"removed-types": "Removed node types:",
@ -138,6 +139,7 @@
"invalid": "Existing __type__ file is not valid json",
"restore": "Restoring __type__ file backup : __path__",
"restore-fail": "Restoring __type__ file backup failed : __message__",
"fsync-fail": "Flushing file __path__ to disk failed : __message__",
"projects": {
"changing-project": "Setting active project : __project__",
"active-project": "Active project : __project__",

View File

@ -74,11 +74,11 @@ var consoleLogger = function(msg) {
if (msg.level == log.METRIC || msg.level == log.AUDIT) {
util.log("["+levelNames[msg.level]+"] "+JSON.stringify(msg));
} else {
if (verbose && msg.msg.stack) {
if (verbose && msg.msg && msg.msg.stack) {
util.log("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+msg.msg.stack);
} else {
var message = msg.msg;
if (typeof message === 'object' && message.toString() === '[object Object]' && message.message) {
if (typeof message === 'object' && message !== null && message.toString() === '[object Object]' && message.message) {
message = message.message;
}
util.log("["+levelNames[msg.level]+"] "+(msg.type?"["+msg.type+":"+(msg.name||msg.id)+"] ":"")+message);

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