mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Merge pull request #3383 from node-red/dev
Merge dev to master for 2.2.0
This commit is contained in:
commit
3a69af9034
2802
CHANGELOG.md
2802
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@ -38,12 +38,9 @@ If you want to raise a pull-request with a new feature, or a refactoring
|
||||
of existing code, it may well get rejected if you haven't discussed it on
|
||||
the [forum](https://discourse.nodered.org) first.
|
||||
|
||||
All contributors need to sign the JS Foundation's Contributor License Agreement.
|
||||
It is an online process and quick to do. You can read the details of the agreement
|
||||
here: https://cla.js.foundation/node-red/node-red.
|
||||
|
||||
If you raise a pull-request without having signed the CLA, you will be prompted
|
||||
to do so automatically.
|
||||
All contributors need to sign the OpenJS Foundation's Contributor License Agreement.
|
||||
It is an online process and quick to do. If you raise a pull-request without
|
||||
having signed the CLA, you will be prompted to do so automatically.
|
||||
|
||||
|
||||
### Code Branches
|
||||
|
10
README.md
10
README.md
@ -56,13 +56,13 @@ This project adheres to the [Contributor Covenant 1.4](http://contributor-covena
|
||||
|
||||
## Authors
|
||||
|
||||
Node-RED is a project of the [OpenJS Foundation](https://openjsf.org).
|
||||
Node-RED is a project of the [OpenJS Foundation](http://openjsf.org).
|
||||
|
||||
It was created by [IBM Emerging Technology](https://www.ibm.com/blogs/emerging-technology/).
|
||||
|
||||
* Nick O'Leary [@knolleary](http://twitter.com/knolleary)
|
||||
* Dave Conway-Jones [@ceejay](http://twitter.com/ceejay)
|
||||
It is maintained by:
|
||||
|
||||
* Nick O'Leary [@knolleary](http://twitter.com/knolleary)
|
||||
* Dave Conway-Jones [@ceejay](http://twitter.com/ceejay)
|
||||
* And many others...
|
||||
|
||||
|
||||
## Copyright and license
|
||||
|
22
package.json
22
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-red",
|
||||
"version": "2.1.6",
|
||||
"version": "2.2.0",
|
||||
"description": "Low-code programming for event-driven applications",
|
||||
"homepage": "http://nodered.org",
|
||||
"license": "Apache-2.0",
|
||||
@ -28,7 +28,7 @@
|
||||
"dependencies": {
|
||||
"acorn": "8.7.0",
|
||||
"acorn-walk": "8.2.0",
|
||||
"ajv": "8.8.2",
|
||||
"ajv": "8.9.0",
|
||||
"async-mutex": "0.3.2",
|
||||
"basic-auth": "2.0.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
@ -50,7 +50,7 @@
|
||||
"hash-sum": "2.0.0",
|
||||
"hpagent": "0.1.2",
|
||||
"https-proxy-agent": "5.0.0",
|
||||
"i18next": "21.6.6",
|
||||
"i18next": "21.6.10",
|
||||
"iconv-lite": "0.6.3",
|
||||
"is-utf8": "0.2.1",
|
||||
"js-yaml": "3.14.1",
|
||||
@ -59,10 +59,10 @@
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"media-typer": "1.1.0",
|
||||
"memorystore": "1.6.6",
|
||||
"mime": "2.5.2",
|
||||
"mime": "3.0.0",
|
||||
"moment-timezone": "0.5.34",
|
||||
"mqtt": "4.3.4",
|
||||
"multer": "1.4.3",
|
||||
"multer": "1.4.4",
|
||||
"mustache": "4.2.0",
|
||||
"node-red-admin": "^2.2.1",
|
||||
"nopt": "5.0.0",
|
||||
@ -75,9 +75,9 @@
|
||||
"semver": "7.3.5",
|
||||
"tar": "6.1.11",
|
||||
"tough-cookie": "4.0.0",
|
||||
"uglify-js": "3.14.5",
|
||||
"uglify-js": "3.15.0",
|
||||
"uuid": "8.3.2",
|
||||
"ws": "7.5.1",
|
||||
"ws": "7.5.6",
|
||||
"xml2js": "0.4.23"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
@ -104,16 +104,16 @@
|
||||
"grunt-sass": "~3.1.0",
|
||||
"grunt-simple-mocha": "~0.4.1",
|
||||
"grunt-simple-nyc": "^3.0.1",
|
||||
"i18next-http-backend": "1.3.1",
|
||||
"i18next-http-backend": "1.3.2",
|
||||
"jquery-i18next": "1.2.1",
|
||||
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
|
||||
"marked": "4.0.10",
|
||||
"marked": "4.0.12",
|
||||
"minami": "1.2.3",
|
||||
"mocha": "9.1.3",
|
||||
"mocha": "9.2.0",
|
||||
"node-red-node-test-helper": "^0.2.7",
|
||||
"nodemon": "2.0.15",
|
||||
"proxy": "^1.0.2",
|
||||
"sass": "1.48.0",
|
||||
"sass": "1.49.0",
|
||||
"should": "13.2.3",
|
||||
"sinon": "11.1.2",
|
||||
"stoppable": "^1.1.0",
|
||||
|
@ -1,4 +1,4 @@
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
|
@ -122,6 +122,7 @@ module.exports = {
|
||||
}
|
||||
|
||||
if (req.body.active) {
|
||||
opts.clearContext = req.body.hasOwnProperty('clearContext')?req.body.clearContext:true
|
||||
runtimeAPI.projects.setActiveProject(opts).then(function() {
|
||||
listProjects(req,res);
|
||||
}).catch(function(err) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/editor-api",
|
||||
"version": "2.1.6",
|
||||
"version": "2.2.0",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
@ -16,8 +16,8 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/util": "2.1.6",
|
||||
"@node-red/editor-client": "2.1.6",
|
||||
"@node-red/util": "2.2.0",
|
||||
"@node-red/editor-client": "2.2.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.19.1",
|
||||
"clone": "2.1.2",
|
||||
@ -25,14 +25,14 @@
|
||||
"express-session": "1.17.2",
|
||||
"express": "4.17.2",
|
||||
"memorystore": "1.6.6",
|
||||
"mime": "2.5.2",
|
||||
"multer": "1.4.3",
|
||||
"mime": "3.0.0",
|
||||
"multer": "1.4.4",
|
||||
"mustache": "4.2.0",
|
||||
"oauth2orize": "1.11.1",
|
||||
"passport-http-bearer": "1.0.1",
|
||||
"passport-oauth2-client-password": "0.1.2",
|
||||
"passport": "0.5.2",
|
||||
"ws": "7.5.1"
|
||||
"ws": "7.5.6"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bcrypt": "5.0.1"
|
||||
|
@ -1,4 +1,4 @@
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
|
@ -75,6 +75,8 @@
|
||||
"view": {
|
||||
"view": "View",
|
||||
"grid": "Grid",
|
||||
"storeZoom": "Restore zoom level on load",
|
||||
"storePosition": "Restore scroll position on load",
|
||||
"showGrid": "Show grid",
|
||||
"snapGrid": "Snap to grid",
|
||||
"gridSize": "Grid size",
|
||||
@ -894,6 +896,8 @@
|
||||
"addTitle": "add an item"
|
||||
},
|
||||
"search": {
|
||||
"history": "Search history",
|
||||
"clear": "clear all",
|
||||
"empty": "No matches found",
|
||||
"addNode": "add a node..."
|
||||
},
|
||||
@ -1091,7 +1095,8 @@
|
||||
"not-git": "Not a git repository",
|
||||
"no-resource": "Repository not found",
|
||||
"cant-get-ssh-key-path": "Error! Can't get selected SSH key path.",
|
||||
"unexpected_error": "unexpected_error"
|
||||
"unexpected_error": "unexpected_error",
|
||||
"clearContext": "Clear context when switching projects"
|
||||
},
|
||||
"delete": {
|
||||
"confirm": "Are you sure you want to delete this project?"
|
||||
|
@ -75,6 +75,8 @@
|
||||
"view": {
|
||||
"view": "表示",
|
||||
"grid": "グリッド",
|
||||
"storeZoom": "読み込み時に拡大/縮小のレベルを復元",
|
||||
"storePosition": "読み込み時にスクロール位置を復元",
|
||||
"showGrid": "グリッドを表示",
|
||||
"snapGrid": "ノードの配置を補助",
|
||||
"gridSize": "グリッドの大きさ",
|
||||
@ -894,6 +896,8 @@
|
||||
"addTitle": "要素を追加"
|
||||
},
|
||||
"search": {
|
||||
"history": "検索履歴",
|
||||
"clear": "全て削除",
|
||||
"empty": "一致したものが見つかりませんでした",
|
||||
"addNode": "ノードを追加..."
|
||||
},
|
||||
@ -1091,7 +1095,8 @@
|
||||
"not-git": "Gitリポジトリではありません",
|
||||
"no-resource": "リポジトリが見つかりません",
|
||||
"cant-get-ssh-key-path": "エラー! 選択したSSHキーのパスを取得できません。",
|
||||
"unexpected_error": "予期しないエラー"
|
||||
"unexpected_error": "予期しないエラー",
|
||||
"clearContext": "プロジェクトを切り替る際にコンテキストを初期化"
|
||||
},
|
||||
"delete": {
|
||||
"confirm": "プロジェクトを削除しても良いですか?"
|
||||
@ -1151,5 +1156,137 @@
|
||||
"ru": "ロシア語",
|
||||
"zh-CN": "中国語(簡体)",
|
||||
"zh-TW": "中国語(繁体)"
|
||||
},
|
||||
"action-list": {
|
||||
"toggle-show-tips": "ヒント表示切替",
|
||||
"show-about": "Node-REDの説明を表示",
|
||||
"show-welcome-tour": "ウェルカムツアー表示",
|
||||
"show-next-tab": "次のタブを表示",
|
||||
"show-previous-tab": "前のタブを表示",
|
||||
"add-flow": "フローを追加",
|
||||
"add-flow-to-right": "フローを右に追加",
|
||||
"edit-flow": "フローを編集",
|
||||
"remove-flow": "フローを削除",
|
||||
"enable-flow": "フローを有効化",
|
||||
"disable-flow": "フローを無効化",
|
||||
"hide-flow": "フローを隠す",
|
||||
"hide-other-flows": "他のフローを非表示",
|
||||
"hide-all-flows": "全てのフローを非表示",
|
||||
"show-all-flows": "全てのフローを表示",
|
||||
"show-last-hidden-flow": "最後に非表示にしたフローを表示",
|
||||
"list-hidden-flows": "非表示フローを表示",
|
||||
"list-flows": "フロー一覧",
|
||||
"list-subflows": "サブフロー一覧",
|
||||
"go-to-previous-location": "前の位置に移動",
|
||||
"go-to-next-location": "次の位置に移動",
|
||||
"copy-selection-to-internal-clipboard": "選択をクリップボードにコピー",
|
||||
"cut-selection-to-internal-clipboard": "選択をクリップボードに切り取り",
|
||||
"paste-from-internal-clipboard": "クリップボードから貼り付け",
|
||||
"detach-selected-nodes": "選択ノードを接続から外す",
|
||||
"delete-selection": "選択を削除",
|
||||
"delete-selection-and-reconnect": "選択を削除し再接続",
|
||||
"edit-selected-node": "選択したノードを編集",
|
||||
"go-to-selection": "選択に移動",
|
||||
"undo": "変更操作を戻す",
|
||||
"redo": "変更操作をやり直し",
|
||||
"select-all-nodes": "全てのノードを選択",
|
||||
"select-none": "ノードを選択",
|
||||
"enable-selected-nodes": "選択ノードを有効化",
|
||||
"disable-selected-nodes": "選択ノードを無効化",
|
||||
"toggle-show-grid": "グリッド表示切替",
|
||||
"toggle-snap-grid": "ノードの配置補助切替",
|
||||
"toggle-status": "ステータス表示切替",
|
||||
"show-selected-node-labels": "選択したノードのラベルを表示",
|
||||
"hide-selected-node-labels": "選択したノードのラベルを非表示",
|
||||
"scroll-view-up": "上スクロール",
|
||||
"scroll-view-right": "右スクロール",
|
||||
"scroll-view-down": "下スクロール",
|
||||
"scroll-view-left": "左スクロール",
|
||||
"step-view-up": "一単位上スクロール",
|
||||
"step-view-right": "一単位右スクロール",
|
||||
"step-view-down": "一単位下スクロール",
|
||||
"step-view-left": "一単位左スクロール",
|
||||
"move-selection-up": "選択を上移動",
|
||||
"move-selection-right": "選択を右移動",
|
||||
"move-selection-down": "選択を下移動",
|
||||
"move-selection-left": "選択を左移動",
|
||||
"move-selection-forwards": "選択を前面に移動",
|
||||
"move-selection-backwards": "選択を背面に移動",
|
||||
"move-selection-to-front": "選択を最前面に移動",
|
||||
"move-selection-to-back": "選択を最背面に移動",
|
||||
"step-selection-up": "選択を一単位上移動",
|
||||
"step-selection-right": "選択を一単位右移動",
|
||||
"step-selection-down": "選択を一単位下移動",
|
||||
"step-selection-left": "選択を一単位左移動",
|
||||
"select-connected-nodes": "接続されたノードを選択",
|
||||
"select-downstream-nodes": "後方に接続されたノードを選択",
|
||||
"select-upstream-nodes": "前方に接続されたノードを選択",
|
||||
"go-to-next-node": "次のノードに移動",
|
||||
"go-to-previous-node": "前のノードに移動",
|
||||
"go-to-next-sibling": "次の兄弟ノードに移動",
|
||||
"go-to-previous-sibling": "前の兄弟ノードに移動",
|
||||
"go-to-nearest-node-on-left": "最も近い左側ノードに移動",
|
||||
"go-to-nearest-node-on-right": "最も近い右側ノードに移動",
|
||||
"go-to-nearest-node-above": "最も近い上側ノードに移動",
|
||||
"go-to-nearest-node-below": "最も近い下側ノードに移動",
|
||||
"align-selection-to-grid": "選択を整列",
|
||||
"align-selection-to-left": "選択を左揃え",
|
||||
"align-selection-to-right": "選択を右揃え",
|
||||
"align-selection-to-top": "選択を上揃え",
|
||||
"align-selection-to-bottom": "選択を下揃え",
|
||||
"align-selection-to-middle": "選択を上下中央揃え",
|
||||
"align-selection-to-center": "選択を左右中央揃え",
|
||||
"distribute-selection-horizontally": "選択を左右に整列",
|
||||
"distribute-selection-vertically": "選択を上下に整列",
|
||||
"wire-series-of-nodes": "ノードを一続きに接続",
|
||||
"wire-node-to-multiple": "ノードを複数に接続",
|
||||
"show-user-settings": "ユーザ設定を表示",
|
||||
"show-help": "ヘルプを表示",
|
||||
"toggle-palette": "パレットの表示切替",
|
||||
"show-event-log": "イベントログを表示",
|
||||
"manage-palette": "パレットの管理",
|
||||
"toggle-sidebar": "サイドバーの表示切替",
|
||||
"show-info-tab": "ノード情報タブの表示",
|
||||
"show-help-tab": "ノードヘルプタブの表示",
|
||||
"show-config-tab": "設定ノードタブの表示",
|
||||
"select-all-config-nodes": "全ての設定ノードを選択",
|
||||
"delete-config-selection": "選択した設定ノードを削除",
|
||||
"show-context-tab": "コンテキストデータタブを表示",
|
||||
"create-subflow": "サブフローを作成",
|
||||
"convert-to-subflow": "選択をサブフローに変換",
|
||||
"group-selection": "選択をグループ化",
|
||||
"ungroup-selection": "選択をグループ解除",
|
||||
"merge-selection-to-group": "選択をグループにマージ",
|
||||
"remove-selection-from-group": "選択をグループから削除",
|
||||
"copy-group-style": "グループのスタイルをコピー",
|
||||
"paste-group-style": "グループのスタイルを貼り付け",
|
||||
"show-export-dialog": "書き出しダイアログを表示",
|
||||
"show-import-dialog": "読み込みダイアログを表示",
|
||||
"show-library-export-dialog": "ライブラリ書き出しダイアログを表示",
|
||||
"show-library-import-dialog": "ライブラリ読み込みダイアログを表示",
|
||||
"show-examples-import-dialog": "サンプル読み込みダイアログを表示",
|
||||
"search": "検索",
|
||||
"show-action-list": "アクション一覧を表示",
|
||||
"confirm-edit-tray": "編集を完了",
|
||||
"cancel-edit-tray": "編集をキャンセル",
|
||||
"show-remote-diff": "リモートとの変更差分を表示",
|
||||
"deploy-flows": "フローをデプロイ",
|
||||
"restart-flows": "フローを再起動",
|
||||
"set-deploy-type-to-full": "デプロイを「全て」に設定",
|
||||
"set-deploy-type-to-modified-flows": "デプロイを「変更したフロー」に設定",
|
||||
"set-deploy-type-to-modified-nodes": "デプロイを「変更したノード」に設定",
|
||||
"show-debug-tab": "デバッグタブを表示",
|
||||
"clear-debug-messages": "デバッグメッセージをクリア",
|
||||
"clear-filtered-debug-messages": "フィルタしたデバッグメッセージをクリア",
|
||||
"activate-selected-debug-nodes": "選択したデバッグノードを有効化",
|
||||
"activate-all-debug-nodes": "全てのデバッグノードを有効化",
|
||||
"activate-all-flow-debug-nodes": "フロー内の全デバッグノードを有効化",
|
||||
"deactivate-selected-debug-nodes": "選択したデバッグノードを無効化",
|
||||
"deactivate-all-debug-nodes": "全てのデバッグノードを無効化",
|
||||
"deactivate-all-flow-debug-nodes": "フロー内の全デバッグノードを無効化",
|
||||
"zoom-in": "ズームイン",
|
||||
"zoom-out": "ズームアウト",
|
||||
"zoom-reset": "ズームリセット",
|
||||
"toggle-navigator": "ナビゲータ表示切替"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/editor-client",
|
||||
"version": "2.1.6",
|
||||
"version": "2.2.0",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -684,6 +684,13 @@ RED.history = (function() {
|
||||
peek: function() {
|
||||
return undoHistory[undoHistory.length-1];
|
||||
},
|
||||
replace: function(ev) {
|
||||
if (undoHistory.length === 0) {
|
||||
RED.history.push(ev);
|
||||
} else {
|
||||
undoHistory[undoHistory.length-1] = ev;
|
||||
}
|
||||
},
|
||||
clear: function() {
|
||||
undoHistory = [];
|
||||
redoHistory = [];
|
||||
|
@ -38,7 +38,9 @@
|
||||
},
|
||||
"red-ui-workspace": {
|
||||
"backspace": "core:delete-selection",
|
||||
"ctrl-backspace": "core:delete-selection-and-reconnect",
|
||||
"delete": "core:delete-selection",
|
||||
"ctrl-delete": "core:delete-selection-and-reconnect",
|
||||
"enter": "core:edit-selected-node",
|
||||
"ctrl-enter": "core:go-to-selection",
|
||||
"ctrl-c": "core:copy-selection-to-internal-clipboard",
|
||||
|
@ -15,6 +15,9 @@
|
||||
**/
|
||||
RED.nodes = (function() {
|
||||
|
||||
var PORT_TYPE_INPUT = 1;
|
||||
var PORT_TYPE_OUTPUT = 0;
|
||||
|
||||
var node_defs = {};
|
||||
var linkTabMap = {};
|
||||
|
||||
@ -2458,6 +2461,144 @@ RED.nodes = (function() {
|
||||
return helpContent;
|
||||
}
|
||||
|
||||
function getNodeIslands(nodes) {
|
||||
var selectedNodes = new Set(nodes);
|
||||
// Maps node => island index
|
||||
var nodeToIslandIndex = new Map();
|
||||
// Maps island index => [nodes in island]
|
||||
var islandIndexToNodes = new Map();
|
||||
var internalLinks = new Set();
|
||||
nodes.forEach((node, index) => {
|
||||
nodeToIslandIndex.set(node,index);
|
||||
islandIndexToNodes.set(index, [node]);
|
||||
var inboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_INPUT);
|
||||
var outboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_OUTPUT);
|
||||
inboundLinks.forEach(l => {
|
||||
if (selectedNodes.has(l.source)) {
|
||||
internalLinks.add(l)
|
||||
}
|
||||
})
|
||||
outboundLinks.forEach(l => {
|
||||
if (selectedNodes.has(l.target)) {
|
||||
internalLinks.add(l)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
internalLinks.forEach(l => {
|
||||
let source = l.source;
|
||||
let target = l.target;
|
||||
if (nodeToIslandIndex.get(source) !== nodeToIslandIndex.get(target)) {
|
||||
let sourceIsland = nodeToIslandIndex.get(source);
|
||||
let islandToMove = nodeToIslandIndex.get(target);
|
||||
let nodesToMove = islandIndexToNodes.get(islandToMove);
|
||||
nodesToMove.forEach(n => {
|
||||
nodeToIslandIndex.set(n,sourceIsland);
|
||||
islandIndexToNodes.get(sourceIsland).push(n);
|
||||
})
|
||||
islandIndexToNodes.delete(islandToMove);
|
||||
}
|
||||
})
|
||||
const result = [];
|
||||
islandIndexToNodes.forEach((nodes,index) => {
|
||||
result.push(nodes);
|
||||
})
|
||||
return result;
|
||||
}
|
||||
|
||||
function detachNodes(nodes) {
|
||||
let allSelectedNodes = [];
|
||||
nodes.forEach(node => {
|
||||
if (node.type === 'group') {
|
||||
let groupNodes = RED.group.getNodes(node,true,true);
|
||||
allSelectedNodes = allSelectedNodes.concat(groupNodes);
|
||||
} else {
|
||||
allSelectedNodes.push(node);
|
||||
}
|
||||
})
|
||||
if (allSelectedNodes.length > 0 ) {
|
||||
const nodeIslands = RED.nodes.getNodeIslands(allSelectedNodes);
|
||||
let removedLinks = [];
|
||||
let newLinks = [];
|
||||
let createdLinkIds = new Set();
|
||||
|
||||
nodeIslands.forEach(nodes => {
|
||||
let selectedNodes = new Set(nodes);
|
||||
let allInboundLinks = [];
|
||||
let allOutboundLinks = [];
|
||||
// Identify links that enter or exit this island of nodes
|
||||
nodes.forEach(node => {
|
||||
var inboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_INPUT);
|
||||
var outboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_OUTPUT);
|
||||
inboundLinks.forEach(l => {
|
||||
if (!selectedNodes.has(l.source)) {
|
||||
allInboundLinks.push(l)
|
||||
}
|
||||
})
|
||||
outboundLinks.forEach(l => {
|
||||
if (!selectedNodes.has(l.target)) {
|
||||
allOutboundLinks.push(l)
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
// Identify the links to restore
|
||||
allInboundLinks.forEach(inLink => {
|
||||
// For Each inbound link,
|
||||
// - get source node.
|
||||
// - trace through to all outbound links
|
||||
let sourceNode = inLink.source;
|
||||
let targetNodes = new Set();
|
||||
let visited = new Set();
|
||||
let stack = [inLink.target];
|
||||
while (stack.length > 0) {
|
||||
let node = stack.pop(stack);
|
||||
visited.add(node)
|
||||
let links = RED.nodes.getNodeLinks(node, PORT_TYPE_OUTPUT);
|
||||
links.forEach(l => {
|
||||
if (visited.has(l.target)) {
|
||||
return
|
||||
}
|
||||
visited.add(l.target);
|
||||
if (selectedNodes.has(l.target)) {
|
||||
// internal link
|
||||
stack.push(l.target)
|
||||
} else {
|
||||
targetNodes.add(l.target)
|
||||
}
|
||||
})
|
||||
}
|
||||
targetNodes.forEach(target => {
|
||||
let linkId = `${sourceNode.id}[${inLink.sourcePort}] -> ${target.id}`
|
||||
if (!createdLinkIds.has(linkId)) {
|
||||
createdLinkIds.add(linkId);
|
||||
let link = {
|
||||
source: sourceNode,
|
||||
sourcePort: inLink.sourcePort,
|
||||
target: target
|
||||
}
|
||||
let existingLinks = RED.nodes.filterLinks(link)
|
||||
if (existingLinks.length === 0) {
|
||||
newLinks.push(link);
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 2. delete all those links
|
||||
allInboundLinks.forEach(l => { RED.nodes.removeLink(l); removedLinks.push(l)})
|
||||
allOutboundLinks.forEach(l => { RED.nodes.removeLink(l); removedLinks.push(l)})
|
||||
})
|
||||
|
||||
newLinks.forEach(l => RED.nodes.addLink(l));
|
||||
return {
|
||||
newLinks,
|
||||
removedLinks
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
RED.events.on("registry:node-type-added",function(type) {
|
||||
@ -2539,7 +2680,7 @@ RED.nodes = (function() {
|
||||
add: addNode,
|
||||
remove: removeNode,
|
||||
clear: clear,
|
||||
|
||||
detachNodes: detachNodes,
|
||||
moveNodesForwards: moveNodesForwards,
|
||||
moveNodesBackwards: moveNodesBackwards,
|
||||
moveNodesToFront: moveNodesToFront,
|
||||
@ -2551,7 +2692,20 @@ RED.nodes = (function() {
|
||||
|
||||
addLink: addLink,
|
||||
removeLink: removeLink,
|
||||
|
||||
getNodeLinks: function(id, portType) {
|
||||
if (typeof id !== 'string') {
|
||||
id = id.id;
|
||||
}
|
||||
if (nodeLinks[id]) {
|
||||
if (portType === 1) {
|
||||
// Return cloned arrays so they can be safely modified by caller
|
||||
return [].concat(nodeLinks[id].in)
|
||||
} else {
|
||||
return [].concat(nodeLinks[id].out)
|
||||
}
|
||||
}
|
||||
return [];
|
||||
},
|
||||
addWorkspace: addWorkspace,
|
||||
removeWorkspace: removeWorkspace,
|
||||
getWorkspaceOrder: function() { return workspacesOrder },
|
||||
@ -2625,6 +2779,7 @@ RED.nodes = (function() {
|
||||
getAllFlowNodes: getAllFlowNodes,
|
||||
getAllUpstreamNodes: getAllUpstreamNodes,
|
||||
getAllDownstreamNodes: getAllDownstreamNodes,
|
||||
getNodeIslands: getNodeIslands,
|
||||
createExportableNodeSet: createExportableNodeSet,
|
||||
createCompleteNodeSet: createCompleteNodeSet,
|
||||
updateConfigNodeUsers: updateConfigNodeUsers,
|
||||
|
@ -556,8 +556,7 @@ var RED = (function() {
|
||||
|
||||
$(".red-ui-header-toolbar").show();
|
||||
|
||||
|
||||
RED.sidebar.show(":first");
|
||||
RED.sidebar.show(":first", true);
|
||||
|
||||
setTimeout(function() {
|
||||
loader.end();
|
||||
|
@ -160,18 +160,19 @@ RED.actionList = (function() {
|
||||
createDialog();
|
||||
}
|
||||
dialog.slideDown(300);
|
||||
searchInput.searchBox('value',v)
|
||||
searchInput.searchBox('value',v);
|
||||
searchResults.editableList('empty');
|
||||
results = [];
|
||||
var actions = RED.actions.list();
|
||||
actions.sort(function(A,B) {
|
||||
return A.id.localeCompare(B.id);
|
||||
var Akey = A.label;
|
||||
var Bkey = B.label;
|
||||
return Akey.localeCompare(Bkey);
|
||||
});
|
||||
actions.forEach(function(action) {
|
||||
action.label = action.id.replace(/:/,": ").replace(/-/g," ").replace(/(^| )./g,function() { return arguments[0].toUpperCase()});
|
||||
action._label = action.label.toLowerCase();
|
||||
searchResults.editableList('addItem',action)
|
||||
})
|
||||
searchResults.editableList('addItem',action);
|
||||
});
|
||||
RED.events.emit("actionList:open");
|
||||
visible = true;
|
||||
}
|
||||
|
@ -1,33 +1,39 @@
|
||||
RED.actions = (function() {
|
||||
var actions = {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
function addAction(name,handler) {
|
||||
function addAction(name,handler,options) {
|
||||
if (typeof handler !== 'function') {
|
||||
throw new Error("Action handler not a function");
|
||||
}
|
||||
if (actions[name]) {
|
||||
throw new Error("Cannot override existing action");
|
||||
}
|
||||
actions[name] = handler;
|
||||
actions[name] = {
|
||||
handler: handler,
|
||||
options: options,
|
||||
};
|
||||
}
|
||||
function removeAction(name) {
|
||||
delete actions[name];
|
||||
}
|
||||
function getAction(name) {
|
||||
return actions[name];
|
||||
return actions[name].handler;
|
||||
}
|
||||
function invokeAction() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var name = args.shift();
|
||||
if (actions.hasOwnProperty(name)) {
|
||||
actions[name].apply(null, args);
|
||||
var handler = actions[name].handler;
|
||||
handler.apply(null, args);
|
||||
}
|
||||
}
|
||||
function listActions() {
|
||||
var result = [];
|
||||
var missing = [];
|
||||
Object.keys(actions).forEach(function(action) {
|
||||
var def = actions[action];
|
||||
var shortcut = RED.keyboard.getShortcut(action);
|
||||
var isUser = false;
|
||||
if (shortcut) {
|
||||
@ -35,13 +41,38 @@ RED.actions = (function() {
|
||||
} else {
|
||||
isUser = !!RED.keyboard.getUserShortcut(action);
|
||||
}
|
||||
if (!def.label) {
|
||||
var name = action;
|
||||
var options = def.options;
|
||||
var key = options ? options.label : undefined;
|
||||
if (!key) {
|
||||
key = "action-list." +name.replace(/^.*:/,"");
|
||||
}
|
||||
var label = RED._(key);
|
||||
if (label === key) {
|
||||
// no translation. convert `name` to description
|
||||
label = name.replace(/(^.+:([a-z]))|(-([a-z]))/g, function() {
|
||||
if (arguments[5] === 0) {
|
||||
return arguments[2].toUpperCase();
|
||||
} else {
|
||||
return " "+arguments[4].toUpperCase();
|
||||
}
|
||||
});
|
||||
missing.push(key);
|
||||
}
|
||||
def.label = label;
|
||||
}
|
||||
//console.log("; missing:", missing);
|
||||
|
||||
result.push({
|
||||
id:action,
|
||||
scope:shortcut?shortcut.scope:undefined,
|
||||
key:shortcut?shortcut.key:undefined,
|
||||
user:isUser
|
||||
})
|
||||
})
|
||||
user:isUser,
|
||||
label: def.label,
|
||||
options: def.options,
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
return {
|
||||
|
@ -557,23 +557,19 @@ RED.tabs = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
li.one("transitionend", function(evt) {
|
||||
li.remove();
|
||||
if (tabs[id].pinned) {
|
||||
pinnedTabsCount--;
|
||||
}
|
||||
if (options.onremove) {
|
||||
options.onremove(tabs[id]);
|
||||
}
|
||||
delete tabs[id];
|
||||
updateTabWidths();
|
||||
if (collapsibleMenu) {
|
||||
collapsibleMenu.remove();
|
||||
collapsibleMenu = null;
|
||||
}
|
||||
})
|
||||
li.addClass("hide-tab");
|
||||
li.width(0);
|
||||
li.remove();
|
||||
if (tabs[id].pinned) {
|
||||
pinnedTabsCount--;
|
||||
}
|
||||
if (options.onremove) {
|
||||
options.onremove(tabs[id]);
|
||||
}
|
||||
delete tabs[id];
|
||||
updateTabWidths();
|
||||
if (collapsibleMenu) {
|
||||
collapsibleMenu.remove();
|
||||
collapsibleMenu = null;
|
||||
}
|
||||
}
|
||||
|
||||
function findPreviousVisibleTab(li) {
|
||||
|
@ -333,6 +333,16 @@ RED.deploy = (function() {
|
||||
var unknownNodes = [];
|
||||
var invalidNodes = [];
|
||||
|
||||
RED.nodes.eachConfig(function(node) {
|
||||
if (!node.valid && !node.d) {
|
||||
invalidNodes.push(getNodeInfo(node));
|
||||
}
|
||||
if (node.type === "unknown") {
|
||||
if (unknownNodes.indexOf(node.name) == -1) {
|
||||
unknownNodes.push(node.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
RED.nodes.eachNode(function(node) {
|
||||
if (!node.valid && !node.d) {
|
||||
invalidNodes.push(getNodeInfo(node));
|
||||
|
@ -35,9 +35,10 @@
|
||||
editState.changed = true;
|
||||
}
|
||||
if (!node._def.defaults || !node._def.defaults.hasOwnProperty("icon")) {
|
||||
var icon = $("#red-ui-editor-node-icon").val()||""
|
||||
var icon = $("#red-ui-editor-node-icon").val()||"";
|
||||
if (!this.isDefaultIcon) {
|
||||
if (icon !== node.icon) {
|
||||
if ((icon !== node.icon) &&
|
||||
(icon !== "")) {
|
||||
editState.changes.icon = node.icon;
|
||||
node.icon = icon;
|
||||
editState.changed = true;
|
||||
@ -101,14 +102,14 @@
|
||||
if (showLabel) {
|
||||
// Default to show label
|
||||
if (node.l !== false) {
|
||||
editState.changes.l = node.l
|
||||
editState.changes.l = node.l;
|
||||
editState.changed = true;
|
||||
}
|
||||
node.l = false;
|
||||
} else {
|
||||
// Node has showLabel:false (eg link nodes)
|
||||
if (node.hasOwnProperty('l') && node.l) {
|
||||
editState.changes.l = node.l
|
||||
editState.changes.l = node.l;
|
||||
editState.changed = true;
|
||||
}
|
||||
delete node.l;
|
||||
@ -118,20 +119,20 @@
|
||||
if (showLabel) {
|
||||
// Default to show label
|
||||
if (node.hasOwnProperty('l') && !node.l) {
|
||||
editState.changes.l = node.l
|
||||
editState.changes.l = node.l;
|
||||
editState.changed = true;
|
||||
}
|
||||
delete node.l;
|
||||
} else {
|
||||
if (!node.l) {
|
||||
editState.changes.l = node.l
|
||||
editState.changes.l = node.l;
|
||||
editState.changed = true;
|
||||
}
|
||||
node.l = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
function buildAppearanceForm(container,node) {
|
||||
@ -164,10 +165,10 @@
|
||||
var categories = RED.palette.getCategories();
|
||||
categories.sort(function(A,B) {
|
||||
return A.label.localeCompare(B.label);
|
||||
})
|
||||
});
|
||||
categories.forEach(function(cat) {
|
||||
categorySelector.append($("<option/>").val(cat.id).text(cat.label));
|
||||
})
|
||||
});
|
||||
categorySelector.append($("<option/>").attr('disabled',true).text("---"));
|
||||
categorySelector.append($("<option/>").val("_custom_").text(RED._("palette.addCategory")));
|
||||
|
||||
@ -180,7 +181,7 @@
|
||||
$("#subflow-appearance-input-category").width(250);
|
||||
$("#subflow-appearance-input-custom-category").hide();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
$("#subflow-appearance-input-category").val(node.category||"subflows");
|
||||
var userCount = 0;
|
||||
@ -204,7 +205,7 @@
|
||||
$("#node-input-show-label").toggleButton({
|
||||
enabledLabel: RED._("editor.show"),
|
||||
disabledLabel: RED._("editor.hide")
|
||||
})
|
||||
});
|
||||
|
||||
if (!node.hasOwnProperty("l")) {
|
||||
// Show label unless def.showLabel set to false
|
||||
@ -230,7 +231,7 @@
|
||||
"#E9967A", "#F3B567", "#FDD0A2",
|
||||
"#FDF0C2", "#FFAAAA", "#FFCC66",
|
||||
"#FFF0F0", "#FFFFFF"
|
||||
]
|
||||
];
|
||||
|
||||
RED.editor.colorPicker.create({
|
||||
id: "red-ui-editor-node-color",
|
||||
@ -245,9 +246,9 @@
|
||||
nodeDiv.css('backgroundColor',colour);
|
||||
var borderColor = RED.utils.getDarkerColor(colour);
|
||||
if (borderColor !== colour) {
|
||||
nodeDiv.css('border-color',borderColor)
|
||||
nodeDiv.css('border-color',borderColor);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -264,7 +265,7 @@
|
||||
nodeDiv.css('backgroundColor',colour);
|
||||
var borderColor = RED.utils.getDarkerColor(colour);
|
||||
if (borderColor !== colour) {
|
||||
nodeDiv.css('border-color',borderColor)
|
||||
nodeDiv.css('border-color',borderColor);
|
||||
}
|
||||
|
||||
var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
|
||||
@ -292,7 +293,7 @@
|
||||
|
||||
RED.popover.tooltip(iconButton, function() {
|
||||
return $("#red-ui-editor-node-icon").val() || RED._("editor.default");
|
||||
})
|
||||
});
|
||||
$('<input type="hidden" id="red-ui-editor-node-icon">').val(node.icon).appendTo(iconRow);
|
||||
}
|
||||
|
||||
@ -417,11 +418,11 @@
|
||||
});
|
||||
rows.sort(function(A,B) {
|
||||
return A.i-B.i;
|
||||
})
|
||||
});
|
||||
rows.forEach(function(r,i) {
|
||||
r.r.find("label").text((i+1)+".");
|
||||
r.r.appendTo(outputsDiv);
|
||||
})
|
||||
});
|
||||
if (rows.length === 0) {
|
||||
buildLabelRow("output",i,"").appendTo(outputsDiv);
|
||||
} else {
|
||||
@ -467,7 +468,7 @@
|
||||
clear.on("click", function(evt) {
|
||||
evt.preventDefault();
|
||||
input.val("");
|
||||
})
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -501,6 +502,12 @@
|
||||
}
|
||||
var v = $(this).val();
|
||||
hasNonBlankLabel = hasNonBlankLabel || v!== "";
|
||||
|
||||
// mark changed output port labels as dirty
|
||||
if (node.type === "subflow" && (!node.outputLabels || node.outputLabels[index] !== v)) {
|
||||
node.out[index].dirty = true;
|
||||
}
|
||||
|
||||
newValue[index] = v;
|
||||
});
|
||||
|
||||
@ -509,6 +516,12 @@
|
||||
changes.outputLabels = node.outputLabels;
|
||||
node.outputLabels = newValue;
|
||||
changed = true;
|
||||
|
||||
// trigger redraw of dirty port labels
|
||||
if (node.type === "subflow") {
|
||||
RED.view.redraw();
|
||||
}
|
||||
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
@ -590,12 +590,14 @@ RED.group = (function() {
|
||||
markDirty(group);
|
||||
}
|
||||
|
||||
function getNodes(group,recursive) {
|
||||
function getNodes(group,recursive,excludeGroup) {
|
||||
var nodes = [];
|
||||
group.nodes.forEach(function(n) {
|
||||
nodes.push(n);
|
||||
if (n.type !== 'group' || !excludeGroup) {
|
||||
nodes.push(n);
|
||||
}
|
||||
if (recursive && n.type === 'group') {
|
||||
nodes = nodes.concat(getNodes(n,recursive))
|
||||
nodes = nodes.concat(getNodes(n,recursive,excludeGroup))
|
||||
}
|
||||
})
|
||||
return nodes;
|
||||
|
@ -49,15 +49,15 @@ RED.keyboard = (function() {
|
||||
"]": 221,
|
||||
"{": 219,// <- QWERTY specific
|
||||
"}": 221 // <- QWERTY specific
|
||||
}
|
||||
};
|
||||
var metaKeyCodes = {
|
||||
16: true,
|
||||
17: true,
|
||||
18: true,
|
||||
91: true,
|
||||
93: true
|
||||
}
|
||||
var actionToKeyMap = {}
|
||||
};
|
||||
var actionToKeyMap = {};
|
||||
var defaultKeyMap = {};
|
||||
|
||||
// FF generates some different keycodes because reasons.
|
||||
@ -65,7 +65,7 @@ RED.keyboard = (function() {
|
||||
59:186,
|
||||
61:187,
|
||||
173:189
|
||||
}
|
||||
};
|
||||
|
||||
function migrateOldKeymap() {
|
||||
// pre-0.18
|
||||
@ -80,7 +80,7 @@ RED.keyboard = (function() {
|
||||
}
|
||||
|
||||
function getUserKey(action) {
|
||||
return RED.settings.get('editor.keymap',{})[action]
|
||||
return RED.settings.get('editor.keymap',{})[action];
|
||||
}
|
||||
|
||||
function mergeKeymaps(defaultKeymap, themeKeymap) {
|
||||
@ -105,7 +105,7 @@ RED.keyboard = (function() {
|
||||
scope:scope,
|
||||
key:key,
|
||||
user:false
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -115,13 +115,13 @@ RED.keyboard = (function() {
|
||||
if (themeKeymap.hasOwnProperty(action)) {
|
||||
if (!themeKeymap[action].key) {
|
||||
// No key for this action - default is no keybinding
|
||||
delete mergedKeymap[action]
|
||||
delete mergedKeymap[action];
|
||||
} else {
|
||||
mergedKeymap[action] = [{
|
||||
scope: themeKeymap[action].scope || "*",
|
||||
key: themeKeymap[action].key,
|
||||
user: false
|
||||
}]
|
||||
}];
|
||||
if (mergedKeymap[action][0].scope === "workspace") {
|
||||
mergedKeymap[action][0].scope = "red-ui-workspace";
|
||||
}
|
||||
@ -179,7 +179,7 @@ RED.keyboard = (function() {
|
||||
close: function() {
|
||||
RED.menu.refreshShortcuts();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function revertToDefault(action) {
|
||||
@ -327,7 +327,7 @@ RED.keyboard = (function() {
|
||||
scope:scope,
|
||||
key:key,
|
||||
user:false
|
||||
}
|
||||
};
|
||||
}
|
||||
if (!ondown) {
|
||||
var userAction = getUserKey(cbdown);
|
||||
@ -350,7 +350,7 @@ RED.keyboard = (function() {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
keys.push([key,mod])
|
||||
keys.push([key,mod]);
|
||||
}
|
||||
var slot = handlers;
|
||||
for (i=0;i<keys.length;i++) {
|
||||
@ -373,7 +373,7 @@ RED.keyboard = (function() {
|
||||
//slot[key] = {scope: scope, ondown:cbdown};
|
||||
}
|
||||
slot.handlers = slot.handlers || [];
|
||||
slot.handlers.push({scope:scope,ondown:cbdown})
|
||||
slot.handlers.push({scope:scope,ondown:cbdown});
|
||||
slot.scope = scope;
|
||||
slot.ondown = cbdown;
|
||||
}
|
||||
@ -390,12 +390,12 @@ RED.keyboard = (function() {
|
||||
if (parsedKey) {
|
||||
keys.push(parsedKey);
|
||||
} else {
|
||||
console.log("Unrecognised key specifier:",key)
|
||||
console.log("Unrecognised key specifier:",key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
keys.push([key,mod])
|
||||
keys.push([key,mod]);
|
||||
}
|
||||
var slot = handlers;
|
||||
for (i=0;i<keys.length;i++) {
|
||||
@ -417,7 +417,7 @@ RED.keyboard = (function() {
|
||||
}
|
||||
if (typeof slot.ondown === "string") {
|
||||
if (typeof modifiers === 'boolean' && modifiers) {
|
||||
actionToKeyMap[slot.ondown] = {user: modifiers}
|
||||
actionToKeyMap[slot.ondown] = {user: modifiers};
|
||||
} else {
|
||||
delete actionToKeyMap[slot.ondown];
|
||||
}
|
||||
@ -433,11 +433,11 @@ RED.keyboard = (function() {
|
||||
function formatKey(key,plain) {
|
||||
var formattedKey = isMac?key.replace(/ctrl-?/,"⌘"):key;
|
||||
formattedKey = isMac?formattedKey.replace(/alt-?/,"⌥"):key;
|
||||
formattedKey = formattedKey.replace(/shift-?/,"⇧")
|
||||
formattedKey = formattedKey.replace(/left/,"←")
|
||||
formattedKey = formattedKey.replace(/up/,"↑")
|
||||
formattedKey = formattedKey.replace(/right/,"→")
|
||||
formattedKey = formattedKey.replace(/down/,"↓")
|
||||
formattedKey = formattedKey.replace(/shift-?/,"⇧");
|
||||
formattedKey = formattedKey.replace(/left/,"←");
|
||||
formattedKey = formattedKey.replace(/up/,"↑");
|
||||
formattedKey = formattedKey.replace(/right/,"→");
|
||||
formattedKey = formattedKey.replace(/down/,"↓");
|
||||
if (plain) {
|
||||
return formattedKey;
|
||||
}
|
||||
@ -461,7 +461,6 @@ RED.keyboard = (function() {
|
||||
var container = $(this);
|
||||
var object = container.data('data');
|
||||
|
||||
|
||||
if (!container.hasClass('keyboard-shortcut-entry-expanded')) {
|
||||
endEditShortcut();
|
||||
|
||||
@ -485,7 +484,7 @@ RED.keyboard = (function() {
|
||||
}
|
||||
$(this).toggleClass("input-error",!valid);
|
||||
okButton.attr("disabled",!valid);
|
||||
})
|
||||
});
|
||||
|
||||
var scopeSelect = $('<select><option value="*" data-i18n="keyboard.global"></option><option value="red-ui-workspace" data-i18n="keyboard.workspace"></option></select>').appendTo(scope);
|
||||
scopeSelect.i18n();
|
||||
@ -495,7 +494,7 @@ RED.keyboard = (function() {
|
||||
scopeSelect.val(object.scope||'*');
|
||||
scopeSelect.on("change", function() {
|
||||
keyInput.trigger("change");
|
||||
})
|
||||
});
|
||||
|
||||
var div = $('<div class="keyboard-shortcut-edit button-group-vertical"></div>').appendTo(scope);
|
||||
var okButton = $('<button class="red-ui-button red-ui-button-small"><i class="fa fa-check"></i></button>').appendTo(div);
|
||||
@ -521,10 +520,13 @@ RED.keyboard = (function() {
|
||||
id:object.id,
|
||||
scope:shortcut?shortcut.scope:undefined,
|
||||
key:shortcut?shortcut.key:undefined,
|
||||
user:shortcut?shortcut.user:undefined
|
||||
}
|
||||
user:shortcut?shortcut.user:undefined,
|
||||
|
||||
label: object.label,
|
||||
options: object.options,
|
||||
};
|
||||
buildShortcutRow(container,obj);
|
||||
})
|
||||
});
|
||||
|
||||
keyInput.trigger("focus");
|
||||
}
|
||||
@ -559,7 +561,7 @@ RED.keyboard = (function() {
|
||||
delete object.scope;
|
||||
} else {
|
||||
keyDiv.parent().removeClass("keyboard-shortcut-entry-unassigned");
|
||||
keyDiv.append(RED.keyboard.formatKey(key))
|
||||
keyDiv.append(RED.keyboard.formatKey(key));
|
||||
$("<span>").text(scope).appendTo(scopeDiv);
|
||||
object.key = key;
|
||||
object.scope = scope;
|
||||
@ -572,7 +574,7 @@ RED.keyboard = (function() {
|
||||
userKeymap[object.id] = {
|
||||
scope:shortcut.scope,
|
||||
key:shortcut.key
|
||||
}
|
||||
};
|
||||
RED.settings.set('editor.keymap',userKeymap);
|
||||
}
|
||||
}
|
||||
@ -588,13 +590,7 @@ RED.keyboard = (function() {
|
||||
var item = $('<div class="keyboard-shortcut-entry">').appendTo(container);
|
||||
container.data('data',object);
|
||||
|
||||
var text = object.id.replace(/(^.+:([a-z]))|(-([a-z]))/g,function() {
|
||||
if (arguments[5] === 0) {
|
||||
return arguments[2].toUpperCase();
|
||||
} else {
|
||||
return " "+arguments[4].toUpperCase();
|
||||
}
|
||||
});
|
||||
var text = object.label;
|
||||
var label = $('<div>').addClass("keyboard-shortcut-entry-text").text(text).appendTo(item);
|
||||
|
||||
var user = $('<i class="fa fa-user"></i>').prependTo(label);
|
||||
@ -635,8 +631,9 @@ RED.keyboard = (function() {
|
||||
} else {
|
||||
filterValue = filterValue.replace(/\s/g,"");
|
||||
shortcutList.editableList('filter', function(data) {
|
||||
return data.id.toLowerCase().replace(/^.*:/,"").replace("-","").indexOf(filterValue) > -1;
|
||||
})
|
||||
var label = data.label.toLowerCase();
|
||||
return label.indexOf(filterValue) > -1;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -657,9 +654,9 @@ RED.keyboard = (function() {
|
||||
});
|
||||
var shortcuts = RED.actions.list();
|
||||
shortcuts.sort(function(A,B) {
|
||||
var Aid = A.id.replace(/^.*:/,"").replace(/[ -]/g,"").toLowerCase();
|
||||
var Bid = B.id.replace(/^.*:/,"").replace(/[ -]/g,"").toLowerCase();
|
||||
return Aid.localeCompare(Bid);
|
||||
var Akey = A.label;
|
||||
var Bkey = B.label;
|
||||
return Akey.localeCompare(Bkey);
|
||||
});
|
||||
knownShortcuts = new Set();
|
||||
shortcuts.forEach(function(s) {
|
||||
|
@ -1212,6 +1212,9 @@ RED.projects = (function() {
|
||||
}
|
||||
}).appendTo(row);
|
||||
|
||||
row = $('<div class="form-row red-ui-projects-dialog-screen-create-row red-ui-projects-dialog-screen-create-row-open"></div>').hide().appendTo(container);
|
||||
$('<span style="display: flex; align-items: center;"><input style="padding:0; margin: 0 5px 0 0" checked type="checkbox" id="red-ui-projects-dialog-screen-clear-context"> <label for="red-ui-projects-dialog-screen-clear-context" style="padding:0; margin: 0"> <span data-i18n="projects.create.clearContext"></span></label></span>').appendTo(row).i18n();
|
||||
|
||||
row = $('<div class="form-row red-ui-projects-dialog-screen-create-row red-ui-projects-dialog-screen-create-row-empty red-ui-projects-dialog-screen-create-row-clone"></div>').appendTo(container);
|
||||
$('<label for="red-ui-projects-dialog-screen-create-project-name">'+RED._("projects.create.project-name")+'</label>').appendTo(row);
|
||||
|
||||
@ -1501,7 +1504,8 @@ RED.projects = (function() {
|
||||
};
|
||||
}
|
||||
} else if (projectType === 'open') {
|
||||
return switchProject(selectedProject.name,function(err,data) {
|
||||
var clearContext = $("#red-ui-projects-dialog-screen-clear-context").prop("checked")
|
||||
return switchProject(selectedProject.name, clearContext, function(err,data) {
|
||||
if (err) {
|
||||
if (err.code !== 'credentials_load_failed') {
|
||||
console.log(RED._("projects.create.unexpected_error"),err)
|
||||
@ -1595,7 +1599,7 @@ RED.projects = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
function switchProject(name,done) {
|
||||
function switchProject(name,clearContext,done) {
|
||||
RED.deploy.setDeployInflight(true);
|
||||
RED.projects.settings.switchProject(name);
|
||||
sendRequest({
|
||||
@ -1614,7 +1618,7 @@ RED.projects = (function() {
|
||||
'*': done
|
||||
},
|
||||
}
|
||||
},{active:true}).then(function() {
|
||||
},{active:true, clearContext:clearContext}).then(function() {
|
||||
dialog.dialog( "close" );
|
||||
RED.events.emit("project:change", {name:name});
|
||||
}).always(function() {
|
||||
@ -1687,7 +1691,7 @@ RED.projects = (function() {
|
||||
dialogHeight = 590 - (750 - winHeight);
|
||||
}
|
||||
$(".red-ui-projects-dialog-box").height(dialogHeight);
|
||||
$(".red-ui-projects-dialog-project-list-inner-container").height(Math.max(500,dialogHeight) - 180);
|
||||
$(".red-ui-projects-dialog-project-list-inner-container").height(Math.max(500,dialogHeight) - 210);
|
||||
dialog.dialog('option','title',screen.title||"");
|
||||
dialog.dialog("open");
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ RED.search = (function() {
|
||||
var selected = -1;
|
||||
var visible = false;
|
||||
|
||||
var searchHistory = [];
|
||||
var index = {};
|
||||
var currentResults = [];
|
||||
var previousActiveElement;
|
||||
@ -52,10 +53,22 @@ RED.search = (function() {
|
||||
}
|
||||
l = l||n.label||n.name||n.id||"";
|
||||
|
||||
|
||||
var properties = ['id','type','name','label','info'];
|
||||
if (n._def && n._def.defaults) {
|
||||
properties = properties.concat(Object.keys(n._def.defaults));
|
||||
const node_def = n && n._def;
|
||||
if (node_def) {
|
||||
if (node_def.defaults) {
|
||||
properties = properties.concat(Object.keys(node_def.defaults));
|
||||
}
|
||||
if (n.type !== "group" && node_def.paletteLabel && node_def.paletteLabel !== node_def.type) {
|
||||
try {
|
||||
const label = ("" + (typeof node_def.paletteLabel === "function" ? node_def.paletteLabel.call(node_def) : node_def.paletteLabel)).toLowerCase();
|
||||
if(label && label !== (""+node_def.type).toLowerCase()) {
|
||||
indexProperty(n, l, label);
|
||||
}
|
||||
} catch(err) {
|
||||
console.warn(`error indexing ${l}`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var i=0;i<properties.length;i++) {
|
||||
if (n.hasOwnProperty(properties[i])) {
|
||||
@ -205,6 +218,20 @@ RED.search = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
function populateSearchHistory() {
|
||||
if (searchHistory.length > 0) {
|
||||
searchResults.editableList('addItem',{
|
||||
historyHeader: true
|
||||
});
|
||||
searchHistory.forEach(function(entry) {
|
||||
searchResults.editableList('addItem',{
|
||||
history: true,
|
||||
value: entry
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
function createDialog() {
|
||||
dialog = $("<div>",{id:"red-ui-search",class:"red-ui-search"}).appendTo("#red-ui-main-container");
|
||||
var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(dialog);
|
||||
@ -213,7 +240,12 @@ RED.search = (function() {
|
||||
change: function() {
|
||||
searchResults.editableList('empty');
|
||||
selected = -1;
|
||||
currentResults = search($(this).val());
|
||||
var value = $(this).val();
|
||||
if (value === "") {
|
||||
populateSearchHistory();
|
||||
return;
|
||||
}
|
||||
currentResults = search(value);
|
||||
if (currentResults.length > 0) {
|
||||
for (i=0;i<Math.min(currentResults.length,25);i++) {
|
||||
searchResults.editableList('addItem',currentResults[i])
|
||||
@ -285,7 +317,12 @@ RED.search = (function() {
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} if ($(children[selected]).hasClass("red-ui-search-history")) {
|
||||
var object = $(children[selected]).find(".red-ui-editableList-item-content").data('data');
|
||||
if (object) {
|
||||
searchInput.searchBox('value',object.value)
|
||||
}
|
||||
} else if (!$(children[selected]).hasClass("red-ui-search-historyHeader")) {
|
||||
if (currentResults.length > 0) {
|
||||
reveal(currentResults[Math.max(0,selected)].node);
|
||||
}
|
||||
@ -301,7 +338,32 @@ RED.search = (function() {
|
||||
addItem: function(container,i,object) {
|
||||
var node = object.node;
|
||||
var div;
|
||||
if (object.more) {
|
||||
if (object.historyHeader) {
|
||||
container.parent().addClass("red-ui-search-historyHeader")
|
||||
$('<div>',{class:"red-ui-search-empty"}).text(RED._("search.history")).appendTo(container);
|
||||
$('<button type="button" class="red-ui-button red-ui-button-small"></button>').text(RED._("search.clear")).appendTo(container).on("click", function(evt) {
|
||||
evt.preventDefault();
|
||||
searchHistory = [];
|
||||
searchResults.editableList('empty');
|
||||
});
|
||||
} else if (object.history) {
|
||||
container.parent().addClass("red-ui-search-history")
|
||||
div = $('<a>',{href:'#',class:"red-ui-search-result"}).appendTo(container);
|
||||
div.text(object.value);
|
||||
div.on("click", function(evt) {
|
||||
evt.preventDefault();
|
||||
searchInput.searchBox('value',object.value)
|
||||
searchInput.focus();
|
||||
})
|
||||
$('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-remove"></i></button>').appendTo(container).on("click", function(evt) {
|
||||
evt.preventDefault();
|
||||
var index = searchHistory.indexOf(object.value);
|
||||
searchHistory.splice(index,1);
|
||||
searchResults.editableList('removeItem', object);
|
||||
});
|
||||
|
||||
|
||||
} else if (object.more) {
|
||||
container.parent().addClass("red-ui-search-more")
|
||||
div = $('<a>',{href:'#',class:"red-ui-search-result red-ui-search-empty"}).appendTo(container);
|
||||
div.text(RED._("palette.editor.more",{count:object.more.results.length-object.more.start}));
|
||||
@ -356,6 +418,12 @@ RED.search = (function() {
|
||||
}
|
||||
|
||||
function reveal(node) {
|
||||
var searchVal = searchInput.val();
|
||||
var existingIndex = searchHistory.indexOf(searchVal);
|
||||
if (existingIndex > -1) {
|
||||
searchHistory.splice(existingIndex,1);
|
||||
}
|
||||
searchHistory.unshift(searchInput.val());
|
||||
hide();
|
||||
RED.view.reveal(node.id);
|
||||
}
|
||||
@ -374,9 +442,14 @@ RED.search = (function() {
|
||||
|
||||
if (dialog === null) {
|
||||
createDialog();
|
||||
} else {
|
||||
searchResults.editableList('empty');
|
||||
}
|
||||
dialog.slideDown(300);
|
||||
searchInput.searchBox('value',v)
|
||||
if (!v || v === "") {
|
||||
populateSearchHistory();
|
||||
}
|
||||
RED.events.emit("search:open");
|
||||
visible = true;
|
||||
}
|
||||
|
@ -19,6 +19,15 @@ RED.sidebar = (function() {
|
||||
var sidebar_tabs;
|
||||
var knownTabs = {};
|
||||
|
||||
// We store the current sidebar tab id in localStorage as 'last-sidebar-tab'
|
||||
// This is restored when the editor is reloaded.
|
||||
// We use sidebar_tabs.onchange to update localStorage. However that will
|
||||
// also get triggered when the first tab gets added to the tabs - typically
|
||||
// the 'info' tab. So we use the following variable to store the retrieved
|
||||
// value from localStorage before we start adding the actual tabs
|
||||
var lastSessionSelectedTab = null;
|
||||
|
||||
|
||||
function addTab(title,content,closeable,visible) {
|
||||
var options;
|
||||
if (typeof title === "string") {
|
||||
@ -194,16 +203,16 @@ RED.sidebar = (function() {
|
||||
RED.events.emit("sidebar:resize");
|
||||
}
|
||||
|
||||
function showSidebar(id) {
|
||||
function showSidebar(id, skipShowSidebar) {
|
||||
if (id === ":first") {
|
||||
id = RED.settings.get("editor.sidebar.order",["info", "help", "version-control", "debug"])[0]
|
||||
id = lastSessionSelectedTab || RED.settings.get("editor.sidebar.order",["info", "help", "version-control", "debug"])[0]
|
||||
}
|
||||
if (id) {
|
||||
if (!containsTab(id) && knownTabs[id]) {
|
||||
sidebar_tabs.addTab(knownTabs[id]);
|
||||
}
|
||||
sidebar_tabs.activateTab(id);
|
||||
if (!RED.menu.isSelected("menu-item-sidebar")) {
|
||||
if (!skipShowSidebar && !RED.menu.isSelected("menu-item-sidebar")) {
|
||||
RED.menu.setSelected("menu-item-sidebar",true);
|
||||
}
|
||||
}
|
||||
@ -227,6 +236,7 @@ RED.sidebar = (function() {
|
||||
if (tab.toolbar) {
|
||||
$(tab.toolbar).show();
|
||||
}
|
||||
RED.settings.setLocal("last-sidebar-tab", tab.id)
|
||||
},
|
||||
onremove: function(tab) {
|
||||
$(tab.wrapper).hide();
|
||||
@ -255,7 +265,9 @@ RED.sidebar = (function() {
|
||||
}
|
||||
});
|
||||
RED.popover.tooltip($("#red-ui-sidebar-separator").find(".red-ui-sidebar-control-right"),RED._("keyboard.toggleSidebar"),"core:toggle-sidebar");
|
||||
showSidebar();
|
||||
|
||||
lastSessionSelectedTab = RED.settings.getLocal("last-sidebar-tab")
|
||||
|
||||
RED.sidebar.info.init();
|
||||
RED.sidebar.help.init();
|
||||
RED.sidebar.config.init();
|
||||
|
@ -27,5 +27,7 @@ RED.state = {
|
||||
PANNING: 10,
|
||||
SELECTING_NODE: 11,
|
||||
GROUP_DRAGGING: 12,
|
||||
GROUP_RESIZE: 13
|
||||
GROUP_RESIZE: 13,
|
||||
DETACHED_DRAGGING: 14,
|
||||
SLICING: 15
|
||||
}
|
||||
|
@ -64,15 +64,17 @@ RED.sidebar.help = (function() {
|
||||
style: "compact",
|
||||
delay: 100,
|
||||
change: function() {
|
||||
var val = $(this).val().toLowerCase();
|
||||
if (val) {
|
||||
const searchFor = $(this).val().toLowerCase();
|
||||
if (searchFor) {
|
||||
showTOC();
|
||||
var c = treeList.treeList('filter',function(item) {
|
||||
treeList.treeList('filter',function(item) {
|
||||
if (item.depth === 0) {
|
||||
return true;
|
||||
}
|
||||
return (item.nodeType && item.nodeType.indexOf(val) > -1) ||
|
||||
(item.subflowLabel && item.subflowLabel.indexOf(val) > -1)
|
||||
let found = item.nodeType && item.nodeType.toLowerCase().indexOf(searchFor) > -1;
|
||||
found = found || item.subflowLabel && item.subflowLabel.toLowerCase().indexOf(searchFor) > -1;
|
||||
found = found || item.palleteLabel && item.palleteLabel.toLowerCase().indexOf(searchFor) > -1;
|
||||
return found;
|
||||
},true)
|
||||
} else {
|
||||
treeList.treeList('filter',null);
|
||||
@ -224,17 +226,21 @@ RED.sidebar.help = (function() {
|
||||
|
||||
|
||||
moduleNames.forEach(function(moduleName) {
|
||||
var module = modules[moduleName];
|
||||
var nodeTypes = [];
|
||||
|
||||
var setNames = Object.keys(module.sets);
|
||||
const module = modules[moduleName];
|
||||
const nodeTypes = [];
|
||||
const moduleSets = module.sets;
|
||||
const setNames = Object.keys(moduleSets);
|
||||
setNames.forEach(function(setName) {
|
||||
module.sets[setName].types.forEach(function(nodeType) {
|
||||
const moduleSet = moduleSets[setName];
|
||||
moduleSet.types.forEach(function(nodeType) {
|
||||
if ($("script[data-help-name='"+nodeType+"']").length) {
|
||||
const n = {_def:RED.nodes.getType(nodeType),type:nodeType}
|
||||
n.name = getNodePaletteLabel(n);
|
||||
nodeTypes.push({
|
||||
id: "node-type:"+nodeType,
|
||||
nodeType: nodeType,
|
||||
element:getNodeLabel({_def:RED.nodes.getType(nodeType),type:nodeType})
|
||||
palleteLabel: n.name,
|
||||
element: getNodeLabel(n)
|
||||
})
|
||||
}
|
||||
})
|
||||
@ -254,18 +260,21 @@ RED.sidebar.help = (function() {
|
||||
treeList.treeList("data",helpData);
|
||||
}
|
||||
|
||||
function getNodeLabel(n) {
|
||||
var div = $('<div>',{class:"red-ui-node-list-item"});
|
||||
var icon = RED.utils.createNodeIcon(n).appendTo(div);
|
||||
var label = n.name;
|
||||
function getNodePaletteLabel(n) {
|
||||
let label = n.name;
|
||||
if (!label && n._def && n._def.paletteLabel) {
|
||||
try {
|
||||
label = (typeof n._def.paletteLabel === "function" ? n._def.paletteLabel.call(n._def) : n._def.paletteLabel)||"";
|
||||
} catch (err) {
|
||||
}
|
||||
}
|
||||
label = label || n.type;
|
||||
$('<div>',{class:"red-ui-node-label"}).text(n.name||n.type).appendTo(icon);
|
||||
return label || n.type;
|
||||
}
|
||||
|
||||
function getNodeLabel(n) {
|
||||
const div = $('<div>',{class:"red-ui-node-list-item"});
|
||||
const icon = RED.utils.createNodeIcon(n).appendTo(div);
|
||||
$('<div>',{class:"red-ui-node-label"}).text(getNodePaletteLabel(n)).appendTo(icon);
|
||||
return div;
|
||||
}
|
||||
|
||||
|
@ -256,6 +256,10 @@ RED.tourGuide = (function() {
|
||||
}
|
||||
$('<div>').css("text-align","left").html(getLocaleText(step.description)).appendTo(stepDescription);
|
||||
|
||||
if (step.image) {
|
||||
$(`<img src="red/tours/${step.image}" />`).appendTo(stepDescription)
|
||||
}
|
||||
|
||||
var stepToolbar = $('<div>',{class:"red-ui-tourGuide-toolbar"}).appendTo(stepContent);
|
||||
|
||||
// var breadcrumbs = $('<div>',{class:"red-ui-tourGuide-breadcrumbs"}).appendTo(stepToolbar);
|
||||
|
@ -121,6 +121,13 @@ RED.userSettings = (function() {
|
||||
// {setting:"theme", label:"Theme",options:function(done){ done([{val:'',text:'default'}].concat(RED.settings.theme("themes"))) }},
|
||||
// ]
|
||||
// },
|
||||
{
|
||||
title: "menu.label.view.view",
|
||||
options: [
|
||||
{setting:"view-store-zoom",label:"menu.label.view.storeZoom", default: false, toggle:true, onchange: function(val) { if (!val) { RED.settings.removeLocal("zoom-level")}}},
|
||||
{setting:"view-store-position",label:"menu.label.view.storePosition", default: false, toggle:true, onchange: function(val) { if (!val) { RED.settings.removeLocal("scroll-positions")}}},
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "menu.label.view.grid",
|
||||
options: [
|
||||
|
@ -725,6 +725,90 @@ RED.view.tools = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function wireSeriesOfNodes() {
|
||||
var selection = RED.view.selection();
|
||||
if (selection.nodes) {
|
||||
if (selection.nodes.length > 1) {
|
||||
var i = 0;
|
||||
var newLinks = [];
|
||||
while (i < selection.nodes.length - 1) {
|
||||
var nodeA = selection.nodes[i];
|
||||
var nodeB = selection.nodes[i+1];
|
||||
if (nodeA.outputs > 0 && nodeB.inputs > 0) {
|
||||
var existingLinks = RED.nodes.filterLinks({
|
||||
source: nodeA,
|
||||
target: nodeB,
|
||||
sourcePort: 0
|
||||
})
|
||||
if (existingLinks.length === 0) {
|
||||
var newLink = {
|
||||
source: nodeA,
|
||||
target: nodeB,
|
||||
sourcePort: 0
|
||||
}
|
||||
RED.nodes.addLink(newLink);
|
||||
newLinks.push(newLink);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (newLinks.length > 0) {
|
||||
RED.history.push({
|
||||
t: 'add',
|
||||
links: newLinks,
|
||||
dirty: RED.nodes.dirty()
|
||||
})
|
||||
RED.nodes.dirty(true);
|
||||
RED.view.redraw(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function wireNodeToMultiple() {
|
||||
var selection = RED.view.selection();
|
||||
if (selection.nodes) {
|
||||
if (selection.nodes.length > 1) {
|
||||
var sourceNode = selection.nodes[0];
|
||||
if (sourceNode.outputs === 0) {
|
||||
return;
|
||||
}
|
||||
var i = 1;
|
||||
var newLinks = [];
|
||||
while (i < selection.nodes.length) {
|
||||
var targetNode = selection.nodes[i];
|
||||
if (targetNode.inputs > 0) {
|
||||
var existingLinks = RED.nodes.filterLinks({
|
||||
source: sourceNode,
|
||||
target: targetNode,
|
||||
sourcePort: Math.min(sourceNode.outputs-1,i-1)
|
||||
})
|
||||
if (existingLinks.length === 0) {
|
||||
var newLink = {
|
||||
source: sourceNode,
|
||||
target: targetNode,
|
||||
sourcePort: Math.min(sourceNode.outputs-1,i-1)
|
||||
}
|
||||
RED.nodes.addLink(newLink);
|
||||
newLinks.push(newLink);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (newLinks.length > 0) {
|
||||
RED.history.push({
|
||||
t: 'add',
|
||||
links: newLinks,
|
||||
dirty: RED.nodes.dirty()
|
||||
})
|
||||
RED.nodes.dirty(true);
|
||||
RED.view.redraw(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); })
|
||||
@ -783,7 +867,8 @@ RED.view.tools = (function() {
|
||||
RED.actions.add("core:distribute-selection-horizontally", function() { distributeSelection('h') })
|
||||
RED.actions.add("core:distribute-selection-vertically", function() { distributeSelection('v') })
|
||||
|
||||
|
||||
RED.actions.add("core:wire-series-of-nodes", function() { wireSeriesOfNodes() })
|
||||
RED.actions.add("core:wire-node-to-multiple", function() { wireNodeToMultiple() })
|
||||
|
||||
// RED.actions.add("core:add-node", function() { addNode() })
|
||||
},
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,13 @@
|
||||
stroke-dasharray: 10 5;
|
||||
}
|
||||
|
||||
.nr-ui-view-slice {
|
||||
stroke-width: 1px;
|
||||
stroke: $view-lasso-stroke;
|
||||
fill: none;
|
||||
stroke-dasharray: 10 5;
|
||||
}
|
||||
|
||||
.node_label_italic, // deprecated: use red-ui-flow-node-label-italic
|
||||
.red-ui-flow-node-label-italic {
|
||||
font-style: italic;
|
||||
|
@ -204,6 +204,28 @@
|
||||
font-style: italic;
|
||||
color: $form-placeholder-color;
|
||||
}
|
||||
.red-ui-search-history {
|
||||
button {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 7px;
|
||||
}
|
||||
|
||||
&:hover button {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
.red-ui-search-historyHeader {
|
||||
button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 7px;
|
||||
}
|
||||
}
|
||||
.red-ui-search-history-result {
|
||||
|
||||
}
|
||||
|
||||
.red-ui-search-result-action {
|
||||
color: $primary-text-color;
|
||||
|
@ -78,6 +78,12 @@
|
||||
}
|
||||
.red-ui-tourGuide-popover-description {
|
||||
padding: 10px 20px 5px;
|
||||
|
||||
img {
|
||||
max-height: 150px;
|
||||
border: 1px solid var(--red-ui-tourGuide-border);
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
.red-ui-tourGuide-popover-full {
|
||||
.red-ui-tourGuide-popover-description {
|
||||
|
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/delete-repair.gif
vendored
Normal file
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/delete-repair.gif
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/detach-repair.gif
vendored
Normal file
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/detach-repair.gif
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/slice.gif
vendored
Normal file
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/slice.gif
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 81 KiB |
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/subflow-labels.png
vendored
Normal file
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/subflow-labels.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
@ -1,12 +1,12 @@
|
||||
export default {
|
||||
version: "2.1.0",
|
||||
version: "2.2.0",
|
||||
steps: [
|
||||
{
|
||||
titleIcon: "fa fa-map-o",
|
||||
title: {
|
||||
"en-US": "Welcome to Node-RED 2.1!",
|
||||
"ja": "Node-RED 2.1へようこそ!"
|
||||
},
|
||||
"en-US": "Welcome to Node-RED 2.2!",
|
||||
"ja": "Node-RED 2.2へようこそ!"
|
||||
},
|
||||
description: {
|
||||
"en-US": "Let's take a moment to discover the new features in this release.",
|
||||
"ja": "本リリースの新機能を見つけてみましょう。"
|
||||
@ -14,215 +14,142 @@ export default {
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "A new Tour Guide",
|
||||
"ja": "新しいツアーガイド"
|
||||
"en-US": "Search history",
|
||||
"ja": "検索履歴"
|
||||
},
|
||||
description: {
|
||||
"en-US": "<p>First, as you've already found, we now have this tour of new features. We'll only show the tour the first time you open the editor for each new version of Node-RED.</p>" +
|
||||
"<p>You can choose not to see this tour in the future by disabling it under the View tab of User Settings.</p>",
|
||||
"ja": "<p>最初に、既に見つけている様に、新機能の本ツアーがあります。本ツアーは、新バージョンのNode-REDフローエディタを初めて開いた時のみ表示されます。</p>" +
|
||||
"<p>ユーザ設定の表示タブの中で、この機能を無効化することで、本ツアーを表示しないようにすることもできます。</p>"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "New Edit menu",
|
||||
"ja": "新しい編集メニュー"
|
||||
"en-US": "<p>The Search dialog now keeps a history of your searches, making it easier to go back to a previous search.</p>",
|
||||
"ja": "<p>検索ダイアログが検索履歴を保持するようになりました。これによって、過去の検索に戻りやすくなりました。</p>"
|
||||
},
|
||||
prepare() {
|
||||
$("#red-ui-header-button-sidemenu").trigger("click");
|
||||
$("#menu-item-edit-menu").parent().addClass("open");
|
||||
},
|
||||
complete() {
|
||||
$("#menu-item-edit-menu").parent().removeClass("open");
|
||||
},
|
||||
element: "#menu-item-edit-menu-submenu",
|
||||
interactive: false,
|
||||
direction: "left",
|
||||
description: {
|
||||
"en-US": "<p>The main menu has been updated with a new 'Edit' section. This includes all of the familar options, like cut/paste and undo/redo.</p>" +
|
||||
"<p>The menu now displays keyboard shortcuts for the options.</p>",
|
||||
"ja": "<p>メインメニューに「編集」セクションが追加されました。本セクションには、切り取り/貼り付けや、変更操作を戻す/やり直しの様な使い慣れたオプションが含まれています。</p>" +
|
||||
"<p>本メニューには、オプションのためのキーボードショートカットも表示されるようになりました。</p>"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Arranging nodes",
|
||||
"ja": "ノードの配置"
|
||||
},
|
||||
prepare() {
|
||||
$("#red-ui-header-button-sidemenu").trigger("click");
|
||||
$("#menu-item-arrange-menu").parent().addClass("open");
|
||||
},
|
||||
complete() {
|
||||
$("#menu-item-arrange-menu").parent().removeClass("open");
|
||||
},
|
||||
element: "#menu-item-arrange-menu-submenu",
|
||||
interactive: false,
|
||||
direction: "left",
|
||||
description: {
|
||||
"en-US": "<p>The new 'Arrange' section of the menu provides new options to help arrange your nodes. You can align them to a common edge, spread them out evenly or change their order.</p>",
|
||||
"ja": "<p>メニューの新しい「配置」セクションには、ノードの配置を助ける新しいオプションが提供されています。ノードの端を揃えたり、均等に配置したり、表示順序を変更したりできます。</p>"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Hiding tabs",
|
||||
"ja": "タブの非表示"
|
||||
},
|
||||
element: "#red-ui-workspace-tabs > li.active",
|
||||
description: {
|
||||
"en-US": '<p>Tabs can now be hidden by clicking their <i class="fa fa-eye-slash"></i> icon.</p><p>The Info Sidebar will still list all of your tabs, and tell you which ones are currently hidden.',
|
||||
"ja": '<p><i class="fa fa-eye-slash"></i> アイコンをクリックすることで、タブを非表示にできます。</p><p>情報サイドバーには、全てのタブが一覧表示されており、現在非表示になっているタブを確認できます。'
|
||||
},
|
||||
interactive: false,
|
||||
prepare() {
|
||||
$("#red-ui-workspace-tabs > li.active .red-ui-tab-close").css("display","block");
|
||||
},
|
||||
complete() {
|
||||
$("#red-ui-workspace-tabs > li.active .red-ui-tab-close").css("display","");
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Tab menu",
|
||||
"ja": "タブメニュー"
|
||||
},
|
||||
element: "#red-ui-workspace-tabs-menu",
|
||||
description: {
|
||||
"en-US": "<p>The new tab menu also provides lots of new options for your tabs.</p>",
|
||||
"ja": "<p>新しいタブメニューには、タブに関する沢山の新しいオプションが提供されています。</p>"
|
||||
},
|
||||
interactive: false,
|
||||
direction: "left",
|
||||
prepare() {
|
||||
$("#red-ui-workspace > .red-ui-tabs > .red-ui-tabs-menu a").trigger("click");
|
||||
},
|
||||
complete() {
|
||||
$(document).trigger("click");
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Flow and Group level environment variables",
|
||||
"ja": "フローとグループの環境変数"
|
||||
},
|
||||
element: "#red-ui-workspace-tabs > li.active",
|
||||
interactive: false,
|
||||
description: {
|
||||
"en-US": "<p>Flows and Groups can now have their own environment variables that can be referenced by nodes inside them.</p>",
|
||||
"ja": "<p>フローとグループには、内部のノードから参照できる環境変数を設定できるようになりました。</p>"
|
||||
}
|
||||
},
|
||||
{
|
||||
element: "#red-ui-search .red-ui-searchBox-form",
|
||||
prepare(done) {
|
||||
RED.editor.editFlow(RED.nodes.workspace(RED.workspaces.active()),"editor-tab-envProperties");
|
||||
setTimeout(done,700);
|
||||
RED.search.show();
|
||||
setTimeout(done,400);
|
||||
},
|
||||
complete() {
|
||||
RED.search.hide();
|
||||
},
|
||||
element: "#red-ui-tab-editor-tab-envProperties-link-button",
|
||||
description: {
|
||||
"en-US": "<p>Their edit dialogs have a new Environment Variables section.</p>",
|
||||
"ja": "<p>編集ダイアログに環境変数セクションが追加されました。</p>"
|
||||
}
|
||||
},
|
||||
{
|
||||
element: ".node-input-env-container-row",
|
||||
direction: "left",
|
||||
title: {
|
||||
"en-US": "Remembering Zoom & Position",
|
||||
"ja": "拡大/縮小のレベルや位置を記憶"
|
||||
},
|
||||
description: {
|
||||
"en-US": '<p>The environment variables are listed in this table and new ones can be added by clicking the <i class="fa fa-plus"></i> button.</p>',
|
||||
"ja": '<p>この表に環境変数が一覧表示されており、<i class="fa fa-plus"></i>ボタンをクリックすることで新しい変数を追加できます。</p>'
|
||||
"en-US": "<p>The editor has new options to restore the zoom level and scroll position when reloading the editor.</p>",
|
||||
"ja": "<p>エディタを再読み込みした時に、拡大/縮小のレベルやスクロール位置を復元するための新しいオプションを利用できます。</p>"
|
||||
},
|
||||
element: function() { return $("#user-settings-view-store-position").parent()},
|
||||
prepare(done) {
|
||||
RED.actions.invoke("core:show-user-settings")
|
||||
setTimeout(done,400);
|
||||
},
|
||||
complete(done) {
|
||||
$("#node-dialog-cancel").trigger("click");
|
||||
setTimeout(done,500);
|
||||
$("#node-dialog-ok").trigger("click");
|
||||
setTimeout(done,400);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "New wiring actions",
|
||||
"ja": "新しいワイヤー操作"
|
||||
},
|
||||
// image: "images/",
|
||||
description: {
|
||||
"en-US": `<p>A pair of new actions have been added to help with wiring nodes together:</p>
|
||||
<ul>
|
||||
<li><b><code>Wire Series Of Nodes</code></b> - adds a wire (if necessary) between each pair of nodes in the order they were selected.</li>
|
||||
<li><b><code>Wire Node To Multiple</code></b> - wires the first node selected to all of the other selected nodes.</li>
|
||||
</ul>
|
||||
<p>Actions can be accessed from the Action List in the main menu.</p>`,
|
||||
"ja": `<p>ノード接続を支援する2つの新しい操作が追加されました:</p>
|
||||
<ul>
|
||||
<li><b><code>Wire Series Of Nodes</code></b> - ノードを選択した順序で、各ノードのペアの間にワイヤーを(必要に応じて)追加します。</li>
|
||||
<li><b><code>Wire Node To Multiple</code></b> - 最初に選択したノードから、他の選択した全てのノードに対して、ワイヤーを追加します。</li>
|
||||
</ul>
|
||||
<p>メインメニュー内の動作一覧から、これらの操作を利用できます。</p>`
|
||||
},
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Deleting nodes and reconnecting wires",
|
||||
"ja": "ノードの削除とワイヤーの再接続"
|
||||
},
|
||||
image: "images/delete-repair.gif",
|
||||
description: {
|
||||
"en-US": `<p>It is now possible to delete a selection of nodes and automatically repair the wiring behind them.</p>
|
||||
<p>This is really useful if you want to remove a node from the middle of the flow.</p>
|
||||
<p>Hold the Ctrl (or Cmd) key when you press Delete and the nodes will be gone and the wires repaired.</p>
|
||||
`,
|
||||
"ja": `<p>選択したノードを削除した後、その背後にあるワイヤーを自動的に修復できるようになりました。</p>
|
||||
<p>これは、フローの中からノードを削除する時に、とても便利に使えます。</p>
|
||||
<p>Ctrl (またはCmd)キーを押しながらDeleteキーを押すと、ノードがなくなり、ワイヤーが修復されます。</p>
|
||||
`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Link Call node added",
|
||||
"ja": "Link Callノードを追加"
|
||||
"en-US": "Detaching nodes from a flow",
|
||||
"ja": "フローからノードの切り離し"
|
||||
},
|
||||
prepare(done) {
|
||||
this.paletteWasClosed = $("#red-ui-main-container").hasClass("red-ui-palette-closed");
|
||||
RED.actions.invoke("core:toggle-palette",true)
|
||||
$('[data-palette-type="link call"]')[0].scrollIntoView({block:"center"})
|
||||
setTimeout(done,100);
|
||||
},
|
||||
element: '[data-palette-type="link call"]',
|
||||
direction: "right",
|
||||
image: "images/detach-repair.gif",
|
||||
description: {
|
||||
"en-US": "<p>The <code>Link Call</code> node lets you call another flow that begins with a <code>Link In</code> node and get the result back when the message reaches a <code>Link Out</code> node.</p>",
|
||||
"ja": "<p><code>Link Call</code>ノードを用いることで、<code>Link In</code>ノードから始まるフローを呼び出し、<code>Link Out</code>ノードに到達した時に、結果を取得できます。</p>"
|
||||
"en-US": `<p>If you want to remove a node from a flow without deleting it,
|
||||
you can use the <b><code>Detach Selected Nodes</code></b> action.</p>
|
||||
<p>The nodes will be removed from their flow, the wiring repaired behind them, and then attached to the mouse
|
||||
so you can drop them wherever you want in the workspace.</p>
|
||||
<p>There isn't a default keyboard shortcut assigned for this new action, but
|
||||
you can add your own via the Keyboard pane of the main Settings dialog.</p>`,
|
||||
"ja": `<p>ノードを削除することなく、フローからノードを除きたい場合は、<b><code>Detach Selected Nodes</code></b>操作を利用できます。</p>
|
||||
<p>フローからノードが除かれた後、背後のワイヤーが修復され、ノードはマウスポインタにつながります。そのため、ワークスペースの好きな所にノードを配置できます。</p>
|
||||
<p>この新しい操作に対して、デフォルトのキーボードショートカットは登録されていませんが、メイン設定ダイアログのキーボード設定から追加できます。</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "MQTT nodes support dynamic connections",
|
||||
"ja": "MQTTノードが動的接続をサポート"
|
||||
"en-US": "More wiring tricks",
|
||||
"ja": "その他のワイヤー操作"
|
||||
},
|
||||
prepare(done) {
|
||||
$('[data-palette-type="mqtt out"]')[0].scrollIntoView({block:"center"})
|
||||
setTimeout(done,100);
|
||||
},
|
||||
element: '[data-palette-type="mqtt out"]',
|
||||
direction: "right",
|
||||
image: "images/slice.gif",
|
||||
description: {
|
||||
"en-US": '<p>The <code>MQTT</code> nodes now support creating their connections and subscriptions dynamically.</p>',
|
||||
"ja": '<p><code>MQTT</code>ノードは、動的な接続や購読ができるようになりました。</p>'
|
||||
},
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "File nodes renamed",
|
||||
"ja": "ファイルノードの名前変更"
|
||||
},
|
||||
prepare(done) {
|
||||
$('[data-palette-type="file"]')[0].scrollIntoView({block:"center"});
|
||||
setTimeout(done,100);
|
||||
},
|
||||
complete() {
|
||||
if (this.paletteWasClosed) {
|
||||
RED.actions.invoke("core:toggle-palette",false)
|
||||
}
|
||||
},
|
||||
element: '[data-palette-type="file"]',
|
||||
direction: "right",
|
||||
description: {
|
||||
"en-US": "<p>The file nodes have been renamed to make it clearer which node does what.</p>",
|
||||
"ja": "<p>fileノードの名前が変更され、どのノードが何を行うかが明確になりました。</p>"
|
||||
"en-US": `<p>A couple more wiring tricks to share.</p>
|
||||
<p>You can now select multiple wires by holding the Ctrl (or Cmd) key
|
||||
when clicking on a wire. This makes it easier to delete multiple wires in one go.</p>
|
||||
<p>If you hold the Ctrl (or Cmd) key, then click and drag with the right-hand mouse button,
|
||||
you can slice through wires to remove them.</p>`,
|
||||
"ja": `<p>その他のいくつかのワイヤー操作</p>
|
||||
<p>Ctrl (またはCmd)キーを押しながらワイヤーをクリックすることで、複数のワイヤーを選択できるようになりました。これによって、複数のワイヤーを一度に削除することが簡単になりました。</p>
|
||||
<p>Ctrl (またはCmd)キーを押しながら、マウスの右ボタンを用いてドラッグすると、ワイヤーを切って削除できます。</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Deep copy option on Change node",
|
||||
"ja": "Changeノードのディープコピーオプション"
|
||||
},
|
||||
prepare(done) {
|
||||
var def = RED.nodes.getType('change');
|
||||
RED.editor.edit({id:"test",type:"change",rules:[{t:"set",p:"payload",pt:"msg", tot:"msg",to:"anotherProperty"}],_def:def, _:def._});
|
||||
setTimeout(done,700);
|
||||
},
|
||||
complete(done) {
|
||||
$("#node-dialog-cancel").trigger("click");
|
||||
setTimeout(done,500);
|
||||
},
|
||||
element: function() {
|
||||
return $(".node-input-rule-property-deepCopy").next();
|
||||
"en-US": "Subflow Output Labels",
|
||||
"ja": "サブフローの出力ラベル"
|
||||
},
|
||||
image: "images/subflow-labels.png",
|
||||
description: {
|
||||
"en-US": "<p>The Set rule has a new option to create a deep copy of the value. This ensures a complete copy is made, rather than using a reference.</p>",
|
||||
"ja": "<p>値を代入に、値のディープコピーを作成するオプションが追加されました。これによって参照ではなく、完全なコピーが作成されます。</p>"
|
||||
}
|
||||
"en-US": "<p>If a subflow has labels set for its outputs, they now get shown on the ports within the subflow template view.</p>",
|
||||
"ja": "<p>サブフローの出力にラベルが設定されている場合、サブフローテンプレート画面内のポートにラベルが表示されるようになりました。</p>"
|
||||
},
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "And that's not all...",
|
||||
"ja": "これが全てではありません..."
|
||||
"en-US": "Node Updates",
|
||||
"ja": "ノードの更新"
|
||||
},
|
||||
// image: "images/",
|
||||
description: {
|
||||
"en-US": "<p>There are many more smaller changes, including:</p><ul><li>Auto-complete suggestions in the <code>msg</code> TypedInput.</li><li>Support for <code>msg.resetTimeout</code> in the <code>Join</code> node.</li><li>Pushing messages to the front of the queue in the <code>Delay</code> node's rate limiting mode.</li><li>An optional second output on the <code>Delay</code> node for rate limited messages.</li></ul>",
|
||||
"ja": "<p>以下の様な小さな変更が沢山あります:</p><ul><li><code>msg</code> TypedInputの自動補完提案</li><li><code>Join</code>ノードで<code>msg.resetTimeout</code>のサポート</li><li><code>Delay</code>ノードの流量制御モードにおいて先頭メッセージをキューに追加</li><li><code>Delay</code>ノードで流量制限されたメッセージ向けの任意の2つ目の出力</li></ul>"
|
||||
"en-US": `<ul>
|
||||
<li>The JSON node will now handle parsing Buffer payloads</li>
|
||||
<li>The TCP Client nodes support TLS connections</li>
|
||||
<li>The WebSocket node allows you to specify a sub-protocol when connecting</li>
|
||||
</ul>`,
|
||||
"ja": `<ul>
|
||||
<li>JSONノードが、バッファ形式のペイロードを解析できるようになりました。</li>
|
||||
<li>TCPクライアントノードが、TLS接続をサポートしました。</li>
|
||||
<li>WebSocketノードで、接続時にサブプロトコルを指定できるようになりました。</li>
|
||||
</ul>`
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -31,12 +31,12 @@ interface NodeStatus {
|
||||
}
|
||||
|
||||
declare class node {
|
||||
/**
|
||||
* Send 1 or more messages asynchronously
|
||||
* @param {object | object[]} msg The msg object
|
||||
* @param {Boolean} [clone=true] Flag to indicate the `msg` should be cloned. Default = `true`
|
||||
* @see node-red documentation [writing-functions: sending messages asynchronously](https://nodered.org/docs/user-guide/writing-functions#sending-messages-asynchronously)
|
||||
*/
|
||||
/**
|
||||
* Send 1 or more messages asynchronously
|
||||
* @param {object | object[]} msg The msg object
|
||||
* @param {Boolean} [clone=true] Flag to indicate the `msg` should be cloned. Default = `true`
|
||||
* @see node-red documentation [writing-functions: sending messages asynchronously](https://nodered.org/docs/user-guide/writing-functions#sending-messages-asynchronously)
|
||||
*/
|
||||
static send(msg:object|object[], clone?:Boolean): void;
|
||||
/** Inform runtime this instance has completed its operation */
|
||||
static done();
|
||||
@ -57,11 +57,13 @@ declare class node {
|
||||
*/
|
||||
static status(status:string|boolean|number);
|
||||
/** the id of this node */
|
||||
public readonly id:string;
|
||||
public static readonly id:string;
|
||||
/** the name of this node */
|
||||
public readonly name:string;
|
||||
public static readonly name:string;
|
||||
/** the path identifier for this node */
|
||||
public static readonly path:string;
|
||||
/** the number of outputs of this node */
|
||||
public readonly outputCount:number;
|
||||
public static readonly outputCount:number;
|
||||
}
|
||||
declare class context {
|
||||
/**
|
||||
@ -93,27 +95,27 @@ declare class context {
|
||||
/**
|
||||
* Set one or multiple values in context (synchronous).
|
||||
* @param name - Name (or array of names) to set in context
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
*/
|
||||
static set(name: string | string[], value?: any | any[]);
|
||||
/**
|
||||
* Set one or multiple values in context (asynchronous).
|
||||
* @param name - Name (or array of names) to set in context
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param callback - (optional) Callback function (`(err) => {}`)
|
||||
*/
|
||||
static set(name: string | string[], value?: any | any[], callback?: Function);
|
||||
/**
|
||||
* Set one or multiple values in context (synchronous).
|
||||
* @param name - Name (or array of names) to set in context
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param store - (optional) Name of context store
|
||||
*/
|
||||
static set(name: string | string[], value?: any | any[], store?: string);
|
||||
/**
|
||||
* Set one or multiple values in context (asynchronous).
|
||||
* @param name - Name (or array of names) to set in context
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param store - (optional) Name of context store
|
||||
* @param callback - (optional) Callback function (`(err) => {}`)
|
||||
*/
|
||||
@ -158,27 +160,27 @@ declare class flow {
|
||||
/**
|
||||
* Set one or multiple values in context (synchronous).
|
||||
* @param name - Name (or array of names) to set in context
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
*/
|
||||
static set(name: string | string[], value?: any | any[]);
|
||||
/**
|
||||
* Set one or multiple values in context (asynchronous).
|
||||
* @param name - Name (or array of names) to set in context
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param callback - (optional) Callback function (`(err) => {}`)
|
||||
*/
|
||||
static set(name: string | string[], value?: any | any[], callback?: Function);
|
||||
/**
|
||||
* Set one or multiple values in context (synchronous).
|
||||
* @param name - Name (or array of names) to set in context
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param store - (optional) Name of context store
|
||||
*/
|
||||
static set(name: string | string[], value?: any | any[], store?: string);
|
||||
/**
|
||||
* Set one or multiple values in context (asynchronous).
|
||||
* @param name - Name (or array of names) to set in context
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param store - (optional) Name of context store
|
||||
* @param callback - (optional) Callback function (`(err) => {}`)
|
||||
*/
|
||||
@ -225,32 +227,32 @@ declare class global {
|
||||
/**
|
||||
* Set one or multiple values in context (synchronous).
|
||||
* @param name - Name (or array of names) to set in context
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
*/
|
||||
static set(name: string | string[], value?: any | any[]);
|
||||
/**
|
||||
* Set one or multiple values in context (asynchronous).
|
||||
* @param name - Name (or array of names) to set in context
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param callback - (optional) Callback function (`(err) => {}`)
|
||||
*/
|
||||
static set(name: string | string[], value?: any | any[], callback?: Function);
|
||||
/**
|
||||
* Set one or multiple values in context (synchronous).
|
||||
* @param name - Name (or array of names) to set in context
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param store - (optional) Name of context store
|
||||
*/
|
||||
static set(name: string | string[], value?: any | any[], store?: string);
|
||||
/**
|
||||
* Set one or multiple values in context (asynchronous).
|
||||
* @param name - Name (or array of names) to set in context
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param value - The value (or array of values) to store in context. If the value(s) are null/undefined, the context item(s) will be removed.
|
||||
* @param store - (optional) Name of context store
|
||||
* @param callback - (optional) Callback function (`(err) => {}`)
|
||||
*/
|
||||
static set(name: string | string[], value?: any | any[], store?: string, callback?: Function);
|
||||
|
||||
|
||||
/** Get an array of the keys in the context store */
|
||||
static keys(): Array<string>;
|
||||
/** Get an array of the keys in the context store */
|
||||
|
@ -1,4 +1,4 @@
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
|
@ -75,16 +75,12 @@ module.exports = function(RED) {
|
||||
node.repeaterSetup = function () {
|
||||
if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
|
||||
this.repeat = this.repeat * 1000;
|
||||
if (RED.settings.verbose) {
|
||||
this.log(RED._("inject.repeat", this));
|
||||
}
|
||||
this.debug(RED._("inject.repeat", this));
|
||||
this.interval_id = setInterval(function() {
|
||||
node.emit("input", {});
|
||||
}, this.repeat);
|
||||
} else if (this.crontab) {
|
||||
if (RED.settings.verbose) {
|
||||
this.log(RED._("inject.crontab", this));
|
||||
}
|
||||
this.debug(RED._("inject.crontab", this));
|
||||
this.cronjob = scheduleTask(this.crontab,() => { node.emit("input", {})});
|
||||
}
|
||||
};
|
||||
@ -148,10 +144,8 @@ module.exports = function(RED) {
|
||||
}
|
||||
if (this.interval_id != null) {
|
||||
clearInterval(this.interval_id);
|
||||
if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
|
||||
} else if (this.cronjob != null) {
|
||||
this.cronjob.stop();
|
||||
if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
|
||||
delete this.cronjob;
|
||||
}
|
||||
};
|
||||
|
@ -112,6 +112,7 @@ module.exports = function(RED) {
|
||||
"var node = {"+
|
||||
"id:__node__.id,"+
|
||||
"name:__node__.name,"+
|
||||
"path:__node__.path,"+
|
||||
"outputCount:__node__.outputCount,"+
|
||||
"log:__node__.log,"+
|
||||
"error:__node__.error,"+
|
||||
@ -163,6 +164,7 @@ module.exports = function(RED) {
|
||||
__node__: {
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
path: node._path,
|
||||
outputCount: node.outputs,
|
||||
log: function() {
|
||||
node.log.apply(node, arguments);
|
||||
@ -344,6 +346,7 @@ module.exports = function(RED) {
|
||||
var node = {
|
||||
id:__node__.id,
|
||||
name:__node__.name,
|
||||
path:__node__.path,
|
||||
outputCount:__node__.outputCount,
|
||||
log:__node__.log,
|
||||
error:__node__.error,
|
||||
@ -366,6 +369,7 @@ module.exports = function(RED) {
|
||||
var node = {
|
||||
id:__node__.id,
|
||||
name:__node__.name,
|
||||
path:__node__.path,
|
||||
outputCount:__node__.outputCount,
|
||||
log:__node__.log,
|
||||
error:__node__.error,
|
||||
|
@ -86,7 +86,7 @@ module.exports = function(RED) {
|
||||
});
|
||||
var cmd = arg.shift();
|
||||
/* istanbul ignore else */
|
||||
if (RED.settings.verbose) { node.log(cmd+" ["+arg+"]"); }
|
||||
node.debug(cmd+" ["+arg+"]");
|
||||
child = spawn(cmd,arg,node.spawnOpt);
|
||||
node.status({fill:"blue",shape:"dot",text:"pid:"+child.pid});
|
||||
var unknownCommand = (child.pid === undefined);
|
||||
@ -136,7 +136,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
else {
|
||||
/* istanbul ignore else */
|
||||
if (RED.settings.verbose) { node.log(arg); }
|
||||
node.debug(arg);
|
||||
child = exec(arg, node.execOpt, function (error, stdout, stderr) {
|
||||
var msg2, msg3;
|
||||
delete msg.payload;
|
||||
@ -155,7 +155,7 @@ module.exports = function(RED) {
|
||||
if (error.signal) { msg3.payload.signal = error.signal; }
|
||||
if (error.code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); }
|
||||
else { node.status({fill:"red",shape:"dot",text:"error:"+error.code}); }
|
||||
if (RED.settings.verbose) { node.log('error:' + error); }
|
||||
node.debug('error:' + error);
|
||||
}
|
||||
else if (node.oldrc === "false") {
|
||||
msg3 = RED.util.cloneMessage(msg);
|
||||
|
@ -288,7 +288,7 @@ module.exports = function(RED) {
|
||||
//TODO: delete msg.responseTopic - to prevent it being resent?
|
||||
}
|
||||
}
|
||||
topicOK = topicOK && !/[\+#]/.test(msg.topic);
|
||||
topicOK = topicOK && !/[\+#\b\f\n\r\t\v\0]/.test(msg.topic);
|
||||
|
||||
if (topicOK) {
|
||||
node.brokerConn.publish(msg, done); // send the message
|
||||
@ -730,12 +730,18 @@ module.exports = function(RED) {
|
||||
node.client.on("reconnect", function() {
|
||||
setStatusConnecting(node, true);
|
||||
});
|
||||
//TODO: what to do with this event? Anything? Necessary?
|
||||
//Broker Disconnect - V5 event
|
||||
node.client.on("disconnect", function(packet) {
|
||||
//Emitted after receiving disconnect packet from broker. MQTT 5.0 feature.
|
||||
var rc = packet && packet.properties && packet.properties.reasonString;
|
||||
var rc = packet && packet.properties && packet.reasonCode;
|
||||
//TODO: If keeping this event, do we use these? log these?
|
||||
const rc = (packet && packet.properties && packet.reasonCode) || packet.reasonCode;
|
||||
const rs = packet && packet.properties && packet.properties.reasonString || "";
|
||||
const details = {
|
||||
broker: (node.clientid?node.clientid+"@":"")+node.brokerurl,
|
||||
reasonCode: rc,
|
||||
reasonString: rs
|
||||
}
|
||||
node.log(RED._("mqtt.state.broker-disconnected", details));
|
||||
setStatusDisconnected(node, true);
|
||||
});
|
||||
// Register disconnect handlers
|
||||
node.client.on('close', function () {
|
||||
|
@ -264,7 +264,7 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
if (opts.headers.hasOwnProperty('cookie')) {
|
||||
var cookies = cookie.parse(opts.headers.cookie, {decode:String});
|
||||
for (var name in cookies) {
|
||||
opts.cookieJar.setCookie(cookie.serialize(name, cookies[name], {encode:String}), url, {ignoreError: true});
|
||||
opts.cookieJar.setCookieSync(cookie.serialize(name, cookies[name], {encode:String}), url, {ignoreError: true});
|
||||
}
|
||||
delete opts.headers.cookie;
|
||||
}
|
||||
@ -277,13 +277,13 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
} else if (typeof msg.cookies[name] === 'object') {
|
||||
if(msg.cookies[name].encode === false){
|
||||
// If the encode option is false, the value is not encoded.
|
||||
opts.cookieJar.setCookie(cookie.serialize(name, msg.cookies[name].value, {encode: String}), url, {ignoreError: true});
|
||||
opts.cookieJar.setCookieSync(cookie.serialize(name, msg.cookies[name].value, {encode: String}), url, {ignoreError: true});
|
||||
} else {
|
||||
// The value is encoded by encodeURIComponent().
|
||||
opts.cookieJar.setCookie(cookie.serialize(name, msg.cookies[name].value), url, {ignoreError: true});
|
||||
opts.cookieJar.setCookieSync(cookie.serialize(name, msg.cookies[name].value), url, {ignoreError: true});
|
||||
}
|
||||
} else {
|
||||
opts.cookieJar.setCookie(cookie.serialize(name, msg.cookies[name]), url, {ignoreError: true});
|
||||
opts.cookieJar.setCookieSync(cookie.serialize(name, msg.cookies[name]), url, {ignoreError: true});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -177,7 +177,8 @@
|
||||
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/)},
|
||||
tls: {type:"tls-config",required: false},
|
||||
wholemsg: {value:"false"},
|
||||
hb: {value: "", validate: RED.validators.number(/*blank allowed*/true) }
|
||||
hb: {value: "", validate: RED.validators.number(/*blank allowed*/true) },
|
||||
subprotocol: {value:"",required: false}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:0,
|
||||
@ -265,7 +266,10 @@
|
||||
<label for="node-config-input-tls" data-i18n="httpin.tls-config"></label>
|
||||
<input type="text" id="node-config-input-tls">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-subprotocol"><i class="fa fa-tag"></i> <span data-i18n="websocket.label.subprotocol"></span></label>
|
||||
<input type="text" id="node-config-input-subprotocol">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-wholemsg" data-i18n="websocket.sendrec"></label>
|
||||
<select type="text" id="node-config-input-wholemsg" style="width: 70%;">
|
||||
|
@ -46,6 +46,12 @@ module.exports = function(RED) {
|
||||
|
||||
// Store local copies of the node configuration (as defined in the .html)
|
||||
node.path = n.path;
|
||||
if (typeof n.subprotocol === "string") {
|
||||
// Split the string on comma and trim each result
|
||||
node.subprotocol = n.subprotocol.split(",").map(v => v.trim())
|
||||
} else {
|
||||
node.subprotocol = [];
|
||||
}
|
||||
node.wholemsg = (n.wholemsg === "true");
|
||||
|
||||
node._inputNodes = []; // collection of nodes that want to receive events
|
||||
@ -92,7 +98,7 @@ module.exports = function(RED) {
|
||||
tlsNode.addTLSOptions(options);
|
||||
}
|
||||
}
|
||||
var socket = new ws(node.path,options);
|
||||
var socket = new ws(node.path,node.subprotocol,options);
|
||||
socket.setMaxListeners(0);
|
||||
node.server = socket; // keep for closing
|
||||
handleConnection(socket);
|
||||
|
@ -23,9 +23,17 @@
|
||||
</select>
|
||||
<span data-i18n="tcpin.label.port"></span> <input type="text" id="node-input-port" style="width:65px">
|
||||
</div>
|
||||
<div class="form-row hidden" id="node-input-host-row" style="padding-left: 110px;">
|
||||
<div class="form-row hidden" id="node-input-host-row" style="padding-left:110px;">
|
||||
<span data-i18n="tcpin.label.host"></span> <input type="text" id="node-input-host" placeholder="localhost" style="width: 60%;">
|
||||
</div>
|
||||
<div class="form-row" id="node-input-tls-enable">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-usetls" style="display: inline-block; width:auto; vertical-align:top;">
|
||||
<label for="node-input-usetls" style="width:auto" data-i18n="httpin.use-tls"></label>
|
||||
<div id="node-row-tls" class="hide">
|
||||
<label style="width:auto; margin-left:20px; margin-right:10px;" for="node-input-tls"><span data-i18n="httpin.tls-config"></span></label><input type="text" style="width: 300px" id="node-input-tls">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label><i class="fa fa-sign-out"></i> <span data-i18n="tcpin.label.output"></span></label>
|
||||
@ -42,7 +50,7 @@
|
||||
</div>
|
||||
|
||||
<div id="node-row-newline" class="form-row hidden" style="padding-left:110px;">
|
||||
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;">
|
||||
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
@ -58,17 +66,18 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('tcp in',{
|
||||
category: 'network',
|
||||
color:"Silver",
|
||||
color: "Silver",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
server: {value:"server",required:true},
|
||||
host: {value:"",validate:function(v) { return (this.server == "server")||v.length > 0;} },
|
||||
port: {value:"",required:true,validate:RED.validators.number()},
|
||||
server: {value:"server", required:true},
|
||||
host: {value:"", validate:function(v) { return (this.server == "server")||v.length > 0;} },
|
||||
port: {value:"", required:true, validate:RED.validators.number()},
|
||||
datamode:{value:"stream"},
|
||||
datatype:{value:"buffer"},
|
||||
newline:{value:""},
|
||||
topic: {value:""},
|
||||
base64: {/*deprecated*/ value:false,required:true}
|
||||
base64: {/*deprecated*/ value:false, required:true},
|
||||
tls: {type:"tls-config", value:'', required:false}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
@ -77,7 +86,7 @@
|
||||
return this.name || "tcp:"+(this.host?this.host+":":"")+this.port;
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
return this.name ? "node_label_italic" : "";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var updateOptions = function() {
|
||||
@ -103,6 +112,27 @@
|
||||
$("#node-input-server").change(updateOptions);
|
||||
$("#node-input-datatype").change(updateOptions);
|
||||
$("#node-input-datamode").change(updateOptions);
|
||||
function updateTLSOptions() {
|
||||
if ($("#node-input-usetls").is(':checked')) {
|
||||
$("#node-row-tls").show();
|
||||
} else {
|
||||
$("#node-row-tls").hide();
|
||||
}
|
||||
}
|
||||
if (this.tls) {
|
||||
$('#node-input-usetls').prop('checked', true);
|
||||
} else {
|
||||
$('#node-input-usetls').prop('checked', false);
|
||||
}
|
||||
updateTLSOptions();
|
||||
$("#node-input-usetls").on("click",function() {
|
||||
updateTLSOptions();
|
||||
});
|
||||
},
|
||||
oneditsave: function() {
|
||||
if (!$("#node-input-usetls").is(':checked')) {
|
||||
$("#node-input-tls").val("_ADD_");
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -123,6 +153,15 @@
|
||||
<span data-i18n="tcpin.label.host"></span> <input type="text" id="node-input-host" style="width: 60%;">
|
||||
</div>
|
||||
|
||||
<div class="form-row" id="node-input-tls-enable">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-usetls" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-usetls" style="width: auto" data-i18n="httpin.use-tls"></label>
|
||||
<div id="node-row-tls" class="hide">
|
||||
<label style="width: auto; margin-left: 20px; margin-right: 10px;" for="node-input-tls"><span data-i18n="httpin.tls-config"></span></label><input type="text" style="width: 300px" id="node-input-tls">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row hidden" id="node-input-end-row">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-end" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
@ -144,14 +183,15 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('tcp out',{
|
||||
category: 'network',
|
||||
color:"Silver",
|
||||
color: "Silver",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
host: {value:"",validate:function(v) { return (this.beserver != "client")||v.length > 0;} },
|
||||
port: {value:"",validate:function(v) { return (this.beserver == "reply")||RED.validators.number()(v); } },
|
||||
beserver: {value:"client",required:true},
|
||||
base64: {value:false,required:true},
|
||||
end: {value:false,required:true},
|
||||
name: {value:""}
|
||||
beserver: {value:"client", required:true},
|
||||
base64: {value:false, required:true},
|
||||
end: {value:false, required:true},
|
||||
tls: {type:"tls-config", value:'', required:false}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
@ -170,18 +210,42 @@
|
||||
$("#node-input-port-row").hide();
|
||||
$("#node-input-host-row").hide();
|
||||
$("#node-input-end-row").hide();
|
||||
$("#node-input-tls-enable").hide();
|
||||
} else if (sockettype == "client"){
|
||||
$("#node-input-port-row").show();
|
||||
$("#node-input-host-row").show();
|
||||
$("#node-input-end-row").show();
|
||||
$("#node-input-tls-enable").show();
|
||||
} else {
|
||||
$("#node-input-port-row").show();
|
||||
$("#node-input-host-row").hide();
|
||||
$("#node-input-end-row").show();
|
||||
$("#node-input-tls-enable").show();
|
||||
}
|
||||
};
|
||||
updateOptions();
|
||||
$("#node-input-beserver").change(updateOptions);
|
||||
function updateTLSOptions() {
|
||||
if ($("#node-input-usetls").is(':checked')) {
|
||||
$("#node-row-tls").show();
|
||||
} else {
|
||||
$("#node-row-tls").hide();
|
||||
}
|
||||
}
|
||||
if (this.tls) {
|
||||
$('#node-input-usetls').prop('checked', true);
|
||||
} else {
|
||||
$('#node-input-usetls').prop('checked', false);
|
||||
}
|
||||
updateTLSOptions();
|
||||
$("#node-input-usetls").on("click",function() {
|
||||
updateTLSOptions();
|
||||
});
|
||||
},
|
||||
oneditsave: function() {
|
||||
if (!$("#node-input-usetls").is(':checked')) {
|
||||
$("#node-input-tls").val("_ADD_");
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -194,15 +258,23 @@
|
||||
<span data-i18n="tcpin.label.port"></span>
|
||||
<input type="text" id="node-input-port" style="width:60px">
|
||||
</div>
|
||||
<div class="form-row" id="node-input-tls-enable">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-usetls" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-usetls" style="width: auto" data-i18n="httpin.use-tls"></label>
|
||||
<div id="node-row-tls" class="hide">
|
||||
<label style="width: auto; margin-left: 20px; margin-right: 10px;" for="node-input-tls"><span data-i18n="httpin.tls-config"></span></label><input type="text" style="width: 300px" id="node-input-tls">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-out"><i class="fa fa-sign-out"></i> <span data-i18n="tcpin.label.return"></span></label>
|
||||
<label for="node-input-ret"><i class="fa fa-sign-out"></i> <span data-i18n="tcpin.label.return"></span></label>
|
||||
<select type="text" id="node-input-ret" style="width:54%;">
|
||||
<option value="buffer" data-i18n="tcpin.output.buffer"></option>
|
||||
<option value="string" data-i18n="tcpin.output.string"></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-out"> </label>
|
||||
<label for="node-input-out"><i class="fa fa-sign-out fa-rotate-90"></i> <span data-i18n="tcpin.label.close"></span></label>
|
||||
<select type="text" id="node-input-out" style="width:54%;">
|
||||
<option value="time" data-i18n="tcpin.return.timeout"></option>
|
||||
<option value="char" data-i18n="tcpin.return.character"></option>
|
||||
@ -213,6 +285,9 @@
|
||||
<input type="text" id="node-input-splitc" style="width:50px;">
|
||||
<span id="node-units"></span>
|
||||
</div>
|
||||
<div id="node-row-newline" class="form-row hidden" style="padding-left:162px;">
|
||||
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
@ -222,14 +297,16 @@
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('tcp request',{
|
||||
category: 'network',
|
||||
color:"Silver",
|
||||
color: "Silver",
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
server: {value:""},
|
||||
port: {value:"",validate:RED.validators.regex(/^(\d*|)$/)},
|
||||
out: {value:"time",required:true},
|
||||
port: {value:"", validate:RED.validators.regex(/^(\d*|)$/)},
|
||||
out: {value:"time", required:true},
|
||||
ret: {value:"buffer"},
|
||||
splitc: {value:"0",required:true},
|
||||
name: {value:""}
|
||||
splitc: {value:"0", required:true},
|
||||
newline: {value:""},
|
||||
tls: {type:"tls-config", value:'', required:false}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
@ -246,6 +323,14 @@
|
||||
$("#node-input-ret").val("buffer");
|
||||
this.ret = "buffer";
|
||||
}
|
||||
$("#node-input-ret").on("change", function() {
|
||||
if ($("#node-input-ret").val() === "string" && $("#node-input-out").val() === "sit") { $("#node-row-newline").show(); }
|
||||
else { $("#node-row-newline").hide(); }
|
||||
});
|
||||
$("#node-input-out").on("change", function() {
|
||||
if ($("#node-input-ret").val() === "string" && $("#node-input-out").val() === "sit") { $("#node-row-newline").show(); }
|
||||
else { $("#node-row-newline").hide(); }
|
||||
});
|
||||
$("#node-input-out").on('focus', function () { previous = this.value; }).on("change", function() {
|
||||
$("#node-input-splitc").show();
|
||||
if (previous === null) { previous = $("#node-input-out").val(); }
|
||||
@ -272,6 +357,27 @@
|
||||
$("#node-input-splitc").hide();
|
||||
}
|
||||
});
|
||||
function updateTLSOptions() {
|
||||
if ($("#node-input-usetls").is(':checked')) {
|
||||
$("#node-row-tls").show();
|
||||
} else {
|
||||
$("#node-row-tls").hide();
|
||||
}
|
||||
}
|
||||
if (this.tls) {
|
||||
$('#node-input-usetls').prop('checked', true);
|
||||
} else {
|
||||
$('#node-input-usetls').prop('checked', false);
|
||||
}
|
||||
updateTLSOptions();
|
||||
$("#node-input-usetls").on("click",function() {
|
||||
updateTLSOptions();
|
||||
});
|
||||
},
|
||||
oneditsave: function() {
|
||||
if (!$("#node-input-usetls").is(':checked')) {
|
||||
$("#node-input-tls").val("_ADD_");
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -16,13 +16,46 @@
|
||||
|
||||
module.exports = function(RED) {
|
||||
"use strict";
|
||||
var reconnectTime = RED.settings.socketReconnectTime||10000;
|
||||
var socketTimeout = RED.settings.socketTimeout||null;
|
||||
let reconnectTime = RED.settings.socketReconnectTime || 10000;
|
||||
let socketTimeout = RED.settings.socketTimeout || null;
|
||||
const msgQueueSize = RED.settings.tcpMsgQueueSize || 1000;
|
||||
const Denque = require('denque');
|
||||
var net = require('net');
|
||||
const net = require('net');
|
||||
const tls = require('tls');
|
||||
|
||||
var connectionPool = {};
|
||||
let connectionPool = {};
|
||||
|
||||
function normalizeConnectArgs(listArgs) {
|
||||
const args = net._normalizeArgs(listArgs);
|
||||
const options = args[0];
|
||||
const cb = args[1];
|
||||
|
||||
// If args[0] was options, then normalize dealt with it.
|
||||
// If args[0] is port, or args[0], args[1] is host, port, we need to
|
||||
// find the options and merge them in, normalize's options has only
|
||||
// the host/port/path args that it knows about, not the tls options.
|
||||
// This means that options.host overrides a host arg.
|
||||
if (listArgs[1] !== null && typeof listArgs[1] === 'object') {
|
||||
ObjectAssign(options, listArgs[1]);
|
||||
} else if (listArgs[2] !== null && typeof listArgs[2] === 'object') {
|
||||
ObjectAssign(options, listArgs[2]);
|
||||
}
|
||||
|
||||
return cb ? [options, cb] : [options];
|
||||
}
|
||||
|
||||
function getAllowUnauthorized() {
|
||||
const allowUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED === '0';
|
||||
|
||||
if (allowUnauthorized) {
|
||||
process.emitWarning(
|
||||
'Setting the NODE_TLS_REJECT_UNAUTHORIZED ' +
|
||||
'environment variable to \'0\' makes TLS connections ' +
|
||||
'and HTTPS requests insecure by disabling ' +
|
||||
'certificate verification.');
|
||||
}
|
||||
return allowUnauthorized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue `item` in `queue`
|
||||
@ -53,13 +86,14 @@ module.exports = function(RED) {
|
||||
this.topic = n.topic;
|
||||
this.stream = (!n.datamode||n.datamode=='stream'); /* stream,single*/
|
||||
this.datatype = n.datatype||'buffer'; /* buffer,utf8,base64 */
|
||||
this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r");
|
||||
this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r").replace("\\t","\t");
|
||||
this.base64 = n.base64;
|
||||
this.server = (typeof n.server == 'boolean')?n.server:(n.server == "server");
|
||||
this.closing = false;
|
||||
this.connected = false;
|
||||
var node = this;
|
||||
var count = 0;
|
||||
if (n.tls) { var tlsNode = RED.nodes.getNode(n.tls); }
|
||||
|
||||
if (!node.server) {
|
||||
var buffer = null;
|
||||
@ -70,13 +104,25 @@ module.exports = function(RED) {
|
||||
node.log(RED._("tcpin.status.connecting",{host:node.host,port:node.port}));
|
||||
node.status({fill:"grey",shape:"dot",text:"common.status.connecting"});
|
||||
var id = RED.util.generateId();
|
||||
client = net.connect(node.port, node.host, function() {
|
||||
buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : "";
|
||||
node.connected = true;
|
||||
node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port}));
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected",_session:{type:"tcp",id:id}});
|
||||
});
|
||||
client.setKeepAlive(true,120000);
|
||||
var connOpts = {host: node.host};
|
||||
if (n.tls) {
|
||||
var connOpts = tlsNode.addTLSOptions({host: node.host});
|
||||
client = tls.connect(node.port, connOpts, function() {
|
||||
buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : "";
|
||||
node.connected = true;
|
||||
node.log(RED._("status.connected", {host: node.host, port: node.port}));
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected",_session:{type:"tcp",id:id}});
|
||||
});
|
||||
}
|
||||
else {
|
||||
client = net.connect(node.port, node.host, function() {
|
||||
buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : "";
|
||||
node.connected = true;
|
||||
node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port}));
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected",_session:{type:"tcp",id:id}});
|
||||
});
|
||||
}
|
||||
client.setKeepAlive(true, 120000);
|
||||
connectionPool[id] = client;
|
||||
|
||||
client.on('data', function (data) {
|
||||
@ -89,7 +135,7 @@ module.exports = function(RED) {
|
||||
buffer = buffer+data;
|
||||
var parts = buffer.split(node.newline);
|
||||
for (var i = 0; i<parts.length-1; i+=1) {
|
||||
msg = {topic:node.topic, payload:parts[i]};
|
||||
msg = {topic:node.topic, payload:parts[i] + node.newline.trimEnd()};
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
@ -150,7 +196,13 @@ module.exports = function(RED) {
|
||||
});
|
||||
}
|
||||
else {
|
||||
var server = net.createServer(function (socket) {
|
||||
let srv = net;
|
||||
let connOpts;
|
||||
if (n.tls) {
|
||||
srv = tls;
|
||||
connOpts = tlsNode.addTLSOptions({});
|
||||
}
|
||||
var server = srv.createServer(connOpts, function (socket) {
|
||||
socket.setKeepAlive(true,120000);
|
||||
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
|
||||
var id = RED.util.generateId();
|
||||
@ -177,7 +229,7 @@ module.exports = function(RED) {
|
||||
buffer = buffer+data;
|
||||
var parts = buffer.split(node.newline);
|
||||
for (var i = 0; i<parts.length-1; i+=1) {
|
||||
msg = {topic:node.topic, payload:parts[i], ip:socket.remoteAddress, port:socket.remotePort};
|
||||
msg = {topic:node.topic, payload:parts[i] + node.newline.trimEnd(), ip:socket.remoteAddress, port:socket.remotePort};
|
||||
msg._session = {type:"tcp",id:id};
|
||||
node.send(msg);
|
||||
}
|
||||
@ -269,8 +321,9 @@ module.exports = function(RED) {
|
||||
this.closing = false;
|
||||
this.connected = false;
|
||||
var node = this;
|
||||
if (n.tls) { var tlsNode = RED.nodes.getNode(n.tls); }
|
||||
|
||||
if (!node.beserver||node.beserver=="client") {
|
||||
if (!node.beserver || node.beserver == "client") {
|
||||
var reconnectTimeout;
|
||||
var client = null;
|
||||
var end = false;
|
||||
@ -278,11 +331,24 @@ module.exports = function(RED) {
|
||||
var setupTcpClient = function() {
|
||||
node.log(RED._("tcpin.status.connecting",{host:node.host,port:node.port}));
|
||||
node.status({fill:"grey",shape:"dot",text:"common.status.connecting"});
|
||||
client = net.connect(node.port, node.host, function() {
|
||||
node.connected = true;
|
||||
node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port}));
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||
});
|
||||
if (n.tls) {
|
||||
// connOpts = tlsNode.addTLSOptions(connOpts);
|
||||
// client = tls.connect(connOpts, function() {
|
||||
var connOpts = tlsNode.addTLSOptions({host: node.host});
|
||||
client = tls.connect(node.port, connOpts, function() {
|
||||
// buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : "";
|
||||
node.connected = true;
|
||||
node.log(RED._("status.connected", {host: node.host, port: node.port}));
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||
});
|
||||
}
|
||||
else {
|
||||
client = net.connect(node.port, node.host, function() {
|
||||
node.connected = true;
|
||||
node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port}));
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||
});
|
||||
}
|
||||
client.setKeepAlive(true,120000);
|
||||
client.on('error', function (err) {
|
||||
node.log(RED._("tcpin.errors.error",{error:err.toString()}));
|
||||
@ -368,7 +434,13 @@ module.exports = function(RED) {
|
||||
else {
|
||||
var connectedSockets = [];
|
||||
node.status({text:RED._("tcpin.status.connections",{count:0})});
|
||||
var server = net.createServer(function (socket) {
|
||||
let srv = net;
|
||||
let connOpts;
|
||||
if (n.tls) {
|
||||
srv = tls;
|
||||
connOpts = tlsNode.addTLSOptions({});
|
||||
}
|
||||
var server = srv.createServer(connOpts, function (socket) {
|
||||
socket.setKeepAlive(true,120000);
|
||||
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
|
||||
node.log(RED._("tcpin.status.connection-from",{host:socket.remoteAddress, port:socket.remotePort}));
|
||||
@ -445,7 +517,11 @@ module.exports = function(RED) {
|
||||
this.port = Number(n.port);
|
||||
this.out = n.out;
|
||||
this.ret = n.ret || "buffer";
|
||||
this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r").replace("\\t","\t");
|
||||
this.splitc = n.splitc;
|
||||
if (n.tls) {
|
||||
var tlsNode = RED.nodes.getNode(n.tls);
|
||||
}
|
||||
|
||||
if (this.out === "immed") { this.splitc = -1; this.out = "time"; }
|
||||
if (this.out !== "char") { this.splitc = Number(this.splitc); }
|
||||
@ -500,12 +576,48 @@ module.exports = function(RED) {
|
||||
}
|
||||
else { buf = Buffer.alloc(65536); } // set it to 64k... hopefully big enough for most TCP packets.... but only hopefully
|
||||
|
||||
clients[connection_id].client = net.Socket();
|
||||
var connOpts = {host:host, port:port};
|
||||
if (n.tls) {
|
||||
connOpts = tlsNode.addTLSOptions(connOpts);
|
||||
const allowUnauthorized = getAllowUnauthorized();
|
||||
|
||||
let options = {
|
||||
rejectUnauthorized: !allowUnauthorized,
|
||||
ciphers: tls.DEFAULT_CIPHERS,
|
||||
checkServerIdentity: tls.checkServerIdentity,
|
||||
minDHSize: 1024,
|
||||
...connOpts
|
||||
};
|
||||
|
||||
if (!options.keepAlive) { options.singleUse = true; }
|
||||
|
||||
const context = options.secureContext || tls.createSecureContext(options);
|
||||
|
||||
clients[connection_id].client = new tls.TLSSocket(options.socket, {
|
||||
allowHalfOpen: options.allowHalfOpen,
|
||||
pipe: !!options.path,
|
||||
secureContext: context,
|
||||
isServer: false,
|
||||
requestCert: false, // true,
|
||||
rejectUnauthorized: false, // options.rejectUnauthorized !== false,
|
||||
session: options.session,
|
||||
ALPNProtocols: options.ALPNProtocols,
|
||||
requestOCSP: options.requestOCSP,
|
||||
enableTrace: options.enableTrace,
|
||||
pskCallback: options.pskCallback,
|
||||
highWaterMark: options.highWaterMark,
|
||||
onread: options.onread,
|
||||
signal: options.signal,
|
||||
});
|
||||
}
|
||||
else {
|
||||
clients[connection_id].client = net.Socket();
|
||||
}
|
||||
if (socketTimeout !== null) { clients[connection_id].client.setTimeout(socketTimeout);}
|
||||
|
||||
if (host && port) {
|
||||
clients[connection_id].connecting = true;
|
||||
clients[connection_id].client.connect(port, host, function() {
|
||||
clients[connection_id].client.connect(connOpts, function() {
|
||||
//node.log(RED._("tcpin.errors.client-connected"));
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||
if (clients[connection_id] && clients[connection_id].client) {
|
||||
@ -528,17 +640,32 @@ module.exports = function(RED) {
|
||||
else {
|
||||
node.warn(RED._("tcpin.errors.no-host"));
|
||||
}
|
||||
|
||||
var chunk = "";
|
||||
clients[connection_id].client.on('data', function(data) {
|
||||
if (node.out === "sit") { // if we are staying connected just send the buffer
|
||||
if (clients[connection_id]) {
|
||||
const msg = clients[connection_id].lastMsg || {};
|
||||
msg.payload = RED.util.cloneMessage(data);
|
||||
if (node.ret === "string") {
|
||||
try { msg.payload = msg.payload.toString(); }
|
||||
catch(e) { node.error("Failed to create string", msg); }
|
||||
try {
|
||||
if (node.newline && node.newline !== "" ) {
|
||||
chunk += msg.payload.toString();
|
||||
let parts = chunk.split(node.newline);
|
||||
for (var p=0; p<parts.length-1; p+=1) {
|
||||
let m = RED.util.cloneMessage(msg);
|
||||
m.payload = parts[p] + node.newline.trimEnd();
|
||||
nodeSend(m);
|
||||
}
|
||||
chunk = parts[parts.length-1];
|
||||
}
|
||||
else {
|
||||
msg.payload = msg.payload.toString();
|
||||
nodeSend(msg);
|
||||
}
|
||||
}
|
||||
catch(e) { node.error(RED._("tcpin.errors.bad-string"), msg); }
|
||||
}
|
||||
nodeSend(msg);
|
||||
else { nodeSend(msg); }
|
||||
}
|
||||
}
|
||||
// else if (node.splitc === 0) {
|
||||
@ -675,7 +802,13 @@ module.exports = function(RED) {
|
||||
//node.warn(RED._("tcpin.errors.connect-timeout"));
|
||||
if (clients[connection_id].client) {
|
||||
clients[connection_id].connecting = true;
|
||||
clients[connection_id].client.connect(port, host, function() {
|
||||
|
||||
var connOpts = {host:host, port:port};
|
||||
if (n.tls) {
|
||||
connOpts = tlsNode.addTLSOptions(connOpts);
|
||||
}
|
||||
|
||||
clients[connection_id].client.connect(connOpts, function() {
|
||||
clients[connection_id].connected = true;
|
||||
clients[connection_id].connecting = false;
|
||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||
|
@ -49,7 +49,11 @@ module.exports = function(RED) {
|
||||
}
|
||||
var value = RED.util.getMessageProperty(msg,node.property);
|
||||
if (value !== undefined) {
|
||||
if (typeof value === "string") {
|
||||
if (typeof value === "string" || Buffer.isBuffer(value)) {
|
||||
// if (Buffer.isBuffer(value) && node.action !== "obj") {
|
||||
// node.warn(RED._("json.errors.dropped")); done();
|
||||
// }
|
||||
// else
|
||||
if (node.action === "" || node.action === "obj") {
|
||||
try {
|
||||
RED.util.setMessageProperty(msg,node.property,JSON.parse(value));
|
||||
|
@ -71,9 +71,7 @@ module.exports = function(RED) {
|
||||
node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg);
|
||||
}
|
||||
else {
|
||||
if (RED.settings.verbose) {
|
||||
node.log(RED._("file.status.deletedfile",{file:filename}));
|
||||
}
|
||||
node.debug(RED._("file.status.deletedfile",{file:filename}));
|
||||
nodeSend(msg);
|
||||
}
|
||||
done();
|
||||
|
@ -499,7 +499,8 @@
|
||||
"label": {
|
||||
"type": "Typ",
|
||||
"path": "Pfad",
|
||||
"url": "URL"
|
||||
"url": "URL",
|
||||
"subprotocol": "Subprotokoll"
|
||||
},
|
||||
"listenon": "Lauschen (listen on)",
|
||||
"connectto": "Verbinden mit",
|
||||
|
@ -442,7 +442,8 @@
|
||||
"state": {
|
||||
"connected": "Connected to broker: __broker__",
|
||||
"disconnected": "Disconnected from broker: __broker__",
|
||||
"connect-failed": "Connection failed to broker: __broker__"
|
||||
"connect-failed": "Connection failed to broker: __broker__",
|
||||
"broker-disconnected": "Broker __broker__ disconnected client: __reasonCode__ __reasonString__"
|
||||
},
|
||||
"retain": "Retain",
|
||||
"output": {
|
||||
@ -530,7 +531,8 @@
|
||||
"label": {
|
||||
"type": "Type",
|
||||
"path": "Path",
|
||||
"url": "URL"
|
||||
"url": "URL",
|
||||
"subprotocol": "Subprotocol"
|
||||
},
|
||||
"listenon": "Listen on",
|
||||
"connectto": "Connect to",
|
||||
@ -579,7 +581,9 @@
|
||||
"server": "Server",
|
||||
"return": "Return",
|
||||
"ms": "ms",
|
||||
"chars": "chars"
|
||||
"chars": "chars",
|
||||
"close": "Close",
|
||||
"optional": "(optional)"
|
||||
},
|
||||
"type": {
|
||||
"listen": "Listen on",
|
||||
@ -596,7 +600,7 @@
|
||||
"return": {
|
||||
"timeout": "after a fixed timeout of",
|
||||
"character": "when character received is",
|
||||
"number": "a fixed number of chars",
|
||||
"number": "after a fixed number of characters",
|
||||
"never": "never - keep connection open",
|
||||
"immed": "immediately - don't wait for reply"
|
||||
},
|
||||
@ -616,11 +620,11 @@
|
||||
"timeout": "timeout closed socket port __port__",
|
||||
"cannot-listen": "unable to listen on port __port__, error: __error__",
|
||||
"error": "error: __error__",
|
||||
|
||||
"socket-error": "socket error from __host__:__port__",
|
||||
"no-host": "Host and/or port not set",
|
||||
"connect-timeout": "connect timeout",
|
||||
"connect-fail": "connect failed"
|
||||
"connect-fail": "connect failed",
|
||||
"bad-string": "failed to convert to string"
|
||||
}
|
||||
},
|
||||
"udp": {
|
||||
|
@ -442,7 +442,8 @@
|
||||
"state": {
|
||||
"connected": "ブローカへ接続しました: __broker__",
|
||||
"disconnected": "ブローカから切断されました: __broker__",
|
||||
"connect-failed": "ブローカへの接続に失敗しました: __broker__"
|
||||
"connect-failed": "ブローカへの接続に失敗しました: __broker__",
|
||||
"broker-disconnected": "ブローカ __broker__ がクライアントを切断しました: __reasonCode__ __reasonString__"
|
||||
},
|
||||
"retain": "保持",
|
||||
"output": {
|
||||
@ -530,7 +531,8 @@
|
||||
"label": {
|
||||
"type": "種類",
|
||||
"path": "パス",
|
||||
"url": "URL"
|
||||
"url": "URL",
|
||||
"subprotocol": "サブプロトコル"
|
||||
},
|
||||
"listenon": "待ち受け",
|
||||
"connectto": "接続",
|
||||
@ -579,7 +581,9 @@
|
||||
"server": "サーバ",
|
||||
"return": "戻り値",
|
||||
"ms": "ミリ秒",
|
||||
"chars": "文字"
|
||||
"chars": "文字",
|
||||
"close": "終了",
|
||||
"optional": "(任意)"
|
||||
},
|
||||
"type": {
|
||||
"listen": "待ち受け",
|
||||
@ -618,7 +622,8 @@
|
||||
"socket-error": "__host__:__port__ にてソケットのエラーが生じました",
|
||||
"no-host": "ホスト名またはポートが設定されていません",
|
||||
"connect-timeout": "接続がタイムアウトしました",
|
||||
"connect-fail": "接続に失敗しました"
|
||||
"connect-fail": "接続に失敗しました",
|
||||
"bad-string": "文字列への変換に失敗しました"
|
||||
}
|
||||
},
|
||||
"udp": {
|
||||
|
@ -433,7 +433,8 @@
|
||||
"label": {
|
||||
"type": "종류",
|
||||
"path": "패스",
|
||||
"url": "URL"
|
||||
"url": "URL",
|
||||
"subprotocol": "서브 프로토콜"
|
||||
},
|
||||
"listenon": "대기",
|
||||
"connectto": "접속",
|
||||
|
@ -461,7 +461,8 @@
|
||||
"label": {
|
||||
"type": "Тип",
|
||||
"path": "Путь",
|
||||
"url": "URL"
|
||||
"url": "URL",
|
||||
"subprotocol": "Подпротокол"
|
||||
},
|
||||
"listenon": "Слушать на ...",
|
||||
"connectto": "Присоединиться к ...",
|
||||
|
@ -454,7 +454,8 @@
|
||||
"label": {
|
||||
"type": "类型",
|
||||
"path": "路径",
|
||||
"url": "URL"
|
||||
"url": "URL",
|
||||
"subprotocol": "子协议"
|
||||
},
|
||||
"listenon": "监听",
|
||||
"connectto": "连接",
|
||||
|
@ -458,7 +458,8 @@
|
||||
"label": {
|
||||
"type": "類型",
|
||||
"path": "路徑",
|
||||
"url": "URL"
|
||||
"url": "URL",
|
||||
"subprotocol": "子协议"
|
||||
},
|
||||
"listenon": "監聽",
|
||||
"connectto": "連接",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/nodes",
|
||||
"version": "2.1.6",
|
||||
"version": "2.2.0",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -17,7 +17,7 @@
|
||||
"dependencies": {
|
||||
"acorn": "8.7.0",
|
||||
"acorn-walk": "8.2.0",
|
||||
"ajv": "8.8.2",
|
||||
"ajv": "8.9.0",
|
||||
"body-parser": "1.19.1",
|
||||
"cheerio": "1.0.0-rc.10",
|
||||
"content-type": "1.0.4",
|
||||
@ -37,13 +37,13 @@
|
||||
"js-yaml": "3.14.1",
|
||||
"media-typer": "1.1.0",
|
||||
"mqtt": "4.3.4",
|
||||
"multer": "1.4.3",
|
||||
"multer": "1.4.4",
|
||||
"mustache": "4.2.0",
|
||||
"on-headers": "1.0.2",
|
||||
"raw-body": "2.4.2",
|
||||
"tough-cookie": "4.0.0",
|
||||
"uuid": "8.3.2",
|
||||
"ws": "7.5.1",
|
||||
"ws": "7.5.6",
|
||||
"xml2js": "0.4.23",
|
||||
"iconv-lite": "0.6.3"
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/registry",
|
||||
"version": "2.1.6",
|
||||
"version": "2.2.0",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
@ -16,11 +16,11 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/util": "2.1.6",
|
||||
"@node-red/util": "2.2.0",
|
||||
"clone": "2.1.2",
|
||||
"fs-extra": "10.0.0",
|
||||
"semver": "7.3.5",
|
||||
"tar": "6.1.11",
|
||||
"uglify-js": "3.14.5"
|
||||
"uglify-js": "3.15.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
|
@ -99,6 +99,7 @@ var api = module.exports = {
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project to activate
|
||||
* @param {boolean} opts.clearContext - whether to clear context
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
@ -107,7 +108,7 @@ var api = module.exports = {
|
||||
var currentProject = runtime.storage.projects.getActiveProject(opts.user);
|
||||
runtime.log.audit({event: "projects.set",id:opts.id}, opts.req);
|
||||
if (!currentProject || opts.id !== currentProject.name) {
|
||||
return runtime.storage.projects.setActiveProject(opts.user, opts.id);
|
||||
return runtime.storage.projects.setActiveProject(opts.user, opts.id, opts.clearContext);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -424,6 +424,17 @@ class Flow {
|
||||
*/
|
||||
getGroupEnvSetting(node, group, name) {
|
||||
if (group) {
|
||||
if (name === "NR_GROUP_NAME") {
|
||||
return [{
|
||||
val: group.name
|
||||
}, null];
|
||||
}
|
||||
if (name === "NR_GROUP_ID") {
|
||||
return [{
|
||||
val: group.id
|
||||
}, null];
|
||||
}
|
||||
|
||||
if (group.credentials === undefined) {
|
||||
group.credentials = credentials.get(group.id) || {};
|
||||
}
|
||||
@ -498,7 +509,13 @@ class Flow {
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
getSetting(key) {
|
||||
const flow = this.flow;
|
||||
const flow = this.flow;
|
||||
if (key === "NR_FLOW_NAME") {
|
||||
return flow.label;
|
||||
}
|
||||
if (key === "NR_FLOW_ID") {
|
||||
return flow.id;
|
||||
}
|
||||
if (flow.credentials === undefined) {
|
||||
flow.credentials = credentials.get(flow.id) || {};
|
||||
}
|
||||
|
@ -371,6 +371,17 @@ class Subflow extends Flow {
|
||||
name = name.substring(8);
|
||||
}
|
||||
const node = this.subflowInstance;
|
||||
if (node) {
|
||||
if (name === "NR_NODE_NAME") {
|
||||
return node.name;
|
||||
}
|
||||
if (name === "NR_NODE_ID") {
|
||||
return node.id;
|
||||
}
|
||||
if (name === "NR_NODE_PATH") {
|
||||
return node._path;
|
||||
}
|
||||
}
|
||||
if (node.g) {
|
||||
const group = this.getGroupNode(node.g);
|
||||
const [result, newName] = this.getGroupEnvSetting(node, group, name);
|
||||
|
@ -85,6 +85,7 @@ function createNode(flow,config) {
|
||||
try {
|
||||
Object.defineProperty(conf,'_module', {value: typeRegistry.getNodeInfo(type), enumerable: false, writable: true })
|
||||
Object.defineProperty(conf,'_flow', {value: flow, enumerable: false, writable: true })
|
||||
Object.defineProperty(conf,'_path', {value: `${flow.path}/${config._alias||config.id}`, enumerable: false, writable: true })
|
||||
newNode = new nodeTypeConstructor(conf);
|
||||
} catch (err) {
|
||||
Log.log({
|
||||
|
@ -62,6 +62,9 @@ function Node(n) {
|
||||
if (n._module) {
|
||||
Object.defineProperty(this,'_module', {value: n._module, enumerable: false, writable: true })
|
||||
}
|
||||
if (n._path) {
|
||||
Object.defineProperty(this,'_path', {value: n._path, enumerable: false, writable: true })
|
||||
}
|
||||
this.updateWires(n.wires);
|
||||
}
|
||||
|
||||
|
@ -14,30 +14,30 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var clone = require("clone");
|
||||
var log = require("@node-red/util").log;
|
||||
var util = require("@node-red/util").util;
|
||||
var memory = require("./memory");
|
||||
const clone = require("clone");
|
||||
const log = require("@node-red/util").log;
|
||||
const util = require("@node-red/util").util;
|
||||
const memory = require("./memory");
|
||||
|
||||
var settings;
|
||||
let settings;
|
||||
|
||||
// A map of scope id to context instance
|
||||
var contexts = {};
|
||||
let contexts = {};
|
||||
|
||||
// A map of store name to instance
|
||||
var stores = {};
|
||||
var storeList = [];
|
||||
var defaultStore;
|
||||
let stores = {};
|
||||
let storeList = [];
|
||||
let defaultStore;
|
||||
|
||||
// Whether there context storage has been configured or left as default
|
||||
var hasConfiguredStore = false;
|
||||
let hasConfiguredStore = false;
|
||||
|
||||
// Unknown Stores
|
||||
var unknownStores = {};
|
||||
let unknownStores = {};
|
||||
|
||||
function logUnknownStore(name) {
|
||||
if (name) {
|
||||
var count = unknownStores[name] || 0;
|
||||
let count = unknownStores[name] || 0;
|
||||
if (count == 0) {
|
||||
log.warn(log._("context.unknown-store", {name: name}));
|
||||
count++;
|
||||
@ -52,8 +52,8 @@ function init(_settings) {
|
||||
stores = {};
|
||||
storeList = [];
|
||||
hasConfiguredStore = false;
|
||||
var seed = settings.functionGlobalContext || {};
|
||||
contexts['global'] = createContext("global",seed);
|
||||
initialiseGlobalContext();
|
||||
|
||||
// create a default memory store - used by the unit tests that skip the full
|
||||
// `load()` initialisation sequence.
|
||||
// If the user has any stores configured, this will be disgarded
|
||||
@ -61,6 +61,11 @@ function init(_settings) {
|
||||
defaultStore = "memory";
|
||||
}
|
||||
|
||||
function initialiseGlobalContext() {
|
||||
const seed = settings.functionGlobalContext || {};
|
||||
contexts['global'] = createContext("global",seed);
|
||||
}
|
||||
|
||||
function load() {
|
||||
return new Promise(function(resolve,reject) {
|
||||
// load & init plugins in settings.contextStorage
|
||||
@ -233,12 +238,15 @@ function validateContextKey(key) {
|
||||
|
||||
function createContext(id,seed,parent) {
|
||||
// Seed is only set for global context - sourced from functionGlobalContext
|
||||
var scope = id;
|
||||
var obj = seed || {};
|
||||
var seedKeys;
|
||||
var insertSeedValues;
|
||||
const scope = id;
|
||||
const obj = {};
|
||||
let seedKeys;
|
||||
let insertSeedValues;
|
||||
if (seed) {
|
||||
seedKeys = Object.keys(seed);
|
||||
seedKeys.forEach(key => {
|
||||
obj[key] = seed[key];
|
||||
})
|
||||
insertSeedValues = function(keys,values) {
|
||||
if (!Array.isArray(keys)) {
|
||||
if (values[0] === undefined) {
|
||||
@ -540,8 +548,28 @@ function getContext(nodeId, flowId) {
|
||||
return newContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the context of the given node/flow/global
|
||||
*
|
||||
* If the user has configured a context store, this
|
||||
* will no-op a request to delete node/flow context.
|
||||
*/
|
||||
function deleteContext(id,flowId) {
|
||||
if(!hasConfiguredStore){
|
||||
if (id === "global") {
|
||||
// 1. delete global from all configured stores
|
||||
var promises = [];
|
||||
for(var plugin in stores){
|
||||
if(stores.hasOwnProperty(plugin)){
|
||||
promises.push(stores[plugin].delete('global'));
|
||||
}
|
||||
}
|
||||
return Promise.all(promises).then(function() {
|
||||
// 2. delete global context
|
||||
delete contexts['global'];
|
||||
// 3. reinitialise global context
|
||||
initialiseGlobalContext();
|
||||
})
|
||||
} else if (!hasConfiguredStore) {
|
||||
// only delete context if there's no configured storage.
|
||||
var contextId = id;
|
||||
if (flowId) {
|
||||
@ -549,12 +577,19 @@ function deleteContext(id,flowId) {
|
||||
}
|
||||
delete contexts[contextId];
|
||||
return stores["_"].delete(contextId);
|
||||
}else{
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete any contexts that are no longer in use
|
||||
* @param flowConfig object includes allNodes as object of id->node
|
||||
*
|
||||
* If flowConfig is undefined, all flow/node contexts will be removed
|
||||
**/
|
||||
function clean(flowConfig) {
|
||||
flowConfig = flowConfig || { allNodes: {} };
|
||||
var promises = [];
|
||||
for(var plugin in stores){
|
||||
if(stores.hasOwnProperty(plugin)){
|
||||
@ -572,6 +607,16 @@ function clean(flowConfig) {
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all contexts, including global and reinitialises global to
|
||||
* initial state.
|
||||
*/
|
||||
function clear() {
|
||||
return clean().then(function() {
|
||||
return deleteContext('global')
|
||||
})
|
||||
}
|
||||
|
||||
function close() {
|
||||
var promises = [];
|
||||
for(var plugin in stores){
|
||||
@ -594,5 +639,6 @@ module.exports = {
|
||||
getFlowContext:getFlowContext,
|
||||
delete: deleteContext,
|
||||
clean: clean,
|
||||
clear: clear,
|
||||
close: close
|
||||
};
|
||||
|
@ -206,6 +206,7 @@ module.exports = {
|
||||
eachNode: flows.eachNode,
|
||||
getContext: context.get,
|
||||
|
||||
clearContext: context.clear,
|
||||
|
||||
installerEnabled: registry.installerEnabled,
|
||||
installModule: installModule,
|
||||
|
@ -377,8 +377,17 @@ function getActiveProject(user) {
|
||||
return activeProject;
|
||||
}
|
||||
|
||||
function reloadActiveProject(action) {
|
||||
function reloadActiveProject(action, clearContext) {
|
||||
// Stop the current flows
|
||||
return runtime.nodes.stopFlows().then(function() {
|
||||
if (clearContext) {
|
||||
// Reset context to remove any old values
|
||||
return runtime.nodes.clearContext()
|
||||
} else {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}).then(function() {
|
||||
// Load the new project flows and start them
|
||||
return runtime.nodes.loadFlows(true).then(function() {
|
||||
events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}});
|
||||
}).catch(function(err) {
|
||||
@ -387,6 +396,9 @@ function reloadActiveProject(action) {
|
||||
events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}});
|
||||
throw err;
|
||||
});
|
||||
}).catch(function(err) {
|
||||
console.log(err.stack);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
function createProject(user, metadata) {
|
||||
@ -424,7 +436,7 @@ function createProject(user, metadata) {
|
||||
return getProject(user, metadata.name);
|
||||
})
|
||||
}
|
||||
function setActiveProject(user, projectName) {
|
||||
function setActiveProject(user, projectName, clearContext) {
|
||||
return loadProject(projectName).then(function(project) {
|
||||
var globalProjectSettings = settings.get("projects")||{};
|
||||
globalProjectSettings.activeProject = project.name;
|
||||
@ -434,7 +446,7 @@ function setActiveProject(user, projectName) {
|
||||
// console.log("Updated file targets to");
|
||||
// console.log(flowsFullPath)
|
||||
// console.log(credentialsFile)
|
||||
return reloadActiveProject("loaded");
|
||||
return reloadActiveProject("loaded", clearContext);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/runtime",
|
||||
"version": "2.1.6",
|
||||
"version": "2.2.0",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
@ -16,8 +16,8 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/registry": "2.1.6",
|
||||
"@node-red/util": "2.1.6",
|
||||
"@node-red/registry": "2.2.0",
|
||||
"@node-red/util": "2.2.0",
|
||||
"async-mutex": "0.3.2",
|
||||
"clone": "2.1.2",
|
||||
"express": "4.17.2",
|
||||
|
2
packages/node_modules/@node-red/util/LICENSE
vendored
2
packages/node_modules/@node-red/util/LICENSE
vendored
@ -1,4 +1,4 @@
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
|
11
packages/node_modules/@node-red/util/lib/util.js
vendored
11
packages/node_modules/@node-red/util/lib/util.js
vendored
@ -522,6 +522,17 @@ function setObjectProperty(msg,prop,value,createMissing) {
|
||||
* @return {String} value of env var
|
||||
*/
|
||||
function getSetting(node, name, flow_) {
|
||||
if (node) {
|
||||
if (name === "NR_NODE_NAME") {
|
||||
return node.name;
|
||||
}
|
||||
if (name === "NR_NODE_ID") {
|
||||
return node.id;
|
||||
}
|
||||
if (name === "NR_NODE_PATH") {
|
||||
return node._path;
|
||||
}
|
||||
}
|
||||
var flow = (flow_ ? flow_ : (node ? node._flow : null));
|
||||
if (flow) {
|
||||
if (node && node.g) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/util",
|
||||
"version": "2.1.6",
|
||||
"version": "2.2.0",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -16,7 +16,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"fs-extra": "10.0.0",
|
||||
"i18next": "21.6.6",
|
||||
"i18next": "21.6.10",
|
||||
"json-stringify-safe": "5.0.1",
|
||||
"jsonata": "1.8.5",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
|
2
packages/node_modules/node-red/LICENSE
vendored
2
packages/node_modules/node-red/LICENSE
vendored
@ -1,4 +1,4 @@
|
||||
Copyright JS Foundation and other contributors, http://js.foundation
|
||||
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
|
20
packages/node_modules/node-red/README.md
vendored
20
packages/node_modules/node-red/README.md
vendored
@ -2,12 +2,9 @@
|
||||
|
||||
http://nodered.org
|
||||
|
||||
[![Build Status](https://travis-ci.org/node-red/node-red.svg)](https://travis-ci.org/node-red/node-red)
|
||||
[![Coverage Status](https://coveralls.io/repos/node-red/node-red/badge.svg?branch=master)](https://coveralls.io/r/node-red/node-red?branch=master)
|
||||
Low-code programming for event-driven applications.
|
||||
|
||||
A visual tool for wiring the Internet of Things.
|
||||
|
||||
![Node-RED: A visual tool for wiring the Internet of Things](http://nodered.org/images/node-red-screenshot.png)
|
||||
![Node-RED: Low-code programming for event-driven applications.](http://nodered.org/images/node-red-screenshot.png)
|
||||
|
||||
## Quick Start
|
||||
|
||||
@ -39,15 +36,14 @@ This project adheres to the [Contributor Covenant 1.4](http://contributor-covena
|
||||
|
||||
## Authors
|
||||
|
||||
Node-RED is a project of the [JS Foundation](http://js.foundation).
|
||||
|
||||
It was created by [IBM Emerging Technology](https://www.ibm.com/blogs/emerging-technology/).
|
||||
|
||||
* Nick O'Leary [@knolleary](http://twitter.com/knolleary)
|
||||
* Dave Conway-Jones [@ceejay](http://twitter.com/ceejay)
|
||||
Node-RED is a project of the [OpenJS Foundation](http://openjsf.org).
|
||||
|
||||
It is maintained by:
|
||||
|
||||
* Nick O'Leary [@knolleary](http://twitter.com/knolleary)
|
||||
* Dave Conway-Jones [@ceejay](http://twitter.com/ceejay)
|
||||
* And many others...
|
||||
|
||||
## Copyright and license
|
||||
|
||||
Copyright JS Foundation and other contributors, http://js.foundation under [the Apache 2.0 license](LICENSE).
|
||||
Copyright OpenJS Foundation and other contributors, https://openjsf.org under [the Apache 2.0 license](LICENSE).
|
||||
|
10
packages/node_modules/node-red/package.json
vendored
10
packages/node_modules/node-red/package.json
vendored
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-red",
|
||||
"version": "2.1.6",
|
||||
"version": "2.2.0",
|
||||
"description": "Low-code programming for event-driven applications",
|
||||
"homepage": "http://nodered.org",
|
||||
"license": "Apache-2.0",
|
||||
@ -31,10 +31,10 @@
|
||||
"flow"
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/editor-api": "2.1.6",
|
||||
"@node-red/runtime": "2.1.6",
|
||||
"@node-red/util": "2.1.6",
|
||||
"@node-red/nodes": "2.1.6",
|
||||
"@node-red/editor-api": "2.2.0",
|
||||
"@node-red/runtime": "2.2.0",
|
||||
"@node-red/util": "2.2.0",
|
||||
"@node-red/nodes": "2.2.0",
|
||||
"basic-auth": "2.0.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"express": "4.17.2",
|
||||
|
8
packages/node_modules/node-red/settings.js
vendored
8
packages/node_modules/node-red/settings.js
vendored
@ -328,6 +328,12 @@ module.exports = {
|
||||
* a collection of themes to chose from.
|
||||
*/
|
||||
//theme: "",
|
||||
|
||||
/** To disable the 'Welcome to Node-RED' tour that is displayed the first
|
||||
* time you access the editor for each release of Node-RED, set this to false
|
||||
*/
|
||||
//tours: false,
|
||||
|
||||
palette: {
|
||||
/** The following property can be used to order the categories in the editor
|
||||
* palette. If a node's category is not in the list, the category will get
|
||||
@ -336,6 +342,7 @@ module.exports = {
|
||||
*/
|
||||
//categories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'],
|
||||
},
|
||||
|
||||
projects: {
|
||||
/** To enable the Projects feature, set this value to true */
|
||||
enabled: false,
|
||||
@ -349,6 +356,7 @@ module.exports = {
|
||||
mode: "manual"
|
||||
}
|
||||
},
|
||||
|
||||
codeEditor: {
|
||||
/** Select the text editor component used by the editor.
|
||||
* Defaults to "ace", but can be set to "ace" or "monaco"
|
||||
|
@ -106,6 +106,142 @@ describe('inject node', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('inject name of node as environment variable ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_NODE_NAME", payloadType: "env", wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper"}];
|
||||
helper.load(injectNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function (msg) {
|
||||
try {
|
||||
msg.should.have.property("payload", "NAME");
|
||||
done();
|
||||
} catch (err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
n1.receive({});
|
||||
});
|
||||
});
|
||||
|
||||
it('inject id of node as environment variable ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_NODE_ID", payloadType: "env", wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper"}];
|
||||
helper.load(injectNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function (msg) {
|
||||
try {
|
||||
msg.should.have.property("payload", "n1");
|
||||
done();
|
||||
} catch (err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
n1.receive({});
|
||||
});
|
||||
});
|
||||
|
||||
it('inject path of node as environment variable ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_NODE_PATH", payloadType: "env", wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper"}];
|
||||
helper.load(injectNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function (msg) {
|
||||
try {
|
||||
msg.should.have.property("payload", "flow/n1");
|
||||
done();
|
||||
} catch (err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
n1.receive({});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('inject name of flow as environment variable ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_FLOW_NAME", payloadType: "env", wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper"},
|
||||
{id: "flow", type: "tab", label: "FLOW" },
|
||||
];
|
||||
helper.load(injectNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function (msg) {
|
||||
try {
|
||||
msg.should.have.property("payload", "FLOW");
|
||||
done();
|
||||
} catch (err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
n1.receive({});
|
||||
});
|
||||
});
|
||||
|
||||
it('inject id of flow as environment variable ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_FLOW_ID", payloadType: "env", wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper"},
|
||||
{id: "flow", type: "tab", name: "FLOW" },
|
||||
];
|
||||
helper.load(injectNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function (msg) {
|
||||
try {
|
||||
msg.should.have.property("payload", "flow");
|
||||
done();
|
||||
} catch (err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
n1.receive({});
|
||||
});
|
||||
});
|
||||
|
||||
it('inject name of group as environment variable ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_GROUP_NAME", payloadType: "env", wires: [["n2"]], z: "flow", g: "g0"},
|
||||
{id: "n2", type: "helper"},
|
||||
{id: "g0", type: "group", name: "GROUP" },
|
||||
];
|
||||
helper.load(injectNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function (msg) {
|
||||
try {
|
||||
msg.should.have.property("payload", "GROUP");
|
||||
done();
|
||||
} catch (err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
n1.receive({});
|
||||
});
|
||||
});
|
||||
|
||||
it('inject id of group as environment variable ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_GROUP_ID", payloadType: "env", wires: [["n2"]], z: "flow", g: "g0"},
|
||||
{id: "n2", type: "helper"},
|
||||
{id: "g0", type: "group", name: "GROUP" },
|
||||
];
|
||||
helper.load(injectNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function (msg) {
|
||||
try {
|
||||
msg.should.have.property("payload", "g0");
|
||||
done();
|
||||
} catch (err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
n1.receive({});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('sets the value of flow context property', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", topic: "t1", payload: "flowValue", payloadType: "flow", wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper"}];
|
||||
@ -149,6 +285,7 @@ describe('inject node', function() {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('sets the value of two persistable flow context property', function (done) {
|
||||
var flow = [{id: "n0", z: "flow", type: "inject", topic: "t0", payload: "#:(memory0)::val", payloadType: "flow", wires: [["n2"]]},
|
||||
{id: "n1", z: "flow", type: "inject", topic: "t1", payload: "#:(memory1)::val", payloadType: "flow", wires: [["n2"]]},
|
||||
|
@ -366,6 +366,18 @@ describe('websocket Node', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle protocol property', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws") },
|
||||
{ id: "n2", type: "websocket-client", path: getWsUrl("/ws"), subprotocol: "testprotocol1, testprotocol2" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
helper.getNode("n1").should.have.property("subprotocol", []);
|
||||
helper.getNode("n2").should.have.property("subprotocol", ["testprotocol1","testprotocol2"]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should connect to server', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
@ -378,6 +390,18 @@ describe('websocket Node', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should initiate with subprotocol', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
{ id: "n2", type: "websocket-client", path: getWsUrl("/ws"), subprotocol: "testprotocol" }];
|
||||
helper.load(websocketNode, flow, function() {
|
||||
getSocket('server').on('connection', function (sock) {
|
||||
sock.should.have.property("protocol", "testprotocol")
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should close on delete', function(done) {
|
||||
var flow = [
|
||||
{ id: "server", type: "websocket-listener", path: "/ws" },
|
||||
|
@ -84,9 +84,17 @@ describe('TCP Request Node', function() {
|
||||
n2.on("input", msg => {
|
||||
try {
|
||||
if (typeof result === 'object') {
|
||||
msg.should.have.properties(Object.assign({}, result, {payload: Buffer.from(result.payload)}));
|
||||
if (flow[0].ret === "string") {
|
||||
msg.should.have.properties(Object.assign({}, result, {payload: result.payload}));
|
||||
} else {
|
||||
msg.should.have.properties(Object.assign({}, result, {payload: Buffer.from(result.payload)}));
|
||||
}
|
||||
} else {
|
||||
msg.should.have.property('payload', Buffer.from(result));
|
||||
if (flow[0].ret === "string") {
|
||||
msg.should.have.property('payload', result);
|
||||
} else {
|
||||
msg.should.have.property('payload', Buffer.from(result));
|
||||
}
|
||||
}
|
||||
done();
|
||||
} catch(err) {
|
||||
@ -245,10 +253,41 @@ describe('TCP Request Node', function() {
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & receive, then keep connection, and not split return strings', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"sit", ret:"string", newline:"", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [{
|
||||
payload: "foo",
|
||||
topic: 'boo'
|
||||
}, {
|
||||
payload: "bar<A>\nfoo",
|
||||
topic: 'boo'
|
||||
}], {
|
||||
payload: "ACK:foobar<A>\nfoo",
|
||||
topic: 'boo'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & receive, then keep connection, and split return strings', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"sit", ret:"string", newline:"<A>\\n", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [{
|
||||
payload: "foo",
|
||||
topic: 'boo'
|
||||
}, {
|
||||
payload: "bar<A>\nfoo",
|
||||
topic: 'boo'
|
||||
}], {
|
||||
payload: "ACK:foobar<A>",
|
||||
topic: 'boo'
|
||||
}, done);
|
||||
});
|
||||
|
||||
it('should send & recv data to/from server:port from msg', function(done) {
|
||||
var flow = [{id:"n1", type:"tcp request", server:"", port:"", out:"time", splitc: "0", wires:[["n2"]] },
|
||||
{id:"n2", type:"helper"}];
|
||||
testTCPMany(flow, [{
|
||||
testTCPMany(flow, [
|
||||
{
|
||||
payload: "f",
|
||||
host: "localhost",
|
||||
port: port
|
||||
|
@ -50,6 +50,24 @@ describe('JSON node', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should convert a buffer of a valid json string to a javascript object', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",action:"obj",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn2.on("input", function(msg) {
|
||||
msg.should.have.property('topic', 'bar');
|
||||
msg.payload.should.have.property('employees');
|
||||
msg.payload.employees[0].should.have.property('firstName', 'John');
|
||||
msg.payload.employees[0].should.have.property('lastName', 'Smith');
|
||||
done();
|
||||
});
|
||||
var jsonString = Buffer.from('{"employees":[{"firstName":"John", "lastName":"Smith"}]}');
|
||||
jn1.receive({payload:jsonString,topic: "bar"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should convert a javascript object to a json string', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
@ -166,29 +184,55 @@ describe('JSON node', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should log an error if asked to parse something thats not json or js', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
it('should log an error if asked to parse an invalid json string in a buffer', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",action:"obj",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
helper.load(jsonNode, flow, function() {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
setTimeout(function() {
|
||||
try {
|
||||
var logEvents = helper.log().args.filter(function(evt) {
|
||||
return evt[0].type == "json";
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
logEvents[0][0].should.have.a.property('msg');
|
||||
logEvents[0][0].msg.toString().should.eql('json.errors.dropped-object');
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
},50);
|
||||
jn1.receive({payload:Buffer.from("a")});
|
||||
try {
|
||||
var jn1 = helper.getNode("jn1");
|
||||
var jn2 = helper.getNode("jn2");
|
||||
jn1.receive({payload:Buffer.from('{"name":foo}'),topic: "bar"});
|
||||
setTimeout(function() {
|
||||
try {
|
||||
var logEvents = helper.log().args.filter(function(evt) {
|
||||
return evt[0].type == "json";
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
logEvents[0][0].should.have.a.property('msg');
|
||||
logEvents[0][0].msg.should.startWith("Unexpected token o");
|
||||
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
|
||||
done();
|
||||
} catch(err) { done(err) }
|
||||
},20);
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// it('should log an error if asked to parse something thats not json or js and not in force object mode', function(done) {
|
||||
// var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
// {id:"jn2", type:"helper"}];
|
||||
// helper.load(jsonNode, flow, function() {
|
||||
// var jn1 = helper.getNode("jn1");
|
||||
// var jn2 = helper.getNode("jn2");
|
||||
// setTimeout(function() {
|
||||
// try {
|
||||
// var logEvents = helper.log().args.filter(function(evt) {
|
||||
// return evt[0].type == "json";
|
||||
// });
|
||||
// logEvents.should.have.length(1);
|
||||
// logEvents[0][0].should.have.a.property('msg');
|
||||
// logEvents[0][0].msg.toString().should.eql('json.errors.dropped');
|
||||
// done();
|
||||
// } catch(err) {
|
||||
// done(err);
|
||||
// }
|
||||
// },50);
|
||||
// jn1.receive({payload:Buffer.from("abcd")});
|
||||
// });
|
||||
// });
|
||||
|
||||
it('should pass straight through if no payload set', function(done) {
|
||||
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
|
||||
{id:"jn2", type:"helper"}];
|
||||
|
@ -567,4 +567,43 @@ describe('subflow', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should access NR_NODE_PATH env var within subflow instance', function(done) {
|
||||
var flow = [
|
||||
{id:"t0", type:"tab", label:"", disabled:false, info:""},
|
||||
{id:"n1", x:10, y:10, z:"t0", type:"subflow:s1",
|
||||
env: [], wires:[["n2"]]},
|
||||
{id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
|
||||
// Subflow
|
||||
{id:"s1", type:"subflow", name:"Subflow", info:"",
|
||||
in:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1"} ]
|
||||
}],
|
||||
out:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1", port:0} ]
|
||||
}]
|
||||
},
|
||||
{id:"s1-n1", x:10, y:10, z:"s1", type:"function",
|
||||
func:"msg.payload = env.get('NR_NODE_PATH'); return msg;",
|
||||
wires:[]}
|
||||
];
|
||||
helper.load(functionNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property("payload", "t0/n1/s1-n1");
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
n1.receive({payload:"foo"});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
@ -201,6 +201,64 @@ describe('Subflow', function() {
|
||||
}
|
||||
util.inherits(TestEnvNode,Node);
|
||||
|
||||
var TestNameEnvNode = function(n) {
|
||||
Node.call(this,n);
|
||||
this._index = createCount++;
|
||||
this.scope = n.scope;
|
||||
this.foo = n.foo;
|
||||
var node = this;
|
||||
this.stopped = false;
|
||||
this.received = null;
|
||||
currentNodes[node.id] = node;
|
||||
this.on('input',function(msg) {
|
||||
var flow = node._flow;
|
||||
var val = flow.getSetting("NR_NODE_NAME");
|
||||
node.received = val;
|
||||
node.send({payload: val});
|
||||
});
|
||||
this.on('close',function() {
|
||||
node.stopped = true;
|
||||
stoppedNodes[node.id] = node;
|
||||
delete currentNodes[node.id];
|
||||
});
|
||||
this.__updateWires = this.updateWires;
|
||||
this.updateWires = function(newWires) {
|
||||
rewiredNodes[node.id] = node;
|
||||
node.newWires = newWires;
|
||||
node.__updateWires(newWires);
|
||||
};
|
||||
}
|
||||
util.inherits(TestNameEnvNode,Node);
|
||||
|
||||
var TestIDEnvNode = function(n) {
|
||||
Node.call(this,n);
|
||||
this._index = createCount++;
|
||||
this.scope = n.scope;
|
||||
this.foo = n.foo;
|
||||
var node = this;
|
||||
this.stopped = false;
|
||||
this.received = null;
|
||||
currentNodes[node.id] = node;
|
||||
this.on('input',function(msg) {
|
||||
var flow = node._flow;
|
||||
var val = flow.getSetting("NR_NODE_ID");
|
||||
node.received = val;
|
||||
node.send({payload: val});
|
||||
});
|
||||
this.on('close',function() {
|
||||
node.stopped = true;
|
||||
stoppedNodes[node.id] = node;
|
||||
delete currentNodes[node.id];
|
||||
});
|
||||
this.__updateWires = this.updateWires;
|
||||
this.updateWires = function(newWires) {
|
||||
rewiredNodes[node.id] = node;
|
||||
node.newWires = newWires;
|
||||
node.__updateWires(newWires);
|
||||
};
|
||||
}
|
||||
util.inherits(TestIDEnvNode,Node);
|
||||
|
||||
before(function() {
|
||||
getType = sinon.stub(typeRegistry,"get").callsFake(function(type) {
|
||||
if (type=="test") {
|
||||
@ -211,6 +269,10 @@ describe('Subflow', function() {
|
||||
return TestStatusNode;
|
||||
} else if (type=="testEnv"){
|
||||
return TestEnvNode;
|
||||
} else if (type=="testNameEnv"){
|
||||
return TestNameEnvNode;
|
||||
} else if (type=="testIDEnv"){
|
||||
return TestIDEnvNode;
|
||||
} else {
|
||||
return TestAsyncNode;
|
||||
}
|
||||
@ -880,6 +942,65 @@ describe('Subflow', function() {
|
||||
},150);
|
||||
});
|
||||
|
||||
it("can access name of subflow as env var", function(done) {
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]},
|
||||
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",name:"SFN",wires:["3"]},
|
||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"t1.3",wires:[]},
|
||||
{id:"sf1",type:"subflow",name:"Subflow 2",info:"",
|
||||
"in":[ {wires:[{id:"sf1-1"}]} ],
|
||||
"out":[ {wires:[{id:"sf1-2",port:0}]} ]},
|
||||
{id:"sf1-1",type:"test",z:"sf1",foo:"sf1.1",x:166,y:99,wires:[["sf1-2"]]},
|
||||
{id:"sf1-2",type:"testNameEnv",z:"sf1",foo:"sf1.2",x:166,y:99,wires:[[]]}
|
||||
]);
|
||||
var flow = Flow.create({
|
||||
getSetting: k=> process.env[k],
|
||||
handleError: (a,b,c) => { console.log(a,b,c); }
|
||||
},config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
|
||||
currentNodes["1"].receive({payload: "test"});
|
||||
setTimeout(function() {
|
||||
currentNodes["3"].should.have.a.property("received", "SFN");
|
||||
|
||||
flow.stop().then(function() {
|
||||
done();
|
||||
});
|
||||
},150);
|
||||
});
|
||||
|
||||
it("can access id of subflow as env var", function(done) {
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]},
|
||||
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",name:"SFN",wires:["3"]},
|
||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"t1.3",wires:[]},
|
||||
{id:"sf1",type:"subflow",name:"Subflow 2",info:"",
|
||||
"in":[ {wires:[{id:"sf1-1"}]} ],
|
||||
"out":[ {wires:[{id:"sf1-2",port:0}]} ]},
|
||||
{id:"sf1-1",type:"test",z:"sf1",foo:"sf1.1",x:166,y:99,wires:[["sf1-2"]]},
|
||||
{id:"sf1-2",type:"testIDEnv",z:"sf1",foo:"sf1.2",x:166,y:99,wires:[[]]}
|
||||
]);
|
||||
var flow = Flow.create({
|
||||
getSetting: k=> process.env[k],
|
||||
handleError: (a,b,c) => { console.log(a,b,c); }
|
||||
},config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
|
||||
currentNodes["1"].receive({payload: "test"});
|
||||
setTimeout(function() {
|
||||
currentNodes["3"].should.have.a.property("received", "2");
|
||||
|
||||
flow.stop().then(function() {
|
||||
done();
|
||||
});
|
||||
},150);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -156,6 +156,22 @@ describe('context', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes global context',function() {
|
||||
Context.init({functionGlobalContext: {foo:"bar"}});
|
||||
return Context.load().then(function(){
|
||||
var globalContextA = Context.get("global")
|
||||
|
||||
globalContextA.get('foo').should.eql('bar')
|
||||
globalContextA.set("another","value");
|
||||
|
||||
return Context.delete("global").then(function(){
|
||||
var globalContextB = Context.get("global")
|
||||
globalContextB.get('foo').should.eql('bar')
|
||||
should.not.exist(globalContextB.get("another"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('enumerates context keys - sync', function() {
|
||||
var flowContextA = Context.getFlowContext("flowA")
|
||||
var context = Context.get("1","flowA");
|
||||
@ -316,6 +332,31 @@ describe('context', function() {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("clear", function() {
|
||||
it('clears all context',function() {
|
||||
Context.init({functionGlobalContext: {foo:"bar"}});
|
||||
return Context.load().then(function(){
|
||||
var globalContextA = Context.get("global")
|
||||
globalContextA.get('foo').should.eql('bar')
|
||||
globalContextA.set("another","value");
|
||||
|
||||
var flowContextA = Context.getFlowContext("flowA")
|
||||
flowContextA.set("foo","abc");
|
||||
flowContextA.get("foo").should.equal("abc");
|
||||
|
||||
return Context.clear().then(function(){
|
||||
var globalContextB = Context.getFlowContext("global")
|
||||
globalContextB.get('foo').should.eql('bar')
|
||||
should.not.exist(globalContextB.get("another"));
|
||||
|
||||
flowContextA = Context.getFlowContext("flowA")
|
||||
should.not.exist(flowContextA.get("foo"))
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe('external context storage',function() {
|
||||
|
Loading…
Reference in New Issue
Block a user