Compare commits

...

50 Commits

Author SHA1 Message Date
Dave Conway-Jones
c256e27a83 simplistic detect can't write settings and set readOnly flag 2018-04-06 17:00:34 +01:00
Dave Conway-Jones
3f41036919 add node-red to project with minor server tolerance. 2018-04-06 16:46:54 +01:00
Dave Conway-Jones
95753ce5cd add "add node-red core" button to dependencies panel 2018-04-06 15:32:53 +01:00
Dave Conway-Jones
1906818c87 much simpler to always add start
whether node-red is s deep or not…  as if not it would need to be there
anyway….
2018-04-06 09:06:35 +01:00
Dave Conway-Jones
02765f8aad run by calling node-red script
handles npm start better after both clone and npm pack
2018-04-05 23:24:22 +01:00
Dave Conway-Jones
89f1dedf20 Add .config.json to .gitignore 2018-04-05 23:21:28 +01:00
Dave Conway-Jones
7a8906535e add --credentialSecret -k to command-line options 2018-04-05 14:48:47 +01:00
Dave Conway-Jones
499d22daca Add start script to default project package 2018-04-05 11:30:16 +01:00
Dave Conway-Jones
838c7a5e89 make debug slightly larger to pass WCAG AA rating 2018-04-05 11:25:08 +01:00
Dave Conway-Jones
89bfc90f40 Make core nodes labels more consistent, to close #1673
and make them translateable
2018-03-30 14:31:59 +01:00
Dave Conway-Jones
acad9f57f9 Add "not available" to common messages 2018-03-30 14:03:04 +01:00
Fabien Marchewka
0d08dc410e Prevent Following Redirect (#615) (#1684) 2018-03-29 08:28:44 +01:00
Nick O'Leary
ebb3fb96cd Merge pull request #1670 from node-red-hitachi/subflow-icon-change
Enable user defined icon for subflow
2018-03-27 10:22:20 +01:00
Nick O'Leary
f31f23ff07 Allow template node to be updated more than once
Fixes #1671
2018-03-27 10:14:39 +01:00
Kazuki-Nakanishi
8b0e76dd55 Hide the subflow check logic inside getDefaultNodeIcon function 2018-03-22 14:14:09 +09:00
Dave Conway-Jones
884618adfe remove down carat from typed input with only 1 type 2018-03-20 21:01:10 +00:00
Nick O'Leary
98f7271ac8 Merge pull request #1657 from node-red-hitachi/move-i18n-info-text
move i18n info text of core nodes under nodes/core/locales directory
2018-03-20 20:44:54 +00:00
Dave Conway-Jones
087cd121b8 add debug and trace to function node (#1654) 2018-03-20 20:40:36 +00:00
Kazuki Nakanishi
2d52527fb4 Don't mark a subflow changed when actually modified nothing (#1665) 2018-03-20 20:39:46 +00:00
Kazuki Nakanishi
fe289e62b5 Fix the problem that output labels of switch node sometimes disappear (#1664) 2018-03-20 20:37:29 +00:00
Nick O'Leary
2845475e3f Keep backup of .config.json 2018-03-20 00:04:52 +00:00
Nick O'Leary
b307492487 Add warning if using _credentialSecret 2018-03-20 00:04:52 +00:00
Nick O'Leary
d48284f7ea Remove unused references to settings 2018-03-20 00:04:52 +00:00
Dave Conway-Jones
7e416797e9 make trigger test a bit more robust 2018-03-19 17:33:18 +00:00
Kroderia
5d54ca7477 Chinese translations for core nodes (#1607)
* Fix typo

* Fix and Update some Chinese translations.

* Fix and Add Chinese translations to match all en-US's items
2018-03-17 17:49:17 +00:00
Qi Xiu
b979b4e61a Master chinese3 (#1666)
* Translated jsonata.json to Chinese

* Translated file jsonata.json to Chinese
2018-03-17 17:48:01 +00:00
Kazuki Nakanishi
2527f7984a Translate rpi-gpio node (#1669)
Thanks
2018-03-17 17:46:44 +00:00
Kazuki-Nakanishi
d9350b2362 Enable user defined icon for subflow 2018-03-14 13:51:50 +09:00
Kazuhito Yokoi
bd0b903f1a Fix typo in info messages of file node 2018-03-14 01:19:37 +00:00
Kazuhito Yokoi
f243c0df19 Fix typo in info messages of json node 2018-03-14 01:18:00 +00:00
Kazuhito Yokoi
7482978953 Fix typo in info messages of html node 2018-03-14 01:16:59 +00:00
Kazuhito Yokoi
77966689d4 Fix typo in info messages of csv node 2018-03-14 01:15:36 +00:00
Kazuhito Yokoi
cf43939d65 Fix typo in info messages of split node 2018-03-14 01:11:11 +00:00
Kazuhito Yokoi
391ac4b351 Fix typo in info messages of change node 2018-03-14 01:09:29 +00:00
Kazuhito Yokoi
e1e48aadd9 Fix typo in info messages of switch node 2018-03-14 01:08:10 +00:00
Kazuhito Yokoi
0681f206c4 Fix typo in info messages of udp node 2018-03-14 01:05:38 +00:00
Kazuhito Yokoi
d257c6f3d3 Fix typo in info messages of tcpin node 2018-03-14 01:04:21 +00:00
Kazuhito Yokoi
fa45c82cdc Fix typo in info messages of watch node 2018-03-14 01:01:58 +00:00
Kazuhito Yokoi
e805b58da6 Fix typo in info messages of websocket node 2018-03-14 01:00:13 +00:00
Kazuhito Yokoi
943976d207 Fix typo in info messages of trigger node 2018-03-14 00:49:44 +00:00
Kazuhito Yokoi
3a2e5a6ccd Fix typo in info messages of exec node 2018-03-14 00:47:50 +00:00
Kazuhito Yokoi
35ef036246 Fix typo in info messages of debug node 2018-03-14 00:46:07 +00:00
Kazuhito Yokoi
e09c3bbdd3 Fix typo in info messages of inject node 2018-03-14 00:41:07 +00:00
Nick O'Leary
3b12076d4b Ignore subflow debug nodes when building filter
Fixes #1660

As the editor doesn't know the ids of subflow instance debug nodes
there's no easy way to build a list of them as part of the filter
options. So for now, disable the filter option if we don't know
about the debug node.
2018-03-03 22:41:02 +00:00
Hiroyasu Nishiyama
cfcf78ae28 fix failure of node installation (#1658) 2018-03-03 07:35:17 +00:00
Hiroyasu Nishiyama
341ff9bf5c move i18n info text of core nodes under nodes/core/locales directory 2018-03-03 10:39:11 +09:00
Dave Conway-Jones
4ebb5d099e add trigger reset test for null on 2nd output 2018-03-01 14:00:14 +00:00
Dave Conway-Jones
1e82b66bf0 remove octalbonescript example line from settings as no longer supported 2018-03-01 11:41:53 +00:00
Nick O'Leary
06a5e4273b Move all event emitting into runtime side, not api side 2018-02-28 11:24:12 +00:00
Dave Conway-Jones
e123e7b0b0 Fix pi gpio output of boolean to actually send 1/0
rather than true/false
2018-02-27 23:26:32 +00:00
87 changed files with 814 additions and 407 deletions

View File

@@ -229,10 +229,12 @@ RED.history = (function() {
}
});
}
RED.editor.validateNode(ev.node);
RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) {
n.inputs = ev.node.in.length;
n.outputs = ev.node.out.length;
RED.editor.updateNodeProperties(n);
RED.editor.validateNode(n);
});
} else {
var outputMap;

View File

@@ -355,7 +355,7 @@ RED.nodes = (function() {
RED.nodes.registerType("subflow:"+sf.id, {
defaults:{name:{value:""}},
info: sf.info,
icon:"subflow.png",
icon: function() { return sf.icon||"subflow.png" },
category: "subflows",
inputs: sf.in.length,
outputs: sf.out.length,
@@ -550,7 +550,11 @@ RED.nodes = (function() {
if (node.out.length > 0 && n.outputLabels && !/^\s*$/.test(n.outputLabels.join(""))) {
node.outputLabels = n.outputLabels.slice();
}
if (n.icon) {
if (n.icon !== "node-red/subflow.png") {
node.icon = n.icon;
}
}
return node;
}

View File

@@ -116,7 +116,9 @@
this.options.types = this.options.types||Object.keys(allOptions);
this.selectTrigger = $('<button tabindex="0"></button>').prependTo(this.uiSelect);
$('<i class="fa fa-sort-desc"></i>').appendTo(this.selectTrigger);
if (this.options.types.length > 1) {
$('<i class="fa fa-sort-desc"></i>').appendTo(this.selectTrigger);
}
this.selectLabel = $('<span></span>').appendTo(this.selectTrigger);
this.types(this.options.types);

View File

@@ -408,9 +408,12 @@ RED.deploy = (function() {
delete confNode.credentials;
}
});
RED.nodes.eachSubflow(function(subflow) {
subflow.changed = false;
});
RED.nodes.eachWorkspace(function(ws) {
ws.changed = false;
})
});
// Once deployed, cannot undo back to a clean state
RED.history.markAllDirty();
RED.view.redraw();

View File

@@ -48,7 +48,7 @@ RED.editor = (function() {
isValid = validateNode(subflow);
hasChanged = subflow.changed;
}
node.valid = isValid;
node.valid = isValid && validateNodeProperties(node, node._def.defaults, node);
node.changed = node.changed || hasChanged;
} else if (node._def) {
node.valid = validateNodeProperties(node, node._def.defaults, node);
@@ -170,6 +170,10 @@ RED.editor = (function() {
}
}
}
validateIcon(node);
}
function validateIcon(node) {
if (node._def.hasOwnProperty("defaults") && !node._def.defaults.hasOwnProperty("icon") && node.icon) {
var iconPath = RED.utils.separateIconPath(node.icon);
var iconSets = RED.nodes.getIconSets();
@@ -188,6 +192,7 @@ RED.editor = (function() {
}
}
}
function validateNodeEditorProperty(node,defaults,property,prefix) {
var input = $("#"+prefix+"-"+property);
if (input.length > 0) {
@@ -742,7 +747,7 @@ RED.editor = (function() {
buildLabelRow().appendTo(outputsDiv);
}
if ((!node._def.defaults || !node._def.defaults.hasOwnProperty("icon")) && node.type !== "subflow") {
if ((!node._def.defaults || !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);
@@ -816,9 +821,51 @@ RED.editor = (function() {
});
}
selectIconFile.prop("disabled", !iconFileList);
selectIconFile.removeClass("input-error");
selectIconModule.removeClass("input-error");
}
function updateLabels(editing_node, changes, outputMap) {
var inputLabels = $("#node-label-form-inputs").children().find("input");
var outputLabels = $("#node-label-form-outputs").children().find("input");
var hasNonBlankLabel = false;
var changed = false;
var newValue = inputLabels.map(function() {
var v = $(this).val();
hasNonBlankLabel = hasNonBlankLabel || v!== "";
return v;
}).toArray().slice(0,editing_node.inputs);
if ((editing_node.inputLabels === undefined && hasNonBlankLabel) ||
(editing_node.inputLabels !== undefined && JSON.stringify(newValue) !== JSON.stringify(editing_node.inputLabels))) {
changes.inputLabels = editing_node.inputLabels;
editing_node.inputLabels = newValue;
changed = true;
}
hasNonBlankLabel = false;
newValue = new Array(editing_node.outputs);
outputLabels.each(function() {
var index = $(this).attr('id').substring(23); // node-label-form-output-<index>
if (outputMap && outputMap.hasOwnProperty(index)) {
index = parseInt(outputMap[index]);
if (index === -1) {
return;
}
}
var v = $(this).val();
hasNonBlankLabel = hasNonBlankLabel || v!== "";
newValue[index] = v;
});
if ((editing_node.outputLabels === undefined && hasNonBlankLabel) ||
(editing_node.outputLabels !== undefined && JSON.stringify(newValue) !== JSON.stringify(editing_node.outputLabels))) {
changes.outputLabels = editing_node.outputLabels;
editing_node.outputLabels = newValue;
changed = true;
}
return changed;
}
function showEditDialog(node) {
var editing_node = node;
var isDefaultIcon;
@@ -1034,40 +1081,7 @@ RED.editor = (function() {
// }
var removedLinks = updateNodeProperties(editing_node,outputMap);
var inputLabels = $("#node-label-form-inputs").children().find("input");
var outputLabels = $("#node-label-form-outputs").children().find("input");
var hasNonBlankLabel = false;
newValue = inputLabels.map(function() {
var v = $(this).val();
hasNonBlankLabel = hasNonBlankLabel || v!== "";
return v;
}).toArray().slice(0,editing_node.inputs);
if ((editing_node.inputLabels === undefined && hasNonBlankLabel) ||
(editing_node.inputLabels !== undefined && JSON.stringify(newValue) !== JSON.stringify(editing_node.inputLabels))) {
changes.inputLabels = editing_node.inputLabels;
editing_node.inputLabels = newValue;
changed = true;
}
hasNonBlankLabel = false;
newValue = new Array(editing_node.outputs);
outputLabels.each(function() {
var index = $(this).attr('id').substring(23); // node-label-form-output-<index>
if (outputMap && outputMap.hasOwnProperty(index)) {
index = parseInt(outputMap[index]);
if (index === -1) {
return;
}
}
var v = $(this).val();
hasNonBlankLabel = hasNonBlankLabel || v!== "";
newValue[index] = v;
})
if ((editing_node.outputLabels === undefined && hasNonBlankLabel) ||
(editing_node.outputLabels !== undefined && JSON.stringify(newValue) !== JSON.stringify(editing_node.outputLabels))) {
changes.outputLabels = editing_node.outputLabels;
editing_node.outputLabels = newValue;
if (updateLabels(editing_node, changes, outputMap)) {
changed = true;
}
@@ -1675,25 +1689,25 @@ RED.editor = (function() {
editing_node.info = newDescription;
changed = true;
}
var inputLabels = $("#node-label-form-inputs").children().find("input");
var outputLabels = $("#node-label-form-outputs").children().find("input");
var newValue = inputLabels.map(function() { return $(this).val();}).toArray().slice(0,editing_node.inputs);
if (JSON.stringify(newValue) !== JSON.stringify(editing_node.inputLabels)) {
changes.inputLabels = editing_node.inputLabels;
editing_node.inputLabels = newValue;
if (updateLabels(editing_node, changes, null)) {
changed = true;
}
newValue = outputLabels.map(function() { return $(this).val();}).toArray().slice(0,editing_node.outputs);
if (JSON.stringify(newValue) !== JSON.stringify(editing_node.outputLabels)) {
changes.outputLabels = editing_node.outputLabels;
editing_node.outputLabels = newValue;
var iconModule = $("#node-settings-icon-module-hidden").val();
var iconFile = $("#node-settings-icon-file-hidden").val();
var icon = (iconModule && iconFile) ? iconModule+"/"+iconFile : "";
if ((editing_node.icon === undefined && icon !== "node-red/subflow.png") ||
(editing_node.icon !== undefined && editing_node.icon !== icon)) {
changes.icon = editing_node.icon;
editing_node.icon = icon;
changed = true;
}
RED.palette.refresh();
if (changed) {
var wasChanged = editing_node.changed;
editing_node.changed = true;
validateNode(editing_node);
var subflowInstances = [];
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+editing_node.id) {
@@ -1704,10 +1718,9 @@ RED.editor = (function() {
n.changed = true;
n.dirty = true;
updateNodeProperties(n);
validateNode(n);
}
});
var wasChanged = editing_node.changed;
editing_node.changed = true;
RED.nodes.dirty(true);
var historyEvent = {
t:'edit',
@@ -1786,6 +1799,7 @@ RED.editor = (function() {
$("#subflow-dialog-user-count").html(RED._("subflow.subflowInstances", {count:userCount})).show();
buildLabelForm(portLabels.content,subflow);
validateIcon(subflow);
trayBody.i18n();
},
close: function() {

View File

@@ -116,6 +116,12 @@ RED.palette = (function() {
el.data('popover').setContent(popOverContent);
}
function setIcon(element,sf) {
var iconElement = element.find(".palette_icon");
var icon_url = RED.utils.getNodeIcon(sf._def,sf);
iconElement.attr("style", "background-image: url("+icon_url+")");
}
function escapeNodeType(nt) {
return nt.replace(" ","_").replace(".","_").replace(":","_");
}
@@ -375,6 +381,7 @@ RED.palette = (function() {
portOutput.remove();
}
setLabel(sf.type+":"+sf.id,paletteNode,sf.name,marked(sf.info||""));
setIcon(paletteNode,sf);
});
}

View File

@@ -352,6 +352,7 @@ RED.projects.settings = (function() {
}
},{dependencies:dependencies});
}
function editDependencies(activeProject,depsJSON,container,depsList) {
var json = depsJSON||JSON.stringify(activeProject.dependencies||{},"",4);
if (json === "{}") {
@@ -380,6 +381,7 @@ RED.projects.settings = (function() {
function createDependenciesPane(activeProject) {
var pane = $('<div id="project-settings-tab-deps" class="project-settings-tab-pane node-help"></div>');
var nrDepButton;
if (RED.user.hasPermission("projects.write")) {
$('<button class="editor-button editor-button-small" style="margin-top:10px;float: right;">edit</button>')
.appendTo(pane)
@@ -387,6 +389,16 @@ RED.projects.settings = (function() {
evt.preventDefault();
editDependencies(activeProject,null,pane,depsList)
});
nrDepButton = $('<button class="editor-button editor-button-small" style="margin-top:10px;">add Node-RED core</button>')
.appendTo(pane)
.click(function(evt) {
evt.preventDefault();
activeProject.dependencies["node-red"] = "~"+RED.settings.version;
updateProjectDependencies(activeProject,depsList);
$(this).hide();
});
if (activeProject.dependencies.hasOwnProperty("node-red")) { nrDepButton.hide(); }
}
var depsList = $("<ol>",{style:"position: absolute;top: 60px;bottom: 20px;left: 20px;right: 20px;"}).appendTo(pane);
depsList.editableList({
@@ -473,6 +485,7 @@ RED.projects.settings = (function() {
evt.preventDefault();
var deps = $.extend(true, {}, activeProject.dependencies);
delete deps[entry.id];
if (entry.id === "node-red") { nrDepButton.show(); }
saveDependencies(depsList,row,deps,function(err) {
if (!err) {
row.fadeOut(200,function() {
@@ -519,7 +532,6 @@ RED.projects.settings = (function() {
updateProjectDependencies(activeProject,depsList);
return pane;
}
function showProjectFileListing(row,activeProject,current,filter,done) {

View File

@@ -708,7 +708,9 @@ RED.utils = (function() {
function getDefaultNodeIcon(def,node) {
var icon_url;
if (typeof def.icon === "function") {
if (node && node.type === "subflow") {
icon_url = "node-red/subflow.png";
} else if (typeof def.icon === "function") {
try {
icon_url = def.icon.call(node);
} catch(err) {
@@ -731,6 +733,16 @@ RED.utils = (function() {
return iconPath;
}
function isIconExists(iconPath) {
var iconSets = RED.nodes.getIconSets();
var iconFileList = iconSets[iconPath.module];
if (iconFileList && iconFileList.indexOf(iconPath.file) !== -1) {
return true;
} else {
return false;
}
}
function getNodeIcon(def,node) {
if (def.category === 'config') {
return "icons/node-red/cog.png"
@@ -738,18 +750,19 @@ RED.utils = (function() {
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) {
if (isIconExists(iconPath)) {
return "icons/" + node.icon;
}
}
var iconPath = getDefaultNodeIcon(def, node);
if (def.category === 'subflows') {
if (!isIconExists(iconPath)) {
return "icons/node-red/subflow.png";
}
}
return "icons/"+iconPath.module+"/"+iconPath.file;
}

View File

@@ -490,6 +490,7 @@ RED.view = (function() {
}
} else {
var subflow = RED.nodes.subflow(m[1]);
nn.name = "";
nn.inputs = subflow.in.length;
nn.outputs = subflow.out.length;
}

View File

@@ -113,8 +113,8 @@
.debug-message-meta {
background: #fff;
font-size: 10px;
color: #777;
font-size: 11px;
color: #707070;
}
.debug-message-date {
padding: 1px 5px 1px 1px;
@@ -125,7 +125,7 @@
}
.debug-message-name {
padding: 1px 5px;
color: #777;
color: #707070;
}
.debug-message-tools {
position: absolute;
@@ -159,7 +159,7 @@
.debug-message-element {
color: #333;
font-family: Menlo, monospace;
font-size: 12px !important;
font-size: 13px !important;
line-height: 1.3em;
}
.debug-message-object-key {
@@ -188,11 +188,9 @@
.debug-message-element.collapsed>span>.debug-message-object-handle {
transform: rotate(0deg);
}
.debug-message-object-entry.collapsed > .debug-message-object-entry {
display:none;
}
.debug-message-element.collapsed .debug-message-object-entry {
display:none;
}
@@ -202,14 +200,13 @@
.debug-message-element.collapsed .debug-message-buffer-opts {
display: none;
}
.debug-message-element.collapsed .debug-message-object-type-header {
display:none;
}
.debug-message-object-entry pre {
font-family: Menlo, monospace;
font-size: 12px;
line-height: 1.4em;
font-size: 13px;
line-height: 1.2em;
margin: 0 0 0 -1em;
}

View File

@@ -64,9 +64,10 @@
vertical-align: middle;
color: #555;
i {
position:relative;
top:-3px;
margin-right:4px;
position: relative;
top: -3px;
margin-left: 1px;
margin-right: 2px;
margin-top: 1px;
vertical-align: middle;
&.fa-ellipsis-h {

View File

@@ -138,7 +138,7 @@
icon: "arrow-in.png",
align: "right",
label: function() {
return this.name||this.command;
return this.name||this.command||(this.useSpawn=="true"?this._("exec.spawn"):this._("exec.exec"));
},
labelStyle: function() {
return this.name?"node_label_italic":"";

View File

@@ -65,6 +65,8 @@ module.exports = function(RED) {
"log:__node__.log,"+
"error:__node__.error,"+
"warn:__node__.warn,"+
"debug:__node__.debug,"+
"trace:__node__.trace,"+
"on:__node__.on,"+
"status:__node__.status,"+
"send:function(msgs){ __node__.send(__msgid__,msgs);}"+
@@ -91,6 +93,12 @@ module.exports = function(RED) {
warn: function() {
node.warn.apply(node, arguments);
},
debug: function() {
node.debug.apply(node, arguments);
},
trace: function() {
node.trace.apply(node, arguments);
},
send: function(id, msgs) {
sendResults(node, id, msgs);
},

View File

@@ -99,7 +99,7 @@
outputs:1,
icon: "template.png",
label: function() {
return this.name;
return this.name||this._("template.template");;
},
labelStyle: function() {
return this.name?"node_label_italic":"";

View File

@@ -50,6 +50,7 @@ module.exports = function(RED) {
// try node context:
var dot = name.indexOf(".");
/* istanbul ignore else */
if (dot > 0) {
var contextName = name.substr(0, dot);
var variableName = name.substr(dot + 1);
@@ -61,7 +62,8 @@ module.exports = function(RED) {
return this.nodeContext.global.get(variableName);
}
}
}catch(err) {
}
catch(err) {
throw err;
}
}
@@ -87,24 +89,27 @@ module.exports = function(RED) {
* Allow template contents to be defined externally
* through inbound msg.template IFF node.template empty
*/
var template = node.template;
if (msg.hasOwnProperty("template")) {
if (node.template == "" || node.template === null) {
node.template = msg.template;
if (template == "" || template === null) {
template = msg.template;
}
}
if (node.syntax === "mustache") {
if (node.outputFormat === "json") {
value = mustache.render(node.template,new NodeContext(msg, node.context(), null, true));
value = mustache.render(template,new NodeContext(msg, node.context(), null, true));
} else {
value = mustache.render(node.template,new NodeContext(msg, node.context(), null, false));
value = mustache.render(template,new NodeContext(msg, node.context(), null, false));
}
} else {
value = node.template;
value = template;
}
/* istanbul ignore else */
if (node.outputFormat === "json") {
value = JSON.parse(value);
}
/* istanbul ignore else */
if (node.outputFormat === "yaml") {
value = yaml.load(value);
}
@@ -117,7 +122,8 @@ module.exports = function(RED) {
node.context().global.set(node.field,value);
}
node.send(msg);
} catch(err) {
}
catch(err) {
node.error(err.message);
}
});

View File

@@ -33,7 +33,7 @@
outputs:0,
icon: "comment.png",
label: function() {
return this.name||"";
return this.name||this._("comment.comment");
},
labelStyle: function() {
return this.name?"node_label_italic":"";

View File

@@ -222,7 +222,6 @@ RED.debug = (function() {
clearMessageList(false);
});
return {
content: content,
footer: footerToolbar
@@ -238,6 +237,9 @@ RED.debug = (function() {
workspaceOrder.forEach(function(ws,i) {
workspaceOrderMap[ws] = i;
});
candidateNodes = candidateNodes.filter(function(node) {
return workspaceOrderMap.hasOwnProperty(node.z);
})
candidateNodes.sort(function(A,B) {
var wsA = workspaceOrderMap[A.z];
var wsB = workspaceOrderMap[B.z];
@@ -339,7 +341,7 @@ RED.debug = (function() {
activeMenuMessage.clearPinned();
}},
null,
{id:"debug-message-menu-item-filter",label:RED._("node-red:debug.messageMenu.filterNode"),onselect:function(){
{id:"debug-message-menu-item-filter", label:RED._("node-red:debug.messageMenu.filterNode"),onselect:function(){
var candidateNodes = RED.nodes.filterNodes({type:'debug'});
candidateNodes.forEach(function(n) {
filteredNodes[n.id] = true;
@@ -361,6 +363,15 @@ RED.debug = (function() {
menuOptionMenu.on('mouseup', function() { $(this).hide() });
menuOptionMenu.appendTo("body");
}
var filterOptionDisabled = false;
var sourceNode = RED.nodes.node(sourceId);
if (sourceNode && sourceNode.type !== 'debug') {
filterOptionDisabled = true;
}
RED.menu.setDisabled('debug-message-menu-item-filter',filterOptionDisabled);
RED.menu.setDisabled('debug-message-menu-item-clear-filter',filterOptionDisabled);
var elementPos = button.offset();
menuOptionMenu.css({
top: elementPos.top+"px",

View File

@@ -142,9 +142,9 @@ module.exports = function(RED) {
var limit = 1;
if (node.out === "pwm") { limit = 100; }
if ((out >= 0) && (out <= limit)) {
if (RED.settings.verbose) { node.log("out: "+msg.payload); }
if (RED.settings.verbose) { node.log("out: "+out); }
if (node.child !== null) {
node.child.stdin.write(msg.payload+"\n");
node.child.stdin.write(out+"\n");
node.status({fill:"green",shape:"dot",text:msg.payload.toString()});
}
else {

View File

@@ -88,6 +88,8 @@
<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>
<dt class="optional">followRedirects</dt>
<dd>If set to <code>false</code> prevent following Redirect (HTTP 301).<code>true</code> by default</dd>
</dl>
<h3>Outputs</h3>
<dl class="message-properties">

View File

@@ -108,6 +108,9 @@ module.exports = function(RED) {
}
}
}
if (msg.hasOwnProperty('followRedirects')) {
opts.followRedirects = msg.followRedirects;
}
if (msg.cookies) {
var cookies = [];
if (opts.headers.hasOwnProperty('cookie')) {

View File

@@ -59,7 +59,7 @@
outputs:1,
icon: "watch.png",
label: function() {
return this.name||this.files;
return this.name||this.files||this._("watch.watch");
},
labelStyle: function() {
return this.name?"node_label_italic":"";

View File

@@ -70,8 +70,8 @@
}
},
"catch": {
"catch": "catch all",
"catchNodes": "catch (__number__)",
"catch": "catch: all",
"catchNodes": "catch: __number__",
"label": {
"source": "Catch errors from",
"node": "node",
@@ -86,8 +86,8 @@
}
},
"status": {
"status": "status (all)",
"statusNodes": "status (__number__)",
"status": "status: all",
"statusNodes": "status: __number__",
"label": {
"source": "Report status from",
"node": "node",
@@ -166,6 +166,8 @@
}
},
"exec": {
"exec": "exec",
"spawn": "spawn",
"label": {
"command": "Command",
"append": "Append",
@@ -195,6 +197,7 @@
"tip": "See the Info tab for help writing functions."
},
"template": {
"template": "template",
"label": {
"template": "Template",
"property": "Set property",
@@ -301,6 +304,7 @@
}
},
"comment": {
"comment": "comment",
"label": {
"title": "Title",
"body": "Body"
@@ -416,6 +420,7 @@
}
},
"watch": {
"watch": "watch",
"label": {
"files": "File(s)",
"recursive": "Watch sub-directories recursively"
@@ -542,6 +547,7 @@
}
},
"switch": {
"switch": "switch",
"label": {
"property": "Property",
"rule": "rule",
@@ -597,6 +603,7 @@
}
},
"range": {
"range": "range",
"label": {
"action": "Action",
"inputrange": "Map the input range",
@@ -763,7 +770,9 @@
"status": {
"stopped": "stopped",
"closed": "closed",
"not-running": "not running"
"not-running": "not running",
"not-available": "not available",
"na": "N/A : __value__"
},
"errors": {
"ignorenode": "Ignoring Raspberry Pi specific node",
@@ -782,6 +791,7 @@
}
},
"tail": {
"tail": "tail",
"label": {
"filename": "Filename",
"type": "File type",
@@ -835,6 +845,7 @@
"tip": "Tip: The filename should be an absolute path, otherwise it will be relative to the working directory of the Node-RED process."
},
"split": {
"split": "split",
"intro":"Split <code>msg.payload</code> based on type:",
"object":"<b>Object</b>",
"objectSend":"Send a message for each key/value pair",
@@ -846,6 +857,7 @@
"addname":" Copy key to "
},
"join":{
"join": "join",
"mode":{
"mode":"Mode",
"auto":"automatic",
@@ -892,6 +904,7 @@
}
},
"sort" : {
"sort": "sort",
"target" : "Sort",
"seq" : "message sequence",
"key" : "Key",
@@ -905,6 +918,7 @@
"clear" : "clear pending message in sort node"
},
"batch" : {
"batch": "batch",
"mode": {
"label" : "Mode",
"num-msgs" : "Group by number of messages",

View File

@@ -25,7 +25,7 @@
</dl>
<h3>詳細</h3>
<p>injectードを用いることで、指定したペイロード値を用いてフローを開始できます。デフォルトのペイロード値は現在時刻のタイムスタンプを1970年1月1日からの経過ミリ秒で表現した値です。</p>
<p>文字列、数値、論理値、JavaScriptオブジェクト、フロー/グローバルコンテストの値などの送出も可能です。</p>
<p>文字列、数値、論理値、JavaScriptオブジェクト、フロー/グローバルコンテストの値などの送出も可能です。</p>
<p> デフォルト設定では、エディタ内に表示されるボタンをクリックすることで、ノードを手動で起動できます。指定間隔もしくはスケジュールに従ってメッセージを送出するように設定することも可能です。</p>
<p>また、フロー開始の際に一度だけメッセージを送出させることもできます。</p>
<p><i>時間間隔</i>」に指定可能な値の最大値は、約596時間(もしくは24日)です。一日より長い間隔を扱いたい場合は、電源停止や再起動にも対応可能なスケジューラノードの利用を検討すると良いでしょう。</p>

View File

@@ -21,7 +21,6 @@
<p>JavaScriptオブジェクトと配列は必要に応じて折り畳んだり展開したりできます。バッファオブジェクトを生データとして表示したり、表現可能な場合に文字列として表示することも可能です。</p>
<p>メッセージを受信した時刻、送信ード、メッセージの型に関する情報を「デバッグ」サイドバーに表示されたメッセージに付随して表示します。送信元ードのIDを選択すると、ワークスペース内の対応ードを確認できます。</p>
<p>出力の有効/無効はード上のボタンで切り替えられます。フロー上で未使用のdebugードは、無効化するか削除することを推奨します。</p>
<p>全てのメッセージをランタイムログに送付、もしくは、(32文字の)短いデータを
debugードの下のステータステキストに表示することも可能です。</p>
<p>全てのメッセージをランタイムログに送付、もしくは、(32文字の)短いデータをdebugードの下のステータステキストに表示することも可能です。</p>
</script>

View File

@@ -38,7 +38,7 @@
</dl>
<dl class="message-properties">
<dt>rc <span class="property-type">オブジェクト</span></dt>
<dd>返却コードオブジェクト(3番ポートでも受取り可能)のコピー(execモードのみ)</dd>
<dd>返却コードオブジェクト(3番目の端子でも受取り可能)のコピー(execモードのみ)</dd>
</dl>
</li>
<li>標準エラー出力(stderr)
@@ -48,7 +48,7 @@
</dl>
<dl class="message-properties">
<dt>rc <span class="property-type">オブジェクト</span></dt>
<dd>返却コードオブジェクト(3番ポートでも受取り可能)のコピー(execモードのみ)</dd>
<dd>返却コードオブジェクト(3番目の端子でも受取り可能)のコピー(execモードのみ)</dd>
</dl>
</li>
<li>返却コード(return code)
@@ -61,8 +61,8 @@
<h3>詳細</h3>
<p>デフォルトでは、<code>exec</code>システムコールを用いてコマンドを呼び出してその完了を待ち、出力を返します。例えば、コマンドの実行が成功した場合には、<code>{ code: 0 }</code>と言う返却値を返します。</p>
<p><code>spawn</code>を使ってコマンドを実行し、
標準出力および標準エラー出力へ出力を返すようにすることもできます。この場合、通常1行毎に値を返します。コマンドの実行が完了すると、3番目のポートにオブジェクトを出力します。例えば、コマンドの実行が成功した場合には、<code>{ code: 0 }</code>と言う返却値を返します。</p>
<p>エラー発生時には、3番目のポート<code>msg.payload</code><code>message</code><code>signal</code>など付加情報を返します。</p>
標準出力および標準エラー出力へ出力を返すようにすることもできます。この場合、通常1行毎に値を返します。コマンドの実行が完了すると、3番目の端子にオブジェクトを出力します。例えば、コマンドの実行が成功した場合には、<code>{ code: 0 }</code>と言う返却値を返します。</p>
<p>エラー発生時には、3番目の端子<code>msg.payload</code><code>message</code><code>signal</code>など付加情報を返します。</p>
<p>実行対象のコマンドはノード設定で定義します。<code>msg.payload</code>や追加引数をコマンドに追加することもできます。</p>
<p>コマンドもしくはパラメータが空白を含む場合には、引用符で囲みます。- <code>"This is a single parameter"</code></p>
<p>返却する<code>payload</code>は通常<i>文字列</i>ですが、UTF8文字以外が存在すると<i>バッファ</i>となります。</p>

View File

@@ -24,11 +24,8 @@
</dl>
<h3>詳細</h3>
<p>フロー内でタイムアウトを作成するのに利用します。メッセージを受け取ると、
デフォルトでは<code>payload</code><code>1</code>を設定して送信します。送信後250ms待機し、<code>payload</code><code>0</code>に設定した2つ目のメッセージを送信します。この機能は、例えばRaspberry PIのGPIOピンに接続したLEDを点滅させるために活用できます。</p>
<p>各送信メッセージのペイロードはさまざまな種類の値に設定できます。再送信デ
ータなしとすることも可能です。例えば、再送信データを「<i>なし</i>」とし、
メッセージを受け取った時に遅延を延長することを選択した場合、triggerードは監視タイマとして動作します。すなわち、指定間隔内にメッセージを受信しない場合にメッセージを送信します。</p>
<p>フロー内でタイムアウトを作成するのに利用します。メッセージを受け取ると、デフォルトでは<code>payload</code><code>1</code>を設定して送信します。送信後250ms待機し、<code>payload</code><code>0</code>に設定した2つ目のメッセージを送信します。この機能は、例えばRaspberry PIのGPIOピンに接続したLEDを点滅させるために活用できます。</p>
<p>各送信メッセージのペイロードはさまざまな種類の値に設定できます。送信データなしとすることも可能です。例えば、再送信データを「<i>なし</i>」とし、メッセージを受け取った時に遅延を延長することを選択した場合、triggerードは監視タイマとして動作します。すなわち、指定間隔内にメッセージを受信しない場合にメッセージを送信します。</p>
<p>ペイロードに<i>文字列</i>を指定する場合、mustache形式のテンプレートが利用できます。</p>
<p><code>reset</code>プロパティを持つメッセージを受信した場合、もしくは、<code>payload</code>が設定した値にマッチする場合、仕掛かり中の待機や繰り返しをクリアしメッセージの送信は行いません。</p>
<p>受信メッセージでリセットするまで一定間隔でメッセージを再送するように指定することもできます。</p>

View File

@@ -0,0 +1,71 @@
<!--
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.
-->
<script type="text/x-red" data-help-name="rpi-gpio in">
<p>Raspberry Piの入力ード入力ピンの状態に応じて0 または 1 の値を持つ<code>msg.payload</code></p>
<h3>出力</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">数値</span></dt>
<dd>ペイロードには0 または 1 が設定されます</dd>
<dt>topic <span class="property-type">文字列</span></dt>
<dd>トピックには<code>pi/{ピン番号}</code></dd>
</dl>
<h3>詳細</h3>
<p>入力のプルアップ抵抗またはプルダウン抵抗を有効にすることもできます</p>
<p>動作にはRPi.GPIO pythonライブラリのバージョン 0.5.10 (またはそれ以上)が必要です</p>
</script>
<script type="text/x-red" data-help-name="rpi-gpio out">
<p>Raspberry Piの出力ードデジタルモードまたはPWMモードで利用できます
<h3>入力</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">数値 | 文字列 | 真偽値</span></dt>
</dl>
<h3>詳細</h3>
<p>デジタルモード - <code>msg.payload</code> 0 1 ( true false ) </p>
<p>デプロイ時にピンの初期値として 0 または 1 を設定することもできます</p>
<p>PWMモード - 入力値に 0 から 100 の数値を指定でき小数値の指定も可能です</p>
<p>サーボの制御にPWMモードが利用でき入力に小数値も含む 10 から 20 の値が指定可能です
PWMを行うハードウェアを利用していることからPWMモードの指定にはGPIO2ピンが最も適しています
より良くサーボの制御を行いたい場合はnode-red-node-pi-gpiod ノードの利用も検討してください</p>
<p>動作にはRPi.GPIO pythonライブラリのバージョン 0.5.10 (またはそれ以上)が必要です</p>
</script>
<script type="text/x-red" data-help-name="rpi-mouse">
<p>Raspberry Pi のマウスボタンノードUSBマウスが必要です</p>
<h3>出力</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">数値</span></dt>
<dd>選択されたマウスのボタンが押されたまたは離された場合に 1 または 0 が設定されます</dd>
<dt>button <span class="property-type">数値</span></dt>
<dd>真ん中のボタンに応じて 1, 2, 4 が設定されボタンあるいはボタンの組み合わせに応じた処理ができます</dd>
<dt>topic <span class="property-type">文字列</span></dt>
<dd><code>pi/mouse</code></dd>
</dl>
</script>
<script type="text/x-red" data-help-name="rpi-keyboard">
<p>Raspberry Pi のキーボードを制御するノードUSBキーボードが必要です</p>
<h3>出力</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">数値</span></dt>
<dd>キーコードを含みます</dd>
<dt>action <span class="property-type">文字列</span></dt>
<dd>"up", "down", または "repeat" が設定されます</dd>
<dt>topic <span class="property-type">文字列</span></dt>
<dd><code>pi/key</code></dd>
</dl>
</script>

View File

@@ -16,14 +16,12 @@
<script type="text/x-red" data-help-name="websocket in">
<p>WebSocket入力ード</p>
<p>デフォルトでは、WebSocketにより受信したデータは<code>msg.payload</code>に格納します。
ソケットはJSON形式の文字列を待ち受けるように設定することができます。JSON形式の文字列を受け付けると、オブジェクトへの変換を行い、メッセージ全体として送信します。</p>
<p>デフォルトでは、WebSocketにより受信したデータは<code>msg.payload</code>に格納します。ソケットはJSON形式の文字列を待ち受けるように設定することができます。JSON形式の文字列を受け付けると、オブジェクトへの変換を行い、メッセージ全体として送信します。</p>
</script>
<script type="text/x-red" data-help-name="websocket out">
<p>WebSocket出力ード</p>
<p>デフォルトでは、<code>msg.payload</code>をWebSocket経由で送信します。
ソケットは<code>msg</code>全体をJSON文字列にエンコードしてWebSocketを介して送信することもできます。</p>
<p>デフォルトでは、<code>msg.payload</code>をWebSocket経由で送信します。ソケットは<code>msg</code>全体をJSON文字列にエンコードしてWebSocketを介して送信することもできます。</p>
<p>このードが受信したメッセージがWebSocket Inードが生成したものである場合、メッセージはフローを起動したクライアントに送り返されます。それ以外の場合、メッセージは接続している全てのクライアントにブロードキャストされます。</p>
<p>WebSocket Inードが生成したメッセージをブロードキャストしたい場合には、フロー中で<code>msg._session</code>プロパティを削除します。</p>
@@ -34,5 +32,5 @@
</script>
<script type="text/x-red" data-help-name="websocket-client">
<p>この設定ードは指定したURLにWebSocketクライアントを接続します。</p>
<p>この設定ードは指定したURLにWebSocketクライアントを接続します。</p>
</script>

View File

@@ -18,8 +18,7 @@
<p>ディレクトリもしくはファイルの変化を検知します。</p>
<p>カンマ区切りでディレクトリおよびファイルのリストを指定します。空白を含む場合は、引用符で"..."のように囲んでください。</p>
<p>Windowsでは、2重バックスラッシュ\\をディレクトリ名に使用します。</p>
<p>実際に変化したファイルのフルパス名を<code>msg.payload</code>に、
、検知対象リストの文字列を<code>msg.topic</code>に返します。</p>
<p>実際に変化したファイルのフルパス名を<code>msg.payload</code>に、検知対象リストの文字列を<code>msg.topic</code>に返します。</p>
<p><code>msg.file</code>は変化したファイルのファイル名表します。<code>msg.type</code>は変化した対象の種別(<i>file</i>もしくは<i>directory</i>)を、<code>msg.size</code>はファイルサイズ(バイト数)を表します。</p>
<p>Linuxではファイルとして表されるもの<i>全て</i>が、検知対象にできます。</p>
<p><b>注: </b>検知対象のディレクトリもしくはファイルは存在していなくてはなりません。対象ファイルもしくはディレクトリが削除された場合、再作成されても検知対象から外れたままです。</p>

View File

@@ -16,22 +16,20 @@
<script type="text/x-red" data-help-name="tcp in">
<p>TCPからの入力を行います。リモートTCPポートに接続するか、外部らからのコネクションを受け付けます。</p>
<p><b>注: </b>1024番より小さな番号のポートをアクセスするにはroodもしくはadministrator権限が必要なシステムもあります。</p>
<p><b>注: </b>1024番より小さな番号のポートをアクセスするにはrootもしくはadministrator権限が必要なシステムもあります。</p>
</script>
<script type="text/x-red" data-help-name="tcp out">
<p>TCPへの出力を行います。リモートTCPポートへ接続、外部からのコネクションの受け付け、もしくは、TCP Inードで受け付けたメッセージへのリプライを行います。</p>
<p><code>msg.payload</code>のみが送信対象となります。</p>
<p><code>msg.payload</code>がバイナリデータをBase64エンコーディングの文字列に変換したものの場合、Base64デコードオプションを指定するとデータをバイナリに変化んして送信します。</p>
<p><code>msg.payload</code>がバイナリデータをBase64エンコーディングの文字列に変換したものの場合、Base64デコードオプションを指定するとデータをバイナリに変して送信します。</p>
<p><code>msg._session</code>が存在しない場合、接続している<b>全ての</b>クライアントに送信します。</p>
<p><b>注: </b>1024番より小さな番号のポートをアクセスするにはroodもしくはadministrator権限が必要なシステムもあります。</p>
<p><b>注: </b>1024番より小さな番号のポートをアクセスするにはrootもしくはadministrator権限が必要なシステムもあります。</p>
</script>
<script type="text/x-red" data-help-name="tcp request">
<p>シンプルなTCPリクエストード。<code>msg.payload</code>をサーバのTCPポートに送信し、レスポンスを待ちます。</p>
<p>
サーバに接続、"リクエスト"送信、"レスポンス"受信を行います。固定長の文字数、指定文字へのマッチ、最初のリプライの到着から指定した時間待つ、データの到着待ち、データ送信を行いリプライを待たず接続を即時解除、などから動作を選択できます</p>
<p>
レスポンスはバッファ形式で<code>msg.payload</code>に出力されます。文字列として扱いには、toString()を使用してください。</p>
<p>サーバに接続、"リクエスト"送信、"レスポンス"受信を行います。固定長の文字数、指定文字へのマッチ、最初のリプライの到着から指定した時間待つ、データの到着待ち、データ送信を行いリプライを待たず接続を即時解除、などから動作を選択できます。</p>
<p>レスポンスはバッファ形式で<code>msg.payload</code>に出力されます。文字列として扱いには、toString()を使用してください</p>
<p>TCPホストのポート番号設定を空にした場合、<code>msg.host</code>および<code>msg.port</code>プロパティを設定しなくてはなりません。</p>
</script>

View File

@@ -17,13 +17,12 @@
<script type="text/x-red" data-help-name="udp in">
<p>UDP入力ード。<code>msg.payload</code>にバッファ、文字列、もしくは、Base64エンコーディング文字列を生成します。マルチキャストをサポートしています。</p>
<p><code>msg.ip</code><code>msg.port</code>に受信したメッセージのIPアドレスとポートを設定します。</p>
<p><b></b>: 1024番より小さな番号のポートへのアクセス、ブロードキャストを行うにはroodもしくはadministrator権限が必要なシステムもあります。</p>
<p><b></b>: 1024番より小さな番号のポートへのアクセス、ブロードキャストを行うにはrootもしくはadministrator権限が必要なシステムもあります。</p>
</script>
<script type="text/x-red" data-help-name="udp out">
<p><code>msg.payload</code>を指定したUDPのホストとポートに送信します。マルチキャストをサポートします。</p>
<p><code>msg.ip</code><code>msg.port</code>に接続先を設定できますが、ノード設定の方が優先されます。</p>
<p>
ブロードキャストを行うには、アドレスをローカルブロードキャストIPアドレスに設定するか、グローバルブロードキャストアドレスである255.255.255.255を試してください</p>
<p><b></b>: 1024番より小さな番号のポートへのアクセス、ブロードキャストを行うにはroodもしくはadministrator権限が必要なシステムもあります。</p>
<p>ブロードキャストを行うには、アドレスをローカルブロードキャストIPアドレスに設定するか、グローバルブロードキャストアドレスである255.255.255.255を試してください。</p>
<p><b></b>: 1024番より小さな番号のポートへのアクセス、ブロードキャストを行うにはrootもしくはadministrator権限が必要なシステムもあります</p>
</script>

View File

@@ -32,5 +32,4 @@
<h3>メッセージ列の扱い</h3>
<p>switchードは入力メッセージの列に関する情報を保持する<code>msg.parts</code>をデフォルトでは変更しません。</p>
<p><b>メッセージ列の補正</b>」オプションを指定すると、マッチした各ルールに対して新しいメッセージ列を生成します。このモードでは、switchードは新たなメッセージ列を送信する前に、入力メッセージ列全体を内部に蓄積します。<code>nodeMessageBufferMaxLength</code>を設定すると、蓄積するメッセージ数を制限できます。</p>
</script>

View File

@@ -29,6 +29,5 @@
<dt>移動</dt>
<dd>プロパティの移動または名前の変更を行います。</dd>
</dl>
<p>「expression」には<a href="http://jsonata.org/" target="_new">JSONata</a>言語を指定できます。
</p>
<p>「expression」には<a href="http://jsonata.org/" target="_new">JSONata</a>言語を指定できます。</p>
</script>

View File

@@ -49,7 +49,7 @@
<p><code>msg.parts</code>プロパティを用いて元のメッセージとメッセージ列との対応関係を記憶します。</p>
<h4>ストリームモード</h4>
<p>このノードはメッセージ列を再構成して送信する際にも有用です。例えば、改行終端のコマンドを送信するようなシリアルデバイスでは、メッセージの最後のコマンド部分が途切れたメッセージを送出する場合があります。「ストリームモード」を用いることで、完結した個別コマンドにメッセージを分割することができます。入力メッセージの最後に未完部分がある場合、<b>split</b>ノードは未完部分を記憶しておいて、次に受信したメッセージの先頭に付加します。</p>
<p>このモードで処理する際には、メッセージ数を予め知ることができないため、<code>msg.parts.count</code>プロパティは設定されません。従って、<b>join</b>ノードの「自動モード」と組みわせることはできません。</p>
<p>このモードで処理する際には、メッセージ数を予め知ることができないため、<code>msg.parts.count</code>プロパティは設定されません。従って、<b>join</b>ノードの「自動モード」と組みわせることはできません。</p>
</script>

View File

@@ -38,8 +38,7 @@
<p>「列名」にカラム名のリストを指定することができます。CSVからオブジェクトに変換を行う際、カラム名をプロパティ名として使用します。「列名」の代わりに、CSVデータの1行目にカラム名を含めることもできます。</p>
<p>CSVへの変換を行う際には、オブジェクトから取り出すべきプロパティとその順序を「列名」を参照して決めます。</p>
<p>入力が配列の場合には、「列名」はカラム名を表す行の出力指定がされた場合だけ用います。</p>
<p>
<code>parts</code>プロパティが正しく設定されている場合、メッセージ列を入力として受け付けます。</p>
<p><code>parts</code>プロパティが正しく設定されている場合、メッセージ列を入力として受け付けます。</p>
<p>CSVを複数のメッセージに変換して出力する場合、出力がメッセージ列となるよう<code>parts</code>プロパティを設定します。</p>
<p><b>注:</b> カンマ以外の区切り文字を設定した場合であっても、「列名」はカンマ区切りとしてください。</p>
</script>

View File

@@ -20,9 +20,9 @@
<dl class="message-properties">
<dt>payload <span class="property-type">文字列</span></dt>
<dd>要素を取り出すHTML文字列</dd>
<dt class="optional">select <span class="property-type">string</span></dt>
<dt class="optional">select <span class="property-type">文字列</span></dt>
<dd>編集パネルでセレクタを指定していない場合、メッセージのプロパティとして設定できます</dd>
</dl>
</dl>
<h3>出力</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">配列 | 文字列</span></dt>

View File

@@ -15,7 +15,7 @@
-->
<script type="text/x-red" data-help-name="json">
<p>Converts between a JSON string and its JavaScript object representation, in either direction.</p>
<p>JSON文字列とJavaScriptオブジェクトとの間で相互変換を行います。</p>
<h3>入力</h3>
<dl class="message-properties">
<dt>payload<span class="property-type">オブジェクト | 文字列</span></dt>
@@ -33,6 +33,6 @@
</dl>
<h3>詳細</h3>
<p>デフォルトの変換対象は<code>msg.payload</code>ですが、他のメッセージプロパティを変換対象とすることも可能です。</p>
<p>双方向の変換を自動選択するのではなく、特定の変換のみ行うように設定できます。この機能は、例えば、<code>HTTP In</code>ードに対するリクエストがcontent-typeを正しく設定していないであっても、JSONードによる変換結果がJavaScriptオブジェクトであることを保証するために利用します。</p>
<p>双方向の変換を自動選択するのではなく、特定の変換のみ行うように設定できます。この機能は、例えば、<code>HTTP In</code>ードに対するリクエストがcontent-typeを正しく設定していない場合であっても、JSONードによる変換結果がJavaScriptオブジェクトであることを保証するために利用します。</p>
<p>JSON文字列への変換が指定されている場合、受信した文字列に対してさらなるチェックは行いません。すなわち、文字列がJSONとして正しいかどうかの検査や、整形オプションを指定していたとしても整形処理を実施しません。</p>
</script>

View File

@@ -15,9 +15,7 @@
-->
<script type="text/x-red" data-help-name="file">
<p><code>msg.payload</code>をファイルに書き出します。書き出しは、ファイルの
最後に追記もしくは既存の内容の置き換えを選択できます。
この他、ファイルの削除を行うことも可能です。</p>
<p><code>msg.payload</code>をファイルに書き出します。書き出しは、ファイルの最後に追記もしくは既存の内容の置き換えを選択できます。この他、ファイルの削除を行うことも可能です。</p>
<h3>入力</h3>
<dl class="message-properties">
<dt class="optional">filename <span class="property-type">文字列</span></dt>

View File

@@ -5,7 +5,8 @@
"topic": "主题",
"name": "名称",
"username": "用户名",
"password": "密码"
"password": "密码",
"property": "属性"
},
"status": {
"connected": "已连接",
@@ -13,7 +14,7 @@
"disconnected": "已断开",
"connecting": "连接中",
"error": "错误",
"ok": "确"
"ok": "确"
},
"notification": {
"error": "<strong>错误</strong>: __message__",
@@ -37,17 +38,17 @@
"repeat": "重复"
},
"timestamp": "时间戳",
"none": "空白",
"interval": "间隔",
"interval-time": "定时间内间隔",
"time": "定时间",
"none": "",
"interval": "周期性执行",
"interval-time": "定时间段周期性执行",
"time": "定时间",
"seconds": "秒",
"minutes": "分钟",
"hours": "小时",
"between": "介于",
"previous": "之前数值",
"at": "在",
"and": "之间",
"and": "",
"every": "每隔",
"days": [
"星期一",
@@ -59,18 +60,20 @@
"星期天"
],
"on": "在",
"onstart": "运行时注入?",
"tip": "<b>注意:</b> \"特定时间内间隔\" 和 \"特定时间\" 会使用cron系统.<br/> 详情查看信息页.",
"onstart": "立刻执行于",
"onceDelay": "秒后, 此后",
"tip": "<b>注意:</b> \"指定时间段周期性执行\" 和 \"指定时间\" 会使用cron系统.<br/> 详情查看信息页.",
"success": "成功注入: __label__",
"errors": {
"failed": "注入失败, 请查看日志"
"failed": "注入失败, 请查看日志",
"toolong": "周期过长"
}
},
"catch": {
"catch": "检测异常",
"catchNodes": "检测到 (__number__)",
"catch": "监测所有节点",
"catchNodes": "监测__number__个节点",
"label": {
"source": "检测错误来自",
"source": "监测范围",
"node": "节点",
"type": "类型",
"selectAll": "全选",
@@ -79,14 +82,14 @@
},
"scope": {
"all": "所有节点",
"selected": "已选节点"
"selected": "指定节点"
}
},
"status": {
"status": "状态 (所有)",
"statusNodes": "状态显示 (__number__)",
"status": "报告所有节点状态",
"statusNodes": "报告__number__个节点状态",
"label": {
"source": "状态报告来自",
"source": "报告状态范围",
"node": "节点",
"type": "类型",
"selectAll": "全选",
@@ -95,7 +98,7 @@
},
"scope": {
"all": "所有节点",
"selected": "已选节点"
"selected": "指定节点"
}
},
"debug": {
@@ -104,7 +107,11 @@
"msgobj": "完整信息",
"to": "目标",
"debtab": "调试窗口",
"tabcon": "调试窗口及终端控制台",
"tabcon": "调试窗口及Console",
"toSidebar": "调试窗口",
"toConsole": "Console",
"toStatus": "节点状态 (32位字符)",
"severity": "级别",
"notification": {
"activated": "成功激活: __label__",
"deactivated": "成功取消: __label__"
@@ -114,21 +121,21 @@
"name": "名称",
"filterAll": "所有节点",
"filterSelected": "已选节点",
"filterCurrent": "前流程",
"filterCurrent": "前流程",
"debugNodes": "调试节点",
"clearLog": "清日志",
"clearLog": "清日志",
"openWindow": "在新窗口打开"
},
"messageMenu": {
"collapseAll": "折叠所有路径",
"clearPinned": "清已固定路径",
"clearPinned": "清已固定路径",
"filterNode": "过滤此节点",
"clearFilter": "清除已设过滤"
"clearFilter": "清空过滤条件"
}
},
"link": {
"linkIn": "连接入口",
"linkOut": "连接出口",
"linkIn": "输入",
"linkOut": "输出",
"label": {
"event": "事件名称",
"node": "节点名称",
@@ -144,16 +151,18 @@
"upload": "上传",
"cert": "证书",
"key": "私钥",
"passphrase": "密码",
"ca": "CA证书",
"verify-server-cert":"验证服务器证书"
},
"placeholder": {
"cert":"证书路径 (PEM 格式)",
"key":"私路径 (PEM 格式)",
"ca":"CA证书路径 (PEM 格式)"
"key":"私路径 (PEM 格式)",
"ca":"CA证书路径 (PEM 格式)",
"passphrase":"私钥密码 (可选)"
},
"error": {
"missing-file": "证书/密文件提供"
"missing-file": "未提供证书/密文件"
}
},
"exec": {
@@ -169,10 +178,10 @@
"extraparams": "额外的输入参数"
},
"opt": {
"exec": "当命令任务完成时 - exec 模式",
"spawn": "当命令任务进行时 - spawn 模式"
"exec": "当命令完成时 - exec模式",
"spawn": "当命令进行时 - spawn模式"
},
"oldrc": "使用旧式输出模式 (传统模式)"
"oldrc": "使用旧式输出 (兼容模式)"
},
"function": {
"label": {
@@ -180,7 +189,7 @@
"outputs": "输出"
},
"error": {
"inputListener":"无法在函数里面加入对‘注入事件的监视",
"inputListener":"无法在函数中监听对'注入'事件",
"non-message-returned":"函数节点尝试返回类型为 __type__ 的信息"
},
"tip": "可从信息页面查看更多关于如何编写函数的帮助"
@@ -194,7 +203,8 @@
"output": "输出为",
"mustache": "Mustache 模版",
"plain": "纯文本",
"json": "解析JSON",
"json": "JSON",
"yaml": "YAML",
"none": "无"
},
"templatevalue": "This is the payload: {{payload}} !"
@@ -204,13 +214,13 @@
"for": "时长",
"delaymsg": "延迟每一条信息",
"delayfixed": "固定延迟时间",
"delayvarmsg": "msg.delay写延迟时长",
"delayvarmsg": "允许msg.delay写延迟时长",
"randomdelay": "随机延迟",
"limitrate": "信息速度限制",
"limitrate": "限制信息速",
"limitall": "所有信息",
"limittopic": "每一个 msg.topic",
"fairqueue": "轮流发每一个主题",
"timedqueue": "发所有主题",
"limittopic": "每一个msg.topic",
"fairqueue": "依次发送每一个topic",
"timedqueue": "发所有topic",
"milisecs": "毫秒",
"secs": "秒",
"sec": "秒",
@@ -218,10 +228,10 @@
"min": "分",
"hours": "小时",
"hour": "小时",
"days": "",
"day": "",
"days": "",
"day": "",
"between": "介于",
"and": "",
"and": "",
"rate": "速度",
"msgper": "信息 每",
"dropmsg": "不传输中间信息",
@@ -245,14 +255,14 @@
"singular": "小时"
},
"day": {
"plural" : "",
"singular": ""
"plural" : "",
"singular": ""
}
}
},
"error": {
"buffer": "缓冲超过 1000 条信息",
"buffer1": "缓冲超过 10000 条信息"
"buffer": "缓冲超过 1000 条信息",
"buffer1": "缓冲超过 10000 条信息"
}
},
"trigger": {
@@ -267,9 +277,9 @@
"latest": "最新信息对象",
"nothing": "无"
},
"wait-reset": "等待重置",
"wait-reset": "等待重置",
"wait-for": "等待",
"wait-loop": "重发",
"wait-loop": "周期性重发",
"duration": {
"ms": "毫秒",
"s": "秒",
@@ -279,11 +289,11 @@
"extend": " 如有新信息,延长延迟",
"label": {
"trigger": "触发",
"trigger-block": "发并阻止",
"trigger-loop": "重发",
"trigger-block": "发并阻止",
"trigger-loop": "周期性重发",
"reset": "重置触发节点条件 如果:",
"resetMessage":"msg.reset 已设置",
"resetPayload":"msg.payload 等于",
"resetMessage":"msg.reset已设置",
"resetPayload":"msg.payload等于",
"resetprompt": "可选填"
}
},
@@ -292,7 +302,7 @@
"title": "标题",
"body": "主体"
},
"tip": "提示: 主题内容可以添加格式化为 <a href=\"https://help.github.com/articles/markdown-basics/\" target=\"_blank\">Github 风格 Markdown</a>"
"tip": "提示: 主题内容可格式化为 <a href=\"https://help.github.com/articles/markdown-basics/\" target=\"_blank\">Github风格Markdown</a>"
},
"unknown": {
"label": {
@@ -307,24 +317,24 @@
"qos": "QoS",
"clientid": "客户端ID",
"port": "端口",
"keepalive": "存货定时器(秒)",
"keepalive": "Keepalive计时(秒)",
"cleansession": "使用新的会话",
"use-tls": "使用安全连接 (SSL/TLS)",
"tls-config":"TLS 设置",
"verify-server-cert":"验证服务器证书",
"compatmode": "使用旧式 MQTT 3.1 支持"
"compatmode": "使用旧式MQTT 3.1支持"
},
"tabs-label": {
"connection": "连接",
"security": "安全",
"will": "终结信息",
"birth": "初始信息"
"will": "Will信息",
"birth": "Birth信息"
},
"placeholder": {
"clientid": "留空白将会自动生成",
"clientid": "留白则自动生成",
"clientid-nonclean":"如非新会话必须设置客户端ID",
"will-topic": "留白将禁止终止信息",
"birth-topic": "留白将禁止初始信息"
"will-topic": "留白将禁止Will信息",
"birth-topic": "留白将禁止Birth信息"
},
"state": {
"connected": "已连接到服务端: __broker__",
@@ -334,7 +344,7 @@
"retain": "保留",
"true": "是",
"false": "否",
"tip": "提示: 如果你想用msg属性来设置主题qos 或者是否保存,请将这几个区域留空",
"tip": "提示: 若希望通过msg属性对topic(信息), qos及retain(保留)进行设置, 则将上述项留白",
"errors": {
"not-defined": "主题未设置",
"missing-config": "未设置服务端",
@@ -344,13 +354,13 @@
},
"httpin": {
"label": {
"method": "方法",
"method": "请求方式",
"url": "URL",
"doc": "文档",
"return": "返回",
"upload": "接受文件上传?",
"status": "状态码",
"headers": "头子段",
"headers": "Header",
"other": "其他"
},
"setby": "- 用 msg.method 设定 -",
@@ -358,21 +368,21 @@
"use-tls": "使用安全连接 (SSL/TLS) ",
"tls-config":"TLS 设置",
"utf8": "UTF-8 字符串",
"binary": "二进制缓冲模块",
"json": "解析JSON对象",
"binary": "二进制数据",
"json": "JSON对象",
"tip": {
"in": "相对URL",
"res": "发送到此节点的消息<b>必须</b>来自 <i>http input</i> 节点",
"res": "发送到此节点的消息<b>必须</b>来自<i>http input</i>节点",
"req": "提示如果JSON解析失败则获取的字符串将按原样返回."
},
"httpreq": "http 请求",
"errors": {
"not-created": "当httpNodeRoot为否时无法创建 http-in 节点",
"not-created": "当httpNodeRoot为否时无法创建http-in节点",
"missing-path": "无路径",
"no-response": "无响应对象",
"json-error": "JSON 解析错误",
"no-url": "未设定 URL",
"deprecated-call":"__method__ 方法已弃用",
"deprecated-call":"__method__方法已弃用",
"invalid-transport":"非HTTP传输请求"
},
"status": {
@@ -387,13 +397,14 @@
},
"listenon": "监听",
"connectto": "连接",
"payload": "发送/接受 有效载荷",
"message": "发送/接受 完整信息",
"sendrec": "发送/接受",
"payload": "有效载荷",
"message": "完整信息",
"tip": {
"path1": "默认情况下,<code> payload </code>将包含要发送或从Websocket接收的数据。侦听器可以配置为以JSON格式的字符串发送或接收整个消息对象.",
"path1": "默认情况下,<code>payload</code>将包含要发送或从Websocket接收的数据。侦听器可以配置为以JSON格式的字符串发送或接收整个消息对象.",
"path2": "这条路径将相对于 ",
"url1": "URL 应该使用 ws:&#47;&#47; 或者 wss:&#47;&#47; 方案并指向现有的websocket侦听器.",
"url2": "默认情况下,<code> payload </code>将包含要发送或从Websocket接收的数据。可以将客户端配置为以JSON格式的字符串发送或接收整个消息对象."
"url1": "URL 应该使用ws:&#47;&#47;或者wss:&#47;&#47;方案并指向现有的websocket侦听器.",
"url2": "默认情况下,<code>payload</code> 将包含要发送或从Websocket接收的数据。可以将客户端配置为以JSON格式的字符串发送或接收整个消息对象."
},
"errors": {
"connect-error": "ws连接发生了错误: ",
@@ -403,8 +414,8 @@
},
"watch": {
"label": {
"files": "文件(s)",
"recursive": "递归查看文件夹"
"files": "文件",
"recursive": "递归所有子文件夹"
},
"placeholder": {
"files": "逗号分开文件或文件夹"
@@ -416,8 +427,8 @@
"type": "类型",
"output": "输出",
"port": "端口",
"host": "主服务器",
"payload": "有效载荷(s)",
"host": "主机地址",
"payload": "有效载荷",
"delimited": "分隔符号",
"close-connection": "是否在成功发送每条信息后断开连接?",
"decode-base64": "用 Base64 解码信息?",
@@ -429,19 +440,19 @@
"type": {
"listen": "监听",
"connect": "连接",
"reply": "回应到 TCP"
"reply": "响应 TCP"
},
"output": {
"stream": "字串流",
"single": "单一",
"buffer": "缓冲模块",
"buffer": "Buffer",
"string": "字符串",
"base64": "Base64 字符串"
},
"return": {
"timeout": "在固定时间超时后",
"character": "当收到某个字符",
"number": "固定数目的字符",
"timeout": "定时间后",
"character": "当收到某个字符",
"number": "指定字符",
"never": "永不 - 保持连接",
"immed": "马上 - 不需要等待回复"
},
@@ -452,8 +463,8 @@
"stopped-listening": "已停止监听端口",
"connection-from": "连接来自 __host__:__port__",
"connection-closed": "连接已关闭 __host__:__port__",
"connections": "__count__ 连接",
"connections_plural": "__count__ 连接"
"connections": "__count__ 连接",
"connections_plural": "__count__ 连接"
},
"errors": {
@@ -463,7 +474,7 @@
"error": "错误: __error__",
"socket-error": "套接字连接错误来自 __host__:__port__",
"no-host": "主服务器和/或者端口未设定",
"no-host": "主机地址或端口未设定",
"connect-timeout": "连接超时",
"connect-fail": "连接失败"
}
@@ -476,23 +487,23 @@
"output": "输出",
"group": "组",
"interface": "本地IP",
"interfaceprompt": "(可选)本地 IP 绑定到",
"interfaceprompt": "(可选)本地 IP 绑定到",
"send": "发送一个",
"toport": "到端口",
"address": "地址",
"decode-base64": "是否解码编码为Base64的信息?"
"decode-base64": "是否解码Base64编码的信息?"
},
"placeholder": {
"interface": "(可选eth0 的 ip 地址",
"address": "目的地 ip 地址"
"interface": "可选eth0的IP地址",
"address": "目标IP地址"
},
"udpmsgs": "udp 信息",
"udpmsgs": "udp信息",
"mcmsgs": "组播信息",
"udpmsg": "udp 信息",
"udpmsg": "udp信息",
"bcmsg": "广播信息",
"mcmsg": "组播信息",
"output": {
"buffer": "缓冲模块",
"buffer": "Buffer",
"string": "字符串",
"base64": "Base64编码字符串"
},
@@ -503,8 +514,8 @@
},
"tip": {
"in": "提示:确保您的防火墙将允许数据进入",
"out": "提示:如果要使用<code> msg.ip </code>和<code> msg.port </code>设置,请将地址和端口留空",
"port": "端口已在使用: "
"out": "提示:如果要使用<code>msg.ip</code>和<code>msg.port</code>设置,请将地址和端口留空",
"port": "在使用端口: "
},
"status": {
"listener-at": "udp 监听器正在监听 __host__:__port__",
@@ -520,66 +531,72 @@
"access-error": "UDP 访问错误, 你可能需要root权限才能接入1024以下的端口",
"error": "错误: __error__",
"bad-mcaddress": "无效的组播地址",
"interface": "必须是需要接口的 ip 地址",
"ip-notset": "udp: ip 地址未设定",
"interface": "必须是指定接口的IP地址",
"ip-notset": "udp: IP地址未设定",
"port-notset": "udp: 端口未设定",
"port-invalid": "udp: 无效端口号码",
"alreadyused": "udp: 端口已经在使用"
"alreadyused": "udp: 端口已被占用"
}
},
"switch": {
"label": {
"property": "属性",
"rule": "规"
"rule": "规",
"repair" : "重建信息队列"
},
"and": "",
"and": "",
"checkall": "全选所有规则",
"stopfirst": "接受第一条匹配信息后停止",
"ignorecase": "忽大小写",
"ignorecase": "忽大小写",
"rules": {
"btwn":"在之间",
"cont":"包含",
"regex":"匹配正则表达式",
"true":"为真",
"false":"为假",
"null":"为空",
"nnull":"非空",
"null":"为空",
"nnull":"非空",
"head":"head",
"tail":"tail",
"index":"index between",
"exp":"JSONata表达式",
"else":"除此以外"
},
"errors": {
"invalid-expr": "无效 JSONata 表达: __error__"
"invalid-expr": "无效JSONata表达: __error__",
"too-many" : "Switch节点中有太多待定信息"
}
},
"change": {
"label": {
"rules": "规",
"rule": "规",
"rules": "规",
"rule": "规",
"set": "设定 __property__",
"change": "改 __property__",
"change": "改 __property__",
"delete": "删除 __property__",
"move": "移动 __property__",
"changeCount": "改: __count__ 条规矩",
"regex": "用正则表达式"
"changeCount": "改: __count__条规矩",
"regex": "使用正则表达式"
},
"action": {
"set": "设定",
"change": "改",
"change": "改",
"delete": "删除",
"move": "转移",
"to": "到",
"search": "搜索",
"replace": "更改为"
"replace": "替代为"
},
"errors": {
"invalid-from": "无效来源 from 属性: __error__",
"invalid-json": "无效 to 属性",
"invalid-expr": "无效 JSONata 表示: __error__"
"invalid-from": "无效的'from'属性: __error__",
"invalid-json": "无效的'to'JSON 属性",
"invalid-expr": "无效JSONata表达式: __error__"
}
},
"range": {
"label": {
"action": "行为作用",
"inputrange": "映射输入数据范围",
"action": "操作",
"inputrange": "映射输入数据",
"resultrange": "至目标范围",
"from": "从",
"to": "到",
@@ -591,26 +608,28 @@
"maxout": "e.g. 255"
},
"scale": {
"payload": "按比例 msg.payload",
"payload": "按比例msg.payload",
"limit": "按比例并设定界限至目标范围",
"wrap": "按比例并包含在目标范围内"
},
"tip": "提示: 此节点仅对数字有效",
"errors": {
"notnumber": "不是一个数"
"notnumber": "不是一个数"
}
},
"csv": {
"label": {
"columns": "列",
"separator": "分隔符",
"c2o": "CSV 至对象选项",
"o2c": "对象至 to CSV 选项",
"separator": "分隔符",
"c2o": "CSV至对象",
"o2c": "对象至CSV",
"input": "输入",
"skip-s": "忽略前",
"skip-e": "行",
"firstrow": "第一行包含列名",
"output": "输出",
"includerow": "包含列名行",
"newline": "新的一行"
"newline": "换行符"
},
"placeholder": {
"columns": "用逗号分割列名"
@@ -625,8 +644,8 @@
"other": "其他..."
},
"output": {
"row": "每行包含一条信息",
"array": "一条单独信息 [数组]"
"row": "每行一条信息",
"array": "一条信息 [数组]"
},
"newline": {
"linux": "Linux (\\n)",
@@ -634,8 +653,8 @@
"windows": "Windows (\\r\\n)"
},
"errors": {
"csv_js": "此节点仅处理 CSV 字符串或 js 对象",
"obj_csv": "对象 -> CSV 转换未设定列模版"
"csv_js": "此节点仅处理CSV字符串或JS对象",
"obj_csv": "对象->CSV转换未设定列模版"
}
},
"html": {
@@ -644,13 +663,13 @@
"output": "输出"
},
"output": {
"html": "选定元素的 html 内容",
"html": "选定元素的html内容",
"text": "选定元素的纯文本内容",
"attr": "选定元素的所有属性对象"
"attr": "包含选定元素的所有属性对象"
},
"format": {
"single": "由一个单独信息包含一个数组",
"multi": "多条信息,每一条包含一个元素"
"single": "一条信息 [数组]",
"multi": "多条信息,每一个元素"
}
},
"json": {
@@ -660,8 +679,15 @@
"dropped-error": "转换有效负载失败"
},
"label": {
"o2j": "对象至 JSON 选项",
"pretty": "格式化 JSON 字符串"
"o2j": "对象至JSON",
"pretty": "格式化JSON字符串",
"action": "操作",
"property": "属性",
"actions": {
"toggle": "JSON字符串与对象互转",
"str":"总是转为JSON字符串",
"obj":"总是转为JS对象"
}
}
},
"yaml": {
@@ -687,14 +713,14 @@
"gpiopin": "GPIO",
"selectpin": "选择引脚",
"resistor": "电阻?",
"readinitial": "在部署/重新启动时读取引脚的初始状态?",
"readinitial": "在部署/重时读取引脚的初始状态?",
"type": "类型",
"initpin": "初始化引脚状态?",
"debounce": "去抖动",
"freq": "频率",
"button": "按钮",
"pimouse": "Pi 鼠标",
"pikeyboard": "Pi 键盘",
"pimouse": "Pi鼠标",
"pikeyboard": "Pi键盘",
"left": "左",
"right": "右",
"middle": "中"
@@ -705,21 +731,21 @@
"pulldown": "下拉电阻"
},
"digout": "数字输出",
"pwmout": "PWM 输出",
"pwmout": "PWM输出",
"servo": "伺服输出",
"initpin0": "初始引脚电平 - 低 (0)",
"initpin1": "初始引脚电平 - 高 (1)",
"initpin0": "初始引脚电平 - 低(0)",
"initpin1": "初始引脚电平 - 高(1)",
"left": "左",
"right": "右",
"middle": "中",
"any": "任何",
"pinname": "引脚",
"alreadyuse": "已经在用",
"alreadyset": "已经设定为",
"alreadyuse": "已被使用",
"alreadyset": "已被设为",
"tip": {
"pin": "<b>引脚在使用</b>: ",
"in": "提示: 仅接受数字输入 - 输出必须为 0 或 1.",
"dig": "提示: 如用数字输出 - 输入必须为 0 或 1.",
"pin": "<b>在使用引脚</b>: ",
"in": "提示: 仅接受数字输入 - 输出必须为0或1.",
"dig": "提示: 如用数字输出 - 输入必须为0或1.",
"pwm": "提示: 如用PWM输出 - 输入必须为0至100之间; 如用高频率可能会比预期占用更多CPU资源.",
"ser": "<b>提示</b>: 如用伺服输出 - 输入必须为0至100之间. 50为中间值."
},
@@ -728,7 +754,7 @@
"input": "输入",
"pullup": "含有上拉电阻的输入",
"pulldown": "含有下拉电阻的输入",
"pwmout": "PWM 输出",
"pwmout": "PWM输出",
"servo": "伺服输出"
},
"status": {
@@ -740,27 +766,27 @@
"ignorenode": "忽略树莓派的特定节点",
"version": "版本命令失败",
"sawpitype": "查看Pi类型",
"libnotfound": "找不到树莓派 RPi.GPIO python库",
"alreadyset": "GPIO 引脚 __pin__ 已经被设定为类型: __type__",
"invalidpin": "无效 GPIO 引脚",
"libnotfound": "找不到树莓派RPi.GPIOpython库",
"alreadyset": "GPIO引脚 __pin__ 已经被设定为类型: __type__",
"invalidpin": "无效GPIO引脚",
"invalidinput": "无效输入",
"needtobeexecutable": "__command__ 需要为可运行命令",
"mustbeexecutable": "nrgpio 需要为可运行",
"commandnotfound": "nrgpio 命令不存在",
"commandnotexecutable": "nrgpio 命令无法运行",
"needtobeexecutable": "__command__为可运行命令",
"mustbeexecutable": "nrgpio为可运行",
"commandnotfound": "nrgpio命令不存在",
"commandnotexecutable": "nrgpio命令不可运行",
"error": "错误: __error__",
"pythoncommandnotfound": "nrpgio python 命令不运行"
"pythoncommandnotfound": "nrpgio python命令未处于运行状态"
}
},
"tail": {
"label": {
"filename": "文件名",
"type": "文件类型",
"splitlines": "拆分线 \\n?"
"splitlines": "以\\n来拆分行?"
},
"action": {
"text": "文本 - 返回字符串",
"binary": "二进制 - 返回缓冲区"
"binary": "二进制 - 返回Buffer"
},
"errors": {
"windowsnotsupport": "Windows目前不支持."
@@ -770,7 +796,7 @@
"label": {
"filename": "文件名",
"action": "行为",
"addnewline": "向每个有效载荷添加换行符(\\ n?",
"addnewline": "向每个有效载荷添加换行符(\\n?",
"createdir": "创建目录(如果不存在)?",
"outputas": "输出",
"breakchunks": "分拆成块",
@@ -781,14 +807,14 @@
},
"action": {
"append": "追加至文件",
"overwrite": "写文件",
"overwrite": "写文件",
"delete": "删除文件"
},
"output": {
"utf8": "一条单独 utf8 字符串",
"buffer": "一条单独缓冲区对象",
"utf8": "一utf8字符串",
"buffer": "一个Buffer对象",
"lines": "每行一条信息",
"stream": "缓冲区流"
"stream": "一个Buffer流"
},
"status": {
"wrotefile": "写入至文件: __file__",
@@ -806,41 +832,99 @@
"tip": "提示: 文件名应该是绝对路径否则它将相对于Node-RED进程的工作目录。"
},
"split": {
"intro":"分<code>msg.payload</code> 基于类型:",
"intro":"基于以下类型拆分<code>msg.payload</code>:",
"object":"<b>对象</b>",
"objectSend":"发送每个键/值对的消息",
"strBuff":"<b>字符串</b> / <b>缓冲区</b>",
"objectSend":"每个键值对作为单个消息发送",
"strBuff":"<b>字符串</b> / <b>Buffer</b>",
"array":"<b>数组</b>",
"splitUsing":"拆分使用",
"splitLength":"固定长度",
"stream":"处理为消息流",
"stream":"为消息流处理",
"addname":" 复制键到 "
},
"join":{
"mode":{
"mode":"模式",
"auto":"自动",
"merge":"合并序列",
"reduce":"缩减序列",
"custom":"手动"
},
"combine":"结合每一个",
"create":"创建输出",
"combine":"合并每个",
"create":"输出",
"type":{
"string":"字符串",
"array":"数组",
"buffer":"缓冲区",
"object":"键/值对象",
"buffer":"Buffer",
"object":"键值对象",
"merged":"合并对象"
},
"using":"使用值",
"key":"作键",
"using":"使用值",
"key":"作键",
"joinedUsing":"合并符号",
"send":"发送信息:",
"afterCount":"达到一定数的信息部件时",
"count":"数",
"afterCount":"达到一定数的信息时",
"count":"数",
"subsequent":"和每个后续的消息",
"afterTimeout":"第一条消息的超时后",
"afterTimeout":"第一条消息的若干时间后",
"seconds":"秒",
"complete":"在使用<code> msg.complete </ code>属性设置的消息后",
"tip":"此模式假定此节点与 <i>split</i> 或者接收到的消息将具有正确配置的 <code>msg.parts</code> 属性."
"complete":"在收到存在<code>msg.complete</code>的消息后",
"tip":"此模式假定此节点与<i>split</i>相连, 或者接收到的消息有正确配置的<code>msg.parts</code>属性.",
"too-many" : "join节点中有太多待定信息",
"merge": {
"topics-label":"合并主题",
"topics":"主题",
"topic" : "主题",
"on-change":"当收到一个新主题时发送已合并信息"
},
"reduce": {
"exp": "Reduce表达式",
"exp-value": "exp",
"init": "初始值",
"right": "反向求值(从后往前)",
"fixup": "Fix-up exp"
},
"errors": {
"invalid-expr": "无效的JSONata表达式: __error__"
}
},
"sort" : {
"target" : "排序属性",
"seq" : "信息队列",
"key" : "键值",
"elem" : "元素值",
"order" : "顺序",
"ascending" : "升序",
"descending" : "降序",
"as-number" : "作为数值",
"invalid-exp" : "sort节点中存在无效的JSONata表达式",
"too-many" : "sort节点中有太多待定信息",
"clear" : "清空sort节点中的待定信息"
},
"batch" : {
"mode": {
"label" : "模式",
"num-msgs" : "按指定数量分组",
"interval" : "按时间间隔分组",
"concat" : "按主题分组"
},
"count": {
"label" : "分组数量",
"overlap" : "队末队首重叠数量",
"count" : "数量",
"invalid" : "无效的分组数量或重叠数量"
},
"interval": {
"label" : "时间间隔",
"seconds" : "秒",
"empty" : "无数据到达时发送空信息"
},
"concat": {
"topics-label": "主题",
"topic" : "主题"
},
"too-many" : "batch节点中有太多待定信息",
"unexpected" : "未知模式",
"no-parts" : "信息中没有parts属性"
}
}

View File

@@ -140,7 +140,7 @@
},
icon: "switch.png",
label: function() {
return this.name||"switch";
return this.name||this._("swicth.switch");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
@@ -199,7 +199,7 @@
rule.t = 'eq';
}
if (!opt.hasOwnProperty('i')) {
opt._i = Math.floor((0x99999-0x10000)*Math.random()).toString(16);
opt._i = Math.floor((0x99999-0x10000)*Math.random()).toString();
}
container.css({
overflow: 'hidden',

View File

@@ -76,7 +76,8 @@
outputs: 1,
icon: "range.png",
label: function() {
return this.name || "range";
if (this.minout !== "" && this.maxout !== "") { return this.name||this.minout + " - " + this.maxout; }
else { return this.name||this._("range.range"); }
},
labelStyle: function() {
return this.name ? "node_label_italic" : "";

View File

@@ -112,7 +112,7 @@
outputs:1,
icon: "split.png",
label: function() {
return this.name||"split";
return this.name||this._("split.split");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
@@ -367,7 +367,7 @@
outputs:1,
icon: "join.png",
label: function() {
return this.name||"join";
return this.name||this._("join.join");
},
labelStyle: function() {
return this.name?"node_label_italic":"";

View File

@@ -103,7 +103,7 @@
outputs:1,
icon: "sort.png",
label: function() {
return this.name || "sort";
return this.name||this._("sort.sort");
},
labelStyle: function() {
return this.name ? "node_label_italic" : "";

View File

@@ -110,7 +110,7 @@
outputs:1,
icon: "batch.png",
label: function() {
return this.name || "batch";
return this.name||this._("batch.batch");;
},
labelStyle: function() {
return this.name ? "node_label_italic" : "";

View File

@@ -46,7 +46,7 @@
outputs:1,
icon: "file.png",
label: function() {
return this.name||this.filename||"tail";
return this.name||this.filename||this._("tail.tail");
},
labelStyle: function() {
return this.name?"node_label_italic":"";

26
red.js
View File

@@ -31,8 +31,10 @@ var app = express();
var settingsFile;
var flowFile;
var readonly;
var knownOpts = {
"credentialSecret": String,
"help": Boolean,
"port": Number,
"settings": [path],
@@ -41,6 +43,7 @@ var knownOpts = {
"verbose": Boolean
};
var shortHands = {
"k":["--credentialSecret"],
"?":["--help"],
"p":["--port"],
"s":["--settings"],
@@ -59,9 +62,11 @@ var parsedArgs = nopt(knownOpts,shortHands,process.argv,2)
if (parsedArgs.help) {
console.log("Node-RED v"+RED.version());
console.log("Usage: node-red [-v] [-?] [--settings settings.js] [--userDir DIR]");
console.log(" [--port PORT] [--title TITLE] [flows.json]");
console.log(" [--port PORT] [--credentialSecret SECRET_KEY]");
console.log(" [--title TITLE] [flows.json]");
console.log("");
console.log("Options:");
console.log(" -k, --credentialSecret SECRET_KEY key to unlock credentials file");
console.log(" -p, --port PORT port to listen on");
console.log(" -s, --settings FILE use specified settings file");
console.log(" --title TITLE process window title");
@@ -101,8 +106,15 @@ if (parsedArgs.settings) {
var settingsStat = fs.statSync(defaultSettings);
if (settingsStat.mtime.getTime() <= settingsStat.ctime.getTime()) {
// Default settings file has not been modified - safe to copy
fs.copySync(defaultSettings,userSettingsFile);
settingsFile = userSettingsFile;
try {
fs.copySync(defaultSettings,userSettingsFile);
settingsFile = userSettingsFile;
}
catch (err) {
console.log("Can't copy settings file.");
settingsFile = defaultSettings;
readonly = true;
}
} else {
// Use default settings.js as it has been modified
settingsFile = defaultSettings;
@@ -114,6 +126,10 @@ if (parsedArgs.settings) {
try {
var settings = require(settingsFile);
settings.settingsFile = settingsFile;
if (readonly === true) {
console.log("Setting to read Only mode.");
settings.readOnly = true;
}
} catch(err) {
console.log("Error loading settings file: "+settingsFile)
if (err.code == 'MODULE_NOT_FOUND') {
@@ -126,6 +142,10 @@ try {
process.exit();
}
if (parsedArgs.credentialSecret) {
settings.credentialSecret = parsedArgs.credentialSecret;
}
if (parsedArgs.verbose) {
settings.verbose = true;
}

View File

@@ -16,11 +16,9 @@
var log;
var redNodes;
var settings;
module.exports = {
init: function(runtime) {
settings = runtime.settings;
redNodes = runtime.nodes;
log = runtime.log;
},

View File

@@ -16,11 +16,9 @@
var log;
var redNodes;
var settings;
module.exports = {
init: function(runtime) {
settings = runtime.settings;
redNodes = runtime.nodes;
log = runtime.log;
},

View File

@@ -18,17 +18,13 @@ var when = require("when");
var apiUtils = require("../util");
var redNodes;
var log;
var i18n;
var settings;
var events;
module.exports = {
init: function(runtime) {
redNodes = runtime.nodes;
log = runtime.log;
i18n = runtime.i18n;
settings = runtime.settings;
events = runtime.events;
},
getAll: function(req,res) {
if (req.get("accept") == "application/json") {
@@ -72,11 +68,6 @@ module.exports = {
return;
}
promise.then(function(info) {
if (isUpgrade) {
events.emit("runtime-event",{id:"node/upgraded",retain:false,payload:{module:node.module,version:node.version}});
} else {
events.emit("runtime-event",{id:"node/added",retain:false,payload:info.nodes});
}
if (node.module) {
log.audit({event: "nodes.install",module:node.module,version:node.version},req);
res.json(info);
@@ -114,7 +105,6 @@ module.exports = {
}
promise.then(function(list) {
events.emit("runtime-event",{id:"node/removed",retain:false,payload:list});
log.audit({event: "nodes.remove",module:mod},req);
res.status(204).end();
}).catch(function(err) {
@@ -248,21 +238,6 @@ function putNode(node, enabled) {
} else {
promise = redNodes.disableNode(node.id);
}
return promise.then(function(info) {
if (info.enabled === enabled && !info.err) {
events.emit("runtime-event",{id:"node/"+(enabled?"enabled":"disabled"),retain:false,payload:info});
log.info(" "+log._("api.nodes."+(enabled?"enabled":"disabled")));
for (var i=0;i<info.types.length;i++) {
log.info(" - "+info.types[i]);
}
} else if (enabled && info.err) {
log.warn(log._("api.nodes.error-enable"));
log.warn(" - "+info.name+" : "+info.err);
}
return info;
});
}
return promise;
}

View File

@@ -239,7 +239,7 @@
"managePalette": "管理面板"
},
"library": {
"openLibrary": "打开库...",
"openLibrary": "打开库...",
"saveToLibrary": "保存到库...",
"typeLibrary": "__type__类型库",
"unnamedType": "无名__type__",

View File

@@ -194,5 +194,25 @@
"$globalContext": {
"args": "string",
"desc": "获取全局上下文的属性。"
},
"$pad": {
"args": "string, width [, char]",
"desc": "根据需要,向字符串`string`的副本中填充文字使该字符串的字数达到`width`的绝对值并返回填充文字后的字符串。\n\n如果`width`的值为正,则向字符串`string`的右侧填充文字,如果`width`为负,则向字符串`string`的左侧填充文字。\n\n可选参数`char`用来指定填充的文字。如果未指定该参数,则填充空白文字。"
},
"$fromMillis": {
"args": "number",
"desc": "将表示从UNIX时间 (1970年1月1日 UTC/GMT的午夜开始到现在的毫秒数的数值转换成ISO 8601形式时间戳的字符串。"
},
"$formatNumber": {
"args": "number, picture [, options]",
"desc": "将`number`转换成具有`picture`所指定的数值格式的字符串。\n\n此函数的功能与XPath F&O 3.1规格中定义的XPath/XQuery函数的fn:format-number功能相一致。参数`picture`用于指定数值的转换格式其语法与fn:format-number中的定义一致。\n\n可选的第三参数`options`用来覆盖默认的局部环境格式如小数点分隔符。如果指定该参数那么该参数必须是包含name/value对的对象并且name/value对必须符合XPath F&O 3.1规格中记述的数值格式。"
},
"$formatBase": {
"args": "number [, radix]",
"desc": "将`number`变换为以参数`radix`的值为基数形式的字符串。如果不指定`radix`的值则默认基数为10。指定的`radix`值必须在236之间否则抛出错误。"
},
"$toMillis": {
"args": "timestamp",
"desc": "将ISO 8601格式的字符串`timestamp`转换为从UNIX时间 (1970年1月1日 UTC/GMT的午夜开始到现在的毫秒数。如果该字符串的格式不正确则抛出错误。"
}
}

View File

@@ -16,13 +16,11 @@
var express = require("express");
var runtime;
var settings;
var needsPermission = require("../../auth").needsPermission;
module.exports = {
init: function(_runtime) {
runtime = _runtime;
settings = runtime.settings;
},
app: function() {
var app = express();

View File

@@ -17,7 +17,6 @@
var express = require("express");
var os = require("os");
var runtime;
var settings;
var needsPermission = require("../auth").needsPermission;
function getUsername(userObj) {
@@ -31,7 +30,6 @@ function getUsername(userObj) {
module.exports = {
init: function(_runtime) {
runtime = _runtime;
settings = runtime.settings;
},
app: function() {
var app = express();

View File

@@ -24,8 +24,6 @@ var cors = require('cors');
var auth = require("./auth");
var apiUtil = require("./util");
var i18n;
var log;
var adminApp;
var server;
var runtime;
@@ -35,8 +33,6 @@ function init(_server,_runtime) {
server = _server;
runtime = _runtime;
var settings = runtime.settings;
i18n = runtime.i18n;
log = runtime.log;
if (settings.httpAdminRoot !== false) {
apiUtil.init(runtime);
adminApp = express();

View File

@@ -83,7 +83,8 @@
"credentials": {
"error":"Error loading credentials: __message__",
"error-saving":"Error saving credentials: __message__",
"not-registered": "Credential type '__type__' is not registered"
"not-registered": "Credential type '__type__' is not registered",
"system-key-warning": "\n\n---------------------------------------------------------------------\nYour flow credentials file is encrypted using a system-generated key.\n\nIf the system-generated key is lost for any reason, your credentials\nfile will not be recoverable, you will have to delete it and re-enter\nyour credentials.\n\nYou should set your own key using the 'credentialSecret' option in\nyour settings file. Node-RED will then re-encrypt your credentials\nfile using your chosen key the next time you deploy a change.\n---------------------------------------------------------------------\n"
},
"flows": {
"registered-missing": "Missing type registered: __type__",

View File

@@ -201,6 +201,9 @@ var api = module.exports = {
encryptedCredentials = credentials;
}
log.debug("red/runtime/nodes/credentials.load : keyType="+encryptionKeyType);
if (encryptionKeyType === 'system') {
log.warn(log._("nodes.credentials.system-key-warning"));
}
return setupEncryptionPromise.then(function() {
var clearInvalidFlag = false;
if (credentials.hasOwnProperty("$")) {

View File

@@ -104,7 +104,43 @@ function init(runtime) {
function disableNode(id) {
flows.checkTypeInUse(id);
return registry.disableNode(id);
return registry.disableNode(id).then(function(info) {
reportNodeStateChange(info,false);
return info;
});
}
function enableNode(id) {
return registry.enableNode(id).then(function(info) {
reportNodeStateChange(info,true);
return info;
});
}
function reportNodeStateChange(info,enabled) {
if (info.enabled === enabled && !info.err) {
events.emit("runtime-event",{id:"node/"+(enabled?"enabled":"disabled"),retain:false,payload:info});
log.info(" "+log._("api.nodes."+(enabled?"enabled":"disabled")));
for (var i=0;i<info.types.length;i++) {
log.info(" - "+info.types[i]);
}
} else if (enabled && info.err) {
log.warn(log._("api.nodes.error-enable"));
log.warn(" - "+info.name+" : "+info.err);
}
}
function installModule(module,version) {
var ex_module = registry.getModuleInfo(module);
var isUpgrade = !!ex_module;
return registry.installModule(module,version).then(function(info) {
if (isUpgrade) {
events.emit("runtime-event",{id:"node/upgraded",retain:false,payload:{module:module,version:version}});
} else {
events.emit("runtime-event",{id:"node/added",retain:false,payload:info.nodes});
}
return info;
});
}
function uninstallModule(module) {
@@ -115,7 +151,10 @@ function uninstallModule(module) {
for (var i=0;i<info.nodes.length;i++) {
flows.checkTypeInUse(module+"/"+info.nodes[i].name);
}
return registry.uninstallModule(module);
return registry.uninstallModule(module).then(function(list) {
events.emit("runtime-event",{id:"node/removed",retain:false,payload:list});
return list;
});
}
}
@@ -130,10 +169,10 @@ module.exports = {
eachNode: flows.eachNode,
paletteEditorEnabled: registry.paletteEditorEnabled,
installModule: registry.installModule,
installModule: installModule,
uninstallModule: uninstallModule,
enableNode: registry.enableNode,
enableNode: enableNode,
disableNode: disableNode,
// Node type registry

View File

@@ -376,9 +376,18 @@ function addModule(module) {
}
function loadNodeHelp(node,lang) {
var dir = path.dirname(node.template);
var base = path.basename(node.template);
var localePath = path.join(dir,"locales",lang,base);
var localePath = undefined;
if (node.module === 'node-red') {
var cat_dir = path.dirname(node.template);
var cat = path.basename(cat_dir);
var dir = path.dirname(cat_dir);
localePath = path.join(dir, "locales", lang, cat, base)
}
else {
var dir = path.dirname(node.template);
localePath = path.join(dir,"locales",lang,base);
}
try {
// TODO: make this async
var content = fs.readFileSync(localePath, "utf8")

View File

@@ -276,10 +276,12 @@ Project.prototype.update = function (user, data) {
saveREADME = true;
this.description = data.description;
}
if (data.hasOwnProperty('dependencies')) {
savePackage = true;
this.package.dependencies = data.dependencies;
}
if (data.hasOwnProperty('summary')) {
savePackage = true;
this.package.description = data.summary;
@@ -332,6 +334,7 @@ Project.prototype.update = function (user, data) {
if (data.files.hasOwnProperty('flow') && this.package['node-red'].settings.flowFile !== data.files.flow) {
this.paths.flowFile = data.files.flow;
this.package['node-red'].settings.flowFile = data.files.flow;
this.package.scripts = {"start":"node-red -u . " + data.files.flow};
savePackage = true;
flowFilesChanged = true;
}

View File

@@ -24,7 +24,8 @@ module.exports = {
"node-red": {
"settings": {
}
}
},
"scripts": {}
};
if (project.files) {
if (project.files.flow) {
@@ -43,5 +44,5 @@ module.exports = {
return content;
},
".gitignore": function() { return "*.backup" ;}
".gitignore": function() { return "*.backup\n.config.json\n" ;}
}

View File

@@ -514,11 +514,6 @@ function saveFlows(flows) {
}
flowsFileExists = true;
try {
fs.renameSync(flowsFullPath,flowsFileBackup);
} catch(err) {
}
var flowData;
@@ -527,7 +522,7 @@ function saveFlows(flows) {
} else {
flowData = JSON.stringify(flows);
}
return util.writeFile(flowsFullPath, flowData);
return util.writeFile(flowsFullPath, flowData, flowsFileBackup);
}
function getCredentials() {
@@ -539,17 +534,13 @@ function saveCredentials(credentials) {
return when.resolve();
}
try {
fs.renameSync(credentialsFile,credentialsFileBackup);
} catch(err) {
}
var credentialData;
if (settings.flowFilePretty) {
credentialData = JSON.stringify(credentials,null,4);
} else {
credentialData = JSON.stringify(credentials);
}
return util.writeFile(credentialsFile, credentialData);
return util.writeFile(credentialsFile, credentialData, credentialsFileBackup);
}
function getFlowFilename() {

View File

@@ -22,12 +22,14 @@ var log = require("../../log");
var util = require("./util");
var globalSettingsFile;
var globalSettingsBackup;
var settings;
module.exports = {
init: function(_settings) {
settings = _settings;
globalSettingsFile = fspath.join(settings.userDir,".config.json");
globalSettingsBackup = fspath.join(settings.userDir,".config.json.backup");
},
getSettings: function() {
return when.promise(function(resolve,reject) {
@@ -47,6 +49,6 @@ module.exports = {
if (settings.readOnly) {
return when.resolve();
}
return util.writeFile(globalSettingsFile,JSON.stringify(newSettings,null,1));
return util.writeFile(globalSettingsFile,JSON.stringify(newSettings,null,1),globalSettingsBackup);
}
}

View File

@@ -78,7 +78,16 @@ module.exports = {
* This forces a fsync before completing to ensure
* the write hits disk.
*/
writeFile: function(path,content) {
writeFile: function(path,content,backupPath) {
if (backupPath) {
try {
// console.log(path);
// console.log(backupPath);
fs.renameSync(path,backupPath);
} catch(err) {
console.log(err);
}
}
return when.promise(function(resolve,reject) {
var stream = fs.createWriteStream(path);
stream.on('open',function(fd) {

View File

@@ -204,7 +204,6 @@ module.exports = {
functionGlobalContext: {
// os:require('os'),
// octalbonescript:require('octalbonescript'),
// jfive:require("johnny-five"),
// j5board:require("johnny-five").Board({repl:false})
},

View File

@@ -527,6 +527,50 @@ describe('function node', function() {
}
});
});
it('should log a Debug Message', function (done) {
var flow = [{id: "n1", type: "function", wires: [["n2"]], func: "node.debug('test');"}];
helper.load(functionNode, flow, function () {
var n1 = helper.getNode("n1");
n1.receive({payload: "foo", topic: "bar"});
try {
helper.log().called.should.be.true();
var logEvents = helper.log().args.filter(function (evt) {
return evt[0].type == "function";
});
logEvents.should.have.length(1);
var msg = logEvents[0][0];
msg.should.have.property('level', helper.log().DEBUG);
msg.should.have.property('id', 'n1');
msg.should.have.property('type', 'function');
msg.should.have.property('msg', 'test');
done();
} catch (err) {
done(err);
}
});
});
it('should log a Trace Message', function (done) {
var flow = [{id: "n1", type: "function", wires: [["n2"]], func: "node.trace('test');"}];
helper.load(functionNode, flow, function () {
var n1 = helper.getNode("n1");
n1.receive({payload: "foo", topic: "bar"});
try {
helper.log().called.should.be.true();
var logEvents = helper.log().args.filter(function (evt) {
return evt[0].type == "function";
});
logEvents.should.have.length(1);
var msg = logEvents[0][0];
msg.should.have.property('level', helper.log().TRACE);
msg.should.have.property('id', 'n1');
msg.should.have.property('type', 'function');
msg.should.have.property('msg', 'test');
done();
} catch (err) {
done(err);
}
});
});
it('should log a Warning Message', function (done) {
var flow = [{id: "n1", type: "function", wires: [["n2"]], func: "node.warn('test');"}];
helper.load(functionNode, flow, function () {

View File

@@ -29,37 +29,42 @@ describe('template node', function() {
});
it('should modify payload', function(done) {
it('should modify payload using node-configured template', function(done) {
var flow = [{id:"n1", type:"template", field:"payload", template:"payload={{payload}}",wires:[["n2"]]},{id:"n2",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'payload=foo');
msg.should.have.property('template', '{{payload}}');
done();
try {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'payload=foo');
msg.should.have.property('template', 'this should be ignored as the node has its own template {{payload}}');
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo",topic: "bar", template: "{{payload}}"});
n1.receive({payload:"foo",topic: "bar", template: "this should be ignored as the node has its own template {{payload}}"});
});
});
it('should modify template from msg.template', function(done) {
var flow = [{id:"n1", type:"template", field:"template", template:"",wires:[["n2"]]},{id:"n2",type:"helper"}];
it('should modify the configured property using msg.template', function(done) {
var flow = [{id:"n1", type:"template", field:"randomProperty", template:"",wires:[["n2"]]},{id:"n2",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.should.have.property('payload', 'foo');
msg.should.have.property('template', 'payload=foo');
msg.should.have.property('template', 'payload={{payload}}');
msg.should.have.property('randomProperty', 'payload=foo');
done();
});
n1.receive({payload:"foo", topic: "bar", template: "payload={{payload}}"});
});
});
it('should modify payload from msg.template', function(done) {
it('should be able to overwrite msg.template using the template from msg.template', function(done) {
var flow = [{id:"n1", type:"template", field:"payload", template:"",wires:[["n2"]]},{id:"n2",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
@@ -74,6 +79,40 @@ describe('template node', function() {
});
});
it('should modify payload from msg.template', function(done) {
var flow = [{id:"n1", type:"template", field:"payload", template:"",wires:[["n2"]]},{id:"n2",type:"helper"}];
helper.load(templateNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var received = [];
n2.on("input", function(msg) {
try {
received.push(msg);
if (received.length === 3) {
received[0].should.have.property('topic', 'bar');
received[0].should.have.property('payload', 'topic=bar');
received[0].should.have.property('template', 'topic={{topic}}');
received[1].should.have.property('topic', 'another bar');
received[1].should.have.property('payload', 'topic=another bar');
received[1].should.have.property('template', 'topic={{topic}}');
received[2].should.have.property('topic', 'bar');
received[2].should.have.property('payload', 'payload=foo');
received[2].should.have.property('template', 'payload={{payload}}');
done();
}
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo", topic: "bar", template: "topic={{topic}}"});
n1.receive({payload:"foo", topic: "another bar", template: "topic={{topic}}"});
n1.receive({payload:"foo", topic: "bar", template: "payload={{payload}}"});
});
});
it('should modify payload from flow context', function(done) {
var flow = [{id:"n1",z:"t1", type:"template", field:"payload", template:"payload={{flow.value}}",wires:[["n2"]]},{id:"n2",z:"t1",type:"helper"}];
helper.load(templateNode, flow, function() {

View File

@@ -355,6 +355,34 @@ describe('trigger node', function() {
});
});
it('should be able to reset correctly having not output anything on second edge', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op2type:"nul", op1:"true",op1type:"val", op2:"false", duration:"35", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
var c = 0;
n2.on("input", function(msg) {
try {
msg.should.have.a.property("payload", true);
c += 1;
}
catch(err) { done(err); }
});
setTimeout( function() {
c.should.equal(3); // should only have had one output.
done();
},300);
n1.emit("input", {payload:1});
setTimeout( function() {
n1.emit("input", {payload:2});
},100);
setTimeout( function() {
n1.emit("input", {payload:3});
},200);
});
});
it('should be able to extend the delay', function(done) {
var spy = sinon.stub(RED.util, 'evaluateNodeProperty',
function(arg1, arg2, arg3, arg4) { return arg1; }
@@ -422,8 +450,8 @@ describe('trigger node', function() {
});
});
it('should be able to extend the delay and output the 2nd payload', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"true", op1type:"nul", op2type:"payl", op1:"false", op2:"true", duration:"50", wires:[["n2"]] },
it('should be able to extend the delay and output the most recent payload', function(done) {
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"true", op1type:"nul", op2type:"payl", op1:"false", op2:"true", duration:"60", wires:[["n2"]] },
{id:"n2", type:"helper"} ];
helper.load(triggerNode, flow, function() {
var n1 = helper.getNode("n1");
@@ -431,15 +459,9 @@ describe('trigger node', function() {
var c = 0;
n2.on("input", function(msg) {
try {
if (c === 0) {
msg.should.have.a.property("payload", "Goodbye");
c += 1;
}
else {
msg.should.have.a.property("payload", "World");
(Date.now() - ss).should.be.greaterThan(70);
done();
}
msg.should.have.a.property("payload", "World");
(Date.now() - ss).should.be.greaterThan(120);
done();
}
catch(err) { done(err); }
});
@@ -447,7 +469,7 @@ describe('trigger node', function() {
n1.emit("input", {payload:"Hello"});
setTimeout( function() {
n1.emit("input", {payload:"Goodbye"});
},20);
},40);
setTimeout( function() {
n1.emit("input", {payload:"World"});
},80);

View File

@@ -240,7 +240,7 @@ describe("red/nodes/index", function() {
}
});
sinon.stub(registry,"disableNode",function(id) {
return randomNodeInfo;
return when.resolve(randomNodeInfo);
});
});
afterEach(function() {
@@ -252,11 +252,12 @@ describe("red/nodes/index", function() {
index.init(runtime);
index.registerType('test-node-set','test', TestNode);
index.loadFlows().then(function() {
var info = index.disableNode("5678");
registry.disableNode.calledOnce.should.be.true();
registry.disableNode.calledWith("5678").should.be.true();
info.should.eql(randomNodeInfo);
done();
return index.disableNode("5678").then(function(info) {
registry.disableNode.calledOnce.should.be.true();
registry.disableNode.calledWith("5678").should.be.true();
info.should.eql(randomNodeInfo);
done();
});
}).otherwise(function(err) {
done(err);
});