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 mqtt5

This commit is contained in:
Steve-Mcl 2021-02-18 18:50:26 +00:00
commit ab4a9e72d4
247 changed files with 10223 additions and 2444 deletions

View File

@ -1,5 +1,6 @@
name: PublishDockerImage
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
on:
release:
types: [published]
@ -27,9 +28,6 @@ jobs:
with:
node-version: '12'
- run: node ./node-red/.github/scripts/update-node-red-docker.js
with:
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
- name: Create Docker Pull Request
uses: peter-evans/create-pull-request@v2
with:

View File

@ -11,5 +11,11 @@ matrix:
before_script:
- npm install -g coveralls
- node_js: "12"
script:
- ./node_modules/.bin/grunt no-coverage
- node_js: "10"
script:
- ./node_modules/.bin/grunt no-coverage
- node_js: "8"
script:
- ./node_modules/.bin/grunt no-coverage

2
API.md
View File

@ -10,6 +10,6 @@ Module | Description
[@node-red/editor-api](@node-red_editor-api.html) | an Express application that serves the Node-RED editor and provides the Admin HTTP API
[@node-red/runtime](@node-red_runtime.html) | the core runtime of Node-RED
[@node-red/util](@node-red_util.html) | common utilities for the Node-RED runtime and editor modules
@node-red/registry | the internal node registry
[@node-red/registry](@node-red_registry.html) | the internal node registry
@node-red/nodes | the default set of core nodes
@node-red/editor-client | the client-side resources of the Node-RED editor application

View File

