mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Merge branch 'master' into runtime-api
This commit is contained in:
41
red/api/admin/context.js
Normal file
41
red/api/admin/context.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var apiUtils = require("../util");
|
||||
|
||||
var runtimeAPI;
|
||||
|
||||
|
||||
module.exports = {
|
||||
init: function(_runtimeAPI) {
|
||||
runtimeAPI = _runtimeAPI;
|
||||
},
|
||||
|
||||
get: function(req,res) {
|
||||
var opts = {
|
||||
user: req.user,
|
||||
scope: req.params.scope,
|
||||
id: req.params.id,
|
||||
key: req.params[0],
|
||||
store: req.query['store']
|
||||
}
|
||||
runtimeAPI.context.getValue(opts).then(function(result) {
|
||||
res.json(result);
|
||||
}).catch(function(err) {
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
}
|
||||
}
|
@@ -19,6 +19,7 @@ var express = require("express");
|
||||
var nodes = require("./nodes");
|
||||
var flows = require("./flows");
|
||||
var flow = require("./flow");
|
||||
var context = require("./context");
|
||||
var auth = require("../auth");
|
||||
|
||||
var apiUtil = require("../util");
|
||||
@@ -28,6 +29,7 @@ module.exports = {
|
||||
flows.init(runtimeAPI);
|
||||
flow.init(runtimeAPI);
|
||||
nodes.init(runtimeAPI);
|
||||
context.init(runtimeAPI);
|
||||
|
||||
var needsPermission = auth.needsPermission;
|
||||
|
||||
@@ -54,6 +56,12 @@ module.exports = {
|
||||
adminApp.get(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.read"),nodes.getSet,apiUtil.errorHandler);
|
||||
adminApp.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("nodes.write"),nodes.putSet,apiUtil.errorHandler);
|
||||
|
||||
// Context
|
||||
adminApp.get("/context/:scope(global)",needsPermission("context.read"),context.get,apiUtil.errorHandler);
|
||||
adminApp.get("/context/:scope(global)/*",needsPermission("context.read"),context.get,apiUtil.errorHandler);
|
||||
adminApp.get("/context/:scope(node|flow)/:id",needsPermission("context.read"),context.get,apiUtil.errorHandler);
|
||||
adminApp.get("/context/:scope(node|flow)/:id/*",needsPermission("context.read"),context.get,apiUtil.errorHandler);
|
||||
|
||||
return adminApp;
|
||||
}
|
||||
}
|
||||
|
@@ -144,6 +144,7 @@ module.exports = {
|
||||
runtimeAPI.nodes.getModuleCatalog(opts).then(function(result) {
|
||||
res.json(result);
|
||||
}).catch(function(err) {
|
||||
console.log(err.stack);
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
@@ -156,6 +157,7 @@ module.exports = {
|
||||
runtimeAPI.nodes.getModuleCatalogs(opts).then(function(result) {
|
||||
res.json(result);
|
||||
}).catch(function(err) {
|
||||
console.log(err.stack);
|
||||
apiUtils.rejectHandler(req,res,err);
|
||||
})
|
||||
},
|
||||
|
@@ -30,13 +30,12 @@ module.exports = {
|
||||
var lngs = req.query.lng;
|
||||
namespace = namespace.replace(/\.json$/,"");
|
||||
var lang = req.query.lng; //apiUtil.determineLangFromHeaders(req.acceptsLanguages() || []);
|
||||
var prevLang = i18n.i.lng();
|
||||
var prevLang = i18n.i.language;
|
||||
// Trigger a load from disk of the language if it is not the default
|
||||
i18n.i.setLng(lang, function(){
|
||||
var catalog = i18n.catalog(namespace,lang);
|
||||
i18n.i.changeLanguage(lang, function(){
|
||||
var catalog = i18n.i.getResourceBundle(lang, namespace);
|
||||
res.json(catalog||{});
|
||||
});
|
||||
i18n.i.setLng(prevLang);
|
||||
|
||||
i18n.i.changeLanguage(prevLang);
|
||||
}
|
||||
}
|
||||
|
@@ -240,6 +240,7 @@
|
||||
"output": "outputs:",
|
||||
"deleteSubflow": "delete subflow",
|
||||
"info": "Description",
|
||||
"category": "Category",
|
||||
"format":"markdown format",
|
||||
"errors": {
|
||||
"noNodesSelected": "<strong>Cannot create subflow</strong>: no nodes selected",
|
||||
@@ -264,6 +265,8 @@
|
||||
"settingIcon": "Icon",
|
||||
"noDefaultLabel": "none",
|
||||
"defaultLabel": "use default label",
|
||||
"searchIcons": "Search icons",
|
||||
"useDefault": "use default",
|
||||
"errors": {
|
||||
"scopeChange": "Changing the scope will make it unavailable to nodes in other flows that use it"
|
||||
}
|
||||
@@ -305,19 +308,18 @@
|
||||
"savedNodes": "Saved nodes",
|
||||
"savedType": "Saved __type__",
|
||||
"saveFailed": "Save failed: __message__",
|
||||
|
||||
"filename": "Filename",
|
||||
"folder": "Folder",
|
||||
"filenamePlaceholder": "file",
|
||||
"fullFilenamePlaceholder": "a/b/file",
|
||||
"folderPlaceholder": "a/b",
|
||||
|
||||
"breadcrumb": "Library"
|
||||
},
|
||||
"palette": {
|
||||
"noInfo": "no information available",
|
||||
"filter": "filter nodes",
|
||||
"search": "search modules",
|
||||
"addCategory": "Add new...",
|
||||
"label": {
|
||||
"subflows": "subflows",
|
||||
"input": "input",
|
||||
@@ -356,7 +358,6 @@
|
||||
"monthsV_plural": "__count__ months ago",
|
||||
"yearsV": "__count__ year ago",
|
||||
"yearsV_plural": "__count__ years ago",
|
||||
|
||||
"yearMonthsV": "__y__ year, __count__ month ago",
|
||||
"yearMonthsV_plural": "__y__ year, __count__ months ago",
|
||||
"yearsMonthsV": "__y__ years, __count__ month ago",
|
||||
@@ -457,6 +458,16 @@
|
||||
"filterAll":"all",
|
||||
"filtered": "__count__ hidden"
|
||||
},
|
||||
"context": {
|
||||
"name":"Context Data",
|
||||
"label":"context",
|
||||
"none": "none selected",
|
||||
"refresh": "refresh to load",
|
||||
"empty": "empty",
|
||||
"node": "Node",
|
||||
"flow": "Flow",
|
||||
"global": "Global"
|
||||
},
|
||||
"palette": {
|
||||
"name": "Palette management",
|
||||
"label": "palette"
|
||||
@@ -477,15 +488,14 @@
|
||||
"install": "install",
|
||||
"removeFromProject": "remove from project",
|
||||
"addToProject": "add to project",
|
||||
"none": "None",
|
||||
"files": "Files",
|
||||
"flow": "Flow",
|
||||
"credentials": "Credentials",
|
||||
"invalidEncryptionKey": "Invalid encryption key",
|
||||
"encryptionEnabled": "Encryption enabled",
|
||||
"encryptionDisabled": "Encryption disabled",
|
||||
"resetTheEncryptionKey": "Reset the encryption key:",
|
||||
"setTheEncryptionKey": "Set the encryption key:",
|
||||
"resetTheEncryptionKey": "Reset the encryption key:",
|
||||
"changeTheEncryptionKey": "Change the encryption key:",
|
||||
"currentKey": "Current key",
|
||||
"newKey": "New key",
|
||||
@@ -608,7 +618,9 @@
|
||||
"bool": "boolean",
|
||||
"json": "JSON",
|
||||
"bin": "buffer",
|
||||
"date": "timestamp"
|
||||
"date": "timestamp",
|
||||
"jsonata": "expression",
|
||||
"env": "env variable"
|
||||
}
|
||||
},
|
||||
"editableList": {
|
||||
@@ -637,6 +649,9 @@
|
||||
"eval": "Error evaluating expression:\n __message__"
|
||||
}
|
||||
},
|
||||
"jsEditor": {
|
||||
"title": "JavaScript editor"
|
||||
},
|
||||
"jsonEditor": {
|
||||
"title": "JSON editor",
|
||||
"format": "format JSON"
|
||||
@@ -699,7 +714,7 @@
|
||||
"ssh-key-add": "Add an ssh key",
|
||||
"credential-key": "Credentials encryption key",
|
||||
"cant-get-ssh-key": "Error! Can't get selected SSH key path.",
|
||||
"already-exists": "already exists",
|
||||
"already-exists2": "already exists",
|
||||
"git-error": "git error",
|
||||
"connection-failed": "Connection failed",
|
||||
"not-git-repo": "Not a git repository",
|
||||
@@ -793,6 +808,7 @@
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"passphrase": "Passphrase",
|
||||
"retry": "Retry",
|
||||
"update-failed": "Failed to update auth",
|
||||
"unhandled": "Unhandled error response"
|
||||
},
|
||||
|
@@ -189,11 +189,11 @@
|
||||
"desc":"Returns an aggregated value derived from applying the `function` parameter successively to each value in `array` in combination with the result of the previous application of the function.\n\nThe function must accept two arguments, and behaves like an infix operator between each value within the `array`.\n\nThe optional `init` parameter is used as the initial value in the aggregation."
|
||||
},
|
||||
"$flowContext": {
|
||||
"args": "string",
|
||||
"args": "string[, string]",
|
||||
"desc": "Retrieves a flow context property.\n\nThis is a Node-RED defined function."
|
||||
},
|
||||
"$globalContext": {
|
||||
"args": "string",
|
||||
"args": "string[, string]",
|
||||
"desc": "Retrieves a global context property.\n\nThis is a Node-RED defined function."
|
||||
},
|
||||
"$pad": {
|
||||
|
@@ -98,10 +98,13 @@
|
||||
"nodeActionDisabled": "ノードのアクションは、サブフロー内で無効になっています",
|
||||
"missing-types": "不明なノードが存在するため、フローを停止しました。詳細はログを確認してください。",
|
||||
"restartRequired": "更新されたモジュールを有効化するため、Node-REDを再起動する必要があります",
|
||||
"credentials_load_failed": "<p>認証情報を復号できないため、フローを停止しました</p><p>フローの認証情報は暗号化されています。しかし、プロジェクトの暗号鍵が存在しない、または不正です</p>",
|
||||
"credentials_load_failed": "<p>認証情報を復号できないため、フローを停止しました</p><p>フローの認証情報ファイルは暗号化されています。しかし、プロジェクトの暗号鍵が存在しない、または不正です</p>",
|
||||
"credentials_load_failed_reset": "<p>認証情報を復号できません</p><p>フローの認証情報ファイルは暗号化されています。しかし、プロジェクトの暗号鍵が存在しない、または不正です。</p><p>次回のデプロイでフローの認証情報ファイルがリセットされます。既存フローの認証情報は削除されます。</p>",
|
||||
"missing_flow_file": "<p>プロジェクトのフローファイルが存在しません</p><p>本プロジェクトにフローファイルが登録されていません</p>",
|
||||
"missing_package_file": "<p>プロジェクトのパッケージファイルが存在しません</p><p>本プロジェクトにはpackage.jsonファイルがありません</p>",
|
||||
"project_empty": "<p>空のプロジェクトです</p><p>デフォルトのプロジェクトファイルを作成しますか?<br/>作成しない場合、エディタの外でファイルをプロジェクトへ手動で追加する必要があります</p>",
|
||||
"project_not_found": "<p>プロジェクト '__project__' が存在しません</p>"
|
||||
"project_not_found": "<p>プロジェクト '__project__' が存在しません</p>",
|
||||
"git_merge_conflict": "<p>変更の自動マージが失敗しました</p><p>マージされていない競合を解決し、コミットしてください</p>"
|
||||
},
|
||||
"error": "<strong>エラー</strong>: __message__",
|
||||
"errors": {
|
||||
@@ -236,6 +239,7 @@
|
||||
"output": "出力:",
|
||||
"deleteSubflow": "サブフローを削除",
|
||||
"info": "詳細",
|
||||
"category": "カテゴリ",
|
||||
"format": "マークダウン形式",
|
||||
"errors": {
|
||||
"noNodesSelected": "<strong>サブフローを作成できません</strong>: ノードが選択されていません",
|
||||
@@ -260,6 +264,8 @@
|
||||
"settingIcon": "アイコン",
|
||||
"noDefaultLabel": "なし",
|
||||
"defaultLabel": "既定の名前を使用",
|
||||
"searchIcons": "アイコンを検索",
|
||||
"useDefault": "デフォルトを使用",
|
||||
"errors": {
|
||||
"scopeChange": "スコープの変更は、他のフローで使われているノードを無効にします"
|
||||
}
|
||||
@@ -312,6 +318,7 @@
|
||||
"noInfo": "情報がありません",
|
||||
"filter": "ノードを検索",
|
||||
"search": "ノードを検索",
|
||||
"addCategory": "新規追加...",
|
||||
"label": {
|
||||
"subflows": "サブフロー",
|
||||
"input": "入力",
|
||||
@@ -449,6 +456,16 @@
|
||||
"filterAll": "全て",
|
||||
"filtered": "__count__ 個が無効"
|
||||
},
|
||||
"context": {
|
||||
"name": "コンテキストデータ",
|
||||
"label": "コンテキストデータ",
|
||||
"none": "選択されていません",
|
||||
"refresh": "読み込みのため更新してください",
|
||||
"empty": "データが存在しません",
|
||||
"node": "Node",
|
||||
"flow": "Flow",
|
||||
"global": "Global"
|
||||
},
|
||||
"palette": {
|
||||
"name": "パレットの管理",
|
||||
"label": "パレット"
|
||||
@@ -568,7 +585,6 @@
|
||||
"pullUnrelatedHistory": "<p>リモートに関連のないコミット履歴があります。</p><p>本当に変更をプルしてローカルリポジトリに反映しますか?</p>",
|
||||
"pullChanges": "プル変更",
|
||||
"history": "履歴",
|
||||
"plural": "",
|
||||
"daysAgo": "__count__ 日前",
|
||||
"daysAgo_plural": "__count__ 日前",
|
||||
"hoursAgo": "__count__ 時間前",
|
||||
@@ -600,7 +616,9 @@
|
||||
"bool": "真偽",
|
||||
"json": "JSON",
|
||||
"bin": "バッファ",
|
||||
"date": "日時"
|
||||
"date": "日時",
|
||||
"jsonata": "JSONata式",
|
||||
"env": "環境変数"
|
||||
}
|
||||
},
|
||||
"editableList": {
|
||||
@@ -629,6 +647,9 @@
|
||||
"eval": "表現評価エラー:\n __message__"
|
||||
}
|
||||
},
|
||||
"jsEditor": {
|
||||
"title": "JavaScriptエディタ"
|
||||
},
|
||||
"jsonEditor": {
|
||||
"title": "JSONエディタ",
|
||||
"format": "JSONフォーマット"
|
||||
@@ -691,7 +712,7 @@
|
||||
"ssh-key-add": "SSHキーの追加",
|
||||
"credential-key": "認証情報の暗号化キー",
|
||||
"cant-get-ssh-key": "エラー! 選択したSSHキーのパスを取得できません。",
|
||||
"already-exists": "既に存在します",
|
||||
"already-exists2": "既に存在します",
|
||||
"git-error": "Gitエラー",
|
||||
"connection-failed": "接続に失敗しました",
|
||||
"not-git-repo": "Gitリポジトリではありません",
|
||||
@@ -785,6 +806,7 @@
|
||||
"username": "ユーザ名",
|
||||
"password": "パスワード",
|
||||
"passphrase": "パスフレーズ",
|
||||
"retry": "リトライ",
|
||||
"update-failed": "認証の更新に失敗しました",
|
||||
"unhandled": "エラー応答が処理されませんでした"
|
||||
},
|
||||
@@ -798,7 +820,7 @@
|
||||
"no-empty": "デフォルトのファイル群を空でないプロジェクトに作成することはできません。",
|
||||
"git-error": "Gitエラー"
|
||||
},
|
||||
"errors" : {
|
||||
"errors": {
|
||||
"no-username-email": "Gitクライアントのユーザ名/emailが設定されていません。",
|
||||
"unexpected": "予期しないエラーが発生しました",
|
||||
"code": "コード"
|
||||
|
@@ -214,5 +214,9 @@
|
||||
"$toMillis": {
|
||||
"args": "timestamp",
|
||||
"desc": "ISO 8601形式の文字列 `timestamp` を、Unixエポック(1 January, 1970 UTC)からの経過ミリ秒を表す数値へ変換します。 文字列が正しい形式でない場合、エラーとなります。"
|
||||
},
|
||||
"$env": {
|
||||
"args": "arg",
|
||||
"desc": "環境変数の値を返します。\n\n本関数はNode-REDの定義関数です。"
|
||||
}
|
||||
}
|
||||
|
156
red/runtime-api/context.js
Normal file
156
red/runtime-api/context.js
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
/**
|
||||
* @namespace RED.context
|
||||
*/
|
||||
|
||||
var runtime;
|
||||
|
||||
// TODO: move runtime/util to util/index
|
||||
var util = require("../runtime/util");
|
||||
|
||||
function exportContextStore(scope,ctx, store, result, callback) {
|
||||
ctx.keys(store,function(err, keys) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
result[store] = {};
|
||||
var c = keys.length;
|
||||
if (c === 0) {
|
||||
callback(null);
|
||||
} else {
|
||||
keys.forEach(function(key) {
|
||||
ctx.get(key,store,function(err, v) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
if (scope !== 'global' ||
|
||||
store === runtime.nodes.listContextStores().default ||
|
||||
!runtime.settings.hasOwnProperty("functionGlobalContext") ||
|
||||
!runtime.settings.functionGlobalContext.hasOwnProperty(key) ||
|
||||
runtime.settings.functionGlobalContext[key] !== v) {
|
||||
result[store][key] = util.encodeObject({msg:v});
|
||||
}
|
||||
c--;
|
||||
if (c === 0) {
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var api = module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
},
|
||||
/**
|
||||
* Gets the info of an individual node set
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.scope - the scope of the context
|
||||
* @param {String} opts.id - the id of the context
|
||||
* @param {String} opts.store - the context store
|
||||
* @param {String} opts.key - the context key
|
||||
|
||||
* @return {Promise} - the node information
|
||||
* @memberof RED.nodes
|
||||
*/
|
||||
getValue: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
var scope = opts.scope;
|
||||
var id = opts.id;
|
||||
var store = opts.store;
|
||||
var key = opts.key;
|
||||
|
||||
var availableStores = runtime.nodes.listContextStores();
|
||||
//{ default: 'default', stores: [ 'default', 'file' ] }
|
||||
if (store && availableStores.stores.indexOf(store) === -1) {
|
||||
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"not_found"});
|
||||
var err = new Error();
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
return reject(err);
|
||||
}
|
||||
var ctx;
|
||||
if (scope === 'global') {
|
||||
ctx = runtime.nodes.getContext('global');
|
||||
} else if (scope === 'flow') {
|
||||
ctx = runtime.nodes.getContext(id);
|
||||
} else if (scope === 'node') {
|
||||
var node = runtime.nodes.getNode(id);
|
||||
if (node) {
|
||||
ctx = node.context();
|
||||
}
|
||||
}
|
||||
if (ctx) {
|
||||
if (key) {
|
||||
store = store || availableStores.default;
|
||||
ctx.get(key,store,function(err, v) {
|
||||
var encoded = util.encodeObject({msg:v});
|
||||
if (store !== availableStores.default) {
|
||||
encoded.store = store;
|
||||
}
|
||||
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key});
|
||||
resolve(encoded);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
var stores;
|
||||
if (!store) {
|
||||
stores = availableStores.stores;
|
||||
} else {
|
||||
stores = [store];
|
||||
}
|
||||
|
||||
var result = {};
|
||||
var c = stores.length;
|
||||
var errorReported = false;
|
||||
stores.forEach(function(store) {
|
||||
exportContextStore(scope,ctx,store,result,function(err) {
|
||||
if (err) {
|
||||
// TODO: proper error reporting
|
||||
if (!errorReported) {
|
||||
errorReported = true;
|
||||
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key,error:"unexpected_error"});
|
||||
var err = new Error();
|
||||
err.code = "unexpected_error";
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
c--;
|
||||
if (c === 0) {
|
||||
if (!errorReported) {
|
||||
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key});
|
||||
resolve(result);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
} else {
|
||||
runtime.log.audit({event: "context.get",scope:scope,id:id,store:store,key:key});
|
||||
resolve({});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -33,6 +33,7 @@ var api = module.exports = {
|
||||
api.settings.init(runtime);
|
||||
api.library.init(runtime);
|
||||
api.projects.init(runtime);
|
||||
api.context.init(runtime);
|
||||
},
|
||||
|
||||
comms: require("./comms"),
|
||||
@@ -41,6 +42,7 @@ var api = module.exports = {
|
||||
nodes: require("./nodes"),
|
||||
settings: require("./settings"),
|
||||
projects: require("./projects"),
|
||||
context: require("./context"),
|
||||
|
||||
/**
|
||||
* Returns whether the runtime is started
|
||||
|
@@ -361,19 +361,19 @@ var api = module.exports = {
|
||||
return new Promise(function(resolve,reject) {
|
||||
var namespace = opts.module;
|
||||
var lang = opts.lang;
|
||||
var prevLang = runtime.i18n.i.lng();
|
||||
var prevLang = runtime.i18n.i.language;
|
||||
// Trigger a load from disk of the language if it is not the default
|
||||
runtime.i18n.i.setLng(lang, function(){
|
||||
runtime.i18n.i.changeLanguage(lang, function(){
|
||||
var nodeList = runtime.nodes.getNodeList();
|
||||
var result = {};
|
||||
nodeList.forEach(function(n) {
|
||||
if (n.module !== "node-red") {
|
||||
result[n.id] = runtime.i18n.catalog(n.id,lang)||{};
|
||||
result[n.id] = runtime.i18n.i.getResourceBundle(lang, n.id)||{};
|
||||
}
|
||||
});
|
||||
resolve(result);
|
||||
});
|
||||
runtime.i18n.i.setLng(prevLang);
|
||||
runtime.i18n.i.changeLanguage(prevLang);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -392,11 +392,11 @@ var api = module.exports = {
|
||||
var lang = opts.lang;
|
||||
var prevLang = runtime.i18n.i.lng();
|
||||
// Trigger a load from disk of the language if it is not the default
|
||||
runtime.i18n.i.setLng(lang, function(){
|
||||
var catalog = runtime.i18n.catalog(namespace,lang);
|
||||
runtime.i18n.i.changeLanguage(lang, function(){
|
||||
var catalog = runtime.i18n.getResourceBundle(lang, namespace);
|
||||
resolve(catalog||{});
|
||||
});
|
||||
runtime.i18n.i.setLng(prevLang);
|
||||
runtime.i18n.i.changeLanguage(prevLang);
|
||||
});
|
||||
},
|
||||
|
||||
|
@@ -79,6 +79,9 @@ var api = module.exports = {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
safeSettings.context = runtime.nodes.listContextStores();
|
||||
|
||||
if (util.isArray(runtime.settings.paletteCategories)) {
|
||||
safeSettings.paletteCategories = runtime.settings.paletteCategories;
|
||||
}
|
||||
|
@@ -185,7 +185,7 @@ function addModule(module) {
|
||||
if (!set.err) {
|
||||
set.types.forEach(function(t) {
|
||||
if (nodeTypeToId.hasOwnProperty(t)) {
|
||||
set.err = new Error("Type already registered");
|
||||
set.err = "Type already registered";
|
||||
set.err.code = "type_already_registered";
|
||||
set.err.details = {
|
||||
type: t,
|
||||
|
@@ -88,6 +88,7 @@ function getVersion() {
|
||||
}
|
||||
|
||||
function start() {
|
||||
|
||||
return i18n.registerMessageCatalog("runtime",path.resolve(path.join(__dirname,"locales")),"runtime.json")
|
||||
.then(function() { return storage.init(runtime)})
|
||||
.then(function() { return settings.load(storage)})
|
||||
@@ -163,8 +164,10 @@ function start() {
|
||||
if (settings.httpStatic) {
|
||||
log.info(log._("runtime.paths.httpStatic",{path:path.resolve(settings.httpStatic)}));
|
||||
}
|
||||
redNodes.loadFlows().then(redNodes.startFlows).catch(function(err) {});
|
||||
started = true;
|
||||
redNodes.loadContextsPlugin().then(function () {
|
||||
redNodes.loadFlows().then(redNodes.startFlows).catch(function(err) {});
|
||||
started = true;
|
||||
});
|
||||
}).catch(function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
@@ -230,7 +233,9 @@ function stop() {
|
||||
clearTimeout(reinstallTimeout);
|
||||
}
|
||||
started = false;
|
||||
return redNodes.stopFlows();
|
||||
return redNodes.stopFlows().then(function(){
|
||||
return redNodes.closeContextsPlugin();
|
||||
});
|
||||
}
|
||||
|
||||
var runtime = module.exports = {
|
||||
|
@@ -155,5 +155,15 @@
|
||||
"readme": "### About\n\nThis is your project's README.md file. It helps users understand what your\nproject does, how to use it and anything else they may need to know."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"context": {
|
||||
"log-store-init": "Context store : '__name__' [__info__]",
|
||||
"error-loading-module": "Error loading context store '__module__': __message__ ",
|
||||
"error-module-not-defined": "Context store '__storage__' missing 'module' option",
|
||||
"error-invalid-module-name": "Invalid context store name: '__name__'",
|
||||
"error-invalid-default-module": "Default context store unknown: '__storage__'",
|
||||
"unknown-store": "Unknown context store '__name__' specified. Using default store."
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,10 +1,169 @@
|
||||
{
|
||||
"runtime": {
|
||||
"welcome": "Welcome to Node-RED",
|
||||
"version": "__component__ バージョン: __version__",
|
||||
"unsupported_version": "__component__ は未サポートのバージョンです。必要: __requires__ 検知: __version__",
|
||||
"paths": {
|
||||
"settings": "設定ファイル: __path__",
|
||||
"httpStatic": "HTTP Static : __path__"
|
||||
}
|
||||
},
|
||||
|
||||
"server": {
|
||||
"loading": "パレットノードのロード",
|
||||
"palette-editor": {
|
||||
"disabled": "パレットエディタを無効化 : ユーザ設定",
|
||||
"npm-not-found": "バレットエディタを無効化 : npmコマンドが見つかりません"
|
||||
},
|
||||
"errors": "__count__ 個のノードの登録に失敗しました",
|
||||
"errors_plural": "__count__ 個のノードの登録に失敗しました",
|
||||
"errors-help": "詳細は -v を指定して実行してください",
|
||||
"missing-modules": "不足しているノードモジュール:",
|
||||
"node-version-mismatch": "ノードモジュールはこのバージョンではロードできません。必要なバージョン: __version__ ",
|
||||
"type-already-registered": "'__type__' はモジュール __module__ で登録済みです",
|
||||
"removing-modules": "設定からモジュールを削除します",
|
||||
"added-types": "追加したノード:",
|
||||
"removed-types": "削除したノード:",
|
||||
"install": {
|
||||
"invalid": "不正なモジュール名",
|
||||
"installing": "モジュール__name__, バージョン: __version__をインスートールします",
|
||||
"installed": "モジュール __name__ をインストールしました",
|
||||
"install-failed": "インストールに失敗しました",
|
||||
"install-failed-long": "モジュール __name__ のインストールに失敗しました:",
|
||||
"install-failed-not-found": "$t(install-failed-long) モジュールが見つかりません",
|
||||
"upgrading": "モジュール __name__ をバージョン __version__ に更新します",
|
||||
"upgraded": "モジュール __name__ を更新しました。新しいバージョンを使うには、Node-REDを再起動してください。",
|
||||
"upgrade-failed-not-found": "$t(server.install.install-failed-long) バージョンが見つかりません",
|
||||
"install-failed-not-found": "$t(server.install.install-failed-long) モジュールが見つかりません",
|
||||
"uninstalling": "モジュールをアンインストールします: __name__",
|
||||
"uninstall-failed": "アンインストールに失敗しました",
|
||||
"uninstall-failed-long": "モジュール __name__ のアンインストールに失敗しました:",
|
||||
"uninstalled": "モジュール __name__ をアンインストールしました"
|
||||
},
|
||||
"unable-to-listen": "__listenpath__ に対してlistenできません",
|
||||
"port-in-use": "エラー: ポートが使用中です",
|
||||
"uncaught-exception": "未補足の例外:",
|
||||
"admin-ui-disabled": "管理UIを無効化しました",
|
||||
"now-running": "サーバは __listenpath__ で実行中です",
|
||||
"failed-to-start": "サーバの起動に失敗しました:",
|
||||
"headless-mode": "ヘッドレスモードで実行中です",
|
||||
"httpadminauth-deprecated": "httpAdminAuthは非推奨です。代わりに adminAuth を使用してください"
|
||||
},
|
||||
|
||||
"api": {
|
||||
"flows": {
|
||||
"error-save": "フローの保存エラー: __message__",
|
||||
"error-reload": "フローの読み込みエラー: __message__"
|
||||
},
|
||||
"library": {
|
||||
"error-load-entry": "ライブラリエントリ '__path__' の読み込みエラー: __message__",
|
||||
"error-save-entry": "ライブラリエントリ '__path__' の保存エラー: __message__",
|
||||
"error-load-flow": "フロー '__path__' の読み込みエラー: __message__",
|
||||
"error-save-flow": "フロー '__path__' の保存エラー: __message__"
|
||||
},
|
||||
"nodes": {
|
||||
"enabled": "ノードを有効化しました:",
|
||||
"disabled": "ノードを無効化しました:",
|
||||
"error-enable": "ノードの有効化に失敗しました:"
|
||||
}
|
||||
},
|
||||
|
||||
"comms": {
|
||||
"error": "通信チャネルエラー: __message__",
|
||||
"error-server": "サーバエラー: __message__",
|
||||
"error-send": "送信エラー: __message__"
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"user-not-available": "ユーザ設定を保存できません: __message__",
|
||||
"not-available": "設定が利用できません",
|
||||
"property-read-only": "プロパティ '__prop__' は読み出し専用です"
|
||||
},
|
||||
|
||||
"nodes": {
|
||||
"credentials": {
|
||||
"error":"クレデンシャルの読み込みエラー: __message__",
|
||||
"error-saving":"クレデンシャルの保存エラー: __message__",
|
||||
"not-registered": "クレデンシャル '__type__' は登録されていません",
|
||||
"system-key-warning": "\n\n---------------------------------------------------------------------\nフローのクレデンシャルファイルはシステム生成キーで暗号化されています。\n\nシステム生成キーを何らかの理由で失った場合、クレデンシャルファイルを\n復元することはできません。その場合、ファイルを削除してクレデンシャルを\n再入力しなければなりません。\n\n設定ファイル内で 'credentialSecret' オプションを使って独自キーを設定\nします。変更を次にデプロイする際、Node-REDは選択したキーを用いてクレ\nデンシャルを再暗号化します。 \n\n---------------------------------------------------------------------\n"
|
||||
},
|
||||
"flows": {
|
||||
"registered-missing": "欠落しているノードを登録します: __type__",
|
||||
"error": "フローの読み込みエラー: __message__",
|
||||
"starting-modified-nodes": "更新されたノードを開始します",
|
||||
"starting-modified-flows": "更新されたフローを開始します",
|
||||
"starting-flows": "フローを開始します",
|
||||
"started-modified-nodes": "更新されたノードを開始しました",
|
||||
"started-modified-flows": "更新されたフローを開始しました",
|
||||
"started-flows": "フローを開始しました",
|
||||
"stopping-modified-nodes": "更新されたノードを停止します",
|
||||
"stopping-modified-flows": "更新されたフローを停止します",
|
||||
"stopping-flows": "フローを停止します",
|
||||
"stopped-modified-nodes": "更新されたノードを停止しました",
|
||||
"stopped-modified-flows": "更新されたフローを停止しました",
|
||||
"stopped-flows": "フローを停止しました",
|
||||
"stopped": "停止しました",
|
||||
"stopping-error": "ノードの停止に失敗しました: __message__",
|
||||
"added-flow": "フローを追加します: __label__",
|
||||
"updated-flow": "フローを更新しました: __label__",
|
||||
"removed-flow": "フローを削除しました: __label__",
|
||||
"missing-types": "欠落しているノードが登録されるのを待っています:",
|
||||
"missing-type-provided": " - __type__ (npmモジュール __module__ からインストールされました)",
|
||||
"missing-type-install-1": "欠落しているモジュールをインストールするには次のコマンドを実行してください:",
|
||||
"missing-type-install-2": "コマンドの実行は次のディレクトリで行います:"
|
||||
},
|
||||
"flow": {
|
||||
"unknown-type": "不明なノード: __type__",
|
||||
"missing-types": "欠落したノード",
|
||||
"error-loop": "メッセージの例外補足回数が最大値を超えました"
|
||||
},
|
||||
"index": {
|
||||
"unrecognised-id": "不明なID: __id__",
|
||||
"type-in-use": "ノードは使用中です: __msg__",
|
||||
"unrecognised-module": "不明なモジュール: __module__"
|
||||
},
|
||||
"registry": {
|
||||
"localfilesystem": {
|
||||
"module-not-found": "モジュール '__module__' が見つかりません"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"storage": {
|
||||
"index": {
|
||||
"forbidden-flow-name": "不正なフロー名"
|
||||
},
|
||||
"localfilesystem": {
|
||||
"user-dir": "ユーザディレクトリ : __path__",
|
||||
"flows-file": "フローファイル : __path__",
|
||||
"create": "__type__ ファイルを作成します",
|
||||
"empty": "既存の __type__ ファイルが空です",
|
||||
"invalid": "既存の __type__ ファイルはJSON形式ではありません",
|
||||
"restore": " __type__ ファイルをバックアップ __path__ から復元します",
|
||||
"restore-fail": "__type__ ファイルをバックアップから復元するのに失敗しました : __message__",
|
||||
"fsync-fail": "ファイル __path__ のディスクへの書き出しに失敗しました : __message__",
|
||||
"projects": {
|
||||
"changing-project": "プロジェクトを設定します : __project__",
|
||||
"active-project": "選択中のプロジェクト : __project__",
|
||||
"project-not-found": "プロジェクトが見つかりません : __project__",
|
||||
"no-active-project": "プロジェクトが選択されていません : デフォルトのフローファイルを使用します",
|
||||
"disabled": "プロジェクトは無効化されています : editorTheme.projects.enabled=false",
|
||||
"disabledNoFlag": "プロジェクトは無効化されています : 有効にするには editorTheme.projects.enabled=true を設定してください",
|
||||
"git-not-found": "プロジェクトは無効化されています : gitコマンドが見つかりません",
|
||||
"git-version-old": "プロジェクトは無効化されています : git __version__ はサポートされていません。2.xが必要です。",
|
||||
"summary": "Node-REDプロジェクト",
|
||||
"readme": "### 説明\nこれはプロジェクトのREADME.mdファイルです。このファイルには、\nプロジェクトの説明、利用方法、その他の情報を記載します。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"context": {
|
||||
"log-store-init": "コンテクストストア : '__name__' [__info__]",
|
||||
"error-loading-module": "コンテクストストア '__module__' のロードでエラーが発生しました: __message__ ",
|
||||
"error-module-not-defined": "コンテクストストア '__storage__' に 'module' オプションが指定されていません",
|
||||
"error-invalid-module-name": "不正なコンテクストストア名: '__name__'",
|
||||
"error-invalid-default-module": "デフォルトコンテクストストアが不明: '__storage__'",
|
||||
"unknown-store": "不明なコンテクストストア '__name__' が指定されました。デフォルトストアを使用します。"
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -105,12 +105,12 @@ Node.prototype.close = function(removed) {
|
||||
if (promises.length > 0) {
|
||||
return when.settle(promises).then(function() {
|
||||
if (this._context) {
|
||||
context.delete(this._alias||this.id,this.z);
|
||||
return context.delete(this._alias||this.id,this.z);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (this._context) {
|
||||
context.delete(this._alias||this.id,this.z);
|
||||
return context.delete(this._alias||this.id,this.z);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@@ -1,91 +0,0 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var clone = require("clone");
|
||||
var when = require("when");
|
||||
var util = require("../util");
|
||||
|
||||
function createContext(id,seed) {
|
||||
var data = seed || {};
|
||||
var obj = seed || {};
|
||||
obj.get = function get(key) {
|
||||
return util.getMessageProperty(data,key);
|
||||
};
|
||||
obj.set = function set(key, value) {
|
||||
util.setMessageProperty(data,key,value);
|
||||
}
|
||||
obj.keys = function() {
|
||||
var keysData = Object.keys(data);
|
||||
if (seed == null) {
|
||||
return keysData;
|
||||
} else {
|
||||
return keysData.filter(function (key) {
|
||||
return key !== "set" && key !== "get" && key !== "keys";
|
||||
});
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
var contexts = {};
|
||||
var globalContext = null;
|
||||
|
||||
function getContext(localId,flowId) {
|
||||
var contextId = localId;
|
||||
if (flowId) {
|
||||
contextId = localId+":"+flowId;
|
||||
}
|
||||
if (contexts.hasOwnProperty(contextId)) {
|
||||
return contexts[contextId];
|
||||
}
|
||||
var newContext = createContext(contextId);
|
||||
if (flowId) {
|
||||
newContext.flow = getContext(flowId);
|
||||
}
|
||||
if (globalContext) {
|
||||
newContext.global = globalContext;
|
||||
}
|
||||
contexts[contextId] = newContext;
|
||||
return newContext;
|
||||
}
|
||||
function deleteContext(id,flowId) {
|
||||
var contextId = id;
|
||||
if (flowId) {
|
||||
contextId = id+":"+flowId;
|
||||
}
|
||||
delete contexts[contextId];
|
||||
}
|
||||
function clean(flowConfig) {
|
||||
var activeIds = {};
|
||||
var contextId;
|
||||
var node;
|
||||
for (var id in contexts) {
|
||||
if (contexts.hasOwnProperty(id)) {
|
||||
var idParts = id.split(":");
|
||||
if (!flowConfig.allNodes.hasOwnProperty(idParts[0])) {
|
||||
delete contexts[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
init: function(settings) {
|
||||
globalContext = createContext("global",settings.functionGlobalContext || {});
|
||||
},
|
||||
get: getContext,
|
||||
delete: deleteContext,
|
||||
clean:clean
|
||||
};
|
374
red/runtime/nodes/context/index.js
Normal file
374
red/runtime/nodes/context/index.js
Normal file
@@ -0,0 +1,374 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var clone = require("clone");
|
||||
var log = require("../../../util").log; // TODO: separate module
|
||||
var memory = require("./memory");
|
||||
|
||||
var settings;
|
||||
|
||||
// A map of scope id to context instance
|
||||
var contexts = {};
|
||||
|
||||
// A map of store name to instance
|
||||
var stores = {};
|
||||
var storeList = [];
|
||||
var defaultStore;
|
||||
|
||||
// Whether there context storage has been configured or left as default
|
||||
var hasConfiguredStore = false;
|
||||
|
||||
// Unknown Stores
|
||||
var unknownStores = {};
|
||||
|
||||
function logUnknownStore(name) {
|
||||
if (name) {
|
||||
var count = unknownStores[name] || 0;
|
||||
if (count == 0) {
|
||||
log.warn(log._("context.unknown-store", {name: name}));
|
||||
count++;
|
||||
unknownStores[name] = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function init(_settings) {
|
||||
settings = _settings;
|
||||
contexts = {};
|
||||
stores = {};
|
||||
storeList = [];
|
||||
hasConfiguredStore = false;
|
||||
var seed = settings.functionGlobalContext || {};
|
||||
contexts['global'] = createContext("global",seed);
|
||||
// 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
|
||||
stores["_"] = new memory();
|
||||
defaultStore = "memory";
|
||||
}
|
||||
|
||||
function load() {
|
||||
return new Promise(function(resolve,reject) {
|
||||
// load & init plugins in settings.contextStorage
|
||||
var plugins = settings.contextStorage || {};
|
||||
var defaultIsAlias = false;
|
||||
var promises = [];
|
||||
if (plugins && Object.keys(plugins).length > 0) {
|
||||
var hasDefault = plugins.hasOwnProperty('default');
|
||||
var defaultName;
|
||||
for (var pluginName in plugins) {
|
||||
if (plugins.hasOwnProperty(pluginName)) {
|
||||
// "_" is a reserved name - do not allow it to be overridden
|
||||
if (pluginName === "_") {
|
||||
continue;
|
||||
}
|
||||
if (!/^[a-zA-Z0-9_]+$/.test(pluginName)) {
|
||||
return reject(new Error(log._("context.error-invalid-module-name", {name:pluginName})));
|
||||
}
|
||||
|
||||
// Check if this is setting the 'default' context to be a named plugin
|
||||
if (pluginName === "default" && typeof plugins[pluginName] === "string") {
|
||||
// Check the 'default' alias exists before initialising anything
|
||||
if (!plugins.hasOwnProperty(plugins[pluginName])) {
|
||||
return reject(new Error(log._("context.error-invalid-default-module", {storage:plugins["default"]})));
|
||||
}
|
||||
defaultIsAlias = true;
|
||||
continue;
|
||||
}
|
||||
if (!hasDefault && !defaultName) {
|
||||
defaultName = pluginName;
|
||||
}
|
||||
var plugin;
|
||||
if (plugins[pluginName].hasOwnProperty("module")) {
|
||||
// Get the provided config and copy in the 'approved' top-level settings (eg userDir)
|
||||
var config = plugins[pluginName].config || {};
|
||||
copySettings(config, settings);
|
||||
|
||||
if (typeof plugins[pluginName].module === "string") {
|
||||
// This config identifies the module by name - assume it is a built-in one
|
||||
// TODO: check it exists locally, if not, try to require it as-is
|
||||
try {
|
||||
plugin = require("./"+plugins[pluginName].module);
|
||||
} catch(err) {
|
||||
return reject(new Error(log._("context.error-loading-module", {module:plugins[pluginName].module,message:err.toString()})));
|
||||
}
|
||||
} else {
|
||||
// Assume `module` is an already-required module we can use
|
||||
plugin = plugins[pluginName].module;
|
||||
}
|
||||
try {
|
||||
// Create a new instance of the plugin by calling its module function
|
||||
stores[pluginName] = plugin(config);
|
||||
log.info(log._("context.log-store-init", {name:pluginName, info:"module="+plugins[pluginName].module}));
|
||||
} catch(err) {
|
||||
return reject(new Error(log._("context.error-loading-module",{module:pluginName,message:err.toString()})));
|
||||
}
|
||||
} else {
|
||||
// Plugin does not specify a 'module'
|
||||
return reject(new Error(log._("context.error-module-not-defined", {storage:pluginName})));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Open all of the configured contexts
|
||||
for (var plugin in stores) {
|
||||
if (stores.hasOwnProperty(plugin)) {
|
||||
promises.push(stores[plugin].open());
|
||||
}
|
||||
}
|
||||
// There is a 'default' listed in the configuration
|
||||
if (hasDefault) {
|
||||
// If 'default' is an alias, point it at the right module - we have already
|
||||
// checked that it exists. If it isn't an alias, then it will
|
||||
// already be set to a configured store
|
||||
if (defaultIsAlias) {
|
||||
stores["_"] = stores[plugins["default"]];
|
||||
defaultStore = plugins["default"];
|
||||
} else {
|
||||
stores["_"] = stores["default"];
|
||||
defaultStore = "default";
|
||||
}
|
||||
} else if (defaultName) {
|
||||
// No 'default' listed, so pick first in list as the default
|
||||
stores["_"] = stores[defaultName];
|
||||
defaultStore = defaultName;
|
||||
defaultIsAlias = true;
|
||||
} else {
|
||||
// else there were no stores list the config object - fall through
|
||||
// to below where we default to a memory store
|
||||
storeList = ["memory"];
|
||||
defaultStore = "memory";
|
||||
}
|
||||
hasConfiguredStore = true;
|
||||
storeList = Object.keys(stores).filter(n=>!(defaultIsAlias && n==="default") && n!== "_");
|
||||
} else {
|
||||
// No configured plugins
|
||||
log.info(log._("context.log-store-init", {name:"default", info:"module=memory"}));
|
||||
promises.push(stores["_"].open())
|
||||
storeList = ["memory"];
|
||||
defaultStore = "memory";
|
||||
}
|
||||
return resolve(Promise.all(promises));
|
||||
});
|
||||
}
|
||||
|
||||
function copySettings(config, settings){
|
||||
var copy = ["userDir"]
|
||||
config.settings = {};
|
||||
copy.forEach(function(setting){
|
||||
config.settings[setting] = clone(settings[setting]);
|
||||
});
|
||||
}
|
||||
|
||||
function getContextStorage(storage) {
|
||||
if (stores.hasOwnProperty(storage)) {
|
||||
// A known context
|
||||
return stores[storage];
|
||||
} else if (stores.hasOwnProperty("_")) {
|
||||
// Not known, but we have a default to fall back to
|
||||
if (storage !== defaultStore) {
|
||||
// It isn't the default store either, so log it
|
||||
logUnknownStore(storage);
|
||||
}
|
||||
return stores["_"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createContext(id,seed) {
|
||||
// Seed is only set for global context - sourced from functionGlobalContext
|
||||
var scope = id;
|
||||
var obj = seed || {};
|
||||
var seedKeys;
|
||||
var insertSeedValues;
|
||||
if (seed) {
|
||||
seedKeys = Object.keys(seed);
|
||||
insertSeedValues = function(keys,values) {
|
||||
if (!Array.isArray(keys)) {
|
||||
if (values[0] === undefined) {
|
||||
values[0] = seed[keys];
|
||||
}
|
||||
} else {
|
||||
for (var i=0;i<keys.length;i++) {
|
||||
if (values[i] === undefined) {
|
||||
values[i] = seed[keys[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
obj.get = function(key, storage, callback) {
|
||||
var context;
|
||||
if (!storage && !callback) {
|
||||
context = stores["_"];
|
||||
} else {
|
||||
if (typeof storage === 'function') {
|
||||
callback = storage;
|
||||
storage = "_";
|
||||
}
|
||||
if (callback && typeof callback !== 'function'){
|
||||
throw new Error("Callback must be a function");
|
||||
}
|
||||
context = getContextStorage(storage);
|
||||
}
|
||||
if (callback) {
|
||||
if (!seed) {
|
||||
context.get(scope,key,callback);
|
||||
} else {
|
||||
context.get(scope,key,function() {
|
||||
if (arguments[0]) {
|
||||
callback(arguments[0]);
|
||||
return;
|
||||
}
|
||||
var results = Array.prototype.slice.call(arguments,[1]);
|
||||
insertSeedValues(key,results);
|
||||
// Put the err arg back
|
||||
results.unshift(undefined);
|
||||
callback.apply(null,results);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// No callback, attempt to do this synchronously
|
||||
var results = context.get(scope,key);
|
||||
if (seed) {
|
||||
if (Array.isArray(key)) {
|
||||
insertSeedValues(key,results);
|
||||
} else if (results === undefined){
|
||||
results = seed[key];
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
};
|
||||
obj.set = function(key, value, storage, callback) {
|
||||
var context;
|
||||
if (!storage && !callback) {
|
||||
context = stores["_"];
|
||||
} else {
|
||||
if (typeof storage === 'function') {
|
||||
callback = storage;
|
||||
storage = "_";
|
||||
}
|
||||
if (callback && typeof callback !== 'function') {
|
||||
throw new Error("Callback must be a function");
|
||||
}
|
||||
context = getContextStorage(storage);
|
||||
}
|
||||
context.set(scope, key, value, callback);
|
||||
};
|
||||
obj.keys = function(storage, callback) {
|
||||
var context;
|
||||
if (!storage && !callback) {
|
||||
context = stores["_"];
|
||||
} else {
|
||||
if (typeof storage === 'function') {
|
||||
callback = storage;
|
||||
storage = "_";
|
||||
}
|
||||
if (callback && typeof callback !== 'function') {
|
||||
throw new Error("Callback must be a function");
|
||||
}
|
||||
context = getContextStorage(storage);
|
||||
}
|
||||
if (seed) {
|
||||
if (callback) {
|
||||
context.keys(scope, function(err,keys) {
|
||||
callback(err,Array.from(new Set(seedKeys.concat(keys)).keys()));
|
||||
});
|
||||
} else {
|
||||
var keys = context.keys(scope);
|
||||
return Array.from(new Set(seedKeys.concat(keys)).keys())
|
||||
}
|
||||
} else {
|
||||
return context.keys(scope, callback);
|
||||
}
|
||||
};
|
||||
return obj;
|
||||
}
|
||||
|
||||
function getContext(localId,flowId) {
|
||||
var contextId = localId;
|
||||
if (flowId) {
|
||||
contextId = localId+":"+flowId;
|
||||
}
|
||||
if (contexts.hasOwnProperty(contextId)) {
|
||||
return contexts[contextId];
|
||||
}
|
||||
var newContext = createContext(contextId);
|
||||
if (flowId) {
|
||||
newContext.flow = getContext(flowId);
|
||||
}
|
||||
newContext.global = contexts['global'];
|
||||
contexts[contextId] = newContext;
|
||||
return newContext;
|
||||
}
|
||||
|
||||
function deleteContext(id,flowId) {
|
||||
if(!hasConfiguredStore){
|
||||
// only delete context if there's no configured storage.
|
||||
var contextId = id;
|
||||
if (flowId) {
|
||||
contextId = id+":"+flowId;
|
||||
}
|
||||
delete contexts[contextId];
|
||||
return stores["_"].delete(contextId);
|
||||
}else{
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
function clean(flowConfig) {
|
||||
var promises = [];
|
||||
for(var plugin in stores){
|
||||
if(stores.hasOwnProperty(plugin)){
|
||||
promises.push(stores[plugin].clean(Object.keys(flowConfig.allNodes)));
|
||||
}
|
||||
}
|
||||
for (var id in contexts) {
|
||||
if (contexts.hasOwnProperty(id) && id !== "global") {
|
||||
var idParts = id.split(":");
|
||||
if (!flowConfig.allNodes.hasOwnProperty(idParts[0])) {
|
||||
delete contexts[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
function close() {
|
||||
var promises = [];
|
||||
for(var plugin in stores){
|
||||
if(stores.hasOwnProperty(plugin)){
|
||||
promises.push(stores[plugin].close());
|
||||
}
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
function listStores() {
|
||||
return {default:defaultStore,stores:storeList};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
load: load,
|
||||
listStores: listStores,
|
||||
get: getContext,
|
||||
delete: deleteContext,
|
||||
clean: clean,
|
||||
close: close
|
||||
};
|
291
red/runtime/nodes/context/localfilesystem.js
Normal file
291
red/runtime/nodes/context/localfilesystem.js
Normal file
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
/**
|
||||
* Local file-system based context storage
|
||||
*
|
||||
* Configuration options:
|
||||
* {
|
||||
* base: "contexts", // the base directory to use
|
||||
* // default: "contexts"
|
||||
* dir: "/path/to/storage", // the directory to create the base directory in
|
||||
* // default: settings.userDir
|
||||
* cache: true // whether to cache contents in memory
|
||||
* // default: true
|
||||
* }
|
||||
*
|
||||
*
|
||||
* $HOME/.node-red/contexts
|
||||
* ├── global
|
||||
* │ └── global_context.json
|
||||
* ├── <id of Flow 1>
|
||||
* │ ├── flow_context.json
|
||||
* │ ├── <id of Node a>.json
|
||||
* │ └── <id of Node b>.json
|
||||
* └── <id of Flow 2>
|
||||
* ├── flow_context.json
|
||||
* ├── <id of Node x>.json
|
||||
* └── <id of Node y>.json
|
||||
*/
|
||||
|
||||
var fs = require('fs-extra');
|
||||
var path = require("path");
|
||||
var util = require("../../util");
|
||||
|
||||
var MemoryStore = require("./memory");
|
||||
|
||||
function getStoragePath(storageBaseDir, scope) {
|
||||
if(scope.indexOf(":") === -1){
|
||||
if(scope === "global"){
|
||||
return path.join(storageBaseDir,"global",scope);
|
||||
}else{ // scope:flow
|
||||
return path.join(storageBaseDir,scope,"flow");
|
||||
}
|
||||
}else{ // scope:local
|
||||
var ids = scope.split(":")
|
||||
return path.join(storageBaseDir,ids[1],ids[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function getBasePath(config) {
|
||||
var base = config.base || "contexts";
|
||||
var storageBaseDir;
|
||||
if (!config.dir) {
|
||||
if(config.settings && config.settings.userDir){
|
||||
storageBaseDir = path.join(config.settings.userDir, base);
|
||||
}else{
|
||||
try {
|
||||
fs.statSync(path.join(process.env.NODE_RED_HOME,".config.json"));
|
||||
storageBaseDir = path.join(process.env.NODE_RED_HOME, base);
|
||||
} catch(err) {
|
||||
try {
|
||||
// Consider compatibility for older versions
|
||||
if (process.env.HOMEPATH) {
|
||||
fs.statSync(path.join(process.env.HOMEPATH,".node-red",".config.json"));
|
||||
storageBaseDir = path.join(process.env.HOMEPATH, ".node-red", base);
|
||||
}
|
||||
} catch(err) {
|
||||
}
|
||||
if (!storageBaseDir) {
|
||||
storageBaseDir = path.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red", base);
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
storageBaseDir = path.join(config.dir, base);
|
||||
}
|
||||
return storageBaseDir;
|
||||
}
|
||||
|
||||
function loadFile(storagePath){
|
||||
return fs.pathExists(storagePath).then(function(exists){
|
||||
if(exists === true){
|
||||
return fs.readFile(storagePath, "utf8");
|
||||
}else{
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}).catch(function(err){
|
||||
throw Promise.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
function LocalFileSystem(config){
|
||||
this.config = config;
|
||||
this.storageBaseDir = getBasePath(this.config);
|
||||
if (config.hasOwnProperty('cache')?config.cache:true) {
|
||||
this.cache = MemoryStore({});
|
||||
}
|
||||
}
|
||||
|
||||
LocalFileSystem.prototype.open = function(){
|
||||
var self = this;
|
||||
if (this.cache) {
|
||||
var scopes = [];
|
||||
var promises = [];
|
||||
var subdirs = [];
|
||||
var subdirPromises = [];
|
||||
return fs.readdir(self.storageBaseDir).then(function(dirs){
|
||||
dirs.forEach(function(fn) {
|
||||
var p = getStoragePath(self.storageBaseDir ,fn)+".json";
|
||||
scopes.push(fn);
|
||||
promises.push(loadFile(p));
|
||||
subdirs.push(path.join(self.storageBaseDir,fn));
|
||||
subdirPromises.push(fs.readdir(path.join(self.storageBaseDir,fn)));
|
||||
})
|
||||
return Promise.all(subdirPromises);
|
||||
}).then(function(dirs) {
|
||||
dirs.forEach(function(files,i) {
|
||||
files.forEach(function(fn) {
|
||||
if (fn !== 'flow.json' && fn !== 'global.json') {
|
||||
scopes.push(fn.substring(0,fn.length-5)+":"+scopes[i]);
|
||||
promises.push(loadFile(path.join(subdirs[i],fn)))
|
||||
}
|
||||
});
|
||||
})
|
||||
return Promise.all(promises);
|
||||
}).then(function(res) {
|
||||
scopes.forEach(function(scope,i) {
|
||||
var data = res[i]?JSON.parse(res[i]):{};
|
||||
Object.keys(data).forEach(function(key) {
|
||||
self.cache.set(scope,key,data[key]);
|
||||
})
|
||||
});
|
||||
}).catch(function(err){
|
||||
if(err.code == 'ENOENT') {
|
||||
return fs.ensureDir(self.storageBaseDir);
|
||||
}else{
|
||||
return Promise.reject(err);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
LocalFileSystem.prototype.close = function(){
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
LocalFileSystem.prototype.get = function(scope, key, callback) {
|
||||
if (this.cache) {
|
||||
return this.cache.get(scope,key,callback);
|
||||
}
|
||||
if(typeof callback !== "function"){
|
||||
throw new Error("Callback must be a function");
|
||||
}
|
||||
var storagePath = getStoragePath(this.storageBaseDir ,scope);
|
||||
loadFile(storagePath + ".json").then(function(data){
|
||||
if(data){
|
||||
data = JSON.parse(data);
|
||||
if (!Array.isArray(key)) {
|
||||
callback(null, util.getObjectProperty(data,key));
|
||||
} else {
|
||||
var results = [undefined];
|
||||
for (var i=0;i<key.length;i++) {
|
||||
results.push(util.getObjectProperty(data,key[i]))
|
||||
}
|
||||
callback.apply(null,results);
|
||||
}
|
||||
}else{
|
||||
callback(null, undefined);
|
||||
}
|
||||
}).catch(function(err){
|
||||
callback(err);
|
||||
});
|
||||
};
|
||||
|
||||
LocalFileSystem.prototype.set = function(scope, key, value, callback) {
|
||||
var storagePath = getStoragePath(this.storageBaseDir ,scope);
|
||||
if (this.cache) {
|
||||
this.cache.set(scope,key,value,callback);
|
||||
// With cache enabled, no need to re-read the file prior to writing.
|
||||
var newContext = this.cache._export()[scope];
|
||||
fs.outputFile(storagePath + ".json", JSON.stringify(newContext, undefined, 4), "utf8").catch(function(err) {
|
||||
});
|
||||
} else if (callback && typeof callback !== 'function') {
|
||||
throw new Error("Callback must be a function");
|
||||
} else {
|
||||
loadFile(storagePath + ".json").then(function(data){
|
||||
var obj = data ? JSON.parse(data) : {}
|
||||
if (!Array.isArray(key)) {
|
||||
key = [key];
|
||||
value = [value];
|
||||
} else if (!Array.isArray(value)) {
|
||||
// key is an array, but value is not - wrap it as an array
|
||||
value = [value];
|
||||
}
|
||||
for (var i=0;i<key.length;i++) {
|
||||
var v = null;
|
||||
if (i<value.length) {
|
||||
v = value[i];
|
||||
}
|
||||
util.setObjectProperty(obj,key[i],v);
|
||||
}
|
||||
return fs.outputFile(storagePath + ".json", JSON.stringify(obj, undefined, 4), "utf8");
|
||||
}).then(function(){
|
||||
if(typeof callback === "function"){
|
||||
callback(null);
|
||||
}
|
||||
}).catch(function(err){
|
||||
if(typeof callback === "function"){
|
||||
callback(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
LocalFileSystem.prototype.keys = function(scope, callback){
|
||||
if (this.cache) {
|
||||
return this.cache.keys(scope,callback);
|
||||
}
|
||||
if(typeof callback !== "function"){
|
||||
throw new Error("Callback must be a function");
|
||||
}
|
||||
var storagePath = getStoragePath(this.storageBaseDir ,scope);
|
||||
loadFile(storagePath + ".json").then(function(data){
|
||||
if(data){
|
||||
callback(null, Object.keys(JSON.parse(data)));
|
||||
}else{
|
||||
callback(null, []);
|
||||
}
|
||||
}).catch(function(err){
|
||||
callback(err);
|
||||
});
|
||||
};
|
||||
|
||||
LocalFileSystem.prototype.delete = function(scope){
|
||||
var cachePromise;
|
||||
if (this.cache) {
|
||||
cachePromise = this.cache.delete(scope);
|
||||
} else {
|
||||
cachePromise = Promise.resolve();
|
||||
}
|
||||
var that = this;
|
||||
return cachePromise.then(function() {
|
||||
var storagePath = getStoragePath(that.storageBaseDir,scope);
|
||||
return fs.remove(storagePath + ".json");
|
||||
});
|
||||
}
|
||||
|
||||
LocalFileSystem.prototype.clean = function(activeNodes){
|
||||
var self = this;
|
||||
var cachePromise;
|
||||
if (this.cache) {
|
||||
cachePromise = this.cache.clean(activeNodes);
|
||||
} else {
|
||||
cachePromise = Promise.resolve();
|
||||
}
|
||||
return cachePromise.then(function() {
|
||||
return fs.readdir(self.storageBaseDir).then(function(dirs){
|
||||
return Promise.all(dirs.reduce(function(result, item){
|
||||
if(item !== "global" && activeNodes.indexOf(item) === -1){
|
||||
result.push(fs.remove(path.join(self.storageBaseDir,item)));
|
||||
}
|
||||
return result;
|
||||
},[]));
|
||||
}).catch(function(err){
|
||||
if(err.code == 'ENOENT') {
|
||||
return Promise.resolve();
|
||||
}else{
|
||||
return Promise.reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function(config){
|
||||
return new LocalFileSystem(config);
|
||||
};
|
166
red/runtime/nodes/context/memory.js
Normal file
166
red/runtime/nodes/context/memory.js
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var util = require("../../util");
|
||||
|
||||
function Memory(config){
|
||||
this.data = {};
|
||||
}
|
||||
|
||||
Memory.prototype.open = function(){
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
Memory.prototype.close = function(){
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
Memory.prototype._getOne = function(scope, key) {
|
||||
var value;
|
||||
var error;
|
||||
if(this.data[scope]){
|
||||
value = util.getObjectProperty(this.data[scope], key);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
Memory.prototype.get = function(scope, key, callback) {
|
||||
var value;
|
||||
var error;
|
||||
if (!Array.isArray(key)) {
|
||||
try {
|
||||
value = this._getOne(scope,key);
|
||||
} catch(err) {
|
||||
if (!callback) {
|
||||
throw err;
|
||||
}
|
||||
error = err;
|
||||
}
|
||||
if (callback) {
|
||||
callback(error,value);
|
||||
return;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
value = [];
|
||||
for (var i=0; i<key.length; i++) {
|
||||
try {
|
||||
value.push(this._getOne(scope,key[i]));
|
||||
} catch(err) {
|
||||
if (!callback) {
|
||||
throw err;
|
||||
} else {
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (callback) {
|
||||
callback.apply(null, [undefined].concat(value));
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
Memory.prototype.set = function(scope, key, value, callback) {
|
||||
if(!this.data[scope]){
|
||||
this.data[scope] = {};
|
||||
}
|
||||
var error;
|
||||
if (!Array.isArray(key)) {
|
||||
key = [key];
|
||||
value = [value];
|
||||
} else if (!Array.isArray(value)) {
|
||||
// key is an array, but value is not - wrap it as an array
|
||||
value = [value];
|
||||
}
|
||||
try {
|
||||
for (var i=0; i<key.length; i++) {
|
||||
var v = null;
|
||||
if (i < value.length) {
|
||||
v = value[i];
|
||||
}
|
||||
util.setObjectProperty(this.data[scope],key[i],v);
|
||||
}
|
||||
} catch(err) {
|
||||
if (callback) {
|
||||
error = err;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
if(callback){
|
||||
callback(error);
|
||||
}
|
||||
};
|
||||
|
||||
Memory.prototype.keys = function(scope, callback){
|
||||
var values = [];
|
||||
var error;
|
||||
try{
|
||||
if(this.data[scope]){
|
||||
if (scope !== "global") {
|
||||
values = Object.keys(this.data[scope]);
|
||||
} else {
|
||||
values = Object.keys(this.data[scope]).filter(function (key) {
|
||||
return key !== "set" && key !== "get" && key !== "keys";
|
||||
});
|
||||
}
|
||||
}
|
||||
}catch(err){
|
||||
if(callback){
|
||||
error = err;
|
||||
}else{
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
if(callback){
|
||||
if(error){
|
||||
callback(error);
|
||||
} else {
|
||||
callback(null, values);
|
||||
}
|
||||
} else {
|
||||
return values;
|
||||
}
|
||||
};
|
||||
|
||||
Memory.prototype.delete = function(scope){
|
||||
delete this.data[scope];
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
Memory.prototype.clean = function(activeNodes){
|
||||
for(var id in this.data){
|
||||
if(this.data.hasOwnProperty(id) && id !== "global"){
|
||||
var idParts = id.split(":");
|
||||
if(activeNodes.indexOf(idParts[0]) === -1){
|
||||
delete this.data[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
Memory.prototype._export = function() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
module.exports = function(config){
|
||||
return new Memory(config);
|
||||
};
|
@@ -163,11 +163,12 @@ function setFlows(_config,type,muteLog,forceStart) {
|
||||
activeFlowConfig = newFlowConfig;
|
||||
if (forceStart || started) {
|
||||
return stop(type,diff,muteLog).then(function() {
|
||||
context.clean(activeFlowConfig);
|
||||
start(type,diff,muteLog).then(function() {
|
||||
events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true});
|
||||
return context.clean(activeFlowConfig).then(function() {
|
||||
start(type,diff,muteLog).then(function() {
|
||||
events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true});
|
||||
});
|
||||
return flowRevision;
|
||||
});
|
||||
return flowRevision;
|
||||
}).catch(function(err) {
|
||||
})
|
||||
} else {
|
||||
|
@@ -187,6 +187,8 @@ module.exports = {
|
||||
createNode: createNode,
|
||||
getNode: flows.get,
|
||||
eachNode: flows.eachNode,
|
||||
getContext: context.get,
|
||||
|
||||
|
||||
paletteEditorEnabled: registry.paletteEditorEnabled,
|
||||
installModule: installModule,
|
||||
@@ -236,5 +238,10 @@ module.exports = {
|
||||
setCredentialSecret: credentials.setKey,
|
||||
clearCredentials: credentials.clear,
|
||||
exportCredentials: credentials.export,
|
||||
getCredentialKeyType: credentials.getKeyType
|
||||
getCredentialKeyType: credentials.getKeyType,
|
||||
|
||||
// Contexts
|
||||
loadContextsPlugin: context.load,
|
||||
closeContextsPlugin: context.close,
|
||||
listContextStores: context.listStores
|
||||
};
|
||||
|
@@ -48,7 +48,9 @@ function runGitCommand(args,cwd,env) {
|
||||
var err = new Error(stderr);
|
||||
err.stdout = stdout;
|
||||
err.stderr = stderr;
|
||||
if(/Connection refused/i.test(stderr)) {
|
||||
if (/Connection refused/i.test(stderr)) {
|
||||
err.code = "git_connection_failed";
|
||||
} else if (/Connection timed out/i.test(stderr)) {
|
||||
err.code = "git_connection_failed";
|
||||
} else if (/fatal: could not read/i.test(stderr)) {
|
||||
// Username/Password
|
||||
|
@@ -16,6 +16,8 @@
|
||||
|
||||
var clone = require("clone");
|
||||
var jsonata = require("jsonata");
|
||||
var safeJSONStringify = require("json-stringify-safe");
|
||||
var util = require("util");
|
||||
|
||||
function generateId() {
|
||||
return (1+Math.random()*4294967295).toString(16);
|
||||
@@ -233,10 +235,13 @@ function normalisePropertyExpression(str) {
|
||||
}
|
||||
|
||||
function getMessageProperty(msg,expr) {
|
||||
var result = null;
|
||||
if (expr.indexOf('msg.')===0) {
|
||||
expr = expr.substring(4);
|
||||
}
|
||||
return getObjectProperty(msg,expr);
|
||||
}
|
||||
function getObjectProperty(msg,expr) {
|
||||
var result = null;
|
||||
var msgPropParts = normalisePropertyExpression(expr);
|
||||
var m;
|
||||
msgPropParts.reduce(function(obj, key) {
|
||||
@@ -247,12 +252,15 @@ function getMessageProperty(msg,expr) {
|
||||
}
|
||||
|
||||
function setMessageProperty(msg,prop,value,createMissing) {
|
||||
if (typeof createMissing === 'undefined') {
|
||||
createMissing = (typeof value !== 'undefined');
|
||||
}
|
||||
if (prop.indexOf('msg.')===0) {
|
||||
prop = prop.substring(4);
|
||||
}
|
||||
return setObjectProperty(msg,prop,value,createMissing);
|
||||
}
|
||||
function setObjectProperty(msg,prop,value,createMissing) {
|
||||
if (typeof createMissing === 'undefined') {
|
||||
createMissing = (typeof value !== 'undefined');
|
||||
}
|
||||
var msgPropParts = normalisePropertyExpression(prop);
|
||||
var depth = 0;
|
||||
var length = msgPropParts.length;
|
||||
@@ -284,7 +292,7 @@ function setMessageProperty(msg,prop,value,createMissing) {
|
||||
}
|
||||
obj = obj[key];
|
||||
} else {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
obj = obj[key];
|
||||
@@ -303,7 +311,7 @@ function setMessageProperty(msg,prop,value,createMissing) {
|
||||
}
|
||||
}
|
||||
|
||||
function evaluteEnvProperty(value) {
|
||||
function evaluateEnvProperty(value) {
|
||||
if (/^\${[^}]+}$/.test(value)) {
|
||||
// ${ENV_VAR}
|
||||
value = value.substring(2,value.length-1);
|
||||
@@ -320,35 +328,63 @@ function evaluteEnvProperty(value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
function evaluateNodeProperty(value, type, node, msg) {
|
||||
var parseContextStore = function(key) {
|
||||
var parts = {};
|
||||
var m = /^#:\((\S+?)\)::(.*)$/.exec(key);
|
||||
if (m) {
|
||||
parts.store = m[1];
|
||||
parts.key = m[2];
|
||||
} else {
|
||||
parts.key = key;
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
function evaluateNodeProperty(value, type, node, msg, callback) {
|
||||
var result = value;
|
||||
if (type === 'str') {
|
||||
return ""+value;
|
||||
result = ""+value;
|
||||
} else if (type === 'num') {
|
||||
return Number(value);
|
||||
result = Number(value);
|
||||
} else if (type === 'json') {
|
||||
return JSON.parse(value);
|
||||
result = JSON.parse(value);
|
||||
} else if (type === 're') {
|
||||
return new RegExp(value);
|
||||
result = new RegExp(value);
|
||||
} else if (type === 'date') {
|
||||
return Date.now();
|
||||
result = Date.now();
|
||||
} else if (type === 'bin') {
|
||||
var data = JSON.parse(value);
|
||||
return Buffer.from(data);
|
||||
result = Buffer.from(data);
|
||||
} else if (type === 'msg' && msg) {
|
||||
return getMessageProperty(msg,value);
|
||||
} else if (type === 'flow' && node) {
|
||||
return node.context().flow.get(value);
|
||||
} else if (type === 'global' && node) {
|
||||
return node.context().global.get(value);
|
||||
try {
|
||||
result = getMessageProperty(msg,value);
|
||||
} catch(err) {
|
||||
if (callback) {
|
||||
callback(err);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if ((type === 'flow' || type === 'global') && node) {
|
||||
var contextKey = parseContextStore(value);
|
||||
result = node.context()[type].get(contextKey.key,contextKey.store,callback);
|
||||
if (callback) {
|
||||
return;
|
||||
}
|
||||
} else if (type === 'bool') {
|
||||
return /^true$/i.test(value);
|
||||
result = /^true$/i.test(value);
|
||||
} else if (type === 'jsonata') {
|
||||
var expr = prepareJSONataExpression(value,node);
|
||||
return evaluateJSONataExpression(expr,msg);
|
||||
result = evaluateJSONataExpression(expr,msg);
|
||||
} else if (type === 'env') {
|
||||
return evaluteEnvProperty(value);
|
||||
result = evaluateEnvProperty(value);
|
||||
}
|
||||
if (callback) {
|
||||
callback(null,result);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function prepareJSONataExpression(value,node) {
|
||||
@@ -364,15 +400,44 @@ function prepareJSONataExpression(value,node) {
|
||||
})
|
||||
expr.registerFunction('clone', cloneMessage, '<(oa)-:o>');
|
||||
expr._legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(value);
|
||||
expr._node = node;
|
||||
return expr;
|
||||
}
|
||||
|
||||
function evaluateJSONataExpression(expr,msg) {
|
||||
function evaluateJSONataExpression(expr,msg,callback) {
|
||||
var context = msg;
|
||||
if (expr._legacyMode) {
|
||||
context = {msg:msg};
|
||||
}
|
||||
return expr.evaluate(context);
|
||||
var bindings = {};
|
||||
|
||||
if (callback) {
|
||||
// If callback provided, need to override the pre-assigned sync
|
||||
// context functions to be their async variants
|
||||
bindings.flowContext = function(val, store) {
|
||||
return new Promise((resolve,reject) => {
|
||||
expr._node.context().flow.get(val, store, function(err,value) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
bindings.globalContext = function(val, store) {
|
||||
return new Promise((resolve,reject) => {
|
||||
expr._node.context().global.get(val, store, function(err,value) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
return expr.evaluate(context, bindings, callback);
|
||||
}
|
||||
|
||||
|
||||
@@ -389,7 +454,135 @@ function normaliseNodeTypeName(name) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function encodeObject(msg,opts) {
|
||||
var debuglength = 1000;
|
||||
if (opts && opts.hasOwnProperty('maxLength')) {
|
||||
debuglength = opts.maxLength;
|
||||
}
|
||||
var msgType = typeof msg.msg;
|
||||
if (msg.msg instanceof Error) {
|
||||
msg.format = "error";
|
||||
var errorMsg = {};
|
||||
if (msg.msg.name) {
|
||||
errorMsg.name = msg.msg.name;
|
||||
}
|
||||
if (msg.msg.hasOwnProperty('message')) {
|
||||
errorMsg.message = msg.msg.message;
|
||||
} else {
|
||||
errorMsg.message = msg.msg.toString();
|
||||
}
|
||||
msg.msg = JSON.stringify(errorMsg);
|
||||
} else if (msg.msg instanceof Buffer) {
|
||||
msg.format = "buffer["+msg.msg.length+"]";
|
||||
msg.msg = msg.msg.toString('hex');
|
||||
if (msg.msg.length > debuglength) {
|
||||
msg.msg = msg.msg.substring(0,debuglength);
|
||||
}
|
||||
} else if (msg.msg && msgType === 'object') {
|
||||
try {
|
||||
msg.format = msg.msg.constructor.name || "Object";
|
||||
// Handle special case of msg.req/res objects from HTTP In node
|
||||
if (msg.format === "IncomingMessage" || msg.format === "ServerResponse") {
|
||||
msg.format = "Object";
|
||||
}
|
||||
} catch(err) {
|
||||
msg.format = "Object";
|
||||
}
|
||||
if (/error/i.test(msg.format)) {
|
||||
msg.msg = JSON.stringify({
|
||||
name: msg.msg.name,
|
||||
message: msg.msg.message
|
||||
});
|
||||
} else {
|
||||
var isArray = util.isArray(msg.msg);
|
||||
if (isArray) {
|
||||
msg.format = "array["+msg.msg.length+"]";
|
||||
if (msg.msg.length > debuglength) {
|
||||
// msg.msg = msg.msg.slice(0,debuglength);
|
||||
msg.msg = {
|
||||
__enc__: true,
|
||||
type: "array",
|
||||
data: msg.msg.slice(0,debuglength),
|
||||
length: msg.msg.length
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isArray || (msg.format === "Object")) {
|
||||
msg.msg = safeJSONStringify(msg.msg, function(key, value) {
|
||||
if (key === '_req' || key === '_res') {
|
||||
value = {
|
||||
__enc__: true,
|
||||
type: "internal"
|
||||
}
|
||||
} else if (value instanceof Error) {
|
||||
value = value.toString()
|
||||
} else if (util.isArray(value) && value.length > debuglength) {
|
||||
value = {
|
||||
__enc__: true,
|
||||
type: "array",
|
||||
data: value.slice(0,debuglength),
|
||||
length: value.length
|
||||
}
|
||||
} else if (typeof value === 'string') {
|
||||
if (value.length > debuglength) {
|
||||
value = value.substring(0,debuglength)+"...";
|
||||
}
|
||||
} else if (typeof value === 'function') {
|
||||
value = {
|
||||
__enc__: true,
|
||||
type: "function"
|
||||
}
|
||||
} else if (typeof value === 'number') {
|
||||
if (isNaN(value) || value === Infinity || value === -Infinity) {
|
||||
value = {
|
||||
__enc__: true,
|
||||
type: "number",
|
||||
data: value.toString()
|
||||
}
|
||||
}
|
||||
} else if (value && value.constructor) {
|
||||
if (value.type === "Buffer") {
|
||||
value.__enc__ = true;
|
||||
value.length = value.data.length;
|
||||
if (value.length > debuglength) {
|
||||
value.data = value.data.slice(0,debuglength);
|
||||
}
|
||||
} else if (value.constructor.name === "ServerResponse") {
|
||||
value = "[internal]"
|
||||
} else if (value.constructor.name === "Socket") {
|
||||
value = "[internal]"
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}," ");
|
||||
} else {
|
||||
try { msg.msg = msg.msg.toString(); }
|
||||
catch(e) { msg.msg = "[Type not printable]"; }
|
||||
}
|
||||
}
|
||||
} else if (msgType === "function") {
|
||||
msg.format = "function";
|
||||
msg.msg = "[function]"
|
||||
} else if (msgType === "boolean") {
|
||||
msg.format = "boolean";
|
||||
msg.msg = msg.msg.toString();
|
||||
} else if (msgType === "number") {
|
||||
msg.format = "number";
|
||||
msg.msg = msg.msg.toString();
|
||||
} else if (msg.msg === null || msgType === "undefined") {
|
||||
msg.format = (msg.msg === null)?"null":"undefined";
|
||||
msg.msg = "(undefined)";
|
||||
} else {
|
||||
msg.format = "string["+msg.msg.length+"]";
|
||||
if (msg.msg.length > debuglength) {
|
||||
msg.msg = msg.msg.substring(0,debuglength)+"...";
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
encodeObject: encodeObject,
|
||||
ensureString: ensureString,
|
||||
ensureBuffer: ensureBuffer,
|
||||
cloneMessage: cloneMessage,
|
||||
@@ -397,9 +590,12 @@ module.exports = {
|
||||
generateId: generateId,
|
||||
getMessageProperty: getMessageProperty,
|
||||
setMessageProperty: setMessageProperty,
|
||||
getObjectProperty: getObjectProperty,
|
||||
setObjectProperty: setObjectProperty,
|
||||
evaluateNodeProperty: evaluateNodeProperty,
|
||||
normalisePropertyExpression: normalisePropertyExpression,
|
||||
normaliseNodeTypeName: normaliseNodeTypeName,
|
||||
prepareJSONataExpression: prepareJSONataExpression,
|
||||
evaluateJSONataExpression: evaluateJSONataExpression
|
||||
evaluateJSONataExpression: evaluateJSONataExpression,
|
||||
parseContextStore: parseContextStore
|
||||
};
|
||||
|
@@ -15,6 +15,7 @@
|
||||
**/
|
||||
|
||||
var i18n = require("i18next");
|
||||
|
||||
var when = require("when");
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
@@ -34,9 +35,9 @@ function registerMessageCatalogs(catalogs) {
|
||||
|
||||
function registerMessageCatalog(namespace,dir,file) {
|
||||
return initPromise.then(function() {
|
||||
when.promise(function(resolve,reject) {
|
||||
return new Promise((resolve,reject) => {
|
||||
resourceMap[namespace] = { basedir:dir, file:file};
|
||||
i18n.loadNamespace(namespace,function() {
|
||||
i18n.loadNamespaces(namespace,function() {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
@@ -56,7 +57,9 @@ function mergeCatalog(fallback,catalog) {
|
||||
}
|
||||
|
||||
var MessageFileLoader = {
|
||||
fetchOne: function(lng, ns, callback) {
|
||||
type: "backend",
|
||||
init: function(services, backendOptions, i18nextOptions) {},
|
||||
read: function(lng, ns, callback) {
|
||||
if (resourceMap[ns]) {
|
||||
var file = path.join(resourceMap[ns].basedir,lng,resourceMap[ns].file);
|
||||
//console.log(file);
|
||||
@@ -97,20 +100,27 @@ function getCurrentLocale() {
|
||||
|
||||
function init() {
|
||||
if (!initPromise) {
|
||||
initPromise = when.promise(function(resolve,reject) {
|
||||
i18n.backend(MessageFileLoader);
|
||||
// Keep this as a 'when' promise as top-level red.js uses 'otherwise'
|
||||
// and embedded users of NR may have copied that.
|
||||
initPromise = when.promise((resolve,reject) => {
|
||||
i18n.use(MessageFileLoader);
|
||||
var opt = {
|
||||
ns: {
|
||||
namespaces: [],
|
||||
defaultNs: "runtime"
|
||||
},
|
||||
fallbackLng: [defaultLang]
|
||||
// debug: true,
|
||||
defaultNS: "runtime",
|
||||
ns: [],
|
||||
fallbackLng: defaultLang,
|
||||
interpolation: {
|
||||
unescapeSuffix: 'HTML',
|
||||
escapeValue: false,
|
||||
prefix: '__',
|
||||
suffix: '__'
|
||||
}
|
||||
};
|
||||
var lang = getCurrentLocale();
|
||||
if (lang) {
|
||||
opt.lng = lang;
|
||||
}
|
||||
i18n.init(opt,function() {
|
||||
i18n.init(opt ,function() {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
@@ -147,5 +157,6 @@ obj['_'] = function() {
|
||||
// opts.defaultValue = def;
|
||||
//}
|
||||
//console.log(arguments);
|
||||
return i18n.t.apply(null,arguments);
|
||||
var res = i18n.t.apply(i18n,arguments);
|
||||
return res;
|
||||
}
|
||||
|
Reference in New Issue
Block a user