Merge branch 'master' into dev

This commit is contained in:
Nick O'Leary
2020-03-30 23:41:33 +01:00
140 changed files with 5068 additions and 1397 deletions

View File

@@ -57,6 +57,8 @@ var api = module.exports = {
* Sets the current flow configuration
* @param {Object} opts
* @param {User} opts.user - the user calling the api
* @param {Object} opts.flows - the flow configuration: `{flows: [..], credentials: {}}`
* @param {Object} opts.deploymentType - the type of deployment - "full", "nodes", "flows", "reload"
* @param {Object} opts.req - the request to log (optional)
* @return {Promise<Flows>} - the active flow configuration
* @memberof @node-red/runtime_flows
@@ -83,7 +85,7 @@ var api = module.exports = {
return reject(err);
}
}
apiPromise = runtime.nodes.setFlows(flows.flows,deploymentType);
apiPromise = runtime.nodes.setFlows(flows.flows,flows.credentials,deploymentType);
}
apiPromise.then(function(flowId) {
return resolve({rev:flowId});

View File

@@ -193,6 +193,7 @@ Node.prototype.emit = function(event, ...args) {
*/
Node.prototype._emitInput = function(arg) {
var node = this;
this.metric("receive", arg);
if (node._inputCallback) {
// Just one callback registered.
try {
@@ -448,7 +449,6 @@ Node.prototype.receive = function(msg) {
if (!msg._msgid) {
msg._msgid = redUtil.generateId();
}
this.metric("receive",msg);
this.emit("input",msg);
};

View File

@@ -420,15 +420,39 @@ function createRootContext() {
Object.defineProperties(obj, {
get: {
value: function(key, storage, callback) {
if (!callback && typeof storage === 'function') {
callback = storage;
storage = undefined;
}
if (callback) {
callback()
return;
}
return undefined;
}
},
set: {
value: function(key, value, storage, callback) {
if (!callback && typeof storage === 'function') {
callback = storage;
storage = undefined;
}
if (callback) {
callback()
return
}
}
},
keys: {
value: function(storage, callback) {
if (!callback && typeof storage === 'function') {
callback = storage;
storage = undefined;
}
if (callback) {
callback();
return;
}
return undefined;
}
}
@@ -436,25 +460,48 @@ function createRootContext() {
return obj;
}
function getContext(localId,flowId,parent) {
var contextId = localId;
/**
* Get a flow-level context object.
* @param {string} flowId [description]
* @param {string} parentFlowId the id of the parent flow. undefined
* @return {object}} the context object
*/
function getFlowContext(flowId,parentFlowId) {
if (contexts.hasOwnProperty(flowId)) {
return contexts[flowId];
}
var parentContext = contexts[parentFlowId];
if (!parentContext) {
parentContext = createRootContext();
contexts[parentFlowId] = parentContext;
// throw new Error("Flow "+flowId+" is missing parent context "+parentFlowId);
}
var newContext = createContext(flowId,undefined,parentContext);
contexts[flowId] = newContext;
return newContext;
}
function getContext(nodeId, flowId) {
var contextId = nodeId;
if (flowId) {
contextId = localId+":"+flowId;
contextId = nodeId+":"+flowId;
}
if (contexts.hasOwnProperty(contextId)) {
return contexts[contextId];
}
var newContext = createContext(contextId,undefined,parent);
var newContext = createContext(contextId);
if (flowId) {
var node = flows.get(flowId);
var parent = undefined;
if (node && node.type.startsWith("subflow:")) {
parent = node.context().flow;
var flowContext = contexts[flowId];
if (!flowContext) {
// This is most likely due to a unit test for a node which doesn't
// initialise the flow properly.
// To keep things working, initialise the missing context.
// This *does not happen* in normal node-red operation
flowContext = createContext(flowId,undefined,createRootContext());
contexts[flowId] = flowContext;
}
else {
parent = createRootContext();
}
var flowContext = getContext(flowId,undefined,parent);
Object.defineProperty(newContext, 'flow', {
value: flowContext
});
@@ -466,6 +513,39 @@ function getContext(localId,flowId,parent) {
return newContext;
}
//
// function getContext(localId,flowId,parent) {
// var contextId = localId;
// if (flowId) {
// contextId = localId+":"+flowId;
// }
// console.log("getContext",localId,flowId,"known?",contexts.hasOwnProperty(contextId));
// if (contexts.hasOwnProperty(contextId)) {
// return contexts[contextId];
// }
// var newContext = createContext(contextId,undefined,parent);
// if (flowId) {
// var node = flows.get(flowId);
// console.log("flows,get",flowId,node&&node.type)
// var parent = undefined;
// if (node && node.type.startsWith("subflow:")) {
// parent = node.context().flow;
// }
// else {
// parent = createRootContext();
// }
// var flowContext = getContext(flowId,undefined,parent);
// Object.defineProperty(newContext, 'flow', {
// value: flowContext
// });
// }
// Object.defineProperty(newContext, 'global', {
// value: contexts['global']
// })
// contexts[contextId] = newContext;
// return newContext;
// }
function deleteContext(id,flowId) {
if(!hasConfiguredStore){
// only delete context if there's no configured storage.
@@ -517,6 +597,7 @@ module.exports = {
load: load,
listStores: listStores,
get: getContext,
getFlowContext:getFlowContext,
delete: deleteContext,
clean: clean,
close: close

View File

@@ -18,6 +18,7 @@ var clone = require("clone");
var redUtil = require("@node-red/util").util;
var flowUtil = require("./util");
var events = require("../../events");
const context = require('../context');
var Subflow;
var Log;
@@ -54,6 +55,8 @@ class Flow {
this.catchNodes = [];
this.statusNodes = [];
this.path = this.id;
// Ensure a context exists for this flow
this.context = context.getFlowContext(this.id,this.parent.id);
}
/**

View File

@@ -16,7 +16,7 @@
const clone = require("clone");
const Flow = require('./Flow').Flow;
const context = require('../context');
const util = require("util");
const redUtil = require("@node-red/util").util;
@@ -246,6 +246,10 @@ class Subflow extends Flow {
this.node.on("input", function(msg) { this.send(msg);});
this.node.on("close", function() { this.status({}); })
this.node.status = status => this.parent.handleStatus(this.node,status);
// Create a context instance
console.log("Node.context",this.type,"id:",this._alias||this.id,"z:",this.z)
this._context = context.get(this._alias||this.id,this.z);
this.node._updateWires = this.node.updateWires;
@@ -483,7 +487,7 @@ function createNodeInSubflow(subflowInstanceId, def) {
* properties in the nodes object to reference the new node ids.
* This handles:
* - node.wires,
* - node.scope of Catch and Status nodes,
* - node.scope of Complete, Catch and Status nodes,
* - node.XYZ for any property where XYZ is recognised as an old property
* @param {[type]} nodes [description]
* @param {[type]} nodeMap [description]
@@ -506,7 +510,7 @@ function remapSubflowNodes(nodes,nodeMap) {
}
}
}
if ((node.type === 'catch' || node.type === 'status') && node.scope) {
if ((node.type === 'complete' || node.type === 'catch' || node.type === 'status') && node.scope) {
node.scope = node.scope.map(function(id) {
return nodeMap[id]?nodeMap[id].id:""
})

View File

@@ -106,15 +106,20 @@ function load(forceStart) {
// This is a force reload from the API - disable safeMode
delete settings.safeMode;
}
return setFlows(null,"load",false,forceStart);
return setFlows(null,null,"load",false,forceStart);
}
/*
* _config - new node array configuration
* _credentials - new credentials configuration (optional)
* type - full/nodes/flows/load (default full)
* muteLog - don't emit the standard log messages (used for individual flow api)
*/
function setFlows(_config,type,muteLog,forceStart) {
function setFlows(_config,_credentials,type,muteLog,forceStart) {
if (typeof _credentials === "string") {
type = _credentials;
_credentials = null;
}
type = type||"full";
if (settings.safeMode) {
if (type !== "load") {
@@ -155,16 +160,27 @@ function setFlows(_config,type,muteLog,forceStart) {
delete newFlowConfig.allNodes[id].credentials;
}
}
var credsDirty;
// Allow the credential store to remove anything no longer needed
credentials.clean(config);
if (_credentials) {
// A full set of credentials have been provided. Use those instead
configSavePromise = credentials.load(_credentials);
credsDirty = true;
} else {
// Allow the credential store to remove anything no longer needed
credentials.clean(config);
// Remember whether credentials need saving or not
var credsDirty = credentials.dirty();
// Remember whether credentials need saving or not
var credsDirty = credentials.dirty();
configSavePromise = Promise.resolve();
}
// Get the latest credentials and ask storage to save them (if needed)
// as well as the new flow configuration.
configSavePromise = credentials.export().then(function(creds) {
configSavePromise = configSavePromise.then(function() {
return credentials.export()
}).then(function(creds) {
var saveConfig = {
flows: config,
credentialsDirty:credsDirty,
@@ -515,7 +531,7 @@ function addFlow(flow) {
var newConfig = clone(activeConfig.flows);
newConfig = newConfig.concat(nodes);
return setFlows(newConfig,'flows',true).then(function() {
return setFlows(newConfig,null,'flows',true).then(function() {
log.info(log._("nodes.flows.added-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
return flow.id;
});
@@ -646,7 +662,7 @@ function updateFlow(id,newFlow) {
}
newConfig = newConfig.concat(nodes);
return setFlows(newConfig,'flows',true).then(function() {
return setFlows(newConfig,null,'flows',true).then(function() {
log.info(log._("nodes.flows.updated-flow",{label:(label?label+" ":"")+"["+id+"]"}));
})
}
@@ -668,7 +684,7 @@ function removeFlow(id) {
return node.z !== id && node.id !== id;
});
return setFlows(newConfig,'flows',true).then(function() {
return setFlows(newConfig,null,'flows',true).then(function() {
log.info(log._("nodes.flows.removed-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
});
}

View File

@@ -25,73 +25,54 @@ var settings;
var libDir;
var libFlowsDir;
function getFileMeta(root,path) {
var fn = fspath.join(root,path);
var fd = fs.openSync(fn,"r");
function getFileMeta(root, path) {
var fn = fspath.join(root, path);
var fd = fs.openSync(fn, 'r');
var size = fs.fstatSync(fd).size;
var meta = {};
var read = 0;
var length = 10;
var remaining = "";
var remaining = Buffer.alloc(0);
var buffer = Buffer.alloc(length);
while(read < size) {
read+=fs.readSync(fd,buffer,0,length);
var data = remaining+buffer.toString();
var parts = data.split("\n");
remaining = parts.splice(-1);
for (var i=0;i<parts.length;i+=1) {
var match = /^\/\/ (\w+): (.*)/.exec(parts[i]);
if (match) {
meta[match[1]] = match[2];
} else {
read = size;
break;
while (read < size) {
read += fs.readSync(fd, buffer, 0, length);
var data = Buffer.concat([remaining, buffer]);
var index = data.lastIndexOf(0x0a);
if (index !== -1) {
var parts = data.slice(0, index).toString().split('\n');
for (var i = 0; i < parts.length; i++) {
var match = /^\/\/ (\w+): (.*)/.exec(parts[i]);
if (match) {
meta[match[1]] = match[2];
} else {
read = size;
break;
}
}
remaining = data.slice(index + 1);
} else {
remaining = data;
}
}
fs.closeSync(fd);
return meta;
}
function getFileBody(root,path) {
var body = "";
var fn = fspath.join(root,path);
var fd = fs.openSync(fn,"r");
var size = fs.fstatSync(fd).size;
function getFileBody(root, path) {
var body = '';
var fn = fspath.join(root, path);
var data = fs.readFileSync(fn, 'utf8');
var parts = data.split('\n');
var scanning = true;
var read = 0;
var length = 50;
var remaining = "";
var buffer = Buffer.alloc(length);
while(read < size) {
var thisRead = fs.readSync(fd,buffer,0,length);
if (scanning) {
var data = remaining+buffer.slice(0,thisRead).toString();
read += thisRead;
var parts = data.split("\n");
if (read < size) {
remaining = parts.splice(-1)[0];
} else {
remaining = "";
}
for (var i=0;i<parts.length;i+=1) {
if (! /^\/\/ \w+: /.test(parts[i])) {
scanning = false;
body += (body.length > 0?"\n":"")+parts[i];
}
}
if (!scanning) {
body += remaining;
}
} else {
read += thisRead;
body += buffer.slice(0,thisRead).toString();
for (var i = 0; i < parts.length; i++) {
if (! /^\/\/ \w+: /.test(parts[i]) || !scanning) {
body += (body.length > 0 ? '\n' : '') + parts[i];
scanning = false;
}
}
fs.closeSync(fd);
return body;
}
function getLibraryEntry(type,path) {
var root = fspath.join(libDir,type);
var rootPath = fspath.join(libDir,type,path);

View File

@@ -41,6 +41,9 @@ function runGitCommand(args,cwd,env,emit) {
err.code = "git_connection_failed";
} else if (/Connection timed out/i.test(stderr)) {
err.code = "git_connection_failed";
} else if(/Host key verification failed/i.test(stderr)) {
// TODO: handle host key verification errors separately
err.code = "git_host_key_verification_failed";
} else if (/fatal: could not read/i.test(stderr)) {
// Username/Password
err.code = "git_auth_failed";
@@ -48,9 +51,6 @@ function runGitCommand(args,cwd,env,emit) {
err.code = "git_auth_failed";
} else if(/Permission denied \(publickey\)/i.test(stderr)) {
err.code = "git_auth_failed";
} else if(/Host key verification failed/i.test(stderr)) {
// TODO: handle host key verification errors separately
err.code = "git_auth_failed";
} else if (/commit your changes or stash/i.test(stderr)) {
err.code = "git_local_overwrite";
} else if (/CONFLICT/.test(err.stdout)) {

View File

@@ -0,0 +1,174 @@
{
"runtime": {
"welcome": "歡迎使用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命令",
"npm-too-old": "控制板編輯器已禁用: npm版本太舊。需要版本npm >= 3.x"
},
"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(server.install.install-failed-long) 模組未發現",
"upgrading": "更新模組: __name__ 至版本: __version__",
"upgraded": "更新模組: __name__。 重新啟動Node-RED以使用新版本",
"upgrade-failed-not-found": "$t(server.install.install-failed-long) 未發現版本",
"uninstalling": "卸載模組: __name__",
"uninstall-failed": "卸載失敗",
"uninstall-failed-long": "卸載模組__name__失敗:",
"uninstalled": "卸載模組: __name__"
},
"unable-to-listen": "無法在__listenpath__上收聽",
"port-in-use": "錯誤: 端口正在使用中",
"uncaught-exception": "未捕獲的異常:",
"admin-ui-disabled": "管理員界面已禁用",
"now-running": "服務器現在在__listenpath__上運行",
"failed-to-start": "無法啟動服務器:",
"headless-mode": "在headless模式下運行",
"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您應該使用您的設置文件中的'credentialSecret'選項設置自己的密鑰。然後下次部署更改時Node-RED將使用選擇的密鑰重新加密您的證書文件。\n---------------------------------------------------------------------\n"
},
"flows": {
"safe-mode": "流程在安全模式下停止。部署開始。",
"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__。 需要的git版本為2.x",
"summary": "一個Node-RED項目",
"readme": "### 關於\n\n這是您項目的README.md文件。它可以幫助用戶了解您的項目如何使用它以及他們可能需要知道的其他任何信息。"
}
}
},
"context": {
"log-store-init": "上下文儲存: '__name__' [__info__]",
"error-loading-module": "加載上下文存儲時出錯: __message__",
"error-loading-module2": "加載上下文存儲時出錯 '__module__': __message__",
"error-module-not-defined": "上下文存儲庫'__storage__'缺少“模組”選項",
"error-invalid-module-name": "無效的上下文存儲名稱:'__ name__'",
"error-invalid-default-module": "無效的默認的上下文存儲庫: '__storage__'",
"unknown-store": "指定了未知的上下文存儲庫'__name__'。 使用默認存儲庫。",
"localfilesystem": {
"error-circular": "上下文__scope__包含無法保留的循環引用",
"error-write": "編寫上下文時出錯__ message__"
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/runtime",
"version": "1.0.3",
"version": "1.0.4",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,8 +16,8 @@
}
],
"dependencies": {
"@node-red/registry": "1.0.3",
"@node-red/util": "1.0.3",
"@node-red/registry": "1.0.4",
"@node-red/util": "1.0.4",
"clone": "2.1.2",
"express": "4.17.1",
"fs-extra": "8.1.0",