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

Merge branch 'dev' into function-modules

This commit is contained in:
Nick O'Leary 2021-02-13 00:21:27 +00:00
commit 6336ab121e
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
122 changed files with 3732 additions and 545 deletions

View File

@ -1,3 +1,23 @@
### 1.2.8: Maintenance Release
Editor
- Ensure subflow help is picked up for palette tooltip Fixes #2834
- Improve Ru locale (#2826) @alexk111
- Fix scrollbars (#2825) @alexk111
Runtime
- Restrict project file access to inside the project directory
- Validate user-provided language parameter before passing to i18n
- Fix grunt release mkdir issue on Node.js 14 (#2827) @alexk111
- Prevent crash when coreNodesDir is empty (#2831) @hardillb
Nodes
- Batch node: Fixing minor typo in node's documentation (#2848) @matthiasradde
- Split node: Handle out of order messages as long as one of the messages has msg.parts.count set to the proper value (#2748) @s4ke
### 1.2.7: Maintenance Release
Editor

View File

@ -142,6 +142,7 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/js/text/bidi.js",
"packages/node_modules/@node-red/editor-client/src/js/text/format.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/state.js",
"packages/node_modules/@node-red/editor-client/src/js/plugins.js",
"packages/node_modules/@node-red/editor-client/src/js/nodes.js",
"packages/node_modules/@node-red/editor-client/src/js/font-awesome.js",
"packages/node_modules/@node-red/editor-client/src/js/history.js",

View File

@ -96,7 +96,7 @@
"grunt-jsdoc": "2.4.1",
"grunt-jsdoc-to-markdown": "5.0.0",
"grunt-jsonlint": "2.1.3",
"grunt-mkdir": "~1.0.0",
"grunt-mkdir": "~1.1.0",
"grunt-npm-command": "~0.1.2",
"grunt-sass": "~3.1.0",
"grunt-simple-mocha": "~0.4.1",

View File

@ -22,6 +22,7 @@ var flow = require("./flow");
var context = require("./context");
var auth = require("../auth");
var info = require("./settings");
var plugins = require("./plugins");
var apiUtil = require("../util");
@ -32,6 +33,7 @@ module.exports = {
nodes.init(runtimeAPI);
context.init(runtimeAPI);
info.init(settings,runtimeAPI);
plugins.init(runtimeAPI);
var needsPermission = auth.needsPermission;
@ -80,6 +82,10 @@ module.exports = {
adminApp.get("/settings",needsPermission("settings.read"),info.runtimeSettings,apiUtil.errorHandler);
// Plugins
adminApp.get("/plugins", needsPermission("plugins.read"), plugins.getAll, apiUtil.errorHandler);
adminApp.get("/plugins/messages", needsPermission("plugins.read"), plugins.getCatalogs, apiUtil.errorHandler);
return adminApp;
}
}

View File

@ -0,0 +1,46 @@
var apiUtils = require("../util");
var runtimeAPI;
module.exports = {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
},
getAll: function(req,res) {
var opts = {
user: req.user,
req: apiUtils.getRequestLogObject(req)
}
if (req.get("accept") == "application/json") {
runtimeAPI.plugins.getPluginList(opts).then(function(list) {
res.json(list);
})
} else {
opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
if (/[^a-z\-\*]/i.test(opts.lang)) {
res.json({});
return;
}
runtimeAPI.plugins.getPluginConfigs(opts).then(function(configs) {
res.send(configs);
})
}
},
getCatalogs: function(req,res) {
var opts = {
user: req.user,
lang: req.query.lng,
req: apiUtils.getRequestLogObject(req)
}
if (/[^a-z\-\*]/i.test(opts.lang)) {
res.json({});
return;
}
runtimeAPI.plugins.getPluginCatalogs(opts).then(function(result) {
res.json(result);
}).catch(function(err) {
console.log(err.stack);
apiUtils.rejectHandler(req,res,err);
})
}
};

View File

@ -76,7 +76,7 @@ module.exports = {
editorApp.get("/icons/:scope/:module/:icon",ui.icon);
var theme = require("./theme");
theme.init(settings);
theme.init(settings, runtimeAPI);
editorApp.use("/theme",theme.app());
editorApp.use("/",ui.editorResources);

View File

@ -39,9 +39,12 @@ module.exports = {
},
get: function(req,res) {
var namespace = req.params[0];
var lngs = req.query.lng;
namespace = namespace.replace(/\.json$/,"");
var lang = req.query.lng || i18n.defaultLang; //apiUtil.determineLangFromHeaders(req.acceptsLanguages() || []);
if (/[^a-z\-\*]/i.test(lang)) {
res.json({});
return;
}
var prevLang = i18n.i.language;
// Trigger a load from disk of the language if it is not the default
i18n.i.changeLanguage(lang, function(){

View File

@ -41,6 +41,10 @@ var theme = null;
var themeContext = clone(defaultContext);
var themeSettings = null;
var activeTheme = null;
var activeThemeInitialised = false;
var runtimeAPI;
var themeApp;
function serveFile(app,baseUrl,file) {
@ -58,7 +62,7 @@ function serveFile(app,baseUrl,file) {
}
}
function serveFilesFromTheme(themeValue, themeApp, directory) {
function serveFilesFromTheme(themeValue, themeApp, directory, baseDirectory) {
var result = [];
if (themeValue) {
var array = themeValue;
@ -67,7 +71,14 @@ function serveFilesFromTheme(themeValue, themeApp, directory) {
}
for (var i=0;i<array.length;i++) {
var url = serveFile(themeApp,directory,array[i]);
let fullPath = array[i];
if (baseDirectory) {
fullPath = path.resolve(baseDirectory,array[i]);
if (fullPath.indexOf(baseDirectory) !== 0) {
continue;
}
}
var url = serveFile(themeApp,directory,fullPath);
if (url) {
result.push(url);
}
@ -77,10 +88,12 @@ function serveFilesFromTheme(themeValue, themeApp, directory) {
}
module.exports = {
init: function(settings) {
init: function(settings, _runtimeAPI) {
runtimeAPI = _runtimeAPI;
themeContext = clone(defaultContext);
themeSettings = null;
theme = settings.editorTheme || {};
activeTheme = theme.theme;
},
app: function() {
@ -169,7 +182,9 @@ module.exports = {
}
}
}
themeApp.get("/", function(req,res) {
themeApp.get("/", async function(req,res) {
const themePluginList = await runtimeAPI.plugins.getPluginsByType({type:"node-red-theme"});
themeContext.themes = themePluginList.map(theme => theme.id);
res.json(themeContext);
})
@ -185,10 +200,38 @@ module.exports = {
themeSettings.projects = theme.projects;
}
if (theme.theme) {
themeSettings.theme = theme.theme;
}
return themeApp;
},
context: function() {
context: async function() {
if (activeTheme && !activeThemeInitialised) {
const themePlugin = await runtimeAPI.plugins.getPlugin({
id:activeTheme
});
if (themePlugin) {
if (themePlugin.css) {
const cssFiles = serveFilesFromTheme(
themePlugin.css,
themeApp,
"/css/",
themePlugin.path
);
themeContext.page.css = cssFiles.concat(themeContext.page.css || [])
}
if (themePlugin.scripts) {
const scriptFiles = serveFilesFromTheme(
themePlugin.scripts,
themeApp,
"/scripts/",
themePlugin.path
)
themeContext.page.scripts = scriptFiles.concat(themeContext.page.scripts || [])
}
}
activeThemeInitialised = true;
}
return themeContext;
},
settings: function() {

View File

@ -68,8 +68,8 @@ module.exports = {
apiUtils.rejectHandler(req,res,err);
})
},
editor: function(req,res) {
res.send(Mustache.render(editorTemplate,theme.context()));
editor: async function(req,res) {
res.send(Mustache.render(editorTemplate,await theme.context()));
},
editorResources: express.static(path.join(editorClientDir,'public'))
};

View File

@ -38,6 +38,7 @@
}
},
"event": {
"loadPlugins": "Loading Plugins",
"loadPalette": "Loading Palette",
"loadNodeCatalogs": "Loading Node catalogs",
"loadNodes": "Loading Nodes __count__",
@ -338,6 +339,7 @@
"output": "outputs:",
"status": "status node",
"deleteSubflow": "delete subflow",
"confirmDelete": "Are you sure you want to delete this subflow?",
"info": "Description",
"category": "Category",
"module": "Module",
@ -397,6 +399,7 @@
"icon": "Icon",
"inputType": "Input type",
"selectType": "select types...",
"loadCredentials": "Loading node credentials",
"inputs" : {
"input": "input",
"select": "select",
@ -431,7 +434,8 @@
},
"errors": {
"scopeChange": "Changing the scope will make it unavailable to nodes in other flows that use it",
"invalidProperties": "Invalid properties:"
"invalidProperties": "Invalid properties:",
"credentialLoadFailed": "Failed to load node credentials"
}
},
"keyboard": {

View File

@ -243,19 +243,19 @@
"args": "array, function",
"desc": "Returns the one and only value in the `array` parameter that satisfies the `function` predicate (i.e. the `function` returns Boolean `true` when passed the value). Throws an exception if the number of matching values is not exactly one.\n\nThe function should be supplied in the following signature: `function(value [, index [, array]])` where value is each input of the array, index is the position of that value and the whole array is passed as the third argument"
},
"$encodeUrl": {
"$encodeUrlComponent": {
"args": "str",
"desc": "Encodes a Uniform Resource Locator (URL) component by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character.\n\nExample: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
},
"$encodeUrlComponent": {
"$encodeUrl": {
"args": "str",
"desc": "Encodes a Uniform Resource Locator (URL) by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character. \n\nExample: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
},
"$decodeUrl": {
"$decodeUrlComponent": {
"args": "str",
"desc": "Decodes a Uniform Resource Locator (URL) component previously created by encodeUrlComponent. \n\nExample: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
},
"$decodeUrlComponent": {
"$decodeUrl": {
"args": "str",
"desc": "Decodes a Uniform Resource Locator (URL) previously created by encodeUrl. \n\nExample: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
},

View File

@ -243,19 +243,19 @@
"args": "array, function",
"desc": "`array`の要素のうち、条件判定関数`function`を満たす(`function`に与えた場合に真偽値`true`を返す)要素が1つのみである場合、それを返します。マッチする要素が1つのみでない場合、例外を送出します。\n\n指定する関数は`function(value [, index [, array]])`というシグネチャでなければなりません。ここで、`value`は`array`の要素値、`index`は要素の添字、第三引数には配列全体を渡します。"
},
"$encodeUrl": {
"$encodeUrlComponent": {
"args": "str",
"desc": "Uniform Resource Locator (URL)を構成する文字を1、2、3、もしくは、4文字エスケープシーケンスのUTF-8文字エンコーディングで置換します。\n\n例: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
},
"$encodeUrlComponent": {
"$encodeUrl": {
"args": "str",
"desc": "Uniform Resource Locator (URL)要素を構成する文字を1、2、3、もしくは、4文字エスケープシーケンスのUTF-8文字エンコーディングで置換します。\n\n例: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
},
"$decodeUrl": {
"$decodeUrlComponent": {
"args": "str",
"desc": "encodeUrlComponentで置換したUniform Resource Locator (URL)をデコードします。\n\n例: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
},
"$decodeUrlComponent": {
"$decodeUrl": {
"args": "str",
"desc": "encodeUrlで置換したUniform Resource Locator (URL)要素をデコードします。 \n\n例: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
},

View File

@ -246,8 +246,8 @@
"import": {
"import": "Импортировать в",
"importSelected": "Импортировать выбранные",
"importCopy": "Импортировать копию",
"viewNodes": "Посмотреть узлы...",
"importCopy": "Импортировать копии",
"viewNodes": "Показать узлы...",
"newFlow": "новый поток",
"replace": "заменить",
"errors": {
@ -257,7 +257,7 @@
"missingType": "Недопустимый поток - у элемента __index__ отсутствует свойство 'type'"
},
"conflictNotification1": "Некоторые импортируемые Вами узлы уже существуют в рабочей области.",
"conflictNotification2": "Выберите, какие узлы импортировать и следует ли заменить существующие узлы или импортировать их копию."
"conflictNotification2": "Выберите, какие узлы импортировать и следует ли заменить ими существующие узлы или импортировать их копии."
},
"copyMessagePath": "Путь скопирован",
"copyMessageValue": "Значение скопировано",
@ -373,12 +373,12 @@
"configAdd": "Добавить",
"configUpdate": "Обновить",
"configDelete": "Удалить",
"nodesUse": "__count__ узел использует эту конфигурацию",
"nodesUse_plural_2": "__count__ узла используют эту конфигурацию",
"nodesUse_plural_5": "__count__ узлов используют эту конфигурацию",
"addNewConfig": "Добавить новый конфигурационный узел __type__",
"nodesUse": "__count__ узел использует этот конфиг",
"nodesUse_plural_2": "__count__ узла используют этот конфиг",
"nodesUse_plural_5": "__count__ узлов используют этот конфиг",
"addNewConfig": "Добавить новый конфиг узел __type__",
"editNode": "Изменить узел __type__",
"editConfig": "Изменить конфигурационный узел __type__",
"editConfig": "Изменить конфиг узел __type__",
"addNewType": "Добавить новый __type__...",
"nodeProperties": "свойства узла",
"label": "Метка",
@ -615,7 +615,7 @@
"info": {
"name": "Информация",
"tabName": "Имя",
"label": "сведения",
"label": "инфо",
"node": "Узел",
"type": "Тип",
"group": "Группа",
@ -648,8 +648,8 @@
"showTips":"Вы можете открыть советы из панели настроек",
"outline": "Структура",
"empty": "пусто",
"globalConfig": "Узлы глобальной конфигурации",
"triggerAction": "Запустить действие",
"globalConfig": "Глобальные конфиг узлы",
"triggerAction": "Вызвать действие",
"find": "Найти в рабочей области",
"search": {
"configNodes": "Узлы конфигурации",
@ -671,8 +671,8 @@
},
"config": {
"name": "Узлы конфигураций",
"label": "конфигурация",
"global": "На всех потока",
"label": "конфиг",
"global": "На всех потоках",
"none": "нет",
"subflows": "подпотоки",
"flows": "потоки",
@ -690,8 +690,8 @@
"none": "ничего не выбрано",
"refresh": "обновите, чтобы загрузить",
"empty": "пусто",
"node": "Узел",
"flow": "Поток",
"node": "Узловой",
"flow": "Потоковый",
"global": "Глобальный",
"deleteConfirm": "Вы уверены, что хотите удалить этот элемент?",
"autoRefresh": "Обновить при изменении выбора",
@ -877,7 +877,7 @@
"bool": "логический тип",
"json": "JSON",
"bin": "буфер",
"date": "отметка времени",
"date": "метка времени",
"jsonata": "выражение",
"env": "переменная среды",
"cred": "учетные данные"

View File

@ -243,19 +243,19 @@
"args": "array, function",
"desc": "Возвращает одно-единственное значение из массива `array`, которое удовлетворяет предикату `function` (то есть когда `function` возвращает логическое `true` при передаче значения). Выдает исключение, если число подходящих значений не одно.\n\nФункция должна соответствовать следующей сигнатуре: `function(value [, index [, array]])` где value - элемент массива, index - позиция этого значения, а весь массив передается в качестве третьего аргумента"
},
"$encodeUrl": {
"$encodeUrlComponent": {
"args": "str",
"desc": "Кодирует компонент Uniform Resource Locator (URL), заменяя каждый экземпляр определенных символов одной, двумя, тремя или четырьмя escape-последовательностями, представляющими кодировку UTF-8 символа.\n\nПример: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
},
"$encodeUrlComponent": {
"$encodeUrl": {
"args": "str",
"desc": "Кодирует Uniform Resource Locator (URL), заменяя каждый экземпляр определенных символов одной, двумя, тремя или четырьмя escape-последовательностями, представляющими кодировку UTF-8 символа.\n\nПример: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
},
"$decodeUrl": {
"$decodeUrlComponent": {
"args": "str",
"desc": "Декодирует компонент Uniform Resource Locator (URL), ранее созданный с помощью encodeUrlComponent.\n\nПример: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
},
"$decodeUrlComponent": {
"$decodeUrl": {
"args": "str",
"desc": "Декодирует компонент Uniform Resource Locator (URL), ранее созданный с помощью encodeUrl. \n\nПример: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
},

View File

@ -243,19 +243,19 @@
"args": "array, function",
"desc": "返回满足参数function谓语的array参数中的唯一值 (比如传递值时函数返回布尔值“true”)。如果匹配值的数量不唯一时,则抛出异常。\n\n应在以下签名中提供函数 `functionvalue [index [array []]]` 其中value是数组的每个输入index是该值的位置整个数组作为第三个参数传递。"
},
"$encodeUrl": {
"$encodeUrlComponent": {
"args": "str",
"desc": "通过用表示字符的UTF-8编码的一个两个三个或四个转义序列替换某些字符的每个实例对统一资源定位符URL组件进行编码。\n\n示例 `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
},
"$encodeUrlComponent": {
"$encodeUrl": {
"args": "str",
"desc": "通过用表示字符的UTF-8编码的一个两个三个或四个转义序列替换某些字符的每个实例对统一资源定位符URL进行编码。\n\n示例 `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
},
"$decodeUrl": {
"$decodeUrlComponent": {
"args": "str",
"desc": "解码以前由encodeUrlComponent创建的统一资源定位器URL组件。 \n\n示例 `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
},
"$decodeUrlComponent": {
"$decodeUrl": {
"args": "str",
"desc": "解码先前由encodeUrl创建的统一资源定位符URL。 \n\n示例 `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
},

View File

@ -243,19 +243,19 @@
"args": "array, function",
"desc": "返回滿足參數function謂語的array參數中的唯一值 (比如傳遞值時函數返回布林值“true”)。如果匹配值的數量不唯一時,則拋出異常。\n\n應在以下簽名中提供函數`functionvalue [index [array []]]`其中value是數組的每個輸入index是該值的位置整個數組作為第三個參數傳遞。"
},
"$encodeUrl": {
"$encodeUrlComponent": {
"args": "str",
"desc": "通過用表示字符的UTF-8編碼的一個兩個三個或四個轉義序列替換某些字符的每個實例對統一資源定位符URL組件進行編碼。\n\n示例`$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
},
"$encodeUrlComponent": {
"$encodeUrl": {
"args": "str",
"desc": "通過用表示字符的UTF-8編碼的一個兩個三個或四個轉義序列替換某些字符的每個實例對統一資源定位符URL進行編碼。\n\n示例 `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
},
"$decodeUrl": {
"$decodeUrlComponent": {
"args": "str",
"desc": "解碼以前由encodeUrlComponent創建的統一資源定位器URL組件。 \n\n示例 `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
},
"$decodeUrlComponent": {
"$decodeUrl": {
"args": "str",
"desc": "解碼先前由encodeUrl創建的統一資源定位符URL。 \n\n示例 `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
},

View File

@ -343,17 +343,29 @@ RED.history = (function() {
if (ev.changes.hasOwnProperty(i)) {
inverseEv.changes[i] = ev.node[i];
if (ev.node._def.defaults && ev.node._def.defaults[i] && ev.node._def.defaults[i].type) {
// This is a config node property
var currentConfigNode = RED.nodes.node(ev.node[i]);
if (currentConfigNode) {
// This property is a reference to another node or nodes.
var nodeList = ev.node[i];
if (!Array.isArray(nodeList)) {
nodeList = [nodeList];
}
nodeList.forEach(function(id) {
var currentConfigNode = RED.nodes.node(id);
if (currentConfigNode && currentConfigNode._def.category === "config") {
currentConfigNode.users.splice(currentConfigNode.users.indexOf(ev.node),1);
RED.events.emit("nodes:change",currentConfigNode);
}
var newConfigNode = RED.nodes.node(ev.changes[i]);
if (newConfigNode) {
});
nodeList = ev.changes[i];
if (!Array.isArray(nodeList)) {
nodeList = [nodeList];
}
nodeList.forEach(function(id) {
var newConfigNode = RED.nodes.node(id);
if (newConfigNode && newConfigNode._def.category === "config") {
newConfigNode.users.push(ev.node);
RED.events.emit("nodes:change",newConfigNode);
}
});
}
ev.node[i] = ev.changes[i];
}

View File

@ -108,6 +108,31 @@ RED.i18n = (function() {
}
});
})
},
loadPluginCatalogs: function(done) {
var languageList = i18n.functions.toLanguages(localStorage.getItem("editor-language")||i18n.detectLanguage());
var toLoad = languageList.length;
languageList.forEach(function(lang) {
$.ajax({
headers: {
"Accept":"application/json"
},
cache: false,
url: apiRootUrl+'plugins/messages?lng='+lang,
success: function(data) {
var namespaces = Object.keys(data);
namespaces.forEach(function(ns) {
i18n.addResourceBundle(lang,ns,data[ns]);
});
toLoad--;
if (toLoad === 0) {
done();
}
}
});
})
}
}
})();

View File

@ -164,6 +164,21 @@ RED.nodes = (function() {
// TODO: too tightly coupled into palette UI
}
if (def.defaults) {
for (var d in def.defaults) {
if (def.defaults.hasOwnProperty(d)) {
if (def.defaults[d].type) {
try {
def.defaults[d]._type = parseNodePropertyTypeString(def.defaults[d].type)
} catch(err) {
console.warn(err);
}
}
}
}
}
RED.events.emit("registry:node-type-added",nt);
},
removeNodeType: function(nt) {
@ -193,6 +208,59 @@ RED.nodes = (function() {
return (1+Math.random()*4294967295).toString(16);
}
function parseNodePropertyTypeString(typeString) {
typeString = typeString.trim();
var c;
var pos = 0;
var isArray = /\[\]$/.test(typeString);
if (isArray) {
typeString = typeString.substring(0,typeString.length-2);
}
var l = typeString.length;
var inBrackets = false;
var inToken = false;
var currentToken = "";
var types = [];
while (pos < l) {
c = typeString[pos];
if (inToken) {
if (c === "|") {
types.push(currentToken.trim())
currentToken = "";
inToken = false;
} else if (c === ")") {
types.push(currentToken.trim())
currentToken = "";
inBrackets = false;
inToken = false;
} else {
currentToken += c;
}
} else {
if (c === "(") {
if (inBrackets) {
throw new Error("Invalid character '"+c+"' at position "+pos)
}
inBrackets = true;
} else if (c !== " ") {
inToken = true;
currentToken = c;
}
}
pos++;
}
currentToken = currentToken.trim();
if (currentToken.length > 0) {
types.push(currentToken)
}
return {
types: types,
array: isArray
}
}
function addNode(n) {
if (n.type.indexOf("subflow") !== 0) {
n["_"] = n._def._;
@ -781,6 +849,12 @@ RED.nodes = (function() {
subflowSet.push(n);
}
});
RED.nodes.eachConfig(function(n) {
if (n.z == subflowId) {
subflowSet.push(n);
exportedConfigNodes[n.id] = true;
}
});
var exportableSubflow = createExportableNodeSet(subflowSet, exportedIds, exportedSubflows, exportedConfigNodes);
nns = exportableSubflow.concat(nns);
}
@ -788,16 +862,29 @@ RED.nodes = (function() {
if (node.type !== "subflow") {
var convertedNode = RED.nodes.convertNode(node);
for (var d in node._def.defaults) {
if (node._def.defaults[d].type && node[d] in configNodes) {
var confNode = configNodes[node[d]];
var exportable = registry.getNodeType(node._def.defaults[d].type).exportable;
if ((exportable == null || exportable)) {
if (!(node[d] in exportedConfigNodes)) {
exportedConfigNodes[node[d]] = true;
set.push(confNode);
if (node._def.defaults[d].type) {
var nodeList = node[d];
if (!Array.isArray(nodeList)) {
nodeList = [nodeList];
}
nodeList = nodeList.filter(function(id) {
if (id in configNodes) {
var confNode = configNodes[id];
if (confNode._def.exportable !== false) {
if (!(id in exportedConfigNodes)) {
exportedConfigNodes[id] = true;
set.push(confNode);
return true;
}
}
return false;
}
return true;
})
if (nodeList.length === 0) {
convertedNode[d] = Array.isArray(node[d])?[]:""
} else {
convertedNode[d] = "";
convertedNode[d] = Array.isArray(node[d])?nodeList:nodeList[0]
}
}
}
@ -1588,15 +1675,6 @@ RED.nodes = (function() {
}
}
}
// TODO: make this a part of the node definition so it doesn't have to
// be hardcoded here
var nodeTypeArrayReferences = {
"catch":"scope",
"status":"scope",
"complete": "scope",
"link in":"links",
"link out":"links"
}
// Remap all wires and config node references
for (i=0;i<new_nodes.length;i++) {
@ -1625,19 +1703,24 @@ RED.nodes = (function() {
}
for (var d3 in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d3)) {
if (n._def.defaults[d3].type && node_map[n[d3]]) {
configNode = node_map[n[d3]];
n[d3] = configNode.id;
if (configNode.users.indexOf(n) === -1) {
configNode.users.push(n);
if (n._def.defaults[d3].type) {
var nodeList = n[d3];
if (!Array.isArray(nodeList)) {
nodeList = [nodeList];
}
} else if (nodeTypeArrayReferences.hasOwnProperty(n.type) && nodeTypeArrayReferences[n.type] === d3 && n[d3] !== undefined && n[d3] !== null) {
for (var j = 0;j<n[d3].length;j++) {
if (node_map[n[d3][j]]) {
n[d3][j] = node_map[n[d3][j]].id;
nodeList = nodeList.map(function(id) {
var node = node_map[id];
if (node) {
if (node._def.category === 'config') {
if (node.users.indexOf(n) === -1) {
node.users.push(n);
}
}
return node.id;
}
return id;
})
n[d3] = Array.isArray(n[d3])?nodeList:nodeList[0];
}
}
}

View File

@ -0,0 +1,46 @@
RED.plugins = (function() {
var plugins = {};
var pluginsByType = {};
function registerPlugin(id,definition) {
plugins[id] = definition;
if (definition.type) {
pluginsByType[definition.type] = pluginsByType[definition.type] || [];
pluginsByType[definition.type].push(definition);
}
if (RED._loadingModule) {
definition.module = RED._loadingModule;
definition["_"] = function() {
var args = Array.prototype.slice.call(arguments);
var originalKey = args[0];
if (!/:/.test(args[0])) {
args[0] = definition.module+":"+args[0];
}
var result = RED._.apply(null,args);
if (result === args[0]) {
return originalKey;
}
return result;
}
} else {
definition["_"] = RED["_"]
}
if (definition.onadd && typeof definition.onadd === 'function') {
definition.onadd();
}
RED.events.emit("registry:plugin-added",id);
}
function getPlugin(id) {
return plugins[id]
}
function getPluginsByType(type) {
return pluginsByType[type] || [];
}
return {
registerPlugin: registerPlugin,
getPlugin: getPlugin,
getPluginsByType: getPluginsByType
}
})();

View File

@ -15,19 +15,65 @@
**/
var RED = (function() {
function appendNodeConfig(nodeConfig,done) {
function loadPluginList() {
loader.reportProgress(RED._("event.loadPlugins"), 10)
$.ajax({
headers: {
"Accept":"application/json"
},
cache: false,
url: 'plugins',
success: function(data) {
loader.reportProgress(RED._("event.loadPlugins"), 13)
RED.i18n.loadPluginCatalogs(function() {
loadPlugins(function() {
loadNodeList();
});
});
}
});
}
function loadPlugins(done) {
loader.reportProgress(RED._("event.loadPlugins",{count:""}), 17)
var lang = localStorage.getItem("editor-language")||i18n.detectLanguage();
$.ajax({
headers: {
"Accept":"text/html",
"Accept-Language": lang
},
cache: false,
url: 'plugins',
success: function(data) {
var configs = data.trim().split(/(?=<!-- --- \[red-plugin:\S+\] --- -->)/);
var totalCount = configs.length;
var stepConfig = function() {
// loader.reportProgress(RED._("event.loadNodes",{count:(totalCount-configs.length)+"/"+totalCount}), 30 + ((totalCount-configs.length)/totalCount)*40 )
if (configs.length === 0) {
done();
} else {
var config = configs.shift();
appendPluginConfig(config,stepConfig);
}
}
stepConfig();
}
});
}
function appendConfig(config, moduleIdMatch, targetContainer, done) {
done = done || function(){};
var m = /<!-- --- \[red-module:(\S+)\] --- -->/.exec(nodeConfig.trim());
var moduleId;
if (m) {
moduleId = m[1];
if (moduleIdMatch) {
moduleId = moduleIdMatch[1];
RED._loadingModule = moduleId;
} else {
moduleId = "unknown";
}
try {
var hasDeferred = false;
var nodeConfigEls = $("<div>"+nodeConfig+"</div>");
var nodeConfigEls = $("<div>"+config+"</div>");
var scripts = nodeConfigEls.find("script");
var scriptCount = scripts.length;
scripts.each(function(i,el) {
@ -38,14 +84,15 @@ var RED = (function() {
newScript.onload = function() {
scriptCount--;
if (scriptCount === 0) {
$("#red-ui-editor-node-configs").append(nodeConfigEls);
$(targetContainer).append(nodeConfigEls);
delete RED._loadingModule;
done()
}
}
if ($(el).attr('type') === "module") {
newScript.type = "module";
}
$("#red-ui-editor-node-configs").append(newScript);
$(targetContainer).append(newScript);
newScript.src = RED.settings.apiRootUrl+srcUrl;
hasDeferred = true;
} else {
@ -61,7 +108,8 @@ var RED = (function() {
}
})
if (!hasDeferred) {
$("#red-ui-editor-node-configs").append(nodeConfigEls);
$(targetContainer).append(nodeConfigEls);
delete RED._loadingModule;
done();
}
} catch(err) {
@ -70,9 +118,27 @@ var RED = (function() {
timeout: 10000
});
console.log("["+moduleId+"] "+err.toString());
delete RED._loadingModule;
done();
}
}
function appendPluginConfig(pluginConfig,done) {
appendConfig(
pluginConfig,
/<!-- --- \[red-plugin:(\S+)\] --- -->/.exec(pluginConfig.trim()),
"#red-ui-editor-plugin-configs",
done
);
}
function appendNodeConfig(nodeConfig,done) {
appendConfig(
nodeConfig,
/<!-- --- \[red-module:(\S+)\] --- -->/.exec(nodeConfig.trim()),
"#red-ui-editor-node-configs",
done
);
}
function loadNodeList() {
loader.reportProgress(RED._("event.loadPalette"), 20)
@ -186,6 +252,7 @@ var RED = (function() {
RED.workspaces.show(currentHash.substring(6));
}
} catch(err) {
console.warn(err);
RED.notify(
RED._("event.importError", {message: err.message}),
{
@ -593,7 +660,7 @@ var RED = (function() {
RED.actions.add("core:show-about", showAbout);
loadNodeList();
loadPluginList();
}
@ -609,6 +676,7 @@ var RED = (function() {
'<div id="red-ui-sidebar"></div>'+
'<div id="red-ui-sidebar-separator"></div>'+
'</div>').appendTo(options.target);
$('<div id="red-ui-editor-plugin-configs"></div>').appendTo(options.target);
$('<div id="red-ui-editor-node-configs"></div>').appendTo(options.target);
$('<div id="red-ui-full-shade" class="hide"></div>').appendTo(options.target);
@ -627,9 +695,12 @@ var RED = (function() {
$('<span>').html(theme.header.title).appendTo(logo);
}
}
if (theme.themes) {
knownThemes = theme.themes;
}
});
}
var knownThemes = null;
var initialised = false;
function init(options) {
@ -649,7 +720,13 @@ var RED = (function() {
buildEditor(options);
RED.i18n.init(options, function() {
RED.settings.init(options, loadEditor);
RED.settings.init(options, function() {
if (knownThemes) {
RED.settings.editorTheme = RED.settings.editorTheme || {};
RED.settings.editorTheme.themes = knownThemes;
}
loadEditor();
});
})
}

View File

@ -72,9 +72,7 @@ RED.clipboard = (function() {
text: RED._("clipboard.export.copy"),
click: function() {
if (activeTab === "red-ui-clipboard-dialog-export-tab-clipboard") {
$("#red-ui-clipboard-dialog-export-text").select();
document.execCommand("copy");
document.getSelection().removeAllRanges();
copyText($("#red-ui-clipboard-dialog-export-text").val());
RED.notify(RED._("clipboard.nodesExported"),{id:"clipboard"});
$( this ).dialog( "close" );
} else {
@ -222,7 +220,14 @@ RED.clipboard = (function() {
'</div>'+
'<div id="red-ui-clipboard-dialog-export-tabs-content" class="red-ui-clipboard-dialog-tabs-content">'+
'<div id="red-ui-clipboard-dialog-export-tab-clipboard" class="red-ui-clipboard-dialog-tab-clipboard">'+
'<div class="form-row" style="height:calc(100% - 30px)">'+
'<div id="red-ui-clipboard-dialog-export-tab-clipboard-tab-bar">'+
'<ul id="red-ui-clipboard-dialog-export-tab-clipboard-tabs"></ul>'+
'</div>'+
'<div class="red-ui-clipboard-dialog-export-tab-clipboard-tab" id="red-ui-clipboard-dialog-export-tab-clipboard-preview">'+
'<div id="red-ui-clipboard-dialog-export-tab-clipboard-preview-list"></div>'+
'</div>'+
'<div class="red-ui-clipboard-dialog-export-tab-clipboard-tab" id="red-ui-clipboard-dialog-export-tab-clipboard-json">'+
'<div class="form-row" style="height:calc(100% - 40px)">'+
'<textarea readonly id="red-ui-clipboard-dialog-export-text"></textarea>'+
'</div>'+
'<div class="form-row" style="text-align: right;">'+
@ -232,6 +237,7 @@ RED.clipboard = (function() {
'</span>'+
'</div>'+
'</div>'+
'</div>'+
'<div id="red-ui-clipboard-dialog-export-tab-library" class="red-ui-clipboard-dialog-tab-library">'+
'<div id="red-ui-clipboard-dialog-export-tab-library-browser"></div>'+
'<div class="form-row">'+
@ -592,6 +598,30 @@ RED.clipboard = (function() {
})
loadFlowLibrary(libraryBrowser,"local",RED._("library.types.local"));
var clipboardTabs = RED.tabs.create({
id: "red-ui-clipboard-dialog-export-tab-clipboard-tabs",
onchange: function(tab) {
$(".red-ui-clipboard-dialog-export-tab-clipboard-tab").hide();
$("#" + tab.id).show();
}
});
clipboardTabs.addTab({
id: "red-ui-clipboard-dialog-export-tab-clipboard-preview",
label: RED._("clipboard.exportNodes")
});
clipboardTabs.addTab({
id: "red-ui-clipboard-dialog-export-tab-clipboard-json",
label: RED._("editor.types.json")
});
var previewList = $("#red-ui-clipboard-dialog-export-tab-clipboard-preview-list").css({position:"absolute",top:0,right:0,bottom:0,left:0}).treeList({
data: []
})
refreshExportPreview();
$("#red-ui-clipboard-dialog-tab-library-name").val("flows.json").select();
dialogContainer.i18n();
@ -630,10 +660,10 @@ RED.clipboard = (function() {
}
$(this).parent().children().removeClass('selected');
$(this).addClass('selected');
var type = $(this).attr('id');
var type = $(this).attr('id').substring("red-ui-clipboard-dialog-export-rng-".length);
var flow = "";
var nodes = null;
if (type === 'red-ui-clipboard-dialog-export-rng-selected') {
if (type === 'selected') {
var selection = RED.workspaces.selection();
if (selection.length > 0) {
nodes = [];
@ -647,14 +677,14 @@ RED.clipboard = (function() {
}
// Don't include the subflow meta-port nodes in the exported selection
nodes = RED.nodes.createExportableNodeSet(nodes.filter(function(n) { return n.type !== 'subflow'}));
} else if (type === 'red-ui-clipboard-dialog-export-rng-flow') {
} else if (type === 'flow') {
var activeWorkspace = RED.workspaces.active();
nodes = RED.nodes.groups(activeWorkspace);
nodes = nodes.concat(RED.nodes.filterNodes({z:activeWorkspace}));
var parentNode = RED.nodes.workspace(activeWorkspace)||RED.nodes.subflow(activeWorkspace);
nodes.unshift(parentNode);
nodes = RED.nodes.createExportableNodeSet(nodes);
} else if (type === 'red-ui-clipboard-dialog-export-rng-full') {
} else if (type === 'full') {
nodes = RED.nodes.createCompleteNodeSet(false);
}
if (nodes !== null) {
@ -670,8 +700,10 @@ RED.clipboard = (function() {
$("#red-ui-clipboard-dialog-export").addClass('disabled');
}
$("#red-ui-clipboard-dialog-export-text").val(flow);
setTimeout(function() { $("#red-ui-clipboard-dialog-export-text").scrollTop(0); },50);
$("#red-ui-clipboard-dialog-export-text").trigger("focus");
setTimeout(function() {
$("#red-ui-clipboard-dialog-export-text").scrollTop(0);
refreshExportPreview(type);
},50);
})
$("#red-ui-clipboard-dialog-ok").hide();
@ -717,6 +749,93 @@ RED.clipboard = (function() {
}
function refreshExportPreview(type) {
var flowData = $("#red-ui-clipboard-dialog-export-text").val() || "[]";
var flow = JSON.parse(flowData);
var flows = {};
var subflows = {};
var nodes = [];
var nodesByZ = {};
var treeFlows = [];
var treeSubflows = [];
flow.forEach(function(node) {
if (node.type === "tab") {
flows[node.id] = {
element: getFlowLabel(node,false),
deferBuild: type !== "flow",
expanded: type === "flow",
children: []
};
treeFlows.push(flows[node.id])
} else if (node.type === "subflow") {
subflows[node.id] = {
element: getNodeLabel(node,false),
deferBuild: true,
children: []
};
treeSubflows.push(subflows[node.id])
} else {
nodes.push(node);
}
});
var globalNodes = [];
var parentlessNodes = [];
nodes.forEach(function(node) {
var treeNode = {
element: getNodeLabel(node, false, false)
};
if (node.z) {
if (!flows[node.z] && !subflows[node.z]) {
parentlessNodes.push(treeNode)
} else if (flows[node.z]) {
flows[node.z].children.push(treeNode)
} else if (subflows[node.z]) {
subflows[node.z].children.push(treeNode)
}
} else {
globalNodes.push(treeNode);
}
});
var treeData = [];
if (parentlessNodes.length > 0) {
treeData = treeData.concat(parentlessNodes);
}
if (type === "flow") {
treeData = treeData.concat(treeFlows);
} else if (treeFlows.length > 0) {
treeData.push({
label: RED._("menu.label.flows"),
deferBuild: treeFlows.length > 20,
expanded: treeFlows.length <= 20,
children: treeFlows
})
}
if (treeSubflows.length > 0) {
treeData.push({
label: RED._("menu.label.subflows"),
deferBuild: treeSubflows.length > 10,
expanded: treeSubflows.length <= 10,
children: treeSubflows
})
}
if (globalNodes.length > 0) {
treeData.push({
label: RED._("sidebar.info.globalConfig"),
deferBuild: globalNodes.length > 10,
expanded: globalNodes.length <= 10,
children: globalNodes
})
}
$("#red-ui-clipboard-dialog-export-tab-clipboard-preview-list").treeList('data',treeData);
}
function loadFlowLibrary(browser,library,label) {
// if (includeExamples) {
// listing.push({
@ -756,6 +875,7 @@ RED.clipboard = (function() {
}
function copyText(value,element,msg) {
var truncated = false;
var currentFocus = document.activeElement;
if (typeof value !== "string" ) {
value = JSON.stringify(value, function(key,value) {
if (value !== null && typeof value === 'object') {
@ -787,7 +907,7 @@ RED.clipboard = (function() {
if (truncated) {
msg += "_truncated";
}
$("#red-ui-clipboard-hidden").val(value).select();
$("#red-ui-clipboard-hidden").val(value).focus().select();
var result = document.execCommand("copy");
if (result && element) {
var popover = RED.popover.create({
@ -801,6 +921,10 @@ RED.clipboard = (function() {
},1000);
popover.open();
}
$("#red-ui-clipboard-hidden").val("");
if (currentFocus) {
$(currentFocus).focus();
}
return result;
}

View File

@ -444,8 +444,9 @@ RED.editor = (function() {
for (var d in definition.defaults) {
if (definition.defaults.hasOwnProperty(d)) {
if (definition.defaults[d].type) {
if (!definition.defaults[d]._type.array) {
var configTypeDef = RED.nodes.getType(definition.defaults[d].type);
if (configTypeDef) {
if (configTypeDef && configTypeDef.category === 'config') {
if (configTypeDef.exclusive) {
prepareConfigNodeButton(node,d,definition.defaults[d].type,prefix);
} else {
@ -455,6 +456,7 @@ RED.editor = (function() {
console.log("Unknown type:", definition.defaults[d].type);
preparePropertyEditor(node,d,prefix,definition.defaults);
}
}
} else {
preparePropertyEditor(node,d,prefix,definition.defaults);
}
@ -494,12 +496,14 @@ RED.editor = (function() {
populateCredentialsInputs(node, definition.credentials, node.credentials, prefix);
completePrepare();
} else {
$.getJSON(getCredentialsURL(node.type, node.id), function (data) {
getNodeCredentials(node.type, node.id, function(data) {
if (data) {
node.credentials = data;
node.credentials._ = $.extend(true,{},data);
if (!/^subflow:/.test(definition.type)) {
populateCredentialsInputs(node, definition.credentials, node.credentials, prefix);
}
}
completePrepare();
});
}
@ -1086,8 +1090,11 @@ RED.editor = (function() {
node.infoEditor = nodeInfoEditor;
return nodeInfoEditor;
}
var buildingEditDialog = false;
function showEditDialog(node, defaultTab) {
if (buildingEditDialog) { return }
buildingEditDialog = true;
var editing_node = node;
var isDefaultIcon;
var defaultIcon;
@ -1612,6 +1619,7 @@ RED.editor = (function() {
if (defaultTab) {
editorTabs.activateTab(defaultTab);
}
buildingEditDialog = false;
done();
});
},
@ -1663,6 +1671,8 @@ RED.editor = (function() {
* prefix - the input prefix of the parent property
*/
function showEditConfigNodeDialog(name,type,id,prefix) {
if (buildingEditDialog) { return }
buildingEditDialog = true;
var adding = (id == "_ADD_");
var node_def = RED.nodes.getType(type);
var editing_config_node = RED.nodes.node(id);
@ -1826,6 +1836,7 @@ RED.editor = (function() {
trayBody.i18n();
trayFooter.i18n();
finishedBuilding = true;
buildingEditDialog = false;
done();
});
},
@ -2149,6 +2160,8 @@ RED.editor = (function() {
}
function showEditSubflowDialog(subflow) {
if (buildingEditDialog) { return }
buildingEditDialog = true;
var editing_node = subflow;
editStack.push(subflow);
RED.view.state(RED.state.EDITING);
@ -2405,15 +2418,17 @@ RED.editor = (function() {
buildEditForm(nodePropertiesTab.content,"dialog-form","subflow-template", undefined, editing_node);
trayBody.i18n();
$.getJSON(getCredentialsURL("subflow", subflow.id), function (data) {
getNodeCredentials("subflow", subflow.id, function(data) {
if (data) {
subflow.credentials = data;
subflow.credentials._ = $.extend(true,{},data);
}
$("#subflow-input-name").val(subflow.name);
RED.text.bidi.prepareInput($("#subflow-input-name"));
finishedBuilding = true;
buildingEditDialog = false;
done();
});
},
@ -2434,7 +2449,39 @@ RED.editor = (function() {
RED.tray.show(trayOptions);
}
function getNodeCredentials(type, id, done) {
var timeoutNotification;
var intialTimeout = setTimeout(function() {
timeoutNotification = RED.notify($('<p data-i18n="[prepend]editor.loadCredentials"> <img src="red/images/spin.svg"/></p>').i18n(),{fixed: true})
},800);
$.ajax({
url: getCredentialsURL(type,id),
dataType: 'json',
success: function(data) {
if (timeoutNotification) {
timeoutNotification.close();
timeoutNotification = null;
}
clearTimeout(intialTimeout);
done(data);
},
error: function(jqXHR,status,error) {
if (timeoutNotification) {
timeoutNotification.close();
timeoutNotification = null;
}
clearTimeout(intialTimeout);
RED.notify(RED._("editor.errors.credentialLoadFailed"),"error")
done(null);
},
timeout: 30000,
});
}
function showEditGroupDialog(group) {
if (buildingEditDialog) { return }
buildingEditDialog = true;
var editing_node = group;
editStack.push(group);
RED.view.state(RED.state.EDITING);
@ -2658,6 +2705,7 @@ RED.editor = (function() {
prepareEditDialog(group,group._def,"node-input", function() {
trayBody.i18n();
finishedBuilding = true;
buildingEditDialog = false;
done();
});
},

View File

@ -97,13 +97,18 @@ RED.palette = (function() {
label = RED.utils.sanitize(label);
var words = label.split(/[ -]/);
var words = label.split(/([ -]|\\n )/);
var displayLines = [];
var currentLine = "";
for (var i=0;i<words.length;i++) {
var word = words[i];
if (word === "\\n ") {
displayLines.push(currentLine);
currentLine = "";
continue;
}
var sep = (i == 0) ? "" : " ";
var newWidth = RED.view.calculateTextWidth(currentLine+sep+word, "red-ui-palette-label");
if (newWidth < nodeWidth) {
@ -165,7 +170,16 @@ RED.palette = (function() {
metaData = typeInfo.set.module+" : ";
}
metaData += type;
$('<button type="button" onclick="RED.sidebar.help.show(\''+type+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right"><i class="fa fa-book"></i></button>').appendTo(popOverContent)
if (/^subflow:/.test(type)) {
$('<button type="button" onclick="RED.workspaces.show(\''+type.substring(8).replace(/'/g,"\\'")+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-pencil"></i></button>').appendTo(popOverContent)
}
var safeType = type.replace(/'/g,"\\'");
$('<button type="button" onclick="RED.search.show(\'type:'+safeType+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-search"></i></button>').appendTo(popOverContent)
$('<button type="button" onclick="RED.sidebar.help.show(\''+safeType+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-book"></i></button>').appendTo(popOverContent)
$('<p>',{style:"font-size: 0.8em"}).text(metaData).appendTo(popOverContent);
}
} catch(err) {
@ -396,7 +410,8 @@ RED.palette = (function() {
RED.workspaces.show(nt.substring(8));
e.preventDefault();
});
nodeInfo = RED.utils.renderMarkdown(def.info||"");
var subflow = RED.nodes.subflow(nt.substring(8));
nodeInfo = RED.utils.renderMarkdown(subflow.info||"");
}
setLabel(nt,d,label,nodeInfo);

View File

@ -464,12 +464,43 @@ RED.subflow = (function() {
$("#red-ui-subflow-delete").on("click", function(event) {
event.preventDefault();
var subflow = RED.nodes.subflow(RED.workspaces.active());
if (subflow.instances.length > 0) {
var msg = $('<div>')
$('<p>').text(RED._("subflow.subflowInstances",{count: subflow.instances.length})).appendTo(msg);
$('<p>').text(RED._("subflow.confirmDelete")).appendTo(msg);
var confirmDeleteNotification = RED.notify(msg, {
modal: true,
fixed: true,
buttons: [
{
text: RED._('common.label.cancel'),
click: function() {
confirmDeleteNotification.close();
}
},
{
text: RED._('workspace.confirmDelete'),
class: "primary",
click: function() {
confirmDeleteNotification.close();
completeDelete();
}
}
]
});
return;
} else {
completeDelete();
}
function completeDelete() {
var startDirty = RED.nodes.dirty();
var historyEvent = removeSubflow(RED.workspaces.active());
historyEvent.t = 'delete';
historyEvent.dirty = startDirty;
RED.history.push(historyEvent);
}
});

View File

@ -119,34 +119,17 @@ RED.sidebar.info.outliner = (function() {
return div;
}
function getSubflowLabel(n) {
var div = $('<div>',{class:"red-ui-info-outline-item"});
RED.utils.createNodeIcon(n).appendTo(div);
var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
var labelText = getNodeLabelText(n);
var label = $('<div>',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).appendTo(contentDiv);
if (labelText) {
label.text(labelText)
} else {
label.html("&nbsp;")
}
addControls(n, div);
return div;
// var div = $('<div>',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow"});
// var contentDiv = $('<div>',{class:"red-ui-search-result-description red-ui-info-outline-item-label"}).appendTo(div);
// contentDiv.text(n.name || n.id);
// addControls(n, div);
// return div;
}
function addControls(n,div) {
var controls = $('<div>',{class:"red-ui-info-outline-item-controls red-ui-info-outline-item-hover-controls"}).appendTo(div);
if (n.type === "subflow") {
var subflowInstanceBadge = $('<button type="button" class="red-ui-info-outline-item-control-users red-ui-button red-ui-button-small"><i class="fa fa-toggle-right"></i></button>').text(n.instances.length).appendTo(controls).on("click",function(evt) {
evt.preventDefault();
evt.stopPropagation();
RED.search.show("type:subflow:"+n.id);
})
// RED.popover.tooltip(userCountBadge,function() { return RED._('editor.nodesUse',{count:n.users.length})});
}
if (n._def.category === "config" && n.type !== "group") {
var userCountBadge = $('<button type="button" class="red-ui-info-outline-item-control-users red-ui-button red-ui-button-small"><i class="fa fa-toggle-right"></i></button>').text(n.users.length).appendTo(controls).on("click",function(evt) {
evt.preventDefault();
@ -169,7 +152,7 @@ RED.sidebar.info.outliner = (function() {
// evt.stopPropagation();
// RED.view.reveal(n.id);
// })
if (n.type !== 'group' && n.type !== 'subflow') {
if (n.type !== 'subflow') {
var toggleButton = $('<button type="button" class="red-ui-info-outline-item-control-disable red-ui-button red-ui-button-small"><i class="fa fa-circle-thin"></i><i class="fa fa-ban"></i></button>').appendTo(controls).on("click",function(evt) {
evt.preventDefault();
evt.stopPropagation();
@ -179,6 +162,46 @@ RED.sidebar.info.outliner = (function() {
} else {
RED.workspaces.disable(n.id)
}
} else if (n.type === 'group') {
var groupNodes = RED.group.getNodes(n,true);
var groupHistoryEvent = {
t:'multi',
events:[],
dirty: RED.nodes.dirty()
}
var targetState;
groupNodes.forEach(function(n) {
if (n.type !== 'group') {
if (targetState === undefined) {
targetState = !n.d;
}
var state = !!n.d;
if (state !== targetState) {
var historyEvent = {
t: "edit",
node: n,
changed: n.changed,
changes: {
d: n.d
}
}
if (n.d) {
delete n.d;
} else {
n.d = true;
}
n.dirty = true;
n.changed = true;
RED.events.emit("nodes:change",n);
groupHistoryEvent.events.push(historyEvent);
}
}
if (groupHistoryEvent.events.length > 0) {
RED.history.push(groupHistoryEvent);
RED.nodes.dirty(true)
RED.view.redraw();
}
})
} else {
// TODO: this ought to be a utility function in RED.nodes
var historyEvent = {
@ -198,11 +221,15 @@ RED.sidebar.info.outliner = (function() {
n.dirty = true;
n.changed = true;
RED.events.emit("nodes:change",n);
RED.history.push(historyEvent);
RED.nodes.dirty(true)
RED.view.redraw();
}
});
RED.popover.tooltip(toggleButton,function() {
if (n.type === "group") {
return RED._("common.label.enable")+" / "+RED._("common.label.disable")
}
return RED._("common.label."+(((n.type==='tab' && n.disabled) || (n.type!=='tab' && n.d))?"enable":"disable"));
});
} else {
@ -486,6 +513,13 @@ RED.sidebar.info.outliner = (function() {
existingObject.treeList.remove();
delete objects[n.id]
if (/^subflow:/.test(n.type)) {
var sfType = n.type.substring(8);
if (objects[sfType]) {
objects[sfType].element.find(".red-ui-info-outline-item-control-users").text(RED.nodes.subflow(sfType).instances.length);
}
}
// If this is a group being removed, it may have an empty item
if (empties[n.id]) {
delete empties[n.id];
@ -587,6 +621,12 @@ RED.sidebar.info.outliner = (function() {
configNodeTypes[parent].types[n.type].treeList.addChild(objects[n.id]);
}
objects[n.id].element.toggleClass("red-ui-info-outline-item-disabled", !!n.d)
if (/^subflow:/.test(n.type)) {
var sfType = n.type.substring(8);
if (objects[sfType]) {
objects[sfType].element.find(".red-ui-info-outline-item-control-users").text(RED.nodes.subflow(sfType).instances.length);
}
}
updateSearch();
}

View File

@ -338,7 +338,7 @@ RED.sidebar.info = (function() {
count++;
propRow = $('<tr class="red-ui-help-info-property-row'+(expandedSections.property?"":" hide")+'"><td></td><td></td></tr>').appendTo(tableBody);
$(propRow.children()[0]).text(n);
if (defaults[n].type) {
if (defaults[n].type && !defaults[n]._type.array) {
var configNode = RED.nodes.node(val);
if (!configNode) {
RED.utils.createObjectElement(undefined).appendTo(propRow.children()[1]);

View File

@ -115,7 +115,13 @@ RED.userSettings = (function() {
options: [
{setting:"editor-language",local: true, label:"menu.label.view.language",options:function(done){ done([{val:'',text:RED._('menu.label.view.browserDefault')}].concat(RED.settings.theme("languages").map(localeToName).sort(compText))) }},
]
},{
},
// {
// options: [
// {setting:"theme", label:"Theme",options:function(done){ done([{val:'',text:'default'}].concat(RED.settings.theme("themes"))) }},
// ]
// },
{
title: "menu.label.view.grid",
options: [
{setting:"view-show-grid",oldSetting:"menu-menu-item-view-show-grid",label:"menu.label.view.showGrid", default: true, toggle:true,onchange:"core:toggle-show-grid"},

View File

@ -615,18 +615,25 @@ RED.utils = (function() {
return element;
}
function normalisePropertyExpression(str) {
function createError(code, message) {
var e = new Error(message);
e.code = code;
return e;
}
function normalisePropertyExpression(str,msg) {
// This must be kept in sync with validatePropertyExpression
// in editor/js/ui/utils.js
var length = str.length;
if (length === 0) {
throw new Error("Invalid property expression: zero-length");
throw createError("INVALID_EXPR","Invalid property expression: zero-length");
}
var parts = [];
var start = 0;
var inString = false;
var inBox = false;
var boxExpression = false;
var quoteChar;
var v;
for (var i=0;i<length;i++) {
@ -634,14 +641,14 @@ RED.utils = (function() {
if (!inString) {
if (c === "'" || c === '"') {
if (i != start) {
throw new Error("Invalid property expression: unexpected "+c+" at position "+i);
throw createError("INVALID_EXPR","Invalid property expression: unexpected "+c+" at position "+i);
}
inString = true;
quoteChar = c;
start = i+1;
} else if (c === '.') {
if (i===0) {
throw new Error("Invalid property expression: unexpected . at position 0");
throw createError("INVALID_EXPR","Invalid property expression: unexpected . at position 0");
}
if (start != i) {
v = str.substring(start,i);
@ -652,57 +659,99 @@ RED.utils = (function() {
}
}
if (i===length-1) {
throw new Error("Invalid property expression: unterminated expression");
throw createError("INVALID_EXPR","Invalid property expression: unterminated expression");
}
// Next char is first char of an identifier: a-z 0-9 $ _
if (!/[a-z0-9\$\_]/i.test(str[i+1])) {
throw new Error("Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
throw createError("INVALID_EXPR","Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
}
start = i+1;
} else if (c === '[') {
if (i === 0) {
throw new Error("Invalid property expression: unexpected "+c+" at position "+i);
throw createError("INVALID_EXPR","Invalid property expression: unexpected "+c+" at position "+i);
}
if (start != i) {
parts.push(str.substring(start,i));
}
if (i===length-1) {
throw new Error("Invalid property expression: unterminated expression");
throw createError("INVALID_EXPR","Invalid property expression: unterminated expression");
}
// Start of a new expression. If it starts with msg it is a nested expression
// Need to scan ahead to find the closing bracket
if (/^msg[.\[]/.test(str.substring(i+1))) {
var depth = 1;
var inLocalString = false;
var localStringQuote;
for (var j=i+1;j<length;j++) {
if (/["']/.test(str[j])) {
if (inLocalString) {
if (str[j] === localStringQuote) {
inLocalString = false
}
} else {
inLocalString = true;
localStringQuote = str[j]
}
}
if (str[j] === '[') {
depth++;
} else if (str[j] === ']') {
depth--;
}
if (depth === 0) {
try {
if (msg) {
parts.push(getMessageProperty(msg, str.substring(i+1,j)))
} else {
parts.push(normalisePropertyExpression(str.substring(i+1,j), msg));
}
inBox = false;
i = j;
start = j+1;
break;
} catch(err) {
throw createError("INVALID_EXPR","Invalid expression started at position "+(i+1))
}
}
}
if (depth > 0) {
throw createError("INVALID_EXPR","Invalid property expression: unmatched '[' at position "+i);
}
continue;
} else if (!/["'\d]/.test(str[i+1])) {
// Next char is either a quote or a number
if (!/["'\d]/.test(str[i+1])) {
throw new Error("Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
throw createError("INVALID_EXPR","Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
}
start = i+1;
inBox = true;
} else if (c === ']') {
if (!inBox) {
throw new Error("Invalid property expression: unexpected "+c+" at position "+i);
throw createError("INVALID_EXPR","Invalid property expression: unexpected "+c+" at position "+i);
}
if (start != i) {
v = str.substring(start,i);
if (/^\d+$/.test(v)) {
parts.push(parseInt(v));
} else {
throw new Error("Invalid property expression: unexpected array expression at position "+start);
throw createError("INVALID_EXPR","Invalid property expression: unexpected array expression at position "+start);
}
}
start = i+1;
inBox = false;
} else if (c === ' ') {
throw new Error("Invalid property expression: unexpected ' ' at position "+i);
throw createError("INVALID_EXPR","Invalid property expression: unexpected ' ' at position "+i);
}
} else {
if (c === quoteChar) {
if (i-start === 0) {
throw new Error("Invalid property expression: zero-length string at position "+start);
throw createError("INVALID_EXPR","Invalid property expression: zero-length string at position "+start);
}
parts.push(str.substring(start,i));
// If inBox, next char must be a ]. Otherwise it may be [ or .
if (inBox && !/\]/.test(str[i+1])) {
throw new Error("Invalid property expression: unexpected array expression at position "+start);
throw createError("INVALID_EXPR","Invalid property expression: unexpected array expression at position "+start);
} else if (!inBox && i+1!==length && !/[\[\.]/.test(str[i+1])) {
throw new Error("Invalid property expression: unexpected "+str[i+1]+" expression at position "+(i+1));
throw createError("INVALID_EXPR","Invalid property expression: unexpected "+str[i+1]+" expression at position "+(i+1));
}
start = i+1;
inString = false;
@ -711,7 +760,7 @@ RED.utils = (function() {
}
if (inBox || inString) {
throw new Error("Invalid property expression: unterminated expression");
throw new createError("INVALID_EXPR","Invalid property expression: unterminated expression");
}
if (start < length) {
parts.push(str.substring(start));

View File

@ -2276,7 +2276,7 @@ RED.view = (function() {
}
function calculateTextWidth(str, className) {
var result=convertLineBreakCharacter(str);
var result = convertLineBreakCharacter(str);
var width = 0;
for (var i=0;i<result.length;i++) {
var calculateTextW=calculateTextDimensions(result[i],className)[0];

View File

@ -15,6 +15,9 @@
**/
body {
overflow: hidden;
}
.red-ui-editor {
font-size: $primary-font-size;

View File

@ -29,8 +29,30 @@
}
}
}
.red-ui-clipboard-dialog-tab-clipboard {
#red-ui-clipboard-dialog-export-tab-clipboard-preview {
.red-ui-treeList-container,.red-ui-editableList-border {
border: none;
border-radius: 0;
}
}
#red-ui-clipboard-dialog-export-tab-clipboard-json {
padding: 10px 10px 0;
}
#red-ui-clipboard-dialog-import-tab-clipboard {
padding: 10px;
}
.red-ui-clipboard-dialog-export-tab-clipboard-tab {
position: absolute;
top: 40px;
right: 0;
left: 0;
bottom: 0;
}
.red-ui-clipboard-dialog-tab-clipboard {
textarea {
resize: none;
width: 100%;

View File

@ -131,10 +131,10 @@
width: 120px;
background-size: contain;
position: relative;
&:not(.red-ui-palette-node-config):first-child {
&:not(.red-ui-palette-node-config):not(.red-ui-palette-node-small):first-child {
margin-top: 15px;
}
&:not(.red-ui-palette-node-config):last-child {
&:not(.red-ui-palette-node-config):not(.red-ui-palette-node-small):first-child {
margin-bottom: 15px;
}
}

View File

@ -326,7 +326,7 @@ div.red-ui-info-table {
border-bottom: 1px solid $secondary-border-color;
}
}
.red-ui-info-outline,.red-ui-sidebar-help-toc, #red-ui-clipboard-dialog-import-conflicts-list {
.red-ui-info-outline,.red-ui-sidebar-help-toc, #red-ui-clipboard-dialog-import-conflicts-list, #red-ui-clipboard-dialog-export-tab-clipboard-preview {
.red-ui-info-outline-item {
display: inline-block;
padding: 0;

View File

@ -18,7 +18,7 @@
color:"#c0edc0",
defaults: {
name: {value:""},
scope: {value:[]},
scope: {value:[], type:"*[]"},
uncaught: {value:false}
},
inputs:0,

View File

@ -30,7 +30,7 @@
color:"#e49191",
defaults: {
name: {value:""},
scope: {value:null},
scope: {value:null, type:"*[]"},
uncaught: {value:false}
},
inputs:0,

View File

@ -26,7 +26,7 @@
color:"#94c1d0",
defaults: {
name: {value:""},
scope: {value:null}
scope: {value:null, type:"*[]"}
},
inputs:0,
outputs:1,

View File

@ -187,7 +187,7 @@
color:"#ddd",//"#87D8CF",
defaults: {
name: {value:""},
links: { value: [] }
links: { value: [], type:"link out[]" }
},
inputs:0,
outputs:1,
@ -216,7 +216,7 @@
color:"#ddd",//"#87D8CF",
defaults: {
name: {value:""},
links: { value: []}
links: { value: [], type:"link in[]"}
},
align:"right",
inputs:1,

View File

@ -168,6 +168,10 @@ module.exports = function(RED) {
return getFromValueType(RED.util.getMessageProperty(msg,rule.from),done);
} else if (rule.fromt === 'flow' || rule.fromt === 'global') {
var contextKey = RED.util.parseContextStore(rule.from);
if (/\[msg\./.test(context.key)) {
// The key has a nest msg. reference to evaluate first
context.key = RED.util.normalisePropertyExpression(contextKey.key,msg,true);
}
node.context()[rule.fromt].get(contextKey.key, contextKey.store, (err,fromValue) => {
if (err) {
done(err)
@ -243,6 +247,10 @@ module.exports = function(RED) {
return done(undefined,msg);
} else if (rule.pt === 'flow' || rule.pt === 'global') {
var contextKey = RED.util.parseContextStore(property);
if (/\[msg/.test(contextKey.key)) {
// The key has a nest msg. reference to evaluate first
contextKey.key = RED.util.normalisePropertyExpression(contextKey.key, msg, true)
}
var target = node.context()[rule.pt];
var callback = err => {
if (err) {

View File

@ -52,7 +52,7 @@
color:"darksalmon",
defaults: {
command: {value:""},
addpay: {value:true},
addpay: {value:false},
append: {value:""},
useSpawn: {value:"false"},
timer: {value:""},

View File

@ -31,7 +31,7 @@ module.exports = function(RED) {
this.timer = Number(n.timer || 0)*1000;
this.activeProcesses = {};
this.oldrc = (n.oldrc || false).toString();
this.execOpt = {encoding:'binary', maxBuffer:10000000};
this.execOpt = {encoding:'binary', maxBuffer:RED.settings.execMaxBufferSize||10000000};
var node = this;
if (process.platform === 'linux' && fs.existsSync('/bin/bash')) { node.execOpt.shell = '/bin/bash'; }

View File

@ -235,6 +235,7 @@
oneditprepare: function() {
var previous = null;
$("#node-input-out").on('focus', function () { previous = this.value; }).on("change", function() {
$("#node-input-splitc").show();
if (previous === null) { previous = $("#node-input-out").val(); }
if ($("#node-input-out").val() == "char") {
if (previous != "char") { $("#node-input-splitc").val("\\n"); }
@ -247,6 +248,7 @@
else if ($("#node-input-out").val() == "immed") {
if (previous != "immed") { $("#node-input-splitc").val(" "); }
$("#node-units").text("");
$("#node-input-splitc").hide();
}
else if ($("#node-input-out").val() == "count") {
if (previous != "count") { $("#node-input-splitc").val("12"); }
@ -255,6 +257,7 @@
else {
if (previous != "sit") { $("#node-input-splitc").val(" "); }
$("#node-units").text("");
$("#node-input-splitc").hide();
}
});
}

View File

@ -737,7 +737,10 @@ module.exports = function(RED) {
group.msg = Object.assign(group.msg, msg);
group.send = send;
var tcnt = group.targetCount;
if (msg.hasOwnProperty("parts")) { tcnt = group.targetCount || msg.parts.count; }
if (msg.hasOwnProperty("parts")) {
tcnt = group.targetCount || msg.parts.count;
group.targetCount = tcnt;
}
if ((tcnt > 0 && group.currentCount >= tcnt) || msg.hasOwnProperty('complete')) {
completeSend(partId);
}
@ -759,3 +762,4 @@ module.exports = function(RED) {
}
RED.nodes.registerType("join",JoinNode);
}

View File

@ -0,0 +1,113 @@
[
{
"id": "84222b92.d65d18",
"type": "inject",
"z": "194a3e4f.a92772",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "Hello, World!",
"payloadType": "str",
"x": 230,
"y": 220,
"wires": [
[
"b4b9f603.739598"
]
]
},
{
"id": "7b014430.dfd94c",
"type": "comment",
"z": "194a3e4f.a92772",
"name": "Write string to a file, then read from the file",
"info": "File-in node can read string from a file.",
"x": 260,
"y": 140,
"wires": []
},
{
"id": "b4b9f603.739598",
"type": "file",
"z": "194a3e4f.a92772",
"name": "",
"filename": "/tmp/hello.txt",
"appendNewline": true,
"createDir": false,
"overwriteFile": "true",
"encoding": "none",
"x": 420,
"y": 220,
"wires": [
[
"6dc01cac.5c4bf4"
]
]
},
{
"id": "2587adb9.7e60f2",
"type": "debug",
"z": "194a3e4f.a92772",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 810,
"y": 220,
"wires": []
},
{
"id": "6dc01cac.5c4bf4",
"type": "file in",
"z": "194a3e4f.a92772",
"name": "",
"filename": "/tmp/hello.txt",
"format": "utf8",
"chunk": false,
"sendError": false,
"encoding": "none",
"x": 620,
"y": 220,
"wires": [
[
"2587adb9.7e60f2"
]
]
},
{
"id": "f4b4309a.3b78a",
"type": "comment",
"z": "194a3e4f.a92772",
"name": "↑read result from file",
"info": "",
"x": 630,
"y": 260,
"wires": []
},
{
"id": "672d3693.3cabd8",
"type": "comment",
"z": "194a3e4f.a92772",
"name": "↓write to /tmp/hello.txt",
"info": "",
"x": 440,
"y": 180,
"wires": []
}
]

View File

@ -0,0 +1,113 @@
[
{
"id": "8997398f.c5d628",
"type": "inject",
"z": "194a3e4f.a92772",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "😀",
"payloadType": "str",
"x": 210,
"y": 480,
"wires": [
[
"56e32d23.050f44"
]
]
},
{
"id": "4e598e65.1799d",
"type": "comment",
"z": "194a3e4f.a92772",
"name": "Read data in specified encoding",
"info": "File-in node can specify encoding of data read from a file.",
"x": 230,
"y": 400,
"wires": []
},
{
"id": "56e32d23.050f44",
"type": "file",
"z": "194a3e4f.a92772",
"name": "",
"filename": "/tmp/hello.txt",
"appendNewline": true,
"createDir": false,
"overwriteFile": "true",
"encoding": "none",
"x": 380,
"y": 480,
"wires": [
[
"38fa0579.f2cd8a"
]
]
},
{
"id": "d28c8994.99c0a8",
"type": "debug",
"z": "194a3e4f.a92772",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 770,
"y": 480,
"wires": []
},
{
"id": "38fa0579.f2cd8a",
"type": "file in",
"z": "194a3e4f.a92772",
"name": "",
"filename": "/tmp/hello.txt",
"format": "utf8",
"chunk": false,
"sendError": false,
"encoding": "base64",
"x": 580,
"y": 480,
"wires": [
[
"d28c8994.99c0a8"
]
]
},
{
"id": "fa22ca20.ae4528",
"type": "comment",
"z": "194a3e4f.a92772",
"name": "↑read data from file as base64 string",
"info": "",
"x": 640,
"y": 520,
"wires": []
},
{
"id": "148e25ad.98891a",
"type": "comment",
"z": "194a3e4f.a92772",
"name": "↓write to /tmp/hello.txt",
"info": "",
"x": 400,
"y": 440,
"wires": []
}
]

View File

@ -0,0 +1,132 @@
[
{
"id": "6a0b1d03.d4cee4",
"type": "inject",
"z": "194a3e4f.a92772",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 220,
"y": 740,
"wires": [
[
"d4b00cb7.a5a23"
]
]
},
{
"id": "f17ea1d1.8ecc3",
"type": "comment",
"z": "194a3e4f.a92772",
"name": "Read data breaking lines into individual messages",
"info": "File-in node can break read text into messages with individual lines",
"x": 290,
"y": 660,
"wires": []
},
{
"id": "99ae7806.1d6428",
"type": "file",
"z": "194a3e4f.a92772",
"name": "",
"filename": "/tmp/hello.txt",
"appendNewline": true,
"createDir": false,
"overwriteFile": "true",
"encoding": "none",
"x": 540,
"y": 740,
"wires": [
[
"70d7892f.d27db8"
]
]
},
{
"id": "7ed8282c.92b338",
"type": "debug",
"z": "194a3e4f.a92772",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 810,
"y": 800,
"wires": []
},
{
"id": "70d7892f.d27db8",
"type": "file in",
"z": "194a3e4f.a92772",
"name": "",
"filename": "/tmp/hello.txt",
"format": "lines",
"chunk": false,
"sendError": false,
"encoding": "none",
"x": 620,
"y": 800,
"wires": [
[
"7ed8282c.92b338"
]
]
},
{
"id": "c1b7e05.1d94b2",
"type": "comment",
"z": "194a3e4f.a92772",
"name": "↑read data from file breaking lines into messages",
"info": "",
"x": 720,
"y": 840,
"wires": []
},
{
"id": "a5f647b2.cf27a8",
"type": "comment",
"z": "194a3e4f.a92772",
"name": "↓write to /tmp/hello.txt",
"info": "",
"x": 560,
"y": 700,
"wires": []
},
{
"id": "d4b00cb7.a5a23",
"type": "template",
"z": "194a3e4f.a92772",
"name": "data",
"field": "payload",
"fieldType": "msg",
"format": "handlebars",
"syntax": "plain",
"template": "one\ntwo\nthree!",
"output": "str",
"x": 370,
"y": 740,
"wires": [
[
"99ae7806.1d6428"
]
]
}
]

View File

@ -0,0 +1,201 @@
[
{
"id": "bdd57acc.2edc48",
"type": "inject",
"z": "194a3e4f.a92772",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 220,
"y": 1040,
"wires": [
[
"7a069b01.0c2324"
]
]
},
{
"id": "1fd12220.33953e",
"type": "comment",
"z": "194a3e4f.a92772",
"name": "Creating a message stream from lines of data",
"info": "File-in node can break read text into messages with individual lines. The messages creates a stream of messages.",
"x": 270,
"y": 960,
"wires": []
},
{
"id": "ab6eb213.2a08d",
"type": "file",
"z": "194a3e4f.a92772",
"name": "",
"filename": "/tmp/hello.txt",
"appendNewline": true,
"createDir": false,
"overwriteFile": "true",
"encoding": "none",
"x": 540,
"y": 1040,
"wires": [
[
"b7ed49b0.649fb8"
]
]
},
{
"id": "c48d8ae0.9ff3a8",
"type": "debug",
"z": "194a3e4f.a92772",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 810,
"y": 1140,
"wires": []
},
{
"id": "b7ed49b0.649fb8",
"type": "file in",
"z": "194a3e4f.a92772",
"name": "",
"filename": "/tmp/hello.txt",
"format": "lines",
"chunk": false,
"sendError": false,
"encoding": "none",
"x": 280,
"y": 1140,
"wires": [
[
"83073ebe.fcce4"
]
]
},
{
"id": "3c33e69f.6a04ba",
"type": "comment",
"z": "194a3e4f.a92772",
"name": "↑read data from file breaking lines into messages",
"info": "",
"x": 380,
"y": 1180,
"wires": []
},
{
"id": "3598bf7d.5712a",
"type": "comment",
"z": "194a3e4f.a92772",
"name": "↓write to /tmp/hello.txt",
"info": "",
"x": 560,
"y": 1000,
"wires": []
},
{
"id": "7a069b01.0c2324",
"type": "template",
"z": "194a3e4f.a92772",
"name": "data",
"field": "payload",
"fieldType": "msg",
"format": "handlebars",
"syntax": "plain",
"template": "Apple\nBanana\nGrape\nOrange",
"output": "str",
"x": 370,
"y": 1040,
"wires": [
[
"ab6eb213.2a08d"
]
]
},
{
"id": "8d4ed1d0.821fe",
"type": "join",
"z": "194a3e4f.a92772",
"name": "",
"mode": "auto",
"build": "string",
"property": "payload",
"propertyType": "msg",
"key": "topic",
"joiner": "\\n",
"joinerType": "str",
"accumulate": "false",
"timeout": "",
"count": "",
"reduceRight": false,
"x": 630,
"y": 1140,
"wires": [
[
"c48d8ae0.9ff3a8"
]
]
},
{
"id": "83073ebe.fcce4",
"type": "switch",
"z": "194a3e4f.a92772",
"name": "< D",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "lt",
"v": "D",
"vt": "str"
}
],
"checkall": "true",
"repair": true,
"outputs": 1,
"x": 470,
"y": 1140,
"wires": [
[
"8d4ed1d0.821fe"
]
]
},
{
"id": "2088e195.f7aebe",
"type": "comment",
"z": "194a3e4f.a92772",
"name": "↓filter data before \"D\"",
"info": "",
"x": 520,
"y": 1100,
"wires": []
},
{
"id": "b848cdc7.61e06",
"type": "comment",
"z": "194a3e4f.a92772",
"name": "↑join to single string",
"info": "",
"x": 670,
"y": 1180,
"wires": []
}
]

View File

@ -0,0 +1,113 @@
[
{
"id": "84222b92.d65d18",
"type": "inject",
"z": "4b63452d.672afc",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "Hello, World!",
"payloadType": "str",
"x": 230,
"y": 200,
"wires": [
[
"b4b9f603.739598"
]
]
},
{
"id": "7b014430.dfd94c",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Write string to a file, then read from the file",
"info": "File node can write string to a file.",
"x": 260,
"y": 120,
"wires": []
},
{
"id": "b4b9f603.739598",
"type": "file",
"z": "4b63452d.672afc",
"name": "",
"filename": "/tmp/hello.txt",
"appendNewline": true,
"createDir": false,
"overwriteFile": "true",
"encoding": "none",
"x": 420,
"y": 200,
"wires": [
[
"6dc01cac.5c4bf4"
]
]
},
{
"id": "2587adb9.7e60f2",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 810,
"y": 200,
"wires": []
},
{
"id": "6dc01cac.5c4bf4",
"type": "file in",
"z": "4b63452d.672afc",
"name": "",
"filename": "/tmp/hello.txt",
"format": "utf8",
"chunk": false,
"sendError": false,
"encoding": "none",
"x": 620,
"y": 200,
"wires": [
[
"2587adb9.7e60f2"
]
]
},
{
"id": "f4b4309a.3b78a",
"type": "comment",
"z": "4b63452d.672afc",
"name": "↑read result from file",
"info": "",
"x": 630,
"y": 240,
"wires": []
},
{
"id": "672d3693.3cabd8",
"type": "comment",
"z": "4b63452d.672afc",
"name": "↓write to /tmp/hello.txt",
"info": "",
"x": 440,
"y": 160,
"wires": []
}
]

View File

@ -0,0 +1,118 @@
[
{
"id": "704479e1.399388",
"type": "inject",
"z": "4b63452d.672afc",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "filename",
"v": "/tmp/hello.txt",
"vt": "str"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "Hello, World!",
"payloadType": "str",
"x": 230,
"y": 400,
"wires": [
[
"402f3b7e.988014"
]
]
},
{
"id": "8e876a75.e9beb8",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Write string to a file specied by filename property, the read from the file",
"info": "File node can target file using `filename` property.",
"x": 350,
"y": 320,
"wires": []
},
{
"id": "402f3b7e.988014",
"type": "file",
"z": "4b63452d.672afc",
"name": "",
"filename": "",
"appendNewline": true,
"createDir": false,
"overwriteFile": "true",
"encoding": "none",
"x": 390,
"y": 400,
"wires": [
[
"26e077d6.bbcd98"
]
]
},
{
"id": "97b6b6b2.a54b38",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 770,
"y": 400,
"wires": []
},
{
"id": "26e077d6.bbcd98",
"type": "file in",
"z": "4b63452d.672afc",
"name": "",
"filename": "/tmp/hello.txt",
"format": "utf8",
"chunk": false,
"sendError": false,
"encoding": "none",
"x": 580,
"y": 400,
"wires": [
[
"97b6b6b2.a54b38"
]
]
},
{
"id": "85062297.da79",
"type": "comment",
"z": "4b63452d.672afc",
"name": "↑read result from file",
"info": "",
"x": 590,
"y": 440,
"wires": []
},
{
"id": "7316c4fc.b1dcdc",
"type": "comment",
"z": "4b63452d.672afc",
"name": "↓write to file specified by filename property",
"info": "",
"x": 500,
"y": 360,
"wires": []
}
]

View File

@ -0,0 +1,85 @@
[
{
"id": "4ac00fb0.d5f52",
"type": "inject",
"z": "4b63452d.672afc",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 220,
"y": 600,
"wires": [
[
"542cc2f4.92857c"
]
]
},
{
"id": "671f8295.0e6f6c",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Delete a file",
"info": "File node can delete a file.",
"x": 170,
"y": 540,
"wires": []
},
{
"id": "542cc2f4.92857c",
"type": "file",
"z": "4b63452d.672afc",
"name": "",
"filename": "/tmp/hello.txt",
"appendNewline": true,
"createDir": false,
"overwriteFile": "delete",
"encoding": "none",
"x": 420,
"y": 600,
"wires": [
[
"a24da523.5babe8"
]
]
},
{
"id": "a24da523.5babe8",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 630,
"y": 600,
"wires": []
},
{
"id": "51157051.2f62",
"type": "comment",
"z": "4b63452d.672afc",
"name": "↓delete a file",
"info": "",
"x": 390,
"y": 560,
"wires": []
}
]

View File

@ -0,0 +1,113 @@
[
{
"id": "e4ef1f5e.7cd82",
"type": "inject",
"z": "4b63452d.672afc",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "8J+YgA==",
"payloadType": "str",
"x": 220,
"y": 820,
"wires": [
[
"72b37cc8.177054"
]
]
},
{
"id": "f5997af4.5a9298",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Specify encoding of written data",
"info": "File node can specify encoding of data.",
"x": 230,
"y": 740,
"wires": []
},
{
"id": "72b37cc8.177054",
"type": "file",
"z": "4b63452d.672afc",
"name": "",
"filename": "/tmp/hello.txt",
"appendNewline": true,
"createDir": false,
"overwriteFile": "true",
"encoding": "base64",
"x": 400,
"y": 820,
"wires": [
[
"2da33ec.f45cac2"
]
]
},
{
"id": "2e814354.278c8c",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 790,
"y": 820,
"wires": []
},
{
"id": "2da33ec.f45cac2",
"type": "file in",
"z": "4b63452d.672afc",
"name": "",
"filename": "/tmp/hello.txt",
"format": "utf8",
"chunk": false,
"sendError": false,
"encoding": "none",
"x": 600,
"y": 820,
"wires": [
[
"2e814354.278c8c"
]
]
},
{
"id": "ec754c99.84bfd",
"type": "comment",
"z": "4b63452d.672afc",
"name": "↓write string with base64 encoding",
"info": "",
"x": 460,
"y": 780,
"wires": []
},
{
"id": "3e6704ff.4ce25c",
"type": "comment",
"z": "4b63452d.672afc",
"name": "↑read result from file",
"info": "",
"x": 610,
"y": 860,
"wires": []
}
]

View File

@ -0,0 +1,108 @@
[
{
"id": "84222b92.d65d18",
"type": "inject",
"z": "a7ac8a68.0f7218",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "Hello, World!",
"payloadType": "str",
"x": 230,
"y": 160,
"wires": [
[
"b4b9f603.739598"
]
]
},
{
"id": "7b014430.dfd94c",
"type": "comment",
"z": "a7ac8a68.0f7218",
"name": "Watch changes of a file",
"info": "Watch node can watch and report changes of a file.",
"x": 200,
"y": 80,
"wires": []
},
{
"id": "b4b9f603.739598",
"type": "file",
"z": "a7ac8a68.0f7218",
"name": "",
"filename": "/tmp/hello.txt",
"appendNewline": true,
"createDir": false,
"overwriteFile": "true",
"encoding": "none",
"x": 420,
"y": 160,
"wires": [
[]
]
},
{
"id": "672d3693.3cabd8",
"type": "comment",
"z": "a7ac8a68.0f7218",
"name": "↓write to /tmp/hello.txt",
"info": "",
"x": 440,
"y": 120,
"wires": []
},
{
"id": "15f1f5aa.506ffa",
"type": "watch",
"z": "a7ac8a68.0f7218",
"name": "",
"files": "/tmp/hello.txt",
"recursive": "",
"x": 410,
"y": 200,
"wires": [
[
"a91562b9.ca805"
]
]
},
{
"id": "a91562b9.ca805",
"type": "debug",
"z": "a7ac8a68.0f7218",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 610,
"y": 200,
"wires": []
},
{
"id": "2ab4eba8.267d64",
"type": "comment",
"z": "a7ac8a68.0f7218",
"name": "↑watch changes of /tmp/hello.txt",
"info": "",
"x": 470,
"y": 240,
"wires": []
}
]

View File

@ -0,0 +1,163 @@
[
{
"id": "acec9dcc.eca82",
"type": "inject",
"z": "a7ac8a68.0f7218",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "Hello, World!",
"payloadType": "str",
"x": 230,
"y": 460,
"wires": [
[
"137d60e2.0e267f"
]
]
},
{
"id": "cf011d1e.8afa6",
"type": "comment",
"z": "a7ac8a68.0f7218",
"name": "Watch changes in a directory",
"info": "Watch node can watch and report changes in a directory",
"x": 220,
"y": 340,
"wires": []
},
{
"id": "137d60e2.0e267f",
"type": "file",
"z": "a7ac8a68.0f7218",
"name": "",
"filename": "/tmp/HG/hello.txt",
"appendNewline": true,
"createDir": false,
"overwriteFile": "true",
"encoding": "none",
"x": 430,
"y": 460,
"wires": [
[]
]
},
{
"id": "9dfce283.63e5a",
"type": "comment",
"z": "a7ac8a68.0f7218",
"name": "↓write to /tmp/HG/*",
"info": "",
"x": 430,
"y": 420,
"wires": []
},
{
"id": "cbfb788b.297f98",
"type": "watch",
"z": "a7ac8a68.0f7218",
"name": "",
"files": "/tmp/HG",
"recursive": false,
"x": 400,
"y": 540,
"wires": [
[
"3c691cd5.a0f2b4"
]
]
},
{
"id": "3c691cd5.a0f2b4",
"type": "debug",
"z": "a7ac8a68.0f7218",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 550,
"y": 540,
"wires": []
},
{
"id": "c0d7ca6e.cad418",
"type": "comment",
"z": "a7ac8a68.0f7218",
"name": "↑watch changes in /tmp/HG",
"info": "",
"x": 460,
"y": 580,
"wires": []
},
{
"id": "a04e5231.5a2e1",
"type": "inject",
"z": "a7ac8a68.0f7218",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "Goodbye, World!",
"payloadType": "str",
"x": 240,
"y": 500,
"wires": [
[
"655d7bab.cda6f4"
]
]
},
{
"id": "655d7bab.cda6f4",
"type": "file",
"z": "a7ac8a68.0f7218",
"name": "",
"filename": "/tmp/HG/goodbye.txt",
"appendNewline": true,
"createDir": false,
"overwriteFile": "true",
"encoding": "none",
"x": 440,
"y": 500,
"wires": [
[]
]
},
{
"id": "f3b42209.a7673",
"type": "comment",
"z": "a7ac8a68.0f7218",
"name": "Notice: Create /tmp/HG directory before deploying this example",
"info": "",
"x": 330,
"y": 380,
"wires": []
}
]

View File

@ -20,19 +20,18 @@
<p>Es gibt drei Modi für die Erstellung von Nachrichtensequenzen:</p>
<dl>
<dt>Anzahl der Nachrichten</dt>
<dd>Die Nachrichten werden zu Sequenzen einer bestimmten Länge gruppiert. Die Option <b>Überlappung</b>>
<dd>Die Nachrichten werden zu Sequenzen einer bestimmten Länge gruppiert. Die Option <b>Überlappung</b>
gibt an, wie viele Nachrichten vom Ende einer Sequenz am Anfang der nächsten Sequenz wiederholt werden sollen.</dd>
<dt>Zeitintervall</dt>
<dd>Gruppiert Nachrichten, die innerhalb des angegebenen Intervalls eingehen. Wenn keine Nachrichten
innerhalb des Intervalls ankommen, kann der Node optional eine leere Nachricht senden.</dd>
<dt>Sequenzesn verketten/dt>
<dt>Sequenzen verketten/dt>
<dd>Erzeugt eine Nachrichtensequenz durch die Verkettung eingehender Sequenzen. Jede Nachricht muss eine <code>msg.topic</code>
und eine <code>msg.parts</code> Eigenschaft haben, um die Sequenz identifizieren zu können.
Der Node ist mit einer Liste von <code>topic</code> konfiguriert,
mit der die Reihnmefolge der Verkettung der Sequenzen definiert wird.
</dd>
mit der die Reihenfolge der Verkettung der Sequenzen definiert wird.</dd>
</dl>
<h4>Speichern der Nachrichten</h4>

View File

@ -16,7 +16,7 @@
<script type="text/html" data-help-name="inject">
<p>
Запускает сообщение в поток вручную или через равные промежутки времени. Данные сообщения могут быть различных типов, включая строку, объект JavaScript или текущее время.
Вбрасывает сообщение в поток вручную или через равные промежутки времени. Данные в сообщении могут быть различных типов, включая строку, объект JavaScript или метку текущего времени.
</p>
<h3>Выводит</h3>
@ -29,26 +29,24 @@
<h3>Подробности</h3>
<p>
Узел Inject может инициировать поток с определенными данными (значение payload). Данные по умолчанию - это отметка текущего времени в миллисекундах с 1 января 1970 года.
Узел Inject может инициировать выполнение потока с определенными данными (значение payload). Данные по умолчанию - это метка текущего времени в миллисекундах, прошедших с 1 января 1970 года.
</p>
<p>
Узел также поддерживает вывод строк, чисел, логических значений, объектов JavaScript или значений потоковых/глобальных контекстов.
</p>
<p>
По умолчанию узел запускается вручную при нажатии на его кнопку в редакторе. Его также можно настроить для автоматического запуска через равные промежутки времени или по расписанию.
По умолчанию узел запускается вручную при нажатии его кнопки в редакторе. Его также можно настроить на автоматический запуск через равные промежутки времени или по расписанию.
</p>
<p>
Он также может быть настроен так, чтобы делать вывод один раз при каждом запуске потоков.
Он также может быть настроен однократный вброс сообщения при каждом (пере)запуске потоков.
</p>
<p>
Максимальный <i>интервал</i>, который можно указать, составляет около 596 часов / 24 дней. Однако если Вам нужны интервалы, превышающие один день, Вам следует рассмотреть возможность использования узла планировщика, который может работать с перебоями электроэнергии и перезапусками.
Максимальный <i>интервал</i>, который можно указать, составляет около 596 часов / 24 дней. Однако если Вам нужны интервалы, превышающие один день, Вам следует рассмотреть возможность использования функций планировщика в узле, которые смогут корректно работать с перебоями электроэнергии и перезапусками.
</p>
<p>
<b>Примечание</b>: В параметрах <i>"с интервалом в промежутке"</i> и <i>"в определенное время"</i> используется стандартная система cron.
Это означает, что 20 минут будут в следующем часу, 20 минут спустя и 40 минут спустя - а не через 20 минут.
Если нужно каждые 20 минут - используйте параметр <i>"с интервалом"</i>.
<b>Примечание</b>: В параметрах <i>"с интервалом в промежутке"</i> и <i>"в определенное время"</i> используется стандартная система "cron". Это означает, что "20 минут" будут в следующем часу, 20 минут спустя и 40 минут спустя - а не через 20 минут. Если нужен вброс сообщений каждые 20 минут - используйте параметр <i>"с интервалом"</i>.
</p>
<p>
<b>Примечание</b>: Чтобы включить многострочный текст в строковое значение, необходимо использовать узел Function для создания данных.
<b>Примечание</b>: Чтобы включить многострочный текст в строковое значение, необходимо использовать узел Function для формирования данных.
</p>
</script>

View File

@ -16,24 +16,24 @@
<script type="text/html" data-help-name="debug">
<p>
Отображает выбранные свойства сообщения на боковой панели во вкладке отладки и, при необходимости, журнале выполнения. По умолчанию отображается содержимое <code>msg.payload</code>, но его можно настроить для отображения любого свойства, полного сообщения или результата выражения JSONata.
Отображает выбранные свойства получаемых узлом сообщения на боковой панели во вкладке отладки и, при необходимости, журнале среды выполнения. По умолчанию отображается содержимое <code>msg.payload</code>, но узел можно настроить на отображение любого свойства, всех свойств сообщения или результата выражения JSONata.
</p>
<h3>Подробности</h3>
<p>
Вкладка отладки на боковой панели обеспечивает структурированное представление отправляемых сообщений, что упрощает понимание их структуры.
Вкладка отладки на боковой панели обеспечивает структурированное представление полученных узлом сообщений, что упрощает исследование их структуры.
</p>
<p>
Объекты и массивы JavaScript могут быть свернуты и развернуты по мере необходимости. Буферные объекты могут отображаться в виде сырых данных или как строка, когда это возможно.
Объекты и массивы JavaScript могут быть свернуты и развернуты по мере необходимости. Буферы могут отображаться в виде данных как есть или в виде строки, когда это возможно.
</p>
<p>
Рядом с каждым сообщением отладочная панель содержит информацию о времени получения сообщения, узле, который его отправил, и типе сообщения.
Нажатие на идентификатор узла-источника, показывает этот узел в рабочей области.
Рядом с каждым сообщением отладочная панель показывает информацию о времени получения сообщения, узле, который его отправил, и типе данных.
Нажатие на идентификатор узла-источника покажет этот узел в рабочей области.
</p>
<p>
Кнопка на узле может использоваться для включения или отключения его вывода. Рекомендуется отключать или удалять любые отладочные узлы, которые не используются.
Кнопка на узле может использоваться для включения или отключения вывода информации о получаемых сообщениях. Рекомендуется отключать или удалять любые отладочные узлы, которые не используются.
</p>
<p>
Узел также может быть сконфигурирован для отправки всех сообщений в журнал выполнения или для отправки короткого (32 символа) текста в статус под узлом отладки.
Узел также может быть настроен на отправку всех сообщений в журнал выполнения или отправку короткого (32 символа) текста в статус под узлом.
</p>
</script>

View File

@ -16,7 +16,7 @@
<script type="text/html" data-help-name="complete">
<p>
Запустить поток, когда другой узел завершит обработку сообщения.
Инициирует запуск потока, когда другой узел завершает обработку сообщения.
</p>
<h3>Подробности</h3>
@ -24,12 +24,12 @@
Если узел сообщает среде выполнения о завершении обработки сообщения, этот узел можно использовать для запуска второго потока.
</p>
<p>
Например, это можно использовать вместе с узлом без выходного порта, таким как узел Email, для продолжения потока.
Например, его можно использовать вместе с узлом, у которого нет выходного порта, таким как узел Email, для продолжения потока.
</p>
<p>
Этот узел должен быть настроен для обработки события выбранных узлов в потоке. В отличие от узла Catch, он не предоставляет режим 'обрабатывать все' для автоматического применения ко всем узлам в потоке.
Этот узел должен быть настроен на отслеживание событий выбранных узлов в потоке. В отличие от узла Catch, у него нет режима 'обрабатывать все' для автоматического применения ко всем узлам в потоке.
</p>
<p>
Не все узлы будут инициировать это событие - это будет зависеть от того, была ли в них реализована поддержка этой функции, добавленной в Node-RED 1.0.
Не все узлы могут инициировать запуск потока по завршению обработки сообщения. все зависит от того, была ли в них реализована поддержка этой функции, добавленной в Node-RED 1.0, или нет.
</p>
</script>

View File

@ -16,7 +16,7 @@
<script type="text/html" data-help-name="catch">
<p>
Ловит ошибки, выдаваемые узлами на той же вкладке.
Ловит ошибки, выбрасываемые узлами на той же вкладке.
</p>
<h3>Выводит</h3>
@ -28,21 +28,21 @@
<dt>error.source.type <span class="property-type">строка</span></dt>
<dd>тип узла, выдавшего ошибку.</dd>
<dt>error.source.name <span class="property-type">строка</span></dt>
<dd>имя, если установлено, узла, выдавшего ошибку.</dd>
<dd>имя узла, выдавшего ошибку, если было настроено.</dd>
</dl>
<h3>Подробности</h3>
<p>
Если узел выдает ошибку во время обработки сообщения, поток обычно останавливается. Этот узел можно использовать для отлова этих ошибок и обработки их с помощью отдельного потока.
Если узел выбрасывает ошибку во время обработки сообщения, поток обычно останавливается. Этот узел можно использовать для отлова и обработки таких ошибок с помощью отдельного потока.
</p>
<p>
По умолчанию узел будет отлавливать ошибки, генерируемые любым узлом на той же вкладке. По желанию он может быть нацелен на определенные узлы или настроен на перехват только тех ошибок, которые не были еще перехвачены 'нацеленным' Catch узлом.
По умолчанию узел будет отлавливать ошибки, генерируемые любым узлом на той же вкладке. По желанию он может быть нацелен на определенные узлы или настроен на перехват только тех ошибок, которые не были перехвачены 'нацеленным' Catch узлом.
</p>
<p>
Когда выдается ошибка, все соответствующие Catch узлы получат сообщение.
</p>
<p>
Если ошибка выдается в подпотоке, она будет обработана любыми Catch узлами внутри подпотока. Если таковых нет, ошибка будет распространена до вкладки, на которой находится экземпляр подпотока.
Если ошибка выбрасывается в подпотоке, она будет обработана любыми Catch узлами внутри подпотока. Если таковых нет, ошибка будет распространена до вкладки, на которой находится экземпляр подпотока.
</p>
<p>
Если сообщение уже имеет свойство <code>error</code>, оно копируется в <code>_error</code>.

View File

@ -16,7 +16,7 @@
<script type="text/html" data-help-name="status">
<p>
Сообщает о статусах других узлов на той же вкладке.
Инициирует запуск потока при изменении статуса других узлов на той же вкладке.
</p>
<h3>Выводит</h3>
@ -28,7 +28,7 @@
<dt>status.source.id <span class="property-type">строка</span></dt>
<dd>идентификатор узла, сообщившего о статусе.</dd>
<dt>status.source.name <span class="property-type">строка</span></dt>
<dd>имя, если установлено, узла, сообщившего о статусе.</dd>
<dd>имя узла, сообщившего о статусе, если настроено.</dd>
</dl>
<h3>Подробности</h3>
@ -36,6 +36,6 @@
Этот узел не создает <code>payload</code> данные.
</p>
<p>
По умолчанию узел сообщает о статусах всех узлов на той же вкладке в рабочей области. Его можно настроить для выборочного отчета о статусе отдельных узлов.
По умолчанию узел сообщает о статусах всех узлов на той же вкладке в рабочей области. Его можно настроить на выборочное отслеживание статуса только отдельных узлов.
</p>
</script>

View File

@ -21,13 +21,13 @@
<h3>Подробности</h3>
<p>
Этот узел может быть подключен к любому узлу <code>link out</code> на любой вкладке. После подключения они ведут себя так, как если бы они были соединены вместе обычным проводом.
Этот узел может быть подключен к любому <code>link out</code> узлу на любой вкладке. После подключения они ведут себя так, как если бы они были соединены вместе обычным проводом.
</p>
<p>
Связи между link-узлами отображаются, только когда выбран link-узел. Если есть какие-либо провода к другим вкладкам, отображается виртуальный узел, по которому можно кликнуть, чтобы перейти на соответствующую вкладку.
Связи между link-узлами отображаются, только когда выбран один из соединенных link узлов. Если есть какие-либо провода, ведущие на другие вкладки, они отображаются в виде виртуального узла, по которому можно кликнуть, чтобы перейти на соответствующую вкладку.
</p>
<p>
<b>Примечание:</b> Связи не могут идти внутрь подпотока или изнутри подпотока наружу.
<b>Примечание:</b> Провод не может вести внутрь подпотока или изнутри подпотока наружу.
</p>
</script>
@ -38,12 +38,12 @@
<h3>Подробности</h3>
<p>
Узел может быть подключен к любому узлу <code>link in</code> на любой вкладке. После подключения они ведут себя так, как если бы они были соединены вместе обычным проводом.
Узел может быть подключен к любому <code>link in</code> узлу на любой вкладке. После подключения они ведут себя так, как если бы они были соединены вместе обычным проводом.
</p>
<p>
Связи между link-узлами отображаются, только когда выбран link-узел. Если есть какие-либо провода к другим вкладкам, отображается виртуальный узел, по которому можно кликнуть, чтобы перейти на соответствующую вкладку.
Связи между link-узлами отображаются, только когда выбран один из соединенных link узлов. Если есть какие-либо провода, ведущие на другие вкладки, они отображаются в виде виртуального узла, по которому можно кликнуть, чтобы перейти на соответствующую вкладку.
</p>
<p>
<b>Примечание:</b> Связи не могут идти внутрь подпотока или изнутри подпотока наружу.
<b>Примечание:</b> Провод не может вести внутрь подпотока или изнутри подпотока наружу.
</p>
</script>

View File

@ -16,11 +16,11 @@
<script type="text/html" data-help-name="comment">
<p>
Узел, который Вы можете использовать для добавления комментариев к Вашим потокам.
Узел, который можно использовать для добавления комментариев к вашим потокам.
</p>
<h3>Подробности</h3>
<p>
Панель редактирования поддерживает синтаксис Markdown. Текст будет отображен как 'описание' на этой информационной вкладке.
Окно редактирования поддерживает синтаксис Markdown. Текст будет отображен на информационной вкладке боковой панели в секции 'описание'.
</p>
</script>

View File

@ -16,22 +16,22 @@
<script type="text/html" data-help-name="unknown">
<p>
Тип этого узла неизвестен Вашему Node-RED.
Тип этого узла неизвестен вашему Node-RED.
</p>
<h3>Подробности</h3>
<p>
<i>
Если Вы развернете узел в этом состоянии, его конфигурация будет сохранена, но поток не будет запущен, пока не будет установлен отсутствующий тип.
Если вы развернете узел в этом состоянии, его настройки будут сохранены, но поток не будет запущен до тех пор, пока не будет установлен отсутствующий тип.
</i>
</p>
<p>
Используйте опцию <code>Меню - Управление палитрой</code> для поиска и установки узлов или <b>npm install &lt;модуль&gt;</b> для установки любых отсутствующих модулей и перезапустите Node-RED и повторно импортируйте узлы.
Используйте опцию <code>Меню - Управление палитрой</code> для поиска и установки узлов или <b>npm install &lt;модуль&gt;</b> для установки отсутствующих модулей. Затем перезапустите Node-RED и повторно импортируйте узлы.
</p>
<p>
Возможно, этот тип узла уже установлен, но отсутствуют какие-то из его зависимостей. Проверьте журнал запуска Node-RED на наличие сообщений об ошибках, связанных с отсутствующим типом узла.
Возможно, этот тип узла уже установлен, но отсутствуют какие-то из его зависимостей. Проверьте журнал выполнения Node-RED на наличие сообщений об ошибках, связанных с отсутствующим типом узла.
</p>
<p>
В противном случае Вам следует связаться с автором потока, чтобы получить копию отсутствующего типа узла.
В противном случае вам следует связаться с автором потока, чтобы получить отсутствующий тип узла.
</p>
</script>

View File

@ -16,22 +16,22 @@
<script type="text/html" data-help-name="function">
<p>
Блок с JavaScript функцией, написанной во вкладке <b>Функция</b>, которая исполняется для сообщений, получаемых узлом.
Исполняет JavaScript функцию (введенную в настройках, во вкладке <b>Функция</b>) для всех получаемых узлом сообщений.
</p>
<p>
Сообщения передаются в виде объекта JavaScript с именем <code>msg</code>.
</p>
<p>
Обычно он имеет свойство <code>msg.payload</code>, содержащее тело сообщения.
Обычно у объекта есть свойство <code>msg.payload</code>, содержащее тело сообщения.
</p>
<p>
Ожидается, что функция вернет объект сообщения (или несколько объектов сообщения), но может также ничего не возвращать, чтобы остановить поток.
Ожидается, что функция вернет объект сообщения (или несколько объектов сообщения), которые будут отправлены следующим узлам в потоке, но может также ничего не возвращать, чтобы остановить поток.
</p>
<p>
Код настройки, выполняемый один раз при запуске сервера или при развертывании новой конфигурации потока, можно указать во вкладке <b>Настройка</b>. Кроме того, код очистки, выполняемый при остановке или повторном развертывании узла, можно указать во вкладке <b>Закрытие</b>.
Код настройки, выполняемый один раз при запуске сервера или при развертывании новой конфигурации потока, можно ввести во вкладке <b>Настройка</b>. Также во вкладке <b>Закрытие</b> можно ввести код очистки, выполняемый при остановке или повторном развертывании узла.
</p>
<p>
Если код настройки возвращает объект Promise, обработка входящих сообщений узлом начнется после его завершения.
Если код настройки возвращает Promise объект, тогда обработка входящих сообщений узлом начнется после его завершения.
</p>
<h3>Подробности</h3>
@ -47,8 +47,8 @@
Она может вернуть/отправить:
</p>
<ul>
<li>один объект сообщения - передается узлам, подключенным к первому выходу</li>
<li>массив объектов сообщений - передается на узлы, подключенные к соответствующим выходам</li>
<li>один объект сообщения - передается узлам, подключенным к первому порту выхода</li>
<li>массив объектов сообщений - передается на узлы, подключенные к соответствующим портам выхода</li>
</ul>
<p>
Примечание: код настройки выполняется во время инициализации узлов. Таким образом, если на вкладке настройки кода вызывается <code>node.send</code>, последующие узлы могут не получить это сообщение.
@ -70,13 +70,13 @@
<li><code>node.error("Ошибка")</code></li>
</ul>
<p>
Узел Catch также может использоваться для обработки ошибок. Чтобы узел Catch мог ловить сообщение об ошибке, передайте <code>msg</code> в качестве второго аргумента в <code>node.error</code>:
Также узел Catch может использоваться для обработки ошибок. Чтобы можно было ловить оповещения об ошибке, при вызове <code>node.error</code> передайте <code>msg</code> в качестве второго аргумента:
</p>
<pre>node.error("Ошибка", msg);</pre>
<h4>Доступ к информации об узле</h4>
<p>
В функциональном блоке к идентификатору и имени узла можно обращаться, используя следующие свойства:
В функции можно обращаться к идентификатору и имени узла, используя следующие свойства:
</p>
<ul>
<li><code>node.id</code> - идентификатор узла</li>

View File

@ -16,18 +16,18 @@
<script type="text/html" data-help-name="switch">
<p>
Направляет сообщения по разным ветвям потока на основе значений свойств сообщения или его позиции в последовательности.
Направляет сообщения по разным ветвям потока, в завсимости от значений свойств сообщения или его позиции в последовательности.
</p>
<h3>Подробности</h3>
<p>
Когда приходит сообщение, узел выполняет проверку соответствия каждому из определенных правил и перенаправляет сообщение на их выходы.
Когда приходит сообщение, узел выполняет проверку соответствия сообщения каждому из установленных правил и перенаправляет сообщение на соответствующие выходы.
</p>
<p>
При желании узел может быть настроен на прекращение проверки правил, как только он найдет первое подходящее.
При желании узел может быть настроен на прекращение проверки правил после того как найдено первое подходящее.
</p>
<p>
Правила могут выполняться для отдельного свойства сообщения, свойства потокового или глобального контекста, переменной среды или результата выражения JSONata.
Правила могут применяться к свойству сообщения, свойству потокового или глобального контекста, переменной среды или результату выражения JSONata.
</p>
<h4>Правила</h4>
@ -35,8 +35,8 @@
Существует четыре типа правил:
</p>
<ol>
<li>Правила <b>value rules</b> сравниваются с указанным свойством</li>
<li>Правила <b>sequence rules</b> могут применяться для последовательностей сообщений, таких как сгенерированные узлом Split</li>
<li>Правила категории <b>value rules</b> сравнивают значение указанного свойства сообщения</li>
<li>Правила категории <b>sequence rules</b> могут применяться для последовательностей сообщений, таких как сгенерированные узлом Split</li>
<li><b>Выражение JSONata</b> исполняется для сообщения и считается подходящим, если оно возвращает истинное значение</li>
<li>Правило <b>иначе</b> используется для сообщений, не подходящих ни под одно из предыдущих правил.</li>
</ol>
@ -51,7 +51,7 @@
<h4>Обработка последовательностей сообщений</h4>
<p>
По умолчанию узел не изменяет свойство <code>msg.parts</code> у сообщений, которые являются частью последовательности.
По умолчанию узел не изменяет свойство <code>msg.parts</code> у сообщений, являющихся частью последовательности.
</p>
<p>
Можно включить параметр <b>пересоздавать последовательности сообщений</b> для создания новых последовательностей сообщений для каждого соответствующего правила. В этом режиме узел будет помещать в буфер всю входящую последовательность перед отправкой новых последовательностей. Настройка <code>nodeMessageBufferMaxLength</code> может быть использована для ограничения количества сообщений в буфере.

View File

@ -19,7 +19,7 @@
Устанавливает, изменяет, удаляет или перемещает свойства сообщения, контекста потока или глобального контекста.
</p>
<p>
В узле можно указать несколько правил, которые будут применяться в том порядке, в котором они определены.
В узле можно указать несколько операций, которые будут применяться в том порядке, в котором они заданы.
</p>
<h3>Подробности</h3>
@ -28,13 +28,13 @@
</p>
<dl class="message-properties">
<dt>Установить</dt>
<dd>установить свойство. Значение может быть разных типов или может быть взято из существующего свойства сообщения или контекста.</dd>
<dd>устанавливает свойство. Значение может быть различных типов или может быть взято из существующего свойства сообщения или контекста.</dd>
<dt>Изменить</dt>
<dd>найти &amp; заменить части свойства. Если используется регулярное выражение, настройка &quot;заменить на&quot; может включать группы захвата, например <code>$1</code>. При полном совпадении заменяет только тип.</dd>
<dd>ищет и заменяет части текста в свойстве сообщения. Если используется регулярное выражение, настройка &quot;заменить на&quot; может включать группы захвата, например <code>$1</code>. При полном совпадении заменяет только тип.</dd>
<dt>Удалить</dt>
<dd>удалить свойство.</dd>
<dd>удаляет свойство.</dd>
<dt>Переместить</dt>
<dd>переместить или переименовать свойство.</dd>
<dd>перемещает или переименовывает свойство.</dd>
</dl>
<p>
Тип свойства &quot;выражение&quot; использует язык запросов и выражений <a href="http://jsonata.org/" target="_new">JSONata</a>.

View File

@ -16,7 +16,7 @@
<script type="text/html" data-help-name="range">
<p>
Соотносит значение одного числового диапазона к другому числовому диапазону.
Соотносит значение одного числового диапазона с другим числовым диапазоном.
</p>
<h3>Принимает</h3>
@ -28,7 +28,7 @@
<h3>Выводит</h3>
<dl class="message-properties">
<dt>payload <span class="property-type">число</span></dt>
<dd>Значение, соотнесенное к новому диапазону.</dd>
<dd>Значение, соотнесенное с новым диапазоном.</dd>
</dl>
<h3>Подробности</h3>
@ -42,7 +42,7 @@
<i>Масштабировать и обернуть в целевой диапазон</i> означает, что результат будет обернут вокруг целевого диапазона:
</p>
<p>
Например, для входа 0 - 10 сопоставленного с 0 - 100.
Например, если входные 0 - 10 сопоставляются с 0 - 100:
</p>
<table style="outline-width:#888 solid thin">
<tr><th width="80px">режим</th><th width="80px">вход</th><th width="80px">выход</th></tr>

View File

@ -16,7 +16,7 @@
<script type="text/html" data-help-name="template">
<p>
Устанавливает свойство на основе предоставленного шаблона.
Генерирует текст по установленному шаблону и присваивает его выбранному свойству сообщения.
</p>
<h3>Принимает</h3>
@ -24,23 +24,23 @@
<dt>msg <span class="property-type">объект</span></dt>
<dd>Объект сообщения msg, содержащий информацию для заполнения шаблона.</dd>
<dt class="optional">шаблон <span class="property-type">строка</span></dt>
<dd>Шаблон для заполнения данными из сообщения msg. Если шаблон не настроен на панели редактирования, тогда он может быть установлен через свойство msg.template.</dd>
<dd>Шаблон текста для заполнения данными из сообщения msg. Если шаблон не введен на панели редактирования, тогда он может быть установлен через свойство msg.template.</dd>
</dl>
<h3>Выводит</h3>
<dl class="message-properties">
<dt>msg <span class="property-type">объект</span></dt>
<dd>Сообщение msg, у которого выбранному свойству присвоено значение, сформированное заполнением шаблона свойствами из входящего сообщения.</dd>
<dd>Сообщение msg, у которого выбранному свойству присвоено тексктовое значение, полученное после заполнения шаблона данными входящего сообщения.</dd>
</dl>
<h3>Подробности</h3>
<p>
По умолчанию используется формат <i><a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache</a></i>, но это может быть выключено при необходимости.
По умолчанию используется формат <i><a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache</a></i>. При необходимости это можно отключить.
</p>
<p>
Например, когда шаблон:
</p>
<pre>Привет, {{payload.name}}. Сегодня {{date}}</pre>
<pre>Привет, {{payload.name}}. Сегодня {{date}}.</pre>
<p>
получает сообщение msg, содержащее:
</p>
@ -51,13 +51,13 @@
}
}</pre>
<p>
Результирующее свойство будет:
Выбранному свойству будет присвоен текст:
</p>
<pre>Привет, Иван. Сегодня понедельник</pre>
<pre>Привет, Иван. Сегодня понедельник.</pre>
<p>
Можно использовать свойство из контекста потока или глобального контекста. Просто используйте <code>{{flow.name}}</code> или <code>{{global.name}}</code>, или для постоянного хранилища <code>store</code> используйте <code>{{flow[store].name}}</code> или <code>{{global[store].name}}</code>.
</p>
<p>
<b>Примечание:</b> по умолчанию <i>mustache</i> кодирует любые не алфавитно-цифровых или HTML-сущности в значениях, которые он подставляет, для безопасного использования в HTML. Чтобы предотвратить это, используйте тройные фигурные скобки <code>{{{triple}}}</code>.
<b>Примечание:</b> по умолчанию <i>mustache</i> заменяет определенные символы их escape-кодами для безопасного использования в HTML. Чтобы это не происходило, вы можете использовать тройные фигурные скобки <code>{{{triple}}}</code>.
</p>
</script>

View File

@ -21,20 +21,20 @@
<h3>Принимает</h3>
<dl class="message-properties">
<dt class="optional">delay <span class="property-type">число</span></dt>
<dd>Устанавливает задержку в миллисекундах, которая будет применена к сообщению. Этот параметр применяется только в том случае, если узел настроен на то, чтобы разрешать сообщению переопределять установленный интервал задержки по умолчанию.</dd>
<dd>Устанавливает задержку в миллисекундах, которая будет применена к сообщению. Этот параметр применяется только в том случае, если узел настроен так, чтобы разрешать сообщению переопределять установленный интервал задержки по умолчанию.</dd>
<dt class="optional">reset</dt>
<dd>Если в полученном сообщении этому свойству присвоено какое-либо значение, все ожидающие сообщения, удерживаемые узлом, очищаются без дальнейшей отправки.</dd>
<dd>Если в полученном сообщении этому свойству присвоено какое-либо значение, все ожидающие сообщения, удерживаемые узлом, сбрасываются без дальнейшей отправки.</dd>
<dt class="optional">flush</dt>
<dd>Если в полученном сообщении этому свойству присвоено какое-либо значение, все ожидающие сообщения, удерживаемые узлом, немедленно отправляются далее.</dd>
</dl>
<h3>Подробности</h3>
<p>
Когда настроено задерживать сообщения, интервал задержки может быть фиксированным значением, случайным значением в пределах диапазона или динамически установленным для каждого сообщения. Каждое сообщение задерживается независимо от любых других сообщений, основываясь на времени его прибытия.
Когда узел настроен на задержку сообщений, интервал задержки может быть фиксированным значением, случайным значением в пределах диапазона или динамически установленным для каждого сообщения. Каждое сообщение задерживается независимо от любых других сообщений, основываясь на времени его прибытия в узел.
</p>
<p>
Когда настроено ограничивать скорость сообщений, их доставка распределяется по настроенному периоду времени. Статус показывает количество сообщений, находящихся в данный момент в очереди. Если выбрано он может отбрасывать промежуточные сообщений по мере их поступления.
Когда узел настроен на ограничение скорости сообщений, их доставка распределяется по установленному периоду времени. Статус показывает количество сообщений, находящихся в данный момент в очереди. При необходимости узел может быть настроен на отбрасывание промежуточных сообщений по мере их поступления.
</p>
<p>
Ограничение скорости может применяться ко всем сообщениям или группировать их в соответствии со значением <code>msg.topic</code>. При группировании промежуточные сообщения автоматически отбрасываются. В каждом интервале времени узел может либо выпустить самое последнее сообщение для всех тем, либо выпустить самое последнее сообщение для следующей темы.
Ограничение скорости может применяться ко всем сообщениям или группам сообщений в соответствии с их значением темы (<code>msg.topic</code>). При группировании промежуточные сообщения автоматически отбрасываются. В каждом интервале времени узел может либо выпустить самое последнее сообщение для всех тем, либо выпустить самое последнее сообщение для следующей темы.
</p>
</script>

View File

@ -16,7 +16,7 @@
<script type="text/html" data-help-name="trigger">
<p>
При срабатывании может отправить сообщение, а затем дополнительно второе сообщение, если оно не продлено или не сброшено.
При срабатывании может отправить сообщение, а затем дополнительно второе сообщение, если не было сделано продление или сброс.
</p>
<h3>Принимает</h3>
@ -24,15 +24,15 @@
<dt class="optional">delay <span class="property-type">число</span></dt>
<dd>Устанавливает задержку в миллисекундах, которая будет применяться к сообщению. Этот параметр применяется только в том случае, если узел настроен так, чтобы сообщение могло отменять настроенный интервал задержки по умолчанию.</dd>
<dt class="optional">reset</dt>
<dd>Если сообщение получено с этим свойством, любой тайм-аут или повтор, находящийся в обработке в текущий момент, будет сброшен, и сообщение не сработает.</dd>
<dd>При получении сообщения с этим свойством, любой тайм-аут или повтор, находящиеся в обработке в текущий момент, будут сброшены, а сообщение не сработает.</dd>
</dl>
<h3>Подробности</h3>
<p>
Этот узел можно использовать для создания тайм-аута в потоке. По умолчанию, когда узел получает сообщение, он отправляет сообщение с <code>payload</code> равным <code>1</code>. Затем он ждет 250 мс, прежде чем отправить второе сообщение с <code>payload</code> равным <code>0</code>. Это можно использовать, например, для мигания светодиода, подключенного к выводу Raspberry Pi GPIO.
Этот узел можно использовать для создания тайм-аута внутри потока. По умолчанию, когда узел получает сообщение, он отправляет сообщение с <code>payload</code> равным <code>1</code>. Затем он ждет 250 мс, прежде чем отправить второе сообщение с <code>payload</code> равным <code>0</code>. Это можно использовать, например, для мигания светодиодом, подключеным к выходу GPIO у Raspberry Pi.
</p>
<p>
Данные каждого отправленного сообщения могут быть настроены на различные значения, включая возможность ничего не отправлять. Например, если установить начальное сообщение на <i>ничего</i> и выбрать опцию продления таймера с каждым новым сообщением, узел будет действовать как сторожевой таймер, отправляя сообщение только в том случае, если ничего не получено в течение установленного интервала.
Данные каждого отправленного сообщения могут быть настроены на различные значения, включая возможность ничего не отправлять. Например, если установить первоначальное сообщение на <i>ничего</i> и выбрать опцию продления таймера с каждым новым сообщением, узел будет действовать как сторожевой таймер, отправляя сообщение только в том случае, если ничего не получено в течение установленного интервала.
</p>
<p>
Если установлен тип <i>строка</i>, узел поддерживает синтаксис шаблона mustache.
@ -44,12 +44,12 @@
Если узел получает сообщение со свойством <code>reset</code> или <code>payload</code>, который совпадает с настроенным в узле, любой тайм-аут или повтор, находящийся в обработке в текущий момент, будет сброшен, и сообщение не сработает.
</p>
<p>
Узел может быть настроен на повторную отправку сообщения с регулярным интервалом, пока оно не будет сброшено полученным сообщением.
Узел может быть настроен на повторную отправку сообщения с регулярным интервалом, пока не будет произведен сброс отправкой на него сообщения.
</p>
<p>
При желании узел может быть настроен на обработку сообщений, как если бы они были отдельными потоками, используя свойство msg для идентификации каждого потока. По умолчанию <code>msg.topic</code>.
</p>
<p>
Статус показывает, что узел в данный момент активен. Если используется несколько потоков, статус показывает количество удерживаемых потоков.
Статус показывает - активен ли узел в данный момент. Если работает в многопоточном режиме, статус показывает количество удерживаемых потоков.
</p>
</script>

View File

@ -19,16 +19,16 @@
Запускает системную команду и возвращает ее вывод.
</p>
<p>
Узел может быть настроен либо на ожидание завершения команды, либо на отправку своих выходных данных, пока команда их генерирует.
Узел может быть настроен либо на ожидание завершения выполнения команды, либо на отправку выходных данных по мере их генерации в ходе выполнения.
</p>
<p>
Выполняемая команда может быть настроена в узле или предоставлена полученным сообщением.
Выполняемая команда может быть установлена в настройках узла или полученным сообщением.
</p>
<h3>Принимает</h3>
<dl class="message-properties">
<dt class="optional">payload <span class="property-type">строка</span></dt>
<dd>будет добавлено к выполненной команде, если настроено так делать.</dd>
<dd>будет добавлено к выполняемой команде, если узел настроен так делать.</dd>
<dt class="optional">kill <span class="property-type">строка</span></dt>
<dd>тип сигнала уничтожения для отправки существующему процессу узла exec.</dd>
<dt class="optional">pid <span class="property-type">число|строка</span></dt>
@ -44,7 +44,7 @@
</dl>
<dl class="message-properties">
<dt>rc <span class="property-type">объект</span></dt>
<dd>только в exec режиме, копия объекта кода возврата (также доступна на порту 3)</dd>
<dd>только в exec режиме, копия объекта кода возврата (также доступна по 3-му порту)</dd>
</dl>
</li>
<li>Стандартный вывод ошибок
@ -54,7 +54,7 @@
</dl>
<dl class="message-properties">
<dt>rc <span class="property-type">объект</span></dt>
<dd>только в exec режиме, копия объекта кода возврата (также доступна на порту 3)</dd>
<dd>только в exec режиме, копия объекта кода возврата (также доступна по 3-му порту)</dd>
</dl>
</li>
<li>Код возврата
@ -73,7 +73,7 @@
При желании вместо этого можно использовать <code>spawn</code>, который возвращает выходные данные из stdout и stderr по ходу выполнения команды, обычно по одной строке за раз. После завершения он возвращает объект на 3-й порт. Например, успешная команда должна вернуть <code>{code: 0}</code>.
</p>
<p>
Ошибки могут возвращать дополнительную информацию на 3-м порту в <code>msg.payload</code>, такую как строка <code>message</code>, строка <code>signal</code>.
Ошибки могут возвращать на 3-й порт дополнительную информацию в <code>msg.payload</code>, такую как строка <code>message</code>, строка <code>signal</code>.
</p>
<p>
Выполняемая команда настраивается в узле, с возможностью добавления к ней <code>msg.payload</code> и дополнительного набора параметров.
@ -82,25 +82,25 @@
Команды или параметры с пробелами должны быть заключены в кавычки - <code>"Это один параметр"</code>
</p>
<p>
Возвращаемый <code>payload</code> обычно представляет собой <i>строку</i>, пока не обнаружены символы, отличные от UTF8, в этом случае это <i>буфер</i>.
Возвращаемый <code>payload</code> обычно представляет собой <i>строку</i>, пока не обнаружены символы, отличные от UTF8, в этом случае возвращаемое значение будет иметь тип <i>буфер</i>.
</p>
<p>
Значок состояния узла и PID будут видны, пока узел активен. Изменения в статусе могут быть прочитаны узлом <code>Status</code>.
Значок статуса узла и PID будут видны, когда узел активен. Изменения в статусе можно отслеживать узлом <code>Status</code>.
</p>
<h4>
Уничтожения процессов
</h4>
<p>
Отправка <code>msg.kill</code> уничтожит один активный процесс. <code>msg.kill</code> должен быть строкой, содержащей тип передаваемого сигнала, например, <code>SIGINT</code>, <code>SIGQUIT</code> или <code>SIGHUP</code>. По умолчанию <code>SIGTERM</code>, если задана пустая строка.
Отправка <code>msg.kill</code> уничтожит один активный процесс. <code>msg.kill</code> должен быть строкой, содержащей тип передаваемого сигнала, например, <code>SIGINT</code>, <code>SIGQUIT</code> или <code>SIGHUP</code>. По умолчанию будет передан <code>SIGTERM</code>, если задана пустая строка.
</p>
<p>
Если узлом запущено более одного процесса, тогда в <code>msg.pid</code> также должно быть установлено значение PID для уничтожения.
Если узлом запущено более одного процесса, тогда в <code>msg.pid</code> также должно быть установлено значение PID процесса для уничтожения.
</p>
<p>
Если в поле <code>Тайм-аут</code> указано значение, то, если процесс не завершится по истечении указанного количества секунд, он будет автоматически уничтожен
Если в поле <code>Тайм-аут</code> введено значение, тогда, если процесс не завершится по истечении указанного количества секунд, он будет автоматически уничтожен.
</p>
<p>
Совет: если Вы запускаете Python приложение, Вам может потребоваться использовать параметр <code>-u</code>, чтобы остановить буферизацию вывода.
Совет: если вы запускаете Python приложение, вам может потребоваться использование параметра <code>-u</code>, чтобы остановить буферизацию вывода.
</p>
</script>

View File

@ -46,7 +46,7 @@
"bool": "логический тип",
"json": "объект",
"bin": "буфер",
"date": "отметка времени",
"date": "метка времени",
"env": "переменная среды",
"object": "объект",
"string": "строка",
@ -55,7 +55,7 @@
"Array": "массив",
"invalid": "Неверный объект JSON"
},
"timestamp": "отметка времени",
"timestamp": "метка времени",
"none": "нет",
"interval": "с интервалом",
"interval-time": "с интервалом в промежутке",

View File

@ -15,5 +15,5 @@
-->
<script type="text/html" data-help-name="tls-config">
<p>Параметры конфигурации для соединений TLS.</p>
<p>Параметры конфигурации для TLS соединений.</p>
</script>

View File

@ -21,6 +21,6 @@
<h3>Подробности</h3>
<p>
При доступе к хосту, находящемуся в списке игнорируемых хостов, прокси не будет использоваться.
При обращении к хосту, находящемуся в списке игнорируемых хостов, прокси не будет использоваться.
</p>
</script>

View File

@ -13,7 +13,7 @@
<script type="text/html" data-help-name="mqtt in">
<p>
Подключается к брокеру MQTT и подписывается на сообщения из указанной темы.
Подключается к MQTT брокеру и подписывается на сообщения указанной темы.
</p>
<h3>Выводит</h3>
@ -36,13 +36,13 @@
Для этого узла требуется соединение с брокером MQTT. Это настраивается нажатием на значок карандаша.
</p>
<p>
Несколько узлов MQTT (in или out) могут совместно использовать одно и то же соединение с брокером, если это необходимо.
Несколько MQTT узлов (in или out) могут совместно использовать одно и то же соединение с брокером, если это необходимо.
</p>
</script>
<script type="text/html" data-help-name="mqtt out">
<p>
Подключается к брокеру MQTT и публикует сообщения.
Подключается к MQTT брокеру и публикует сообщения.
</p>
<h3>Принимает</h3>
@ -80,13 +80,13 @@
<script type="text/html" data-help-name="mqtt-broker">
<p>
Конфигурация для подключения к брокеру MQTT.
Конфигурация для подключения к MQTT брокеру.
</p>
<p>
Эта конфигурация создаст одно соединение с посредником, которое затем может быть повторно использовано узлами <code>MQTT In</code> и <code>MQTT Out</code>.
</p>
<p>
Узел сгенерирует случайный идентификатор клиента, если он не задан, а узел настроен на использование соединения с чистым сеансом. Если идентификатор клиента установлен, он должен быть уникальным для брокера, к которому Вы подключаетесь.
Узел сгенерирует случайный идентификатор клиента, если он не задан, а узел настроен на использование соединения с чистым сеансом. Если идентификатор клиента установлен, он должен быть уникальным для брокера, к которому вы подключаетесь.
</p>
<h4>Birth сообщение</h4>
@ -96,7 +96,7 @@
<h4>Close сообщение</h4>
<p>
Это сообщение будет опубликовано в настроенной теме перед тем, как соединение будет закрыто нормально, либо путем повторного развертывания узла, либо остановкой.
Это сообщение будет опубликовано в настроенной теме перед тем, как соединение будет закрыто обычным образом, либо путем повторного развертывания узла, либо остановкой.
</p>
<h4>Will сообщение</h4>
@ -106,7 +106,7 @@
<h4>WebSockets</h4>
<p>
Узел может быть настроен на использование соединения WebSocket. Для этого в поле Сервер должен быть указан полный URI для соединения. Например:
Узел может быть настроен на использование WebSocket соединения. Для этого в поле Сервер должен быть указан полный URI для соединения. Например:
</p>
<pre>ws://example.com:4000/mqtt</pre>

View File

@ -16,7 +16,7 @@
<script type="text/html" data-help-name="http in">
<p>
Создает конечную точку HTTP для создания веб-сервисов.
Создает конечную точку HTTP для веб-службы.
</p>
<h3>Выводит</h3>
@ -35,12 +35,12 @@
</ul>
</dd>
<dt>res<span class="property-type">объект</span></dt>
<dd>Объект ответа HTTP. Это свойство не должно использоваться напрямую; узел ответа <code>HTTP Response</code> описывает, как ответить на запрос. Это свойство должно оставаться в сообщении, передаваемому узлу ответа.</dd>
<dd>Объект ответа HTTP. Это свойство не должно использоваться напрямую. Для ответа на входящий HTTP запрос используется узел ответа <code>HTTP Response</code>. Это свойство должно оставаться в сообщении вплоть до передачи узлу ответа.</dd>
</dl>
<h3>Подробности</h3>
<p>
По настроенному пути узел будет слушать запросы определенного типа. Путь может быть указан полностью, например <code>/user</code>, или включать именованные параметры, которые принимают любое значение, например <code>/user/:name</code>. Когда используются именованные параметры, их фактическое значение в запросе будет доступно в <code>msg.req.params</code>.
По настроенному пути узел будет принимать запросы определенного типа. Путь может быть указан конкретно, например <code>/user</code>, или включать именованные параметры, которые принимают любое значение, например <code>/user/:name</code>. Когда используются именованные параметры, их фактическое значение в запросе будет доступно в <code>msg.req.params</code>.
</p>
<p>
Для запросов, которые содержат тело, такие как POST или PUT, содержимое запроса доступно в <code>msg.payload</code>.
@ -67,7 +67,7 @@
<dt class="optional">headers <span class="property-type">объект</span></dt>
<dd>Если установлено, предоставляет заголовки HTTP для включения в ответ.</dd>
<dt class="optional">cookies <span class="property-type">объект</span></dt>
<dd>Если установлено, может использоваться для установки или удаления куков.</dd>
<dd>Если установлено, может использоваться для записи или удаления куков.</dd>
</dl>
<h3>Подробности</h3>
@ -80,7 +80,7 @@
Свойство <code>cookies</code> должно быть объектом пар имя/значение. Значением может быть либо строка для установки значения куки с параметрами по умолчанию, либо это может быть объект параметров.
</p>
<p>
В следующем примере устанавливаются два куки - один с именем <code>name</code> со значением <code>nick</code>, другой с именем <code>session</code> со значением <code>1234</code> и сроком действия в 15 минут.
В следующем примере устанавливаются два куки - один с именем <code>name</code> и значением <code>nick</code>, другой с именем <code>session</code> и значением <code>1234</code> и сроком действия в 15 минут.
</p>
<pre>
msg.cookies = {

View File

@ -32,7 +32,7 @@
<dt class="optional">payload</dt>
<dd>Отправляется как тело запроса.</dd>
<dt class="optional">rejectUnauthorized</dt>
<dd>Если установлено значение <code>false</code>, разрешает отправлять запросы на сайты https, которые используют самозаверяющие сертификаты.</dd>
<dd>Если установлено значение <code>false</code>, разрешает отправлять запросы на сайты https, которые используют самозаверенные сертификаты.</dd>
<dt class="optional">followRedirects</dt>
<dd>Если установлено значение <code>false</code>, запрещает переадресацию (HTTP 301). По умолчанию <code>true</code>.</dd>
<dt class="optional">requestTimeout</dt>
@ -68,20 +68,20 @@
<h4>Использование нескольких узлов HTTP-запросов</h4>
<p>
Чтобы использовать более одного из этих узлов в одном потоке, нужно позаботиться о свойстве <code>msg.headers</code>. Первый узел установит это свойство с заголовками ответа. Затем следующий узел будет использовать эти заголовки для своего запроса - обычно это неправильный подход. Если свойство <code>msg.headers</code> остается неизменным между узлами, оно будет проигнорировано вторым узлом. Чтобы установить пользовательские заголовки, сначала необходимо удалить <code>msg.headers</code> или сбросить до пустого объекта: <code>{}</code>.
Чтобы использовать несколько таких узлов в одном потоке, нужно позаботиться о свойстве <code>msg.headers</code>. Первый узел установит это свойство с заголовками ответа. Затем следующий узел будет использовать эти заголовки для своего запроса - обычно это неправильный подход. Если свойство <code>msg.headers</code> остается неизменным между узлами, оно будет проигнорировано вторым узлом. Чтобы установить свои заголовки, сначала необходимо удалить <code>msg.headers</code> или сбросить это свойство до пустого объекта: <code>{}</code>.
</p>
<h4>Обработка куки</h4>
<p>
Свойство <code>cookies</code>, передаваемое узлу, должно быть объектом пар имя/значение. Значением может быть либо строка для установки значения куки, либо это может быть объект с единственным свойством <code>value</code>.
Свойство <code>cookies</code>, передаваемое узлу, должно быть объектом из пар имя/значение. Значением может быть либо строка для установки значения куки, либо это может быть объект с единственным свойством <code>value</code>.
</p>
<p>
Все куки, возвращаемые запросом, передаются обратно в свойство <code>responseCookies</code>.
Все куки, возвращаемые запросом, передаются обратно в свойстве <code>responseCookies</code>.
</p>
<h4>Обработка типов контента</h4>
<p>
Если <code>msg.payload</code> является объектом, узел автоматически установит тип содержимого запроса в <code>application/json</code> и закодирует тело как таковое.
Если <code>msg.payload</code> является объектом, узел автоматически установит тип содержимого запроса в <code>application/json</code> и закодирует тело соответствующим образом.
</p>
<p>
Чтобы закодировать запрос как данные формы, для <code>msg.headers["content-type"]</code> должно быть установлено <code>application/x-www-form-urlencoded</code>.

View File

@ -16,7 +16,7 @@
<script type="text/html" data-help-name="websocket in">
<p>
Входной узел WebSocket.
Входящий узел WebSocket.
</p>
<p>
По умолчанию данные, полученные из WebSocket, будут в <code>msg.payload</code>. Сокет можно настроить так, чтобы он ожидал правильно сформированную строку JSON. В этом случае он будет анализировать JSON и отправлять полученный объект как полное сообщение.
@ -25,17 +25,17 @@
<script type="text/html" data-help-name="websocket out">
<p>
Выходной узел WebSocket.
Исходящий узел WebSocket.
</p>
<p>
По умолчанию <code>msg.payload</code> будет отправлено через WebSocket. Сокет может быть сконфигурирован для кодирования всего объекта <code>msg</code> в виде строки JSON и отправки его через WebSocket.
</p>
<p>
Если сообщение, поступающее на этот узел, началось с узла WebSocket In, сообщение будет отправлено обратно клиенту, который запустил поток. В противном случае сообщение будет передано всем подключенным клиентам.
Если сообщение, поступающее на этот узел, было начато в потоке узлом WebSocket In, сообщение будет отправлено обратно клиенту, который запустил поток. В противном случае сообщение будет передано всем подключенным клиентам.
</p>
<p>
Если Вы хотите передать сообщение, которое началось на узле WebSocket In, всем подключенным клиентам, тогда Вы должны удалить свойство <code>msg._session</code> в потоке.
Если Вы хотите передать сообщение, которое началось с узла WebSocket In, всем подключенным клиентам, тогда вам нужно удалить свойство <code>msg._session</code> перед тем как передавать его этому узлу.
</p>
</script>

View File

@ -16,16 +16,16 @@
<script type="text/html" data-help-name="tcp in">
<p>
Предоставляет выбор TCP-входов. Можно либо подключаться к удаленному TCP-порту, либо принимать входящие подключения.
Предоставляет выбор из нескольких входящих TCP-узлов. Можно либо подключаться к удаленному TCP-порту, либо принимать входящие подключения.
</p>
<p>
<b>Примечание.</b> В некоторых системах Вам могут потребоваться права root или администратора для доступа к портам ниже 1024.
<b>Примечание.</b> В некоторых системах вам могут потребоваться права root или администратора для доступа к портам ниже 1024.
</p>
</script>
<script type="text/html" data-help-name="tcp out">
<p>
Предоставляет выбор TCP-выходов. Может подключаться к удаленному TCP-порту, принимать входящие подключения или отвечать на сообщения, полученные от узла TCP In.
Предоставляет выбор из нескольких исходящих TCP-узлов. Может подключаться к удаленному TCP-порту, принимать входящие подключения или отвечать на сообщения, полученные от узла TCP In.
</p>
<p>
Отправляются только данные <code>msg.payload</code>.
@ -37,21 +37,21 @@
Если <code>msg._session</code> отсутствует, данные отправляется <b>всем</b> подключенным клиентам.
</p>
<p>
<b>Примечание.</b> В некоторых системах Вам могут потребоваться права root или администратора для доступа к портам ниже 1024.
<b>Примечание.</b> В некоторых системах вам могут потребоваться права root или администратора для доступа к портам ниже 1024.
</p>
</script>
<script type="text/html" data-help-name="tcp request">
<p>
Простой узел TCP-запроса - отправляет <code>msg.payload</code> на tcp-порт сервера и ожидает ответа.
Узел TCP-запроса - отправляет <code>msg.payload</code> на tcp-порт сервера и ожидает ответа.
</p>
<p>
Подключается, отправляет &quot;запрос&quot; и читает &quot;ответ&quot;. Он может либо подсчитать количество возвращенных символов в фиксированный буфер, сопоставить указанный символ перед возвратом, дождаться фиксированного времени ожидания от первого ответа и затем вернуться, сидеть и дожидаться данных, или отправить, а затем немедленно закрыть соединение, не дожидаясь ответа.
</p>
<p>
Ответ будет выведен в <code>msg.payload</code> в виде буфера, поэтому Вы можете захотеть применить к нему .toString().
Ответ будет возвращен в <code>msg.payload</code> в виде буфера, поэтому вам может понадобиться применить к нему .toString(), если нужно преобразование в строку.
</p>
<p>
Если Вы оставите tcp-хост или порт пустыми, они должны быть установлены с помощью свойств <code>msg.host</code> и <code>msg.port</code> в каждом сообщении, отправляемом узлу.
Если вы оставите tcp-хост или порт пустыми, они должны быть установлены с помощью свойств <code>msg.host</code> и <code>msg.port</code> в каждом сообщении, отправляемом узлу.
</p>
</script>

View File

@ -16,13 +16,13 @@
<script type="text/html" data-help-name="udp in">
<p>
Входной узел UDP, который создает <code>msg.payload</code>, содержащий буфер, строку или base64-строку. Поддерживает многоадресную рассылку.
Входящий UDP-узел, который создает <code>msg.payload</code>, содержащий буфер, строку или base64-строку. Поддерживает многоадресную рассылку.
</p>
<p>
Он также предоставляет <code>msg.ip</code> и <code>msg.port</code> для IP-адреса и порта, с которого было получено сообщение.
</p>
<p>
<b>Примечание</b>. В некоторых системах Вам могут потребоваться права root или администратора для доступа к портам ниже 1024 и/или широковещательной рассылки.
<b>Примечание</b>. В некоторых системах вам могут потребоваться права root или администратора для доступа к портам ниже 1024 и/или широковещательной рассылки.
</p>
</script>
@ -37,6 +37,6 @@
Если Вы выберете широковещательную рассылку, то либо задайте в качестве адреса локальный широковещательный IP-адрес, либо попробуйте 255.255.255.255, который является глобальным широковещательным адресом.
</p>
<p>
<b>Примечание</b>. В некоторых системах Вам могут потребоваться права root или администратора для доступа к портам ниже 1024 и/или широковещательной рассылки.
<b>Примечание</b>. В некоторых системах вам могут потребоваться права root или администратора для доступа к портам ниже 1024 и/или широковещательной рассылки.
</p>
</script>

View File

@ -30,10 +30,10 @@
<dt>payload<span class="property-type">объект | массив | строка</span></dt>
<dd>
<ul>
<li>Если вход является значением строкового типа, узел пытается проанализировать ее как CSV и создает объект JavaScript из пар ключ/значение для каждой строки. Затем узел либо отправит сообщение для каждой строки или одно сообщение, содержащее массив объектов.</li>
<li>Если вход является JavaScript объектом, узел пытается построить CSV-строку.</li>
<li>Если вход является массивом простых значений, узел построит однострочную CSV-строку.</li>
<li>Если вход является массивом массивов или массивом объектов, создается многострочная CSV-строка.</li>
<li>Если на входе значение строкового типа, узел попытается проанализировать его как CSV и создает объект JavaScript из пар ключ/значение для каждой строки. Затем узел либо отправит сообщение для каждой строки или одно сообщение, содержащее массив объектов.</li>
<li>Если на входе JavaScript объект, узел попробует построить CSV-строку.</li>
<li>Если на входе массив простых значений, узел построит однострочную CSV-строку.</li>
<li>Если на входе массив массивов или массив объектов, создается многострочная CSV-строка.</li>
</ul>
</dd>
</dl>
@ -46,7 +46,7 @@
При преобразовании в CSV шаблон столбцов используется для определения того, какие свойства извлекать из объекта и в каком порядке.
</p>
<p>
Если шаблон пуст, то узел может использовать простой список свойств, разделенных запятыми, предоставленных в <code>msg.columns</code>, чтобы определить, что извлечь. Если этого нет, то все свойства объекта выводятся в том порядке, в котором они были найдены в первой строке.
Если шаблон пуст, то узел может использовать простой список свойств, разделенных запятыми, предоставленных в <code>msg.columns</code>, чтобы определить, что извлечь. Если его нет, то все свойства объекта выводятся в том порядке, в котором они были найдены в первой строке.
</p>
<p>
Если входные данные являются массивом, то шаблон столбцов используется только для необязательного генерирования строки с заголовками столбцов.

View File

@ -37,7 +37,7 @@
</ul>
</dd>
<dt>schemaError<span class="property-type">массив</span></dt>
<dd>Если проверка JSON-схемы завершится неудачно, узел catch будет иметь свойство <code>schemaError</code>, содержащее массив ошибок.</dd>
<dd>Если проверка JSON-схемы завершится неудачно, узлом catch можно получить свойство <code>schemaError</code>, содержащее массив ошибок.</dd>
</dl>
<h3>Подробности</h3>
@ -45,10 +45,10 @@
По умолчанию узел работает с <code>msg.payload</code>, но его можно настроить для преобразования любого свойства сообщения.
</p>
<p>
Узел также может быть сконфигурирован для обеспечения конкретной кодировки вместо переключения между ними. Это можно использовать, например, с узлом <code>HTTP In</code>, чтобы гарантировать, что данные payload являются объектом, даже если входящий запрос неправильно установил свой тип содержимого для узла HTTP In, чтобы выполнить преобразование.
Узел также может быть сконфигурирован для обеспечения конкретной кодировки вместо переключения между ними. Это можно использовать, например, при работе с узлом <code>HTTP In</code>, чтобы гарантировать, что данные payload всегда будут являться объектом, даже если входящий запрос неправильно установил свой тип содержимого для узла HTTP In, чтобы выполнить преобразование.
</p>
<p>
Если узел настроен на то, чтобы свойство кодировалось как строка, и он получает строку, дальнейшие проверки этого свойства выполняться не будут. Он не будет проверять, является ли строка допустимым JSON, и не будет переформатировать ее, если выбрана опция форматирования.
Если узел настроен так, чтобы свойство кодировалось как строка, и он получает строку, дальнейшие проверки этого свойства выполняться не будут. Он не будет проверять, является ли строка допустимым JSON, и не будет переформатировать ее, если выбрана опция форматирования.
</p>
<p>
Подробнее о JSON-схеме Вы можете узнать в спецификации <a href="http://json-schema.org/latest/json-schema-validation.html">здесь</a>.

View File

@ -32,8 +32,8 @@
<dt>payload<span class="property-type">объект | строка</span></dt>
<dd>
<ul>
<li>Если вход является значением строкового типа, узел пытается проанализировать ее как XML и создает объект JavaScript.</li>
<li>Если вход является JavaScript объектом, узел пытается построить XML-строку.</li>
<li>Если на входе значение строкового типа, узел пытается проанализировать его как XML и создает объект JavaScript.</li>
<li>Если на входе JavaScript объект, узел пытается построить XML-строку.</li>
</ul>
</dd>
</dl>

View File

@ -30,8 +30,8 @@
<dt>payload<span class="property-type">объект | строка</span></dt>
<dd>
<ul>
<li>Если вход является YAML-строкой, узел пытается проанализировать ее как JavaScript объект.</li>
<li>Если вход является JavaScript объектом, узел создает YAML-строку.</li>
<li>Если на входе YAML-строка, узел пытается проанализировать ее как JavaScript объект.</li>
<li>Если на входе JavaScript объект, узел создает YAML-строку.</li>
</ul>
</dd>
</dl>

View File

@ -24,7 +24,7 @@
<dt>payload<span class="property-type">объект | строка | массив | буфер</span></dt>
<dd>Поведение узла определяется типом <code>msg.payload</code>:
<ul>
<li><b>строка</b>/<b>буфер</b> - сообщение разделяется с помощью указанного символа (по умолчанию: <code>\n</code>), последовательности буфера или фиксированной длины.</li>
<li><b>строка</b>/<b>буфер</b> - сообщение разделяется с помощью указанного символа (по умолчанию: <code>\n</code>), последовательности буфера или по фиксированной длине.</li>
<li><b>массив</b> - сообщение разбивается на отдельные элементы массива или массивы фиксированной длины.</li>
<li><b>объект</b> - сообщение отправляется для каждой пары ключ/значение объекта.</li>
</ul>
@ -34,7 +34,7 @@
<h3>Выводит</h3>
<dl class="message-properties">
<dt>parts<span class="property-type">объект</span></dt>
<dd>Это свойство содержит информацию о том, как сообщение было отделено от исходного сообщения. При передаче на узел <b>join</b> последовательность может быть собрана в одно сообщение. Свойство имеет следующие свойства:
<dd>Это свойство содержит информацию о том, как сообщение было отделено от исходного сообщения. При передаче на узел <b>join</b> последовательность может быть собрана обратно в одно сообщение. Объект содержит следующие свойства:
<ul>
<li><code>id</code> - идентификатор группы сообщений</li>
<li><code>index</code> - позиция в группе</li>
@ -49,7 +49,7 @@
<h3>Подробности</h3>
<p>
Этот узел облегчает создание потока, который выполняет общие действия над последовательностью сообщений, перед тем как с помощью узла <b>join</b> объединить последовательность в одно сообщение.
Этот узел облегчает создание потока, который выполняет общие действия над последовательностью сообщений, перед тем как с помощью узла <b>join</b> объединить последовательность обратно в одно сообщение.
</p>
<p>
Он использует свойство <code>msg.parts</code> для отслеживания отдельных частей последовательности.
@ -60,7 +60,7 @@
Узел также может использоваться для переформатирования потока сообщений. Например, последовательное устройство, которое отправляет завершенные новой строкой команды, может доставлять одно сообщение с частичной командой в конце. В 'потоковом режиме' этот узел будет разбивать сообщение и отправлять каждый завершенный сегмент. Если в конце есть частичный сегмент, узел удержит его и добавит к следующему полученному сообщению.
</p>
<p>
При работе в этом режиме узел не будет устанавливать свойство <code>msg.parts.count</code>, так как он не знает, сколько сообщений ожидать в потоке. Это означает, что его нельзя использовать с узлом <b>join</b> в его автоматическом режиме.
При работе в этом режиме узел не будет устанавливать свойство <code>msg.parts.count</code>, так как он не знает, сколько сообщений ожидать в потоке. Это означает, что его нельзя использовать вместе с узлом <b>join</b> в его автоматическом режиме.
</p>
</script>
@ -73,7 +73,7 @@
</p>
<dl>
<dt>автоматический</dt>
<dd>При использовании с узлом <b>split</b> он автоматически объединит сообщения, чтобы отменить выполненное разделение.</dd>
<dd>При использовании с узлом <b>split</b> он автоматически объединит сообщения, чтобы восстановить структуру сообщения, которая была до разделения.</dd>
<dt>ручной</dt>
<dd>Объединяет последовательности сообщений различными способами.</dd>
<dt>агрегация последовательности</dt>
@ -103,7 +103,7 @@
<h3>Подробности</h3>
<h4>Автоматический режим</h4>
<p>
В автоматическом режиме используется свойство <code>parts</code> входящих сообщений, чтобы определить способ объединения последовательности. Это позволяет автоматически отменять действие узла <b>split</b>.
В автоматическом режиме используется свойство <code>parts</code> входящих сообщений, чтобы определить способ объединения последовательности. Это позволяет автоматически выполнять обратное действие для узла <b>split</b>.
</p>
<h4>Ручной режим</h4>
@ -123,7 +123,7 @@
Свойство <i>кол-во</i> может быть установлено для количества сообщений, которое должно быть получено перед генерацией выходного сообщения. Для выходных данных объекта, когда это число достигнуто, узел может быть настроен на отправку сообщения для каждого последующего полученного сообщения.
</p>
<p>
Свойство <i>время (сек)</i> может быть установлено, чтобы инициировать отправку нового сообщения с использованием того, что было получено до сих пор.
Свойство <i>время (сек)</i> может быть установлено, чтобы инициировать отправку нового сообщения с использованием всего, что было получено до сих пор.
</p>
<p>
Если сообщение получено с установленным свойством <b>msg.complete</b>, выходное сообщение завершается и отправляется. Это сбрасывает любой подсчет частей.

View File

@ -48,7 +48,7 @@
</ul>
</p>
<p>
<b>Примечание.</b> Этот узел внутренне хранит сообщения для своей работы. Чтобы предотвратить непредвиденное использование памяти, можно указать максимальное количество хранимых сообщений. По умолчанию количество сообщений не ограничено.
<b>Примечание.</b> Этот узел буферизирует сообщения внутри для своей работы. Чтобы предотвратить непредвиденное использование памяти, можно указать максимальное количество хранимых сообщений. По умолчанию количество сообщений не ограничено.
<ul>
<li>Свойство <code>nodeMessageBufferMaxLength</code> устанавливается в <b>settings.js</b>.</li>
</ul>

View File

@ -37,7 +37,7 @@
<h4>Хранение сообщений</h4>
<p>
Этот узел будет буферизировать сообщения внутри, чтобы работать между последовательностями. Параметр <code>nodeMessageBufferMaxLength</code> можно использовать для ограничения количества сообщений, которые узел будут буферизовать.
Этот узел будет буферизировать сообщения внутри, чтобы работать с последовательностями. Параметр <code>nodeMessageBufferMaxLength</code> можно использовать для ограничения количества сообщений, которые узел будут буферизовать.
</p>
<p>
Если сообщение получено с установленным свойством <b>msg.reset</b>, буферизованные сообщения удаляются и не отправляются.

View File

@ -16,7 +16,7 @@
<script type="text/html" data-help-name="file">
<p>
Записывает <code>msg.payload</code> в файл, либо добавляя в конец, либо заменяя существующий контент. Кроме того, он может удалить файл.
Записывает <code>msg.payload</code> в файл, либо добавлением в конец, либо заменой существующего контента. Кроме того, им можно удалять файл.
</p>
<h3>Принимает</h3>
@ -38,7 +38,7 @@
Если используется <code>msg.filename</code>, файл будет закрыт после каждой записи. Для лучшей производительности используйте фиксированное имя файла.
</p>
<p>
Он может быть настроен на перезапись всего файла, а не на добавление. Например, при записи двоичных данных в файл, например изображение, следует использовать эту опцию и отключить опцию добавления новой строки.
Он может быть настроен на перезапись всего файла, а не на добавление. Например, при записи двоичных данных (типа изображения) в файл, следует использовать эту опцию и отключить опцию добавления новой строки.
</p>
<p>
Кодировка данных, записанных в файл, может быть выбрана в списке кодировок.
@ -50,13 +50,13 @@
<script type="text/html" data-help-name="file in">
<p>
Читает содержимое файла как строковый тип или двоичный буфер.
Читает содержимое файла как строку или двоичный буфер.
</p>
<h3>Принимает</h3>
<dl class="message-properties">
<dt class="optional">filename <span class="property-type">строка</span></dt>
<dd>если не задано в конфигурации узла, это свойство устанавливает имя файла для чтения.</dd>
<dd>это свойство устанавливает имя файла для чтения, если оно не задано в настройках узла.</dd>
</dl>
<h3>Выводит</h3>
@ -69,13 +69,13 @@
<h3>Подробности</h3>
<p>
Имя файла должно быть абсолютным путем, иначе оно будет относительно рабочего каталога процесса Node-RED.
Имя файла должно быть абсолютным путем к файлу, иначе оно будет путем относительно рабочего каталога процесса Node-RED.
</p>
<p>
В Windows может быть необходимо кодировать разделители пути двойной косой чертой, например: <code>\\Users\\myUser</code>.
</p>
<p>
При желании текстовый файл можно разбить на строки, выводя по одному сообщению на строку, или двоичный файл разбить на более мелкие фрагменты буфера - размер блока зависит от операционной системы, но обычно составляет 64 КБ (Linux/Mac) или 41 КБ (Windows).
При желании текстовый файл можно разбить на строки, выводя по одному сообщению для каждой строки, или двоичный файл разбить на более мелкие фрагменты буфера - размер блока зависит от операционной системы, но обычно составляет 64 КБ (Linux/Mac) или 41 КБ (Windows).
</p>
<p>
При разбиении на несколько сообщений каждое сообщение будет иметь свойство <code>parts</code>, формирующее полную последовательность сообщений.

View File

@ -16,13 +16,13 @@
<script type="text/html" data-help-name="watch">
<p>
Наблюдает за изменениями каталога или файла.
Наблюдает за изменениями директории или файла.
</p>
<p>
Вы можете ввести список разделенных запятыми каталогов и/или файлов. Вам нужно взять в кавычки "..." те из них, в которых есть пробелы.
Вы можете ввести список разделенных запятыми директорий и/или файлов. Вам нужно взять в кавычки "..." те из них, в которых есть пробелы.
</p>
<p>
В Windows Вы должны использовать двойную обратную косую черту \\ в любых именах каталогов.
В Windows вы должны использовать двойную обратную косую черту \\ вместо одной в именах директорий.
</p>
<p>
Полное имя фактически измененного файла помещается в <code>msg.payload</code> и <code>msg.filename</code>, а строковая версия списка наблюдения возвращается в <code>msg.topic</code>.
@ -31,9 +31,9 @@
<code>msg.file</code> содержит только краткое имя файла, который изменился. <code>msg.type</code> содержит тип измененной единицы, обычно это <i>file</i> для файла или <i>directory</i> для директории, тогда как <code>msg.size</code> содержит размер файла в байтах.
</p>
<p>
В Linux <i>все</i> является файлом, и может быть под наблюдением...
В Linux <i>все</i> является файлами, и может быть под наблюдением...
</p>
<p>
<b>Примечание.</b> Наблюдаемые каталог или файл должны существовать. Если файл или каталог будет удален, они могут перестать отслеживаться, даже если они будут созданы заново.
<b>Примечание.</b> Наблюдаемые директория или файл должны существовать. Если файл или директория будут удалены, они могут перестать отслеживаться, даже если они затем будут созданы заново.
</p>
</script>

View File

@ -29,6 +29,7 @@ var loader = require("./loader");
var installer = require("./installer");
var library = require("./library");
const externalModules = require("./externalModules")
var plugins = require("./plugins");
/**
* Initialise the registry with a reference to a runtime object
@ -41,6 +42,7 @@ function init(runtime) {
// the util module it. The Util module is responsible for constructing the
// RED object passed to node modules when they are loaded.
loader.init(runtime);
plugins.init(runtime.settings);
registry.init(runtime.settings,loader);
library.init();
externalModules.init(runtime.settings);
@ -301,6 +303,12 @@ module.exports = {
checkFlowDependencies: externalModules.checkFlowDependencies,
registerPlugin: plugins.registerPlugin,
getPlugin: plugins.getPlugin,
getPluginsByType: plugins.getPluginsByType,
getPluginList: plugins.getPluginList,
getPluginConfigs: plugins.getPluginConfigs,
deprecated: require("./deprecated")
};

View File

@ -27,6 +27,7 @@ async function getFlowsFromPath(path) {
var validFiles = [];
return fs.readdir(path).then(files => {
var promises = [];
if (files) {
files.forEach(function(file) {
var fullPath = fspath.join(path,file);
var stats = fs.lstatSync(fullPath);
@ -38,6 +39,7 @@ async function getFlowsFromPath(path) {
promises.push(Promise.resolve(file.split(".")[0]))
}
})
}
return Promise.all(promises)
}).then(results => {
results.forEach(function(r,i) {

View File

@ -36,35 +36,25 @@ function load(disableNodePathScan) {
// To skip node scan, the following line will use the stored node list.
// We should expose that as an option at some point, although the
// performance gains are minimal.
//return loadNodeFiles(registry.getModuleList());
//return loadModuleFiles(registry.getModuleList());
log.info(log._("server.loading"));
var nodeFiles = localfilesystem.getNodeFiles(disableNodePathScan);
return loadNodeFiles(nodeFiles);
var modules = localfilesystem.getNodeFiles(disableNodePathScan);
return loadModuleFiles(modules);
}
function loadNodeFiles(nodeFiles) {
var promises = [];
var nodes = [];
for (var module in nodeFiles) {
/* istanbul ignore else */
if (nodeFiles.hasOwnProperty(module)) {
if (nodeFiles[module].redVersion &&
!semver.satisfies((settings.version||"0.0.0").replace(/(\-[1-9A-Za-z-][0-9A-Za-z-\.]*)?(\+[0-9A-Za-z-\.]+)?$/,""), nodeFiles[module].redVersion)) {
//TODO: log it
log.warn("["+module+"] "+log._("server.node-version-mismatch",{version:nodeFiles[module].redVersion}));
nodeFiles[module].err = "version_mismatch";
continue;
}
if (module == "node-red" || !registry.getModuleInfo(module)) {
function loadModuleTypeFiles(module, type) {
const things = module[type];
var first = true;
for (var node in nodeFiles[module].nodes) {
var promises = [];
for (var thingName in things) {
/* istanbul ignore else */
if (nodeFiles[module].nodes.hasOwnProperty(node)) {
if (module != "node-red" && first) {
if (things.hasOwnProperty(thingName)) {
if (module.name != "node-red" && first) {
// Check the module directory exists
first = false;
var fn = nodeFiles[module].nodes[node].file;
var fn = things[thingName].file;
var parts = fn.split("/");
var i = parts.length-1;
for (;i>=0;i--) {
@ -83,31 +73,103 @@ function loadNodeFiles(nodeFiles) {
}
try {
promises.push(loadNodeConfig(nodeFiles[module].nodes[node]).then((function() {
var m = module;
var n = node;
return function(nodeSet) {
nodeFiles[m].nodes[n] = nodeSet;
nodes.push(nodeSet);
var promise;
if (type === "nodes") {
promise = loadNodeConfig(things[thingName]);
} else if (type === "plugins") {
promise = loadPluginConfig(things[thingName]);
}
})()).catch(err => {}));
promises.push(
promise.then(
(function() {
var m = module.name;
var n = thingName;
return function(nodeSet) {
things[n] = nodeSet;
return nodeSet;
}
})()
).catch(err => {console.log(err)})
);
} catch(err) {
console.log(err)
//
}
}
}
return promises;
}
function loadModuleFiles(modules) {
var pluginPromises = [];
var nodePromises = [];
for (var module in modules) {
/* istanbul ignore else */
if (modules.hasOwnProperty(module)) {
if (modules[module].redVersion &&
!semver.satisfies((settings.version||"0.0.0").replace(/(\-[1-9A-Za-z-][0-9A-Za-z-\.]*)?(\+[0-9A-Za-z-\.]+)?$/,""), modules[module].redVersion)) {
//TODO: log it
log.warn("["+module+"] "+log._("server.node-version-mismatch",{version:modules[module].redVersion}));
modules[module].err = "version_mismatch";
continue;
}
if (module == "node-red" || !registry.getModuleInfo(module)) {
if (modules[module].nodes) {
nodePromises = nodePromises.concat(loadModuleTypeFiles(modules[module], "nodes"));
}
if (modules[module].plugins) {
pluginPromises = pluginPromises.concat(loadModuleTypeFiles(modules[module], "plugins"));
}
}
}
return Promise.all(promises).then(function(results) {
for (var module in nodeFiles) {
if (nodeFiles.hasOwnProperty(module)) {
if (!nodeFiles[module].err) {
registry.addModule(nodeFiles[module]);
}
var pluginList;
var nodeList;
return Promise.all(pluginPromises).then(function(results) {
pluginList = results.filter(r => !!r);
// Initial plugin load has happened. Ensure modules that provide
// plugins are in the registry now.
for (var module in modules) {
if (modules.hasOwnProperty(module)) {
if (modules[module].plugins && Object.keys(modules[module].plugins).length > 0) {
// Add the modules for plugins
if (!modules[module].err) {
registry.addModule(modules[module]);
}
}
}
return loadNodeSetList(nodes);
}
return loadNodeSetList(pluginList);
}).then(function() {
return Promise.all(nodePromises);
}).then(function(results) {
nodeList = results.filter(r => !!r);
// Initial node load has happened. Ensure remaining modules are in the registry
for (var module in modules) {
if (modules.hasOwnProperty(module)) {
if (!modules[module].plugins || Object.keys(modules[module].plugins).length === 0) {
if (!modules[module].err) {
registry.addModule(modules[module]);
}
}
}
}
return loadNodeSetList(nodeList);
});
}
async function loadPluginTemplate(plugin) {
return fs.readFile(plugin.template,'utf8').then(content => {
plugin.config = content;
return plugin;
}).catch(err => {
if (err.code === 'ENOENT') {
plugin.err = "Error: "+plugin.template+" does not exist";
} else {
plugin.err = err.toString();
}
return plugin;
});
}
@ -175,11 +237,12 @@ async function loadNodeLocales(node) {
node.namespace = node.module;
return node
}
return fs.stat(path.join(path.dirname(node.file),"locales")).then(stat => {
const baseFile = node.file||node.template;
return fs.stat(path.join(path.dirname(baseFile),"locales")).then(stat => {
node.namespace = node.id;
return i18n.registerMessageCatalog(node.id,
path.join(path.dirname(node.file),"locales"),
path.basename(node.file,".js")+".json")
path.join(path.dirname(baseFile),"locales"),
path.basename(baseFile).replace(/\.[^.]+$/,".json"))
.then(() => node);
}).catch(err => {
node.namespace = node.module;
@ -204,6 +267,7 @@ async function loadNodeConfig(fileInfo) {
}
var node = {
type: "node",
id: id,
module: module,
name: name,
@ -227,6 +291,58 @@ async function loadNodeConfig(fileInfo) {
return node;
}
async function loadPluginConfig(fileInfo) {
var file = fileInfo.file;
var module = fileInfo.module;
var name = fileInfo.name;
var version = fileInfo.version;
var id = module + "/" + name;
var isEnabled = true;
// TODO: registry.getPluginInfo
// var info = registry.getPluginInfo(id);
// if (info) {
// if (info.hasOwnProperty("loaded")) {
// throw new Error(file+" already loaded");
// }
// isEnabled = info.enabled;
// }
if (!fs.existsSync(jsFile)) {
}
var plugin = {
type: "plugin",
id: id,
module: module,
name: name,
enabled: isEnabled,
loaded:false,
version: version,
local: fileInfo.local,
plugins: [],
config: "",
help: {}
};
var jsFile = file.replace(/\.[^.]+$/,".js");
var htmlFile = file.replace(/\.[^.]+$/,".html");
if (fs.existsSync(jsFile)) {
plugin.file = jsFile;
}
if (fs.existsSync(htmlFile)) {
plugin.template = htmlFile;
}
await loadNodeLocales(plugin)
if (plugin.template && !settings.disableEditor) {
return loadPluginTemplate(plugin);
}
return plugin
}
/**
* Loads the specified node into the runtime
* @param node a node info object - see loadNodeConfig
@ -236,8 +352,6 @@ async function loadNodeConfig(fileInfo) {
*
*/
function loadNodeSet(node) {
var nodeDir = path.dirname(node.file);
var nodeFn = path.basename(node.file);
if (!node.enabled) {
return Promise.resolve(node);
} else {
@ -284,11 +398,59 @@ function loadNodeSet(node) {
}
}
async function loadPlugin(plugin) {
if (!plugin.file) {
// No runtime component - nothing to load
return plugin;
}
try {
var r = require(plugin.file);
if (typeof r === "function") {
var red = registryUtil.createNodeApi(plugin);
var promise = r(red);
if (promise != null && typeof promise.then === "function") {
return promise.then(function() {
plugin.enabled = true;
plugin.loaded = true;
return plugin;
}).catch(function(err) {
plugin.err = err;
return plugin;
});
}
}
plugin.enabled = true;
plugin.loaded = true;
return plugin;
} catch(err) {
console.log(err);
plugin.err = err;
var stack = err.stack;
var message;
if (stack) {
var i = stack.indexOf(plugin.file);
if (i > -1) {
var excerpt = stack.substring(i+node.file.length+1,i+plugin.file.length+20);
var m = /^(\d+):(\d+)/.exec(excerpt);
if (m) {
plugin.err = err+" (line:"+m[1]+")";
}
}
}
return plugin;
}
}
function loadNodeSetList(nodes) {
var promises = [];
nodes.forEach(function(node) {
if (!node.err) {
if (node.type === "plugin") {
promises.push(loadPlugin(node).catch(err => {}));
} else {
promises.push(loadNodeSet(node).catch(err => {}));
}
} else {
promises.push(node);
}
@ -338,7 +500,7 @@ function addModule(module) {
}
}
}
return loadNodeFiles(moduleFiles).then(() => module)
return loadModuleFiles(moduleFiles).then(() => module)
} catch(err) {
return Promise.reject(err);
}

View File

@ -220,21 +220,22 @@ function getModuleNodeFiles(module) {
var moduleDir = module.dir;
var pkg = module.package;
var nodes = pkg['node-red'].nodes||{};
var results = [];
var iconDirs = [];
var iconList = [];
for (var n in nodes) {
function scanTypes(types) {
const files = [];
for (var n in types) {
/* istanbul ignore else */
if (nodes.hasOwnProperty(n)) {
var file = path.join(moduleDir,nodes[n]);
results.push({
if (types.hasOwnProperty(n)) {
var file = path.join(moduleDir,types[n]);
files.push({
file: file,
module: pkg.name,
name: n,
version: pkg.version
});
var iconDir = path.join(moduleDir,path.dirname(nodes[n]),"icons");
var iconDir = path.join(moduleDir,path.dirname(types[n]),"icons");
if (iconDirs.indexOf(iconDir) == -1) {
try {
fs.statSync(iconDir);
@ -246,7 +247,14 @@ function getModuleNodeFiles(module) {
}
}
}
var result = {files:results,icons:iconList};
return files;
}
var result = {
nodeFiles:scanTypes(pkg['node-red'].nodes||{}),
pluginFiles:scanTypes(pkg['node-red'].plugins||{}),
icons:iconList
};
var examplesDir = path.join(moduleDir,"examples");
try {
@ -396,6 +404,7 @@ function convertModuleFileListToObject(moduleFiles) {
local: moduleFile.local||false,
user: moduleFile.user||false,
nodes: {},
plugins: {},
icons: nodeModuleFiles.icons,
examples: nodeModuleFiles.examples
};
@ -408,11 +417,14 @@ function convertModuleFileListToObject(moduleFiles) {
if (moduleFile.usedBy) {
nodeList[moduleFile.package.name].usedBy = moduleFile.usedBy;
}
nodeModuleFiles.files.forEach(function(node) {
node.local = moduleFile.local||false;
nodeModuleFiles.nodeFiles.forEach(function(node) {
nodeList[moduleFile.package.name].nodes[node.name] = node;
nodeList[moduleFile.package.name].nodes[node.name].local = moduleFile.local || false;
});
nodeModuleFiles.pluginFiles.forEach(function(plugin) {
nodeList[moduleFile.package.name].plugins[plugin.name] = plugin;
nodeList[moduleFile.package.name].plugins[plugin.name].local = moduleFile.local || false;
});
});
return nodeList;
}

View File

@ -0,0 +1,103 @@
const registry = require("./registry");
const {events} = require("@node-red/util")
var pluginConfigCache = {};
var pluginToId = {};
var plugins = {};
var pluginsByType = {};
var settings;
function init(_settings) {
settings = _settings;
plugins = {};
pluginConfigCache = {};
pluginToId = {};
pluginsByType = {};
}
function registerPlugin(nodeSetId,id,definition) {
var moduleId = registry.getModuleFromSetId(nodeSetId);
var pluginId = registry.getNodeFromSetId(nodeSetId);
definition.id = id;
definition.module = moduleId;
pluginToId[id] = nodeSetId;
plugins[id] = definition;
var module = registry.getModule(moduleId);
definition.path = module.path;
module.plugins[pluginId].plugins.push(definition);
if (definition.type) {
pluginsByType[definition.type] = pluginsByType[definition.type] || [];
pluginsByType[definition.type].push(definition);
}
if (definition.onadd && typeof definition.onadd === 'function') {
definition.onadd();
}
events.emit("registry:plugin-added",id);
}
function getPlugin(id) {
return plugins[id]
}
function getPluginsByType(type) {
return pluginsByType[type] || [];
}
function getPluginConfigs(lang) {
if (!pluginConfigCache[lang]) {
var result = "";
var script = "";
var moduleConfigs = registry.getModuleList();
for (var module in moduleConfigs) {
/* istanbul ignore else */
if (moduleConfigs.hasOwnProperty(module)) {
var plugins = moduleConfigs[module].plugins;
for (var plugin in plugins) {
if (plugins.hasOwnProperty(plugin)) {
var config = plugins[plugin];
if (config.enabled && !config.err && config.config) {
result += "\n<!-- --- [red-plugin:"+config.id+"] --- -->\n";
result += config.config;
}
}
}
}
}
pluginConfigCache[lang] = result;
}
return pluginConfigCache[lang];
}
function getPluginList() {
var list = [];
var moduleConfigs = registry.getModuleList();
for (var module in moduleConfigs) {
/* istanbul ignore else */
if (moduleConfigs.hasOwnProperty(module)) {
var plugins = moduleConfigs[module].plugins;
for (var plugin in plugins) {
/* istanbul ignore else */
if (plugins.hasOwnProperty(plugin)) {
var pluginInfo = registry.filterNodeInfo(plugins[plugin]);
pluginInfo.version = moduleConfigs[module].version;
// if (moduleConfigs[module].pending_version) {
// nodeInfo.pending_version = moduleConfigs[module].pending_version;
// }
list.push(pluginInfo);
}
}
}
}
return list;
}
module.exports = {
init,
registerPlugin,
getPlugin,
getPluginsByType,
getPluginConfigs,
getPluginList
}

View File

@ -64,17 +64,24 @@ function filterNodeInfo(n) {
if (n.hasOwnProperty("err")) {
r.err = n.err;
}
if (n.hasOwnProperty("plugins")) {
r.plugins = n.plugins;
}
if (n.type === "plugin") {
r.editor = !!n.template;
r.runtime = !!n.file;
}
return r;
}
function getModule(id) {
function getModuleFromSetId(id) {
var parts = id.split("/");
return parts.slice(0,parts.length-1).join("/");
}
function getNode(id) {
function getNodeFromSetId(id) {
var parts = id.split("/");
return parts[parts.length-1];
}
@ -217,11 +224,11 @@ function addModule(module) {
function removeNode(id) {
var config = moduleConfigs[getModule(id)].nodes[getNode(id)];
var config = moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
if (!config) {
throw new Error("Unrecognised id: "+id);
}
delete moduleConfigs[getModule(id)].nodes[getNode(id)];
delete moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
var i = nodeList.indexOf(id);
if (i > -1) {
nodeList.splice(i,1);
@ -292,9 +299,9 @@ function getNodeInfo(typeOrId) {
}
/* istanbul ignore else */
if (id) {
var module = moduleConfigs[getModule(id)];
var module = moduleConfigs[getModuleFromSetId(id)];
if (module) {
var config = module.nodes[getNode(id)];
var config = module.nodes[getNodeFromSetId(id)];
if (config) {
var info = filterNodeInfo(config);
if (config.hasOwnProperty("loaded")) {
@ -321,9 +328,9 @@ function getFullNodeInfo(typeOrId) {
}
/* istanbul ignore else */
if (id) {
var module = moduleConfigs[getModule(id)];
var module = moduleConfigs[getModuleFromSetId(id)];
if (module) {
return module.nodes[getNode(id)];
return module.nodes[getNodeFromSetId(id)];
}
}
return null;
@ -357,16 +364,10 @@ function getNodeList(filter) {
}
function getModuleList() {
//var list = [];
//for (var module in moduleNodes) {
// /* istanbul ignore else */
// if (moduleNodes.hasOwnProperty(module)) {
// list.push(registry.getModuleInfo(module));
// }
//}
//return list;
return moduleConfigs;
}
function getModule(id) {
return moduleConfigs[id];
}
function getModuleInfo(module) {
@ -465,13 +466,11 @@ function getAllNodeConfigs(lang) {
var script = "";
for (var i=0;i<nodeList.length;i++) {
var id = nodeList[i];
var module = moduleConfigs[getModule(id)]
var module = moduleConfigs[getModuleFromSetId(id)]
if (!module.user && (module.usedBy && module.usedBy.length > 0)) {
continue;
}
var config = module.nodes[getNode(id)];
var config = module.nodes[getNodeFromSetId(id)];
if (config.enabled && !config.err) {
result += "\n<!-- --- [red-module:"+id+"] --- -->\n";
result += config.config;
@ -490,11 +489,11 @@ function getAllNodeConfigs(lang) {
}
function getNodeConfig(id,lang) {
var config = moduleConfigs[getModule(id)];
var config = moduleConfigs[getModuleFromSetId(id)];
if (!config) {
return null;
}
config = config.nodes[getNode(id)];
config = config.nodes[getNodeFromSetId(id)];
if (config) {
var result = "<!-- --- [red-module:"+id+"] --- -->\n"+config.config;
result += loader.getNodeHelp(config,lang||"en-US")
@ -515,7 +514,7 @@ function getNodeConstructor(type) {
if (typeof id === "undefined") {
config = undefined;
} else {
config = moduleConfigs[getModule(id)].nodes[getNode(id)];
config = moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
}
if (!config || (config.enabled && !config.err)) {
@ -553,7 +552,7 @@ function enableNodeSet(typeOrId) {
}
var config;
try {
config = moduleConfigs[getModule(id)].nodes[getNode(id)];
config = moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
delete config.err;
config.enabled = true;
nodeConfigCache = {};
@ -576,7 +575,7 @@ function disableNodeSet(typeOrId) {
}
var config;
try {
config = moduleConfigs[getModule(id)].nodes[getNode(id)];
config = moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
// TODO: persist setting
config.enabled = false;
nodeConfigCache = {};
@ -715,6 +714,7 @@ var registry = module.exports = {
getFullNodeInfo: getFullNodeInfo,
getNodeList: getNodeList,
getModuleList: getModuleList,
getModule: getModule,
getModuleInfo: getModuleInfo,
getNodeIconPath: getNodeIconPath,
@ -730,5 +730,8 @@ var registry = module.exports = {
saveNodeList: saveNodeList,
cleanModuleList: cleanModuleList
cleanModuleList: cleanModuleList,
getModuleFromSetId: getModuleFromSetId,
getNodeFromSetId: getNodeFromSetId,
filterNodeInfo: filterNodeInfo
};

View File

@ -70,6 +70,17 @@ function createNodeApi(node) {
})
}
},
plugins: {
registerPlugin: function(id,definition) {
return runtime.plugins.registerPlugin(node.id,id,definition);
},
get: function(id) {
return runtime.plugins.getPlugin(id);
},
getByType: function(type) {
return runtime.plugins.getPluginsByType(type);
}
},
library: {
register: function(type) {
return runtime.library.register(node.id,type);

View File

@ -28,6 +28,7 @@ var api = module.exports = {
api.library.init(runtime);
api.projects.init(runtime);
api.context.init(runtime);
api.plugins.init(runtime);
},
comms: require("./comms"),
@ -37,6 +38,7 @@ var api = module.exports = {
settings: require("./settings"),
projects: require("./projects"),
context: require("./context"),
plugins: require("./plugins"),
isStarted: async function(opts) {
return runtime.isStarted();

View File

@ -94,6 +94,10 @@ var api = module.exports = {
getNodeConfig: async function(opts) {
var id = opts.id;
var lang = opts.lang;
if (/[^a-z\-\*]/i.test(opts.lang)) {
reject(new Error("Invalid language: "+opts.lang));
return
}
var result = runtime.nodes.getNodeConfig(id,lang);
if (result) {
runtime.log.audit({event: "nodes.config.get",id:id}, opts.req);
@ -116,6 +120,10 @@ var api = module.exports = {
* @memberof @node-red/runtime_nodes
*/
getNodeConfigs: async function(opts) {
if (/[^a-z\-\*]/i.test(opts.lang)) {
reject(new Error("Invalid language: "+opts.lang));
return
}
runtime.log.audit({event: "nodes.configs.get"}, opts.req);
return runtime.nodes.getNodeConfigs(opts.lang);
},
@ -374,6 +382,10 @@ var api = module.exports = {
getModuleCatalogs: async function(opts) {
var namespace = opts.module;
var lang = opts.lang;
if (/[^a-z\-\*]/i.test(lang)) {
reject(new Error("Invalid language: "+lang));
return
}
var prevLang = runtime.i18n.i.language;
// Trigger a load from disk of the language if it is not the default
return new Promise( (resolve,reject) => {
@ -404,6 +416,10 @@ var api = module.exports = {
getModuleCatalog: async function(opts) {
var namespace = opts.module;
var lang = opts.lang;
if (/[^a-z\-\*]/i.test(lang)) {
reject(new Error("Invalid language: "+lang));
return
}
var prevLang = runtime.i18n.i.language;
// Trigger a load from disk of the language if it is not the default
return new Promise(resolve => {

View File

@ -0,0 +1,95 @@
/**
* @mixin @node-red/runtime_plugins
*/
var runtime;
var api = module.exports = {
init: function(_runtime) {
runtime = _runtime;
},
/**
* Gets a plugin definition from the registry
* @param {Object} opts
* @param {String} opts.id - the id of the plugin to get
* @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<PluginDefinition>} - the plugin definition
* @memberof @node-red/runtime_plugins
*/
getPlugin: async function(opts) {
return runtime.plugins.getPlugin(opts.id);
},
/**
* Gets all plugin definitions of a given type
* @param {Object} opts
* @param {String} opts.type - the type of the plugins to get
* @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Array>} - the plugin definitions
* @memberof @node-red/runtime_plugins
*/
getPluginsByType: async function(opts) {
return runtime.plugins.getPluginsByType(opts.type);
},
/**
* Gets the editor content for an individual plugin
* @param {String} opts.lang - the locale language to return
* @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<NodeInfo>} - the node information
* @memberof @node-red/runtime_plugins
*/
getPluginList: async function(opts) {
runtime.log.audit({event: "plugins.list.get"}, opts.req);
return runtime.plugins.getPluginList();
},
/**
* Gets the editor content for all registered plugins
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {User} opts.user - the user calling the api
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<NodeInfo>} - the node information
* @memberof @node-red/runtime_plugins
*/
getPluginConfigs: async function(opts) {
if (/[^a-z\-]/i.test(opts.lang)) {
throw new Error("Invalid language: "+opts.lang)
return;
}
runtime.log.audit({event: "plugins.configs.get"}, opts.req);
return runtime.plugins.getPluginConfigs(opts.lang);
},
/**
* Gets all registered module message catalogs
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {User} opts.lang - the i18n language to return. If not set, uses runtime default (en-US)
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Object>} - the message catalogs
* @memberof @node-red/runtime_plugins
*/
getPluginCatalogs: async function(opts) {
var lang = opts.lang;
var prevLang = runtime.i18n.i.language;
// Trigger a load from disk of the language if it is not the default
return new Promise( (resolve,reject) => {
runtime.i18n.i.changeLanguage(lang, function(){
var nodeList = runtime.plugins.getPluginList();
var result = {};
nodeList.forEach(function(n) {
if (n.module !== "node-red") {
result[n.id] = runtime.i18n.i.getResourceBundle(lang, n.id)||{};
}
});
runtime.i18n.i.changeLanguage(prevLang);
resolve(result);
});
});
},
}

View File

@ -21,6 +21,7 @@ var flows = require("./flows");
var storage = require("./storage");
var library = require("./library");
var hooks = require("./hooks");
var plugins = require("./plugins");
var settings = require("./settings");
var express = require("express");
@ -281,6 +282,7 @@ var runtime = {
storage: storage,
hooks: hooks,
nodes: redNodes,
plugins: plugins,
flows: flows,
library: library,
exec: exec,
@ -342,6 +344,12 @@ module.exports = {
*/
context: externalAPI.context,
/**
* @memberof @node-red/runtime
* @mixes @node-red/runtime_plugins
*/
plugins: externalAPI.plugins,
/**
* Returns whether the runtime is started
* @param {Object} opts

View File

@ -0,0 +1,10 @@
const registry = require("@node-red/registry");
module.exports = {
init: function() {},
registerPlugin: registry.registerPlugin,
getPlugin: registry.getPlugin,
getPluginsByType: registry.getPluginsByType,
getPluginList: registry.getPluginList,
getPluginConfigs: registry.getPluginConfigs,
}

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