@ -1,3 +1,65 @@
### 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
- Ensure subflow-scoped config nodes do not get moved on import Fixes #2789
- Allow TypedInput to be disabled (#2752) @bartbutenaers
- Allow userMenu to be explicitly enabled (#2805) @tfmf
- Improvements to DE translation (#2192) @ketzu
Runtime
- Handle `undefined` error passed to node.error (#2781) @johnwang71
- Disable nyc coverage reporting on older node versions
- Improve Editor API unit test coverage (#2777) @aaronmyatt
Nodes
- Trigger: ensure timestamp option sends .now() at point of sending
### 1.2.6: Maintenance Release
Editor
- Update Japanese translations for 1.2.5 (#2764) @kazuhitoyokoi
- Library: properly handle symlinked folders (#2768) @natcl
Runtime
- Support Windows paths when installing tarball by path name Fixes #2769
- Fix unsecure command usage in GH Action
Nodes
- Update MQTT to latest to fix Node 8 URL breakage
### 1.2.5: 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",
@ -461,7 +462,8 @@ module.exports = function(grunt) {
'packages/node_modules/@node-red/runtime/lib/hooks.js',
'packages/node_modules/@node-red/util/**/*.js',
'packages/node_modules/@node-red/editor-api/lib/index.js',
'packages/node_modules/@node-red/editor-api/lib/auth/index.js'
'packages/node_modules/@node-red/editor-api/lib/auth/index.js',
'packages/node_modules/@node-red/registry/lib/index.js'
],
options: {
destination: 'docs',
@ -623,6 +625,11 @@ module.exports = function(grunt) {
'Builds editor content then runs code style checks and unit tests on all components',
['build','verifyPackageDependencies','jshint:editor','nyc:all']);
grunt.registerTask('no-coverage',
'Builds editor content then runs code style checks and unit tests on all components without code coverage',
['build','verifyPackageDependencies','jshint:editor','simplemocha:all']);
grunt.registerTask('test-core',
'Runs code style check and unit tests on core runtime code',
['build','nyc:core']);

View File

@ -27,7 +27,7 @@
],
"dependencies": {
"ajv": "6.12.6",
"async-mutex": "0.2.4",
"async-mutex": "0.2.6",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"body-parser": "1.19.0",
@ -38,7 +38,7 @@
"cookie-parser": "1.4.5",
"cors": "2.8.5",
"cron": "1.7.2",
"denque": "1.4.1",
"denque": "1.5.0",
"express": "4.17.1",
"express-session": "1.17.1",
"fs-extra": "8.1.0",
@ -54,11 +54,11 @@
"lodash.clonedeep": "^4.5.0",
"media-typer": "1.1.0",
"memorystore": "1.6.4",
"mime": "2.4.6",
"mime": "2.4.7",
"moment-timezone": "0.5.32",
"mqtt": "4.2.5",
"mqtt": "4.2.6",
"multer": "1.4.2",
"mustache": "4.0.1",
"mustache": "4.1.0",
"node-red-admin": "^0.2.6",
"node-red-node-rbe": "^0.2.9",
"node-red-node-sentiment": "^0.1.6",
@ -73,8 +73,7 @@
"request": "2.88.0",
"semver": "6.3.0",
"tar": "6.0.5",
"uglify-js": "3.11.6",
"when": "3.7.8",
"uglify-js": "3.12.4",
"ws": "6.2.1",
"xml2js": "0.4.23"
},
@ -82,7 +81,7 @@
"bcrypt": "3.0.8"
},
"devDependencies": {
"dompurify": "2.2.2",
"dompurify": "2.2.6",
"grunt": "1.3.0",
"grunt-chmod": "~1.1.1",
"grunt-cli": "~1.3.2",
@ -97,17 +96,17 @@
"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",
"grunt-simple-nyc": "^3.0.1",
"http-proxy": "1.18.1",
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
"marked": "1.2.4",
"marked": "1.2.7",
"minami": "1.2.3",
"mocha": "^5.2.0",
"node-red-node-test-helper": "^0.2.5",
"node-red-node-test-helper": "^0.2.6",
"node-sass": "^4.14.1",
"nodemon": "2.0.6",
"should": "13.2.3",

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;
@ -50,13 +52,15 @@ module.exports = {
// Nodes
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,apiUtil.errorHandler);
if (!settings.editorTheme || !settings.editorTheme.palette || settings.editorTheme.palette.upload !== false) {
if (!settings.externalModules || !settings.externalModules.palette || settings.externalModules.palette.allowInstall !== false) {
if (!settings.externalModules || !settings.externalModules.palette || settings.externalModules.palette.allowUpload !== false) {
const multer = require('multer');
const upload = multer({ storage: multer.memoryStorage() });
adminApp.post("/nodes",needsPermission("nodes.write"),upload.single("tarball"),nodes.post,apiUtil.errorHandler);
} else {
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler);
}
}
adminApp.get(/^\/nodes\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalogs,apiUtil.errorHandler);
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalog,apiUtil.errorHandler);
adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+)$/,needsPermission("nodes.read"),nodes.getModule,apiUtil.errorHandler);
@ -78,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

@ -60,6 +60,7 @@ module.exports = {
runtimeAPI.nodes.addModule(opts).then(function(info) {
res.json(info);
}).catch(function(err) {
console.log(err.stack);
apiUtils.rejectHandler(req,res,err);
})
},

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

@ -17,7 +17,6 @@
var apiUtils = require("../util");
var fs = require('fs');
var fspath = require('path');
var when = require('when');
var runtimeAPI;

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

@ -28,7 +28,6 @@ var express = require("express");
var bodyParser = require("body-parser");
var util = require('util');
var passport = require('passport');
var when = require('when');
var cors = require('cors');
var auth = require("./auth");
@ -60,8 +59,8 @@ function init(settings,_server,storage,runtimeAPI) {
adminApp.use(corsHandler);
if (settings.httpAdminMiddleware) {
if (typeof settings.httpAdminMiddleware === "function") {
adminApp.use(settings.httpAdminMiddleware)
if (typeof settings.httpAdminMiddleware === "function" || Array.isArray(settings.httpAdminMiddleware)) {
adminApp.use(settings.httpAdminMiddleware);
}
}
@ -111,11 +110,9 @@ function init(settings,_server,storage,runtimeAPI) {
* @return {Promise} resolves when the application is ready to handle requests
* @memberof @node-red/editor-api
*/
function start() {
async function start() {
if (editor) {
return editor.start();
} else {
return when.resolve();
}
}
@ -124,11 +121,10 @@ function start() {
* @return {Promise} resolves when the application is stopped
* @memberof @node-red/editor-api
*/
function stop() {
async function stop() {
if (editor) {
editor.stop();
}
return when.resolve();
}
module.exports = {
init: init,

View File

@ -25,14 +25,13 @@
"express-session": "1.17.1",
"express": "4.17.1",
"memorystore": "1.6.4",
"mime": "2.4.6",
"mime": "2.4.7",
"multer": "1.4.2",
"mustache": "4.0.1",
"mustache": "4.1.0",
"oauth2orize": "1.11.0",
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"passport": "0.4.1",
"when": "3.7.8",
"ws": "6.2.1"
},
"optionalDependencies": {

View File

@ -32,7 +32,7 @@
"label" : {
"view" : {
"view" : "Ansicht",
"grid" : "Gitter",
"grid" : "Raster",
"showGrid" : "Raster anzeigen",
"snapGrid" : "Am Raster ausrichten",
"gridSize" : "Rastergröße",

View File

@ -38,6 +38,7 @@
}
},
"event": {
"loadPlugins": "Loading Plugins",
"loadPalette": "Loading Palette",
"loadNodeCatalogs": "Loading Node catalogs",
"loadNodes": "Loading Nodes __count__",
@ -337,8 +338,21 @@
"output": "outputs:",
"status": "status node",
"deleteSubflow": "delete subflow",
"confirmDelete": "Are you sure you want to delete this subflow?",
"info": "Description",
"category": "Category",
"module": "Module",
"license": "License",
"licenseNone": "none",
"licenseOther": "Other",
"type": "Node Type",
"version": "Version",
"versionPlaceholder": "x.y.z",
"keys": "Keywords",
"keysPlaceholder": "Comma-separated keywords",
"author": "Author",
"authorPlaceholder": "Your Name <email@example.com>",
"desc": "Description",
"env": {
"restore": "Restore to subflow default",
"remove": "Remove environment variable"
@ -385,6 +399,7 @@
"icon": "Icon",
"inputType": "Input type",
"selectType": "select types...",
"loadCredentials": "Loading node credentials",
"inputs" : {
"input": "input",
"select": "select",
@ -419,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": {
@ -1079,6 +1095,7 @@
"editor-tab": {
"properties": "Properties",
"envProperties": "Environment Variables",
"module": "Module Properties",
"description": "Description",
"appearance": "Appearance",
"preview": "UI Preview",

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

@ -38,6 +38,7 @@
}
},
"event": {
"loadPlugins": "プラグインを読み込み中",
"loadPalette": "パレットを読み込み中",
"loadNodeCatalogs": "ノードカタログを読み込み中",
"loadNodes": "ノードを読み込み中 __count__",
@ -85,7 +86,7 @@
"userSettings": "ユーザ設定",
"nodes": "ノード",
"displayStatus": "ノードのステータスを表示",
"displayConfig": "ノードの設定",
"displayConfig": "設定ノード",
"import": "読み込み",
"export": "書き出し",
"search": "ノードを検索",
@ -203,8 +204,8 @@
"replacedNodes_plural": "置換された __count__ 個のノード",
"pasteNodes": "JSON形式のフローデータを貼り付け",
"selectFile": "読み込むファイルを選択",
"importNodes": "フローをクリップボードから読み込み",
"exportNodes": "フローをクリップボードへ書き出し",
"importNodes": "フローを読み込み",
"exportNodes": "フローを書き出し",
"download": "ダウンロード",
"importUnrecognised": "認識できない型が読み込まれました:",
"importUnrecognised_plural": "認識できない型が読み込まれました:",
@ -266,7 +267,7 @@
"successfulDeploy": "デプロイが成功しました",
"successfulRestart": "フローの再起動が成功しました",
"deployFailed": "デプロイが失敗しました: __message__",
"unusedConfigNodes": "使われていない「ノードの設定」があります。",
"unusedConfigNodes": "使われていない設定ノードがあります。",
"unusedConfigNodesLink": "設定を参照する",
"errors": {
"noResponse": "サーバの応答がありません"
@ -337,8 +338,21 @@
"output": "出力:",
"status": "ステータスノード",
"deleteSubflow": "サブフローを削除",
"confirmDelete": "このサブフローを削除しても良いですか?",
"info": "詳細",
"category": "カテゴリ",
"module": "モジュール",
"license": "ライセンス",
"licenseNone": "なし",
"licenseOther": "その他",
"type": "ノードの型",
"version": "バージョン",
"versionPlaceholder": "x.y.z",
"keys": "キーワード",
"keysPlaceholder": "カンマ区切りのキーワード",
"author": "作者",
"authorPlaceholder": "名前 <email@example.com>",
"desc": "説明",
"env": {
"restore": "デフォルト値に戻す",
"remove": "環境変数を削除"
@ -362,9 +376,9 @@
"configDelete": "削除",
"nodesUse": "__count__ 個のノードが、この設定を使用しています",
"nodesUse_plural": "__count__ 個のノードが、この設定を使用しています",
"addNewConfig": "新規に __type__ ノードの設定を追加",
"addNewConfig": "新規に __type__ 設定ノードを追加",
"editNode": "__type__ ノードを編集",
"editConfig": "__type__ ノードの設定を編集",
"editConfig": "__type__ 設定ノードを編集",
"addNewType": "新規に __type__ を追加...",
"nodeProperties": "プロパティ",
"label": "ラベル",
@ -385,6 +399,7 @@
"icon": "記号",
"inputType": "入力形式",
"selectType": "形式選択...",
"loadCredentials": "ノードの認証情報を読み込み中",
"inputs": {
"input": "入力",
"select": "メニュー",
@ -419,7 +434,8 @@
},
"errors": {
"scopeChange": "スコープの変更は、他のフローで使われているノードを無効にします",
"invalidProperties": "プロパティが不正です:"
"invalidProperties": "プロパティが不正です:",
"credentialLoadFailed": "ノードの認証情報の読み込みに失敗"
}
},
"keyboard": {
@ -637,8 +653,8 @@
"noHelp": "ヘルプのトピックが未選択"
},
"config": {
"name": "ノードの設定を表示",
"label": "ノードの設定",
"name": "設定ノードを表示",
"label": "設定ノード",
"global": "全てのフロー上",
"none": "なし",
"subflows": "サブフロー",
@ -1079,6 +1095,7 @@
"editor-tab": {
"properties": "プロパティ",
"envProperties": "環境変数",
"module": "モジュールプロパティ",
"description": "説明",
"appearance": "外観",
"preview": "UIプレビュー",
@ -1089,6 +1106,7 @@
"en-US": "英語",
"ja": "日本語",
"ko": "韓国語",
"ru": "ロシア語",
"zh-CN": "中国語(簡体)",
"zh-TW": "中国語(繁体)"
}

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._;
@ -670,6 +738,7 @@ RED.nodes = (function() {
node.in = [];
node.out = [];
node.env = n.env;
node.meta = n.meta;
if (exportCreds) {
var credentialSet = {};
@ -780,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);
}
@ -787,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]
}
}
}
@ -1360,7 +1448,7 @@ RED.nodes = (function() {
}
}
} else {
if (n.z && !workspaces[n.z]) {
if (n.z && !workspaces[n.z] && !subflow_map[n.z]) {
n.z = activeWorkspace;
}
}
@ -1587,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++) {
@ -1624,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];
}
}
}
@ -1920,6 +2004,18 @@ RED.nodes = (function() {
RED.events.emit("groups:remove",group);
}
function getNodeHelp(type) {
var helpContent = "";
var helpElement = $("script[data-help-name='"+type+"']");
if (helpElement) {
helpContent = helpElement.html();
var helpType = helpElement.attr("type");
if (helpType === "text/markdown") {
helpContent = RED.utils.renderMarkdown(helpContent);
}
}
return helpContent;
}
return {
init: function() {
@ -1999,6 +2095,7 @@ RED.nodes = (function() {
registerType: registry.registerNodeType,
getType: registry.getNodeType,
getNodeHelp: getNodeHelp,
convertNode: convertNode,
add: addNode,

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

@ -52,6 +52,5 @@
Set.prototype = _Set.prototype;
Set.prototype.constructor = Set;
}
}
})();

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}),
{
@ -269,7 +336,7 @@ var RED = (function() {
}
}
]
// } else if (RED.settings.theme('palette.editable') !== false) {
// } else if (RED.settings.get('externalModules.palette.allowInstall', true) !== false) {
} else {
options.buttons = [
{
@ -509,7 +576,7 @@ var RED = (function() {
]});
menuOptions.push(null);
if (RED.settings.theme('palette.editable') !== false) {
if (RED.settings.get('externalModules.palette.allowInstall', true) !== false) {
menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"});
menuOptions.push(null);
}
@ -544,7 +611,7 @@ var RED = (function() {
RED.palette.init();
RED.eventLog.init();
if (RED.settings.theme('palette.editable') !== false) {
if (RED.settings.get('externalModules.palette.allowInstall', true) !== false) {
RED.palette.editor.init();
} else {
console.log("Palette editor disabled");
@ -579,7 +646,7 @@ var RED = (function() {
RED.actions.add("core:show-about", showAbout);
loadNodeList();
loadPluginList();
}
@ -595,6 +662,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);
@ -613,9 +681,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) {
@ -635,7 +706,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

@ -57,12 +57,11 @@ RED.settings = (function () {
return JSON.parse(localStorage.getItem(key));
} else {
var v;
try {
v = RED.utils.getMessageProperty(userSettings,key);
try { v = RED.utils.getMessageProperty(userSettings,key); } catch(err) {}
if (v === undefined) {
v = defaultIfUndefined;
try { v = RED.utils.getMessageProperty(RED.settings,key); } catch(err) {}
}
} catch(err) {
if (v === undefined) {
v = defaultIfUndefined;
}
return v;

View File

@ -30,6 +30,27 @@ RED.clipboard = (function() {
var pendingImportConfig;
function downloadData(file, data) {
if (window.navigator.msSaveBlob) {
// IE11 workaround
// IE does not support data uri scheme for downloading data
var blob = new Blob([data], {
type: "data:text/plain;charset=utf-8"
});
navigator.msSaveBlob(blob, file);
}
else {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(data));
element.setAttribute('download', file);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
}
function setupDialogs() {
dialog = $('<div id="red-ui-clipboard-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
.appendTo("#red-ui-editor")
@ -56,13 +77,8 @@ RED.clipboard = (function() {
class: "primary",
text: RED._("clipboard.download"),
click: function() {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent($("#red-ui-clipboard-dialog-export-text").val()));
element.setAttribute('download', "flows.json");
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
var data = $("#red-ui-clipboard-dialog-export-text").val();
downloadData("flows.json", data);
$( this ).dialog( "close" );
}
},
@ -72,9 +88,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 +236,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 +253,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 +614,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 +676,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 +693,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 +716,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 +765,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 +891,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 +923,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 +937,10 @@ RED.clipboard = (function() {
},1000);
popover.open();
}
$("#red-ui-clipboard-hidden").val("");
if (currentFocus) {
$(currentFocus).focus();
}
return result;
}
@ -1103,7 +1243,7 @@ RED.clipboard = (function() {
init: function() {
setupDialogs();
$('<input type="text" id="red-ui-clipboard-hidden" tabIndex="-1">').appendTo("#red-ui-editor");
$('<textarea type="text" id="red-ui-clipboard-hidden" tabIndex="-1">').appendTo("#red-ui-editor");
RED.actions.add("core:show-export-dialog",showExportNodes);
RED.actions.add("core:show-import-dialog",showImportNodes);

View File

@ -965,6 +965,18 @@
},
hide: function() {
this.uiSelect.hide();
},
disable: function(val) {
if(val === true) {
this.uiSelect.attr("disabled", "disabled");
} else if (val === false) {
this.uiSelect.attr("disabled", null); //remove attr
} else {
this.uiSelect.attr("disabled", val); //user value
}
},
disabled: function() {
return this.uiSelect.attr("disabled");
}
});
})(jQuery);

View File

@ -414,6 +414,7 @@ RED.editor = (function() {
for (var cred in credDefinition) {
if (credDefinition.hasOwnProperty(cred)) {
var input = $("#" + prefix + '-' + cred);
if (input.length > 0) {
var value = input.val();
if (credDefinition[cred].type == 'password') {
node.credentials['has_' + cred] = (value !== "");
@ -429,6 +430,7 @@ RED.editor = (function() {
}
}
}
}
return changed;
}
@ -442,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 {
@ -453,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);
}
@ -465,6 +469,7 @@ RED.editor = (function() {
definition.oneditprepare.call(node);
} catch(err) {
console.log("oneditprepare",node.id,node.type,err.toString());
console.log(err.stack);
}
}
// Now invoke any change handlers added to the fields - passing true
@ -491,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();
});
}
@ -1083,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;
@ -1192,7 +1202,7 @@ RED.editor = (function() {
changed = true;
}
} catch(err) {
console.log("oneditsave",editing_node.id,editing_node.type,err.toString());
console.warn("oneditsave",editing_node.id,editing_node.type,err.toString());
}
for (d in editing_node._def.defaults) {
@ -1609,6 +1619,7 @@ RED.editor = (function() {
if (defaultTab) {
editorTabs.activateTab(defaultTab);
}
buildingEditDialog = false;
done();
});
},
@ -1660,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);
@ -1823,6 +1836,7 @@ RED.editor = (function() {
trayBody.i18n();
trayFooter.i18n();
finishedBuilding = true;
buildingEditDialog = false;
done();
});
},
@ -1890,7 +1904,7 @@ RED.editor = (function() {
try {
configTypeDef.oneditsave.call(editing_config_node);
} catch(err) {
console.log("oneditsave",editing_config_node.id,editing_config_node.type,err.toString());
console.warn("oneditsave",editing_config_node.id,editing_config_node.type,err.toString());
}
}
@ -2146,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);
@ -2250,6 +2266,14 @@ RED.editor = (function() {
changed = true;
}
var newMeta = RED.subflow.exportSubflowModuleProperties(editing_node);
if (!isSameObj(editing_node.meta,newMeta)) {
changes.meta = editing_node.meta;
editing_node.meta = newMeta;
changed = true;
}
if (changed) {
var wasChanged = editing_node.changed;
editing_node.changed = true;
@ -2356,6 +2380,16 @@ RED.editor = (function() {
};
editorTabs.addTab(nodePropertiesTab);
var moduleTab = {
id: "editor-tab-module",
label: RED._("editor-tab.module"),
name: RED._("editor-tab.module"),
content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(),
iconClass: "fa fa-cube",
};
editorTabs.addTab(moduleTab);
RED.subflow.buildModuleForm(moduleTab.content, editing_node);
var descriptionTab = {
id: "editor-tab-description",
label: RED._("editor-tab.description"),
@ -2384,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();
});
},
@ -2413,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);
@ -2457,7 +2525,7 @@ RED.editor = (function() {
changed = true;
}
} catch(err) {
console.log("oneditsave",editing_node.id,editing_node.type,err.toString());
console.warn("oneditsave",editing_node.id,editing_node.type,err.toString());
}
for (d in editing_node._def.defaults) {
@ -2637,6 +2705,7 @@ RED.editor = (function() {
prepareEditDialog(group,group._def,"node-input", function() {
trayBody.i18n();
finishedBuilding = true;
buildingEditDialog = false;
done();
});
},

View File

@ -329,7 +329,9 @@ RED.palette.editor = (function() {
catalogueLoadStatus.push(err||v);
if (!err) {
if (v.modules) {
v.modules.forEach(function(m) {
var a = false;
v.modules = v.modules.filter(function(m) {
if (checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) {
loadedIndex[m.id] = m;
m.index = [m.id];
if (m.keywords) {
@ -344,6 +346,9 @@ RED.palette.editor = (function() {
m.timestamp = 0;
}
m.index = m.index.join(",").toLowerCase();
return true;
}
return false;
})
loadedList = loadedList.concat(v.modules);
}
@ -437,11 +442,84 @@ RED.palette.editor = (function() {
return -1 * (A.info.timestamp-B.info.timestamp);
}
var installAllowList = ['*'];
var installDenyList = [];
function parseModuleList(list) {
list = list || ["*"];
return list.map(function(rule) {
var m = /^(.+?)(?:@(.*))?$/.exec(rule);
var wildcardPos = m[1].indexOf("*");
wildcardPos = wildcardPos===-1?Infinity:wildcardPos;
return {
module: new RegExp("^"+m[1].replace(/\*/g,".*")+"$"),
version: m[2],
wildcardPos: wildcardPos
}
})
}
function checkAgainstList(module,version,list) {
for (var i=0;i<list.length;i++) {
var rule = list[i];
if (rule.module.test(module)) {
// Without a full semver library in the editor,
// we skip the version check.
// Not ideal - but will get caught in the runtime
// if the user tries to install.
return rule;
}
}
}
function checkModuleAllowed(module,version,allowList,denyList) {
if (!allowList && !denyList) {
// Default to allow
return true;
}
if (allowList.length === 0 && denyList.length === 0) {
return true;
}
var allowedRule = checkAgainstList(module,version,allowList);
var deniedRule = checkAgainstList(module,version,denyList);
// console.log("A",allowedRule)
// console.log("D",deniedRule)
if (allowedRule && !deniedRule) {
return true;
}
if (!allowedRule && deniedRule) {
return false;
}
if (!allowedRule && !deniedRule) {
return true;
}
if (allowedRule.wildcardPos !== deniedRule.wildcardPos) {
return allowedRule.wildcardPos > deniedRule.wildcardPos
} else {
// First wildcard in same position.
// Go with the longer matching rule. This isn't going to be 100%
// right, but we are deep into edge cases at this point.
return allowedRule.module.toString().length > deniedRule.module.toString().length
}
return false;
}
function init() {
if (RED.settings.theme('palette.editable') === false) {
if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
return;
}
var settingsAllowList = RED.settings.get("externalModules.palette.allowList")
var settingsDenyList = RED.settings.get("externalModules.palette.denyList")
if (settingsAllowList || settingsDenyList) {
installAllowList = settingsAllowList;
installDenyList = settingsDenyList
}
installAllowList = parseModuleList(installAllowList);
installDenyList = parseModuleList(installDenyList);
createSettingsPane();
RED.userSettings.add({
@ -880,7 +958,7 @@ RED.palette.editor = (function() {
}
});
if (RED.settings.theme('palette.upload') !== false) {
if (RED.settings.get('externalModules.palette.allowUpload', true) !== false) {
var uploadSpan = $('<span class="button-group">').prependTo(toolBar);
var uploadButton = $('<button type="button" class="red-ui-sidebar-header-button red-ui-palette-editor-upload-button"><label><i class="fa fa-upload"></i><form id="red-ui-palette-editor-upload-form" enctype="multipart/form-data"><input name="tarball" type="file" accept=".tgz"></label></button>').appendTo(uploadSpan);
@ -962,7 +1040,7 @@ RED.palette.editor = (function() {
}
function update(entry,version,url,container,done) {
if (RED.settings.theme('palette.editable') === false) {
if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
done(new Error('Palette not editable'));
return;
}
@ -1021,7 +1099,7 @@ RED.palette.editor = (function() {
})
}
function remove(entry,container,done) {
if (RED.settings.theme('palette.editable') === false) {
if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
done(new Error('Palette not editable'));
return;
}
@ -1078,7 +1156,7 @@ RED.palette.editor = (function() {
})
}
function install(entry,container,done) {
if (RED.settings.theme('palette.editable') === false) {
if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
done(new Error('Palette not editable'));
return;
}

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) {
@ -147,7 +152,7 @@ RED.palette = (function() {
var popOverContent;
try {
var l = "<p><b>"+RED.text.bidi.enforceTextDirectionWithUCC(label)+"</b></p>";
popOverContent = $('<div></div>').append($(l+(info?info:$("script[data-help-name='"+type+"']").html()||"<p>"+RED._("palette.noInfo")+"</p>").trim())
popOverContent = $('<div></div>').append($(l+(info?info:RED.nodes.getNodeHelp(type)||"<p>"+RED._("palette.noInfo")+"</p>").trim())
.filter(function(n) {
return (this.nodeType == 1 && this.nodeName == "P") || (this.nodeType == 3 && this.textContent.trim().length > 0)
}).slice(0,2));
@ -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) {
@ -264,27 +278,6 @@ RED.palette = (function() {
d.data('popover',popover);
// $(d).popover({
// title:d.type,
// placement:"right",
// trigger: "hover",
// delay: { show: 750, hide: 50 },
// html: true,
// container:'body'
// });
// d.on("click", function() {
// RED.view.focus();
// var helpText;
// if (nt.indexOf("subflow:") === 0) {
// helpText = RED.utils.renderMarkdown(RED.nodes.subflow(nt.substring(8)).info||"")||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>');
// } else {
// helpText = $("script[data-help-name='"+d.attr("data-palette-type")+"']").html()||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>');
// }
// // Don't look too closely. RED.sidebar.info.set will set the 'Description'
// // section of the sidebar. Pass in the title of the Help section so it looks
// // right.
// RED.sidebar.type.show(helpText,RED._("sidebar.info.nodeHelp"));
// });
var chart = $("#red-ui-workspace-chart");
var chartSVG = $("#red-ui-workspace-chart>svg").get(0);
var activeSpliceLink;
@ -417,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

@ -465,7 +465,7 @@ RED.projects.settings = (function() {
metaRow = $('<div class="red-ui-palette-module-meta"></div>').appendTo(headerRow);
var buttons = $('<div class="red-ui-palette-module-button-group"></div>').appendTo(metaRow);
if (RED.user.hasPermission("projects.write")) {
if (!entry.installed && RED.settings.theme('palette.editable') !== false) {
if (!entry.installed && RED.settings.get('externalModules.palette.allowInstall', true) !== false) {
$('<a href="#" class="red-ui-button red-ui-button-small">' + RED._("sidebar.project.projectSettings.install") + '</a>').appendTo(buttons)
.on("click", function(evt) {
evt.preventDefault();

View File

@ -43,9 +43,11 @@ RED.projects.userSettings = (function() {
function createWorkflowSection(pane) {
var defaultWorkflowMode = RED.settings.theme("projects.workflow.mode","manual");
var currentGitSettings = RED.settings.get('git') || {};
currentGitSettings.workflow = currentGitSettings.workflow || {};
currentGitSettings.workflow.mode = currentGitSettings.workflow.mode || "manual";
currentGitSettings.workflow.mode = currentGitSettings.workflow.mode || defaultWorkflowMode;
var title = $('<h3></h3>').text(RED._("editor:sidebar.project.userSettings.workflow")).appendTo(pane);

View File

@ -294,7 +294,10 @@ RED.sidebar.versionControl = (function() {
// TODO: this is a full refresh of the files - should be able to
// just do an incremental refresh
var workflowMode = ((RED.settings.get('git') || {}).workflow || {}).mode || "manual";
// Get the default workflow mode from theme settings
var defaultWorkflowMode = RED.settings.theme("projects.workflow.mode","manual");
// Check for the user-defined choice of mode
var workflowMode = ((RED.settings.get('git') || {}).workflow || {}).mode || defaultWorkflowMode;
if (workflowMode === 'auto') {
refresh(true);
} else {

View File

@ -47,6 +47,37 @@ RED.subflow = (function() {
'</div>'+
'</script>';
var _subflowModulePaneTemplate = '<form class="dialog-form form-horizontal" autocomplete="off">'+
'<div class="form-row">'+
'<label for="subflow-input-module-module" data-i18n="[append]editor:subflow.module"><i class="fa fa-cube"></i> </label>'+
'<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-module" data-i18n="[placeholder]common.label.name">'+
'</div>'+
'<div class="form-row">'+
'<label for="subflow-input-module-type" data-i18n="[append]editor:subflow.type"> </label>'+
'<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-type">'+
'</div>'+
'<div class="form-row">'+
'<label for="subflow-input-module-version" data-i18n="[append]editor:subflow.version"></label>'+
'<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-version" data-i18n="[placeholder]editor:subflow.versionPlaceholder">'+
'</div>'+
'<div class="form-row">'+
'<label for="subflow-input-module-desc" data-i18n="[append]editor:subflow.desc"></label>'+
'<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-desc">'+
'</div>'+
'<div class="form-row">'+
'<label for="subflow-input-module-license" data-i18n="[append]editor:subflow.license"></label>'+
'<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-license">'+
'</div>'+
'<div class="form-row">'+
'<label for="subflow-input-module-author" data-i18n="[append]editor:subflow.author"></label>'+
'<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-author" data-i18n="[placeholder]editor:subflow.authorPlaceholder">'+
'</div>'+
'<div class="form-row">'+
'<label for="subflow-input-module-keywords" data-i18n="[append]editor:subflow.keys"></label>'+
'<input style="width: calc(100% - 110px)" type="text" id="subflow-input-module-keywords" data-i18n="[placeholder]editor:subflow.keysPlaceholder">'+
'</div>'+
'</form>';
function findAvailableSubflowIOPosition(subflow,isInput) {
var pos = {x:50,y:30};
if (!isInput) {
@ -433,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);
}
});
@ -993,6 +1055,7 @@ RED.subflow = (function() {
icon: "",
type: "cred"
}
opt.ui.type = "cred";
} else {
opt.ui = opt.ui || {
icon: "",
@ -1488,6 +1551,7 @@ RED.subflow = (function() {
var locale = RED.i18n.lang();
var labelText = lookupLabel(labels, labels["en-US"]||tenv.name, locale);
var label = $('<label>').appendTo(row);
$('<span>&nbsp;</span>').appendTo(row);
var labelContainer = $('<span></span>').appendTo(label);
if (ui.icon) {
var newPath = RED.utils.separateIconPath(ui.icon);
@ -1723,8 +1787,6 @@ RED.subflow = (function() {
parentEnv[env.name] = item;
})
}
}
if (node.env) {
for (var i = 0; i < node.env.length; i++) {
var env = node.env[i];
@ -1740,6 +1802,40 @@ RED.subflow = (function() {
}
}
}
} else if (node._def.subflowModule) {
var keys = Object.keys(node._def.defaults);
keys.forEach(function(name) {
if (name !== 'name') {
var prop = node._def.defaults[name];
var nodeProp = node[name];
var nodePropType;
var nodePropValue = nodeProp;
if (prop.ui && prop.ui.type === "cred") {
nodePropType = "cred";
} else {
switch(typeof nodeProp) {
case "string": nodePropType = "str"; break;
case "number": nodePropType = "num"; break;
case "boolean": nodePropType = "bool"; nodePropValue = nodeProp?"true":"false"; break;
default:
nodePropType = nodeProp.type;
nodePropValue = nodeProp.value;
}
}
var item = {
name: name,
type: nodePropType,
value: nodePropValue,
parent: {
type: prop.type,
value: prop.value
},
ui: $.extend(true,{},prop.ui)
}
envList.push(item);
}
})
}
return envList;
}
@ -1859,6 +1955,126 @@ RED.subflow = (function() {
buildPropertiesList(list, node);
}
function setupInputValidation(input,validator) {
var errorTip;
var validateTimeout;
var validateFunction = function() {
if (validateTimeout) {
return;
}
validateTimeout = setTimeout(function() {
var error = validator(input.val());
// if (!error && errorTip) {
// errorTip.close();
// errorTip = null;
// } else if (error && !errorTip) {
// errorTip = RED.popover.create({
// tooltip: true,
// target:input,
// size: "small",
// direction: "bottom",
// content: error,
// }).open();
// }
input.toggleClass("input-error",!!error);
validateTimeout = null;
})
}
input.on("change keyup paste", validateFunction);
}
function buildModuleForm(container, node) {
$(_subflowModulePaneTemplate).appendTo(container);
var moduleProps = node.meta || {};
[
'module',
'type',
'version',
'author',
'desc',
'keywords',
'license'
].forEach(function(property) {
$("#subflow-input-module-"+property).val(moduleProps[property]||"")
})
$("#subflow-input-module-type").attr("placeholder",node.id);
setupInputValidation($("#subflow-input-module-module"), function(newValue) {
newValue = newValue.trim();
var isValid = newValue.length < 215;
isValid = isValid && !/^[._]/.test(newValue);
isValid = isValid && !/[A-Z]/.test(newValue);
if (newValue !== encodeURIComponent(newValue)) {
var m = /^@([^\/]+)\/([^\/]+)$/.exec(newValue);
if (m) {
isValid = isValid && (m[1] === encodeURIComponent(m[1]) && m[2] === encodeURIComponent(m[2]))
} else {
isValid = false;
}
}
return isValid?"":"Invalid module name"
})
setupInputValidation($("#subflow-input-module-version"), function(newValue) {
newValue = newValue.trim();
var isValid = newValue === "" ||
/^(\d|[1-9]\d*)\.(\d|[1-9]\d*)\.(\d|[1-9]\d*)(-(0|[1-9A-Za-z-][0-9A-Za-z-]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9A-Za-z-][0-9A-Za-z-]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/.test(newValue);
return isValid?"":"Invalid version number"
})
var licenses = ["none", "Apache-2.0", "BSD-3-Clause", "BSD-2-Clause", "GPL-2.0", "GPL-3.0", "MIT", "MPL-2.0", "CDDL-1.0", "EPL-2.0"];
var typedLicenses = {
types: licenses.map(function(l) {
return {
value: l,
label: l === "none" ? RED._("editor:subflow.licenseNone") : l,
hasValue: false
};
})
}
typedLicenses.types.push({
value:"_custom_", label:RED._("editor:subflow.licenseOther"), icon:"red/images/typedInput/az.svg"
})
if (!moduleProps.license) {
typedLicenses.default = "none";
} else if (licenses.indexOf(moduleProps.license) > -1) {
typedLicenses.default = moduleProps.license;
} else {
typedLicenses.default = "_custom_";
}
$("#subflow-input-module-license").typedInput(typedLicenses)
}
function exportSubflowModuleProperties(node) {
var value;
var moduleProps = {};
[
'module',
'type',
'version',
'author',
'desc',
'keywords'
].forEach(function(property) {
value = $("#subflow-input-module-"+property).val().trim();
if (value) {
moduleProps[property] = value;
}
})
var selectedLicenseType = $("#subflow-input-module-license").typedInput("type");
if (selectedLicenseType === '_custom_') {
value = $("#subflow-input-module-license").val();
if (value) {
moduleProps.license = value;
}
} else if (selectedLicenseType !== "none") {
moduleProps.license = selectedLicenseType;
}
return moduleProps;
}
return {
init: init,
createSubflow: createSubflow,
@ -1872,9 +2088,11 @@ RED.subflow = (function() {
buildEditForm: buildEditForm,
buildPropertiesForm: buildPropertiesForm,
buildModuleForm: buildModuleForm,
exportSubflowTemplateEnv: exportEnvList,
exportSubflowInstanceEnv: exportSubflowInstanceEnv
exportSubflowInstanceEnv: exportSubflowInstanceEnv,
exportSubflowModuleProperties: exportSubflowModuleProperties
}
})();

View File

@ -247,7 +247,7 @@ RED.sidebar.help = (function() {
helpText = (RED.utils.renderMarkdown(subflowNode.info||"")||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>'));
title = subflowNode.name || nodeType;
} else {
helpText = $("script[data-help-name='"+nodeType+"']").html()||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>');
helpText = RED.nodes.getNodeHelp(nodeType)||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>');
title = nodeType;
}
setInfoText(title, helpText, helpSection);

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]);
@ -382,20 +382,13 @@ RED.sidebar.info = (function() {
var category = subflowNode.category||"subflows";
$(propRow.children()[1]).text(RED._("palette.label."+category,{defaultValue:category}))
$('<tr class="node-info-subflow-row"><td>'+RED._("sidebar.info.instances")+"</td><td>"+subflowUserCount+'</td></tr>').appendTo(tableBody);
if (subflowNode.meta) {
propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("subflow.module")+'</td><td></td></tr>').appendTo(tableBody);
$(propRow.children()[1]).text(subflowNode.meta.module||"")
propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("subflow.version")+'</td><td></td></tr>').appendTo(tableBody);
$(propRow.children()[1]).text(subflowNode.meta.version||"")
}
}
// var helpText = "";
// if (node.type === "tab" || node.type === "subflow") {
// } else {
// if (subflowNode && node.type !== "subflow") {
// // Selected a subflow instance node.
// // - The subflow template info goes into help
// helpText = (RED.utils.renderMarkdown(subflowNode.info||"")||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>'));
// } else {
// helpText = $("script[data-help-name='"+node.type+"']").html()||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>');
// }
// setInfoText(helpText, helpSection.content);
// }
var infoText = "";
@ -409,23 +402,6 @@ RED.sidebar.info = (function() {
}
var infoSectionContainer = $("<div>").css("padding","0 6px 6px").appendTo(propertiesPanelContent)
// var editInfo = $('<button class="red-ui-button red-ui-button-small" style="float: right"><i class="fa fa-file-text-o"></button>').appendTo(infoSectionContainer).on("click", function(evt) {
// //.text(RED._("sidebar.info.editDescription"))
// evt.preventDefault();
// evt.stopPropagation();
// if (node.type === 'tab') {
//
// } else if (node.type === 'subflow') {
//
// } else if (node.type === 'group') {
//
// } else if (node._def.category !== 'config') {
// RED.editor.edit(node,"editor-tab-description");
// } else {
//
// }
// })
setInfoText(infoText, infoSectionContainer);
$(".red-ui-sidebar-info-stack").scrollTop(0);

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

@ -219,7 +219,7 @@ RED.user = (function() {
function init() {
if (RED.settings.user) {
if (!RED.settings.editorTheme || !RED.settings.editorTheme.hasOwnProperty("userMenu")) {
if (!RED.settings.editorTheme || !RED.settings.editorTheme.hasOwnProperty("userMenu") || RED.settings.editorTheme.userMenu) {
var userMenu = $('<li><a id="red-ui-header-button-user" class="button hide" href="#"></a></li>')
.prependTo(".red-ui-header-toolbar");

View File

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

View File

@ -765,6 +765,10 @@ button.red-ui-toggleButton.toggle {
width: calc(100% - 10px);
padding-left: 3px;
}
select {
padding: 0 3px;
font-size: 11px;
}
.placeholder-input {
span:first-child {
display:inline-block;

View File

@ -139,6 +139,9 @@
stroke-width: 2;
}
.red-ui-flow-node-icon-group {
text {
@include disable-selection;
}
.fa-lg {
@include disable-selection;
stroke: none;

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

@ -26,6 +26,14 @@
box-sizing: border-box;
overflow:visible;
position: relative;
&[disabled] {
input, button {
background: $secondary-background-inactive;
pointer-events: none;
cursor: not-allowed;
}
}
.red-ui-typedInput-input-wrap {
flex-grow: 1;
}

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

@ -80,10 +80,10 @@ module.exports = function(RED) {
this.drop = n.drop;
var node = this;
function ourTimeout(handler, delay) {
function ourTimeout(handler, delay, clearHandler) {
var toutID = setTimeout(handler, delay);
return {
clear: function() { clearTimeout(toutID); },
clear: function() { clearTimeout(toutID); clearHandler(); },
trigger: function() { clearTimeout(toutID); return handler(); }
};
}
@ -113,14 +113,15 @@ module.exports = function(RED) {
}
if (node.pauseType === "delay") {
node.on("input", function(msg) {
if (msg.hasOwnProperty("flush")) { flushDelayList(); }
node.on("input", function(msg, send, done) {
if (msg.hasOwnProperty("flush")) { flushDelayList(); done(); }
else {
var id = ourTimeout(function() {
node.idList.splice(node.idList.indexOf(id),1);
if (node.idList.length === 0) { node.status({}); }
node.send(msg);
}, node.timeout);
send(msg);
done();
}, node.timeout, () => done());
node.idList.push(id);
if ((node.timeout > 1000) && (node.idList.length !== 0)) {
node.status({fill:"blue",shape:"dot",text:" "});
@ -131,7 +132,7 @@ module.exports = function(RED) {
node.on("close", function() { clearDelayList(); });
}
else if (node.pauseType === "delayv") {
node.on("input", function(msg) {
node.on("input", function(msg, send, done) {
var delayvar = Number(node.timeout);
if (msg.hasOwnProperty("delay") && !isNaN(parseFloat(msg.delay))) {
delayvar = parseFloat(msg.delay);
@ -140,8 +141,9 @@ module.exports = function(RED) {
var id = ourTimeout(function() {
node.idList.splice(node.idList.indexOf(id),1);
if (node.idList.length === 0) { node.status({}); }
node.send(msg);
}, delayvar);
send(msg);
done();
}, delayvar, () => done());
node.idList.push(id);
if ((delayvar >= 0) && (node.idList.length !== 0)) {
node.status({fill:"blue",shape:"dot",text:delayvar/1000+"s"});
@ -152,7 +154,7 @@ module.exports = function(RED) {
node.on("close", function() { clearDelayList(); });
}
else if (node.pauseType === "rate") {
node.on("input", function(msg) {
node.on("input", function(msg, send, done) {
if (msg.hasOwnProperty("reset")) {
if (node.intervalID !== -1 ) {
clearInterval(node.intervalID);
@ -161,17 +163,18 @@ module.exports = function(RED) {
delete node.lastSent;
node.buffer = [];
node.status({text:"reset"});
done();
return;
}
if (!node.drop) {
var m = RED.util.cloneMessage(msg);
delete m.flush;
if (node.intervalID !== -1) {
node.buffer.push(m);
node.buffer.push({msg: m, send: send, done: done});
node.reportDepth();
}
else {
node.send(m);
send(m);
node.reportDepth();
node.intervalID = setInterval(function() {
if (node.buffer.length === 0) {
@ -179,16 +182,22 @@ module.exports = function(RED) {
node.intervalID = -1;
}
if (node.buffer.length > 0) {
node.send(node.buffer.shift());
const msgInfo = node.buffer.shift();
msgInfo.send(msgInfo.msg);
msgInfo.done();
}
node.reportDepth();
}, node.rate);
done();
}
if (msg.hasOwnProperty("flush")) {
while (node.buffer.length > 0) {
node.send(node.buffer.shift());
const msgInfo = node.buffer.shift();
msgInfo.send(msgInfo.msg);
msgInfo.done();
}
node.status({});
done();
}
}
else {
@ -198,17 +207,19 @@ module.exports = function(RED) {
}
if (!node.lastSent) { // ensuring that we always send the first message
node.lastSent = process.hrtime();
node.send(msg);
send(msg);
}
else if ( ( (timeSinceLast[0] * SECONDS_TO_NANOS) + timeSinceLast[1] ) > (node.rate * MILLIS_TO_NANOS) ) {
node.lastSent = process.hrtime();
node.send(msg);
send(msg);
}
done();
}
});
node.on("close", function() {
clearInterval(node.intervalID);
clearTimeout(node.busy);
node.buffer.forEach((msgInfo) => msgInfo.done());
node.buffer = [];
node.status({});
});
@ -217,57 +228,75 @@ module.exports = function(RED) {
node.intervalID = setInterval(function() {
if (node.pauseType === "queue") {
if (node.buffer.length > 0) {
node.send(node.buffer.shift()); // send the first on the queue
const msgInfo = node.buffer.shift();
msgInfo.send(msgInfo.msg); // send the first on the queue
msgInfo.done();
}
}
else {
while (node.buffer.length > 0) { // send the whole queue
node.send(node.buffer.shift());
const msgInfo = node.buffer.shift();
msgInfo.send(msgInfo.msg);
msgInfo.done();
}
}
node.reportDepth();
},node.rate);
var hit;
node.on("input", function(msg) {
node.on("input", function(msg, send, done) {
if (!msg.hasOwnProperty("topic")) { msg.topic = "_none_"; }
hit = false;
for (var b in node.buffer) { // check if already in queue
if (msg.topic === node.buffer[b].topic) {
node.buffer[b] = msg; // if so - replace existing entry
if (msg.topic === node.buffer[b].msg.topic) {
node.buffer[b].done();
node.buffer[b] = {msg, send, done}; // if so - replace existing entry
hit = true;
break;
}
}
if (!hit) {
node.buffer.push(msg); // if not add to end of queue
node.buffer.push({msg, send, done}); // if not add to end of queue
node.reportDepth();
}
if (msg.hasOwnProperty("reset")) {
while (node.buffer.length > 0) {
const msgInfo = node.buffer.shift();
msgInfo.done();
}
node.buffer = [];
node.status({text:"reset"});
done();
}
if (msg.hasOwnProperty("flush")) {
while (node.buffer.length > 0) {
node.send(node.buffer.shift());
const msgInfo = node.buffer.shift();
msgInfo.send(msgInfo.msg);
msgInfo.done();
}
node.status({});
done();
}
});
node.on("close", function() {
clearInterval(node.intervalID);
while (node.buffer.length > 0) {
const msgInfo = node.buffer.shift();
msgInfo.done();
}
node.buffer = [];
node.status({});
});
}
else if (node.pauseType === "random") {
node.on("input", function(msg) {
node.on("input", function(msg, send, done) {
var wait = node.randomFirst + (node.diff * Math.random());
var id = ourTimeout(function() {
node.idList.splice(node.idList.indexOf(id),1);
node.send(msg);
send(msg);
node.status({});
}, wait);
done();
}, wait, () => done());
node.idList.push(id);
if ((node.timeout >= 1000) && (node.idList.length !== 0)) {
node.status({fill:"blue",shape:"dot",text:parseInt(wait/10)/100+"s"});

View File

@ -82,10 +82,10 @@ module.exports = function(RED) {
var npay = {};
var pendingMessages = [];
var activeMessagePromise = null;
var processMessageQueue = function(msg) {
if (msg) {
var processMessageQueue = function(msgInfo) {
if (msgInfo) {
// A new message has arrived - add it to the message queue
pendingMessages.push(msg);
pendingMessages.push(msgInfo);
if (activeMessagePromise !== null) {
// The node is currently processing a message, so do nothing
// more with this message
@ -101,17 +101,17 @@ module.exports = function(RED) {
// There are more messages to process. Get the next message and
// start processing it. Recurse back in to check for any more
var nextMsg = pendingMessages.shift();
activeMessagePromise = processMessage(nextMsg)
var nextMsgInfo = pendingMessages.shift();
activeMessagePromise = processMessage(nextMsgInfo)
.then(processMessageQueue)
.catch((err) => {
node.error(err,nextMsg);
nextMsgInfo.done(err);
return processMessageQueue();
});
}
this.on('input', function(msg) {
processMessageQueue(msg);
this.on('input', function(msg, send, done) {
processMessageQueue({msg, send, done});
});
var stat = function() {
@ -121,7 +121,8 @@ module.exports = function(RED) {
else return {fill:"blue",shape:"dot",text:l};
}
var processMessage = function(msg) {
var processMessage = function(msgInfo) {
let msg = msgInfo.msg;
var topic = RED.util.getMessageProperty(msg,node.topic) || "_none";
var promise;
var delayDuration = node.duration;
@ -179,7 +180,10 @@ module.exports = function(RED) {
/* istanbul ignore else */
if (node.op1type !== "nul") {
var msg2 = RED.util.cloneMessage(msg);
node.topics[topic].tout = setInterval(function() { node.send(RED.util.cloneMessage(msg2)); }, delayDuration);
node.topics[topic].tout = setInterval(function() {
if (node.op1type === "date") { msg2.payload = Date.now(); }
msgInfo.send(RED.util.cloneMessage(msg2));
}, delayDuration);
}
}
else {
@ -203,14 +207,15 @@ module.exports = function(RED) {
}
promise.then(() => {
if (node.op2type === "payl") {
if (node.second === true) { node.send([null,npay[topic]]); }
else { node.send(npay[topic]); }
if (node.second === true) { msgInfo.send([null,npay[topic]]); }
else { msgInfo.send(npay[topic]); }
delete npay[topic];
}
else {
msg2.payload = node.topics[topic].m2;
if (node.second === true) { node.send([null,msg2]); }
else { node.send(msg2); }
if (node.op2type === "date") { msg2.payload = Date.now(); }
if (node.second === true) { msgInfo.send([null,msg2]); }
else { msgInfo.send(msg2); }
}
delete node.topics[topic];
node.status(stat());
@ -225,8 +230,9 @@ module.exports = function(RED) {
}, delayDuration);
}
}
msgInfo.done();
node.status(stat());
if (node.op1type !== "nul") { node.send(RED.util.cloneMessage(msg)); }
if (node.op1type !== "nul") { msgInfo.send(RED.util.cloneMessage(msg)); }
});
});
}
@ -262,8 +268,8 @@ module.exports = function(RED) {
}
delete node.topics[topic];
node.status(stat());
if (node.second === true) { node.send([null,msg2]); }
else { node.send(msg2); }
if (node.second === true) { msgInfo.send([null,msg2]); }
else { msgInfo.send(msg2); }
}).catch(err => {
node.error(err);
});
@ -273,6 +279,7 @@ module.exports = function(RED) {
// if (node.op2type === "payl") {node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
// }
}
msgInfo.done();
return Promise.resolve();
}
this.on("close", function() {

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

@ -210,7 +210,7 @@ module.exports = function(RED) {
var httpMiddleware = function(req,res,next) { next(); }
if (RED.settings.httpNodeMiddleware) {
if (typeof RED.settings.httpNodeMiddleware === "function") {
if (typeof RED.settings.httpNodeMiddleware === "function" || Array.isArray(RED.settings.httpNodeMiddleware)) {
httpMiddleware = RED.settings.httpNodeMiddleware;
}
}

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

@ -18,7 +18,7 @@ module.exports = function(RED) {
"use strict";
function CSVNode(n) {
RED.nodes.createNode(this,n);
this.template = (n.temp || "").split(",");
this.template = (n.temp || "");
this.sep = (n.sep || ',').replace("\\t","\t").replace("\\n","\n").replace("\\r","\r");
this.quo = '"';
this.ret = (n.ret || "\n").replace("\\n","\n").replace("\\r","\r");
@ -38,16 +38,12 @@ module.exports = function(RED) {
if (this.hdrout === true) { this.hdrout = "all"; }
var tmpwarn = true;
var node = this;
var re = new RegExp(',(?=(?:(?:[^"]*"){2})*[^"]*$)','g');
// pass in an array of column names to be trimed, de-quoted and retrimed
var clean = function(col) {
for (var t = 0; t < col.length; t++) {
col[t] = col[t].trim(); // remove leading and trailing whitespace
if (col[t].charAt(0) === '"' && col[t].charAt(col[t].length -1) === '"') {
// remove leading and trailing quotes (if they exist) - and remove whitepace again.
col[t] = col[t].substr(1,col[t].length -2).trim();
}
}
col = col.trim().split(re) || [""];
col = col.map(x => x.replace(/"/g,'').trim());
if ((col.length === 1) && (col[0] === "")) { node.goodtmpl = false; }
else { node.goodtmpl = true; }
return col;
@ -55,7 +51,7 @@ module.exports = function(RED) {
node.template = clean(node.template);
node.hdrSent = false;
this.on("input", function(msg) {
this.on("input", function(msg, send, done) {
if (msg.hasOwnProperty("reset")) {
node.hdrSent = false;
}
@ -67,13 +63,14 @@ module.exports = function(RED) {
if (node.hdrout !== "none" && node.hdrSent === false) {
if ((node.template.length === 1) && (node.template[0] === '')) {
if (msg.hasOwnProperty("columns")) {
node.template = clean((msg.columns || "").split(","));
node.template = clean(msg.columns || "");
}
else {
node.template = Object.keys(msg.payload[0]);
}
}
ou += node.template.join(node.sep) + node.ret;
// ou += node.template.join(node.sep) + node.ret;
ou += node.template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).join(node.sep) + node.ret;
if (node.hdrout === "once") { node.hdrSent = true; }
}
for (var s = 0; s < msg.payload.length; s++) {
@ -93,7 +90,7 @@ module.exports = function(RED) {
}
else {
if ((node.template.length === 1) && (node.template[0] === '') && (msg.hasOwnProperty("columns"))) {
node.template = clean((msg.columns || "").split(","));
node.template = clean(msg.columns || "")//.split(","));
}
if ((node.template.length === 1) && (node.template[0] === '')) {
/* istanbul ignore else */
@ -144,10 +141,11 @@ module.exports = function(RED) {
}
}
msg.payload = ou;
msg.columns = node.template.join(',');
if (msg.payload !== '') { node.send(msg); }
msg.columns = node.template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).join(',');
if (msg.payload !== '') { send(msg); }
done();
}
catch(e) { node.error(e,msg); }
catch(e) { done(e); }
}
else if (typeof msg.payload == "string") { // convert CSV string to object
try {
@ -178,7 +176,7 @@ module.exports = function(RED) {
if ((node.hdrin === true) && first) { // if the template is in the first line
if ((line[i] === "\n")||(line[i] === "\r")||(line.length - i === 1)) { // look for first line break
if (line.length - i === 1) { tmp += line[i]; }
node.template = clean(tmp.split(node.sep));
node.template = clean(tmp);
first = false;
}
else { tmp += line[i]; }
@ -254,22 +252,22 @@ module.exports = function(RED) {
}
if (msg.parts.index + 1 === msg.parts.count) {
msg.payload = node.store;
msg.columns = node.template.filter(val => val).join(',');
msg.columns = node.template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(',');
delete msg.parts;
node.send(msg);
send(msg);
node.store = [];
}
}
else {
msg.columns = node.template.filter(val => val).join(',');
node.send(msg); // finally send the array
msg.columns = node.template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(',');
send(msg); // finally send the array
}
}
else {
var len = a.length;
for (var i = 0; i < len; i++) {
var newMessage = RED.util.cloneMessage(msg);
newMessage.columns = node.template.filter(val => val).join(',');
newMessage.columns = node.template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).filter(v => v).join(',');
newMessage.payload = a[i];
if (!has_parts) {
newMessage.parts = {
@ -286,19 +284,21 @@ module.exports = function(RED) {
newMessage.parts.count -= 1;
}
}
node.send(newMessage);
send(newMessage);
}
}
node.linecount = 0;
done();
}
catch(e) { node.error(e,msg); }
catch(e) { done(e); }
}
else { node.warn(RED._("csv.errors.csv_js")); }
else { node.warn(RED._("csv.errors.csv_js")); done(); }
}
else {
if (!msg.hasOwnProperty("reset")) {
node.send(msg); // If no payload and not reset - just pass it on.
send(msg); // If no payload and not reset - just pass it on.
}
done();
}
});
}

View File

@ -17,18 +17,18 @@
module.exports = function(RED) {
"use strict";
function sendArray(node,msg,array) {
function sendArray(node,msg,array,send) {
for (var i = 0; i < array.length-1; i++) {
msg.payload = array[i];
msg.parts.index = node.c++;
if (node.stream !== true) { msg.parts.count = array.length; }
node.send(RED.util.cloneMessage(msg));
send(RED.util.cloneMessage(msg));
}
if (node.stream !== true) {
msg.payload = array[i];
msg.parts.index = node.c++;
msg.parts.count = array.length;
node.send(RED.util.cloneMessage(msg));
send(RED.util.cloneMessage(msg));
node.c = 0;
}
else { node.remainder = array[i]; }
@ -67,7 +67,8 @@ module.exports = function(RED) {
}
node.c = 0;
node.buffer = Buffer.from([]);
this.on("input", function(msg) {
node.pendingDones = [];
this.on("input", function(msg, send, done) {
if (msg.hasOwnProperty("payload")) {
if (msg.hasOwnProperty("parts")) { msg.parts = { parts:msg.parts }; } // push existing parts to a stack
else { msg.parts = {}; }
@ -93,14 +94,23 @@ module.exports = function(RED) {
msg.payload = data.substring(pos,pos+node.splt);
msg.parts.index = node.c++;
pos += node.splt;
node.send(RED.util.cloneMessage(msg));
send(RED.util.cloneMessage(msg));
}
if (count > 1) {
node.pendingDones.forEach(d => d());
node.pendingDones = [];
}
node.remainder = data.substring(pos);
if ((node.stream !== true) || (node.remainder.length === node.splt)) {
msg.payload = node.remainder;
msg.parts.index = node.c++;
node.send(RED.util.cloneMessage(msg));
send(RED.util.cloneMessage(msg));
node.pendingDones.forEach(d => d());
node.pendingDones = [];
done();
node.remainder = "";
} else {
node.pendingDones.push(done);
}
}
else {
@ -115,7 +125,8 @@ module.exports = function(RED) {
a = msg.payload.split(node.splt);
msg.parts.ch = node.splt; // pass the split char to other end for rejoin
}
sendArray(node,msg,a);
sendArray(node,msg,a,send);
done();
}
}
else if (Array.isArray(msg.payload)) { // then split array into messages
@ -135,8 +146,9 @@ module.exports = function(RED) {
}
msg.parts.index = i;
pos += node.arraySplt;
node.send(RED.util.cloneMessage(msg));
send(RED.util.cloneMessage(msg));
}
done();
}
else if ((typeof msg.payload === "object") && !Buffer.isBuffer(msg.payload)) {
var j = 0;
@ -152,10 +164,11 @@ module.exports = function(RED) {
msg.parts.key = p;
msg.parts.index = j;
msg.parts.count = l;
node.send(RED.util.cloneMessage(msg));
send(RED.util.cloneMessage(msg));
j += 1;
}
}
done();
}
else if (Buffer.isBuffer(msg.payload)) {
var len = node.buffer.length + msg.payload.length;
@ -176,14 +189,23 @@ module.exports = function(RED) {
msg.payload = buff.slice(pos,pos+node.splt);
msg.parts.index = node.c++;
pos += node.splt;
node.send(RED.util.cloneMessage(msg));
send(RED.util.cloneMessage(msg));
}
if (count > 1) {
node.pendingDones.forEach(d => d());
node.pendingDones = [];
}
node.buffer = buff.slice(pos);
if ((node.stream !== true) || (node.buffer.length === node.splt)) {
msg.payload = node.buffer;
msg.parts.index = node.c++;
node.send(RED.util.cloneMessage(msg));
send(RED.util.cloneMessage(msg));
node.pendingDones.forEach(d => d());
node.pendingDones = [];
done();
node.buffer = Buffer.from([]);
} else {
node.pendingDones.push(done);
}
}
else {
@ -210,23 +232,34 @@ module.exports = function(RED) {
while (pos > -1) {
msg.payload = buff.slice(p,pos);
msg.parts.index = node.c++;
node.send(RED.util.cloneMessage(msg));
send(RED.util.cloneMessage(msg));
i++;
p = pos+node.splt.length;
pos = buff.indexOf(node.splt,p);
}
if (count > 1) {
node.pendingDones.forEach(d => d());
node.pendingDones = [];
}
if ((node.stream !== true) && (p < buff.length)) {
msg.payload = buff.slice(p,buff.length);
msg.parts.index = node.c++;
msg.parts.count = node.c++;
node.send(RED.util.cloneMessage(msg));
send(RED.util.cloneMessage(msg));
node.pendingDones.forEach(d => d());
node.pendingDones = [];
}
else {
node.buffer = buff.slice(p,buff.length);
node.pendingDones.push(done);
}
if (node.buffer.length == 0) {
done();
}
}
} else { // otherwise drop the message.
done();
}
//else { } // otherwise drop the message.
}
});
}
@ -264,16 +297,16 @@ module.exports = function(RED) {
}
function reduceMessageGroup(node,msgs,exp,fixup,count,accumulator,done) {
var msg = msgs.shift();
exp.assign("I", msg.parts.index);
function reduceMessageGroup(node,msgInfos,exp,fixup,count,accumulator,done) {
var msgInfo = msgInfos.shift();
exp.assign("I", msgInfo.msg.parts.index);
exp.assign("N", count);
exp.assign("A", accumulator);
RED.util.evaluateJSONataExpression(exp, msg, (err,result) => {
RED.util.evaluateJSONataExpression(exp, msgInfo.msg, (err,result) => {
if (err) {
return done(err);
}
if (msgs.length === 0) {
if (msgInfos.length === 0) {
if (fixup) {
fixup.assign("N", count);
fixup.assign("A", result);
@ -281,39 +314,43 @@ module.exports = function(RED) {
if (err) {
return done(err);
}
node.send({payload: result});
msgInfo.send({payload: result});
done();
});
} else {
node.send({payload: result});
msgInfo.send({payload: result});
done();
}
} else {
reduceMessageGroup(node,msgs,exp,fixup,count,result,done);
reduceMessageGroup(node,msgInfos,exp,fixup,count,result,done);
}
});
}
function reduceAndSendGroup(node, group, done) {
var is_right = node.reduce_right;
var flag = is_right ? -1 : 1;
var msgs = group.msgs;
var msgInfos = group.msgs;
const preservedMsgInfos = [...msgInfos];
try {
RED.util.evaluateNodeProperty(node.exp_init, node.exp_init_type, node, {}, (err,accum) => {
var reduceExpression = node.reduceExpression;
var fixupExpression = node.fixupExpression;
var count = group.count;
msgs.sort(function(x,y) {
var ix = x.parts.index;
var iy = y.parts.index;
msgInfos.sort(function(x,y) {
var ix = x.msg.parts.index;
var iy = y.msg.parts.index;
if (ix < iy) {return -flag;}
if (ix > iy) {return flag;}
return 0;
});
reduceMessageGroup(node, msgs,reduceExpression,fixupExpression,count,accum,(err,result) => {
reduceMessageGroup(node, msgInfos,reduceExpression,fixupExpression,count,accum,(err,result) => {
if (err) {
preservedMsgInfos.pop(); // omit last message to emit error message
preservedMsgInfos.forEach(mInfo => mInfo.done());
done(err);
return;
} else {
preservedMsgInfos.forEach(mInfo => mInfo.done());
done();
}
})
@ -323,7 +360,8 @@ module.exports = function(RED) {
}
}
function reduceMessage(node, msg, done) {
function reduceMessage(node, msgInfo, done) {
let msg = msgInfo.msg;
if (msg.hasOwnProperty('parts')) {
var parts = msg.parts;
var pending = node.pending;
@ -344,7 +382,7 @@ module.exports = function(RED) {
if (parts.hasOwnProperty('count') && (group.count === undefined)) {
group.count = parts.count;
}
msgs.push(msg);
msgs.push(msgInfo);
pending_count++;
var completeProcess = function(err) {
if (err) {
@ -353,6 +391,13 @@ module.exports = function(RED) {
node.pending_count = pending_count;
var max_msgs = maxKeptMsgsCount(node);
if ((max_msgs > 0) && (pending_count > max_msgs)) {
Object.values(node.pending).forEach(group => {
group.msgs.forEach(mInfo => {
if (mInfo.msg._msgid !== msgInfo.msg._msgid) {
mInfo.done();
}
});
});
node.pending = {};
node.pending_count = 0;
done(RED._("join.too-many"));
@ -368,7 +413,8 @@ module.exports = function(RED) {
completeProcess();
}
} else {
node.send(msg);
msgInfo.send(msg);
msgInfo.done();
done();
}
}
@ -480,7 +526,9 @@ module.exports = function(RED) {
delete group.msg.parts;
}
delete group.msg.complete;
node.send(RED.util.cloneMessage(group.msg));
group.send(RED.util.cloneMessage(group.msg));
group.dones.forEach(f => f());
group.dones = [];
}
var pendingMessages = [];
@ -489,10 +537,10 @@ module.exports = function(RED) {
// groups may overlap and cause unexpected results. The use of JSONata
// means some async processing *might* occur if flow/global context is
// accessed.
var processReduceMessageQueue = function(msg) {
if (msg) {
var processReduceMessageQueue = function(msgInfo) {
if (msgInfo) {
// A new message has arrived - add it to the message queue
pendingMessages.push(msg);
pendingMessages.push(msgInfo);
if (activeMessage !== null) {
// The node is currently processing a message, so do nothing
// more with this message
@ -508,22 +556,23 @@ module.exports = function(RED) {
// There are more messages to process. Get the next message and
// start processing it. Recurse back in to check for any more
var nextMsg = pendingMessages.shift();
var nextMsgInfo = pendingMessages.shift();
activeMessage = true;
reduceMessage(node, nextMsg, err => {
reduceMessage(node, nextMsgInfo, err => {
if (err) {
node.error(err,nextMsg);
nextMsgInfo.done(err);//.error(err,nextMsg);
}
activeMessage = null;
processReduceMessageQueue();
})
}
this.on("input", function(msg) {
this.on("input", function(msg, send, done) {
try {
var property;
if (node.mode === 'auto' && (!msg.hasOwnProperty("parts")||!msg.parts.hasOwnProperty("id"))) {
node.warn("Message missing msg.parts property - cannot join in 'auto' mode")
done();
return;
}
@ -535,6 +584,7 @@ module.exports = function(RED) {
property = RED.util.getMessageProperty(msg,node.property);
} catch(err) {
node.warn("Message property "+node.property+" not found");
done();
return;
}
}
@ -557,7 +607,7 @@ module.exports = function(RED) {
propertyIndex = msg.parts.index;
}
else if (node.mode === 'reduce') {
return processReduceMessageQueue(msg);
return processReduceMessageQueue({msg, send, done});
}
else {
// Use the node configuration to identify all of the group information
@ -578,9 +628,11 @@ module.exports = function(RED) {
if (inflight[partId].timeout) {
clearTimeout(inflight[partId].timeout);
}
inflight[partId].dones.forEach(f => f());
delete inflight[partId]
}
return
done();
return;
}
if ((payloadType === 'object') && (propertyKey === null || propertyKey === undefined || propertyKey === "")) {
@ -591,6 +643,7 @@ module.exports = function(RED) {
if (msg.hasOwnProperty('complete')) {
if (inflight[partId]) {
inflight[partId].msg.complete = msg.complete;
inflight[partId].send = send;
completeSend(partId);
}
}
@ -598,6 +651,7 @@ module.exports = function(RED) {
node.warn("Message missing key property 'msg."+node.key+"' - cannot add to object")
}
}
done();
return;
}
@ -608,7 +662,9 @@ module.exports = function(RED) {
payload:{},
targetCount:targetCount,
type:"object",
msg:RED.util.cloneMessage(msg)
msg:RED.util.cloneMessage(msg),
send: send,
dones: []
};
}
else {
@ -617,7 +673,9 @@ module.exports = function(RED) {
payload:[],
targetCount:targetCount,
type:payloadType,
msg:RED.util.cloneMessage(msg)
msg:RED.util.cloneMessage(msg),
send: send,
dones: []
};
if (payloadType === 'string') {
inflight[partId].joinChar = joinChar;
@ -634,6 +692,7 @@ module.exports = function(RED) {
}, node.timer)
}
}
inflight[partId].dones.push(done);
var group = inflight[partId];
if (payloadType === 'buffer') {
@ -642,7 +701,7 @@ module.exports = function(RED) {
inflight[partId].bufferLen += property.length;
}
else {
node.error(RED._("join.errors.invalid-type",{error:(typeof property)}),msg);
done(RED._("join.errors.invalid-type",{error:(typeof property)}));
return;
}
}
@ -676,13 +735,18 @@ 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);
}
}
catch(err) {
done(err);
console.log(err.stack);
}
});
@ -691,9 +755,11 @@ module.exports = function(RED) {
for (var i in inflight) {
if (inflight.hasOwnProperty(i)) {
clearTimeout(inflight[i].timeout);
inflight[i].dones.forEach(d => d());
}
}
});
}
RED.nodes.registerType("join",JoinNode);
}

View File

@ -81,16 +81,16 @@ module.exports = function(RED) {
function sortMessageGroup(group) {
var promise;
var msgs = group.msgs;
var msgInfos = group.msgInfos;
if (key_is_exp) {
var evaluatedDataPromises = msgs.map(msg => {
var evaluatedDataPromises = msgInfos.map(mInfo => {
return new Promise((resolve,reject) => {
RED.util.evaluateJSONataExpression(key_exp, msg, (err, result) => {
RED.util.evaluateJSONataExpression(key_exp, mInfo.msg, (err, result) => {
if (err) {
reject(RED._("sort.invalid-exp",{message:err.toString()}));
} else {
resolve({
item: msg,
item: mInfo,
sortValue: result
})
}
@ -106,20 +106,21 @@ module.exports = function(RED) {
var key = function(msg) {
return ;
}
var comp = generateComparisonFunction(msg => RED.util.getMessageProperty(msg, key_prop));
var comp = generateComparisonFunction(mInfo => RED.util.getMessageProperty(mInfo.msg, key_prop));
try {
msgs.sort(comp);
msgInfos.sort(comp);
}
catch (e) {
return; // not send when error
}
promise = Promise.resolve(msgs);
promise = Promise.resolve(msgInfos);
}
return promise.then(msgs => {
for (var i = 0; i < msgs.length; i++) {
var msg = msgs[i];
return promise.then(msgInfos => {
for (let i = 0; i < msgInfos.length; i++) {
const msg = msgInfos[i].msg;
msg.parts.index = i;
node.send(msg);
msgInfos[i].send(msg);
msgInfos[i].done();
}
});
}
@ -181,65 +182,79 @@ module.exports = function(RED) {
}
}
if(oldest !== undefined) {
oldest.msgInfos[oldest.msgInfos.length - 1].done(RED._("sort.too-many"));
for (let i = 0; i < oldest.msgInfos.length - 1; i++) {
oldest.msgInfos[i].done();
}
delete pending[oldest_key];
return oldest.msgs.length;
return oldest.msgInfos.length;
}
return 0;
}
function processMessage(msg) {
function processMessage(msgInfo) {
const msg = msgInfo.msg;
if (target_is_prop) {
sortMessageProperty(msg).then(send => {
if (send) {
node.send(msg);
msgInfo.send(msg);
}
msgInfo.done();
}).catch(err => {
node.error(err,msg);
msgInfo.done(err);
});
return;
}
var parts = msg.parts;
if (!parts || !parts.hasOwnProperty("id") || !parts.hasOwnProperty("index")) {
msgInfo.done();
return;
}
var gid = parts.id;
if (!pending.hasOwnProperty(gid)) {
pending[gid] = {
count: undefined,
msgs: [],
msgInfos: [],
seq_no: pending_id++
};
}
var group = pending[gid];
var msgs = group.msgs;
msgs.push(msg);
var msgInfos = group.msgInfos;
msgInfos.push(msgInfo);
if (parts.hasOwnProperty("count")) {
group.count = parts.count;
}
pending_count++;
if (group.count === msgs.length) {
if (group.count === msgInfos.length) {
delete pending[gid]
sortMessageGroup(group).catch(err => {
node.error(err,msg);
// throw an error for last message, and just call done() for remaining messages
msgInfos[msgInfos.length-1].done(err);
for (let i = 0; i < msgInfos.length - 1; i++) {
msgInfos[i].done()
};
});
pending_count -= msgs.length;
pending_count -= msgInfos.length;
} else {
var max_msgs = max_kept_msgs_count(node);
if ((max_msgs > 0) && (pending_count > max_msgs)) {
pending_count -= removeOldestPending();
node.error(RED._("sort.too-many"), msg);
}
}
}
this.on("input", function(msg) {
processMessage(msg);
this.on("input", function(msg, send, done) {
processMessage({msg, send, done});
});
this.on("close", function() {
for(var key in pending) {
if (pending.hasOwnProperty(key)) {
node.log(RED._("sort.clear"), pending[key].msgs[0]);
node.log(RED._("sort.clear"), pending[key].msgInfos[0]);
const group = pending[key];
group.msgInfos.forEach(mInfo => {
mInfo.done();
});
delete pending[key];
}
}

View File

@ -32,11 +32,11 @@ module.exports = function(RED) {
return _max_kept_msgs_count;
}
function send_msgs(node, msgs, clone_msg) {
var count = msgs.length;
var msg_id = msgs[0]._msgid;
function send_msgs(node, msgInfos, clone_msg) {
var count = msgInfos.length;
var msg_id = msgInfos[0].msg._msgid;
for (var i = 0; i < count; i++) {
var msg = clone_msg ? RED.util.cloneMessage(msgs[i]) : msgs[i];
var msg = clone_msg ? RED.util.cloneMessage(msgInfos[i].msg) : msgInfos[i].msg;
if (!msg.hasOwnProperty("parts")) {
msg.parts = {};
}
@ -44,14 +44,16 @@ module.exports = function(RED) {
parts.id = msg_id;
parts.index = i;
parts.count = count;
node.send(msg);
msgInfos[i].send(msg);
//msgInfos[i].done();
}
}
function send_interval(node, allow_empty_seq) {
let msgs = node.pending;
if (msgs.length > 0) {
send_msgs(node, msgs, false);
let msgInfos = node.pending;
if (msgInfos.length > 0) {
send_msgs(node, msgInfos, false);
msgInfos.forEach(e => e.done());
node.pending = [];
}
else {
@ -108,19 +110,20 @@ module.exports = function(RED) {
return;
}
}
var msgs = [];
var msgInfos = [];
for (var topic of topics) {
var t_msgs = get_msgs_of_topic(pending, topic);
msgs = msgs.concat(t_msgs);
var t_msgInfos = get_msgs_of_topic(pending, topic);
msgInfos = msgInfos.concat(t_msgInfos);
}
for (var topic of topics) {
remove_topic(pending, topic);
}
send_msgs(node, msgs, true);
node.pending_count -= msgs.length;
send_msgs(node, msgInfos, true);
msgInfos.forEach(e => e.done() );
node.pending_count -= msgInfos.length;
}
function add_to_topic_group(pending, topic, gid, msg) {
function add_to_topic_group(pending, topic, gid, msgInfo) {
if (!pending.hasOwnProperty(topic)) {
pending[topic] = { groups: {}, gids: [] };
}
@ -132,32 +135,43 @@ module.exports = function(RED) {
gids.push(gid);
}
var group = groups[gid];
group.msgs.push(msg);
group.msgs.push(msgInfo);
if ((group.count === undefined) &&
msg.parts.hasOwnProperty('count')) {
group.count = msg.parts.count;
msgInfo.msg.parts.hasOwnProperty('count')) {
group.count = msgInfo.msg.parts.count;
}
}
function concat_msg(node, msg) {
function concat_msg(node, msg, send, done) {
var topic = msg.topic;
if(node.topics.indexOf(topic) >= 0) {
if (!msg.hasOwnProperty("parts") ||
!msg.parts.hasOwnProperty("id") ||
!msg.parts.hasOwnProperty("index") ||
!msg.parts.hasOwnProperty("count")) {
node.error(RED._("batch.no-parts"), msg);
done(RED._("batch.no-parts"));
return;
}
var gid = msg.parts.id;
var pending = node.pending;
add_to_topic_group(pending, topic, gid, msg);
add_to_topic_group(pending, topic, gid, {msg, send, done});
node.pending_count++;
var max_msgs = max_kept_msgs_count(node);
if ((max_msgs > 0) && (node.pending_count > max_msgs)) {
Object.values(node.pending).forEach(p_topic => {
Object.values(p_topic.groups).forEach(group => {
group.msgs.forEach(msgInfo => {
if (msgInfo.msg.id === msg.id) {
// the message that caused the overflow
msgInfo.done(RED._("batch.too-many"));
} else {
msgInfo.done();
}
})
})
});
node.pending = {};
node.pending_count = 0;
node.error(RED._("batch.too-many"), msg);
}
try_concat(node, pending);
}
@ -178,29 +192,37 @@ module.exports = function(RED) {
return;
}
node.pending = [];
this.on("input", function(msg) {
this.on("input", function(msg, send, done) {
if (msg.hasOwnProperty("reset")) {
node.pending.forEach(e => e.done());
node.pending = [];
node.pending_count = 0;
done();
return;
}
var queue = node.pending;
queue.push(msg);
queue.push({msg, send, done});
node.pending_count++;
if (queue.length === count) {
send_msgs(node, queue, is_overlap);
for (let i = 0; i < queue.length-overlap; i++) {
queue[i].done();
}
node.pending =
(overlap === 0) ? [] : queue.slice(-overlap);
node.pending_count = 0;
}
var max_msgs = max_kept_msgs_count(node);
if ((max_msgs > 0) && (node.pending_count > max_msgs)) {
let lastMInfo = node.pending.pop();
lastMInfo.done(RED._("batch.too-many"));
node.pending.forEach(e => e.done());
node.pending = [];
node.pending_count = 0;
node.error(RED._("batch.too-many"), msg);
}
});
this.on("close", function() {
node.pending.forEach(e=> e.done());
node.pending_count = 0;
node.pending = [];
});
@ -217,31 +239,36 @@ module.exports = function(RED) {
if (interval > 0) {
timer = setInterval(msgHandler, interval);
}
this.on("input", function(msg) {
this.on("input", function(msg, send, done) {
if (msg.hasOwnProperty("reset")) {
if (timer !== undefined) {
clearInterval(timer);
}
node.pending.forEach(e => e.done());
node.pending = [];
node.pending_count = 0;
done();
if (interval > 0) {
timer = setInterval(msgHandler, interval);
}
return;
}
node.pending.push(msg);
node.pending.push({msg, send, done});
node.pending_count++;
var max_msgs = max_kept_msgs_count(node);
if ((max_msgs > 0) && (node.pending_count > max_msgs)) {
let lastMInfo = node.pending.pop();
lastMInfo.done(RED._("batch.too-many"));
node.pending.forEach(e => e.done());
node.pending = [];
node.pending_count = 0;
node.error(RED._("batch.too-many"), msg);
}
});
this.on("close", function() {
if (timer !== undefined) {
clearInterval(timer);
}
node.pending.forEach(e => e.done());
node.pending = [];
node.pending_count = 0;
});
@ -251,15 +278,26 @@ module.exports = function(RED) {
return x.topic;
});
node.pending = {};
this.on("input", function(msg) {
this.on("input", function(msg, send, done) {
if (msg.hasOwnProperty("reset")) {
Object.values(node.pending).forEach(p_topic => {
Object.values(p_topic.groups).forEach(group => {
group.msgs.forEach(e => e.done());
});
});
node.pending = {};
node.pending_count = 0;
done();
return;
}
concat_msg(node, msg);
concat_msg(node, msg, send, done);
});
this.on("close", function() {
Object.values(node.pending).forEach(p_topic => {
Object.values(p_topic.groups).forEach(group => {
group.msgs.forEach(e => e.done());
});
});
node.pending = {};
node.pending_count = 0;
});

View File

@ -0,0 +1,99 @@
[
{
"id": "330f4888.cccb28",
"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": 260,
"y": 180,
"wires": [
[
"ed11f8d6.5e3c88"
]
]
},
{
"id": "a0288b44.71d488",
"type": "csv",
"z": "4b63452d.672afc",
"name": "",
"sep": ",",
"hdrin": "",
"hdrout": "none",
"multi": "one",
"ret": "\\n",
"temp": "",
"skip": "0",
"strings": true,
"include_empty_strings": "",
"include_null_values": "",
"x": 600,
"y": 180,
"wires": [
[
"369cbe42.4af9f2"
]
]
},
{
"id": "ed11f8d6.5e3c88",
"type": "template",
"z": "4b63452d.672afc",
"name": "CSV data",
"field": "payload",
"fieldType": "msg",
"format": "text",
"syntax": "mustache",
"template": "Apple,100,Canada\nOrange,120,USA\nBanana,80,Philippines",
"output": "str",
"x": 430,
"y": 180,
"wires": [
[
"a0288b44.71d488"
]
]
},
{
"id": "369cbe42.4af9f2",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 780,
"y": 180,
"wires": []
},
{
"id": "783cfaa6.52fbe4",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Parse CSV with default column name as messages",
"info": "CSV node can parse input CSV data.\nParsed CSV record can be sent as a message sequence.\nEach message payload points to an object with `col`*N* as a key and CSV value as a value.\n",
"x": 330,
"y": 120,
"wires": []
}
]

View File

@ -0,0 +1,99 @@
[
{
"id": "98c9d44d.4457b8",
"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": 260,
"y": 360,
"wires": [
[
"65476517.3d760c"
]
]
},
{
"id": "76df98f7.0dcd08",
"type": "csv",
"z": "4b63452d.672afc",
"name": "",
"sep": ",",
"hdrin": "",
"hdrout": "none",
"multi": "mult",
"ret": "\\n",
"temp": "",
"skip": "0",
"strings": true,
"include_empty_strings": "",
"include_null_values": "",
"x": 600,
"y": 360,
"wires": [
[
"557979e0.e6b588"
]
]
},
{
"id": "65476517.3d760c",
"type": "template",
"z": "4b63452d.672afc",
"name": "CSV data",
"field": "payload",
"fieldType": "msg",
"format": "text",
"syntax": "mustache",
"template": "Apple,100,Canada\nOrange,120,USA\nBanana,80,Philippines",
"output": "str",
"x": 430,
"y": 360,
"wires": [
[
"76df98f7.0dcd08"
]
]
},
{
"id": "557979e0.e6b588",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 780,
"y": 360,
"wires": []
},
{
"id": "187f4ab3.4c9ab5",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Parse CSV with default column name as array",
"info": "CSV node can send a single message with array of parsed CSV records.\nEach element of the array consists of objects with key-value pair.",
"x": 320,
"y": 300,
"wires": []
}
]

View File

@ -0,0 +1,99 @@
[
{
"id": "1216e95b.1b1e87",
"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": 260,
"y": 560,
"wires": [
[
"e41ffbbc.de2ed8"
]
]
},
{
"id": "286828bc.9233c8",
"type": "csv",
"z": "4b63452d.672afc",
"name": "",
"sep": ",",
"hdrin": "",
"hdrout": "none",
"multi": "one",
"ret": "\\n",
"temp": "kind,price,origin",
"skip": "0",
"strings": true,
"include_empty_strings": "",
"include_null_values": "",
"x": 600,
"y": 560,
"wires": [
[
"9d8218c.5550ee8"
]
]
},
{
"id": "e41ffbbc.de2ed8",
"type": "template",
"z": "4b63452d.672afc",
"name": "CSV data",
"field": "payload",
"fieldType": "msg",
"format": "text",
"syntax": "mustache",
"template": "Apple,100,Canada\nOrange,120,USA\nBanana,80,Philippines",
"output": "str",
"x": 430,
"y": 560,
"wires": [
[
"286828bc.9233c8"
]
]
},
{
"id": "9d8218c.5550ee8",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 780,
"y": 560,
"wires": []
},
{
"id": "aaa1ee8f.21e2c",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Parse CSV with specified column name as messages",
"info": "CSV node can specify column name of parsed objects in its settings panel.",
"x": 340,
"y": 500,
"wires": []
}
]

View File

@ -0,0 +1,99 @@
[
{
"id": "24093558.0315aa",
"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": 260,
"y": 740,
"wires": [
[
"80abaee1.5fa7f"
]
]
},
{
"id": "d4d2ca3f.1d9488",
"type": "csv",
"z": "4b63452d.672afc",
"name": "",
"sep": ",",
"hdrin": true,
"hdrout": "none",
"multi": "one",
"ret": "\\n",
"temp": "",
"skip": "0",
"strings": true,
"include_empty_strings": "",
"include_null_values": "",
"x": 600,
"y": 740,
"wires": [
[
"b52791c3.08967"
]
]
},
{
"id": "80abaee1.5fa7f",
"type": "template",
"z": "4b63452d.672afc",
"name": "CSV data",
"field": "payload",
"fieldType": "msg",
"format": "text",
"syntax": "mustache",
"template": "kind,price,origin\nApple,100,Canada\nOrange,120,USA\nBanana,80,Philippines",
"output": "str",
"x": 430,
"y": 740,
"wires": [
[
"d4d2ca3f.1d9488"
]
]
},
{
"id": "b52791c3.08967",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 780,
"y": 740,
"wires": []
},
{
"id": "85091361.85644",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Parse CSV with column name in first row as messages",
"info": "CSV node can use first row of input CSV text as a column name of each record object.\n",
"x": 340,
"y": 680,
"wires": []
}
]

View File

@ -0,0 +1,99 @@
[
{
"id": "9e93169c.b763a8",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Convert JavaScript object to CSV",
"info": "CSV node can convert a JavaScript object to CSV text.\nEach object contains key-value pair of specified properties.\n",
"x": 270,
"y": 860,
"wires": []
},
{
"id": "8ca41fee.3303d",
"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": 260,
"y": 920,
"wires": [
[
"c466905b.e8c61"
]
]
},
{
"id": "65146d20.d78204",
"type": "csv",
"z": "4b63452d.672afc",
"name": "",
"sep": ",",
"hdrin": false,
"hdrout": "none",
"multi": "one",
"ret": "\\n",
"temp": "kind,price",
"skip": "0",
"strings": true,
"include_empty_strings": "",
"include_null_values": "",
"x": 600,
"y": 920,
"wires": [
[
"92e99e67.a37d8"
]
]
},
{
"id": "c466905b.e8c61",
"type": "template",
"z": "4b63452d.672afc",
"name": "JS object",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "plain",
"template": "{\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n}",
"output": "json",
"x": 430,
"y": 920,
"wires": [
[
"65146d20.d78204"
]
]
},
{
"id": "92e99e67.a37d8",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 780,
"y": 920,
"wires": []
}
]

View File

@ -0,0 +1,99 @@
[
{
"id": "e89019c5.70ae78",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Convert array of JavaScript objects to CSV",
"info": "CSV node can convert an array of JavaScript objects to multi-line CSV text.",
"x": 300,
"y": 1020,
"wires": []
},
{
"id": "bd0d82ed.7b28",
"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": 260,
"y": 1080,
"wires": [
[
"1d857b8d.3a4014"
]
]
},
{
"id": "66a37667.16ebd8",
"type": "csv",
"z": "4b63452d.672afc",
"name": "",
"sep": ",",
"hdrin": false,
"hdrout": "none",
"multi": "one",
"ret": "\\n",
"temp": "kind,price",
"skip": "0",
"strings": true,
"include_empty_strings": "",
"include_null_values": "",
"x": 600,
"y": 1080,
"wires": [
[
"859725fd.dc93d8"
]
]
},
{
"id": "1d857b8d.3a4014",
"type": "template",
"z": "4b63452d.672afc",
"name": "JS object",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "plain",
"template": "[\n {\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n },\n {\n \"kind\": \"Orange\",\n \"price\": 120,\n \"origin\": \"USA\"\n },\n {\n \"kind\": \"Banana\",\n \"price\": 80,\n \"origin\": \"Philippines\"\n }\n]",
"output": "json",
"x": 430,
"y": 1080,
"wires": [
[
"66a37667.16ebd8"
]
]
},
{
"id": "859725fd.dc93d8",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 780,
"y": 1080,
"wires": []
}
]

View File

@ -0,0 +1,99 @@
[
{
"id": "2ebdd51e.c5d17a",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Convert array of JavaScript objects to CSV with column name header",
"info": "CSV node can convert an array of JavaScript objects to multi-line CSV text with column name header at first line.",
"x": 390,
"y": 1200,
"wires": []
},
{
"id": "2b4d538d.ada07c",
"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": 260,
"y": 1260,
"wires": [
[
"3e5c9e8.5065b62"
]
]
},
{
"id": "db02c7be.0984e8",
"type": "csv",
"z": "4b63452d.672afc",
"name": "",
"sep": ",",
"hdrin": false,
"hdrout": "all",
"multi": "one",
"ret": "\\n",
"temp": "kind,price",
"skip": "0",
"strings": true,
"include_empty_strings": "",
"include_null_values": "",
"x": 600,
"y": 1260,
"wires": [
[
"61f8b772.ddb1f8"
]
]
},
{
"id": "3e5c9e8.5065b62",
"type": "template",
"z": "4b63452d.672afc",
"name": "JS object",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "plain",
"template": "[\n {\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n },\n {\n \"kind\": \"Orange\",\n \"price\": 120,\n \"origin\": \"USA\"\n },\n {\n \"kind\": \"Banana\",\n \"price\": 80,\n \"origin\": \"Philippines\"\n }\n]",
"output": "json",
"x": 430,
"y": 1260,
"wires": [
[
"db02c7be.0984e8"
]
]
},
{
"id": "61f8b772.ddb1f8",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 780,
"y": 1260,
"wires": []
}
]

View File

@ -0,0 +1,99 @@
[
{
"id": "2ebdd51e.c5d17a",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Convert array of JavaScript objects to CSV with column name header",
"info": "CSV node can convert an array of JavaScript objects to multi-line CSV text with column name header at first line.",
"x": 390,
"y": 1200,
"wires": []
},
{
"id": "2b4d538d.ada07c",
"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": 260,
"y": 1260,
"wires": [
[
"3e5c9e8.5065b62"
]
]
},
{
"id": "db02c7be.0984e8",
"type": "csv",
"z": "4b63452d.672afc",
"name": "",
"sep": ",",
"hdrin": false,
"hdrout": "all",
"multi": "one",
"ret": "\\n",
"temp": "kind,price",
"skip": "0",
"strings": true,
"include_empty_strings": "",
"include_null_values": "",
"x": 600,
"y": 1260,
"wires": [
[
"61f8b772.ddb1f8"
]
]
},
{
"id": "3e5c9e8.5065b62",
"type": "template",
"z": "4b63452d.672afc",
"name": "JS object",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "plain",
"template": "[\n {\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n },\n {\n \"kind\": \"Orange\",\n \"price\": 120,\n \"origin\": \"USA\"\n },\n {\n \"kind\": \"Banana\",\n \"price\": 80,\n \"origin\": \"Philippines\"\n }\n]",
"output": "json",
"x": 430,
"y": 1260,
"wires": [
[
"db02c7be.0984e8"
]
]
},
{
"id": "61f8b772.ddb1f8",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 780,
"y": 1260,
"wires": []
}
]

View File

@ -0,0 +1,200 @@
[
{
"id": "1ae28939.9f5fc7",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Send column name when reset property set",
"info": "CSV node can send column names at first or `reset` property exists in input message.",
"x": 310,
"y": 1540,
"wires": []
},
{
"id": "c16ad95b.4f9ac8",
"type": "inject",
"z": "4b63452d.672afc",
"name": "Apple",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 250,
"y": 1600,
"wires": [
[
"7f7bfc72.aed104"
]
]
},
{
"id": "870620b9.95343",
"type": "csv",
"z": "4b63452d.672afc",
"name": "",
"sep": ",",
"hdrin": false,
"hdrout": "once",
"multi": "one",
"ret": "\\n",
"temp": "kind,price",
"skip": "0",
"strings": true,
"include_empty_strings": "",
"include_null_values": "",
"x": 650,
"y": 1720,
"wires": [
[
"d960de42.619c7"
]
]
},
{
"id": "7f7bfc72.aed104",
"type": "template",
"z": "4b63452d.672afc",
"name": "JS object",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "plain",
"template": "{\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n}",
"output": "json",
"x": 470,
"y": 1600,
"wires": [
[
"870620b9.95343"
]
]
},
{
"id": "d960de42.619c7",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 830,
"y": 1720,
"wires": []
},
{
"id": "6f8296e.f95ca68",
"type": "inject",
"z": "4b63452d.672afc",
"name": "Orange",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 250,
"y": 1660,
"wires": [
[
"c37d0dfa.ec1ab"
]
]
},
{
"id": "c37d0dfa.ec1ab",
"type": "template",
"z": "4b63452d.672afc",
"name": "JS object",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "plain",
"template": "{\n \"kind\": \"Orange\",\n \"price\": 120,\n \"origin\": \"USA\"\n}\n",
"output": "json",
"x": 470,
"y": 1660,
"wires": [
[
"870620b9.95343"
]
]
},
{
"id": "35209fe2.16926",
"type": "inject",
"z": "4b63452d.672afc",
"name": "Banana & reset",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
},
{
"p": "reset",
"v": "",
"vt": "date"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 280,
"y": 1720,
"wires": [
[
"afd4e6b3.624a28"
]
]
},
{
"id": "afd4e6b3.624a28",
"type": "template",
"z": "4b63452d.672afc",
"name": "JS object",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "plain",
"template": "{\n \"kind\": \"Banana\",\n \"price\": 80,\n \"origin\": \"Philippines\"\n}",
"output": "json",
"x": 470,
"y": 1720,
"wires": [
[
"870620b9.95343"
]
]
}
]

View File

@ -0,0 +1,150 @@
[
{
"id": "195c168c.44f149",
"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": 260,
"y": 1900,
"wires": [
[
"b270564c.171908"
]
]
},
{
"id": "8ec8cf9e.103fa",
"type": "csv",
"z": "4b63452d.672afc",
"name": "",
"sep": ",",
"hdrin": true,
"hdrout": "none",
"multi": "one",
"ret": "\\n",
"temp": "",
"skip": "0",
"strings": true,
"include_empty_strings": "",
"include_null_values": "",
"x": 600,
"y": 1900,
"wires": [
[
"5c5254a8.bc562c"
]
]
},
{
"id": "b270564c.171908",
"type": "template",
"z": "4b63452d.672afc",
"name": "CSV data",
"field": "payload",
"fieldType": "msg",
"format": "text",
"syntax": "mustache",
"template": "kind,price,origin\nApple,100,Canada\nOrange,120,USA\nBanana,80,Philippines",
"output": "str",
"x": 430,
"y": 1900,
"wires": [
[
"8ec8cf9e.103fa"
]
]
},
{
"id": "1c7be442.6a4bdc",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 1090,
"y": 1900,
"wires": []
},
{
"id": "d3da7cfb.cf596",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Join parsed CSV message sequence using join node",
"info": "Parset CSV message sequence can be joined by join node.",
"x": 330,
"y": 1840,
"wires": []
},
{
"id": "a07c9e26.c84fd",
"type": "csv",
"z": "4b63452d.672afc",
"name": "",
"sep": ",",
"hdrin": "",
"hdrout": "none",
"multi": "one",
"ret": "\\n",
"temp": "kind,price",
"skip": "0",
"strings": true,
"include_empty_strings": "",
"include_null_values": "",
"x": 910,
"y": 1900,
"wires": [
[
"1c7be442.6a4bdc"
]
]
},
{
"id": "5c5254a8.bc562c",
"type": "join",
"z": "4b63452d.672afc",
"name": "",
"mode": "auto",
"build": "string",
"property": "payload",
"propertyType": "msg",
"key": "topic",
"joiner": "\\n",
"joinerType": "str",
"accumulate": false,
"timeout": "",
"count": "",
"reduceRight": false,
"reduceExp": "",
"reduceInit": "",
"reduceInitType": "",
"reduceFixup": "",
"x": 750,
"y": 1900,
"wires": [
[
"a07c9e26.c84fd"
]
]
}
]

View File

@ -0,0 +1,94 @@
[
{
"id": "8c5224a6.201b88",
"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": 180,
"wires": [
[
"d6c67e51.0d709"
]
]
},
{
"id": "d6c67e51.0d709",
"type": "template",
"z": "4b63452d.672afc",
"name": "HTML text",
"field": "payload",
"fieldType": "msg",
"format": "handlebars",
"syntax": "plain",
"template": "<html>\n <head>\n <title>List of Fruits</title>\n </head>\n <body>\n <ul>\n <li class=\"Item\">Apple</li>\n <li class=\"Item\">Orange</li>\n <li class=\"Item\">Banana</li>\n </ul>\n </body>\n</html>\n",
"output": "str",
"x": 390,
"y": 180,
"wires": [
[
"599a1155.61a5c"
]
]
},
{
"id": "b0d5cd89.338df",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Extract array of HTML element by CSS selector",
"info": "HTML node can be used to extract elements in HTML document as an array using CSS selector.",
"x": 280,
"y": 120,
"wires": []
},
{
"id": "599a1155.61a5c",
"type": "html",
"z": "4b63452d.672afc",
"name": "",
"property": "payload",
"outproperty": "payload",
"tag": ".Item",
"ret": "html",
"as": "single",
"x": 550,
"y": 180,
"wires": [
[
"942b23d1.cce09"
]
]
},
{
"id": "942b23d1.cce09",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 710,
"y": 180,
"wires": []
}
]

View File

@ -0,0 +1,94 @@
[
{
"id": "a44973e8.6319b",
"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": 360,
"wires": [
[
"de1b012e.96ec3"
]
]
},
{
"id": "de1b012e.96ec3",
"type": "template",
"z": "4b63452d.672afc",
"name": "HTML text",
"field": "payload",
"fieldType": "msg",
"format": "handlebars",
"syntax": "plain",
"template": "<html>\n <head>\n <title>List of Fruits</title>\n </head>\n <body>\n <ul>\n <li class=\"Item\">Apple</li>\n <li class=\"Item\">Orange</li>\n <li class=\"Item\">Banana</li>\n </ul>\n </body>\n</html>\n",
"output": "str",
"x": 390,
"y": 360,
"wires": [
[
"cee70712.6f3538"
]
]
},
{
"id": "99e32bc7.c8e508",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Extract sequence of HTML element by CSS selector",
"info": "HTML node can be used to extract elements in HTML document as a messege sequence using CSS selector.",
"x": 290,
"y": 300,
"wires": []
},
{
"id": "cee70712.6f3538",
"type": "html",
"z": "4b63452d.672afc",
"name": "",
"property": "payload",
"outproperty": "payload",
"tag": ".Item",
"ret": "html",
"as": "multi",
"x": 550,
"y": 360,
"wires": [
[
"17f25482.d4b56b"
]
]
},
{
"id": "17f25482.d4b56b",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 710,
"y": 360,
"wires": []
}
]

View File

@ -0,0 +1,121 @@
[
{
"id": "653ce9aa.b6a1c8",
"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": 560,
"wires": [
[
"52a16f7f.447d8"
]
]
},
{
"id": "52a16f7f.447d8",
"type": "template",
"z": "4b63452d.672afc",
"name": "HTML text",
"field": "payload",
"fieldType": "msg",
"format": "handlebars",
"syntax": "plain",
"template": "<html>\n <head>\n <title>List of Fruits</title>\n </head>\n <body>\n <ul>\n <li class=\"Item\">Apple</li>\n <li class=\"Item\">Orange</li>\n <li class=\"Item\">Banana</li>\n </ul>\n </body>\n</html>\n",
"output": "str",
"x": 390,
"y": 560,
"wires": [
[
"a52319c3.89b008"
]
]
},
{
"id": "8bc35379.31d99",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Extract array of HTML element by CSS selector specified in message",
"info": "CSS selector for HTML node can be specified by `select` property of input message.",
"x": 350,
"y": 500,
"wires": []
},
{
"id": "9c49de8a.bad25",
"type": "html",
"z": "4b63452d.672afc",
"name": "",
"property": "payload",
"outproperty": "payload",
"tag": "",
"ret": "html",
"as": "single",
"x": 730,
"y": 560,
"wires": [
[
"d4f4b987.278a68"
]
]
},
{
"id": "d4f4b987.278a68",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 890,
"y": 560,
"wires": []
},
{
"id": "a52319c3.89b008",
"type": "change",
"z": "4b63452d.672afc",
"name": "",
"rules": [
{
"t": "set",
"p": "select",
"pt": "msg",
"to": ".Item",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 560,
"y": 560,
"wires": [
[
"9c49de8a.bad25"
]
]
}
]

View File

@ -0,0 +1,122 @@
[
{
"id": "66cff4ee.f2761c",
"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": 760,
"wires": [
[
"2baaf6bf.0a02ca"
]
]
},
{
"id": "2baaf6bf.0a02ca",
"type": "template",
"z": "4b63452d.672afc",
"name": "HTML text",
"field": "payload",
"fieldType": "msg",
"format": "handlebars",
"syntax": "plain",
"template": "<html>\n <head>\n <title>List of Fruits</title>\n </head>\n <body>\n <ul>\n <li class=\"Item\">Apple</li>\n <li class=\"Item\">Orange</li>\n <li class=\"Item\">Banana</li>\n </ul>\n </body>\n</html>\n",
"output": "str",
"x": 390,
"y": 760,
"wires": [
[
"bbb22e6b.0fa25"
]
]
},
{
"id": "a57d35d0.8aa538",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Join extracted sequence of HTML element using join node",
"info": "Message sequence extracted by HTML node can be combined using join node.",
"x": 310,
"y": 700,
"wires": []
},
{
"id": "bbb22e6b.0fa25",
"type": "html",
"z": "4b63452d.672afc",
"name": "",
"property": "payload",
"outproperty": "payload",
"tag": ".Item",
"ret": "html",
"as": "multi",
"x": 550,
"y": 760,
"wires": [
[
"bd01ca4.966ad38"
]
]
},
{
"id": "4d2616a8.84de88",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 850,
"y": 760,
"wires": []
},
{
"id": "bd01ca4.966ad38",
"type": "join",
"z": "4b63452d.672afc",
"name": "",
"mode": "custom",
"build": "string",
"property": "payload",
"propertyType": "msg",
"key": "topic",
"joiner": ",",
"joinerType": "str",
"accumulate": false,
"timeout": "",
"count": "",
"reduceRight": false,
"reduceExp": "",
"reduceInit": "",
"reduceInitType": "",
"reduceFixup": "",
"x": 690,
"y": 760,
"wires": [
[
"4d2616a8.84de88"
]
]
}
]

View File

@ -0,0 +1,92 @@
[
{
"id": "9976e95d.2f8398",
"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": 240,
"y": 180,
"wires": [
[
"d94fc083.49d87"
]
]
},
{
"id": "6684abb1.8eb454",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Convert JSON string to JS object",
"info": "JSON node can convert JSON string to JavaScript object.",
"x": 250,
"y": 120,
"wires": []
},
{
"id": "d94fc083.49d87",
"type": "template",
"z": "4b63452d.672afc",
"name": "JSON string",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "plain",
"template": "{\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n}",
"output": "str",
"x": 410,
"y": 180,
"wires": [
[
"1a3dc54a.78598b"
]
]
},
{
"id": "8950a55d.023988",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 730,
"y": 180,
"wires": []
},
{
"id": "1a3dc54a.78598b",
"type": "json",
"z": "4b63452d.672afc",
"name": "",
"property": "payload",
"action": "",
"pretty": false,
"x": 570,
"y": 180,
"wires": [
[
"8950a55d.023988"
]
]
}
]

View File

@ -0,0 +1,92 @@
[
{
"id": "cb13761f.56c328",
"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": 240,
"y": 380,
"wires": [
[
"c607642a.78c3c8"
]
]
},
{
"id": "180b1e22.0074e2",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Convert JS object to JSON string",
"info": "JSON node can convert JavaScript object to JSON string.",
"x": 250,
"y": 320,
"wires": []
},
{
"id": "c607642a.78c3c8",
"type": "template",
"z": "4b63452d.672afc",
"name": "JS object",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "plain",
"template": "{\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n}",
"output": "json",
"x": 400,
"y": 380,
"wires": [
[
"bf309844.fa12e8"
]
]
},
{
"id": "5b6b130b.72a14c",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 730,
"y": 380,
"wires": []
},
{
"id": "bf309844.fa12e8",
"type": "json",
"z": "4b63452d.672afc",
"name": "",
"property": "payload",
"action": "",
"pretty": false,
"x": 570,
"y": 380,
"wires": [
[
"5b6b130b.72a14c"
]
]
}
]

View File

@ -0,0 +1,160 @@
[
{
"id": "2b18621b.e2670e",
"type": "inject",
"z": "4b63452d.672afc",
"name": "OK",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 230,
"y": 580,
"wires": [
[
"5986faee.aef954"
]
]
},
{
"id": "59acf99.9a92308",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Validate input JSON string",
"info": "JSON node can validate input JSON string using [JSON schema](https://json-schema.org/) when converting to JavaScript object.",
"x": 230,
"y": 520,
"wires": []
},
{
"id": "5986faee.aef954",
"type": "template",
"z": "4b63452d.672afc",
"name": "JSON string",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "plain",
"template": "{\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n}",
"output": "str",
"x": 410,
"y": 580,
"wires": [
[
"f8a67c6d.4f1f1"
]
]
},
{
"id": "ca27c92c.ad7cb8",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 910,
"y": 580,
"wires": []
},
{
"id": "2fad9978.ea1916",
"type": "json",
"z": "4b63452d.672afc",
"name": "",
"property": "payload",
"action": "",
"pretty": false,
"x": 750,
"y": 580,
"wires": [
[
"ca27c92c.ad7cb8"
]
]
},
{
"id": "f8a67c6d.4f1f1",
"type": "template",
"z": "4b63452d.672afc",
"name": "Schema",
"field": "schema",
"fieldType": "msg",
"format": "json",
"syntax": "plain",
"template": "{\n \"type\": \"object\",\n \"properties\": {\n \"kind\": {\n \"type\": \"string\"\n },\n \"price\": {\n \"type\": \"number\"\n },\n \"origin\": {\n \"type\": \"string\"\n }\n }\n}",
"output": "json",
"x": 590,
"y": 580,
"wires": [
[
"2fad9978.ea1916"
]
]
},
{
"id": "8337e847.ac18d8",
"type": "inject",
"z": "4b63452d.672afc",
"name": "NG",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 230,
"y": 660,
"wires": [
[
"fa14d8bf.1ac938"
]
]
},
{
"id": "fa14d8bf.1ac938",
"type": "template",
"z": "4b63452d.672afc",
"name": "JSON string",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "plain",
"template": "{\n \"kind\": \"Apple\",\n \"price\": \"100\",\n \"origin\": \"Canada\"\n}",
"output": "str",
"x": 410,
"y": 660,
"wires": [
[
"f8a67c6d.4f1f1"
]
]
}
]

View File

@ -0,0 +1,92 @@
[
{
"id": "82f1bd0b.43474",
"type": "xml",
"z": "4b63452d.672afc",
"name": "",
"property": "payload",
"attr": "",
"chr": "",
"x": 530,
"y": 180,
"wires": [
[
"1cd4ad02.9a5423"
]
]
},
{
"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": "",
"payloadType": "date",
"x": 200,
"y": 180,
"wires": [
[
"cdd1c154.3a655"
]
]
},
{
"id": "7b014430.dfd94c",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Convert JavaScript object to XML",
"info": "XML node can convert JavaScript object to XML string.",
"x": 240,
"y": 120,
"wires": []
},
{
"id": "1cd4ad02.9a5423",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 690,
"y": 180,
"wires": []
},
{
"id": "cdd1c154.3a655",
"type": "template",
"z": "4b63452d.672afc",
"name": "JS object",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "plain",
"template": "{\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n}",
"output": "json",
"x": 360,
"y": 180,
"wires": [
[
"82f1bd0b.43474"
]
]
}
]

View File

@ -0,0 +1,92 @@
[
{
"id": "93e423a9.a407d",
"type": "xml",
"z": "4b63452d.672afc",
"name": "",
"property": "payload",
"attr": "",
"chr": "",
"x": 530,
"y": 360,
"wires": [
[
"2d0dde7e.a50082"
]
]
},
{
"id": "ba1dab90.8d1da8",
"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": 200,
"y": 360,
"wires": [
[
"16617f26.14ced1"
]
]
},
{
"id": "a9f97b00.57d658",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Convert XML to JavaScript object",
"info": "XML node can convert XML string to JavaScript object.",
"x": 240,
"y": 300,
"wires": []
},
{
"id": "2d0dde7e.a50082",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 690,
"y": 360,
"wires": []
},
{
"id": "16617f26.14ced1",
"type": "template",
"z": "4b63452d.672afc",
"name": "XML string",
"field": "payload",
"fieldType": "msg",
"format": "html",
"syntax": "plain",
"template": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<fruit id=\"100\">\n <kind>Apple</kind>\n <price>100</price>\n <origin>Canada</origin>\n</fruit>",
"output": "str",
"x": 370,
"y": 360,
"wires": [
[
"93e423a9.a407d"
]
]
}
]

View File

@ -0,0 +1,119 @@
[
{
"id": "581bd648.636628",
"type": "xml",
"z": "4b63452d.672afc",
"name": "",
"property": "payload",
"attr": "",
"chr": "",
"x": 710,
"y": 540,
"wires": [
[
"b74237dc.1e5028"
]
]
},
{
"id": "d0899f9b.f1ac6",
"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": 200,
"y": 540,
"wires": [
[
"f04ffb9a.68edb8"
]
]
},
{
"id": "8a214c05.dc61f",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Control conversion using options property",
"info": "XML node can control conversion by setting `options` property (defined by [xml2js](https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md#options)) in input message.",
"x": 260,
"y": 480,
"wires": []
},
{
"id": "b74237dc.1e5028",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 870,
"y": 540,
"wires": []
},
{
"id": "f04ffb9a.68edb8",
"type": "template",
"z": "4b63452d.672afc",
"name": "XML string",
"field": "payload",
"fieldType": "msg",
"format": "html",
"syntax": "plain",
"template": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<fruit id=\"100\">\n <kind>Apple</kind>\n <price>100</price>\n <origin>Canada</origin>\n</fruit>",
"output": "str",
"x": 370,
"y": 540,
"wires": [
[
"fedf79.5889c088"
]
]
},
{
"id": "fedf79.5889c088",
"type": "change",
"z": "4b63452d.672afc",
"name": "set options",
"rules": [
{
"t": "set",
"p": "options",
"pt": "msg",
"to": "{\"explicitArray\":false}",
"tot": "json"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 550,
"y": 540,
"wires": [
[
"581bd648.636628"
]
]
}
]

View File

@ -0,0 +1,90 @@
[
{
"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": "",
"payloadType": "date",
"x": 200,
"y": 180,
"wires": [
[
"cdd1c154.3a655"
]
]
},
{
"id": "7b014430.dfd94c",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Convert JavaScript object to YAML",
"info": "YAML node can convert JavaScript object to YAML string.",
"x": 240,
"y": 120,
"wires": []
},
{
"id": "1cd4ad02.9a5423",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 670,
"y": 180,
"wires": []
},
{
"id": "cdd1c154.3a655",
"type": "template",
"z": "4b63452d.672afc",
"name": "JS object",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "plain",
"template": "{\n \"fruits\" : {\n \"kind\": \"Apple\",\n \"price\": 100,\n \"origin\": \"Canada\"\n }\n}",
"output": "json",
"x": 360,
"y": 180,
"wires": [
[
"aaf0100b.16628"
]
]
},
{
"id": "aaf0100b.16628",
"type": "yaml",
"z": "4b63452d.672afc",
"property": "payload",
"name": "",
"x": 510,
"y": 180,
"wires": [
[
"1cd4ad02.9a5423"
]
]
}
]

View File

@ -0,0 +1,90 @@
[
{
"id": "ba1dab90.8d1da8",
"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": 200,
"y": 360,
"wires": [
[
"16617f26.14ced1"
]
]
},
{
"id": "a9f97b00.57d658",
"type": "comment",
"z": "4b63452d.672afc",
"name": "Convert YAML to JavaScript object",
"info": "YAML node can convert YAML string to JavaScript object.",
"x": 240,
"y": 300,
"wires": []
},
{
"id": "2d0dde7e.a50082",
"type": "debug",
"z": "4b63452d.672afc",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 690,
"y": 360,
"wires": []
},
{
"id": "16617f26.14ced1",
"type": "template",
"z": "4b63452d.672afc",
"name": "YAML string",
"field": "payload",
"fieldType": "msg",
"format": "yaml",
"syntax": "plain",
"template": "fruits:\n kind: Apple\n price: 100\n origin: Canada",
"output": "str",
"x": 370,
"y": 360,
"wires": [
[
"e2e4f862.f9d7d8"
]
]
},
{
"id": "e2e4f862.f9d7d8",
"type": "yaml",
"z": "4b63452d.672afc",
"property": "payload",
"name": "",
"x": 530,
"y": 360,
"wires": [
[
"2d0dde7e.a50082"
]
]
}
]

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": []
}
]

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