mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
WIP: move all the code
This commit is contained in:
131
packages/node_modules/@node-red/runtime/api/comms.js
generated
vendored
Normal file
131
packages/node_modules/@node-red/runtime/api/comms.js
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* 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.comms
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef CommsConnection
|
||||
* @type {object}
|
||||
* @property {string} session - a unique session identifier
|
||||
* @property {Object} user - the user associated with the connection
|
||||
* @property {Function} send - publish a message to the connection
|
||||
*/
|
||||
|
||||
|
||||
var runtime;
|
||||
var retained = {};
|
||||
var connections = [];
|
||||
|
||||
|
||||
function handleCommsEvent(event) {
|
||||
publish(event.topic,event.data,event.retain);
|
||||
}
|
||||
function handleStatusEvent(event) {
|
||||
publish("status/"+event.id,event.status,true);
|
||||
}
|
||||
function handleRuntimeEvent(event) {
|
||||
runtime.log.trace("runtime event: "+JSON.stringify(event));
|
||||
publish("notification/"+event.id,event.payload||{},event.retain);
|
||||
}
|
||||
|
||||
function publish(topic,data,retain) {
|
||||
if (retain) {
|
||||
retained[topic] = data;
|
||||
} else {
|
||||
delete retained[topic];
|
||||
}
|
||||
connections.forEach(connection => connection.send(topic,data))
|
||||
}
|
||||
|
||||
|
||||
var api = module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
connections = [];
|
||||
retained = {};
|
||||
runtime.events.removeListener("node-status",handleStatusEvent);
|
||||
runtime.events.on("node-status",handleStatusEvent);
|
||||
runtime.events.removeListener("runtime-event",handleRuntimeEvent);
|
||||
runtime.events.on("runtime-event",handleRuntimeEvent);
|
||||
runtime.events.removeListener("comms",handleCommsEvent);
|
||||
runtime.events.on("comms",handleCommsEvent);
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers a new comms connection
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {CommsConnection} opts.client - the client connection
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.comms
|
||||
*/
|
||||
addConnection: function(opts) {
|
||||
connections.push(opts.client);
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* Unregisters a comms connection
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {CommsConnection} opts.client - the client connection
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.comms
|
||||
*/
|
||||
removeConnection: function(opts) {
|
||||
for (var i=0;i<connections.length;i++) {
|
||||
if (connections[i] === opts.client) {
|
||||
connections.splice(i,1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* Subscribes a comms connection to a given topic. Currently, all clients get
|
||||
* automatically subscribed to everything and cannot unsubscribe. Sending a subscribe
|
||||
* request will trigger retained messages to be sent.
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {CommsConnection} opts.client - the client connection
|
||||
* @param {String} opts.topic - the topic to subscribe to
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.comms
|
||||
*/
|
||||
subscribe: function(opts) {
|
||||
var re = new RegExp("^"+opts.topic.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
|
||||
for (var t in retained) {
|
||||
if (re.test(t)) {
|
||||
opts.client.send(t,retained[t]);
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* TODO: Unsubscribes a comms connection from a given topic
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {CommsConnection} opts.client - the client connection
|
||||
* @param {String} opts.topic - the topic to unsubscribe from
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.comms
|
||||
*/
|
||||
unsubscribe: function(opts) {}
|
||||
};
|
156
packages/node_modules/@node-red/runtime/api/context.js
generated
vendored
Normal file
156
packages/node_modules/@node-red/runtime/api/context.js
generated
vendored
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("../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({});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
251
packages/node_modules/@node-red/runtime/api/flows.js
generated
vendored
Normal file
251
packages/node_modules/@node-red/runtime/api/flows.js
generated
vendored
Normal file
@@ -0,0 +1,251 @@
|
||||
/**
|
||||
* 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.flows
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef Flows
|
||||
* @type {object}
|
||||
* @property {string} rev - the flow revision identifier
|
||||
* @property {Array} flows - the flow configuration, an array of node configuration objects
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef Flow
|
||||
* @type {object}
|
||||
* @property {string} id - the flow identifier
|
||||
* @property {string} label - a label for the flow
|
||||
* @property {Array} nodes - an array of node configuration objects
|
||||
*/
|
||||
|
||||
var runtime;
|
||||
|
||||
var api = module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
},
|
||||
/**
|
||||
* Gets the current flow configuration
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @return {Promise<Flows>} - the active flow configuration
|
||||
* @memberof RED.flows
|
||||
*/
|
||||
getFlows: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
runtime.log.audit({event: "flows.get"}/*,req*/);
|
||||
return resolve(runtime.nodes.getFlows());
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Sets the current flow configuration
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @return {Promise<Flows>} - the active flow configuration
|
||||
* @memberof RED.flows
|
||||
*/
|
||||
setFlows: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
|
||||
var flows = opts.flows;
|
||||
var deploymentType = opts.deploymentType||"full";
|
||||
runtime.log.audit({event: "flows.set",type:deploymentType}/*,req*/);
|
||||
|
||||
var apiPromise;
|
||||
if (deploymentType === 'reload') {
|
||||
apiPromise = runtime.nodes.loadFlows();
|
||||
} else {
|
||||
if (flows.hasOwnProperty('rev')) {
|
||||
var currentVersion = runtime.nodes.getFlows().rev;
|
||||
if (currentVersion !== flows.rev) {
|
||||
var err;
|
||||
err = new Error();
|
||||
err.code = "version_mismatch";
|
||||
err.status = 409;
|
||||
//TODO: log warning
|
||||
return reject(err);
|
||||
}
|
||||
}
|
||||
apiPromise = runtime.nodes.setFlows(flows.flows,deploymentType);
|
||||
}
|
||||
apiPromise.then(function(flowId) {
|
||||
return resolve({rev:flowId});
|
||||
}).catch(function(err) {
|
||||
runtime.log.warn(runtime.log._("api.flows.error-"+(deploymentType === 'reload'?'reload':'save'),{message:err.message}));
|
||||
runtime.log.warn(err.stack);
|
||||
return reject(err);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a flow configuration
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {Object} opts.flow - the flow to add
|
||||
* @return {Promise<String>} - the id of the added flow
|
||||
* @memberof RED.flows
|
||||
*/
|
||||
addFlow: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
var flow = opts.flow;
|
||||
runtime.nodes.addFlow(flow).then(function(id) {
|
||||
runtime.log.audit({event: "flow.add",id:id});
|
||||
return resolve(id);
|
||||
}).catch(function(err) {
|
||||
runtime.log.audit({event: "flow.add",error:err.code||"unexpected_error",message:err.toString()});
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets an individual flow configuration
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {Object} opts.id - the id of the flow to retrieve
|
||||
* @return {Promise<Flow>} - the active flow configuration
|
||||
* @memberof RED.flows
|
||||
*/
|
||||
getFlow: function(opts) {
|
||||
return new Promise(function (resolve,reject) {
|
||||
var flow = runtime.nodes.getFlow(opts.id);
|
||||
if (flow) {
|
||||
runtime.log.audit({event: "flow.get",id:opts.id});
|
||||
return resolve(flow);
|
||||
} else {
|
||||
runtime.log.audit({event: "flow.get",id:opts.id,error:"not_found"});
|
||||
var err = new Error();
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
return reject(err);
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
/**
|
||||
* Updates an existing flow configuration
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {Object} opts.id - the id of the flow to update
|
||||
* @param {Object} opts.flow - the flow configuration
|
||||
* @return {Promise<String>} - the id of the updated flow
|
||||
* @memberof RED.flows
|
||||
*/
|
||||
updateFlow: function(opts) {
|
||||
return new Promise(function (resolve,reject) {
|
||||
var flow = opts.flow;
|
||||
var id = opts.id;
|
||||
try {
|
||||
runtime.nodes.updateFlow(id,flow).then(function() {
|
||||
runtime.log.audit({event: "flow.update",id:id});
|
||||
return resolve(id);
|
||||
}).catch(function(err) {
|
||||
runtime.log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()});
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
})
|
||||
} catch(err) {
|
||||
if (err.code === 404) {
|
||||
runtime.log.audit({event: "flow.update",id:id,error:"not_found"});
|
||||
// TODO: this swap around of .code and .status isn't ideal
|
||||
err.status = 404;
|
||||
err.code = "not_found";
|
||||
return reject(err);
|
||||
} else {
|
||||
runtime.log.audit({event: "flow.update",error:err.code||"unexpected_error",message:err.toString()});
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
/**
|
||||
* Deletes a flow
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {Object} opts.id - the id of the flow to delete
|
||||
* @return {Promise} - resolves if successful
|
||||
* @memberof RED.flows
|
||||
*/
|
||||
deleteFlow: function(opts) {
|
||||
return new Promise(function (resolve,reject) {
|
||||
var id = opts.id;
|
||||
try {
|
||||
runtime.nodes.removeFlow(id).then(function() {
|
||||
runtime.log.audit({event: "flow.remove",id:id});
|
||||
return resolve();
|
||||
}).catch(function(err) {
|
||||
runtime.log.audit({event: "flow.remove",id:id,error:err.code||"unexpected_error",message:err.toString()});
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
});
|
||||
} catch(err) {
|
||||
if (err.code === 404) {
|
||||
runtime.log.audit({event: "flow.remove",id:id,error:"not_found"});
|
||||
// TODO: this swap around of .code and .status isn't ideal
|
||||
err.status = 404;
|
||||
err.code = "not_found";
|
||||
return reject(err);
|
||||
} else {
|
||||
runtime.log.audit({event: "flow.remove",id:id,error:err.code||"unexpected_error",message:err.toString()});
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the safe credentials for a node
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.type - the node type to return the credential information for
|
||||
* @param {String} opts.id - the node id
|
||||
* @return {Promise<Object>} - the safe credentials
|
||||
* @memberof RED.flows
|
||||
*/
|
||||
getNodeCredentials: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
runtime.log.audit({event: "credentials.get",type:opts.type,id:opts.id});
|
||||
var credentials = runtime.nodes.getCredentials(opts.id);
|
||||
if (!credentials) {
|
||||
return resolve({});
|
||||
}
|
||||
var definition = runtime.nodes.getCredentialDefinition(opts.type) || {};
|
||||
|
||||
var sendCredentials = {};
|
||||
for (var cred in definition) {
|
||||
if (definition.hasOwnProperty(cred)) {
|
||||
if (definition[cred].type == "password") {
|
||||
var key = 'has_' + cred;
|
||||
sendCredentials[key] = credentials[cred] != null && credentials[cred] !== '';
|
||||
continue;
|
||||
}
|
||||
sendCredentials[cred] = credentials[cred] || '';
|
||||
}
|
||||
}
|
||||
resolve(sendCredentials);
|
||||
})
|
||||
}
|
||||
|
||||
}
|
68
packages/node_modules/@node-red/runtime/api/index.js
generated
vendored
Normal file
68
packages/node_modules/@node-red/runtime/api/index.js
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
/**
|
||||
* A user accessing the API
|
||||
* @typedef User
|
||||
* @type {object}
|
||||
*/
|
||||
|
||||
var runtime;
|
||||
/**
|
||||
* @namespace RED
|
||||
*/
|
||||
var api = module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
api.comms.init(runtime);
|
||||
api.flows.init(runtime);
|
||||
api.nodes.init(runtime);
|
||||
api.settings.init(runtime);
|
||||
api.library.init(runtime);
|
||||
api.projects.init(runtime);
|
||||
api.context.init(runtime);
|
||||
},
|
||||
|
||||
comms: require("./comms"),
|
||||
flows: require("./flows"),
|
||||
library: require("./library"),
|
||||
nodes: require("./nodes"),
|
||||
settings: require("./settings"),
|
||||
projects: require("./projects"),
|
||||
context: require("./context"),
|
||||
|
||||
/**
|
||||
* Returns whether the runtime is started
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @return {Promise<Boolean>} - whether the runtime is started
|
||||
* @memberof RED
|
||||
*/
|
||||
isStarted: function(opts) {
|
||||
return Promise.resolve(runtime.isStarted());
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns version number of the runtime
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @return {Promise<String>} - the runtime version number
|
||||
* @memberof RED
|
||||
*/
|
||||
version: function(opts) {
|
||||
return Promise.resolve(runtime.version());
|
||||
}
|
||||
}
|
120
packages/node_modules/@node-red/runtime/api/library.js
generated
vendored
Normal file
120
packages/node_modules/@node-red/runtime/api/library.js
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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.library
|
||||
*/
|
||||
|
||||
var runtime;
|
||||
|
||||
var api = module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets an entry from the library.
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.type - the type of entry
|
||||
* @param {String} opts.path - the path of the entry
|
||||
* @return {Promise<String|Object>} - resolves when complete
|
||||
* @memberof RED.library
|
||||
*/
|
||||
getEntry: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
runtime.library.getEntry(opts.type,opts.path).then(function(result) {
|
||||
runtime.log.audit({event: "library.get",type:opts.type,path:opts.path});
|
||||
return resolve(result);
|
||||
}).catch(function(err) {
|
||||
if (err) {
|
||||
runtime.log.warn(runtime.log._("api.library.error-load-entry",{path:opts.path,message:err.toString()}));
|
||||
if (err.code === 'forbidden') {
|
||||
err.status = 403;
|
||||
return reject(err);
|
||||
} else if (err.code === "not_found") {
|
||||
err.status = 404;
|
||||
} else {
|
||||
err.status = 400;
|
||||
}
|
||||
runtime.log.audit({event: "library.get",type:opts.type,path:opts.path,error:err.code});
|
||||
return reject(err);
|
||||
}
|
||||
runtime.log.audit({event: "library.get",type:opts.type,error:"not_found"});
|
||||
var error = new Error();
|
||||
error.code = "not_found";
|
||||
error.status = 404;
|
||||
return reject(error);
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Saves an entry to the library
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.type - the type of entry
|
||||
* @param {String} opts.path - the path of the entry
|
||||
* @param {Object} opts.meta - any meta data associated with the entry
|
||||
* @param {String} opts.body - the body of the entry
|
||||
* @return {Promise} - resolves when complete
|
||||
* @memberof RED.library
|
||||
*/
|
||||
saveEntry: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
runtime.library.saveEntry(opts.type,opts.path,opts.meta,opts.body).then(function() {
|
||||
runtime.log.audit({event: "library.set",type:opts.type,path:opts.path});
|
||||
return resolve();
|
||||
}).catch(function(err) {
|
||||
runtime.log.warn(runtime.log._("api.library.error-save-entry",{path:opts.path,message:err.toString()}));
|
||||
if (err.code === 'forbidden') {
|
||||
runtime.log.audit({event: "library.set",type:opts.type,path:opts.path,error:"forbidden"});
|
||||
err.status = 403;
|
||||
return reject(err);
|
||||
}
|
||||
runtime.log.audit({event: "library.set",type:opts.type,path:opts.path,error:"unexpected_error",message:err.toString()});
|
||||
var error = new Error();
|
||||
error.status = 400;
|
||||
return reject(error);
|
||||
});
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Returns a complete listing of all entries of a given type in the library.
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.type - the type of entry
|
||||
* @return {Promise<Object>} - the entry listing
|
||||
* @memberof RED.library
|
||||
*/
|
||||
getEntries: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
if (opts.type !== 'flows') {
|
||||
return reject(new Error("API only supports flows"));
|
||||
|
||||
}
|
||||
runtime.storage.getAllFlows().then(function(flows) {
|
||||
runtime.log.audit({event: "library.get.all",type:"flow"});
|
||||
var examples = runtime.nodes.getNodeExampleFlows();
|
||||
if (examples) {
|
||||
flows.d = flows.d||{};
|
||||
flows.d._examples_ = examples;
|
||||
}
|
||||
return resolve(flows);
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
438
packages/node_modules/@node-red/runtime/api/nodes.js
generated
vendored
Normal file
438
packages/node_modules/@node-red/runtime/api/nodes.js
generated
vendored
Normal file
@@ -0,0 +1,438 @@
|
||||
/**
|
||||
* 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.nodes
|
||||
*/
|
||||
|
||||
var fs = require("fs");
|
||||
|
||||
var runtime;
|
||||
|
||||
function putNode(node, enabled) {
|
||||
var info;
|
||||
var promise;
|
||||
if (!node.err && node.enabled === enabled) {
|
||||
promise = Promise.resolve(node);
|
||||
} else {
|
||||
if (enabled) {
|
||||
promise = runtime.nodes.enableNode(node.id);
|
||||
} else {
|
||||
promise = runtime.nodes.disableNode(node.id);
|
||||
}
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
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.id - the id of the node set to return
|
||||
* @return {Promise<NodeInfo>} - the node information
|
||||
* @memberof RED.nodes
|
||||
*/
|
||||
getNodeInfo: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
var id = opts.id;
|
||||
var result = runtime.nodes.getNodeInfo(id);
|
||||
if (result) {
|
||||
runtime.log.audit({event: "nodes.info.get",id:id});
|
||||
delete result.loaded;
|
||||
return resolve(result);
|
||||
} else {
|
||||
runtime.log.audit({event: "nodes.info.get",id:id,error:"not_found"});
|
||||
var err = new Error();
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
return reject(err);
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the list of node modules installed in the runtime
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @return {Promise<NodeList>} - the list of node modules
|
||||
* @memberof RED.nodes
|
||||
*/
|
||||
getNodeList: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
runtime.log.audit({event: "nodes.list.get"});
|
||||
return resolve(runtime.nodes.getNodeList());
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets an individual node's html content
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the node set to return
|
||||
* @param {String} opts.lang - the locale language to return
|
||||
* @return {Promise<String>} - the node html content
|
||||
* @memberof RED.nodes
|
||||
*/
|
||||
getNodeConfig: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
var id = opts.id;
|
||||
var lang = opts.lang;
|
||||
var result = runtime.nodes.getNodeConfig(id,lang);
|
||||
if (result) {
|
||||
runtime.log.audit({event: "nodes.config.get",id:id});
|
||||
return resolve(result);
|
||||
} else {
|
||||
runtime.log.audit({event: "nodes.config.get",id:id,error:"not_found"});
|
||||
var err = new Error();
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Gets all node html content
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.lang - the locale language to return
|
||||
* @return {Promise<String>} - the node html content
|
||||
* @memberof RED.nodes
|
||||
*/
|
||||
getNodeConfigs: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
runtime.log.audit({event: "nodes.configs.get"});
|
||||
return resolve(runtime.nodes.getNodeConfigs(opts.lang));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the info of a node module
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.module - the id of the module to return
|
||||
* @return {Promise<ModuleInfo>} - the node module info
|
||||
* @memberof RED.nodes
|
||||
*/
|
||||
getModuleInfo: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
var result = runtime.nodes.getModuleInfo(opts.module);
|
||||
if (result) {
|
||||
runtime.log.audit({event: "nodes.module.get",id:opts.module});
|
||||
return resolve(result);
|
||||
} else {
|
||||
runtime.log.audit({event: "nodes.module.get",id:opts.module,error:"not_found"});
|
||||
var err = new Error();
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
return reject(err);
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Install a new module into the runtime
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.module - the id of the module to install
|
||||
* @param {String} opts.version - (optional) the version of the module to install
|
||||
* @return {Promise<ModuleInfo>} - the node module info
|
||||
* @memberof RED.nodes
|
||||
*/
|
||||
addModule: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
if (!runtime.settings.available()) {
|
||||
runtime.log.audit({event: "nodes.install",error:"settings_unavailable"});
|
||||
var err = new Error("Settings unavailable");
|
||||
err.code = "settings_unavailable";
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
if (opts.module) {
|
||||
var existingModule = runtime.nodes.getModuleInfo(opts.module);
|
||||
if (existingModule) {
|
||||
if (!opts.version || existingModule.version === opts.version) {
|
||||
runtime.log.audit({event: "nodes.install",module:opts.module, version:opts.version, error:"module_already_loaded"});
|
||||
var err = new Error("Module already loaded");
|
||||
err.code = "module_already_loaded";
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
if (!existingModule.local) {
|
||||
runtime.log.audit({event: "nodes.install",module:opts.module, version:opts.version, error:"module_not_local"});
|
||||
var err = new Error("Module not locally installed");
|
||||
err.code = "module_not_local";
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
}
|
||||
runtime.nodes.installModule(opts.module,opts.version).then(function(info) {
|
||||
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version});
|
||||
return resolve(info);
|
||||
}).catch(function(err) {
|
||||
if (err.code === 404) {
|
||||
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:"not_found"});
|
||||
// TODO: code/status
|
||||
err.status = 404;
|
||||
} else if (err.code) {
|
||||
err.status = 400;
|
||||
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:err.code});
|
||||
} else {
|
||||
err.status = 400;
|
||||
runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:err.code||"unexpected_error",message:err.toString()});
|
||||
}
|
||||
return reject(err);
|
||||
})
|
||||
} else {
|
||||
runtime.log.audit({event: "nodes.install",module:opts.module,error:"invalid_request"});
|
||||
var err = new Error("Invalid request");
|
||||
err.code = "invalid_request";
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Removes a module from the runtime
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.module - the id of the module to remove
|
||||
* @return {Promise} - resolves when complete
|
||||
* @memberof RED.nodes
|
||||
*/
|
||||
removeModule: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
if (!runtime.settings.available()) {
|
||||
runtime.log.audit({event: "nodes.install",error:"settings_unavailable"});
|
||||
var err = new Error("Settings unavailable");
|
||||
err.code = "settings_unavailable";
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
var module = runtime.nodes.getModuleInfo(opts.module);
|
||||
if (!module) {
|
||||
runtime.log.audit({event: "nodes.remove",module:opts.module,error:"not_found"});
|
||||
var err = new Error();
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
return reject(err);
|
||||
}
|
||||
try {
|
||||
runtime.nodes.uninstallModule(opts.module).then(function() {
|
||||
runtime.log.audit({event: "nodes.remove",module:opts.module});
|
||||
resolve();
|
||||
}).catch(function(err) {
|
||||
err.status = 400;
|
||||
runtime.log.audit({event: "nodes.remove",module:opts.module,error:err.code||"unexpected_error",message:err.toString()});
|
||||
return reject(err);
|
||||
})
|
||||
} catch(error) {
|
||||
runtime.log.audit({event: "nodes.remove",module:opts.module,error:error.code||"unexpected_error",message:error.toString()});
|
||||
error.status = 400;
|
||||
return reject(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Enables or disables a module in the runtime
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.module - the id of the module to enable or disable
|
||||
* @param {String} opts.enabled - whether the module should be enabled or disabled
|
||||
* @return {Promise<ModuleInfo>} - the module info object
|
||||
* @memberof RED.nodes
|
||||
*/
|
||||
setModuleState: function(opts) {
|
||||
var mod = opts.module;
|
||||
return new Promise(function(resolve,reject) {
|
||||
if (!runtime.settings.available()) {
|
||||
runtime.log.audit({event: "nodes.module.set",error:"settings_unavailable"});
|
||||
var err = new Error("Settings unavailable");
|
||||
err.code = "settings_unavailable";
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
try {
|
||||
var module = runtime.nodes.getModuleInfo(mod);
|
||||
if (!module) {
|
||||
runtime.log.audit({event: "nodes.module.set",module:mod,error:"not_found"});
|
||||
var err = new Error();
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
var nodes = module.nodes;
|
||||
var promises = [];
|
||||
for (var i = 0; i < nodes.length; ++i) {
|
||||
promises.push(putNode(nodes[i],opts.enabled));
|
||||
}
|
||||
Promise.all(promises).then(function() {
|
||||
return resolve(runtime.nodes.getModuleInfo(mod));
|
||||
}).catch(function(err) {
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
});
|
||||
} catch(error) {
|
||||
runtime.log.audit({event: "nodes.module.set",module:mod,enabled:opts.enabled,error:error.code||"unexpected_error",message:error.toString()});
|
||||
error.status = 400;
|
||||
return reject(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Enables or disables a n individual node-set in the runtime
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the node-set to enable or disable
|
||||
* @param {String} opts.enabled - whether the module should be enabled or disabled
|
||||
* @return {Promise<ModuleInfo>} - the module info object
|
||||
* @memberof RED.nodes
|
||||
*/
|
||||
setNodeSetState: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
if (!runtime.settings.available()) {
|
||||
runtime.log.audit({event: "nodes.info.set",error:"settings_unavailable"});
|
||||
var err = new Error("Settings unavailable");
|
||||
err.code = "settings_unavailable";
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
var id = opts.id;
|
||||
var enabled = opts.enabled;
|
||||
try {
|
||||
var node = runtime.nodes.getNodeInfo(id);
|
||||
if (!node) {
|
||||
runtime.log.audit({event: "nodes.info.set",id:id,error:"not_found"});
|
||||
var err = new Error();
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
return reject(err);
|
||||
} else {
|
||||
delete node.loaded;
|
||||
putNode(node,enabled).then(function(result) {
|
||||
runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled});
|
||||
return resolve(result);
|
||||
}).catch(function(err) {
|
||||
runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled,error:err.code||"unexpected_error",message:err.toString()});
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
});
|
||||
}
|
||||
} catch(error) {
|
||||
runtime.log.audit({event: "nodes.info.set",id:id,enabled:enabled,error:error.code||"unexpected_error",message:error.toString()});
|
||||
error.status = 400;
|
||||
return reject(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets all registered module message catalogs
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {User} opts.lang - the i18n language to return. If not set, uses runtime default (en-US)
|
||||
* @return {Promise<Object>} - the message catalogs
|
||||
* @memberof RED.nodes
|
||||
*/
|
||||
getModuleCatalogs: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
var namespace = opts.module;
|
||||
var lang = opts.lang;
|
||||
var prevLang = runtime.i18n.i.language;
|
||||
// Trigger a load from disk of the language if it is not the default
|
||||
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.i.getResourceBundle(lang, n.id)||{};
|
||||
}
|
||||
});
|
||||
resolve(result);
|
||||
});
|
||||
runtime.i18n.i.changeLanguage(prevLang);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a modules message catalog
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {User} opts.module - the module
|
||||
* @param {User} opts.lang - the i18n language to return. If not set, uses runtime default (en-US)
|
||||
* @return {Promise<Object>} - the message catalog
|
||||
* @memberof RED.nodes
|
||||
*/
|
||||
getModuleCatalog: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
var namespace = opts.module;
|
||||
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.changeLanguage(lang, function(){
|
||||
var catalog = runtime.i18n.getResourceBundle(lang, namespace);
|
||||
resolve(catalog||{});
|
||||
});
|
||||
runtime.i18n.i.changeLanguage(prevLang);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the list of all icons available in the modules installed within the runtime
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @return {Promise<IconList>} - the list of all icons
|
||||
* @memberof RED.nodes
|
||||
*/
|
||||
getIconList: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
runtime.log.audit({event: "nodes.icons.get"});
|
||||
return resolve(runtime.nodes.getNodeIcons());
|
||||
});
|
||||
|
||||
},
|
||||
/**
|
||||
* Gets a node icon
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.module - the id of the module requesting the icon
|
||||
* @param {String} opts.icon - the name of the icon
|
||||
* @return {Promise<Buffer>} - the icon file as a Buffer
|
||||
* @memberof RED.nodes
|
||||
*/
|
||||
getIcon: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
var iconPath = runtime.nodes.getNodeIconPath(opts.module,opts.icon);
|
||||
fs.readFile(iconPath,function(err,data) {
|
||||
if (err) {
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
return resolve(data)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
440
packages/node_modules/@node-red/runtime/api/projects.js
generated
vendored
Normal file
440
packages/node_modules/@node-red/runtime/api/projects.js
generated
vendored
Normal file
@@ -0,0 +1,440 @@
|
||||
/**
|
||||
* 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.projects
|
||||
*/
|
||||
|
||||
var runtime;
|
||||
|
||||
var api = module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
},
|
||||
available: function(opts) {
|
||||
return Promise.resolve(!!runtime.storage.projects);
|
||||
},
|
||||
|
||||
/**
|
||||
* List projects known to the runtime
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
listProjects: function(opts) {
|
||||
return runtime.storage.projects.listProjects(opts.user).then(function(list) {
|
||||
var active = runtime.storage.projects.getActiveProject(opts.user);
|
||||
var response = {
|
||||
projects: list
|
||||
};
|
||||
if (active) {
|
||||
response.active = active.name;
|
||||
}
|
||||
return response;
|
||||
}).catch(function(err) {
|
||||
err.status = 400;
|
||||
throw err;
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new project
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {Object} opts.project - the project information
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
createProject: function(opts) {
|
||||
return runtime.storage.projects.createProject(opts.user, opts.project)
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialises an empty project
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project to initialise
|
||||
* @param {Object} opts.project - the project information
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
initialiseProject: function(opts) {
|
||||
// Initialised set when creating default files for an empty repo
|
||||
return runtime.storage.projects.initialiseProject(opts.user, opts.id, opts.project)
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the active project
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @return {Promise<Object>} - the active project
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getActiveProject: function(opts) {
|
||||
return Promise.resolve(runtime.storage.projects.getActiveProject(opts.user));
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project to activate
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
setActiveProject: function(opts) {
|
||||
var currentProject = runtime.storage.projects.getActiveProject(opts.user);
|
||||
if (!currentProject || opts.id !== currentProject.name) {
|
||||
return runtime.storage.projects.setActiveProject(opts.user, opts.id);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a projects metadata
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project to get
|
||||
* @return {Promise<Object>} - the project metadata
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getProject: function(opts) {
|
||||
return runtime.storage.projects.getProject(opts.user, opts.id)
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the metadata of an existing project
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project to update
|
||||
* @param {Object} opts.project - the project information
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
updateProject: function(opts) {
|
||||
return runtime.storage.projects.updateProject(opts.user, opts.id, opts.project);
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes a project
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project to update
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
deleteProject: function(opts) {
|
||||
return runtime.storage.projects.deleteProject(opts.user, opts.id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets current git status of a project
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {Boolean} opts.remote - whether to include status of remote repos
|
||||
* @return {Promise<Object>} - the project status
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getStatus: function(opts) {
|
||||
return runtime.storage.projects.getStatus(opts.user, opts.id, opts.remote)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a list of local branches
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {Boolean} opts.remote - whether to return remote branches (true) or local (false)
|
||||
* @return {Promise<Object>} - a list of the local branches
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getBranches: function(opts) {
|
||||
return runtime.storage.projects.getBranches(opts.user, opts.id, opts.remote);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the status of a branch
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.branch - the name of the branch
|
||||
* @return {Promise<Object>} - the status of the branch
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getBranchStatus: function(opts) {
|
||||
return runtime.storage.projects.getBranchStatus(opts.user, opts.id, opts.branch);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the current local branch
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.branch - the name of the branch
|
||||
* @param {Boolean} opts.create - whether to create the branch if it doesn't exist
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
setBranch: function(opts) {
|
||||
return runtime.storage.projects.setBranch(opts.user, opts.id, opts.branch, opts.create)
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes a branch
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.branch - the name of the branch
|
||||
* @param {Boolean} opts.force - whether to force delete
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
deleteBranch: function(opts) {
|
||||
return runtime.storage.projects.deleteBranch(opts.user, opts.id, opts.branch, false, opts.force);
|
||||
},
|
||||
|
||||
/**
|
||||
* Commits the current staged files
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.message - the message to associate with the commit
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
commit: function(opts) {
|
||||
return runtime.storage.projects.commit(opts.user, opts.id,{message: opts.message});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the details of a single commit
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.sha - the sha of the commit to return
|
||||
* @return {Promise<Object>} - the commit details
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getCommit: function(opts) {
|
||||
return runtime.storage.projects.getCommit(opts.user, opts.id, opts.sha);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the commit history of the project
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.limit - limit how many to return
|
||||
* @param {String} opts.before - id of the commit to work back from
|
||||
* @return {Promise<Array>} - an array of commits
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getCommits: function(opts) {
|
||||
return runtime.storage.projects.getCommits(opts.user, opts.id, {
|
||||
limit: opts.limit || 20,
|
||||
before: opts.before
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Abort an in-progress merge
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
abortMerge: function(opts) {
|
||||
return runtime.storage.projects.abortMerge(opts.user, opts.id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolves a merge conflict
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.path - the path of the file being merged
|
||||
* @param {String} opts.resolutions - how to resolve the merge conflict
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
resolveMerge: function(opts) {
|
||||
return runtime.storage.projects.resolveMerge(opts.user, opts.id, opts.path, opts.resolution);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a listing of the files in the project
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @return {Promise<Object>} - the file listing
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getFiles: function(opts) {
|
||||
return runtime.storage.projects.getFiles(opts.user, opts.id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the contents of a file
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.path - the path of the file
|
||||
* @param {String} opts.tree - the version control tree to use
|
||||
* @return {Promise<String>} - the content of the file
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getFile: function(opts) {
|
||||
return runtime.storage.projects.getFile(opts.user, opts.id,opts.path,opts.tree);
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String|Array} opts.path - the path of the file, or an array of paths
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
stageFile: function(opts) {
|
||||
return runtime.storage.projects.stageFile(opts.user, opts.id, opts.path);
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.path - the path of the file. If not set, all staged files are unstaged
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
unstageFile: function(opts) {
|
||||
return runtime.storage.projects.unstageFile(opts.user, opts.id, opts.path);
|
||||
},
|
||||
|
||||
/**
|
||||
* Reverts changes to a file back to its commited version
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.path - the path of the file
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
revertFile: function(opts) {
|
||||
return runtime.storage.projects.revertFile(opts.user, opts.id,opts.path)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the diff of a file
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.path - the path of the file
|
||||
* @param {String} opts.type - the type of diff
|
||||
* @return {Promise<Object>} - the requested diff
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getFileDiff: function(opts) {
|
||||
return runtime.storage.projects.getFileDiff(opts.user, opts.id, opts.path, opts.type);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a list of the project remotes
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @return {Promise<Object>} - a list of project remotes
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
getRemotes: function(opts) {
|
||||
return runtime.storage.projects.getRemotes(opts.user, opts.id);
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {Object} opts.remote - the remote metadata
|
||||
* @param {String} opts.remote.name - the name of the remote
|
||||
* @param {String} opts.remote.url - the url of the remote
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
addRemote: function(opts) {
|
||||
return runtime.storage.projects.addRemote(opts.user, opts.id, opts.remote)
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a project remote
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.remote - the name of the remote
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
removeRemote: function(opts) {
|
||||
return runtime.storage.projects.removeRemote(opts.user, opts.id, opts.remote);
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {Object} opts.remote - the remote metadata
|
||||
* @param {String} opts.remote.name - the name of the remote
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
updateRemote: function(opts) {
|
||||
return runtime.storage.projects.updateRemote(opts.user, opts.id, opts.remote.name, opts.remote)
|
||||
},
|
||||
|
||||
/**
|
||||
* Pull changes from the remote
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
pull: function(opts) {
|
||||
return runtime.storage.projects.pull(opts.user, opts.id, opts.remote, opts.track, opts.allowUnrelatedHistories);
|
||||
},
|
||||
|
||||
/**
|
||||
* Push changes to a remote
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project
|
||||
* @param {String} opts.remote - the name of the remote
|
||||
* @param {String} opts.track - whether to set the remote as the upstream
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof RED.projects
|
||||
*/
|
||||
push: function(opts) {
|
||||
return runtime.storage.projects.push(opts.user, opts.id, opts.remote, opts.track);
|
||||
}
|
||||
|
||||
}
|
267
packages/node_modules/@node-red/runtime/api/settings.js
generated
vendored
Normal file
267
packages/node_modules/@node-red/runtime/api/settings.js
generated
vendored
Normal file
@@ -0,0 +1,267 @@
|
||||
/**
|
||||
* 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.settings
|
||||
*/
|
||||
|
||||
var util = require("util");
|
||||
var runtime;
|
||||
|
||||
function extend(target, source) {
|
||||
var keys = Object.keys(source);
|
||||
var i = keys.length;
|
||||
while(i--) {
|
||||
var value = source[keys[i]]
|
||||
var type = typeof value;
|
||||
if (type === 'string' || type === 'number' || type === 'boolean' || Array.isArray(value)) {
|
||||
target[keys[i]] = value;
|
||||
} else if (value === null) {
|
||||
if (target.hasOwnProperty(keys[i])) {
|
||||
delete target[keys[i]];
|
||||
}
|
||||
} else {
|
||||
// Object
|
||||
if (target.hasOwnProperty(keys[i])) {
|
||||
target[keys[i]] = extend(target[keys[i]],value);
|
||||
} else {
|
||||
target[keys[i]] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
function getSSHKeyUsername(userObj) {
|
||||
var username = '__default';
|
||||
if ( userObj && userObj.name ) {
|
||||
username = userObj.name;
|
||||
}
|
||||
return username;
|
||||
}
|
||||
var api = module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
},
|
||||
/**
|
||||
* Gets the runtime settings object
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @return {Promise<Object>} - the runtime settings
|
||||
* @memberof RED.settings
|
||||
*/
|
||||
getRuntimeSettings: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
try {
|
||||
var safeSettings = {
|
||||
httpNodeRoot: runtime.settings.httpNodeRoot||"/",
|
||||
version: runtime.settings.version
|
||||
}
|
||||
if (opts.user) {
|
||||
safeSettings.user = {}
|
||||
var props = ["anonymous","username","image","permissions"];
|
||||
props.forEach(prop => {
|
||||
if (opts.user.hasOwnProperty(prop)) {
|
||||
safeSettings.user[prop] = opts.user[prop];
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
safeSettings.context = runtime.nodes.listContextStores();
|
||||
|
||||
if (util.isArray(runtime.settings.paletteCategories)) {
|
||||
safeSettings.paletteCategories = runtime.settings.paletteCategories;
|
||||
}
|
||||
|
||||
if (runtime.settings.flowFilePretty) {
|
||||
safeSettings.flowFilePretty = runtime.settings.flowFilePretty;
|
||||
}
|
||||
|
||||
if (!runtime.nodes.paletteEditorEnabled()) {
|
||||
safeSettings.editorTheme = safeSettings.editorTheme || {};
|
||||
safeSettings.editorTheme.palette = safeSettings.editorTheme.palette || {};
|
||||
safeSettings.editorTheme.palette.editable = false;
|
||||
}
|
||||
if (runtime.storage.projects) {
|
||||
var activeProject = runtime.storage.projects.getActiveProject();
|
||||
if (activeProject) {
|
||||
safeSettings.project = activeProject;
|
||||
} else if (runtime.storage.projects.flowFileExists()) {
|
||||
safeSettings.files = {
|
||||
flow: runtime.storage.projects.getFlowFilename(),
|
||||
credentials: runtime.storage.projects.getCredentialsFilename()
|
||||
}
|
||||
}
|
||||
safeSettings.git = {
|
||||
globalUser: runtime.storage.projects.getGlobalGitUser()
|
||||
}
|
||||
}
|
||||
|
||||
safeSettings.flowEncryptionType = runtime.nodes.getCredentialKeyType();
|
||||
|
||||
runtime.settings.exportNodeSettings(safeSettings);
|
||||
|
||||
resolve(safeSettings);
|
||||
}catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets an individual user's settings object
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @return {Promise<Object>} - the user settings
|
||||
* @memberof RED.settings
|
||||
*/
|
||||
getUserSettings: function(opts) {
|
||||
var username;
|
||||
if (!opts.user || opts.user.anonymous) {
|
||||
username = '_';
|
||||
} else {
|
||||
username = opts.user.username;
|
||||
}
|
||||
return Promise.resolve(runtime.settings.getUserSettings(username)||{});
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates an individual user's settings object.
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {Object} opts.settings - the updates to the user settings
|
||||
* @return {Promise<Object>} - the user settings
|
||||
* @memberof RED.settings
|
||||
*/
|
||||
updateUserSettings: function(opts) {
|
||||
var username;
|
||||
if (!opts.user || opts.user.anonymous) {
|
||||
username = '_';
|
||||
} else {
|
||||
username = opts.user.username;
|
||||
}
|
||||
return new Promise(function(resolve,reject) {
|
||||
var currentSettings = runtime.settings.getUserSettings(username)||{};
|
||||
currentSettings = extend(currentSettings, opts.settings);
|
||||
try {
|
||||
runtime.settings.setUserSettings(username, currentSettings).then(function() {
|
||||
runtime.log.audit({event: "settings.update",username:username});
|
||||
return resolve();
|
||||
}).catch(function(err) {
|
||||
runtime.log.audit({event: "settings.update",username:username,error:err.code||"unexpected_error",message:err.toString()});
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
});
|
||||
} catch(err) {
|
||||
log.warn(log._("settings.user-not-available",{message:log._("settings.not-available")}));
|
||||
log.audit({event: "settings.update",username:username,error:err.code||"unexpected_error",message:err.toString()});
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a list of a user's ssh keys
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @return {Promise<Object>} - the user's ssh keys
|
||||
* @memberof RED.settings
|
||||
*/
|
||||
getUserKeys: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
var username = getSSHKeyUsername(opts.user);
|
||||
runtime.storage.projects.ssh.listSSHKeys(username).then(function(list) {
|
||||
return resolve(list);
|
||||
}).catch(function(err) {
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a user's ssh public key
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {User} opts.id - the id of the key to return
|
||||
* @return {Promise<String>} - the user's ssh public key
|
||||
* @memberof RED.settings
|
||||
*/
|
||||
getUserKey: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
var username = getSSHKeyUsername(opts.user);
|
||||
// console.log('username:', username);
|
||||
runtime.storage.projects.ssh.getSSHKey(username, opts.id).then(function(data) {
|
||||
if (data) {
|
||||
return resolve(data);
|
||||
} else {
|
||||
var err = new Error("Key not found");
|
||||
err.code = "not_found";
|
||||
err.status = 404;
|
||||
return reject(err);
|
||||
}
|
||||
}).catch(function(err) {
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates a new ssh key pair
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {User} opts.name - the id of the key to return
|
||||
* @param {User} opts.password - (optional) the password for the key pair
|
||||
* @param {User} opts.comment - (option) a comment to associate with the key pair
|
||||
* @param {User} opts.size - (optional) the size of the key. Default: 2048
|
||||
* @return {Promise<String>} - the id of the generated key
|
||||
* @memberof RED.settings
|
||||
*/
|
||||
generateUserKey: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
var username = getSSHKeyUsername(opts.user);
|
||||
runtime.storage.projects.ssh.generateSSHKey(username, opts).then(function(name) {
|
||||
return resolve(name);
|
||||
}).catch(function(err) {
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes a user's ssh key pair
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {User} opts.id - the id of the key to delete
|
||||
* @return {Promise} - resolves when deleted
|
||||
* @memberof RED.settings
|
||||
*/
|
||||
removeUserKey: function(opts) {
|
||||
return new Promise(function(resolve,reject) {
|
||||
var username = getSSHKeyUsername(req.user);
|
||||
runtime.storage.projects.ssh.deleteSSHKey(username, opts.id).then(function() {
|
||||
return resolve();
|
||||
}).catch(function(err) {
|
||||
err.status = 400;
|
||||
return reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
}
|
19
packages/node_modules/@node-red/runtime/events.js
generated
vendored
Normal file
19
packages/node_modules/@node-red/runtime/events.js
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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 events = require("events");
|
||||
|
||||
module.exports = new events.EventEmitter();
|
279
packages/node_modules/@node-red/runtime/index.js
generated
vendored
Normal file
279
packages/node_modules/@node-red/runtime/index.js
generated
vendored
Normal file
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* 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 when = require('when');
|
||||
|
||||
var externalAPI = require("./api");
|
||||
|
||||
var redNodes = require("./nodes");
|
||||
var storage = require("./storage");
|
||||
var library = require("./library");
|
||||
var events = require("./events");
|
||||
var settings = require("./settings");
|
||||
|
||||
var express = require("express");
|
||||
var path = require('path');
|
||||
var fs = require("fs");
|
||||
var os = require("os");
|
||||
|
||||
var redUtil;
|
||||
var log;
|
||||
var i18n;
|
||||
|
||||
var runtimeMetricInterval = null;
|
||||
|
||||
var started = false;
|
||||
|
||||
var stubbedExpressApp = {
|
||||
get: function() {},
|
||||
post: function() {},
|
||||
put: function() {},
|
||||
delete: function() {}
|
||||
}
|
||||
var adminApi = {
|
||||
auth: {
|
||||
needsPermission: function() {}
|
||||
},
|
||||
comms: {
|
||||
publish: function() {}
|
||||
},
|
||||
adminApp: stubbedExpressApp,
|
||||
server: {}
|
||||
}
|
||||
|
||||
var nodeApp;
|
||||
|
||||
function init(userSettings,_redUtil,_adminApi) {
|
||||
redUtil = _redUtil;
|
||||
log = redUtil.log;
|
||||
i18n = redUtil.i18n;
|
||||
userSettings.version = getVersion();
|
||||
settings.init(userSettings);
|
||||
|
||||
nodeApp = express();
|
||||
|
||||
if (_adminApi) {
|
||||
adminApi = _adminApi;
|
||||
}
|
||||
redNodes.init(runtime);
|
||||
library.init(runtime);
|
||||
externalAPI.init(runtime);
|
||||
}
|
||||
|
||||
var version;
|
||||
|
||||
function getVersion() {
|
||||
if (!version) {
|
||||
version = require(path.join(__dirname,"package.json")).version;
|
||||
/* istanbul ignore else */
|
||||
try {
|
||||
fs.statSync(path.join(__dirname,"..","..","..","..",".git"));
|
||||
version += "-git";
|
||||
} catch(err) {
|
||||
// No git directory
|
||||
}
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
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)})
|
||||
.then(function() {
|
||||
|
||||
if (log.metric()) {
|
||||
runtimeMetricInterval = setInterval(function() {
|
||||
reportMetrics();
|
||||
}, settings.runtimeMetricInterval||15000);
|
||||
}
|
||||
log.info("\n\n"+log._("runtime.welcome")+"\n===================\n");
|
||||
if (settings.version) {
|
||||
log.info(log._("runtime.version",{component:"Node-RED",version:"v"+settings.version}));
|
||||
}
|
||||
log.info(log._("runtime.version",{component:"Node.js ",version:process.version}));
|
||||
if (settings.UNSUPPORTED_VERSION) {
|
||||
log.error("*****************************************************************");
|
||||
log.error("* "+log._("runtime.unsupported_version",{component:"Node.js",version:process.version,requires: ">=4"})+" *");
|
||||
log.error("*****************************************************************");
|
||||
events.emit("runtime-event",{id:"runtime-unsupported-version",payload:{type:"error",text:"notification.errors.unsupportedVersion"},retain:true});
|
||||
}
|
||||
log.info(os.type()+" "+os.release()+" "+os.arch()+" "+os.endianness());
|
||||
return redNodes.load().then(function() {
|
||||
|
||||
var i;
|
||||
var nodeErrors = redNodes.getNodeList(function(n) { return n.err!=null;});
|
||||
var nodeMissing = redNodes.getNodeList(function(n) { return n.module && n.enabled && !n.loaded && !n.err;});
|
||||
if (nodeErrors.length > 0) {
|
||||
log.warn("------------------------------------------------------");
|
||||
for (i=0;i<nodeErrors.length;i+=1) {
|
||||
if (nodeErrors[i].err.code === "type_already_registered") {
|
||||
log.warn("["+nodeErrors[i].id+"] "+log._("server.type-already-registered",{type:nodeErrors[i].err.details.type,module: nodeErrors[i].err.details.moduleA}));
|
||||
} else {
|
||||
log.warn("["+nodeErrors[i].id+"] "+nodeErrors[i].err);
|
||||
}
|
||||
}
|
||||
log.warn("------------------------------------------------------");
|
||||
}
|
||||
if (nodeMissing.length > 0) {
|
||||
log.warn(log._("server.missing-modules"));
|
||||
var missingModules = {};
|
||||
for (i=0;i<nodeMissing.length;i++) {
|
||||
var missing = nodeMissing[i];
|
||||
missingModules[missing.module] = missingModules[missing.module]||{
|
||||
module:missing.module,
|
||||
version:missing.pending_version||missing.version,
|
||||
types:[]
|
||||
}
|
||||
missingModules[missing.module].types = missingModules[missing.module].types.concat(missing.types);
|
||||
}
|
||||
var moduleList = [];
|
||||
var promises = [];
|
||||
var installingModules = [];
|
||||
for (i in missingModules) {
|
||||
if (missingModules.hasOwnProperty(i)) {
|
||||
log.warn(" - "+i+" ("+missingModules[i].version+"): "+missingModules[i].types.join(", "));
|
||||
if (settings.autoInstallModules && i != "node-red") {
|
||||
installingModules.push({id:i,version:missingModules[i].version});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!settings.autoInstallModules) {
|
||||
log.info(log._("server.removing-modules"));
|
||||
redNodes.cleanModuleList();
|
||||
} else if (installingModules.length > 0) {
|
||||
reinstallAttempts = 0;
|
||||
reinstallModules(installingModules);
|
||||
}
|
||||
}
|
||||
if (settings.settingsFile) {
|
||||
log.info(log._("runtime.paths.settings",{path:settings.settingsFile}));
|
||||
}
|
||||
if (settings.httpStatic) {
|
||||
log.info(log._("runtime.paths.httpStatic",{path:path.resolve(settings.httpStatic)}));
|
||||
}
|
||||
redNodes.loadContextsPlugin().then(function () {
|
||||
redNodes.loadFlows().then(redNodes.startFlows).catch(function(err) {});
|
||||
started = true;
|
||||
});
|
||||
}).catch(function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var reinstallAttempts;
|
||||
var reinstallTimeout;
|
||||
function reinstallModules(moduleList) {
|
||||
var promises = [];
|
||||
var failedModules = [];
|
||||
for (var i=0;i<moduleList.length;i++) {
|
||||
if (settings.autoInstallModules && i != "node-red") {
|
||||
promises.push(redNodes.installModule(moduleList[i].id,moduleList[i].version));
|
||||
}
|
||||
}
|
||||
when.settle(promises).then(function(results) {
|
||||
var reinstallList = [];
|
||||
for (var i=0;i<results.length;i++) {
|
||||
if (results[i].state === 'rejected') {
|
||||
reinstallList.push(moduleList[i]);
|
||||
} else {
|
||||
events.emit("runtime-event",{id:"node/added",retain:false,payload:results[i].value.nodes});
|
||||
}
|
||||
}
|
||||
if (reinstallList.length > 0) {
|
||||
reinstallAttempts++;
|
||||
// First 5 at 1x timeout, next 5 at 2x, next 5 at 4x, then 8x
|
||||
var timeout = (settings.autoInstallModulesRetry||30000) * Math.pow(2,Math.min(Math.floor(reinstallAttempts/5),3));
|
||||
reinstallTimeout = setTimeout(function() {
|
||||
reinstallModules(reinstallList);
|
||||
},timeout);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function reportMetrics() {
|
||||
var memUsage = process.memoryUsage();
|
||||
|
||||
log.log({
|
||||
level: log.METRIC,
|
||||
event: "runtime.memory.rss",
|
||||
value: memUsage.rss
|
||||
});
|
||||
log.log({
|
||||
level: log.METRIC,
|
||||
event: "runtime.memory.heapTotal",
|
||||
value: memUsage.heapTotal
|
||||
});
|
||||
log.log({
|
||||
level: log.METRIC,
|
||||
event: "runtime.memory.heapUsed",
|
||||
value: memUsage.heapUsed
|
||||
});
|
||||
}
|
||||
|
||||
function stop() {
|
||||
if (runtimeMetricInterval) {
|
||||
clearInterval(runtimeMetricInterval);
|
||||
runtimeMetricInterval = null;
|
||||
}
|
||||
if (reinstallTimeout) {
|
||||
clearTimeout(reinstallTimeout);
|
||||
}
|
||||
started = false;
|
||||
return redNodes.stopFlows().then(function(){
|
||||
return redNodes.closeContextsPlugin();
|
||||
});
|
||||
}
|
||||
|
||||
// This is the internal api
|
||||
var runtime = {
|
||||
version: getVersion,
|
||||
get log() { return log },
|
||||
get i18n() { return i18n },
|
||||
settings: settings,
|
||||
storage: storage,
|
||||
events: events,
|
||||
nodes: redNodes,
|
||||
library: library,
|
||||
util: require("./util"),
|
||||
get adminApi() { return adminApi },
|
||||
get nodeApp() { return nodeApp },
|
||||
isStarted: function() {
|
||||
return started;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
start: start,
|
||||
stop: stop,
|
||||
|
||||
"_": runtime,
|
||||
|
||||
comms: externalAPI.comms,
|
||||
flows: externalAPI.flows,
|
||||
library: externalAPI.library,
|
||||
nodes: externalAPI.nodes,
|
||||
settings: externalAPI.settings,
|
||||
projects: externalAPI.projects,
|
||||
context: externalAPI.context,
|
||||
|
||||
isStarted: externalAPI.isStarted,
|
||||
version: externalAPI.version
|
||||
}
|
103
packages/node_modules/@node-red/runtime/library/index.js
generated
vendored
Normal file
103
packages/node_modules/@node-red/runtime/library/index.js
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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 fs = require('fs');
|
||||
var fspath = require('path');
|
||||
|
||||
var runtime;
|
||||
var knownTypes = {};
|
||||
|
||||
var storage;
|
||||
|
||||
function init(_runtime) {
|
||||
runtime = _runtime;
|
||||
storage = runtime.storage;
|
||||
knownTypes = {};
|
||||
}
|
||||
|
||||
function registerType(id,type) {
|
||||
// TODO: would like to enforce this, but currently the tests register the same type multiple
|
||||
// times and have no way to remove themselves.
|
||||
// if (knownTypes.hasOwnProperty(type)) {
|
||||
// throw new Error(`Library type '${type}' already registered by ${id}'`)
|
||||
// }
|
||||
knownTypes[type] = id;
|
||||
}
|
||||
|
||||
// function getAllEntries(type) {
|
||||
// if (!knownTypes.hasOwnProperty(type)) {
|
||||
// throw new Error(`Unknown library type '${type}'`);
|
||||
// }
|
||||
// }
|
||||
|
||||
function getEntry(type,path) {
|
||||
if (type !== 'flows') {
|
||||
if (!knownTypes.hasOwnProperty(type)) {
|
||||
throw new Error(`Unknown library type '${type}'`);
|
||||
}
|
||||
return storage.getLibraryEntry(type,path);
|
||||
} else {
|
||||
return new Promise(function(resolve,reject) {
|
||||
if (path.indexOf("_examples_/") === 0) {
|
||||
var m = /^_examples_\/(@.*?\/[^\/]+|[^\/]+)\/(.*)$/.exec(path);
|
||||
if (m) {
|
||||
var module = m[1];
|
||||
var entryPath = m[2];
|
||||
var fullPath = runtime.nodes.getNodeExampleFlowPath(module,entryPath);
|
||||
if (fullPath) {
|
||||
try {
|
||||
fs.readFile(fullPath,'utf8',function(err, data) {
|
||||
runtime.log.audit({event: "library.get",type:"flow",path:path});
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
return resolve(data);
|
||||
})
|
||||
} catch(err) {
|
||||
return reject(err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// IF we get here, we didn't find the file
|
||||
var error = new Error("not_found");
|
||||
error.code = "not_found";
|
||||
return reject(error);
|
||||
} else {
|
||||
resolve(storage.getFlow(path));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
function saveEntry(type,path,meta,body) {
|
||||
if (type !== 'flows') {
|
||||
if (!knownTypes.hasOwnProperty(type)) {
|
||||
throw new Error(`Unknown library type '${type}'`);
|
||||
}
|
||||
return storage.saveLibraryEntry(type,path,meta,body);
|
||||
} else {
|
||||
return storage.saveFlow(path,body);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
register: registerType,
|
||||
// getAllEntries: getAllEntries,
|
||||
getEntry: getEntry,
|
||||
saveEntry: saveEntry
|
||||
|
||||
}
|
169
packages/node_modules/@node-red/runtime/locales/en-US/runtime.json
generated
vendored
Normal file
169
packages/node_modules/@node-red/runtime/locales/en-US/runtime.json
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
{
|
||||
"runtime": {
|
||||
"welcome": "Welcome to Node-RED",
|
||||
"version": "__component__ version: __version__",
|
||||
"unsupported_version": "Unsupported version of __component__. Requires: __requires__ Found: __version__",
|
||||
"paths": {
|
||||
"settings": "Settings file : __path__",
|
||||
"httpStatic": "HTTP Static : __path__"
|
||||
}
|
||||
},
|
||||
|
||||
"server": {
|
||||
"loading": "Loading palette nodes",
|
||||
"palette-editor": {
|
||||
"disabled": "Palette editor disabled : user settings",
|
||||
"npm-not-found": "Palette editor disabled : npm command not found"
|
||||
},
|
||||
"errors": "Failed to register __count__ node type",
|
||||
"errors_plural": "Failed to register __count__ node types",
|
||||
"errors-help": "Run with -v for details",
|
||||
"missing-modules": "Missing node modules:",
|
||||
"node-version-mismatch": "Node module cannot be loaded on this version. Requires: __version__ ",
|
||||
"type-already-registered": "'__type__' already registered by module __module__",
|
||||
"removing-modules": "Removing modules from config",
|
||||
"added-types": "Added node types:",
|
||||
"removed-types": "Removed node types:",
|
||||
"install": {
|
||||
"invalid": "Invalid module name",
|
||||
"installing": "Installing module: __name__, version: __version__",
|
||||
"installed": "Installed module: __name__",
|
||||
"install-failed": "Install failed",
|
||||
"install-failed-long": "Installation of module __name__ failed:",
|
||||
"install-failed-not-found": "$t(install-failed-long) module not found",
|
||||
"upgrading": "Upgrading module: __name__ to version: __version__",
|
||||
"upgraded": "Upgraded module: __name__. Restart Node-RED to use the new version",
|
||||
"upgrade-failed-not-found": "$t(server.install.install-failed-long) version not found",
|
||||
"install-failed-not-found": "$t(server.install.install-failed-long) module not found",
|
||||
"uninstalling": "Uninstalling module: __name__",
|
||||
"uninstall-failed": "Uninstall failed",
|
||||
"uninstall-failed-long": "Uninstall of module __name__ failed:",
|
||||
"uninstalled": "Uninstalled module: __name__"
|
||||
},
|
||||
"unable-to-listen": "Unable to listen on __listenpath__",
|
||||
"port-in-use": "Error: port in use",
|
||||
"uncaught-exception": "Uncaught Exception:",
|
||||
"admin-ui-disabled": "Admin UI disabled",
|
||||
"now-running": "Server now running at __listenpath__",
|
||||
"failed-to-start": "Failed to start server:",
|
||||
"headless-mode": "Running in headless mode",
|
||||
"httpadminauth-deprecated": "use of httpAdminAuth is deprecated. Use adminAuth instead"
|
||||
},
|
||||
|
||||
"api": {
|
||||
"flows": {
|
||||
"error-save": "Error saving flows: __message__",
|
||||
"error-reload": "Error reloading flows: __message__"
|
||||
},
|
||||
"library": {
|
||||
"error-load-entry": "Error loading library entry '__path__': __message__",
|
||||
"error-save-entry": "Error saving library entry '__path__': __message__",
|
||||
"error-load-flow": "Error loading flow '__path__': __message__",
|
||||
"error-save-flow": "Error saving flow '__path__': __message__"
|
||||
},
|
||||
"nodes": {
|
||||
"enabled": "Enabled node types:",
|
||||
"disabled": "Disabled node types:",
|
||||
"error-enable": "Failed to enable node:"
|
||||
}
|
||||
},
|
||||
|
||||
"comms": {
|
||||
"error": "Communication channel error: __message__",
|
||||
"error-server": "Communication server error: __message__",
|
||||
"error-send": "Communication send error: __message__"
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"user-not-available": "Cannot save user settings: __message__",
|
||||
"not-available": "Settings not available",
|
||||
"property-read-only": "Property '__prop__' is read-only"
|
||||
},
|
||||
|
||||
"nodes": {
|
||||
"credentials": {
|
||||
"error":"Error loading credentials: __message__",
|
||||
"error-saving":"Error saving credentials: __message__",
|
||||
"not-registered": "Credential type '__type__' is not registered",
|
||||
"system-key-warning": "\n\n---------------------------------------------------------------------\nYour flow credentials file is encrypted using a system-generated key.\n\nIf the system-generated key is lost for any reason, your credentials\nfile will not be recoverable, you will have to delete it and re-enter\nyour credentials.\n\nYou should set your own key using the 'credentialSecret' option in\nyour settings file. Node-RED will then re-encrypt your credentials\nfile using your chosen key the next time you deploy a change.\n---------------------------------------------------------------------\n"
|
||||
},
|
||||
"flows": {
|
||||
"registered-missing": "Missing type registered: __type__",
|
||||
"error": "Error loading flows: __message__",
|
||||
"starting-modified-nodes": "Starting modified nodes",
|
||||
"starting-modified-flows": "Starting modified flows",
|
||||
"starting-flows": "Starting flows",
|
||||
"started-modified-nodes": "Started modified nodes",
|
||||
"started-modified-flows": "Started modified flows",
|
||||
"started-flows": "Started flows",
|
||||
"stopping-modified-nodes": "Stopping modified nodes",
|
||||
"stopping-modified-flows": "Stopping modified flows",
|
||||
"stopping-flows": "Stopping flows",
|
||||
"stopped-modified-nodes": "Stopped modified nodes",
|
||||
"stopped-modified-flows": "Stopped modified flows",
|
||||
"stopped-flows": "Stopped flows",
|
||||
"stopped": "Stopped",
|
||||
"stopping-error": "Error stopping node: __message__",
|
||||
"added-flow": "Adding flow: __label__",
|
||||
"updated-flow": "Updated flow: __label__",
|
||||
"removed-flow": "Removed flow: __label__",
|
||||
"missing-types": "Waiting for missing types to be registered:",
|
||||
"missing-type-provided": " - __type__ (provided by npm module __module__)",
|
||||
"missing-type-install-1": "To install any of these missing modules, run:",
|
||||
"missing-type-install-2": "in the directory:"
|
||||
},
|
||||
"flow": {
|
||||
"unknown-type": "Unknown type: __type__",
|
||||
"missing-types": "missing types",
|
||||
"error-loop": "Message exceeded maximum number of catches"
|
||||
},
|
||||
"index": {
|
||||
"unrecognised-id": "Unrecognised id: __id__",
|
||||
"type-in-use": "Type in use: __msg__",
|
||||
"unrecognised-module": "Unrecognised module: __module__"
|
||||
},
|
||||
"registry": {
|
||||
"localfilesystem": {
|
||||
"module-not-found": "Cannot find module '__module__'"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"storage": {
|
||||
"index": {
|
||||
"forbidden-flow-name": "forbidden flow name"
|
||||
},
|
||||
"localfilesystem": {
|
||||
"user-dir": "User directory : __path__",
|
||||
"flows-file": "Flows file : __path__",
|
||||
"create": "Creating new __type__ file",
|
||||
"empty": "Existing __type__ file is empty",
|
||||
"invalid": "Existing __type__ file is not valid json",
|
||||
"restore": "Restoring __type__ file backup : __path__",
|
||||
"restore-fail": "Restoring __type__ file backup failed : __message__",
|
||||
"fsync-fail": "Flushing file __path__ to disk failed : __message__",
|
||||
"projects": {
|
||||
"changing-project": "Setting active project : __project__",
|
||||
"active-project": "Active project : __project__",
|
||||
"project-not-found": "Project not found : __project__",
|
||||
"no-active-project": "No active project : using default flows file",
|
||||
"disabled": "Projects disabled : editorTheme.projects.enabled=false",
|
||||
"disabledNoFlag": "Projects disabled : set editorTheme.projects.enabled=true to enable",
|
||||
"git-not-found": "Projects disabled : git command not found",
|
||||
"git-version-old": "Projects disabled : git __version__ not supported. Requires 2.x",
|
||||
"summary": "A Node-RED Project",
|
||||
"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."
|
||||
}
|
||||
|
||||
}
|
169
packages/node_modules/@node-red/runtime/locales/ja/runtime.json
generated
vendored
Normal file
169
packages/node_modules/@node-red/runtime/locales/ja/runtime.json
generated
vendored
Normal file
@@ -0,0 +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__' が指定されました。デフォルトストアを使用します。"
|
||||
}
|
||||
|
||||
}
|
291
packages/node_modules/@node-red/runtime/nodes/Node.js
generated
vendored
Normal file
291
packages/node_modules/@node-red/runtime/nodes/Node.js
generated
vendored
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.
|
||||
**/
|
||||
|
||||
var util = require("util");
|
||||
var EventEmitter = require("events").EventEmitter;
|
||||
var when = require("when");
|
||||
|
||||
var redUtil = require("../util");
|
||||
var Log = require("@node-red/util").log; // TODO: separate module
|
||||
var context = require("./context");
|
||||
var flows = require("./flows");
|
||||
|
||||
function Node(n) {
|
||||
this.id = n.id;
|
||||
this.type = n.type;
|
||||
this.z = n.z;
|
||||
this._closeCallbacks = [];
|
||||
|
||||
if (n.name) {
|
||||
this.name = n.name;
|
||||
}
|
||||
if (n._alias) {
|
||||
this._alias = n._alias;
|
||||
}
|
||||
this.updateWires(n.wires);
|
||||
}
|
||||
|
||||
util.inherits(Node, EventEmitter);
|
||||
|
||||
Node.prototype.updateWires = function(wires) {
|
||||
//console.log("UPDATE",this.id);
|
||||
this.wires = wires || [];
|
||||
delete this._wire;
|
||||
|
||||
var wc = 0;
|
||||
this.wires.forEach(function(w) {
|
||||
wc+=w.length;
|
||||
});
|
||||
this._wireCount = wc;
|
||||
if (wc === 0) {
|
||||
// With nothing wired to the node, no-op send
|
||||
this.send = function(msg) {}
|
||||
} else {
|
||||
this.send = Node.prototype.send;
|
||||
if (this.wires.length === 1 && this.wires[0].length === 1) {
|
||||
// Single wire, so we can shortcut the send when
|
||||
// a single message is sent
|
||||
this._wire = this.wires[0][0];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Node.prototype.context = function() {
|
||||
if (!this._context) {
|
||||
this._context = context.get(this._alias||this.id,this.z);
|
||||
}
|
||||
return this._context;
|
||||
}
|
||||
|
||||
Node.prototype._on = Node.prototype.on;
|
||||
|
||||
Node.prototype.on = function(event, callback) {
|
||||
var node = this;
|
||||
if (event == "close") {
|
||||
this._closeCallbacks.push(callback);
|
||||
} else {
|
||||
this._on(event, callback);
|
||||
}
|
||||
};
|
||||
|
||||
Node.prototype.close = function(removed) {
|
||||
//console.log(this.type,this.id,removed);
|
||||
var promises = [];
|
||||
var node = this;
|
||||
for (var i=0;i<this._closeCallbacks.length;i++) {
|
||||
var callback = this._closeCallbacks[i];
|
||||
if (callback.length > 0) {
|
||||
promises.push(
|
||||
when.promise(function(resolve) {
|
||||
var args = [];
|
||||
if (callback.length === 2) {
|
||||
args.push(!!removed);
|
||||
}
|
||||
args.push(resolve);
|
||||
callback.apply(node, args);
|
||||
})
|
||||
);
|
||||
} else {
|
||||
callback.call(node);
|
||||
}
|
||||
}
|
||||
if (promises.length > 0) {
|
||||
return when.settle(promises).then(function() {
|
||||
if (this._context) {
|
||||
return context.delete(this._alias||this.id,this.z);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (this._context) {
|
||||
return context.delete(this._alias||this.id,this.z);
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
Node.prototype.send = function(msg) {
|
||||
var msgSent = false;
|
||||
var node;
|
||||
|
||||
if (msg === null || typeof msg === "undefined") {
|
||||
return;
|
||||
} else if (!util.isArray(msg)) {
|
||||
if (this._wire) {
|
||||
// A single message and a single wire on output 0
|
||||
// TODO: pre-load flows.get calls - cannot do in constructor
|
||||
// as not all nodes are defined at that point
|
||||
if (!msg._msgid) {
|
||||
msg._msgid = redUtil.generateId();
|
||||
}
|
||||
this.metric("send",msg);
|
||||
node = flows.get(this._wire);
|
||||
/* istanbul ignore else */
|
||||
if (node) {
|
||||
node.receive(msg);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
msg = [msg];
|
||||
}
|
||||
}
|
||||
|
||||
var numOutputs = this.wires.length;
|
||||
|
||||
// Build a list of send events so that all cloning is done before
|
||||
// any calls to node.receive
|
||||
var sendEvents = [];
|
||||
|
||||
var sentMessageId = null;
|
||||
|
||||
// for each output of node eg. [msgs to output 0, msgs to output 1, ...]
|
||||
for (var i = 0; i < numOutputs; i++) {
|
||||
var wires = this.wires[i]; // wires leaving output i
|
||||
/* istanbul ignore else */
|
||||
if (i < msg.length) {
|
||||
var msgs = msg[i]; // msgs going to output i
|
||||
if (msgs !== null && typeof msgs !== "undefined") {
|
||||
if (!util.isArray(msgs)) {
|
||||
msgs = [msgs];
|
||||
}
|
||||
var k = 0;
|
||||
// for each recipent node of that output
|
||||
for (var j = 0; j < wires.length; j++) {
|
||||
node = flows.get(wires[j]); // node at end of wire j
|
||||
if (node) {
|
||||
// for each msg to send eg. [[m1, m2, ...], ...]
|
||||
for (k = 0; k < msgs.length; k++) {
|
||||
var m = msgs[k];
|
||||
if (m !== null && m !== undefined) {
|
||||
/* istanbul ignore else */
|
||||
if (!sentMessageId) {
|
||||
sentMessageId = m._msgid;
|
||||
}
|
||||
if (msgSent) {
|
||||
var clonedmsg = redUtil.cloneMessage(m);
|
||||
sendEvents.push({n:node,m:clonedmsg});
|
||||
} else {
|
||||
sendEvents.push({n:node,m:m});
|
||||
msgSent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* istanbul ignore else */
|
||||
if (!sentMessageId) {
|
||||
sentMessageId = redUtil.generateId();
|
||||
}
|
||||
this.metric("send",{_msgid:sentMessageId});
|
||||
|
||||
for (i=0;i<sendEvents.length;i++) {
|
||||
var ev = sendEvents[i];
|
||||
/* istanbul ignore else */
|
||||
if (!ev.m._msgid) {
|
||||
ev.m._msgid = sentMessageId;
|
||||
}
|
||||
ev.n.receive(ev.m);
|
||||
}
|
||||
};
|
||||
|
||||
Node.prototype.receive = function(msg) {
|
||||
if (!msg) {
|
||||
msg = {};
|
||||
}
|
||||
if (!msg._msgid) {
|
||||
msg._msgid = redUtil.generateId();
|
||||
}
|
||||
this.metric("receive",msg);
|
||||
try {
|
||||
this.emit("input", msg);
|
||||
} catch(err) {
|
||||
this.error(err,msg);
|
||||
}
|
||||
};
|
||||
|
||||
function log_helper(self, level, msg) {
|
||||
var o = {
|
||||
level: level,
|
||||
id: self.id,
|
||||
type: self.type,
|
||||
msg: msg
|
||||
};
|
||||
if (self._alias) {
|
||||
o._alias = self._alias;
|
||||
}
|
||||
if (self.z) {
|
||||
o.z = self.z;
|
||||
}
|
||||
if (self.name) {
|
||||
o.name = self.name;
|
||||
}
|
||||
Log.log(o);
|
||||
}
|
||||
|
||||
Node.prototype.log = function(msg) {
|
||||
log_helper(this, Log.INFO, msg);
|
||||
};
|
||||
|
||||
Node.prototype.warn = function(msg) {
|
||||
log_helper(this, Log.WARN, msg);
|
||||
};
|
||||
|
||||
Node.prototype.error = function(logMessage,msg) {
|
||||
if (typeof logMessage != 'boolean') {
|
||||
logMessage = logMessage || "";
|
||||
}
|
||||
var handled = false;
|
||||
if (msg) {
|
||||
handled = flows.handleError(this,logMessage,msg);
|
||||
}
|
||||
if (!handled) {
|
||||
log_helper(this, Log.ERROR, logMessage);
|
||||
}
|
||||
};
|
||||
|
||||
Node.prototype.debug = function(msg) {
|
||||
log_helper(this, Log.DEBUG, msg);
|
||||
}
|
||||
|
||||
Node.prototype.trace = function(msg) {
|
||||
log_helper(this, Log.TRACE, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* If called with no args, returns whether metric collection is enabled
|
||||
*/
|
||||
Node.prototype.metric = function(eventname, msg, metricValue) {
|
||||
if (typeof eventname === "undefined") {
|
||||
return Log.metric();
|
||||
}
|
||||
var metrics = {};
|
||||
metrics.level = Log.METRIC;
|
||||
metrics.nodeid = this.id;
|
||||
metrics.event = "node."+this.type+"."+eventname;
|
||||
metrics.msgid = msg._msgid;
|
||||
metrics.value = metricValue;
|
||||
Log.log(metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* status: { fill:"red|green", shape:"dot|ring", text:"blah" }
|
||||
*/
|
||||
Node.prototype.status = function(status) {
|
||||
flows.handleStatus(this,status);
|
||||
};
|
||||
module.exports = Node;
|
374
packages/node_modules/@node-red/runtime/nodes/context/index.js
generated
vendored
Normal file
374
packages/node_modules/@node-red/runtime/nodes/context/index.js
generated
vendored
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("@node-red/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
packages/node_modules/@node-red/runtime/nodes/context/localfilesystem.js
generated
vendored
Normal file
291
packages/node_modules/@node-red/runtime/nodes/context/localfilesystem.js
generated
vendored
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
packages/node_modules/@node-red/runtime/nodes/context/memory.js
generated
vendored
Normal file
166
packages/node_modules/@node-red/runtime/nodes/context/memory.js
generated
vendored
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);
|
||||
};
|
432
packages/node_modules/@node-red/runtime/nodes/credentials.js
generated
vendored
Normal file
432
packages/node_modules/@node-red/runtime/nodes/credentials.js
generated
vendored
Normal file
@@ -0,0 +1,432 @@
|
||||
/**
|
||||
* 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 when = require("when");
|
||||
var crypto = require('crypto');
|
||||
var runtime;
|
||||
var settings;
|
||||
var log;
|
||||
|
||||
|
||||
var encryptedCredentials = null;
|
||||
var credentialCache = {};
|
||||
var credentialsDef = {};
|
||||
var dirty = false;
|
||||
|
||||
var removeDefaultKey = false;
|
||||
var encryptionEnabled = null;
|
||||
var encryptionKeyType; // disabled, system, user, project
|
||||
var encryptionAlgorithm = "aes-256-ctr";
|
||||
var encryptionKey;
|
||||
|
||||
function decryptCredentials(key,credentials) {
|
||||
var creds = credentials["$"];
|
||||
var initVector = new Buffer(creds.substring(0, 32),'hex');
|
||||
creds = creds.substring(32);
|
||||
var decipher = crypto.createDecipheriv(encryptionAlgorithm, key, initVector);
|
||||
var decrypted = decipher.update(creds, 'base64', 'utf8') + decipher.final('utf8');
|
||||
return JSON.parse(decrypted);
|
||||
}
|
||||
function encryptCredentials(key,credentials) {
|
||||
var initVector = crypto.randomBytes(16);
|
||||
var cipher = crypto.createCipheriv(encryptionAlgorithm, key, initVector);
|
||||
return {"$":initVector.toString('hex') + cipher.update(JSON.stringify(credentials), 'utf8', 'base64') + cipher.final('base64')};
|
||||
}
|
||||
|
||||
var api = module.exports = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
log = runtime.log;
|
||||
settings = runtime.settings;
|
||||
dirty = false;
|
||||
credentialCache = {};
|
||||
credentialsDef = {};
|
||||
encryptionEnabled = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the credentials from storage.
|
||||
*/
|
||||
load: function (credentials) {
|
||||
dirty = false;
|
||||
var credentialsEncrypted = credentials.hasOwnProperty("$") && Object.keys(credentials).length === 1;
|
||||
|
||||
// Case 1: Active Project in place
|
||||
// - use whatever its config says
|
||||
|
||||
// Case 2: _credentialSecret unset, credentialSecret unset
|
||||
// - generate _credentialSecret and encrypt
|
||||
|
||||
// Case 3: _credentialSecret set, credentialSecret set
|
||||
// - migrate from _credentialSecret to credentialSecret
|
||||
// - delete _credentialSecret
|
||||
|
||||
// Case 4: credentialSecret set
|
||||
// - use it
|
||||
|
||||
var setupEncryptionPromise = when.resolve();
|
||||
|
||||
var projectKey = false;
|
||||
var activeProject;
|
||||
encryptionKeyType = "";
|
||||
|
||||
if (runtime.storage && runtime.storage.projects) {
|
||||
// projects enabled
|
||||
activeProject = runtime.storage.projects.getActiveProject();
|
||||
if (activeProject) {
|
||||
projectKey = activeProject.credentialSecret;
|
||||
if (!projectKey) {
|
||||
log.debug("red/runtime/nodes/credentials.load : using active project key - disabled");
|
||||
encryptionKeyType = "disabled";
|
||||
encryptionEnabled = false;
|
||||
} else {
|
||||
log.debug("red/runtime/nodes/credentials.load : using active project key");
|
||||
encryptionKeyType = "project";
|
||||
encryptionKey = crypto.createHash('sha256').update(projectKey).digest();
|
||||
encryptionEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (encryptionKeyType === '') {
|
||||
var defaultKey;
|
||||
try {
|
||||
defaultKey = settings.get('_credentialSecret');
|
||||
} catch(err) {
|
||||
}
|
||||
if (defaultKey) {
|
||||
defaultKey = crypto.createHash('sha256').update(defaultKey).digest();
|
||||
encryptionKeyType = "system";
|
||||
}
|
||||
var userKey;
|
||||
try {
|
||||
userKey = settings.get('credentialSecret');
|
||||
} catch(err) {
|
||||
userKey = false;
|
||||
}
|
||||
|
||||
if (userKey === false) {
|
||||
encryptionKeyType = "disabled";
|
||||
log.debug("red/runtime/nodes/credentials.load : user disabled encryption");
|
||||
// User has disabled encryption
|
||||
encryptionEnabled = false;
|
||||
// Check if we have a generated _credSecret to decrypt with and remove
|
||||
if (defaultKey) {
|
||||
log.debug("red/runtime/nodes/credentials.load : default key present. Will migrate");
|
||||
if (credentialsEncrypted) {
|
||||
try {
|
||||
credentials = decryptCredentials(defaultKey,credentials)
|
||||
} catch(err) {
|
||||
credentials = {};
|
||||
log.warn(log._("nodes.credentials.error",{message:err.toString()}))
|
||||
var error = new Error("Failed to decrypt credentials");
|
||||
error.code = "credentials_load_failed";
|
||||
return when.reject(error);
|
||||
}
|
||||
}
|
||||
dirty = true;
|
||||
removeDefaultKey = true;
|
||||
}
|
||||
} else if (typeof userKey === 'string') {
|
||||
if (!projectKey) {
|
||||
log.debug("red/runtime/nodes/credentials.load : user provided key");
|
||||
}
|
||||
if (encryptionKeyType !== 'project') {
|
||||
encryptionKeyType = 'user';
|
||||
}
|
||||
// User has provided own encryption key, get the 32-byte hash of it
|
||||
encryptionKey = crypto.createHash('sha256').update(userKey).digest();
|
||||
encryptionEnabled = true;
|
||||
|
||||
if (encryptionKeyType !== 'project' && defaultKey) {
|
||||
log.debug("red/runtime/nodes/credentials.load : default key present. Will migrate");
|
||||
// User has provided their own key, but we already have a default key
|
||||
// Decrypt using default key
|
||||
if (credentialsEncrypted) {
|
||||
try {
|
||||
credentials = decryptCredentials(defaultKey,credentials)
|
||||
} catch(err) {
|
||||
credentials = {};
|
||||
log.warn(log._("nodes.credentials.error",{message:err.toString()}))
|
||||
var error = new Error("Failed to decrypt credentials");
|
||||
error.code = "credentials_load_failed";
|
||||
return when.reject(error);
|
||||
}
|
||||
}
|
||||
dirty = true;
|
||||
removeDefaultKey = true;
|
||||
}
|
||||
} else {
|
||||
log.debug("red/runtime/nodes/credentials.load : no user key present");
|
||||
// User has not provide their own key
|
||||
if (encryptionKeyType !== 'project') {
|
||||
encryptionKeyType = 'system';
|
||||
}
|
||||
encryptionKey = defaultKey;
|
||||
encryptionEnabled = true;
|
||||
if (encryptionKey === undefined) {
|
||||
log.debug("red/runtime/nodes/credentials.load : no default key present - generating one");
|
||||
// No user-provided key, no generated key
|
||||
// Generate a new key
|
||||
defaultKey = crypto.randomBytes(32).toString('hex');
|
||||
try {
|
||||
setupEncryptionPromise = settings.set('_credentialSecret',defaultKey);
|
||||
encryptionKey = crypto.createHash('sha256').update(defaultKey).digest();
|
||||
} catch(err) {
|
||||
log.debug("red/runtime/nodes/credentials.load : settings unavailable - disabling encryption");
|
||||
// Settings unavailable
|
||||
encryptionEnabled = false;
|
||||
encryptionKey = null;
|
||||
}
|
||||
dirty = true;
|
||||
} else {
|
||||
log.debug("red/runtime/nodes/credentials.load : using default key");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (encryptionEnabled && !dirty) {
|
||||
encryptedCredentials = credentials;
|
||||
}
|
||||
log.debug("red/runtime/nodes/credentials.load : keyType="+encryptionKeyType);
|
||||
if (encryptionKeyType === 'system') {
|
||||
log.warn(log._("nodes.credentials.system-key-warning"));
|
||||
}
|
||||
return setupEncryptionPromise.then(function() {
|
||||
var clearInvalidFlag = false;
|
||||
if (credentials.hasOwnProperty("$")) {
|
||||
if (encryptionEnabled === false) {
|
||||
// The credentials appear to be encrypted, but our config
|
||||
// thinks they are not.
|
||||
var error = new Error("Failed to decrypt credentials");
|
||||
error.code = "credentials_load_failed";
|
||||
if (activeProject) {
|
||||
// This is a project with a bad key. Mark it as invalid
|
||||
// TODO: this delves too deep into Project structure
|
||||
activeProject.credentialSecretInvalid = true;
|
||||
return when.reject(error);
|
||||
}
|
||||
return when.reject(error);
|
||||
}
|
||||
// These are encrypted credentials
|
||||
try {
|
||||
credentialCache = decryptCredentials(encryptionKey,credentials)
|
||||
clearInvalidFlag = true;
|
||||
} catch(err) {
|
||||
credentialCache = {};
|
||||
dirty = true;
|
||||
log.warn(log._("nodes.credentials.error",{message:err.toString()}))
|
||||
var error = new Error("Failed to decrypt credentials");
|
||||
error.code = "credentials_load_failed";
|
||||
if (activeProject) {
|
||||
// This is a project with a bad key. Mark it as invalid
|
||||
// TODO: this delves too deep into Project structure
|
||||
activeProject.credentialSecretInvalid = true;
|
||||
return when.reject(error);
|
||||
}
|
||||
return when.reject(error);
|
||||
}
|
||||
} else {
|
||||
credentialCache = credentials;
|
||||
}
|
||||
if (clearInvalidFlag) {
|
||||
// TODO: this delves too deep into Project structure
|
||||
if (activeProject) {
|
||||
delete activeProject.credentialSecretInvalid;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a set of credentials for the given node id.
|
||||
* @param id the node id for the credentials
|
||||
* @param creds an object of credential key/value pairs
|
||||
* @return a promise for backwards compatibility TODO: can this be removed?
|
||||
*/
|
||||
add: function (id, creds) {
|
||||
if (!credentialCache.hasOwnProperty(id) || JSON.stringify(creds) !== JSON.stringify(credentialCache[id])) {
|
||||
credentialCache[id] = creds;
|
||||
dirty = true;
|
||||
}
|
||||
return when.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the credentials for the given node id.
|
||||
* @param id the node id for the credentials
|
||||
* @return the credentials
|
||||
*/
|
||||
get: function (id) {
|
||||
return credentialCache[id];
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes the credentials for the given node id.
|
||||
* @param id the node id for the credentials
|
||||
* @return a promise for the saving of credentials to storage
|
||||
*/
|
||||
delete: function (id) {
|
||||
delete credentialCache[id];
|
||||
dirty = true;
|
||||
},
|
||||
|
||||
clear: function() {
|
||||
credentialCache = {};
|
||||
dirty = true;
|
||||
},
|
||||
/**
|
||||
* Deletes any credentials for nodes that no longer exist
|
||||
* @param config a flow config
|
||||
* @return a promise for the saving of credentials to storage
|
||||
*/
|
||||
clean: function (config) {
|
||||
var existingIds = {};
|
||||
config.forEach(function(n) {
|
||||
existingIds[n.id] = true;
|
||||
if (n.credentials) {
|
||||
api.extract(n);
|
||||
}
|
||||
});
|
||||
var deletedCredentials = false;
|
||||
for (var c in credentialCache) {
|
||||
if (credentialCache.hasOwnProperty(c)) {
|
||||
if (!existingIds[c]) {
|
||||
deletedCredentials = true;
|
||||
delete credentialCache[c];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deletedCredentials) {
|
||||
dirty = true;
|
||||
}
|
||||
return when.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers a node credential definition.
|
||||
* @param type the node type
|
||||
* @param definition the credential definition
|
||||
*/
|
||||
register: function (type, definition) {
|
||||
var dashedType = type.replace(/\s+/g, '-');
|
||||
credentialsDef[dashedType] = definition;
|
||||
},
|
||||
|
||||
/**
|
||||
* Extracts and stores any credential updates in the provided node.
|
||||
* The provided node may have a .credentials property that contains
|
||||
* new credentials for the node.
|
||||
* This function loops through the credentials in the definition for
|
||||
* the node-type and applies any of the updates provided in the node.
|
||||
*
|
||||
* This function does not save the credentials to disk as it is expected
|
||||
* to be called multiple times when a new flow is deployed.
|
||||
*
|
||||
* @param node the node to extract credentials from
|
||||
*/
|
||||
extract: function(node) {
|
||||
var nodeID = node.id;
|
||||
var nodeType = node.type;
|
||||
var newCreds = node.credentials;
|
||||
if (newCreds) {
|
||||
delete node.credentials;
|
||||
var savedCredentials = credentialCache[nodeID] || {};
|
||||
var dashedType = nodeType.replace(/\s+/g, '-');
|
||||
var definition = credentialsDef[dashedType];
|
||||
if (!definition) {
|
||||
log.warn(log._("nodes.credentials.not-registered",{type:nodeType}));
|
||||
return;
|
||||
}
|
||||
|
||||
for (var cred in definition) {
|
||||
if (definition.hasOwnProperty(cred)) {
|
||||
if (newCreds[cred] === undefined) {
|
||||
continue;
|
||||
}
|
||||
if (definition[cred].type == "password" && newCreds[cred] == '__PWRD__') {
|
||||
continue;
|
||||
}
|
||||
if (0 === newCreds[cred].length || /^\s*$/.test(newCreds[cred])) {
|
||||
delete savedCredentials[cred];
|
||||
dirty = true;
|
||||
continue;
|
||||
}
|
||||
if (!savedCredentials.hasOwnProperty(cred) || JSON.stringify(savedCredentials[cred]) !== JSON.stringify(newCreds[cred])) {
|
||||
savedCredentials[cred] = newCreds[cred];
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
credentialCache[nodeID] = savedCredentials;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the credential definition for the given node type
|
||||
* @param type the node type
|
||||
* @return the credential definition
|
||||
*/
|
||||
getDefinition: function (type) {
|
||||
return credentialsDef[type];
|
||||
},
|
||||
|
||||
dirty: function() {
|
||||
return dirty;
|
||||
},
|
||||
setKey: function(key) {
|
||||
if (key) {
|
||||
encryptionKey = crypto.createHash('sha256').update(key).digest();
|
||||
encryptionEnabled = true;
|
||||
dirty = true;
|
||||
encryptionKeyType = "project";
|
||||
} else {
|
||||
encryptionKey = null;
|
||||
encryptionEnabled = false;
|
||||
dirty = true;
|
||||
encryptionKeyType = "disabled";
|
||||
}
|
||||
},
|
||||
getKeyType: function() {
|
||||
return encryptionKeyType;
|
||||
},
|
||||
export: function() {
|
||||
var result = credentialCache;
|
||||
|
||||
if (encryptionEnabled) {
|
||||
if (dirty) {
|
||||
try {
|
||||
log.debug("red/runtime/nodes/credentials.export : encrypting");
|
||||
result = encryptCredentials(encryptionKey, credentialCache);
|
||||
} catch(err) {
|
||||
log.warn(log._("nodes.credentials.error-saving",{message:err.toString()}))
|
||||
}
|
||||
} else {
|
||||
result = encryptedCredentials;
|
||||
}
|
||||
}
|
||||
dirty = false;
|
||||
if (removeDefaultKey) {
|
||||
log.debug("red/runtime/nodes/credentials.export : removing unused default key");
|
||||
return settings.delete('_credentialSecret').then(function() {
|
||||
removeDefaultKey = false;
|
||||
return result;
|
||||
})
|
||||
} else {
|
||||
return when.resolve(result);
|
||||
}
|
||||
}
|
||||
}
|
511
packages/node_modules/@node-red/runtime/nodes/flows/Flow.js
generated
vendored
Normal file
511
packages/node_modules/@node-red/runtime/nodes/flows/Flow.js
generated
vendored
Normal file
@@ -0,0 +1,511 @@
|
||||
/**
|
||||
* 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 when = require("when");
|
||||
var clone = require("clone");
|
||||
var typeRegistry = require("@node-red/registry");
|
||||
var Log;
|
||||
var redUtil = require("../../util");
|
||||
var flowUtil = require("./util");
|
||||
var Node;
|
||||
|
||||
var nodeCloseTimeout = 15000;
|
||||
|
||||
function Flow(global,flow) {
|
||||
if (typeof flow === 'undefined') {
|
||||
flow = global;
|
||||
}
|
||||
var activeNodes = {};
|
||||
var subflowInstanceNodes = {};
|
||||
var catchNodeMap = {};
|
||||
var statusNodeMap = {};
|
||||
|
||||
this.start = function(diff) {
|
||||
var node;
|
||||
var newNode;
|
||||
var id;
|
||||
catchNodeMap = {};
|
||||
statusNodeMap = {};
|
||||
|
||||
var configNodes = Object.keys(flow.configs);
|
||||
var configNodeAttempts = {};
|
||||
while (configNodes.length > 0) {
|
||||
id = configNodes.shift();
|
||||
node = flow.configs[id];
|
||||
if (!activeNodes[id]) {
|
||||
var readyToCreate = true;
|
||||
// This node doesn't exist.
|
||||
// Check it doesn't reference another non-existent config node
|
||||
for (var prop in node) {
|
||||
if (node.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== '_users' && flow.configs[node[prop]]) {
|
||||
if (!activeNodes[node[prop]]) {
|
||||
// References a non-existent config node
|
||||
// Add it to the back of the list to try again later
|
||||
configNodes.push(id);
|
||||
configNodeAttempts[id] = (configNodeAttempts[id]||0)+1;
|
||||
if (configNodeAttempts[id] === 100) {
|
||||
throw new Error("Circular config node dependency detected: "+id);
|
||||
}
|
||||
readyToCreate = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (readyToCreate) {
|
||||
newNode = createNode(node.type,node);
|
||||
if (newNode) {
|
||||
activeNodes[id] = newNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (diff && diff.rewired) {
|
||||
for (var j=0;j<diff.rewired.length;j++) {
|
||||
var rewireNode = activeNodes[diff.rewired[j]];
|
||||
if (rewireNode) {
|
||||
rewireNode.updateWires(flow.nodes[rewireNode.id].wires);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (id in flow.nodes) {
|
||||
if (flow.nodes.hasOwnProperty(id)) {
|
||||
node = flow.nodes[id];
|
||||
if (!node.subflow) {
|
||||
if (!activeNodes[id]) {
|
||||
newNode = createNode(node.type,node);
|
||||
if (newNode) {
|
||||
activeNodes[id] = newNode;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!subflowInstanceNodes[id]) {
|
||||
try {
|
||||
var nodes = createSubflow(flow.subflows[node.subflow]||global.subflows[node.subflow],node,flow.subflows,global.subflows,activeNodes);
|
||||
subflowInstanceNodes[id] = nodes.map(function(n) { return n.id});
|
||||
for (var i=0;i<nodes.length;i++) {
|
||||
if (nodes[i]) {
|
||||
activeNodes[nodes[i].id] = nodes[i];
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
console.log(err.stack)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (id in activeNodes) {
|
||||
if (activeNodes.hasOwnProperty(id)) {
|
||||
node = activeNodes[id];
|
||||
if (node.type === "catch") {
|
||||
catchNodeMap[node.z] = catchNodeMap[node.z] || [];
|
||||
catchNodeMap[node.z].push(node);
|
||||
} else if (node.type === "status") {
|
||||
statusNodeMap[node.z] = statusNodeMap[node.z] || [];
|
||||
statusNodeMap[node.z].push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.stop = function(stopList, removedList) {
|
||||
return when.promise(function(resolve) {
|
||||
var i;
|
||||
if (stopList) {
|
||||
for (i=0;i<stopList.length;i++) {
|
||||
if (subflowInstanceNodes[stopList[i]]) {
|
||||
// The first in the list is the instance node we already
|
||||
// know about
|
||||
stopList = stopList.concat(subflowInstanceNodes[stopList[i]].slice(1))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stopList = Object.keys(activeNodes);
|
||||
}
|
||||
// Convert the list to a map to avoid multiple scans of the list
|
||||
var removedMap = {};
|
||||
removedList = removedList || [];
|
||||
removedList.forEach(function(id) {
|
||||
removedMap[id] = true;
|
||||
});
|
||||
|
||||
var promises = [];
|
||||
for (i=0;i<stopList.length;i++) {
|
||||
var node = activeNodes[stopList[i]];
|
||||
if (node) {
|
||||
delete activeNodes[stopList[i]];
|
||||
if (subflowInstanceNodes[stopList[i]]) {
|
||||
delete subflowInstanceNodes[stopList[i]];
|
||||
}
|
||||
try {
|
||||
var removed = removedMap[stopList[i]];
|
||||
promises.push(
|
||||
when.promise(function(resolve, reject) {
|
||||
var start;
|
||||
var nt = node.type;
|
||||
var nid = node.id;
|
||||
var n = node;
|
||||
when.promise(function(resolve) {
|
||||
Log.trace("Stopping node "+nt+":"+nid+(removed?" removed":""));
|
||||
start = Date.now();
|
||||
resolve(n.close(removed));
|
||||
}).timeout(nodeCloseTimeout).then(function(){
|
||||
var delta = Date.now() - start;
|
||||
Log.trace("Stopped node "+nt+":"+nid+" ("+delta+"ms)" );
|
||||
resolve(delta);
|
||||
},function(err) {
|
||||
var delta = Date.now() - start;
|
||||
n.error(Log._("nodes.flows.stopping-error",{message:err}));
|
||||
Log.debug(err.stack);
|
||||
reject(err);
|
||||
});
|
||||
})
|
||||
);
|
||||
} catch(err) {
|
||||
node.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
when.settle(promises).then(function(results) {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.update = function(_global,_flow) {
|
||||
global = _global;
|
||||
flow = _flow;
|
||||
}
|
||||
|
||||
this.getNode = function(id) {
|
||||
return activeNodes[id];
|
||||
}
|
||||
|
||||
this.getActiveNodes = function() {
|
||||
return activeNodes;
|
||||
}
|
||||
|
||||
this.handleStatus = function(node,statusMessage) {
|
||||
var targetStatusNodes = null;
|
||||
var reportingNode = node;
|
||||
var handled = false;
|
||||
while (reportingNode && !handled) {
|
||||
targetStatusNodes = statusNodeMap[reportingNode.z];
|
||||
if (targetStatusNodes) {
|
||||
targetStatusNodes.forEach(function(targetStatusNode) {
|
||||
if (targetStatusNode.scope && targetStatusNode.scope.indexOf(node.id) === -1) {
|
||||
return;
|
||||
}
|
||||
var message = {
|
||||
status: {
|
||||
text: "",
|
||||
source: {
|
||||
id: node.id,
|
||||
type: node.type,
|
||||
name: node.name
|
||||
}
|
||||
}
|
||||
};
|
||||
if (statusMessage.hasOwnProperty("text")) {
|
||||
message.status.text = statusMessage.text.toString();
|
||||
}
|
||||
targetStatusNode.receive(message);
|
||||
handled = true;
|
||||
});
|
||||
}
|
||||
if (!handled) {
|
||||
reportingNode = activeNodes[reportingNode.z];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.handleError = function(node,logMessage,msg) {
|
||||
var count = 1;
|
||||
if (msg && msg.hasOwnProperty("error") && msg.error !== null) {
|
||||
if (msg.error.hasOwnProperty("source") && msg.error.source !== null) {
|
||||
if (msg.error.source.id === node.id) {
|
||||
count = msg.error.source.count+1;
|
||||
if (count === 10) {
|
||||
node.warn(Log._("nodes.flow.error-loop"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var targetCatchNodes = null;
|
||||
var throwingNode = node;
|
||||
var handled = false;
|
||||
while (throwingNode && !handled) {
|
||||
targetCatchNodes = catchNodeMap[throwingNode.z];
|
||||
if (targetCatchNodes) {
|
||||
targetCatchNodes.forEach(function(targetCatchNode) {
|
||||
if (targetCatchNode.scope && targetCatchNode.scope.indexOf(throwingNode.id) === -1) {
|
||||
return;
|
||||
}
|
||||
var errorMessage;
|
||||
if (msg) {
|
||||
errorMessage = redUtil.cloneMessage(msg);
|
||||
} else {
|
||||
errorMessage = {};
|
||||
}
|
||||
if (errorMessage.hasOwnProperty("error")) {
|
||||
errorMessage._error = errorMessage.error;
|
||||
}
|
||||
errorMessage.error = {
|
||||
message: logMessage.toString(),
|
||||
source: {
|
||||
id: node.id,
|
||||
type: node.type,
|
||||
name: node.name,
|
||||
count: count
|
||||
}
|
||||
};
|
||||
if (logMessage.hasOwnProperty('stack')) {
|
||||
errorMessage.error.stack = logMessage.stack;
|
||||
}
|
||||
targetCatchNode.receive(errorMessage);
|
||||
handled = true;
|
||||
});
|
||||
}
|
||||
if (!handled) {
|
||||
throwingNode = activeNodes[throwingNode.z];
|
||||
}
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
}
|
||||
|
||||
function createNode(type,config) {
|
||||
var nn = null;
|
||||
try {
|
||||
var nt = typeRegistry.get(type);
|
||||
if (nt) {
|
||||
var conf = clone(config);
|
||||
delete conf.credentials;
|
||||
for (var p in conf) {
|
||||
if (conf.hasOwnProperty(p)) {
|
||||
flowUtil.mapEnvVarProperties(conf,p);
|
||||
}
|
||||
}
|
||||
try {
|
||||
nn = new nt(conf);
|
||||
}
|
||||
catch (err) {
|
||||
Log.log({
|
||||
level: Log.ERROR,
|
||||
id:conf.id,
|
||||
type: type,
|
||||
msg: err
|
||||
});
|
||||
}
|
||||
} else {
|
||||
Log.error(Log._("nodes.flow.unknown-type", {type:type}));
|
||||
}
|
||||
} catch(err) {
|
||||
Log.error(err);
|
||||
}
|
||||
return nn;
|
||||
}
|
||||
|
||||
function createSubflow(sf,sfn,subflows,globalSubflows,activeNodes) {
|
||||
//console.log("CREATE SUBFLOW",sf.id,sfn.id);
|
||||
var nodes = [];
|
||||
var node_map = {};
|
||||
var newNodes = [];
|
||||
var node;
|
||||
var wires;
|
||||
var i,j,k;
|
||||
|
||||
var createNodeInSubflow = function(def) {
|
||||
node = clone(def);
|
||||
var nid = redUtil.generateId();
|
||||
node_map[node.id] = node;
|
||||
node._alias = node.id;
|
||||
node.id = nid;
|
||||
node.z = sfn.id;
|
||||
newNodes.push(node);
|
||||
}
|
||||
|
||||
// Clone all of the subflow node definitions and give them new IDs
|
||||
for (i in sf.configs) {
|
||||
if (sf.configs.hasOwnProperty(i)) {
|
||||
createNodeInSubflow(sf.configs[i]);
|
||||
}
|
||||
}
|
||||
// Clone all of the subflow node definitions and give them new IDs
|
||||
for (i in sf.nodes) {
|
||||
if (sf.nodes.hasOwnProperty(i)) {
|
||||
createNodeInSubflow(sf.nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Look for any catch/status nodes and update their scope ids
|
||||
// Update all subflow interior wiring to reflect new node IDs
|
||||
for (i=0;i<newNodes.length;i++) {
|
||||
node = newNodes[i];
|
||||
if (node.wires) {
|
||||
var outputs = node.wires;
|
||||
for (j=0;j<outputs.length;j++) {
|
||||
wires = outputs[j];
|
||||
for (k=0;k<wires.length;k++) {
|
||||
outputs[j][k] = node_map[outputs[j][k]].id
|
||||
}
|
||||
}
|
||||
if ((node.type === 'catch' || node.type === 'status') && node.scope) {
|
||||
node.scope = node.scope.map(function(id) {
|
||||
return node_map[id]?node_map[id].id:""
|
||||
})
|
||||
} else {
|
||||
for (var prop in node) {
|
||||
if (node.hasOwnProperty(prop) && prop !== '_alias') {
|
||||
if (node_map[node[prop]]) {
|
||||
//console.log("Mapped",node.type,node.id,prop,node_map[node[prop]].id);
|
||||
node[prop] = node_map[node[prop]].id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a subflow node to accept inbound messages and route appropriately
|
||||
var Node = require("../Node");
|
||||
var subflowInstance = {
|
||||
id: sfn.id,
|
||||
type: sfn.type,
|
||||
z: sfn.z,
|
||||
name: sfn.name,
|
||||
wires: []
|
||||
}
|
||||
if (sf.in) {
|
||||
subflowInstance.wires = sf.in.map(function(n) { return n.wires.map(function(w) { return node_map[w.id].id;})})
|
||||
subflowInstance._originalWires = clone(subflowInstance.wires);
|
||||
}
|
||||
var subflowNode = new Node(subflowInstance);
|
||||
|
||||
subflowNode.on("input", function(msg) { this.send(msg);});
|
||||
|
||||
|
||||
subflowNode._updateWires = subflowNode.updateWires;
|
||||
|
||||
subflowNode.updateWires = function(newWires) {
|
||||
// Wire the subflow outputs
|
||||
if (sf.out) {
|
||||
var node,wires,i,j;
|
||||
// Restore the original wiring to the internal nodes
|
||||
subflowInstance.wires = clone(subflowInstance._originalWires);
|
||||
for (i=0;i<sf.out.length;i++) {
|
||||
wires = sf.out[i].wires;
|
||||
for (j=0;j<wires.length;j++) {
|
||||
if (wires[j].id != sf.id) {
|
||||
node = node_map[wires[j].id];
|
||||
if (node._originalWires) {
|
||||
node.wires = clone(node._originalWires);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var modifiedNodes = {};
|
||||
var subflowInstanceModified = false;
|
||||
|
||||
for (i=0;i<sf.out.length;i++) {
|
||||
wires = sf.out[i].wires;
|
||||
for (j=0;j<wires.length;j++) {
|
||||
if (wires[j].id === sf.id) {
|
||||
subflowInstance.wires[wires[j].port] = subflowInstance.wires[wires[j].port].concat(newWires[i]);
|
||||
subflowInstanceModified = true;
|
||||
} else {
|
||||
node = node_map[wires[j].id];
|
||||
node.wires[wires[j].port] = node.wires[wires[j].port].concat(newWires[i]);
|
||||
modifiedNodes[node.id] = node;
|
||||
}
|
||||
}
|
||||
}
|
||||
Object.keys(modifiedNodes).forEach(function(id) {
|
||||
var node = modifiedNodes[id];
|
||||
subflowNode.instanceNodes[id].updateWires(node.wires);
|
||||
});
|
||||
if (subflowInstanceModified) {
|
||||
subflowNode._updateWires(subflowInstance.wires);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodes.push(subflowNode);
|
||||
|
||||
// Wire the subflow outputs
|
||||
if (sf.out) {
|
||||
var modifiedNodes = {};
|
||||
for (i=0;i<sf.out.length;i++) {
|
||||
wires = sf.out[i].wires;
|
||||
for (j=0;j<wires.length;j++) {
|
||||
if (wires[j].id === sf.id) {
|
||||
// A subflow input wired straight to a subflow output
|
||||
subflowInstance.wires[wires[j].port] = subflowInstance.wires[wires[j].port].concat(sfn.wires[i])
|
||||
subflowNode._updateWires(subflowInstance.wires);
|
||||
} else {
|
||||
node = node_map[wires[j].id];
|
||||
modifiedNodes[node.id] = node;
|
||||
if (!node._originalWires) {
|
||||
node._originalWires = clone(node.wires);
|
||||
}
|
||||
node.wires[wires[j].port] = (node.wires[wires[j].port]||[]).concat(sfn.wires[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate the nodes
|
||||
for (i=0;i<newNodes.length;i++) {
|
||||
node = newNodes[i];
|
||||
var type = node.type;
|
||||
|
||||
var m = /^subflow:(.+)$/.exec(type);
|
||||
if (!m) {
|
||||
var newNode = createNode(type,node);
|
||||
if (newNode) {
|
||||
activeNodes[node.id] = newNode;
|
||||
nodes.push(newNode);
|
||||
}
|
||||
} else {
|
||||
var subflowId = m[1];
|
||||
nodes = nodes.concat(createSubflow(subflows[subflowId]||globalSubflows[subflowId],node,subflows,globalSubflows,activeNodes));
|
||||
}
|
||||
}
|
||||
|
||||
subflowNode.instanceNodes = {};
|
||||
|
||||
nodes.forEach(function(node) {
|
||||
subflowNode.instanceNodes[node.id] = node;
|
||||
});
|
||||
return nodes;
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
init: function(runtime) {
|
||||
nodeCloseTimeout = runtime.settings.nodeCloseTimeout || 15000;
|
||||
Log = runtime.log;
|
||||
Node = require("../Node");
|
||||
},
|
||||
create: function(global,conf) {
|
||||
return new Flow(global,conf);
|
||||
}
|
||||
}
|
735
packages/node_modules/@node-red/runtime/nodes/flows/index.js
generated
vendored
Normal file
735
packages/node_modules/@node-red/runtime/nodes/flows/index.js
generated
vendored
Normal file
@@ -0,0 +1,735 @@
|
||||
/**
|
||||
* 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 Flow = require('./Flow');
|
||||
|
||||
var typeRegistry = require("@node-red/registry");
|
||||
var deprecated = typeRegistry.deprecated;
|
||||
|
||||
|
||||
var context = require("../context")
|
||||
var credentials = require("../credentials");
|
||||
|
||||
var flowUtil = require("./util");
|
||||
var log;
|
||||
var events = require("../../events");
|
||||
var redUtil = require("../../util");
|
||||
|
||||
var storage = null;
|
||||
var settings = null;
|
||||
|
||||
var activeConfig = null;
|
||||
var activeFlowConfig = null;
|
||||
|
||||
var activeFlows = {};
|
||||
var started = false;
|
||||
var credentialsPendingReset = false;
|
||||
|
||||
var activeNodesToFlow = {};
|
||||
var subflowInstanceNodeMap = {};
|
||||
|
||||
var typeEventRegistered = false;
|
||||
|
||||
function init(runtime) {
|
||||
if (started) {
|
||||
throw new Error("Cannot init without a stop");
|
||||
}
|
||||
settings = runtime.settings;
|
||||
storage = runtime.storage;
|
||||
log = runtime.log;
|
||||
started = false;
|
||||
if (!typeEventRegistered) {
|
||||
events.on('type-registered',function(type) {
|
||||
if (activeFlowConfig && activeFlowConfig.missingTypes.length > 0) {
|
||||
var i = activeFlowConfig.missingTypes.indexOf(type);
|
||||
if (i != -1) {
|
||||
log.info(log._("nodes.flows.registered-missing", {type:type}));
|
||||
activeFlowConfig.missingTypes.splice(i,1);
|
||||
if (activeFlowConfig.missingTypes.length === 0 && started) {
|
||||
events.emit("runtime-event",{id:"runtime-state",retain: true});
|
||||
start();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
typeEventRegistered = true;
|
||||
}
|
||||
Flow.init(runtime);
|
||||
}
|
||||
|
||||
function loadFlows() {
|
||||
var config;
|
||||
return storage.getFlows().then(function(_config) {
|
||||
config = _config;
|
||||
log.debug("loaded flow revision: "+config.rev);
|
||||
return credentials.load(config.credentials).then(function() {
|
||||
events.emit("runtime-event",{id:"runtime-state",retain:true});
|
||||
return config;
|
||||
});
|
||||
}).catch(function(err) {
|
||||
if (err.code === "credentials_load_failed" && !storage.projects) {
|
||||
// project disabled, credential load failed
|
||||
credentialsPendingReset = true;
|
||||
log.warn(log._("nodes.flows.error",{message:err.toString()}));
|
||||
events.emit("runtime-event",{id:"runtime-state",payload:{type:"warning",error:err.code,text:"notification.warnings.credentials_load_failed_reset"},retain:true});
|
||||
return config;
|
||||
} else {
|
||||
activeConfig = null;
|
||||
events.emit("runtime-event",{id:"runtime-state",payload:{type:"warning",error:err.code,project:err.project,text:"notification.warnings."+err.code},retain:true});
|
||||
if (err.code === "project_not_found") {
|
||||
log.warn(log._("storage.localfilesystem.projects.project-not-found",{project:err.project}));
|
||||
} else {
|
||||
log.warn(log._("nodes.flows.error",{message:err.toString()}));
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
function load(forceStart) {
|
||||
return setFlows(null,"load",false,forceStart);
|
||||
}
|
||||
|
||||
/*
|
||||
* _config - new node array configuration
|
||||
* 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) {
|
||||
type = type||"full";
|
||||
|
||||
var configSavePromise = null;
|
||||
var config = null;
|
||||
var diff;
|
||||
var newFlowConfig;
|
||||
var isLoad = false;
|
||||
if (type === "load") {
|
||||
isLoad = true;
|
||||
configSavePromise = loadFlows().then(function(_config) {
|
||||
config = clone(_config.flows);
|
||||
newFlowConfig = flowUtil.parseConfig(clone(config));
|
||||
type = "full";
|
||||
return _config.rev;
|
||||
});
|
||||
} else {
|
||||
config = clone(_config);
|
||||
newFlowConfig = flowUtil.parseConfig(clone(config));
|
||||
diff = flowUtil.diffConfigs(activeFlowConfig,newFlowConfig);
|
||||
|
||||
// Now the flows have been compared, remove any credentials from newFlowConfig
|
||||
// so they don't cause false-positive diffs the next time a flow is deployed
|
||||
for (var id in newFlowConfig.allNodes) {
|
||||
if (newFlowConfig.allNodes.hasOwnProperty(id)) {
|
||||
delete newFlowConfig.allNodes[id].credentials;
|
||||
}
|
||||
}
|
||||
|
||||
credentials.clean(config);
|
||||
var credsDirty = credentials.dirty();
|
||||
configSavePromise = credentials.export().then(function(creds) {
|
||||
var saveConfig = {
|
||||
flows: config,
|
||||
credentialsDirty:credsDirty,
|
||||
credentials: creds
|
||||
}
|
||||
return storage.saveFlows(saveConfig);
|
||||
});
|
||||
}
|
||||
|
||||
return configSavePromise
|
||||
.then(function(flowRevision) {
|
||||
if (!isLoad) {
|
||||
log.debug("saved flow revision: "+flowRevision);
|
||||
}
|
||||
activeConfig = {
|
||||
flows:config,
|
||||
rev:flowRevision
|
||||
};
|
||||
activeFlowConfig = newFlowConfig;
|
||||
if (forceStart || started) {
|
||||
return stop(type,diff,muteLog).then(function() {
|
||||
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;
|
||||
});
|
||||
}).catch(function(err) {
|
||||
})
|
||||
} else {
|
||||
events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getNode(id) {
|
||||
var node;
|
||||
if (activeNodesToFlow[id] && activeFlows[activeNodesToFlow[id]]) {
|
||||
return activeFlows[activeNodesToFlow[id]].getNode(id);
|
||||
}
|
||||
for (var flowId in activeFlows) {
|
||||
if (activeFlows.hasOwnProperty(flowId)) {
|
||||
node = activeFlows[flowId].getNode(id);
|
||||
if (node) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function eachNode(cb) {
|
||||
for (var id in activeFlowConfig.allNodes) {
|
||||
if (activeFlowConfig.allNodes.hasOwnProperty(id)) {
|
||||
cb(activeFlowConfig.allNodes[id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getFlows() {
|
||||
return activeConfig;
|
||||
}
|
||||
|
||||
function delegateError(node,logMessage,msg) {
|
||||
var handled = false;
|
||||
if (activeFlows[node.z]) {
|
||||
handled = activeFlows[node.z].handleError(node,logMessage,msg);
|
||||
} else if (activeNodesToFlow[node.z] && activeFlows[activeNodesToFlow[node.z]]) {
|
||||
handled = activeFlows[activeNodesToFlow[node.z]].handleError(node,logMessage,msg);
|
||||
} else if (activeFlowConfig.subflows[node.z] && subflowInstanceNodeMap[node.id]) {
|
||||
subflowInstanceNodeMap[node.id].forEach(function(n) {
|
||||
handled = handled || delegateError(getNode(n),logMessage,msg);
|
||||
});
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
function handleError(node,logMessage,msg) {
|
||||
var handled = false;
|
||||
if (node.z) {
|
||||
handled = delegateError(node,logMessage,msg);
|
||||
} else {
|
||||
if (activeFlowConfig.configs[node.id]) {
|
||||
activeFlowConfig.configs[node.id]._users.forEach(function(id) {
|
||||
var userNode = activeFlowConfig.allNodes[id];
|
||||
handled = handled || delegateError(userNode,logMessage,msg);
|
||||
})
|
||||
}
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
function delegateStatus(node,statusMessage) {
|
||||
if (activeFlows[node.z]) {
|
||||
activeFlows[node.z].handleStatus(node,statusMessage);
|
||||
} else if (activeNodesToFlow[node.z] && activeFlows[activeNodesToFlow[node.z]]) {
|
||||
activeFlows[activeNodesToFlow[node.z]].handleStatus(node,statusMessage);
|
||||
}
|
||||
}
|
||||
function handleStatus(node,statusMessage) {
|
||||
events.emit("node-status",{
|
||||
id: node.id,
|
||||
status:statusMessage
|
||||
});
|
||||
if (node.z) {
|
||||
delegateStatus(node,statusMessage);
|
||||
} else {
|
||||
if (activeFlowConfig.configs[node.id]) {
|
||||
activeFlowConfig.configs[node.id]._users.forEach(function(id) {
|
||||
var userNode = activeFlowConfig.allNodes[id];
|
||||
delegateStatus(userNode,statusMessage);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function start(type,diff,muteLog) {
|
||||
//dumpActiveNodes();
|
||||
type = type||"full";
|
||||
started = true;
|
||||
var i;
|
||||
if (activeFlowConfig.missingTypes.length > 0) {
|
||||
log.info(log._("nodes.flows.missing-types"));
|
||||
var knownUnknowns = 0;
|
||||
for (i=0;i<activeFlowConfig.missingTypes.length;i++) {
|
||||
var nodeType = activeFlowConfig.missingTypes[i];
|
||||
var info = deprecated.get(nodeType);
|
||||
if (info) {
|
||||
log.info(log._("nodes.flows.missing-type-provided",{type:activeFlowConfig.missingTypes[i],module:info.module}));
|
||||
knownUnknowns += 1;
|
||||
} else {
|
||||
log.info(" - "+activeFlowConfig.missingTypes[i]);
|
||||
}
|
||||
}
|
||||
if (knownUnknowns > 0) {
|
||||
log.info(log._("nodes.flows.missing-type-install-1"));
|
||||
log.info(" npm install <module name>");
|
||||
log.info(log._("nodes.flows.missing-type-install-2"));
|
||||
log.info(" "+settings.userDir);
|
||||
}
|
||||
events.emit("runtime-event",{id:"runtime-state",payload:{error:"missing-types", type:"warning",text:"notification.warnings.missing-types",types:activeFlowConfig.missingTypes},retain:true});
|
||||
return when.resolve();
|
||||
}
|
||||
if (!muteLog) {
|
||||
if (type !== "full") {
|
||||
log.info(log._("nodes.flows.starting-modified-"+type));
|
||||
} else {
|
||||
log.info(log._("nodes.flows.starting-flows"));
|
||||
}
|
||||
}
|
||||
var id;
|
||||
if (type === "full") {
|
||||
if (!activeFlows['global']) {
|
||||
log.debug("red/nodes/flows.start : starting flow : global");
|
||||
activeFlows['global'] = Flow.create(activeFlowConfig);
|
||||
}
|
||||
for (id in activeFlowConfig.flows) {
|
||||
if (activeFlowConfig.flows.hasOwnProperty(id)) {
|
||||
if (!activeFlowConfig.flows[id].disabled && !activeFlows[id]) {
|
||||
activeFlows[id] = Flow.create(activeFlowConfig,activeFlowConfig.flows[id]);
|
||||
log.debug("red/nodes/flows.start : starting flow : "+id);
|
||||
} else {
|
||||
log.debug("red/nodes/flows.start : not starting disabled flow : "+id);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
activeFlows['global'].update(activeFlowConfig,activeFlowConfig);
|
||||
for (id in activeFlowConfig.flows) {
|
||||
if (activeFlowConfig.flows.hasOwnProperty(id)) {
|
||||
if (!activeFlowConfig.flows[id].disabled) {
|
||||
if (activeFlows[id]) {
|
||||
activeFlows[id].update(activeFlowConfig,activeFlowConfig.flows[id]);
|
||||
} else {
|
||||
activeFlows[id] = Flow.create(activeFlowConfig,activeFlowConfig.flows[id]);
|
||||
log.debug("red/nodes/flows.start : starting flow : "+id);
|
||||
}
|
||||
} else {
|
||||
log.debug("red/nodes/flows.start : not starting disabled flow : "+id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (id in activeFlows) {
|
||||
if (activeFlows.hasOwnProperty(id)) {
|
||||
activeFlows[id].start(diff);
|
||||
var activeNodes = activeFlows[id].getActiveNodes();
|
||||
Object.keys(activeNodes).forEach(function(nid) {
|
||||
activeNodesToFlow[nid] = id;
|
||||
if (activeNodes[nid]._alias) {
|
||||
subflowInstanceNodeMap[activeNodes[nid]._alias] = subflowInstanceNodeMap[activeNodes[nid]._alias] || [];
|
||||
subflowInstanceNodeMap[activeNodes[nid]._alias].push(nid);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
events.emit("nodes-started");
|
||||
|
||||
if (credentialsPendingReset === true) {
|
||||
credentialsPendingReset = false;
|
||||
} else {
|
||||
events.emit("runtime-event",{id:"runtime-state",retain:true});
|
||||
}
|
||||
|
||||
if (!muteLog) {
|
||||
if (type !== "full") {
|
||||
log.info(log._("nodes.flows.started-modified-"+type));
|
||||
} else {
|
||||
log.info(log._("nodes.flows.started-flows"));
|
||||
}
|
||||
}
|
||||
return when.resolve();
|
||||
}
|
||||
|
||||
function stop(type,diff,muteLog) {
|
||||
if (!started) {
|
||||
return when.resolve();
|
||||
}
|
||||
type = type||"full";
|
||||
diff = diff||{
|
||||
added:[],
|
||||
changed:[],
|
||||
removed:[],
|
||||
rewired:[],
|
||||
linked:[]
|
||||
};
|
||||
if (!muteLog) {
|
||||
if (type !== "full") {
|
||||
log.info(log._("nodes.flows.stopping-modified-"+type));
|
||||
} else {
|
||||
log.info(log._("nodes.flows.stopping-flows"));
|
||||
}
|
||||
}
|
||||
started = false;
|
||||
var promises = [];
|
||||
var stopList;
|
||||
var removedList = diff.removed;
|
||||
if (type === 'nodes') {
|
||||
stopList = diff.changed.concat(diff.removed);
|
||||
} else if (type === 'flows') {
|
||||
stopList = diff.changed.concat(diff.removed).concat(diff.linked);
|
||||
}
|
||||
|
||||
for (var id in activeFlows) {
|
||||
if (activeFlows.hasOwnProperty(id)) {
|
||||
var flowStateChanged = diff && (diff.added.indexOf(id) !== -1 || diff.removed.indexOf(id) !== -1);
|
||||
log.debug("red/nodes/flows.stop : stopping flow : "+id);
|
||||
promises = promises.concat(activeFlows[id].stop(flowStateChanged?null:stopList,removedList));
|
||||
if (type === "full" || flowStateChanged || diff.removed.indexOf(id)!==-1) {
|
||||
delete activeFlows[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return when.promise(function(resolve,reject) {
|
||||
when.settle(promises).then(function() {
|
||||
for (id in activeNodesToFlow) {
|
||||
if (activeNodesToFlow.hasOwnProperty(id)) {
|
||||
if (!activeFlows[activeNodesToFlow[id]]) {
|
||||
delete activeNodesToFlow[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stopList) {
|
||||
stopList.forEach(function(id) {
|
||||
delete activeNodesToFlow[id];
|
||||
});
|
||||
}
|
||||
// Ideally we'd prune just what got stopped - but mapping stopList
|
||||
// id to the list of subflow instance nodes is something only Flow
|
||||
// can do... so cheat by wiping the map knowing it'll be rebuilt
|
||||
// in start()
|
||||
subflowInstanceNodeMap = {};
|
||||
if (!muteLog) {
|
||||
if (type !== "full") {
|
||||
log.info(log._("nodes.flows.stopped-modified-"+type));
|
||||
} else {
|
||||
log.info(log._("nodes.flows.stopped-flows"));
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function checkTypeInUse(id) {
|
||||
var nodeInfo = typeRegistry.getNodeInfo(id);
|
||||
if (!nodeInfo) {
|
||||
throw new Error(log._("nodes.index.unrecognised-id", {id:id}));
|
||||
} else {
|
||||
var inUse = {};
|
||||
var config = getFlows();
|
||||
config.flows.forEach(function(n) {
|
||||
inUse[n.type] = (inUse[n.type]||0)+1;
|
||||
});
|
||||
var nodesInUse = [];
|
||||
nodeInfo.types.forEach(function(t) {
|
||||
if (inUse[t]) {
|
||||
nodesInUse.push(t);
|
||||
}
|
||||
});
|
||||
if (nodesInUse.length > 0) {
|
||||
var msg = nodesInUse.join(", ");
|
||||
var err = new Error(log._("nodes.index.type-in-use", {msg:msg}));
|
||||
err.code = "type_in_use";
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateMissingTypes() {
|
||||
var subflowInstanceRE = /^subflow:(.+)$/;
|
||||
activeFlowConfig.missingTypes = [];
|
||||
|
||||
for (var id in activeFlowConfig.allNodes) {
|
||||
if (activeFlowConfig.allNodes.hasOwnProperty(id)) {
|
||||
var node = activeFlowConfig.allNodes[id];
|
||||
if (node.type !== 'tab' && node.type !== 'subflow') {
|
||||
var subflowDetails = subflowInstanceRE.exec(node.type);
|
||||
if ( (subflowDetails && !activeFlowConfig.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(node.type)) ) {
|
||||
if (activeFlowConfig.missingTypes.indexOf(node.type) === -1) {
|
||||
activeFlowConfig.missingTypes.push(node.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addFlow(flow) {
|
||||
var i,node;
|
||||
if (!flow.hasOwnProperty('nodes')) {
|
||||
throw new Error('missing nodes property');
|
||||
}
|
||||
flow.id = redUtil.generateId();
|
||||
|
||||
var tabNode = {
|
||||
type:'tab',
|
||||
label:flow.label,
|
||||
id:flow.id
|
||||
}
|
||||
if (flow.hasOwnProperty('info')) {
|
||||
tabNode.info = flow.info;
|
||||
}
|
||||
if (flow.hasOwnProperty('disabled')) {
|
||||
tabNode.disabled = flow.disabled;
|
||||
}
|
||||
|
||||
var nodes = [tabNode];
|
||||
|
||||
for (i=0;i<flow.nodes.length;i++) {
|
||||
node = flow.nodes[i];
|
||||
if (activeFlowConfig.allNodes[node.id]) {
|
||||
// TODO nls
|
||||
return when.reject(new Error('duplicate id'));
|
||||
}
|
||||
if (node.type === 'tab' || node.type === 'subflow') {
|
||||
return when.reject(new Error('invalid node type: '+node.type));
|
||||
}
|
||||
node.z = flow.id;
|
||||
nodes.push(node);
|
||||
}
|
||||
if (flow.configs) {
|
||||
for (i=0;i<flow.configs.length;i++) {
|
||||
node = flow.configs[i];
|
||||
if (activeFlowConfig.allNodes[node.id]) {
|
||||
// TODO nls
|
||||
return when.reject(new Error('duplicate id'));
|
||||
}
|
||||
if (node.type === 'tab' || node.type === 'subflow') {
|
||||
return when.reject(new Error('invalid node type: '+node.type));
|
||||
}
|
||||
node.z = flow.id;
|
||||
nodes.push(node);
|
||||
}
|
||||
}
|
||||
var newConfig = clone(activeConfig.flows);
|
||||
newConfig = newConfig.concat(nodes);
|
||||
|
||||
return setFlows(newConfig,'flows',true).then(function() {
|
||||
log.info(log._("nodes.flows.added-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
|
||||
return flow.id;
|
||||
});
|
||||
}
|
||||
|
||||
function getFlow(id) {
|
||||
var flow;
|
||||
if (id === 'global') {
|
||||
flow = activeFlowConfig;
|
||||
} else {
|
||||
flow = activeFlowConfig.flows[id];
|
||||
}
|
||||
if (!flow) {
|
||||
return null;
|
||||
}
|
||||
var result = {
|
||||
id: id
|
||||
};
|
||||
if (flow.label) {
|
||||
result.label = flow.label;
|
||||
}
|
||||
if (flow.disabled) {
|
||||
result.disabled = flow.disabled;
|
||||
}
|
||||
if (flow.hasOwnProperty('info')) {
|
||||
result.info = flow.info;
|
||||
}
|
||||
if (id !== 'global') {
|
||||
result.nodes = [];
|
||||
}
|
||||
if (flow.nodes) {
|
||||
var nodeIds = Object.keys(flow.nodes);
|
||||
if (nodeIds.length > 0) {
|
||||
result.nodes = nodeIds.map(function(nodeId) {
|
||||
var node = clone(flow.nodes[nodeId]);
|
||||
if (node.type === 'link out') {
|
||||
delete node.wires;
|
||||
}
|
||||
return node;
|
||||
})
|
||||
}
|
||||
}
|
||||
if (flow.configs) {
|
||||
var configIds = Object.keys(flow.configs);
|
||||
result.configs = configIds.map(function(configId) {
|
||||
return clone(flow.configs[configId]);
|
||||
})
|
||||
if (result.configs.length === 0) {
|
||||
delete result.configs;
|
||||
}
|
||||
}
|
||||
if (flow.subflows) {
|
||||
var subflowIds = Object.keys(flow.subflows);
|
||||
result.subflows = subflowIds.map(function(subflowId) {
|
||||
var subflow = clone(flow.subflows[subflowId]);
|
||||
var nodeIds = Object.keys(subflow.nodes);
|
||||
subflow.nodes = nodeIds.map(function(id) {
|
||||
return subflow.nodes[id];
|
||||
});
|
||||
if (subflow.configs) {
|
||||
var configIds = Object.keys(subflow.configs);
|
||||
subflow.configs = configIds.map(function(id) {
|
||||
return subflow.configs[id];
|
||||
})
|
||||
}
|
||||
delete subflow.instances;
|
||||
return subflow;
|
||||
});
|
||||
if (result.subflows.length === 0) {
|
||||
delete result.subflows;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function updateFlow(id,newFlow) {
|
||||
var label = id;
|
||||
if (id !== 'global') {
|
||||
if (!activeFlowConfig.flows[id]) {
|
||||
var e = new Error();
|
||||
e.code = 404;
|
||||
throw e;
|
||||
}
|
||||
label = activeFlowConfig.flows[id].label;
|
||||
}
|
||||
var newConfig = clone(activeConfig.flows);
|
||||
var nodes;
|
||||
|
||||
if (id === 'global') {
|
||||
// Remove all nodes whose z is not a known flow
|
||||
// When subflows can be owned by a flow, this logic will have to take
|
||||
// that into account
|
||||
newConfig = newConfig.filter(function(node) {
|
||||
return node.type === 'tab' || (node.hasOwnProperty('z') && activeFlowConfig.flows.hasOwnProperty(node.z));
|
||||
})
|
||||
|
||||
// Add in the new config nodes
|
||||
nodes = newFlow.configs||[];
|
||||
if (newFlow.subflows) {
|
||||
// Add in the new subflows
|
||||
newFlow.subflows.forEach(function(sf) {
|
||||
nodes = nodes.concat(sf.nodes||[]).concat(sf.configs||[]);
|
||||
delete sf.nodes;
|
||||
delete sf.configs;
|
||||
nodes.push(sf);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
newConfig = newConfig.filter(function(node) {
|
||||
return node.z !== id && node.id !== id;
|
||||
});
|
||||
var tabNode = {
|
||||
type:'tab',
|
||||
label:newFlow.label,
|
||||
id:id
|
||||
}
|
||||
if (newFlow.hasOwnProperty('info')) {
|
||||
tabNode.info = newFlow.info;
|
||||
}
|
||||
if (newFlow.hasOwnProperty('disabled')) {
|
||||
tabNode.disabled = newFlow.disabled;
|
||||
}
|
||||
|
||||
nodes = [tabNode].concat(newFlow.nodes||[]).concat(newFlow.configs||[]);
|
||||
nodes.forEach(function(n) {
|
||||
n.z = id;
|
||||
});
|
||||
}
|
||||
|
||||
newConfig = newConfig.concat(nodes);
|
||||
return setFlows(newConfig,'flows',true).then(function() {
|
||||
log.info(log._("nodes.flows.updated-flow",{label:(label?label+" ":"")+"["+id+"]"}));
|
||||
})
|
||||
}
|
||||
|
||||
function removeFlow(id) {
|
||||
if (id === 'global') {
|
||||
// TODO: nls + error code
|
||||
throw new Error('not allowed to remove global');
|
||||
}
|
||||
var flow = activeFlowConfig.flows[id];
|
||||
if (!flow) {
|
||||
var e = new Error();
|
||||
e.code = 404;
|
||||
throw e;
|
||||
}
|
||||
|
||||
var newConfig = clone(activeConfig.flows);
|
||||
newConfig = newConfig.filter(function(node) {
|
||||
return node.z !== id && node.id !== id;
|
||||
});
|
||||
|
||||
return setFlows(newConfig,'flows',true).then(function() {
|
||||
log.info(log._("nodes.flows.removed-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
|
||||
/**
|
||||
* Load the current flow configuration from storage
|
||||
* @return a promise for the loading of the config
|
||||
*/
|
||||
load: load,
|
||||
|
||||
get:getNode,
|
||||
eachNode: eachNode,
|
||||
|
||||
/**
|
||||
* Gets the current flow configuration
|
||||
*/
|
||||
getFlows: getFlows,
|
||||
|
||||
/**
|
||||
* Sets the current active config.
|
||||
* @param config the configuration to enable
|
||||
* @param type the type of deployment to do: full (default), nodes, flows, load
|
||||
* @return a promise for the saving/starting of the new flow
|
||||
*/
|
||||
setFlows: setFlows,
|
||||
|
||||
/**
|
||||
* Starts the current flow configuration
|
||||
*/
|
||||
startFlows: start,
|
||||
|
||||
/**
|
||||
* Stops the current flow configuration
|
||||
* @return a promise for the stopping of the flow
|
||||
*/
|
||||
stopFlows: stop,
|
||||
|
||||
get started() { return started },
|
||||
|
||||
handleError: handleError,
|
||||
handleStatus: handleStatus,
|
||||
|
||||
checkTypeInUse: checkTypeInUse,
|
||||
|
||||
addFlow: addFlow,
|
||||
getFlow: getFlow,
|
||||
updateFlow: updateFlow,
|
||||
removeFlow: removeFlow,
|
||||
disableFlow:null,
|
||||
enableFlow:null
|
||||
|
||||
};
|
447
packages/node_modules/@node-red/runtime/nodes/flows/util.js
generated
vendored
Normal file
447
packages/node_modules/@node-red/runtime/nodes/flows/util.js
generated
vendored
Normal file
@@ -0,0 +1,447 @@
|
||||
/**
|
||||
* 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 redUtil = require("../../util");
|
||||
var subflowInstanceRE = /^subflow:(.+)$/;
|
||||
var typeRegistry = require("@node-red/registry");
|
||||
|
||||
function diffNodes(oldNode,newNode) {
|
||||
if (oldNode == null) {
|
||||
return true;
|
||||
}
|
||||
var oldKeys = Object.keys(oldNode).filter(function(p) { return p != "x" && p != "y" && p != "wires" });
|
||||
var newKeys = Object.keys(newNode).filter(function(p) { return p != "x" && p != "y" && p != "wires" });
|
||||
if (oldKeys.length != newKeys.length) {
|
||||
return true;
|
||||
}
|
||||
for (var i=0;i<newKeys.length;i++) {
|
||||
var p = newKeys[i];
|
||||
if (!redUtil.compareObjects(oldNode[p],newNode[p])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var EnvVarPropertyRE_old = /^\$\((\S+)\)$/;
|
||||
var EnvVarPropertyRE = /^\${(\S+)}$/;
|
||||
|
||||
function mapEnvVarProperties(obj,prop) {
|
||||
if (Buffer.isBuffer(obj[prop])) {
|
||||
return;
|
||||
} else if (Array.isArray(obj[prop])) {
|
||||
for (var i=0;i<obj[prop].length;i++) {
|
||||
mapEnvVarProperties(obj[prop],i);
|
||||
}
|
||||
} else if (typeof obj[prop] === 'string') {
|
||||
if (obj[prop][0] === "$" && (EnvVarPropertyRE_old.test(obj[prop]) || EnvVarPropertyRE.test(obj[prop])) ) {
|
||||
var envVar = obj[prop].substring(2,obj[prop].length-1);
|
||||
obj[prop] = process.env.hasOwnProperty(envVar)?process.env[envVar]:obj[prop];
|
||||
}
|
||||
} else {
|
||||
for (var p in obj[prop]) {
|
||||
if (obj[prop].hasOwnProperty(p)) {
|
||||
mapEnvVarProperties(obj[prop],p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
diffNodes: diffNodes,
|
||||
mapEnvVarProperties: mapEnvVarProperties,
|
||||
|
||||
parseConfig: function(config) {
|
||||
var flow = {};
|
||||
flow.allNodes = {};
|
||||
flow.subflows = {};
|
||||
flow.configs = {};
|
||||
flow.flows = {};
|
||||
flow.missingTypes = [];
|
||||
|
||||
config.forEach(function(n) {
|
||||
flow.allNodes[n.id] = clone(n);
|
||||
if (n.type === 'tab') {
|
||||
flow.flows[n.id] = n;
|
||||
flow.flows[n.id].subflows = {};
|
||||
flow.flows[n.id].configs = {};
|
||||
flow.flows[n.id].nodes = {};
|
||||
}
|
||||
});
|
||||
|
||||
config.forEach(function(n) {
|
||||
if (n.type === 'subflow') {
|
||||
flow.subflows[n.id] = n;
|
||||
flow.subflows[n.id].configs = {};
|
||||
flow.subflows[n.id].nodes = {};
|
||||
flow.subflows[n.id].instances = [];
|
||||
}
|
||||
});
|
||||
var linkWires = {};
|
||||
var linkOutNodes = [];
|
||||
config.forEach(function(n) {
|
||||
if (n.type !== 'subflow' && n.type !== 'tab') {
|
||||
var subflowDetails = subflowInstanceRE.exec(n.type);
|
||||
|
||||
if ( (subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type)) ) {
|
||||
if (flow.missingTypes.indexOf(n.type) === -1) {
|
||||
flow.missingTypes.push(n.type);
|
||||
}
|
||||
}
|
||||
var container = null;
|
||||
if (flow.flows[n.z]) {
|
||||
container = flow.flows[n.z];
|
||||
} else if (flow.subflows[n.z]) {
|
||||
container = flow.subflows[n.z];
|
||||
}
|
||||
if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
|
||||
if (subflowDetails) {
|
||||
var subflowType = subflowDetails[1]
|
||||
n.subflow = subflowType;
|
||||
flow.subflows[subflowType].instances.push(n)
|
||||
}
|
||||
if (container) {
|
||||
container.nodes[n.id] = n;
|
||||
}
|
||||
} else {
|
||||
if (container) {
|
||||
container.configs[n.id] = n;
|
||||
} else {
|
||||
flow.configs[n.id] = n;
|
||||
flow.configs[n.id]._users = [];
|
||||
}
|
||||
}
|
||||
if (n.type === 'link in' && n.links) {
|
||||
// Ensure wires are present in corresponding link out nodes
|
||||
n.links.forEach(function(id) {
|
||||
linkWires[id] = linkWires[id]||{};
|
||||
linkWires[id][n.id] = true;
|
||||
})
|
||||
} else if (n.type === 'link out' && n.links) {
|
||||
linkWires[n.id] = linkWires[n.id]||{};
|
||||
n.links.forEach(function(id) {
|
||||
linkWires[n.id][id] = true;
|
||||
})
|
||||
linkOutNodes.push(n);
|
||||
}
|
||||
}
|
||||
});
|
||||
linkOutNodes.forEach(function(n) {
|
||||
var links = linkWires[n.id];
|
||||
var targets = Object.keys(links);
|
||||
n.wires = [targets];
|
||||
});
|
||||
|
||||
|
||||
var addedTabs = {};
|
||||
config.forEach(function(n) {
|
||||
if (n.type !== 'subflow' && n.type !== 'tab') {
|
||||
for (var prop in n) {
|
||||
if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== 'type' && prop !== '_users' && flow.configs.hasOwnProperty(n[prop])) {
|
||||
// This property references a global config node
|
||||
flow.configs[n[prop]]._users.push(n.id)
|
||||
}
|
||||
}
|
||||
if (n.z && !flow.subflows[n.z]) {
|
||||
|
||||
if (!flow.flows[n.z]) {
|
||||
flow.flows[n.z] = {type:'tab',id:n.z};
|
||||
flow.flows[n.z].subflows = {};
|
||||
flow.flows[n.z].configs = {};
|
||||
flow.flows[n.z].nodes = {};
|
||||
addedTabs[n.z] = flow.flows[n.z];
|
||||
}
|
||||
if (addedTabs[n.z]) {
|
||||
if (n.hasOwnProperty('x') && n.hasOwnProperty('y')) {
|
||||
addedTabs[n.z].nodes[n.id] = n;
|
||||
} else {
|
||||
addedTabs[n.z].configs[n.id] = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return flow;
|
||||
},
|
||||
|
||||
diffConfigs: function(oldConfig, newConfig) {
|
||||
var id;
|
||||
var node;
|
||||
var nn;
|
||||
var wires;
|
||||
var j,k;
|
||||
|
||||
if (!oldConfig) {
|
||||
oldConfig = {
|
||||
flows:{},
|
||||
allNodes:{}
|
||||
}
|
||||
}
|
||||
var changedSubflows = {};
|
||||
|
||||
var added = {};
|
||||
var removed = {};
|
||||
var changed = {};
|
||||
var wiringChanged = {};
|
||||
|
||||
var linkMap = {};
|
||||
|
||||
var changedTabs = {};
|
||||
|
||||
// Look for tabs that have been removed
|
||||
for (id in oldConfig.flows) {
|
||||
if (oldConfig.flows.hasOwnProperty(id) && (!newConfig.flows.hasOwnProperty(id))) {
|
||||
removed[id] = oldConfig.allNodes[id];
|
||||
}
|
||||
}
|
||||
|
||||
// Look for tabs that have been disabled
|
||||
for (id in oldConfig.flows) {
|
||||
if (oldConfig.flows.hasOwnProperty(id) && newConfig.flows.hasOwnProperty(id)) {
|
||||
var originalState = oldConfig.flows[id].disabled||false;
|
||||
var newState = newConfig.flows[id].disabled||false;
|
||||
if (originalState !== newState) {
|
||||
changedTabs[id] = true;
|
||||
if (originalState) {
|
||||
added[id] = oldConfig.allNodes[id];
|
||||
} else {
|
||||
removed[id] = oldConfig.allNodes[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (id in oldConfig.allNodes) {
|
||||
if (oldConfig.allNodes.hasOwnProperty(id)) {
|
||||
node = oldConfig.allNodes[id];
|
||||
if (node.type !== 'tab') {
|
||||
// build the map of what this node was previously wired to
|
||||
if (node.wires) {
|
||||
linkMap[node.id] = linkMap[node.id] || [];
|
||||
for (j=0;j<node.wires.length;j++) {
|
||||
wires = node.wires[j];
|
||||
for (k=0;k<wires.length;k++) {
|
||||
linkMap[node.id].push(wires[k]);
|
||||
nn = oldConfig.allNodes[wires[k]];
|
||||
if (nn) {
|
||||
linkMap[nn.id] = linkMap[nn.id] || [];
|
||||
linkMap[nn.id].push(node.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// This node has been removed
|
||||
if (removed[node.z] || !newConfig.allNodes.hasOwnProperty(id)) {
|
||||
removed[id] = node;
|
||||
// Mark the container as changed
|
||||
if (!removed[node.z] && newConfig.allNodes[removed[id].z]) {
|
||||
changed[removed[id].z] = newConfig.allNodes[removed[id].z];
|
||||
if (changed[removed[id].z].type === "subflow") {
|
||||
changedSubflows[removed[id].z] = changed[removed[id].z];
|
||||
//delete removed[id];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (added[node.z]) {
|
||||
added[id] = node;
|
||||
} else {
|
||||
// This node has a material configuration change
|
||||
if (diffNodes(node,newConfig.allNodes[id]) || newConfig.allNodes[id].credentials) {
|
||||
changed[id] = newConfig.allNodes[id];
|
||||
if (changed[id].type === "subflow") {
|
||||
changedSubflows[id] = changed[id];
|
||||
}
|
||||
// Mark the container as changed
|
||||
if (newConfig.allNodes[changed[id].z]) {
|
||||
changed[changed[id].z] = newConfig.allNodes[changed[id].z];
|
||||
if (changed[changed[id].z].type === "subflow") {
|
||||
changedSubflows[changed[id].z] = changed[changed[id].z];
|
||||
delete changed[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
// This node's wiring has changed
|
||||
if (!redUtil.compareObjects(node.wires,newConfig.allNodes[id].wires)) {
|
||||
wiringChanged[id] = newConfig.allNodes[id];
|
||||
// Mark the container as changed
|
||||
if (newConfig.allNodes[wiringChanged[id].z]) {
|
||||
changed[wiringChanged[id].z] = newConfig.allNodes[wiringChanged[id].z];
|
||||
if (changed[wiringChanged[id].z].type === "subflow") {
|
||||
changedSubflows[wiringChanged[id].z] = changed[wiringChanged[id].z];
|
||||
delete wiringChanged[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Look for added nodes
|
||||
for (id in newConfig.allNodes) {
|
||||
if (newConfig.allNodes.hasOwnProperty(id)) {
|
||||
node = newConfig.allNodes[id];
|
||||
// build the map of what this node is now wired to
|
||||
if (node.wires) {
|
||||
linkMap[node.id] = linkMap[node.id] || [];
|
||||
for (j=0;j<node.wires.length;j++) {
|
||||
wires = node.wires[j];
|
||||
for (k=0;k<wires.length;k++) {
|
||||
if (linkMap[node.id].indexOf(wires[k]) === -1) {
|
||||
linkMap[node.id].push(wires[k]);
|
||||
}
|
||||
nn = newConfig.allNodes[wires[k]];
|
||||
if (nn) {
|
||||
linkMap[nn.id] = linkMap[nn.id] || [];
|
||||
if (linkMap[nn.id].indexOf(node.id) === -1) {
|
||||
linkMap[nn.id].push(node.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// This node has been added
|
||||
if (!oldConfig.allNodes.hasOwnProperty(id)) {
|
||||
added[id] = node;
|
||||
// Mark the container as changed
|
||||
if (newConfig.allNodes[added[id].z]) {
|
||||
changed[added[id].z] = newConfig.allNodes[added[id].z];
|
||||
if (changed[added[id].z].type === "subflow") {
|
||||
changedSubflows[added[id].z] = changed[added[id].z];
|
||||
delete added[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var madeChange;
|
||||
// Loop through the nodes looking for references to changed config nodes
|
||||
// Repeat the loop if anything is marked as changed as it may need to be
|
||||
// propagated to parent nodes.
|
||||
// TODO: looping through all nodes every time is a bit inefficient - could be more targeted
|
||||
do {
|
||||
madeChange = false;
|
||||
for (id in newConfig.allNodes) {
|
||||
if (newConfig.allNodes.hasOwnProperty(id)) {
|
||||
node = newConfig.allNodes[id];
|
||||
for (var prop in node) {
|
||||
if (node.hasOwnProperty(prop) && prop != "z" && prop != "id" && prop != "wires") {
|
||||
// This node has a property that references a changed/removed node
|
||||
// Assume it is a config node change and mark this node as
|
||||
// changed.
|
||||
if (changed[node[prop]] || removed[node[prop]]) {
|
||||
if (!changed[node.id]) {
|
||||
madeChange = true;
|
||||
changed[node.id] = node;
|
||||
// This node exists within subflow template
|
||||
// Mark the template as having changed
|
||||
if (newConfig.allNodes[node.z]) {
|
||||
changed[node.z] = newConfig.allNodes[node.z];
|
||||
if (changed[node.z].type === "subflow") {
|
||||
changedSubflows[node.z] = changed[node.z];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (madeChange===true)
|
||||
|
||||
// Find any nodes that exist on a subflow template and remove from changed
|
||||
// list as the parent subflow will now be marked as containing a change
|
||||
for (id in newConfig.allNodes) {
|
||||
if (newConfig.allNodes.hasOwnProperty(id)) {
|
||||
node = newConfig.allNodes[id];
|
||||
if (newConfig.allNodes[node.z] && newConfig.allNodes[node.z].type === "subflow") {
|
||||
delete changed[node.id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively mark all instances of changed subflows as changed
|
||||
var changedSubflowStack = Object.keys(changedSubflows);
|
||||
while (changedSubflowStack.length > 0) {
|
||||
var subflowId = changedSubflowStack.pop();
|
||||
for (id in newConfig.allNodes) {
|
||||
if (newConfig.allNodes.hasOwnProperty(id)) {
|
||||
node = newConfig.allNodes[id];
|
||||
if (node.type === 'subflow:'+subflowId) {
|
||||
if (!changed[node.id]) {
|
||||
changed[node.id] = node;
|
||||
if (!changed[changed[node.id].z] && newConfig.allNodes[changed[node.id].z]) {
|
||||
changed[changed[node.id].z] = newConfig.allNodes[changed[node.id].z];
|
||||
if (newConfig.allNodes[changed[node.id].z].type === "subflow") {
|
||||
// This subflow instance is inside a subflow. Add the
|
||||
// containing subflow to the stack to mark
|
||||
changedSubflowStack.push(changed[node.id].z);
|
||||
delete changed[node.id];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var diff = {
|
||||
added:Object.keys(added),
|
||||
changed:Object.keys(changed),
|
||||
removed:Object.keys(removed),
|
||||
rewired:Object.keys(wiringChanged),
|
||||
linked:[]
|
||||
}
|
||||
|
||||
// Traverse the links of all modified nodes to mark the connected nodes
|
||||
var modifiedNodes = diff.added.concat(diff.changed).concat(diff.removed).concat(diff.rewired);
|
||||
var visited = {};
|
||||
while (modifiedNodes.length > 0) {
|
||||
node = modifiedNodes.pop();
|
||||
if (!visited[node]) {
|
||||
visited[node] = true;
|
||||
if (linkMap[node]) {
|
||||
if (!changed[node] && !added[node] && !removed[node] && !wiringChanged[node]) {
|
||||
diff.linked.push(node);
|
||||
}
|
||||
modifiedNodes = modifiedNodes.concat(linkMap[node]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// console.log(diff);
|
||||
// for (id in newConfig.allNodes) {
|
||||
// console.log(
|
||||
// (added[id]?"+":(changed[id]?"!":" "))+(wiringChanged[id]?"w":" ")+(diff.linked.indexOf(id)!==-1?"~":" "),
|
||||
// id,
|
||||
// newConfig.allNodes[id].type,
|
||||
// newConfig.allNodes[id].name||newConfig.allNodes[id].label||""
|
||||
// );
|
||||
// }
|
||||
// for (id in removed) {
|
||||
// console.log(
|
||||
// "- "+(diff.linked.indexOf(id)!==-1?"~":" "),
|
||||
// id,
|
||||
// oldConfig.allNodes[id].type,
|
||||
// oldConfig.allNodes[id].name||oldConfig.allNodes[id].label||""
|
||||
// );
|
||||
// }
|
||||
|
||||
return diff;
|
||||
}
|
||||
}
|
247
packages/node_modules/@node-red/runtime/nodes/index.js
generated
vendored
Normal file
247
packages/node_modules/@node-red/runtime/nodes/index.js
generated
vendored
Normal file
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* 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 when = require("when");
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var clone = require("clone");
|
||||
var util = require("util");
|
||||
|
||||
var registry = require("@node-red/registry");
|
||||
|
||||
var credentials = require("./credentials");
|
||||
var flows = require("./flows");
|
||||
var flowUtil = require("./flows/util")
|
||||
var context = require("./context");
|
||||
var Node = require("./Node");
|
||||
var log;
|
||||
|
||||
var events = require("../events");
|
||||
|
||||
var settings;
|
||||
|
||||
/**
|
||||
* Registers a node constructor
|
||||
* @param nodeSet - the nodeSet providing the node (module/set)
|
||||
* @param type - the string type name
|
||||
* @param constructor - the constructor function for this node type
|
||||
* @param opts - optional additional options for the node
|
||||
*/
|
||||
function registerType(nodeSet,type,constructor,opts) {
|
||||
if (typeof type !== "string") {
|
||||
// This is someone calling the api directly, rather than via the
|
||||
// RED object provided to a node. Log a warning
|
||||
log.warn("["+nodeSet+"] Deprecated call to RED.runtime.nodes.registerType - node-set name must be provided as first argument");
|
||||
opts = constructor;
|
||||
constructor = type;
|
||||
type = nodeSet;
|
||||
nodeSet = "";
|
||||
}
|
||||
if (opts) {
|
||||
if (opts.credentials) {
|
||||
credentials.register(type,opts.credentials);
|
||||
}
|
||||
if (opts.settings) {
|
||||
try {
|
||||
settings.registerNodeSettings(type,opts.settings);
|
||||
} catch(err) {
|
||||
log.warn("["+type+"] "+err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!(constructor.prototype instanceof Node)) {
|
||||
if(Object.getPrototypeOf(constructor.prototype) === Object.prototype) {
|
||||
util.inherits(constructor,Node);
|
||||
} else {
|
||||
var proto = constructor.prototype;
|
||||
while(Object.getPrototypeOf(proto) !== Object.prototype) {
|
||||
proto = Object.getPrototypeOf(proto);
|
||||
}
|
||||
//TODO: This is a partial implementation of util.inherits >= node v5.0.0
|
||||
// which should be changed when support for node < v5.0.0 is dropped
|
||||
// see: https://github.com/nodejs/node/pull/3455
|
||||
proto.constructor.super_ = Node;
|
||||
if(Object.setPrototypeOf) {
|
||||
Object.setPrototypeOf(proto, Node.prototype);
|
||||
} else {
|
||||
// hack for node v0.10
|
||||
proto.__proto__ = Node.prototype;
|
||||
}
|
||||
}
|
||||
}
|
||||
registry.registerType(nodeSet,type,constructor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from a Node's constructor function, invokes the super-class
|
||||
* constructor and attaches any credentials to the node.
|
||||
* @param node the node object being created
|
||||
* @param def the instance definition for the node
|
||||
*/
|
||||
function createNode(node,def) {
|
||||
Node.call(node,def);
|
||||
var id = node.id;
|
||||
if (def._alias) {
|
||||
id = def._alias;
|
||||
}
|
||||
var creds = credentials.get(id);
|
||||
if (creds) {
|
||||
creds = clone(creds);
|
||||
//console.log("Attaching credentials to ",node.id);
|
||||
// allow $(foo) syntax to substitute env variables for credentials also...
|
||||
for (var p in creds) {
|
||||
if (creds.hasOwnProperty(p)) {
|
||||
flowUtil.mapEnvVarProperties(creds,p);
|
||||
}
|
||||
}
|
||||
node.credentials = creds;
|
||||
} else if (credentials.getDefinition(node.type)) {
|
||||
node.credentials = {};
|
||||
}
|
||||
}
|
||||
|
||||
function init(runtime) {
|
||||
settings = runtime.settings;
|
||||
log = runtime.log;
|
||||
credentials.init(runtime);
|
||||
flows.init(runtime);
|
||||
registry.init(runtime);
|
||||
context.init(runtime.settings);
|
||||
}
|
||||
|
||||
function disableNode(id) {
|
||||
flows.checkTypeInUse(id);
|
||||
return registry.disableNode(id).then(function(info) {
|
||||
reportNodeStateChange(info,false);
|
||||
return info;
|
||||
});
|
||||
}
|
||||
|
||||
function enableNode(id) {
|
||||
return registry.enableNode(id).then(function(info) {
|
||||
reportNodeStateChange(info,true);
|
||||
return info;
|
||||
});
|
||||
}
|
||||
|
||||
function reportNodeStateChange(info,enabled) {
|
||||
if (info.enabled === enabled && !info.err) {
|
||||
events.emit("runtime-event",{id:"node/"+(enabled?"enabled":"disabled"),retain:false,payload:info});
|
||||
log.info(" "+log._("api.nodes."+(enabled?"enabled":"disabled")));
|
||||
for (var i=0;i<info.types.length;i++) {
|
||||
log.info(" - "+info.types[i]);
|
||||
}
|
||||
} else if (enabled && info.err) {
|
||||
log.warn(log._("api.nodes.error-enable"));
|
||||
log.warn(" - "+info.name+" : "+info.err);
|
||||
}
|
||||
}
|
||||
|
||||
function installModule(module,version) {
|
||||
var ex_module = registry.getModuleInfo(module);
|
||||
var isUpgrade = !!ex_module;
|
||||
return registry.installModule(module,version).then(function(info) {
|
||||
if (isUpgrade) {
|
||||
events.emit("runtime-event",{id:"node/upgraded",retain:false,payload:{module:module,version:version}});
|
||||
} else {
|
||||
events.emit("runtime-event",{id:"node/added",retain:false,payload:info.nodes});
|
||||
}
|
||||
return info;
|
||||
});
|
||||
}
|
||||
|
||||
function uninstallModule(module) {
|
||||
var info = registry.getModuleInfo(module);
|
||||
if (!info) {
|
||||
throw new Error(log._("nodes.index.unrecognised-module", {module:module}));
|
||||
} else {
|
||||
for (var i=0;i<info.nodes.length;i++) {
|
||||
flows.checkTypeInUse(module+"/"+info.nodes[i].name);
|
||||
}
|
||||
return registry.uninstallModule(module).then(function(list) {
|
||||
events.emit("runtime-event",{id:"node/removed",retain:false,payload:list});
|
||||
return list;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
// Lifecycle
|
||||
init: init,
|
||||
load: registry.load,
|
||||
|
||||
// Node registry
|
||||
createNode: createNode,
|
||||
getNode: flows.get,
|
||||
eachNode: flows.eachNode,
|
||||
getContext: context.get,
|
||||
|
||||
|
||||
paletteEditorEnabled: registry.paletteEditorEnabled,
|
||||
installModule: installModule,
|
||||
uninstallModule: uninstallModule,
|
||||
|
||||
enableNode: enableNode,
|
||||
disableNode: disableNode,
|
||||
|
||||
// Node type registry
|
||||
registerType: registerType,
|
||||
getType: registry.get,
|
||||
|
||||
getNodeInfo: registry.getNodeInfo,
|
||||
getNodeList: registry.getNodeList,
|
||||
|
||||
getModuleInfo: registry.getModuleInfo,
|
||||
|
||||
getNodeConfigs: registry.getNodeConfigs,
|
||||
getNodeConfig: registry.getNodeConfig,
|
||||
getNodeIconPath: registry.getNodeIconPath,
|
||||
getNodeIcons: registry.getNodeIcons,
|
||||
getNodeExampleFlows: registry.getNodeExampleFlows,
|
||||
getNodeExampleFlowPath: registry.getNodeExampleFlowPath,
|
||||
|
||||
clearRegistry: registry.clear,
|
||||
cleanModuleList: registry.cleanModuleList,
|
||||
|
||||
// Flow handling
|
||||
loadFlows: flows.load,
|
||||
startFlows: flows.startFlows,
|
||||
stopFlows: flows.stopFlows,
|
||||
setFlows: flows.setFlows,
|
||||
getFlows: flows.getFlows,
|
||||
|
||||
addFlow: flows.addFlow,
|
||||
getFlow: flows.getFlow,
|
||||
updateFlow: flows.updateFlow,
|
||||
removeFlow: flows.removeFlow,
|
||||
// disableFlow: flows.disableFlow,
|
||||
// enableFlow: flows.enableFlow,
|
||||
|
||||
// Credentials
|
||||
addCredentials: credentials.add,
|
||||
getCredentials: credentials.get,
|
||||
deleteCredentials: credentials.delete,
|
||||
getCredentialDefinition: credentials.getDefinition,
|
||||
setCredentialSecret: credentials.setKey,
|
||||
clearCredentials: credentials.clear,
|
||||
exportCredentials: credentials.export,
|
||||
getCredentialKeyType: credentials.getKeyType,
|
||||
|
||||
// Contexts
|
||||
loadContextsPlugin: context.load,
|
||||
closeContextsPlugin: context.close,
|
||||
listContextStores: context.listStores
|
||||
};
|
5
packages/node_modules/@node-red/runtime/package.json
generated
vendored
Normal file
5
packages/node_modules/@node-red/runtime/package.json
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "@node-red/runtime",
|
||||
"version": "0.20.0",
|
||||
"license": "Apache-2.0"
|
||||
}
|
191
packages/node_modules/@node-red/runtime/settings.js
generated
vendored
Normal file
191
packages/node_modules/@node-red/runtime/settings.js
generated
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* 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 when = require("when");
|
||||
var clone = require("clone");
|
||||
var assert = require("assert");
|
||||
var log = require("@node-red/util").log; // TODO: separate module
|
||||
var util = require("./util");
|
||||
|
||||
// localSettings are those provided in the runtime settings.js file
|
||||
var localSettings = null;
|
||||
// globalSettings are provided by storage - .config.json on localfilesystem
|
||||
var globalSettings = null;
|
||||
// nodeSettings are those settings that a node module defines as being available
|
||||
var nodeSettings = null;
|
||||
|
||||
// A subset of globalSettings that deal with per-user settings
|
||||
var userSettings = null;
|
||||
|
||||
var disableNodeSettings = null;
|
||||
var storage = null;
|
||||
|
||||
var persistentSettings = {
|
||||
init: function(settings) {
|
||||
localSettings = settings;
|
||||
for (var i in settings) {
|
||||
/* istanbul ignore else */
|
||||
if (settings.hasOwnProperty(i) && i !== 'load' && i !== 'get' && i !== 'set' && i !== 'available' && i !== 'reset') {
|
||||
// Don't allow any of the core functions get replaced via settings
|
||||
(function() {
|
||||
var j = i;
|
||||
persistentSettings.__defineGetter__(j,function() { return localSettings[j]; });
|
||||
persistentSettings.__defineSetter__(j,function() { throw new Error("Property '"+j+"' is read-only"); });
|
||||
})();
|
||||
}
|
||||
}
|
||||
globalSettings = null;
|
||||
nodeSettings = {};
|
||||
disableNodeSettings = {};
|
||||
},
|
||||
load: function(_storage) {
|
||||
storage = _storage;
|
||||
return storage.getSettings().then(function(_settings) {
|
||||
globalSettings = _settings;
|
||||
if (globalSettings) {
|
||||
userSettings = globalSettings.users || {};
|
||||
}
|
||||
else {
|
||||
userSettings = {};
|
||||
}
|
||||
});
|
||||
},
|
||||
get: function(prop) {
|
||||
if (prop === 'users') {
|
||||
throw new Error("Do not access user settings directly. Use settings.getUserSettings");
|
||||
}
|
||||
if (localSettings.hasOwnProperty(prop)) {
|
||||
return clone(localSettings[prop]);
|
||||
}
|
||||
if (globalSettings === null) {
|
||||
throw new Error(log._("settings.not-available"));
|
||||
}
|
||||
return clone(globalSettings[prop]);
|
||||
},
|
||||
|
||||
set: function(prop,value) {
|
||||
if (prop === 'users') {
|
||||
throw new Error("Do not access user settings directly. Use settings.setUserSettings");
|
||||
}
|
||||
if (localSettings.hasOwnProperty(prop)) {
|
||||
throw new Error(log._("settings.property-read-only", {prop:prop}));
|
||||
}
|
||||
if (globalSettings === null) {
|
||||
throw new Error(log._("settings.not-available"));
|
||||
}
|
||||
var current = globalSettings[prop];
|
||||
globalSettings[prop] = value;
|
||||
try {
|
||||
assert.deepEqual(current,value);
|
||||
return when.resolve();
|
||||
} catch(err) {
|
||||
return storage.saveSettings(globalSettings);
|
||||
}
|
||||
},
|
||||
delete: function(prop) {
|
||||
if (localSettings.hasOwnProperty(prop)) {
|
||||
throw new Error(log._("settings.property-read-only", {prop:prop}));
|
||||
}
|
||||
if (globalSettings === null) {
|
||||
throw new Error(log._("settings.not-available"));
|
||||
}
|
||||
if (globalSettings.hasOwnProperty(prop)) {
|
||||
delete globalSettings[prop];
|
||||
return storage.saveSettings(globalSettings);
|
||||
}
|
||||
return when.resolve();
|
||||
},
|
||||
|
||||
available: function() {
|
||||
return (globalSettings !== null);
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
for (var i in localSettings) {
|
||||
/* istanbul ignore else */
|
||||
if (localSettings.hasOwnProperty(i)) {
|
||||
delete persistentSettings[i];
|
||||
}
|
||||
}
|
||||
localSettings = null;
|
||||
globalSettings = null;
|
||||
userSettings = null;
|
||||
storage = null;
|
||||
},
|
||||
registerNodeSettings: function(type, opts) {
|
||||
var normalisedType = util.normaliseNodeTypeName(type);
|
||||
for (var property in opts) {
|
||||
if (opts.hasOwnProperty(property)) {
|
||||
if (!property.startsWith(normalisedType)) {
|
||||
throw new Error("Registered invalid property name '"+property+"'. Properties for this node must start with '"+normalisedType+"'");
|
||||
}
|
||||
}
|
||||
}
|
||||
nodeSettings[type] = opts;
|
||||
},
|
||||
exportNodeSettings: function(safeSettings) {
|
||||
for (var type in nodeSettings) {
|
||||
if (nodeSettings.hasOwnProperty(type) && !disableNodeSettings[type]) {
|
||||
var nodeTypeSettings = nodeSettings[type];
|
||||
for (var property in nodeTypeSettings) {
|
||||
if (nodeTypeSettings.hasOwnProperty(property)) {
|
||||
var setting = nodeTypeSettings[property];
|
||||
if (setting.exportable) {
|
||||
if (safeSettings.hasOwnProperty(property)) {
|
||||
// Cannot overwrite existing setting
|
||||
} else if (localSettings.hasOwnProperty(property)) {
|
||||
safeSettings[property] = localSettings[property];
|
||||
} else if (setting.hasOwnProperty('value')) {
|
||||
safeSettings[property] = setting.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return safeSettings;
|
||||
},
|
||||
enableNodeSettings: function(types) {
|
||||
types.forEach(function(type) {
|
||||
disableNodeSettings[type] = false;
|
||||
});
|
||||
},
|
||||
disableNodeSettings: function(types) {
|
||||
types.forEach(function(type) {
|
||||
disableNodeSettings[type] = true;
|
||||
});
|
||||
},
|
||||
getUserSettings: function(username) {
|
||||
return clone(userSettings[username]);
|
||||
},
|
||||
setUserSettings: function(username,settings) {
|
||||
if (globalSettings === null) {
|
||||
throw new Error(log._("settings.not-available"));
|
||||
}
|
||||
var current = userSettings[username];
|
||||
userSettings[username] = settings;
|
||||
try {
|
||||
assert.deepEqual(current,settings);
|
||||
return when.resolve();
|
||||
} catch(err) {
|
||||
globalSettings.users = userSettings;
|
||||
return storage.saveSettings(globalSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = persistentSettings;
|
240
packages/node_modules/@node-red/runtime/storage/index.js
generated
vendored
Normal file
240
packages/node_modules/@node-red/runtime/storage/index.js
generated
vendored
Normal file
@@ -0,0 +1,240 @@
|
||||
/**
|
||||
* 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 when = require('when');
|
||||
var Path = require('path');
|
||||
var crypto = require('crypto');
|
||||
|
||||
var log = require("@node-red/util").log; // TODO: separate module
|
||||
|
||||
var runtime;
|
||||
var storageModule;
|
||||
var settingsAvailable;
|
||||
var sessionsAvailable;
|
||||
|
||||
var libraryFlowsCachedResult = null;
|
||||
|
||||
function moduleSelector(aSettings) {
|
||||
var toReturn;
|
||||
if (aSettings.storageModule) {
|
||||
if (typeof aSettings.storageModule === "string") {
|
||||
// TODO: allow storage modules to be specified by absolute path
|
||||
toReturn = require("./"+aSettings.storageModule);
|
||||
} else {
|
||||
toReturn = aSettings.storageModule;
|
||||
}
|
||||
} else {
|
||||
toReturn = require("./localfilesystem");
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
function is_malicious(path) {
|
||||
return path.indexOf('../') != -1 || path.indexOf('..\\') != -1;
|
||||
}
|
||||
|
||||
var storageModuleInterface = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
try {
|
||||
storageModule = moduleSelector(runtime.settings);
|
||||
settingsAvailable = storageModule.hasOwnProperty("getSettings") && storageModule.hasOwnProperty("saveSettings");
|
||||
sessionsAvailable = storageModule.hasOwnProperty("getSessions") && storageModule.hasOwnProperty("saveSessions");
|
||||
} catch (e) {
|
||||
return when.reject(e);
|
||||
}
|
||||
if (!!storageModule.projects) {
|
||||
var projectsEnabled = false;
|
||||
if (runtime.settings.hasOwnProperty("editorTheme") && runtime.settings.editorTheme.hasOwnProperty("projects")) {
|
||||
projectsEnabled = runtime.settings.editorTheme.projects.enabled === true;
|
||||
}
|
||||
if (projectsEnabled) {
|
||||
storageModuleInterface.projects = storageModule.projects;
|
||||
}
|
||||
}
|
||||
if (storageModule.sshkeys) {
|
||||
storageModuleInterface.sshkeys = storageModule.sshkeys;
|
||||
}
|
||||
return storageModule.init(runtime.settings,runtime);
|
||||
},
|
||||
getFlows: function() {
|
||||
return storageModule.getFlows().then(function(flows) {
|
||||
return storageModule.getCredentials().then(function(creds) {
|
||||
var result = {
|
||||
flows: flows,
|
||||
credentials: creds
|
||||
};
|
||||
result.rev = crypto.createHash('md5').update(JSON.stringify(result.flows)).digest("hex");
|
||||
return result;
|
||||
})
|
||||
});
|
||||
},
|
||||
saveFlows: function(config) {
|
||||
var flows = config.flows;
|
||||
var credentials = config.credentials;
|
||||
var credentialSavePromise;
|
||||
if (config.credentialsDirty) {
|
||||
credentialSavePromise = storageModule.saveCredentials(credentials);
|
||||
} else {
|
||||
credentialSavePromise = when.resolve();
|
||||
}
|
||||
delete config.credentialsDirty;
|
||||
|
||||
return credentialSavePromise.then(function() {
|
||||
return storageModule.saveFlows(flows).then(function() {
|
||||
return crypto.createHash('md5').update(JSON.stringify(config.flows)).digest("hex");
|
||||
})
|
||||
});
|
||||
},
|
||||
// getCredentials: function() {
|
||||
// return storageModule.getCredentials();
|
||||
// },
|
||||
saveCredentials: function(credentials) {
|
||||
return storageModule.saveCredentials(credentials);
|
||||
},
|
||||
getSettings: function() {
|
||||
if (settingsAvailable) {
|
||||
return storageModule.getSettings();
|
||||
} else {
|
||||
return when.resolve(null);
|
||||
}
|
||||
},
|
||||
saveSettings: function(settings) {
|
||||
if (settingsAvailable) {
|
||||
return storageModule.saveSettings(settings);
|
||||
} else {
|
||||
return when.resolve();
|
||||
}
|
||||
},
|
||||
getSessions: function() {
|
||||
if (sessionsAvailable) {
|
||||
return storageModule.getSessions();
|
||||
} else {
|
||||
return when.resolve(null);
|
||||
}
|
||||
},
|
||||
saveSessions: function(sessions) {
|
||||
if (sessionsAvailable) {
|
||||
return storageModule.saveSessions(sessions);
|
||||
} else {
|
||||
return when.resolve();
|
||||
}
|
||||
},
|
||||
|
||||
/* Library Functions */
|
||||
|
||||
getLibraryEntry: function(type, path) {
|
||||
if (is_malicious(path)) {
|
||||
var err = new Error();
|
||||
err.code = "forbidden";
|
||||
return when.reject(err);
|
||||
}
|
||||
return storageModule.getLibraryEntry(type, path);
|
||||
},
|
||||
saveLibraryEntry: function(type, path, meta, body) {
|
||||
if (is_malicious(path)) {
|
||||
var err = new Error();
|
||||
err.code = "forbidden";
|
||||
return when.reject(err);
|
||||
}
|
||||
return storageModule.saveLibraryEntry(type, path, meta, body);
|
||||
},
|
||||
|
||||
/* Deprecated functions */
|
||||
getAllFlows: function() {
|
||||
if (storageModule.hasOwnProperty("getAllFlows")) {
|
||||
return storageModule.getAllFlows();
|
||||
} else {
|
||||
if (libraryFlowsCachedResult) {
|
||||
return Promise.resolve(libraryFlowsCachedResult);
|
||||
} else {
|
||||
return listFlows("/").then(function(result) {
|
||||
libraryFlowsCachedResult = result;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
getFlow: function(fn) {
|
||||
if (is_malicious(fn)) {
|
||||
var err = new Error();
|
||||
err.code = "forbidden";
|
||||
return when.reject(err);
|
||||
}
|
||||
if (storageModule.hasOwnProperty("getFlow")) {
|
||||
return storageModule.getFlow(fn);
|
||||
} else {
|
||||
return storageModule.getLibraryEntry("flows",fn);
|
||||
}
|
||||
|
||||
},
|
||||
saveFlow: function(fn, data) {
|
||||
if (is_malicious(fn)) {
|
||||
var err = new Error();
|
||||
err.code = "forbidden";
|
||||
return when.reject(err);
|
||||
}
|
||||
libraryFlowsCachedResult = null;
|
||||
if (storageModule.hasOwnProperty("saveFlow")) {
|
||||
return storageModule.saveFlow(fn, data);
|
||||
} else {
|
||||
return storageModule.saveLibraryEntry("flows",fn,{},data);
|
||||
}
|
||||
}
|
||||
/* End deprecated functions */
|
||||
|
||||
}
|
||||
|
||||
|
||||
function listFlows(path) {
|
||||
return storageModule.getLibraryEntry("flows",path).then(function(res) {
|
||||
return when.promise(function(resolve) {
|
||||
var promises = [];
|
||||
res.forEach(function(r) {
|
||||
if (typeof r === "string") {
|
||||
promises.push(listFlows(Path.join(path,r)));
|
||||
} else {
|
||||
promises.push(when.resolve(r));
|
||||
}
|
||||
});
|
||||
var i=0;
|
||||
when.settle(promises).then(function(res2) {
|
||||
var result = {};
|
||||
res2.forEach(function(r) {
|
||||
// TODO: name||fn
|
||||
if (r.value.fn) {
|
||||
var name = r.value.name;
|
||||
if (!name) {
|
||||
name = r.value.fn.replace(/\.json$/, "");
|
||||
}
|
||||
result.f = result.f || [];
|
||||
result.f.push(name);
|
||||
} else {
|
||||
result.d = result.d || {};
|
||||
result.d[res[i]] = r.value;
|
||||
//console.log(">",r.value);
|
||||
}
|
||||
i++;
|
||||
});
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = storageModuleInterface;
|
102
packages/node_modules/@node-red/runtime/storage/localfilesystem/index.js
generated
vendored
Normal file
102
packages/node_modules/@node-red/runtime/storage/localfilesystem/index.js
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 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 fs = require('fs-extra');
|
||||
var when = require('when');
|
||||
var fspath = require("path");
|
||||
|
||||
var log = require("@node-red/util").log; // TODO: separate module
|
||||
|
||||
var util = require("./util");
|
||||
var library = require("./library");
|
||||
var sessions = require("./sessions");
|
||||
var runtimeSettings = require("./settings");
|
||||
var projects = require("./projects");
|
||||
|
||||
var initialFlowLoadComplete = false;
|
||||
var settings;
|
||||
|
||||
var localfilesystem = {
|
||||
init: function(_settings, runtime) {
|
||||
settings = _settings;
|
||||
|
||||
var promises = [];
|
||||
|
||||
if (!settings.userDir) {
|
||||
try {
|
||||
fs.statSync(fspath.join(process.env.NODE_RED_HOME,".config.json"));
|
||||
settings.userDir = process.env.NODE_RED_HOME;
|
||||
} catch(err) {
|
||||
try {
|
||||
// Consider compatibility for older versions
|
||||
if (process.env.HOMEPATH) {
|
||||
fs.statSync(fspath.join(process.env.HOMEPATH,".node-red",".config.json"));
|
||||
settings.userDir = fspath.join(process.env.HOMEPATH,".node-red");
|
||||
}
|
||||
} catch(err) {
|
||||
}
|
||||
if (!settings.userDir) {
|
||||
settings.userDir = fspath.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red");
|
||||
if (!settings.readOnly) {
|
||||
promises.push(fs.ensureDir(fspath.join(settings.userDir,"node_modules")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sessions.init(settings);
|
||||
runtimeSettings.init(settings);
|
||||
promises.push(library.init(settings));
|
||||
promises.push(projects.init(settings, runtime));
|
||||
|
||||
var packageFile = fspath.join(settings.userDir,"package.json");
|
||||
var packagePromise = when.resolve();
|
||||
|
||||
if (!settings.readOnly) {
|
||||
packagePromise = function() {
|
||||
try {
|
||||
fs.statSync(packageFile);
|
||||
} catch(err) {
|
||||
var defaultPackage = {
|
||||
"name": "node-red-project",
|
||||
"description": "A Node-RED Project",
|
||||
"version": "0.0.1",
|
||||
"private": true
|
||||
};
|
||||
return util.writeFile(packageFile,JSON.stringify(defaultPackage,"",4));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return when.all(promises).then(packagePromise);
|
||||
},
|
||||
|
||||
|
||||
getFlows: projects.getFlows,
|
||||
saveFlows: projects.saveFlows,
|
||||
getCredentials: projects.getCredentials,
|
||||
saveCredentials: projects.saveCredentials,
|
||||
|
||||
getSettings: runtimeSettings.getSettings,
|
||||
saveSettings: runtimeSettings.saveSettings,
|
||||
getSessions: sessions.getSessions,
|
||||
saveSessions: sessions.saveSessions,
|
||||
getLibraryEntry: library.getLibraryEntry,
|
||||
saveLibraryEntry: library.saveLibraryEntry,
|
||||
projects: projects
|
||||
};
|
||||
|
||||
module.exports = localfilesystem;
|
183
packages/node_modules/@node-red/runtime/storage/localfilesystem/library.js
generated
vendored
Normal file
183
packages/node_modules/@node-red/runtime/storage/localfilesystem/library.js
generated
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* 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 fs = require('fs-extra');
|
||||
var when = require('when');
|
||||
var fspath = require("path");
|
||||
var nodeFn = require('when/node/function');
|
||||
|
||||
var util = require("./util");
|
||||
|
||||
var settings;
|
||||
var libDir;
|
||||
var libFlowsDir;
|
||||
|
||||
|
||||
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 buffer = Buffer(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
var scanning = true;
|
||||
var read = 0;
|
||||
var length = 50;
|
||||
var remaining = "";
|
||||
var buffer = Buffer(length);
|
||||
while(read < size) {
|
||||
var thisRead = fs.readSync(fd,buffer,0,length);
|
||||
read += thisRead;
|
||||
if (scanning) {
|
||||
var data = remaining+buffer.slice(0,thisRead).toString();
|
||||
var parts = data.split("\n");
|
||||
remaining = parts.splice(-1)[0];
|
||||
for (var i=0;i<parts.length;i+=1) {
|
||||
if (! /^\/\/ \w+: /.test(parts[i])) {
|
||||
scanning = false;
|
||||
body += parts[i]+"\n";
|
||||
}
|
||||
}
|
||||
if (! /^\/\/ \w+: /.test(remaining)) {
|
||||
scanning = false;
|
||||
}
|
||||
if (!scanning) {
|
||||
body += remaining;
|
||||
}
|
||||
} else {
|
||||
body += buffer.slice(0,thisRead).toString();
|
||||
}
|
||||
}
|
||||
fs.closeSync(fd);
|
||||
return body;
|
||||
}
|
||||
function getLibraryEntry(type,path) {
|
||||
var root = fspath.join(libDir,type);
|
||||
var rootPath = fspath.join(libDir,type,path);
|
||||
|
||||
// don't create the folder if it does not exist - we are only reading....
|
||||
return nodeFn.call(fs.lstat, rootPath).then(function(stats) {
|
||||
if (stats.isFile()) {
|
||||
return getFileBody(root,path);
|
||||
}
|
||||
if (path.substr(-1) == '/') {
|
||||
path = path.substr(0,path.length-1);
|
||||
}
|
||||
return nodeFn.call(fs.readdir, rootPath).then(function(fns) {
|
||||
var dirs = [];
|
||||
var files = [];
|
||||
fns.sort().filter(function(fn) {
|
||||
var fullPath = fspath.join(path,fn);
|
||||
var absoluteFullPath = fspath.join(root,fullPath);
|
||||
if (fn[0] != ".") {
|
||||
var stats = fs.lstatSync(absoluteFullPath);
|
||||
if (stats.isDirectory()) {
|
||||
dirs.push(fn);
|
||||
} else {
|
||||
var meta = getFileMeta(root,fullPath);
|
||||
meta.fn = fn;
|
||||
files.push(meta);
|
||||
}
|
||||
}
|
||||
});
|
||||
return dirs.concat(files);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
// if path is empty, then assume it was a folder, return empty
|
||||
if (path === ""){
|
||||
return [];
|
||||
}
|
||||
|
||||
// if path ends with slash, it was a folder
|
||||
// so return empty
|
||||
if (path.substr(-1) == '/') {
|
||||
return [];
|
||||
}
|
||||
|
||||
// else path was specified, but did not exist,
|
||||
// check for path.json as an alternative if flows
|
||||
if (type === "flows" && !/\.json$/.test(path)) {
|
||||
return getLibraryEntry(type,path+".json")
|
||||
.catch(function(e) {
|
||||
throw err;
|
||||
});
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(_settings) {
|
||||
settings = _settings;
|
||||
libDir = fspath.join(settings.userDir,"lib");
|
||||
libFlowsDir = fspath.join(libDir,"flows");
|
||||
if (!settings.readOnly) {
|
||||
return fs.ensureDir(libFlowsDir);
|
||||
} else {
|
||||
return when.resolve();
|
||||
}
|
||||
},
|
||||
getLibraryEntry: getLibraryEntry,
|
||||
|
||||
saveLibraryEntry: function(type,path,meta,body) {
|
||||
if (settings.readOnly) {
|
||||
return when.resolve();
|
||||
}
|
||||
if (type === "flows" && !path.endsWith(".json")) {
|
||||
path += ".json";
|
||||
}
|
||||
var fn = fspath.join(libDir, type, path);
|
||||
var headers = "";
|
||||
for (var i in meta) {
|
||||
if (meta.hasOwnProperty(i)) {
|
||||
headers += "// "+i+": "+meta[i]+"\n";
|
||||
}
|
||||
}
|
||||
if (type === "flows" && settings.flowFilePretty) {
|
||||
body = JSON.stringify(JSON.parse(body),null,4);
|
||||
}
|
||||
return fs.ensureDir(fspath.dirname(fn)).then(function () {
|
||||
util.writeFile(fn,headers+body);
|
||||
});
|
||||
}
|
||||
}
|
997
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/Project.js
generated
vendored
Normal file
997
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/Project.js
generated
vendored
Normal file
@@ -0,0 +1,997 @@
|
||||
/**
|
||||
* 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 fs = require('fs-extra');
|
||||
var when = require('when');
|
||||
var fspath = require("path");
|
||||
var os = require('os');
|
||||
|
||||
var gitTools = require("./git");
|
||||
var util = require("../util");
|
||||
var defaultFileSet = require("./defaultFileSet");
|
||||
var sshKeys = require("./ssh");
|
||||
var settings;
|
||||
var runtime;
|
||||
var log;
|
||||
|
||||
var projectsDir;
|
||||
|
||||
var authCache = require("./git/authCache");
|
||||
|
||||
// TODO: DRY - red/api/editor/sshkeys !
|
||||
function getSSHKeyUsername(userObj) {
|
||||
var username = '__default';
|
||||
if ( userObj && userObj.name ) {
|
||||
username = userObj.name;
|
||||
}
|
||||
return username;
|
||||
}
|
||||
function getGitUser(user) {
|
||||
var username;
|
||||
if (!user) {
|
||||
username = "_";
|
||||
} else {
|
||||
username = user.username;
|
||||
}
|
||||
var userSettings = settings.getUserSettings(username);
|
||||
if (userSettings && userSettings.git) {
|
||||
return userSettings.git.user;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function Project(path) {
|
||||
this.path = path;
|
||||
this.name = fspath.basename(path);
|
||||
this.paths = {};
|
||||
this.files = {};
|
||||
this.auth = {origin:{}};
|
||||
this.missingFiles = [];
|
||||
this.credentialSecret = null;
|
||||
}
|
||||
Project.prototype.load = function () {
|
||||
var project = this;
|
||||
var globalProjectSettings = settings.get("projects");
|
||||
// console.log(globalProjectSettings)
|
||||
var projectSettings = {};
|
||||
if (globalProjectSettings) {
|
||||
if (globalProjectSettings.projects.hasOwnProperty(this.name)) {
|
||||
projectSettings = globalProjectSettings.projects[this.name] || {};
|
||||
}
|
||||
}
|
||||
|
||||
this.credentialSecret = projectSettings.credentialSecret;
|
||||
this.git = projectSettings.git || { user:{} };
|
||||
|
||||
// this.paths.flowFile = fspath.join(this.path,"flow.json");
|
||||
// this.paths.credentialsFile = fspath.join(this.path,"flow_cred.json");
|
||||
|
||||
var promises = [];
|
||||
return checkProjectFiles(project).then(function(missingFiles) {
|
||||
project.missingFiles = missingFiles;
|
||||
if (missingFiles.indexOf('package.json') === -1) {
|
||||
project.paths['package.json'] = fspath.join(project.path,"package.json");
|
||||
promises.push(fs.readFile(project.paths['package.json'],"utf8").then(function(content) {
|
||||
try {
|
||||
project.package = util.parseJSON(content);
|
||||
if (project.package.hasOwnProperty('node-red')) {
|
||||
if (project.package['node-red'].hasOwnProperty('settings')) {
|
||||
project.paths.flowFile = project.package['node-red'].settings.flowFile;
|
||||
project.paths.credentialsFile = project.package['node-red'].settings.credentialsFile;
|
||||
}
|
||||
} else {
|
||||
// TODO: package.json doesn't have a node-red section
|
||||
// is that a bad thing?
|
||||
}
|
||||
} catch(err) {
|
||||
// package.json isn't valid JSON... is a merge underway?
|
||||
project.package = {};
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
project.package = {};
|
||||
}
|
||||
if (missingFiles.indexOf('README.md') === -1) {
|
||||
project.paths['README.md'] = fspath.join(project.path,"README.md");
|
||||
promises.push(fs.readFile(project.paths['README.md'],"utf8").then(function(content) {
|
||||
project.description = content;
|
||||
}));
|
||||
} else {
|
||||
project.description = "";
|
||||
}
|
||||
// if (missingFiles.indexOf('flow.json') !== -1) {
|
||||
// console.log("MISSING FLOW FILE");
|
||||
// } else {
|
||||
// project.paths.flowFile = fspath.join(project.path,"flow.json");
|
||||
// }
|
||||
// if (missingFiles.indexOf('flow_cred.json') !== -1) {
|
||||
// console.log("MISSING CREDS FILE");
|
||||
// } else {
|
||||
// project.paths.credentialsFile = fspath.join(project.path,"flow_cred.json");
|
||||
// }
|
||||
|
||||
promises.push(project.loadRemotes());
|
||||
|
||||
return when.settle(promises).then(function(results) {
|
||||
return project;
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
Project.prototype.initialise = function(user,data) {
|
||||
var project = this;
|
||||
// if (!this.empty) {
|
||||
// throw new Error("Cannot initialise non-empty project");
|
||||
// }
|
||||
var files = Object.keys(defaultFileSet);
|
||||
var promises = [];
|
||||
|
||||
if (data.hasOwnProperty('credentialSecret')) {
|
||||
var projects = settings.get('projects');
|
||||
projects.projects[project.name] = projects.projects[project.name] || {};
|
||||
projects.projects[project.name].credentialSecret = data.credentialSecret;
|
||||
promises.push(settings.set('projects',projects));
|
||||
}
|
||||
|
||||
if (data.hasOwnProperty('files')) {
|
||||
if (data.files.hasOwnProperty('flow') && data.files.hasOwnProperty('credentials')) {
|
||||
project.files.flow = data.files.flow;
|
||||
project.files.credentials = data.files.credentials;
|
||||
var flowFilePath = fspath.join(project.path,project.files.flow);
|
||||
var credsFilePath = getCredentialsFilename(flowFilePath);
|
||||
promises.push(util.writeFile(flowFilePath,"[]"));
|
||||
promises.push(util.writeFile(credsFilePath,"{}"));
|
||||
files.push(project.files.flow);
|
||||
files.push(project.files.credentials);
|
||||
}
|
||||
}
|
||||
for (var file in defaultFileSet) {
|
||||
if (defaultFileSet.hasOwnProperty(file)) {
|
||||
var path = fspath.join(project.path,file);
|
||||
if (!fs.existsSync(path)) {
|
||||
promises.push(util.writeFile(path,defaultFileSet[file](project, runtime)));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return when.all(promises).then(function() {
|
||||
return gitTools.stageFile(project.path,files);
|
||||
}).then(function() {
|
||||
return gitTools.commit(project.path,"Create project files",getGitUser(user));
|
||||
}).then(function() {
|
||||
return project.load()
|
||||
})
|
||||
}
|
||||
|
||||
Project.prototype.loadRemotes = function() {
|
||||
var project = this;
|
||||
return gitTools.getRemotes(project.path).then(function(remotes) {
|
||||
project.remotes = remotes;
|
||||
}).then(function() {
|
||||
project.branches = {};
|
||||
return project.status();
|
||||
}).then(function() {
|
||||
if (project.remotes) {
|
||||
var allRemotes = Object.keys(project.remotes);
|
||||
var match = "";
|
||||
if (project.branches.remote) {
|
||||
allRemotes.forEach(function(remote) {
|
||||
if (project.branches.remote.indexOf(remote) === 0 && match.length < remote.length) {
|
||||
match = remote;
|
||||
}
|
||||
});
|
||||
project.currentRemote = project.parseRemoteBranch(project.branches.remote).remote;
|
||||
}
|
||||
} else {
|
||||
delete project.currentRemote;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Project.prototype.parseRemoteBranch = function (remoteBranch) {
|
||||
if (!remoteBranch) {
|
||||
return {}
|
||||
}
|
||||
var project = this;
|
||||
var allRemotes = Object.keys(project.remotes);
|
||||
var match = "";
|
||||
allRemotes.forEach(function(remote) {
|
||||
if (remoteBranch.indexOf(remote) === 0 && match.length < remote.length) {
|
||||
match = remote;
|
||||
}
|
||||
});
|
||||
return {
|
||||
remote: match,
|
||||
branch: remoteBranch.substring(match.length+1)
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Project.prototype.isEmpty = function () {
|
||||
return this.empty;
|
||||
};
|
||||
Project.prototype.isMerging = function() {
|
||||
return this.merging;
|
||||
}
|
||||
|
||||
Project.prototype.update = function (user, data) {
|
||||
var username;
|
||||
if (!user) {
|
||||
username = "_";
|
||||
} else {
|
||||
username = user.username;
|
||||
}
|
||||
|
||||
var promises = [];
|
||||
var project = this;
|
||||
var saveSettings = false;
|
||||
var saveREADME = false;
|
||||
var savePackage = false;
|
||||
var flowFilesChanged = false;
|
||||
var credentialSecretChanged = false;
|
||||
|
||||
var globalProjectSettings = settings.get("projects");
|
||||
if (!globalProjectSettings.projects.hasOwnProperty(this.name)) {
|
||||
globalProjectSettings.projects[this.name] = {};
|
||||
saveSettings = true;
|
||||
}
|
||||
|
||||
|
||||
if (data.credentialSecret && data.credentialSecret !== this.credentialSecret) {
|
||||
var existingSecret = data.currentCredentialSecret;
|
||||
var isReset = data.resetCredentialSecret;
|
||||
var secret = data.credentialSecret;
|
||||
|
||||
// console.log("updating credentialSecret");
|
||||
// console.log("request:");
|
||||
// console.log(JSON.stringify(data,"",4));
|
||||
// console.log(" this.credentialSecret",this.credentialSecret);
|
||||
// console.log(" this.info", this.info);
|
||||
|
||||
if (!isReset && // not a reset
|
||||
this.credentialSecret && // key already set
|
||||
!this.credentialSecretInvalid && // key not invalid
|
||||
this.credentialSecret !== existingSecret) { // key doesn't match provided existing key
|
||||
var e = new Error("Cannot change credentialSecret without current key");
|
||||
e.code = "missing_current_credential_key";
|
||||
return when.reject(e);
|
||||
}
|
||||
this.credentialSecret = secret;
|
||||
|
||||
globalProjectSettings.projects[this.name].credentialSecret = project.credentialSecret;
|
||||
delete this.credentialSecretInvalid;
|
||||
saveSettings = true;
|
||||
credentialSecretChanged = true;
|
||||
}
|
||||
|
||||
if (data.hasOwnProperty('description')) {
|
||||
saveREADME = true;
|
||||
this.description = data.description;
|
||||
}
|
||||
if (data.hasOwnProperty('dependencies')) {
|
||||
savePackage = true;
|
||||
this.package.dependencies = data.dependencies;
|
||||
}
|
||||
if (data.hasOwnProperty('summary')) {
|
||||
savePackage = true;
|
||||
this.package.description = data.summary;
|
||||
}
|
||||
|
||||
if (data.hasOwnProperty('git')) {
|
||||
if (data.git.hasOwnProperty('user')) {
|
||||
globalProjectSettings.projects[this.name].git = globalProjectSettings.projects[this.name].git || {};
|
||||
globalProjectSettings.projects[this.name].git.user = globalProjectSettings.projects[this.name].git.user || {};
|
||||
globalProjectSettings.projects[this.name].git.user[username] = {
|
||||
name: data.git.user.name,
|
||||
email: data.git.user.email
|
||||
}
|
||||
this.git.user[username] = {
|
||||
name: data.git.user.name,
|
||||
email: data.git.user.email
|
||||
}
|
||||
saveSettings = true;
|
||||
}
|
||||
if (data.git.hasOwnProperty('remotes')) {
|
||||
var remoteNames = Object.keys(data.git.remotes);
|
||||
var remotesChanged = false;
|
||||
var modifyRemotesPromise = Promise.resolve();
|
||||
remoteNames.forEach(function(name) {
|
||||
if (data.git.remotes[name].removed) {
|
||||
remotesChanged = true;
|
||||
modifyRemotesPromise = modifyRemotesPromise.then(function() { gitTools.removeRemote(project.path,name) });
|
||||
} else {
|
||||
if (data.git.remotes[name].url) {
|
||||
remotesChanged = true;
|
||||
modifyRemotesPromise = modifyRemotesPromise.then(function() { gitTools.addRemote(project.path,name,data.git.remotes[name])});
|
||||
}
|
||||
if (data.git.remotes[name].username && data.git.remotes[name].password) {
|
||||
var url = data.git.remotes[name].url || project.remotes[name].fetch;
|
||||
authCache.set(project.name,url,username,data.git.remotes[name]);
|
||||
}
|
||||
}
|
||||
})
|
||||
if (remotesChanged) {
|
||||
modifyRemotesPromise = modifyRemotesPromise.then(function() {
|
||||
return project.loadRemotes();
|
||||
});
|
||||
promises.push(modifyRemotesPromise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.hasOwnProperty('files')) {
|
||||
this.package['node-red'] = this.package['node-red'] || { settings: {}};
|
||||
if (data.files.hasOwnProperty('flow') && this.package['node-red'].settings.flowFile !== data.files.flow) {
|
||||
this.paths.flowFile = data.files.flow;
|
||||
this.package['node-red'].settings.flowFile = data.files.flow;
|
||||
savePackage = true;
|
||||
flowFilesChanged = true;
|
||||
}
|
||||
if (data.files.hasOwnProperty('credentials') && this.package['node-red'].settings.credentialsFile !== data.files.credentials) {
|
||||
this.paths.credentialsFile = data.files.credentials;
|
||||
this.package['node-red'].settings.credentialsFile = data.files.credentials;
|
||||
// Don't know if the credSecret is invalid or not so clear the flag
|
||||
delete this.credentialSecretInvalid;
|
||||
|
||||
savePackage = true;
|
||||
flowFilesChanged = true;
|
||||
}
|
||||
}
|
||||
if (saveSettings) {
|
||||
promises.push(settings.set("projects",globalProjectSettings));
|
||||
}
|
||||
if (saveREADME) {
|
||||
promises.push(util.writeFile(this.paths['README.md'], this.description));
|
||||
}
|
||||
if (savePackage) {
|
||||
promises.push(util.writeFile(this.paths['package.json'], JSON.stringify(this.package,"",4)));
|
||||
}
|
||||
return when.settle(promises).then(function(res) {
|
||||
return {
|
||||
flowFilesChanged: flowFilesChanged,
|
||||
credentialSecretChanged: credentialSecretChanged
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
Project.prototype.getFiles = function () {
|
||||
return gitTools.getFiles(this.path).catch(function(err) {
|
||||
if (/ambiguous argument/.test(err.message)) {
|
||||
return {};
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
};
|
||||
Project.prototype.stageFile = function(file) {
|
||||
return gitTools.stageFile(this.path,file);
|
||||
};
|
||||
Project.prototype.unstageFile = function(file) {
|
||||
return gitTools.unstageFile(this.path,file);
|
||||
}
|
||||
Project.prototype.commit = function(user, options) {
|
||||
var self = this;
|
||||
return gitTools.commit(this.path,options.message,getGitUser(user)).then(function() {
|
||||
if (self.merging) {
|
||||
self.merging = false;
|
||||
return
|
||||
}
|
||||
});
|
||||
}
|
||||
Project.prototype.getFileDiff = function(file,type) {
|
||||
return gitTools.getFileDiff(this.path,file,type);
|
||||
}
|
||||
Project.prototype.getCommits = function(options) {
|
||||
return gitTools.getCommits(this.path,options).catch(function(err) {
|
||||
if (/bad default revision/i.test(err.message) || /ambiguous argument/i.test(err.message) || /does not have any commits yet/i.test(err.message)) {
|
||||
return {
|
||||
count:0,
|
||||
commits:[],
|
||||
total: 0
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
Project.prototype.getCommit = function(sha) {
|
||||
return gitTools.getCommit(this.path,sha);
|
||||
}
|
||||
Project.prototype.getFile = function (filePath,treeish) {
|
||||
if (treeish !== "_") {
|
||||
return gitTools.getFile(this.path, filePath, treeish);
|
||||
} else {
|
||||
return fs.readFile(fspath.join(this.path,filePath),"utf8");
|
||||
}
|
||||
};
|
||||
Project.prototype.revertFile = function (filePath) {
|
||||
var self = this;
|
||||
return gitTools.revertFile(this.path, filePath).then(function() {
|
||||
return self.load();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
Project.prototype.status = function(user, includeRemote) {
|
||||
var self = this;
|
||||
|
||||
var fetchPromise;
|
||||
if (this.remotes && includeRemote) {
|
||||
fetchPromise = gitTools.getRemoteBranch(self.path).then(function(remoteBranch) {
|
||||
if (remoteBranch) {
|
||||
var allRemotes = Object.keys(self.remotes);
|
||||
var match = "";
|
||||
allRemotes.forEach(function(remote) {
|
||||
if (remoteBranch.indexOf(remote) === 0 && match.length < remote.length) {
|
||||
match = remote;
|
||||
}
|
||||
})
|
||||
return self.fetch(user, match);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
fetchPromise = Promise.resolve();
|
||||
}
|
||||
|
||||
var completeStatus = function(fetchError) {
|
||||
var promises = [
|
||||
gitTools.getStatus(self.path),
|
||||
fs.exists(fspath.join(self.path,".git","MERGE_HEAD"))
|
||||
];
|
||||
return when.all(promises).then(function(results) {
|
||||
var result = results[0];
|
||||
if (results[1]) {
|
||||
result.merging = true;
|
||||
if (!self.merging) {
|
||||
self.merging = true;
|
||||
runtime.events.emit("runtime-event",{
|
||||
id:"runtime-state",
|
||||
payload:{
|
||||
type:"warning",
|
||||
error:"git_merge_conflict",
|
||||
project:self.name,
|
||||
text:"notification.warnings.git_merge_conflict"
|
||||
},
|
||||
retain:true}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.merging = false;
|
||||
}
|
||||
self.branches.local = result.branches.local;
|
||||
self.branches.remote = result.branches.remote;
|
||||
if (fetchError && !/ambiguous argument/.test(fetchError.message)) {
|
||||
result.branches.remoteError = {
|
||||
remote: fetchError.remote,
|
||||
code: fetchError.code
|
||||
}
|
||||
}
|
||||
if (result.commits.total === 0 && Object.keys(result.files).length === 0) {
|
||||
if (!self.empty) {
|
||||
runtime.events.emit("runtime-event",{
|
||||
id:"runtime-state",
|
||||
payload:{
|
||||
type:"warning",
|
||||
error:"project_empty",
|
||||
text:"notification.warnings.project_empty"},
|
||||
retain:true
|
||||
}
|
||||
);
|
||||
}
|
||||
self.empty = true;
|
||||
} else {
|
||||
if (self.empty) {
|
||||
if (self.paths.flowFile) {
|
||||
runtime.events.emit("runtime-event",{id:"runtime-state",retain:true});
|
||||
} else {
|
||||
runtime.events.emit("runtime-event",{
|
||||
id:"runtime-state",
|
||||
payload:{
|
||||
type:"warning",
|
||||
error:"missing_flow_file",
|
||||
text:"notification.warnings.missing_flow_file"},
|
||||
retain:true
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
delete self.empty;
|
||||
}
|
||||
return result;
|
||||
}).catch(function(err) {
|
||||
if (/ambiguous argument/.test(err.message)) {
|
||||
return {
|
||||
files:{},
|
||||
commits:{total:0},
|
||||
branches:{}
|
||||
};
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
return fetchPromise.then(completeStatus).catch(function(e) {
|
||||
// if (e.code !== 'git_auth_failed') {
|
||||
// console.log("Fetch failed");
|
||||
// console.log(e);
|
||||
// }
|
||||
return completeStatus(e);
|
||||
})
|
||||
};
|
||||
|
||||
Project.prototype.push = function (user,remoteBranchName,setRemote) {
|
||||
var username;
|
||||
if (!user) {
|
||||
username = "_";
|
||||
} else {
|
||||
username = user.username;
|
||||
}
|
||||
var remote = this.parseRemoteBranch(remoteBranchName||this.branches.remote);
|
||||
return gitTools.push(this.path, remote.remote || this.currentRemote,remote.branch, setRemote, authCache.get(this.name,this.remotes[remote.remote || this.currentRemote].fetch,username));
|
||||
};
|
||||
|
||||
Project.prototype.pull = function (user,remoteBranchName,setRemote,allowUnrelatedHistories) {
|
||||
var username;
|
||||
if (!user) {
|
||||
username = "_";
|
||||
} else {
|
||||
username = user.username;
|
||||
}
|
||||
var self = this;
|
||||
if (setRemote) {
|
||||
return gitTools.setUpstream(this.path, remoteBranchName).then(function() {
|
||||
self.currentRemote = self.parseRemoteBranch(remoteBranchName).remote;
|
||||
return gitTools.pull(self.path, null, null, allowUnrelatedHistories, authCache.get(self.name,self.remotes[self.currentRemote].fetch,username),getGitUser(user));
|
||||
})
|
||||
} else {
|
||||
var remote = this.parseRemoteBranch(remoteBranchName);
|
||||
return gitTools.pull(this.path, remote.remote, remote.branch, allowUnrelatedHistories, authCache.get(this.name,this.remotes[remote.remote||self.currentRemote].fetch,username),getGitUser(user));
|
||||
}
|
||||
};
|
||||
|
||||
Project.prototype.resolveMerge = function (file,resolutions) {
|
||||
var filePath = fspath.join(this.path,file);
|
||||
var self = this;
|
||||
if (typeof resolutions === 'string') {
|
||||
return util.writeFile(filePath, resolutions).then(function() {
|
||||
return self.stageFile(file);
|
||||
})
|
||||
}
|
||||
return fs.readFile(filePath,"utf8").then(function(content) {
|
||||
var lines = content.split("\n");
|
||||
var result = [];
|
||||
var ignoreBlock = false;
|
||||
var currentBlock;
|
||||
for (var i=1;i<=lines.length;i++) {
|
||||
if (resolutions.hasOwnProperty(i)) {
|
||||
currentBlock = resolutions[i];
|
||||
if (currentBlock.selection === "A") {
|
||||
ignoreBlock = false;
|
||||
} else {
|
||||
ignoreBlock = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (currentBlock) {
|
||||
if (currentBlock.separator === i) {
|
||||
if (currentBlock.selection === "A") {
|
||||
ignoreBlock = true;
|
||||
} else {
|
||||
ignoreBlock = false;
|
||||
}
|
||||
continue;
|
||||
} else if (currentBlock.changeEnd === i) {
|
||||
currentBlock = null;
|
||||
continue;
|
||||
} else if (ignoreBlock) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result.push(lines[i-1]);
|
||||
}
|
||||
var finalResult = result.join("\n");
|
||||
return util.writeFile(filePath,finalResult).then(function() {
|
||||
return self.stageFile(file);
|
||||
})
|
||||
});
|
||||
};
|
||||
Project.prototype.abortMerge = function () {
|
||||
var self = this;
|
||||
return gitTools.abortMerge(this.path).then(function() {
|
||||
self.merging = false;
|
||||
})
|
||||
};
|
||||
|
||||
Project.prototype.getBranches = function (user, isRemote) {
|
||||
var self = this;
|
||||
var fetchPromise;
|
||||
if (isRemote) {
|
||||
fetchPromise = self.fetch(user);
|
||||
} else {
|
||||
fetchPromise = Promise.resolve();
|
||||
}
|
||||
return fetchPromise.then(function() {
|
||||
return gitTools.getBranches(self.path,isRemote);
|
||||
});
|
||||
};
|
||||
|
||||
Project.prototype.deleteBranch = function (user, branch, isRemote, force) {
|
||||
// TODO: isRemote==true support
|
||||
// TODO: make sure we don't try to delete active branch
|
||||
return gitTools.deleteBranch(this.path,branch,isRemote, force);
|
||||
};
|
||||
|
||||
Project.prototype.fetch = function(user,remoteName) {
|
||||
var username;
|
||||
if (!user) {
|
||||
username = "_";
|
||||
} else {
|
||||
username = user.username;
|
||||
}
|
||||
var project = this;
|
||||
if (remoteName) {
|
||||
return gitTools.fetch(project.path,remoteName,authCache.get(project.name,project.remotes[remoteName].fetch,username)).catch(function(err) {
|
||||
err.remote = remoteName;
|
||||
throw err;
|
||||
})
|
||||
} else {
|
||||
var remotes = Object.keys(this.remotes);
|
||||
var promise = Promise.resolve();
|
||||
remotes.forEach(function(remote) {
|
||||
promise = promise.then(function() {
|
||||
return gitTools.fetch(project.path,remote,authCache.get(project.name,project.remotes[remote].fetch,username))
|
||||
}).catch(function(err) {
|
||||
err.remote = remote;
|
||||
throw err;
|
||||
})
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
||||
Project.prototype.setBranch = function (branchName, isCreate) {
|
||||
var self = this;
|
||||
return gitTools.checkoutBranch(this.path, branchName, isCreate).then(function() {
|
||||
return self.load();
|
||||
})
|
||||
};
|
||||
Project.prototype.getBranchStatus = function (branchName) {
|
||||
return gitTools.getBranchStatus(this.path,branchName);
|
||||
};
|
||||
|
||||
|
||||
|
||||
Project.prototype.getRemotes = function (user) {
|
||||
return gitTools.getRemotes(this.path).then(function(remotes) {
|
||||
var result = [];
|
||||
for (var name in remotes) {
|
||||
if (remotes.hasOwnProperty(name)) {
|
||||
remotes[name].name = name;
|
||||
result.push(remotes[name]);
|
||||
}
|
||||
}
|
||||
return {remotes:result};
|
||||
})
|
||||
};
|
||||
Project.prototype.addRemote = function(user,remote,options) {
|
||||
var project = this;
|
||||
return gitTools.addRemote(this.path,remote,options).then(function() {
|
||||
return project.loadRemotes()
|
||||
});
|
||||
}
|
||||
Project.prototype.updateRemote = function(user,remote,options) {
|
||||
var username;
|
||||
if (!user) {
|
||||
username = "_";
|
||||
} else {
|
||||
username = user.username;
|
||||
}
|
||||
|
||||
if (options.auth) {
|
||||
var url = this.remotes[remote].fetch;
|
||||
if (options.auth.keyFile) {
|
||||
options.auth.key_path = sshKeys.getPrivateKeyPath(getSSHKeyUsername(user), options.auth.keyFile);
|
||||
}
|
||||
authCache.set(this.name,url,username,options.auth);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
Project.prototype.removeRemote = function(user, remote) {
|
||||
// TODO: if this was the last remote using this url, then remove the authCache
|
||||
// details.
|
||||
var project = this;
|
||||
return gitTools.removeRemote(this.path,remote).then(function() {
|
||||
return project.loadRemotes()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Project.prototype.getFlowFile = function() {
|
||||
// console.log("Project.getFlowFile = ",this.paths.flowFile);
|
||||
if (this.paths.flowFile) {
|
||||
return fspath.join(this.path,this.paths.flowFile);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Project.prototype.getFlowFileBackup = function() {
|
||||
var flowFile = this.getFlowFile();
|
||||
if (flowFile) {
|
||||
return getBackupFilename(flowFile);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Project.prototype.getCredentialsFile = function() {
|
||||
// console.log("Project.getCredentialsFile = ",this.paths.credentialsFile);
|
||||
if (this.paths.credentialsFile) {
|
||||
return fspath.join(this.path,this.paths.credentialsFile);
|
||||
} else {
|
||||
return this.paths.credentialsFile;
|
||||
}
|
||||
}
|
||||
Project.prototype.getCredentialsFileBackup = function() {
|
||||
return getBackupFilename(this.getCredentialsFile());
|
||||
}
|
||||
|
||||
Project.prototype.export = function () {
|
||||
|
||||
return {
|
||||
name: this.name,
|
||||
summary: this.package.description,
|
||||
description: this.description,
|
||||
dependencies: this.package.dependencies||{},
|
||||
empty: this.empty,
|
||||
settings: {
|
||||
credentialsEncrypted: (typeof this.credentialSecret === "string"),
|
||||
credentialSecretInvalid: this.credentialSecretInvalid
|
||||
},
|
||||
files: {
|
||||
flow: this.paths.flowFile,
|
||||
credentials: this.paths.credentialsFile
|
||||
},
|
||||
git: {
|
||||
remotes: this.remotes,
|
||||
branches: this.branches
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function getCredentialsFilename(filename) {
|
||||
filename = filename || "undefined";
|
||||
// TODO: DRY - ./index.js
|
||||
var ffDir = fspath.dirname(filename);
|
||||
var ffExt = fspath.extname(filename);
|
||||
var ffBase = fspath.basename(filename,ffExt);
|
||||
return fspath.join(ffDir,ffBase+"_cred"+ffExt);
|
||||
}
|
||||
function getBackupFilename(filename) {
|
||||
// TODO: DRY - ./index.js
|
||||
filename = filename || "undefined";
|
||||
var ffName = fspath.basename(filename);
|
||||
var ffDir = fspath.dirname(filename);
|
||||
return fspath.join(ffDir,"."+ffName+".backup");
|
||||
}
|
||||
|
||||
function checkProjectExists(projectPath) {
|
||||
return fs.pathExists(projectPath).then(function(exists) {
|
||||
if (!exists) {
|
||||
var e = new Error("Project not found");
|
||||
e.code = "project_not_found";
|
||||
var name = fspath.basename(projectPath);
|
||||
e.project = name;
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createDefaultProject(user, project) {
|
||||
var projectPath = fspath.join(projectsDir,project.name);
|
||||
// Create a basic skeleton of a project
|
||||
return gitTools.initRepo(projectPath).then(function() {
|
||||
var promises = [];
|
||||
var files = Object.keys(defaultFileSet);
|
||||
if (project.files) {
|
||||
if (project.files.flow && !/\.\./.test(project.files.flow)) {
|
||||
var flowFilePath;
|
||||
var credsFilePath;
|
||||
|
||||
if (project.migrateFiles) {
|
||||
var baseFlowFileName = project.files.flow || fspath.basename(project.files.oldFlow);
|
||||
var baseCredentialFileName = project.files.credentials || fspath.basename(project.files.oldCredentials);
|
||||
files.push(baseFlowFileName);
|
||||
files.push(baseCredentialFileName);
|
||||
flowFilePath = fspath.join(projectPath,baseFlowFileName);
|
||||
credsFilePath = fspath.join(projectPath,baseCredentialFileName);
|
||||
if (fs.existsSync(project.files.oldFlow)) {
|
||||
log.trace("Migrating "+project.files.oldFlow+" to "+flowFilePath);
|
||||
promises.push(fs.copy(project.files.oldFlow,flowFilePath));
|
||||
} else {
|
||||
log.trace(project.files.oldFlow+" does not exist - creating blank file");
|
||||
promises.push(util.writeFile(flowFilePath,"[]"));
|
||||
}
|
||||
log.trace("Migrating "+project.files.oldCredentials+" to "+credsFilePath);
|
||||
runtime.nodes.setCredentialSecret(project.credentialSecret);
|
||||
promises.push(runtime.nodes.exportCredentials().then(function(creds) {
|
||||
var credentialData;
|
||||
if (settings.flowFilePretty) {
|
||||
credentialData = JSON.stringify(creds,null,4);
|
||||
} else {
|
||||
credentialData = JSON.stringify(creds);
|
||||
}
|
||||
return util.writeFile(credsFilePath,credentialData);
|
||||
}));
|
||||
delete project.migrateFiles;
|
||||
project.files.flow = baseFlowFileName;
|
||||
project.files.credentials = baseCredentialFileName;
|
||||
} else {
|
||||
project.files.credentials = project.files.credentials || getCredentialsFilename(project.files.flow);
|
||||
files.push(project.files.flow);
|
||||
files.push(project.files.credentials);
|
||||
flowFilePath = fspath.join(projectPath,project.files.flow);
|
||||
credsFilePath = getCredentialsFilename(flowFilePath);
|
||||
promises.push(util.writeFile(flowFilePath,"[]"));
|
||||
promises.push(util.writeFile(credsFilePath,"{}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var file in defaultFileSet) {
|
||||
if (defaultFileSet.hasOwnProperty(file)) {
|
||||
promises.push(util.writeFile(fspath.join(projectPath,file),defaultFileSet[file](project, runtime)));
|
||||
}
|
||||
}
|
||||
|
||||
return when.all(promises).then(function() {
|
||||
return gitTools.stageFile(projectPath,files);
|
||||
}).then(function() {
|
||||
return gitTools.commit(projectPath,"Create project",getGitUser(user));
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function checkProjectFiles(project) {
|
||||
var projectPath = project.path;
|
||||
var promises = [];
|
||||
var paths = [];
|
||||
for (var file in defaultFileSet) {
|
||||
if (defaultFileSet.hasOwnProperty(file)) {
|
||||
paths.push(file);
|
||||
promises.push(fs.stat(fspath.join(projectPath,file)));
|
||||
}
|
||||
}
|
||||
return when.settle(promises).then(function(results) {
|
||||
var missing = [];
|
||||
results.forEach(function(result,i) {
|
||||
if (result.state === 'rejected') {
|
||||
missing.push(paths[i]);
|
||||
}
|
||||
});
|
||||
return missing;
|
||||
}).then(function(missing) {
|
||||
// if (createMissing) {
|
||||
// var promises = [];
|
||||
// missing.forEach(function(file) {
|
||||
// promises.push(util.writeFile(fspath.join(projectPath,file),defaultFileSet[file](project)));
|
||||
// });
|
||||
// return promises;
|
||||
// } else {
|
||||
return missing;
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
function createProject(user, metadata) {
|
||||
var username;
|
||||
if (!user) {
|
||||
username = "_";
|
||||
} else {
|
||||
username = user.username;
|
||||
}
|
||||
if (!metadata.path) {
|
||||
throw new Error("Project missing path property");
|
||||
}
|
||||
if (!metadata.name) {
|
||||
throw new Error("Project missing name property");
|
||||
}
|
||||
|
||||
var project = metadata.name;
|
||||
var projectPath = metadata.path;
|
||||
return new Promise(function(resolve,reject) {
|
||||
fs.stat(projectPath, function(err,stat) {
|
||||
if (!err) {
|
||||
var e = new Error("NLS: Project already exists");
|
||||
e.code = "project_exists";
|
||||
return reject(e);
|
||||
}
|
||||
fs.ensureDir(projectPath).then(function() {
|
||||
var projects = settings.get('projects');
|
||||
if (!projects) {
|
||||
projects = {
|
||||
projects:{}
|
||||
}
|
||||
}
|
||||
projects.projects[project] = {};
|
||||
if (metadata.hasOwnProperty('credentialSecret')) {
|
||||
projects.projects[project].credentialSecret = metadata.credentialSecret;
|
||||
}
|
||||
return settings.set('projects',projects);
|
||||
}).then(function() {
|
||||
if (metadata.git && metadata.git.remotes && metadata.git.remotes.origin) {
|
||||
var originRemote = metadata.git.remotes.origin;
|
||||
var auth;
|
||||
if (originRemote.hasOwnProperty("username") && originRemote.hasOwnProperty("password")) {
|
||||
authCache.set(project,originRemote.url,username,{ // TODO: hardcoded remote name
|
||||
username: originRemote.username,
|
||||
password: originRemote.password
|
||||
}
|
||||
);
|
||||
auth = authCache.get(project,originRemote.url,username);
|
||||
}
|
||||
else if (originRemote.hasOwnProperty("keyFile") && originRemote.hasOwnProperty("passphrase")) {
|
||||
authCache.set(project,originRemote.url,username,{ // TODO: hardcoded remote name
|
||||
key_path: sshKeys.getPrivateKeyPath(getSSHKeyUsername(user), originRemote.keyFile),
|
||||
passphrase: originRemote.passphrase
|
||||
}
|
||||
);
|
||||
auth = authCache.get(project,originRemote.url,username);
|
||||
}
|
||||
return gitTools.clone(originRemote,auth,projectPath);
|
||||
} else {
|
||||
return createDefaultProject(user, metadata);
|
||||
}
|
||||
}).then(function() {
|
||||
resolve(loadProject(projectPath))
|
||||
}).catch(function(err) {
|
||||
fs.remove(projectPath,function() {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function deleteProject(user, projectPath) {
|
||||
return checkProjectExists(projectPath).then(function() {
|
||||
return fs.remove(projectPath).then(function() {
|
||||
var name = fspath.basename(projectPath);
|
||||
var projects = settings.get('projects');
|
||||
delete projects.projects[name];
|
||||
return settings.set('projects', projects);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadProject(projectPath) {
|
||||
return checkProjectExists(projectPath).then(function() {
|
||||
var project = new Project(projectPath);
|
||||
return project.load();
|
||||
});
|
||||
}
|
||||
|
||||
function init(_settings, _runtime) {
|
||||
settings = _settings;
|
||||
runtime = _runtime;
|
||||
log = runtime.log;
|
||||
projectsDir = fspath.join(settings.userDir,"projects");
|
||||
authCache.init();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
load: loadProject,
|
||||
create: createProject,
|
||||
delete: deleteProject
|
||||
}
|
48
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/defaultFileSet.js
generated
vendored
Normal file
48
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/defaultFileSet.js
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
module.exports = {
|
||||
"package.json": function(project, runtime) {
|
||||
var i18n = runtime.i18n;
|
||||
var package = {
|
||||
"name": project.name,
|
||||
"description": project.summary||i18n._("storage.localfilesystem.projects.summary"),
|
||||
"version": "0.0.1",
|
||||
"dependencies": {},
|
||||
"node-red": {
|
||||
"settings": {
|
||||
}
|
||||
}
|
||||
};
|
||||
if (project.files) {
|
||||
if (project.files.flow) {
|
||||
package['node-red'].settings.flowFile = project.files.flow;
|
||||
package['node-red'].settings.credentialsFile = project.files.credentials;
|
||||
}
|
||||
}
|
||||
return JSON.stringify(package,"",4);
|
||||
},
|
||||
"README.md": function(project, runtime) {
|
||||
var i18n = runtime.i18n;
|
||||
var content = project.name+"\n"+("=".repeat(project.name.length))+"\n\n";
|
||||
if (project.summary) {
|
||||
content += project.summary+"\n\n";
|
||||
}
|
||||
content += i18n._("storage.localfilesystem.projects.readme");
|
||||
return content;
|
||||
},
|
||||
".gitignore": function() { return "*.backup" ;}
|
||||
}
|
46
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/git/authCache.js
generated
vendored
Normal file
46
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/git/authCache.js
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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 authCache = {}
|
||||
|
||||
module.exports = {
|
||||
init: function() {
|
||||
authCache = {};
|
||||
},
|
||||
clear: function(project,remote, user) {
|
||||
if (user && remote && authCache[project] && authCache[project][remote]) {
|
||||
delete authCache[project][remote][user];
|
||||
} else if (remote && authCache.hasOwnProperty(project)) {
|
||||
delete authCache[project][remote];
|
||||
} else {
|
||||
delete authCache[project];
|
||||
}
|
||||
},
|
||||
set: function(project,remote,user,auth) {
|
||||
// console.log("AuthCache.set",remote,user,auth);
|
||||
authCache[project] = authCache[project]||{};
|
||||
authCache[project][remote] = authCache[project][remote]||{};
|
||||
authCache[project][remote][user] = auth;
|
||||
// console.log(JSON.stringify(authCache,'',4));
|
||||
},
|
||||
get: function(project,remote,user) {
|
||||
// console.log("AuthCache.get",remote,user,authCache[project]&&authCache[project][remote]&&authCache[project][remote][user]);
|
||||
if (authCache[project] && authCache[project][remote]) {
|
||||
return authCache[project][remote][user];
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
132
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/git/authServer.js
generated
vendored
Normal file
132
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/git/authServer.js
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* 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 net = require("net");
|
||||
var fs = require("fs-extra");
|
||||
var path = require("path");
|
||||
var os = require("os");
|
||||
|
||||
function getListenPath() {
|
||||
var seed = (0x100000+Math.random()*0x999999).toString(16);
|
||||
var fn = 'node-red-git-askpass-'+seed+'-sock';
|
||||
var listenPath;
|
||||
if (process.platform === 'win32') {
|
||||
listenPath = '\\\\.\\pipe\\'+fn;
|
||||
} else {
|
||||
listenPath = path.join(process.env['XDG_RUNTIME_DIR'] || os.tmpdir(), fn);
|
||||
}
|
||||
// console.log(listenPath);
|
||||
return listenPath;
|
||||
}
|
||||
|
||||
|
||||
|
||||
var ResponseServer = function(auth) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var server = net.createServer(function(connection) {
|
||||
connection.setEncoding('utf8');
|
||||
var parts = [];
|
||||
connection.on('data', function(data) {
|
||||
var m = data.indexOf("\n");
|
||||
if (m !== -1) {
|
||||
parts.push(data.substring(0, m));
|
||||
data = data.substring(m);
|
||||
var line = parts.join("");
|
||||
// console.log("LINE:",line);
|
||||
parts = [];
|
||||
if (line==='Username') {
|
||||
connection.end(auth.username);
|
||||
} else if (line === 'Password') {
|
||||
connection.end(auth.password);
|
||||
server.close();
|
||||
} else {
|
||||
}
|
||||
}
|
||||
if (data.length > 0) {
|
||||
parts.push(data);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
var listenPath = getListenPath();
|
||||
|
||||
server.listen(listenPath, function(ready) {
|
||||
resolve({path:listenPath,close:function() { server.close(); }});
|
||||
});
|
||||
server.on('close', function() {
|
||||
// console.log("Closing response server");
|
||||
fs.removeSync(listenPath);
|
||||
});
|
||||
server.on('error',function(err) {
|
||||
console.log("ResponseServer unexpectedError:",err.toString());
|
||||
server.close();
|
||||
reject(err);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
var ResponseSSHServer = function(auth) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var server = net.createServer(function(connection) {
|
||||
connection.setEncoding('utf8');
|
||||
var parts = [];
|
||||
connection.on('data', function(data) {
|
||||
var m = data.indexOf("\n");
|
||||
if (m !== -1) {
|
||||
parts.push(data.substring(0, m));
|
||||
data = data.substring(m);
|
||||
var line = parts.join("");
|
||||
parts = [];
|
||||
if (line==='The') {
|
||||
// TODO: document these exchanges!
|
||||
connection.end('yes');
|
||||
// server.close();
|
||||
} else if (line === 'Enter') {
|
||||
connection.end(auth.passphrase);
|
||||
// server.close();
|
||||
} else {
|
||||
}
|
||||
}
|
||||
if (data.length > 0) {
|
||||
parts.push(data);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
var listenPath = getListenPath();
|
||||
|
||||
server.listen(listenPath, function(ready) {
|
||||
resolve({path:listenPath,close:function() { server.close(); }});
|
||||
});
|
||||
server.on('close', function() {
|
||||
// console.log("Closing response server");
|
||||
fs.removeSync(listenPath);
|
||||
});
|
||||
server.on('error',function(err) {
|
||||
console.log("ResponseServer unexpectedError:",err.toString());
|
||||
server.close();
|
||||
reject(err);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
ResponseServer: ResponseServer,
|
||||
ResponseSSHServer: ResponseSSHServer
|
||||
}
|
24
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/git/authWriter.js
generated
vendored
Normal file
24
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/git/authWriter.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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 net = require("net");
|
||||
var socket = net.connect(process.argv[2], function() {
|
||||
socket.on('data', function(data) { console.log(data);});
|
||||
socket.on('end', function() {
|
||||
});
|
||||
socket.write((process.argv[3]||"")+"\n", 'utf8');
|
||||
});
|
||||
socket.setEncoding('utf8');
|
660
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/git/index.js
generated
vendored
Normal file
660
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/git/index.js
generated
vendored
Normal file
@@ -0,0 +1,660 @@
|
||||
/**
|
||||
* 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 when = require('when');
|
||||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
var authResponseServer = require('./authServer').ResponseServer;
|
||||
var sshResponseServer = require('./authServer').ResponseSSHServer;
|
||||
var clone = require('clone');
|
||||
var path = require("path");
|
||||
|
||||
var gitCommand = "git";
|
||||
var gitVersion;
|
||||
var log;
|
||||
|
||||
function runGitCommand(args,cwd,env) {
|
||||
log.trace(gitCommand + JSON.stringify(args));
|
||||
return when.promise(function(resolve,reject) {
|
||||
args.unshift("credential.helper=")
|
||||
args.unshift("-c");
|
||||
var child = spawn(gitCommand, args, {cwd:cwd, detached:true, env:env});
|
||||
var stdout = "";
|
||||
var stderr = "";
|
||||
child.stdout.on('data', function(data) {
|
||||
stdout += data;
|
||||
});
|
||||
child.stderr.on('data', function(data) {
|
||||
stderr += data;
|
||||
});
|
||||
child.on('error', function(err) {
|
||||
stderr = err.toString();
|
||||
})
|
||||
child.on('close', function(code) {
|
||||
if (code !== 0) {
|
||||
var err = new Error(stderr);
|
||||
err.stdout = stdout;
|
||||
err.stderr = 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
|
||||
err.code = "git_auth_failed";
|
||||
} else if(/HTTP Basic: Access denied/i.test(stderr)) {
|
||||
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)) {
|
||||
err.code = "git_pull_merge_conflict";
|
||||
} else if (/not fully merged/i.test(stderr)) {
|
||||
err.code = "git_delete_branch_unmerged";
|
||||
} else if (/remote .* already exists/i.test(stderr)) {
|
||||
err.code = "git_remote_already_exists";
|
||||
} else if (/does not appear to be a git repository/i.test(stderr)) {
|
||||
err.code = "git_not_a_repository";
|
||||
} else if (/Repository not found/i.test(stderr)) {
|
||||
err.code = "git_repository_not_found";
|
||||
} else if (/repository '.*' does not exist/i.test(stderr)) {
|
||||
err.code = "git_repository_not_found";
|
||||
} else if (/refusing to merge unrelated histories/i.test(stderr)) {
|
||||
err.code = "git_pull_unrelated_history"
|
||||
} else if (/Please tell me who you are/i.test(stderr)) {
|
||||
err.code = "git_missing_user";
|
||||
} else if (/name consists only of disallowed characters/i.test(stderr)) {
|
||||
err.code = "git_missing_user";
|
||||
}
|
||||
return reject(err);
|
||||
}
|
||||
resolve(stdout);
|
||||
});
|
||||
});
|
||||
}
|
||||
function runGitCommandWithAuth(args,cwd,auth) {
|
||||
return authResponseServer(auth).then(function(rs) {
|
||||
var commandEnv = clone(process.env);
|
||||
commandEnv.GIT_ASKPASS = path.join(__dirname,"node-red-ask-pass.sh");
|
||||
commandEnv.NODE_RED_GIT_NODE_PATH = process.execPath;
|
||||
commandEnv.NODE_RED_GIT_SOCK_PATH = rs.path;
|
||||
commandEnv.NODE_RED_GIT_ASKPASS_PATH = path.join(__dirname,"authWriter.js");
|
||||
return runGitCommand(args,cwd,commandEnv).finally(function() {
|
||||
rs.close();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function runGitCommandWithSSHCommand(args,cwd,auth) {
|
||||
return sshResponseServer(auth).then(function(rs) {
|
||||
var commandEnv = clone(process.env);
|
||||
commandEnv.SSH_ASKPASS = path.join(__dirname,"node-red-ask-pass.sh");
|
||||
commandEnv.DISPLAY = "dummy:0";
|
||||
commandEnv.NODE_RED_GIT_NODE_PATH = process.execPath;
|
||||
commandEnv.NODE_RED_GIT_SOCK_PATH = rs.path;
|
||||
commandEnv.NODE_RED_GIT_ASKPASS_PATH = path.join(__dirname,"authWriter.js");
|
||||
// For git < 2.3.0
|
||||
commandEnv.GIT_SSH = path.join(__dirname,"node-red-ssh.sh");
|
||||
commandEnv.NODE_RED_KEY_FILE=auth.key_path;
|
||||
// GIT_SSH_COMMAND - added in git 2.3.0
|
||||
commandEnv.GIT_SSH_COMMAND = "ssh -i " + auth.key_path + " -F /dev/null";
|
||||
// console.log('commandEnv:', commandEnv);
|
||||
return runGitCommand(args,cwd,commandEnv).finally(function() {
|
||||
rs.close();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function cleanFilename(name) {
|
||||
if (name[0] !== '"') {
|
||||
return name;
|
||||
}
|
||||
return name.substring(1,name.length-1);
|
||||
}
|
||||
function parseFilenames(name) {
|
||||
var re = /([^ "]+|(".*?"))($| -> ([^ ]+|(".*"))$)/;
|
||||
var m = re.exec(name);
|
||||
var result = [];
|
||||
if (m) {
|
||||
result.push(cleanFilename(m[1]));
|
||||
if (m[4]) {
|
||||
result.push(cleanFilename(m[4]));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// function getBranchInfo(localRepo) {
|
||||
// return runGitCommand(["status","--porcelain","-b"],localRepo).then(function(output) {
|
||||
// var lines = output.split("\n");
|
||||
// var unknownDirs = [];
|
||||
// var branchLineRE = /^## (No commits yet on )?(.+?)($|\.\.\.(.+?)($| \[(ahead (\d+))?.*?(behind (\d+))?\]))/m;
|
||||
// console.log(output);
|
||||
// console.log(lines);
|
||||
// var m = branchLineRE.exec(output);
|
||||
// console.log(m);
|
||||
// var result = {}; //commits:{}};
|
||||
// if (m) {
|
||||
// if (m[1]) {
|
||||
// result.empty = true;
|
||||
// }
|
||||
// result.local = m[2];
|
||||
// if (m[4]) {
|
||||
// result.remote = m[4];
|
||||
// }
|
||||
// }
|
||||
// return result;
|
||||
// });
|
||||
// }
|
||||
function getStatus(localRepo) {
|
||||
// parseFilename('"test with space"');
|
||||
// parseFilename('"test with space" -> knownFile.txt');
|
||||
// parseFilename('"test with space" -> "un -> knownFile.txt"');
|
||||
var result = {
|
||||
files: {},
|
||||
commits: {},
|
||||
branches: {}
|
||||
}
|
||||
return runGitCommand(['rev-list', 'HEAD', '--count'],localRepo).then(function(count) {
|
||||
result.commits.total = parseInt(count);
|
||||
}).catch(function(err) {
|
||||
if (/ambiguous argument/i.test(err.message)) {
|
||||
result.commits.total = 0;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}).then(function() {
|
||||
return runGitCommand(["ls-files","--cached","--others","--exclude-standard"],localRepo).then(function(output) {
|
||||
var lines = output.split("\n");
|
||||
lines.forEach(function(l) {
|
||||
if (l==="") {
|
||||
return;
|
||||
}
|
||||
var fullName = cleanFilename(l);
|
||||
// parseFilename(l);
|
||||
var parts = fullName.split("/");
|
||||
var p = result.files;
|
||||
var name;
|
||||
for (var i = 0;i<parts.length-1;i++) {
|
||||
var name = parts.slice(0,i+1).join("/")+"/";
|
||||
if (!p.hasOwnProperty(name)) {
|
||||
p[name] = {
|
||||
type:"d"
|
||||
}
|
||||
}
|
||||
}
|
||||
result.files[fullName] = {
|
||||
type: /\/$/.test(fullName)?"d":"f"
|
||||
}
|
||||
})
|
||||
return runGitCommand(["status","--porcelain","-b"],localRepo).then(function(output) {
|
||||
var lines = output.split("\n");
|
||||
var unknownDirs = [];
|
||||
var branchLineRE = /^## (?:(?:No commits yet on )|(?:Initial commit on))?(.+?)(?:$|\.\.\.(.+?)(?:$| \[(?:(?:ahead (\d+)(?:,\s*)?)?(?:behind (\d+))?|(gone))\]))/;
|
||||
lines.forEach(function(line) {
|
||||
if (line==="") {
|
||||
return;
|
||||
}
|
||||
if (line[0] === "#") {
|
||||
var m = branchLineRE.exec(line);
|
||||
if (m) {
|
||||
result.branches.local = m[1];
|
||||
if (m[2]) {
|
||||
result.branches.remote = m[2];
|
||||
result.commits.ahead = 0;
|
||||
result.commits.behind = 0;
|
||||
}
|
||||
if (m[3] !== undefined) {
|
||||
result.commits.ahead = parseInt(m[3]);
|
||||
}
|
||||
if (m[4] !== undefined) {
|
||||
result.commits.behind = parseInt(m[4]);
|
||||
}
|
||||
if (m[5] !== undefined) {
|
||||
result.commits.ahead = result.commits.total;
|
||||
result.branches.remoteError = {
|
||||
code: "git_remote_gone"
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
var status = line.substring(0,2);
|
||||
var fileName;
|
||||
var names;
|
||||
if (status !== '??') {
|
||||
names = parseFilenames(line.substring(3));
|
||||
} else {
|
||||
names = [cleanFilename(line.substring(3))];
|
||||
}
|
||||
fileName = names[0];
|
||||
if (names.length > 1) {
|
||||
fileName = names[1];
|
||||
}
|
||||
|
||||
// parseFilename(fileName);
|
||||
if (fileName.charCodeAt(0) === 34) {
|
||||
fileName = fileName.substring(1,fileName.length-1);
|
||||
}
|
||||
if (result.files.hasOwnProperty(fileName)) {
|
||||
result.files[fileName].status = status;
|
||||
} else {
|
||||
result.files[fileName] = {
|
||||
type: "f",
|
||||
status: status
|
||||
};
|
||||
}
|
||||
if (names.length > 1) {
|
||||
result.files[fileName].oldName = names[0];
|
||||
}
|
||||
if (status === "??" && fileName[fileName.length-1] === '/') {
|
||||
unknownDirs.push(fileName);
|
||||
}
|
||||
})
|
||||
var allFilenames = Object.keys(result.files);
|
||||
allFilenames.forEach(function(f) {
|
||||
var entry = result.files[f];
|
||||
if (!entry.hasOwnProperty('status')) {
|
||||
unknownDirs.forEach(function(uf) {
|
||||
if (f.startsWith(uf)) {
|
||||
entry.status = "??"
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
// console.log(files);
|
||||
return result;
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function parseLog(log) {
|
||||
var lines = log.split("\n");
|
||||
var currentCommit = {};
|
||||
var commits = [];
|
||||
lines.forEach(function(l) {
|
||||
if (l === "-----") {
|
||||
commits.push(currentCommit);
|
||||
currentCommit = {}
|
||||
return;
|
||||
}
|
||||
var m = /^(.*): (.*)$/.exec(l);
|
||||
if (m) {
|
||||
// git 2.1.4 (Debian Stable) doesn't support %D for refs - so filter out
|
||||
if (m[1] === 'refs' && m[2]) {
|
||||
if (m[2] !== '%D') {
|
||||
currentCommit[m[1]] = m[2].split(",").map(function(v) { return v.trim() });
|
||||
} else {
|
||||
currentCommit[m[1]] = [];
|
||||
}
|
||||
} else {
|
||||
if (m[1] === 'parents') {
|
||||
currentCommit[m[1]] = m[2].split(" ");
|
||||
} else {
|
||||
currentCommit[m[1]] = m[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return commits;
|
||||
}
|
||||
|
||||
function getRemotes(cwd) {
|
||||
return runGitCommand(['remote','-v'],cwd).then(function(output) {
|
||||
var result;
|
||||
if (output.length > 0) {
|
||||
result = {};
|
||||
var remoteRE = /^(.+)\t(.+) \((.+)\)$/gm;
|
||||
var m;
|
||||
while ((m = remoteRE.exec(output)) !== null) {
|
||||
result[m[1]] = result[m[1]]||{};
|
||||
result[m[1]][m[3]] = m[2];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})
|
||||
}
|
||||
|
||||
function getBranches(cwd, remote) {
|
||||
var args = ['branch','-vv','--no-color'];
|
||||
if (remote) {
|
||||
args.push('-r');
|
||||
}
|
||||
var branchRE = /^([ \*] )(\S+) +(\S+)(?: \[(\S+?)(?:: (?:ahead (\d+)(?:, )?)?(?:behind (\d+))?)?\])? (.*)$/;
|
||||
return runGitCommand(args,cwd).then(function(output) {
|
||||
var branches = [];
|
||||
var lines = output.split("\n");
|
||||
branches = lines.map(function(l) {
|
||||
var m = branchRE.exec(l);
|
||||
var branch = null;
|
||||
if (m) {
|
||||
branch = {
|
||||
name: m[2],
|
||||
remote: m[4],
|
||||
status: {
|
||||
ahead: m[5]||0,
|
||||
behind: m[6]||0,
|
||||
},
|
||||
commit: {
|
||||
sha: m[3],
|
||||
subject: m[7]
|
||||
}
|
||||
}
|
||||
if (m[1] === '* ') {
|
||||
branch.current = true;
|
||||
}
|
||||
}
|
||||
return branch;
|
||||
}).filter(function(v) { return !!v && v.commit.sha !== '->' });
|
||||
|
||||
return {branches:branches};
|
||||
})
|
||||
}
|
||||
function getBranchStatus(cwd,remoteBranch) {
|
||||
var commands = [
|
||||
// #commits master ahead
|
||||
runGitCommand(['rev-list', 'HEAD','^'+remoteBranch, '--count'],cwd),
|
||||
// #commits master behind
|
||||
runGitCommand(['rev-list', '^HEAD',remoteBranch, '--count'],cwd)
|
||||
];
|
||||
return when.all(commands).then(function(results) {
|
||||
return {
|
||||
commits: {
|
||||
ahead: parseInt(results[0]),
|
||||
behind: parseInt(results[1])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function addRemote(cwd,name,options) {
|
||||
var args = ["remote","add",name,options.url]
|
||||
return runGitCommand(args,cwd);
|
||||
}
|
||||
function removeRemote(cwd,name) {
|
||||
var args = ["remote","remove",name];
|
||||
return runGitCommand(args,cwd);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(_settings,_runtime) {
|
||||
log = _runtime.log
|
||||
return new Promise(function(resolve,reject) {
|
||||
Promise.all([
|
||||
runGitCommand(["--version"]),
|
||||
runGitCommand(["config","--global","user.name"]).catch(err=>""),
|
||||
runGitCommand(["config","--global","user.email"]).catch(err=>"")
|
||||
]).then(function(output) {
|
||||
var m = / (\d\S+)/.exec(output[0]);
|
||||
gitVersion = m[1];
|
||||
var globalUserName = output[1].trim();
|
||||
var globalUserEmail = output[2].trim();
|
||||
var result = {
|
||||
version: gitVersion
|
||||
};
|
||||
if (globalUserName && globalUserEmail) {
|
||||
result.user = {
|
||||
name: globalUserName,
|
||||
email: globalUserEmail
|
||||
}
|
||||
}
|
||||
log.trace("git init: "+JSON.stringify(result));
|
||||
resolve(result);
|
||||
}).catch(function(err) {
|
||||
log.trace("git init: git not found");
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
},
|
||||
initRepo: function(cwd) {
|
||||
return runGitCommand(["init"],cwd);
|
||||
},
|
||||
setUpstream: function(cwd,remoteBranch) {
|
||||
var args = ["branch","--set-upstream-to",remoteBranch];
|
||||
return runGitCommand(args,cwd);
|
||||
},
|
||||
pull: function(cwd,remote,branch,allowUnrelatedHistories,auth,gitUser) {
|
||||
var args = ["pull"];
|
||||
if (remote && branch) {
|
||||
args.push(remote);
|
||||
args.push(branch);
|
||||
}
|
||||
if (gitUser && gitUser['name'] && gitUser['email']) {
|
||||
args.unshift('user.name="'+gitUser['name']+'"');
|
||||
args.unshift('-c');
|
||||
args.unshift('user.email="'+gitUser['email']+'"');
|
||||
args.unshift('-c');
|
||||
}
|
||||
if (allowUnrelatedHistories) {
|
||||
args.push("--allow-unrelated-histories");
|
||||
}
|
||||
var promise;
|
||||
if (auth) {
|
||||
if ( auth.key_path ) {
|
||||
promise = runGitCommandWithSSHCommand(args,cwd,auth);
|
||||
}
|
||||
else {
|
||||
promise = runGitCommandWithAuth(args,cwd,auth);
|
||||
}
|
||||
} else {
|
||||
promise = runGitCommand(args,cwd)
|
||||
}
|
||||
return promise;
|
||||
// .catch(function(err) {
|
||||
// if (/CONFLICT/.test(err.stdout)) {
|
||||
// var e = new Error("pull failed - merge conflict");
|
||||
// e.code = "git_pull_merge_conflict";
|
||||
// throw e;
|
||||
// } else if (/Please commit your changes or stash/i.test(err.message)) {
|
||||
// var e = new Error("Pull failed - local changes would be overwritten");
|
||||
// e.code = "git_pull_overwrite";
|
||||
// throw e;
|
||||
// }
|
||||
// throw err;
|
||||
// });
|
||||
},
|
||||
push: function(cwd,remote,branch,setUpstream, auth) {
|
||||
var args = ["push"];
|
||||
if (branch) {
|
||||
if (setUpstream) {
|
||||
args.push("-u");
|
||||
}
|
||||
args.push(remote);
|
||||
args.push("HEAD:"+branch);
|
||||
} else {
|
||||
args.push(remote);
|
||||
}
|
||||
args.push("--porcelain");
|
||||
var promise;
|
||||
if (auth) {
|
||||
if ( auth.key_path ) {
|
||||
promise = runGitCommandWithSSHCommand(args,cwd,auth);
|
||||
}
|
||||
else {
|
||||
promise = runGitCommandWithAuth(args,cwd,auth);
|
||||
}
|
||||
} else {
|
||||
promise = runGitCommand(args,cwd)
|
||||
}
|
||||
return promise.catch(function(err) {
|
||||
if (err.code === 'git_error') {
|
||||
if (/^!.*non-fast-forward/m.test(err.stdout)) {
|
||||
err.code = 'git_push_failed';
|
||||
}
|
||||
throw err;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
},
|
||||
clone: function(remote, auth, cwd) {
|
||||
var args = ["clone",remote.url];
|
||||
if (remote.name) {
|
||||
args.push("-o");
|
||||
args.push(remote.name);
|
||||
}
|
||||
if (remote.branch) {
|
||||
args.push("-b");
|
||||
args.push(remote.branch);
|
||||
}
|
||||
args.push(".");
|
||||
if (auth) {
|
||||
if ( auth.key_path ) {
|
||||
return runGitCommandWithSSHCommand(args,cwd,auth);
|
||||
}
|
||||
else {
|
||||
return runGitCommandWithAuth(args,cwd,auth);
|
||||
}
|
||||
} else {
|
||||
return runGitCommand(args,cwd);
|
||||
}
|
||||
},
|
||||
getStatus: getStatus,
|
||||
getFile: function(cwd, filePath, treeish) {
|
||||
var args = ["show",treeish+":"+filePath];
|
||||
return runGitCommand(args,cwd);
|
||||
},
|
||||
getFiles: function(cwd) {
|
||||
return getStatus(cwd).then(function(status) {
|
||||
return status.files;
|
||||
})
|
||||
},
|
||||
revertFile: function(cwd, filePath) {
|
||||
var args = ["checkout",filePath];
|
||||
return runGitCommand(args,cwd);
|
||||
},
|
||||
stageFile: function(cwd,file) {
|
||||
var args = ["add"];
|
||||
if (Array.isArray(file)) {
|
||||
args = args.concat(file);
|
||||
} else {
|
||||
args.push(file);
|
||||
}
|
||||
return runGitCommand(args,cwd);
|
||||
},
|
||||
unstageFile: function(cwd, file) {
|
||||
var args = ["reset","--"];
|
||||
if (file) {
|
||||
args.push(file);
|
||||
}
|
||||
return runGitCommand(args,cwd);
|
||||
},
|
||||
commit: function(cwd, message, gitUser) {
|
||||
var args = ["commit","-m",message];
|
||||
var env;
|
||||
if (gitUser && gitUser['name'] && gitUser['email']) {
|
||||
args.unshift('user.name="'+gitUser['name']+'"');
|
||||
args.unshift('-c');
|
||||
args.unshift('user.email="'+gitUser['email']+'"');
|
||||
args.unshift('-c');
|
||||
}
|
||||
return runGitCommand(args,cwd,env);
|
||||
},
|
||||
getFileDiff(cwd,file,type) {
|
||||
var args = ["diff","-w"];
|
||||
if (type === "tree") {
|
||||
// nothing else to do
|
||||
} else if (type === "index") {
|
||||
args.push("--cached");
|
||||
}
|
||||
args.push(file);
|
||||
return runGitCommand(args,cwd);
|
||||
},
|
||||
fetch: function(cwd,remote,auth) {
|
||||
var args = ["fetch",remote];
|
||||
if (auth) {
|
||||
if ( auth.key_path ) {
|
||||
return runGitCommandWithSSHCommand(args,cwd,auth);
|
||||
}
|
||||
else {
|
||||
return runGitCommandWithAuth(args,cwd,auth);
|
||||
}
|
||||
} else {
|
||||
return runGitCommand(args,cwd);
|
||||
}
|
||||
},
|
||||
getCommits: function(cwd,options) {
|
||||
var args = ["log", "--format=sha: %H%nparents: %p%nrefs: %D%nauthor: %an%ndate: %ct%nsubject: %s%n-----"];
|
||||
var limit = parseInt(options.limit) || 20;
|
||||
args.push("-n "+limit);
|
||||
var before = options.before;
|
||||
if (before) {
|
||||
args.push(before);
|
||||
}
|
||||
var commands = [
|
||||
runGitCommand(['rev-list', 'HEAD', '--count'],cwd),
|
||||
runGitCommand(args,cwd).then(parseLog)
|
||||
];
|
||||
return when.all(commands).then(function(results) {
|
||||
var result = results[0];
|
||||
result.count = results[1].length;
|
||||
result.before = before;
|
||||
result.commits = results[1];
|
||||
return {
|
||||
count: results[1].length,
|
||||
commits: results[1],
|
||||
before: before,
|
||||
total: parseInt(results[0])
|
||||
};
|
||||
})
|
||||
},
|
||||
getCommit: function(cwd,sha) {
|
||||
var args = ["show",sha];
|
||||
return runGitCommand(args,cwd);
|
||||
},
|
||||
abortMerge: function(cwd) {
|
||||
return runGitCommand(['merge','--abort'],cwd);
|
||||
},
|
||||
getRemotes: getRemotes,
|
||||
getRemoteBranch: function(cwd) {
|
||||
return runGitCommand(['rev-parse','--abbrev-ref','--symbolic-full-name','@{u}'],cwd).catch(function(err) {
|
||||
if (/no upstream configured for branch/i.test(err.message)) {
|
||||
return null;
|
||||
}
|
||||
throw err;
|
||||
})
|
||||
},
|
||||
getBranches: getBranches,
|
||||
// getBranchInfo: getBranchInfo,
|
||||
checkoutBranch: function(cwd, branchName, isCreate) {
|
||||
var args = ['checkout'];
|
||||
if (isCreate) {
|
||||
args.push('-b');
|
||||
}
|
||||
args.push(branchName);
|
||||
return runGitCommand(args,cwd);
|
||||
},
|
||||
deleteBranch: function(cwd, branchName, isRemote, force) {
|
||||
if (isRemote) {
|
||||
throw new Error("Deleting remote branches not supported");
|
||||
}
|
||||
var args = ['branch'];
|
||||
if (force) {
|
||||
args.push('-D');
|
||||
} else {
|
||||
args.push('-d');
|
||||
}
|
||||
args.push(branchName);
|
||||
return runGitCommand(args, cwd);
|
||||
},
|
||||
getBranchStatus: getBranchStatus,
|
||||
addRemote: addRemote,
|
||||
removeRemote: removeRemote
|
||||
}
|
1
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/git/node-red-ask-pass.sh
generated
vendored
Executable file
1
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/git/node-red-ask-pass.sh
generated
vendored
Executable file
@@ -0,0 +1 @@
|
||||
"$NODE_RED_GIT_NODE_PATH" "$NODE_RED_GIT_ASKPASS_PATH" "$NODE_RED_GIT_SOCK_PATH" $@
|
1
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/git/node-red-ssh.sh
generated
vendored
Executable file
1
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/git/node-red-ssh.sh
generated
vendored
Executable file
@@ -0,0 +1 @@
|
||||
ssh -i "$NODE_RED_KEY_FILE" -F /dev/null $@
|
633
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/index.js
generated
vendored
Normal file
633
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/index.js
generated
vendored
Normal file
@@ -0,0 +1,633 @@
|
||||
/**
|
||||
* 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 fs = require('fs-extra');
|
||||
var when = require('when');
|
||||
var fspath = require("path");
|
||||
var nodeFn = require('when/node/function');
|
||||
var crypto = require('crypto');
|
||||
|
||||
var storageSettings = require("../settings");
|
||||
var util = require("../util");
|
||||
var gitTools = require("./git");
|
||||
var sshTools = require("./ssh");
|
||||
|
||||
var Projects = require("./Project");
|
||||
|
||||
var settings;
|
||||
var runtime;
|
||||
var log;
|
||||
|
||||
var projectsEnabled = false;
|
||||
var projectLogMessages = [];
|
||||
|
||||
var projectsDir;
|
||||
var activeProject
|
||||
|
||||
var globalGitUser = false;
|
||||
|
||||
function init(_settings, _runtime) {
|
||||
settings = _settings;
|
||||
runtime = _runtime;
|
||||
log = runtime.log;
|
||||
|
||||
try {
|
||||
if (settings.editorTheme.projects.enabled === true) {
|
||||
projectsEnabled = true;
|
||||
} else if (settings.editorTheme.projects.enabled === false) {
|
||||
projectLogMessages.push(log._("storage.localfilesystem.projects.disabled"))
|
||||
}
|
||||
} catch(err) {
|
||||
projectLogMessages.push(log._("storage.localfilesystem.projects.disabledNoFlag"))
|
||||
projectsEnabled = false;
|
||||
}
|
||||
|
||||
if (settings.flowFile) {
|
||||
flowsFile = settings.flowFile;
|
||||
// handle Unix and Windows "C:\"
|
||||
if ((flowsFile[0] == "/") || (flowsFile[1] == ":")) {
|
||||
// Absolute path
|
||||
flowsFullPath = flowsFile;
|
||||
} else if (flowsFile.substring(0,2) === "./") {
|
||||
// Relative to cwd
|
||||
flowsFullPath = fspath.join(process.cwd(),flowsFile);
|
||||
} else {
|
||||
try {
|
||||
fs.statSync(fspath.join(process.cwd(),flowsFile));
|
||||
// Found in cwd
|
||||
flowsFullPath = fspath.join(process.cwd(),flowsFile);
|
||||
} catch(err) {
|
||||
// Use userDir
|
||||
flowsFullPath = fspath.join(settings.userDir,flowsFile);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
flowsFile = 'flows_'+require('os').hostname()+'.json';
|
||||
flowsFullPath = fspath.join(settings.userDir,flowsFile);
|
||||
}
|
||||
var ffExt = fspath.extname(flowsFullPath);
|
||||
var ffBase = fspath.basename(flowsFullPath,ffExt);
|
||||
|
||||
flowsFileBackup = getBackupFilename(flowsFullPath);
|
||||
credentialsFile = fspath.join(settings.userDir,ffBase+"_cred"+ffExt);
|
||||
credentialsFileBackup = getBackupFilename(credentialsFile)
|
||||
|
||||
var setupProjectsPromise;
|
||||
|
||||
if (projectsEnabled) {
|
||||
return sshTools.init(settings,runtime).then(function() {
|
||||
gitTools.init(_settings, _runtime).then(function(gitConfig) {
|
||||
if (!gitConfig || /^1\./.test(gitConfig.version)) {
|
||||
if (!gitConfig) {
|
||||
projectLogMessages.push(log._("storage.localfilesystem.projects.git-not-found"))
|
||||
} else {
|
||||
projectLogMessages.push(log._("storage.localfilesystem.projects.git-version-old",{version:gitConfig.version}))
|
||||
}
|
||||
projectsEnabled = false;
|
||||
try {
|
||||
// As projects have to be turned on, we know this property
|
||||
// must exist at this point, so turn it off.
|
||||
// TODO: when on-by-default, this will need to do more
|
||||
// work to disable.
|
||||
settings.editorTheme.projects.enabled = false;
|
||||
} catch(err) {
|
||||
}
|
||||
} else {
|
||||
globalGitUser = gitConfig.user;
|
||||
Projects.init(settings,runtime);
|
||||
sshTools.init(settings,runtime);
|
||||
projectsDir = fspath.join(settings.userDir,"projects");
|
||||
if (!settings.readOnly) {
|
||||
return fs.ensureDir(projectsDir)
|
||||
//TODO: this is accessing settings from storage directly as settings
|
||||
// has not yet been initialised. That isn't ideal - can this be deferred?
|
||||
.then(storageSettings.getSettings)
|
||||
.then(function(globalSettings) {
|
||||
var saveSettings = false;
|
||||
if (!globalSettings.projects) {
|
||||
globalSettings.projects = {
|
||||
projects: {}
|
||||
}
|
||||
saveSettings = true;
|
||||
} else {
|
||||
activeProject = globalSettings.projects.activeProject;
|
||||
}
|
||||
if (settings.flowFile) {
|
||||
// if flowFile is a known project name - use it
|
||||
if (globalSettings.projects.projects.hasOwnProperty(settings.flowFile)) {
|
||||
activeProject = settings.flowFile;
|
||||
globalSettings.projects.activeProject = settings.flowFile;
|
||||
saveSettings = true;
|
||||
} else {
|
||||
// if it resolves to a dir - use it... but:
|
||||
// - where to get credsecret from?
|
||||
// - what if the name clashes with a known project?
|
||||
|
||||
// var stat = fs.statSync(settings.flowFile);
|
||||
// if (stat && stat.isDirectory()) {
|
||||
// activeProject = settings.flowFile;
|
||||
// }
|
||||
}
|
||||
}
|
||||
if (!activeProject) {
|
||||
projectLogMessages.push(log._("storage.localfilesystem.no-active-project"))
|
||||
}
|
||||
if (saveSettings) {
|
||||
return storageSettings.saveSettings(globalSettings);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function listProjects() {
|
||||
return fs.readdir(projectsDir).then(function(fns) {
|
||||
var dirs = [];
|
||||
fns.sort(function(A,B) {
|
||||
return A.toLowerCase().localeCompare(B.toLowerCase());
|
||||
}).filter(function(fn) {
|
||||
var fullPath = fspath.join(projectsDir,fn);
|
||||
if (fn[0] != ".") {
|
||||
var stats = fs.lstatSync(fullPath);
|
||||
if (stats.isDirectory()) {
|
||||
dirs.push(fn);
|
||||
}
|
||||
}
|
||||
});
|
||||
return dirs;
|
||||
});
|
||||
}
|
||||
|
||||
function getUserGitSettings(user) {
|
||||
var userSettings = settings.getUserSettings(user)||{};
|
||||
return userSettings.git;
|
||||
}
|
||||
|
||||
function getBackupFilename(filename) {
|
||||
var ffName = fspath.basename(filename);
|
||||
var ffDir = fspath.dirname(filename);
|
||||
return fspath.join(ffDir,"."+ffName+".backup");
|
||||
}
|
||||
|
||||
function loadProject(name) {
|
||||
var projectPath = name;
|
||||
if (projectPath.indexOf(fspath.sep) === -1) {
|
||||
projectPath = fspath.join(projectsDir,name);
|
||||
}
|
||||
return Projects.load(projectPath).then(function(project) {
|
||||
activeProject = project;
|
||||
flowsFullPath = project.getFlowFile();
|
||||
flowsFileBackup = project.getFlowFileBackup();
|
||||
credentialsFile = project.getCredentialsFile();
|
||||
credentialsFileBackup = project.getCredentialsFileBackup();
|
||||
return project;
|
||||
})
|
||||
}
|
||||
|
||||
function getProject(user, name) {
|
||||
checkActiveProject(name);
|
||||
//return when.resolve(activeProject.info);
|
||||
return Promise.resolve(activeProject.export());
|
||||
}
|
||||
|
||||
function deleteProject(user, name) {
|
||||
if (activeProject && activeProject.name === name) {
|
||||
var e = new Error("NLS: Can't delete the active project");
|
||||
e.code = "cannot_delete_active_project";
|
||||
throw e;
|
||||
}
|
||||
var projectPath = fspath.join(projectsDir,name);
|
||||
return Projects.delete(user, projectPath);
|
||||
}
|
||||
|
||||
function checkActiveProject(project) {
|
||||
if (!activeProject || activeProject.name !== project) {
|
||||
//TODO: throw better err
|
||||
throw new Error("Cannot operate on inactive project wanted:"+project+" current:"+(activeProject&&activeProject.name));
|
||||
}
|
||||
}
|
||||
function getFiles(user, project) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.getFiles();
|
||||
}
|
||||
function stageFile(user, project,file) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.stageFile(file);
|
||||
}
|
||||
function unstageFile(user, project,file) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.unstageFile(file);
|
||||
}
|
||||
function commit(user, project,options) {
|
||||
checkActiveProject(project);
|
||||
var isMerging = activeProject.isMerging();
|
||||
return activeProject.commit(user, options).then(function() {
|
||||
// The project was merging, now it isn't. Lets reload.
|
||||
if (isMerging && !activeProject.isMerging()) {
|
||||
return reloadActiveProject("merge-complete");
|
||||
}
|
||||
})
|
||||
}
|
||||
function getFileDiff(user, project,file,type) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.getFileDiff(file,type);
|
||||
}
|
||||
function getCommits(user, project,options) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.getCommits(options);
|
||||
}
|
||||
function getCommit(user, project,sha) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.getCommit(sha);
|
||||
}
|
||||
|
||||
function getFile(user, project,filePath,sha) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.getFile(filePath,sha);
|
||||
}
|
||||
function revertFile(user, project,filePath) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.revertFile(filePath).then(function() {
|
||||
return reloadActiveProject("revert");
|
||||
})
|
||||
}
|
||||
function push(user, project,remoteBranchName,setRemote) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.push(user,remoteBranchName,setRemote);
|
||||
}
|
||||
function pull(user, project,remoteBranchName,setRemote,allowUnrelatedHistories) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.pull(user,remoteBranchName,setRemote,allowUnrelatedHistories).then(function() {
|
||||
return reloadActiveProject("pull");
|
||||
});
|
||||
}
|
||||
function getStatus(user, project, includeRemote) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.status(user, includeRemote);
|
||||
}
|
||||
function resolveMerge(user, project,file,resolution) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.resolveMerge(file,resolution);
|
||||
}
|
||||
function abortMerge(user, project) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.abortMerge().then(function() {
|
||||
return reloadActiveProject("merge-abort")
|
||||
});
|
||||
}
|
||||
function getBranches(user, project,isRemote) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.getBranches(user, isRemote);
|
||||
}
|
||||
|
||||
function deleteBranch(user, project, branch, isRemote, force) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.deleteBranch(user, branch, isRemote, force);
|
||||
}
|
||||
|
||||
function setBranch(user, project,branchName,isCreate) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.setBranch(branchName,isCreate).then(function() {
|
||||
return reloadActiveProject("change-branch");
|
||||
});
|
||||
}
|
||||
function getBranchStatus(user, project,branchName) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.getBranchStatus(branchName);
|
||||
}
|
||||
|
||||
|
||||
function getRemotes(user, project) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.getRemotes(user);
|
||||
}
|
||||
function addRemote(user, project, options) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.addRemote(user, options.name, options);
|
||||
}
|
||||
function removeRemote(user, project, remote) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.removeRemote(user, remote);
|
||||
}
|
||||
function updateRemote(user, project, remote, body) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.updateRemote(user, remote, body);
|
||||
}
|
||||
|
||||
function getActiveProject(user) {
|
||||
return activeProject;
|
||||
}
|
||||
|
||||
function reloadActiveProject(action) {
|
||||
return runtime.nodes.stopFlows().then(function() {
|
||||
return runtime.nodes.loadFlows(true).then(function() {
|
||||
runtime.events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}});
|
||||
}).catch(function(err) {
|
||||
// We're committed to the project change now, so notify editors
|
||||
// that it has changed.
|
||||
runtime.events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}});
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
}
|
||||
function createProject(user, metadata) {
|
||||
// var userSettings = getUserGitSettings(user);
|
||||
if (metadata.files && metadata.migrateFiles) {
|
||||
// We expect there to be no active project in this scenario
|
||||
if (activeProject) {
|
||||
throw new Error("Cannot migrate as there is an active project");
|
||||
}
|
||||
var currentEncryptionKey = settings.get('credentialSecret');
|
||||
if (currentEncryptionKey === undefined) {
|
||||
currentEncryptionKey = settings.get('_credentialSecret');
|
||||
}
|
||||
if (!metadata.hasOwnProperty('credentialSecret')) {
|
||||
metadata.credentialSecret = currentEncryptionKey;
|
||||
}
|
||||
if (!metadata.files.flow) {
|
||||
metadata.files.flow = fspath.basename(flowsFullPath);
|
||||
}
|
||||
if (!metadata.files.credentials) {
|
||||
metadata.files.credentials = fspath.basename(credentialsFile);
|
||||
}
|
||||
|
||||
metadata.files.oldFlow = flowsFullPath;
|
||||
metadata.files.oldCredentials = credentialsFile;
|
||||
metadata.files.credentialSecret = currentEncryptionKey;
|
||||
}
|
||||
metadata.path = fspath.join(projectsDir,metadata.name);
|
||||
return Projects.create(user, metadata).then(function(p) {
|
||||
return setActiveProject(user, p.name);
|
||||
}).then(function() {
|
||||
return getProject(user, metadata.name);
|
||||
})
|
||||
}
|
||||
function setActiveProject(user, projectName) {
|
||||
return loadProject(projectName).then(function(project) {
|
||||
var globalProjectSettings = settings.get("projects");
|
||||
globalProjectSettings.activeProject = project.name;
|
||||
return settings.set("projects",globalProjectSettings).then(function() {
|
||||
log.info(log._("storage.localfilesystem.projects.changing-project",{project:(activeProject&&activeProject.name)||"none"}));
|
||||
log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath}));
|
||||
// console.log("Updated file targets to");
|
||||
// console.log(flowsFullPath)
|
||||
// console.log(credentialsFile)
|
||||
return reloadActiveProject("loaded");
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function initialiseProject(user, project, data) {
|
||||
if (!activeProject || activeProject.name !== project) {
|
||||
// TODO standardise
|
||||
throw new Error("Cannot initialise inactive project");
|
||||
}
|
||||
return activeProject.initialise(user,data).then(function(result) {
|
||||
flowsFullPath = activeProject.getFlowFile();
|
||||
flowsFileBackup = activeProject.getFlowFileBackup();
|
||||
credentialsFile = activeProject.getCredentialsFile();
|
||||
credentialsFileBackup = activeProject.getCredentialsFileBackup();
|
||||
runtime.nodes.setCredentialSecret(activeProject.credentialSecret);
|
||||
return reloadActiveProject("updated");
|
||||
});
|
||||
}
|
||||
function updateProject(user, project, data) {
|
||||
if (!activeProject || activeProject.name !== project) {
|
||||
// TODO standardise
|
||||
throw new Error("Cannot update inactive project");
|
||||
}
|
||||
// In case this triggers a credential secret change
|
||||
var isReset = data.resetCredentialSecret;
|
||||
var wasInvalid = activeProject.credentialSecretInvalid;
|
||||
|
||||
return activeProject.update(user,data).then(function(result) {
|
||||
|
||||
if (result.flowFilesChanged) {
|
||||
flowsFullPath = activeProject.getFlowFile();
|
||||
flowsFileBackup = activeProject.getFlowFileBackup();
|
||||
credentialsFile = activeProject.getCredentialsFile();
|
||||
credentialsFileBackup = activeProject.getCredentialsFileBackup();
|
||||
return reloadActiveProject("updated");
|
||||
} else if (result.credentialSecretChanged) {
|
||||
if (isReset || !wasInvalid) {
|
||||
if (isReset) {
|
||||
runtime.nodes.clearCredentials();
|
||||
}
|
||||
runtime.nodes.setCredentialSecret(activeProject.credentialSecret);
|
||||
return runtime.nodes.exportCredentials()
|
||||
.then(runtime.storage.saveCredentials)
|
||||
.then(function() {
|
||||
if (wasInvalid) {
|
||||
return reloadActiveProject("updated");
|
||||
}
|
||||
});
|
||||
} else if (wasInvalid) {
|
||||
return reloadActiveProject("updated");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function setCredentialSecret(data) { //existingSecret,secret) {
|
||||
var isReset = data.resetCredentialSecret;
|
||||
var wasInvalid = activeProject.credentialSecretInvalid;
|
||||
return activeProject.update(data).then(function() {
|
||||
if (isReset || !wasInvalid) {
|
||||
if (isReset) {
|
||||
runtime.nodes.clearCredentials();
|
||||
}
|
||||
runtime.nodes.setCredentialSecret(activeProject.credentialSecret);
|
||||
return runtime.nodes.exportCredentials()
|
||||
.then(runtime.storage.saveCredentials)
|
||||
.then(function() {
|
||||
if (wasInvalid) {
|
||||
return reloadActiveProject("updated");
|
||||
}
|
||||
});
|
||||
} else if (wasInvalid) {
|
||||
return reloadActiveProject("updated");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
var initialFlowLoadComplete = false;
|
||||
|
||||
var flowsFile;
|
||||
var flowsFullPath;
|
||||
var flowsFileExists = false;
|
||||
var flowsFileBackup;
|
||||
var credentialsFile;
|
||||
var credentialsFileBackup;
|
||||
|
||||
function getFlows() {
|
||||
if (!initialFlowLoadComplete) {
|
||||
initialFlowLoadComplete = true;
|
||||
log.info(log._("storage.localfilesystem.user-dir",{path:settings.userDir}));
|
||||
if (activeProject) {
|
||||
// At this point activeProject will be a string, so go load it and
|
||||
// swap in an instance of Project
|
||||
return loadProject(activeProject).then(function() {
|
||||
log.info(log._("storage.localfilesystem.projects.active-project",{project:activeProject.name||"none"}));
|
||||
log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath}));
|
||||
return getFlows();
|
||||
});
|
||||
} else {
|
||||
if (projectsEnabled) {
|
||||
log.warn(log._("storage.localfilesystem.projects.no-active-project"))
|
||||
} else {
|
||||
projectLogMessages.forEach(log.warn);
|
||||
}
|
||||
log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath}));
|
||||
}
|
||||
}
|
||||
if (activeProject) {
|
||||
var error;
|
||||
if (activeProject.isEmpty()) {
|
||||
log.warn("Project repository is empty");
|
||||
error = new Error("Project repository is empty");
|
||||
error.code = "project_empty";
|
||||
return when.reject(error);
|
||||
}
|
||||
if (activeProject.missingFiles && activeProject.missingFiles.indexOf('package.json') !== -1) {
|
||||
log.warn("Project missing package.json");
|
||||
error = new Error("Project missing package.json");
|
||||
error.code = "missing_package_file";
|
||||
return when.reject(error);
|
||||
}
|
||||
if (!activeProject.getFlowFile()) {
|
||||
log.warn("Project has no flow file");
|
||||
error = new Error("Project has no flow file");
|
||||
error.code = "missing_flow_file";
|
||||
return when.reject(error);
|
||||
}
|
||||
if (activeProject.isMerging()) {
|
||||
log.warn("Project has unmerged changes");
|
||||
error = new Error("Project has unmerged changes. Cannot load flows");
|
||||
error.code = "git_merge_conflict";
|
||||
return when.reject(error);
|
||||
}
|
||||
|
||||
}
|
||||
return util.readFile(flowsFullPath,flowsFileBackup,null,'flow').then(function(result) {
|
||||
if (result === null) {
|
||||
flowsFileExists = false;
|
||||
return [];
|
||||
}
|
||||
flowsFileExists = true;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
function saveFlows(flows) {
|
||||
if (settings.readOnly) {
|
||||
return when.resolve();
|
||||
}
|
||||
if (activeProject && activeProject.isMerging()) {
|
||||
var error = new Error("Project has unmerged changes. Cannot deploy new flows");
|
||||
error.code = "git_merge_conflict";
|
||||
return when.reject(error);
|
||||
}
|
||||
|
||||
flowsFileExists = true;
|
||||
|
||||
var flowData;
|
||||
|
||||
if (settings.flowFilePretty) {
|
||||
flowData = JSON.stringify(flows,null,4);
|
||||
} else {
|
||||
flowData = JSON.stringify(flows);
|
||||
}
|
||||
return util.writeFile(flowsFullPath, flowData, flowsFileBackup);
|
||||
}
|
||||
|
||||
function getCredentials() {
|
||||
return util.readFile(credentialsFile,credentialsFileBackup,{},'credentials');
|
||||
}
|
||||
|
||||
function saveCredentials(credentials) {
|
||||
if (settings.readOnly) {
|
||||
return when.resolve();
|
||||
}
|
||||
|
||||
var credentialData;
|
||||
if (settings.flowFilePretty) {
|
||||
credentialData = JSON.stringify(credentials,null,4);
|
||||
} else {
|
||||
credentialData = JSON.stringify(credentials);
|
||||
}
|
||||
return util.writeFile(credentialsFile, credentialData, credentialsFileBackup);
|
||||
}
|
||||
|
||||
function getFlowFilename() {
|
||||
if (flowsFullPath) {
|
||||
return fspath.basename(flowsFullPath);
|
||||
}
|
||||
}
|
||||
function getCredentialsFilename() {
|
||||
if (flowsFullPath) {
|
||||
return fspath.basename(credentialsFile);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
listProjects: listProjects,
|
||||
getActiveProject: getActiveProject,
|
||||
setActiveProject: setActiveProject,
|
||||
getProject: getProject,
|
||||
deleteProject: deleteProject,
|
||||
createProject: createProject,
|
||||
initialiseProject: initialiseProject,
|
||||
updateProject: updateProject,
|
||||
getFiles: getFiles,
|
||||
getFile: getFile,
|
||||
revertFile: revertFile,
|
||||
stageFile: stageFile,
|
||||
unstageFile: unstageFile,
|
||||
commit: commit,
|
||||
getFileDiff: getFileDiff,
|
||||
getCommits: getCommits,
|
||||
getCommit: getCommit,
|
||||
push: push,
|
||||
pull: pull,
|
||||
getStatus:getStatus,
|
||||
resolveMerge: resolveMerge,
|
||||
abortMerge: abortMerge,
|
||||
getBranches: getBranches,
|
||||
deleteBranch: deleteBranch,
|
||||
setBranch: setBranch,
|
||||
getBranchStatus:getBranchStatus,
|
||||
getRemotes: getRemotes,
|
||||
addRemote: addRemote,
|
||||
removeRemote: removeRemote,
|
||||
updateRemote: updateRemote,
|
||||
getFlowFilename: getFlowFilename,
|
||||
flowFileExists: function() { return flowsFileExists },
|
||||
getCredentialsFilename: getCredentialsFilename,
|
||||
getGlobalGitUser: function() { return globalGitUser },
|
||||
getFlows: getFlows,
|
||||
saveFlows: saveFlows,
|
||||
getCredentials: getCredentials,
|
||||
saveCredentials: saveCredentials,
|
||||
|
||||
ssh: sshTools
|
||||
|
||||
};
|
216
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/ssh/index.js
generated
vendored
Normal file
216
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/ssh/index.js
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
/**
|
||||
* 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 fs = require('fs-extra');
|
||||
var when = require('when');
|
||||
var fspath = require("path");
|
||||
var keygen = require("./keygen");
|
||||
|
||||
var settings;
|
||||
var runtime;
|
||||
var log;
|
||||
var sshkeyDir;
|
||||
var userSSHKeyDir;
|
||||
|
||||
function init(_settings, _runtime) {
|
||||
settings = _settings;
|
||||
runtime = _runtime;
|
||||
log = runtime.log;
|
||||
sshkeyDir = fspath.resolve(fspath.join(settings.userDir, "projects", ".sshkeys"));
|
||||
userSSHKeyDir = fspath.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH, ".ssh");
|
||||
// console.log('sshkeys.init()');
|
||||
return fs.ensureDir(sshkeyDir);
|
||||
}
|
||||
|
||||
function listSSHKeys(username) {
|
||||
return listSSHKeysInDir(sshkeyDir,username + '_').then(function(customKeys) {
|
||||
return listSSHKeysInDir(userSSHKeyDir).then(function(existingKeys) {
|
||||
existingKeys.forEach(function(k){
|
||||
k.system = true;
|
||||
customKeys.push(k);
|
||||
})
|
||||
return customKeys;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function listSSHKeysInDir(dir,startStr) {
|
||||
startStr = startStr || "";
|
||||
return fs.readdir(dir).then(function(fns) {
|
||||
var ret = fns.sort()
|
||||
.filter(function(fn) {
|
||||
var fullPath = fspath.join(dir,fn);
|
||||
if (fn.length > 2 || fn[0] != ".") {
|
||||
var stats = fs.lstatSync(fullPath);
|
||||
if (stats.isFile()) {
|
||||
return fn.startsWith(startStr);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.map(function(filename) {
|
||||
return filename.substr(startStr.length);
|
||||
})
|
||||
.reduce(function(prev, current) {
|
||||
var parsePath = fspath.parse(current);
|
||||
if ( parsePath ) {
|
||||
if ( parsePath.ext !== '.pub' ) {
|
||||
// Private Keys
|
||||
prev.keyFiles.push(parsePath.base);
|
||||
}
|
||||
else if ( parsePath.ext === '.pub' && (prev.keyFiles.some(function(elem){ return elem === parsePath.name; }))) {
|
||||
prev.privateKeyFiles.push(parsePath.name);
|
||||
}
|
||||
}
|
||||
return prev;
|
||||
}, { keyFiles: [], privateKeyFiles: [] });
|
||||
return ret.privateKeyFiles.map(function(filename) {
|
||||
return {
|
||||
name: filename
|
||||
};
|
||||
});
|
||||
}).then(function(result) {
|
||||
return result;
|
||||
}).catch(function() {
|
||||
return []
|
||||
});
|
||||
}
|
||||
|
||||
function getSSHKey(username, name) {
|
||||
return checkSSHKeyFileAndGetPublicKeyFileName(username, name)
|
||||
.then(function(publicSSHKeyPath) {
|
||||
return fs.readFile(publicSSHKeyPath, 'utf-8');
|
||||
}).catch(function() {
|
||||
var privateKeyPath = fspath.join(userSSHKeyDir,name);
|
||||
var publicKeyPath = privateKeyPath+".pub";
|
||||
return checkFilePairExist(privateKeyPath,publicKeyPath).then(function() {
|
||||
return fs.readFile(publicKeyPath, 'utf-8');
|
||||
}).catch(function() {
|
||||
return null
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function generateSSHKey(username, options) {
|
||||
options = options || {};
|
||||
var name = options.name || "";
|
||||
if (!/^[a-zA-Z0-9\-_]+$/.test(options.name)) {
|
||||
var err = new Error("Invalid SSH Key name");
|
||||
e.code = "invalid_key_name";
|
||||
return Promise.reject(err);
|
||||
}
|
||||
return checkExistSSHKeyFiles(username, name)
|
||||
.then(function(result) {
|
||||
if ( result ) {
|
||||
var e = new Error("SSH Key name exists");
|
||||
e.code = "key_exists";
|
||||
throw e;
|
||||
} else {
|
||||
var comment = options.comment || "";
|
||||
var password = options.password || "";
|
||||
var size = options.size || 2048;
|
||||
var sshKeyFileBasename = username + '_' + name;
|
||||
var privateKeyFilePath = fspath.normalize(fspath.join(sshkeyDir, sshKeyFileBasename));
|
||||
return generateSSHKeyPair(name, privateKeyFilePath, comment, password, size)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function deleteSSHKey(username, name) {
|
||||
return checkSSHKeyFileAndGetPublicKeyFileName(username, name)
|
||||
.then(function() {
|
||||
return deleteSSHKeyFiles(username, name);
|
||||
});
|
||||
}
|
||||
|
||||
function checkExistSSHKeyFiles(username, name) {
|
||||
var sshKeyFileBasename = username + '_' + name;
|
||||
var privateKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename);
|
||||
var publicKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename + '.pub');
|
||||
return checkFilePairExist(privateKeyFilePath,publicKeyFilePath)
|
||||
.then(function() {
|
||||
return true;
|
||||
})
|
||||
.catch(function() {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function checkSSHKeyFileAndGetPublicKeyFileName(username, name) {
|
||||
var sshKeyFileBasename = username + '_' + name;
|
||||
var privateKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename);
|
||||
var publicKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename + '.pub');
|
||||
return checkFilePairExist(privateKeyFilePath,publicKeyFilePath).then(function() {
|
||||
return publicKeyFilePath;
|
||||
});
|
||||
}
|
||||
|
||||
function checkFilePairExist(privateKeyFilePath,publicKeyFilePath) {
|
||||
return Promise.all([
|
||||
fs.access(privateKeyFilePath, (fs.constants || fs).R_OK),
|
||||
fs.access(publicKeyFilePath , (fs.constants || fs).R_OK)
|
||||
])
|
||||
}
|
||||
|
||||
function deleteSSHKeyFiles(username, name) {
|
||||
var sshKeyFileBasename = username + '_' + name;
|
||||
var privateKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename);
|
||||
var publicKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename + '.pub');
|
||||
return Promise.all([
|
||||
fs.remove(privateKeyFilePath),
|
||||
fs.remove(publicKeyFilePath)
|
||||
])
|
||||
}
|
||||
|
||||
function generateSSHKeyPair(name, privateKeyPath, comment, password, size) {
|
||||
log.trace("ssh-keygen["+[name,privateKeyPath,comment,size,"hasPassword?"+!!password].join(",")+"]");
|
||||
return keygen.generateKey({location: privateKeyPath, comment: comment, password: password, size: size})
|
||||
.then(function(stdout) {
|
||||
return name;
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.log('[SSHKey generation] error:', err);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
function getPrivateKeyPath(username, name) {
|
||||
var sshKeyFileBasename = username + '_' + name;
|
||||
var privateKeyFilePath = fspath.normalize(fspath.join(sshkeyDir, sshKeyFileBasename));
|
||||
try {
|
||||
fs.accessSync(privateKeyFilePath, (fs.constants || fs).R_OK);
|
||||
} catch(err) {
|
||||
privateKeyFilePath = fspath.join(userSSHKeyDir,name);
|
||||
try {
|
||||
fs.accessSync(privateKeyFilePath, (fs.constants || fs).R_OK);
|
||||
} catch(err2) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (fspath.sep === '\\') {
|
||||
privateKeyFilePath = privateKeyFilePath.replace(/\\/g,'\\\\');
|
||||
}
|
||||
return privateKeyFilePath;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
listSSHKeys: listSSHKeys,
|
||||
getSSHKey: getSSHKey,
|
||||
getPrivateKeyPath: getPrivateKeyPath,
|
||||
generateSSHKey: generateSSHKey,
|
||||
deleteSSHKey: deleteSSHKey
|
||||
};
|
95
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/ssh/keygen.js
generated
vendored
Normal file
95
packages/node_modules/@node-red/runtime/storage/localfilesystem/projects/ssh/keygen.js
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 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 child_process = require('child_process');
|
||||
|
||||
var sshkeygenCommand = "ssh-keygen";
|
||||
|
||||
var log;
|
||||
|
||||
function runSshKeygenCommand(args,cwd,env) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var child = child_process.spawn(sshkeygenCommand, args, {cwd: cwd, detached: true, env: env});
|
||||
var stdout = "";
|
||||
var stderr = "";
|
||||
|
||||
child.stdout.on('data', function(data) {
|
||||
stdout += data;
|
||||
});
|
||||
child.stderr.on('data', function(data) {
|
||||
stderr += data;
|
||||
});
|
||||
child.on('close', function(code, signal) {
|
||||
// console.log(code);
|
||||
// console.log(stdout);
|
||||
// console.log(stderr);
|
||||
if (code !== 0) {
|
||||
var err = new Error(stderr);
|
||||
err.stdout = stdout;
|
||||
err.stderr = stderr;
|
||||
if (/short/.test(stderr)) {
|
||||
err.code = "key_passphrase_too_short";
|
||||
} else if(/Key must at least be 1024 bits/.test(stderr)) {
|
||||
err.code = "key_length_too_short";
|
||||
}
|
||||
reject(err);
|
||||
}
|
||||
else {
|
||||
resolve(stdout);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function init(_settings, _runtime) {
|
||||
log = _runtime.log;
|
||||
}
|
||||
|
||||
function generateKey(options) {
|
||||
var args = ['-q', '-t', 'rsa'];
|
||||
var err;
|
||||
if (options.size) {
|
||||
if (options.size < 1024) {
|
||||
err = new Error("key_length_too_short");
|
||||
err.code = "key_length_too_short";
|
||||
throw err;
|
||||
}
|
||||
args.push('-b', options.size);
|
||||
}
|
||||
if (options.location) {
|
||||
args.push('-f', options.location);
|
||||
}
|
||||
if (options.comment) {
|
||||
args.push('-C', options.comment);
|
||||
}
|
||||
if (options.password) {
|
||||
if (options.password.length < 5) {
|
||||
err = new Error("key_passphrase_too_short");
|
||||
err.code = "key_passphrase_too_short";
|
||||
throw err;
|
||||
}
|
||||
args.push('-N', options.password||'');
|
||||
} else {
|
||||
args.push('-N', '');
|
||||
}
|
||||
|
||||
return runSshKeygenCommand(args,__dirname);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
generateKey: generateKey,
|
||||
};
|
53
packages/node_modules/@node-red/runtime/storage/localfilesystem/sessions.js
generated
vendored
Normal file
53
packages/node_modules/@node-red/runtime/storage/localfilesystem/sessions.js
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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 when = require('when');
|
||||
var fs = require('fs-extra');
|
||||
var fspath = require("path");
|
||||
|
||||
var log = require("@node-red/util").log; // TODO: separate module
|
||||
|
||||
var util = require("./util");
|
||||
|
||||
var sessionsFile;
|
||||
var settings;
|
||||
|
||||
module.exports = {
|
||||
init: function(_settings) {
|
||||
settings = _settings;
|
||||
sessionsFile = fspath.join(settings.userDir,".sessions.json");
|
||||
},
|
||||
getSessions: function() {
|
||||
return when.promise(function(resolve,reject) {
|
||||
fs.readFile(sessionsFile,'utf8',function(err,data){
|
||||
if (!err) {
|
||||
try {
|
||||
return resolve(util.parseJSON(data));
|
||||
} catch(err2) {
|
||||
log.trace("Corrupted sessions file - resetting");
|
||||
}
|
||||
}
|
||||
resolve({});
|
||||
})
|
||||
});
|
||||
},
|
||||
saveSessions: function(sessions) {
|
||||
if (settings.readOnly) {
|
||||
return when.resolve();
|
||||
}
|
||||
return util.writeFile(sessionsFile,JSON.stringify(sessions));
|
||||
}
|
||||
}
|
54
packages/node_modules/@node-red/runtime/storage/localfilesystem/settings.js
generated
vendored
Normal file
54
packages/node_modules/@node-red/runtime/storage/localfilesystem/settings.js
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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 when = require('when');
|
||||
var fs = require('fs-extra');
|
||||
var fspath = require("path");
|
||||
|
||||
var log = require("@node-red/util").log; // TODO: separate module
|
||||
var util = require("./util");
|
||||
|
||||
var globalSettingsFile;
|
||||
var globalSettingsBackup;
|
||||
var settings;
|
||||
|
||||
module.exports = {
|
||||
init: function(_settings) {
|
||||
settings = _settings;
|
||||
globalSettingsFile = fspath.join(settings.userDir,".config.json");
|
||||
globalSettingsBackup = fspath.join(settings.userDir,".config.json.backup");
|
||||
},
|
||||
getSettings: function() {
|
||||
return when.promise(function(resolve,reject) {
|
||||
fs.readFile(globalSettingsFile,'utf8',function(err,data) {
|
||||
if (!err) {
|
||||
try {
|
||||
return resolve(util.parseJSON(data));
|
||||
} catch(err2) {
|
||||
log.trace("Corrupted config detected - resetting");
|
||||
}
|
||||
}
|
||||
return resolve({});
|
||||
})
|
||||
})
|
||||
},
|
||||
saveSettings: function(newSettings) {
|
||||
if (settings.readOnly) {
|
||||
return when.resolve();
|
||||
}
|
||||
return util.writeFile(globalSettingsFile,JSON.stringify(newSettings,null,1),globalSettingsBackup);
|
||||
}
|
||||
}
|
107
packages/node_modules/@node-red/runtime/storage/localfilesystem/util.js
generated
vendored
Normal file
107
packages/node_modules/@node-red/runtime/storage/localfilesystem/util.js
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* 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 fs = require('fs-extra');
|
||||
var when = require('when');
|
||||
var nodeFn = require('when/node/function');
|
||||
|
||||
var log = require("@node-red/util").log; // TODO: separate module
|
||||
|
||||
function parseJSON(data) {
|
||||
if (data.charCodeAt(0) === 0xFEFF) {
|
||||
data = data.slice(1)
|
||||
}
|
||||
return JSON.parse(data);
|
||||
}
|
||||
function readFile(path,backupPath,emptyResponse,type) {
|
||||
return when.promise(function(resolve) {
|
||||
fs.readFile(path,'utf8',function(err,data) {
|
||||
if (!err) {
|
||||
if (data.length === 0) {
|
||||
log.warn(log._("storage.localfilesystem.empty",{type:type}));
|
||||
try {
|
||||
var backupStat = fs.statSync(backupPath);
|
||||
if (backupStat.size === 0) {
|
||||
// Empty flows, empty backup - return empty flow
|
||||
return resolve(emptyResponse);
|
||||
}
|
||||
// Empty flows, restore backup
|
||||
log.warn(log._("storage.localfilesystem.restore",{path:backupPath,type:type}));
|
||||
fs.copy(backupPath,path,function(backupCopyErr) {
|
||||
if (backupCopyErr) {
|
||||
// Restore backup failed
|
||||
log.warn(log._("storage.localfilesystem.restore-fail",{message:backupCopyErr.toString(),type:type}));
|
||||
resolve([]);
|
||||
} else {
|
||||
// Loop back in to load the restored backup
|
||||
resolve(readFile(path,backupPath,emptyResponse,type));
|
||||
}
|
||||
});
|
||||
return;
|
||||
} catch(backupStatErr) {
|
||||
// Empty flow file, no back-up file
|
||||
return resolve(emptyResponse);
|
||||
}
|
||||
}
|
||||
try {
|
||||
return resolve(parseJSON(data));
|
||||
} catch(parseErr) {
|
||||
log.warn(log._("storage.localfilesystem.invalid",{type:type}));
|
||||
return resolve(emptyResponse);
|
||||
}
|
||||
} else {
|
||||
if (type === 'flow') {
|
||||
log.info(log._("storage.localfilesystem.create",{type:type}));
|
||||
}
|
||||
resolve(emptyResponse);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Write content to a file using UTF8 encoding.
|
||||
* This forces a fsync before completing to ensure
|
||||
* the write hits disk.
|
||||
*/
|
||||
writeFile: function(path,content,backupPath) {
|
||||
if (backupPath) {
|
||||
if (fs.existsSync(path)) {
|
||||
fs.renameSync(path,backupPath);
|
||||
}
|
||||
}
|
||||
return when.promise(function(resolve,reject) {
|
||||
var stream = fs.createWriteStream(path);
|
||||
stream.on('open',function(fd) {
|
||||
stream.write(content,'utf8',function() {
|
||||
fs.fsync(fd,function(err) {
|
||||
if (err) {
|
||||
log.warn(log._("storage.localfilesystem.fsync-fail",{path: path, message: err.toString()}));
|
||||
}
|
||||
stream.end(resolve);
|
||||
});
|
||||
});
|
||||
});
|
||||
stream.on('error',function(err) {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
},
|
||||
readFile: readFile,
|
||||
|
||||
parseJSON: parseJSON
|
||||
}
|
601
packages/node_modules/@node-red/runtime/util.js
generated
vendored
Normal file
601
packages/node_modules/@node-red/runtime/util.js
generated
vendored
Normal file
@@ -0,0 +1,601 @@
|
||||
/**
|
||||
* 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 jsonata = require("jsonata");
|
||||
var safeJSONStringify = require("json-stringify-safe");
|
||||
var util = require("util");
|
||||
|
||||
function generateId() {
|
||||
return (1+Math.random()*4294967295).toString(16);
|
||||
}
|
||||
|
||||
function ensureString(o) {
|
||||
if (Buffer.isBuffer(o)) {
|
||||
return o.toString();
|
||||
} else if (typeof o === "object") {
|
||||
return JSON.stringify(o);
|
||||
} else if (typeof o === "string") {
|
||||
return o;
|
||||
}
|
||||
return ""+o;
|
||||
}
|
||||
|
||||
function ensureBuffer(o) {
|
||||
if (Buffer.isBuffer(o)) {
|
||||
return o;
|
||||
} else if (typeof o === "object") {
|
||||
o = JSON.stringify(o);
|
||||
} else if (typeof o !== "string") {
|
||||
o = ""+o;
|
||||
}
|
||||
return new Buffer(o);
|
||||
}
|
||||
|
||||
function cloneMessage(msg) {
|
||||
// Temporary fix for #97
|
||||
// TODO: remove this http-node-specific fix somehow
|
||||
var req = msg.req;
|
||||
var res = msg.res;
|
||||
delete msg.req;
|
||||
delete msg.res;
|
||||
var m = clone(msg);
|
||||
if (req) {
|
||||
m.req = req;
|
||||
msg.req = req;
|
||||
}
|
||||
if (res) {
|
||||
m.res = res;
|
||||
msg.res = res;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
function compareObjects(obj1,obj2) {
|
||||
var i;
|
||||
if (obj1 === obj2) {
|
||||
return true;
|
||||
}
|
||||
if (obj1 == null || obj2 == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var isArray1 = Array.isArray(obj1);
|
||||
var isArray2 = Array.isArray(obj2);
|
||||
if (isArray1 != isArray2) {
|
||||
return false;
|
||||
}
|
||||
if (isArray1 && isArray2) {
|
||||
if (obj1.length !== obj2.length) {
|
||||
return false;
|
||||
}
|
||||
for (i=0;i<obj1.length;i++) {
|
||||
if (!compareObjects(obj1[i],obj2[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
var isBuffer1 = Buffer.isBuffer(obj1);
|
||||
var isBuffer2 = Buffer.isBuffer(obj2);
|
||||
if (isBuffer1 != isBuffer2) {
|
||||
return false;
|
||||
}
|
||||
if (isBuffer1 && isBuffer2) {
|
||||
if (obj1.equals) {
|
||||
// For node 0.12+ - use the native equals
|
||||
return obj1.equals(obj2);
|
||||
} else {
|
||||
if (obj1.length !== obj2.length) {
|
||||
return false;
|
||||
}
|
||||
for (i=0;i<obj1.length;i++) {
|
||||
if (obj1.readUInt8(i) !== obj2.readUInt8(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') {
|
||||
return false;
|
||||
}
|
||||
var keys1 = Object.keys(obj1);
|
||||
var keys2 = Object.keys(obj2);
|
||||
if (keys1.length != keys2.length) {
|
||||
return false;
|
||||
}
|
||||
for (var k in obj1) {
|
||||
/* istanbul ignore else */
|
||||
if (obj1.hasOwnProperty(k)) {
|
||||
if (!compareObjects(obj1[k],obj2[k])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function normalisePropertyExpression(str) {
|
||||
// This must be kept in sync with validatePropertyExpression
|
||||
// in editor/js/ui/utils.js
|
||||
|
||||
var length = str.length;
|
||||
if (length === 0) {
|
||||
throw new Error("Invalid property expression: zero-length");
|
||||
}
|
||||
var parts = [];
|
||||
var start = 0;
|
||||
var inString = false;
|
||||
var inBox = false;
|
||||
var quoteChar;
|
||||
var v;
|
||||
for (var i=0;i<length;i++) {
|
||||
var c = str[i];
|
||||
if (!inString) {
|
||||
if (c === "'" || c === '"') {
|
||||
if (i != start) {
|
||||
throw new Error("Invalid property expression: unexpected "+c+" at position "+i);
|
||||
}
|
||||
inString = true;
|
||||
quoteChar = c;
|
||||
start = i+1;
|
||||
} else if (c === '.') {
|
||||
if (i===0) {
|
||||
throw new Error("Invalid property expression: unexpected . at position 0");
|
||||
}
|
||||
if (start != i) {
|
||||
v = str.substring(start,i);
|
||||
if (/^\d+$/.test(v)) {
|
||||
parts.push(parseInt(v));
|
||||
} else {
|
||||
parts.push(v);
|
||||
}
|
||||
}
|
||||
if (i===length-1) {
|
||||
throw new Error("Invalid property expression: unterminated expression");
|
||||
}
|
||||
// Next char is first char of an identifier: a-z 0-9 $ _
|
||||
if (!/[a-z0-9\$\_]/i.test(str[i+1])) {
|
||||
throw new Error("Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
|
||||
}
|
||||
start = i+1;
|
||||
} else if (c === '[') {
|
||||
if (i === 0) {
|
||||
throw new Error("Invalid property expression: unexpected "+c+" at position "+i);
|
||||
}
|
||||
if (start != i) {
|
||||
parts.push(str.substring(start,i));
|
||||
}
|
||||
if (i===length-1) {
|
||||
throw new Error("Invalid property expression: unterminated expression");
|
||||
}
|
||||
// Next char is either a quote or a number
|
||||
if (!/["'\d]/.test(str[i+1])) {
|
||||
throw new Error("Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
|
||||
}
|
||||
start = i+1;
|
||||
inBox = true;
|
||||
} else if (c === ']') {
|
||||
if (!inBox) {
|
||||
throw new Error("Invalid property expression: unexpected "+c+" at position "+i);
|
||||
}
|
||||
if (start != i) {
|
||||
v = str.substring(start,i);
|
||||
if (/^\d+$/.test(v)) {
|
||||
parts.push(parseInt(v));
|
||||
} else {
|
||||
throw new Error("Invalid property expression: unexpected array expression at position "+start);
|
||||
}
|
||||
}
|
||||
start = i+1;
|
||||
inBox = false;
|
||||
} else if (c === ' ') {
|
||||
throw new Error("Invalid property expression: unexpected ' ' at position "+i);
|
||||
}
|
||||
} else {
|
||||
if (c === quoteChar) {
|
||||
if (i-start === 0) {
|
||||
throw new Error("Invalid property expression: zero-length string at position "+start);
|
||||
}
|
||||
parts.push(str.substring(start,i));
|
||||
// If inBox, next char must be a ]. Otherwise it may be [ or .
|
||||
if (inBox && !/\]/.test(str[i+1])) {
|
||||
throw new Error("Invalid property expression: unexpected array expression at position "+start);
|
||||
} else if (!inBox && i+1!==length && !/[\[\.]/.test(str[i+1])) {
|
||||
throw new Error("Invalid property expression: unexpected "+str[i+1]+" expression at position "+(i+1));
|
||||
}
|
||||
start = i+1;
|
||||
inString = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (inBox || inString) {
|
||||
throw new Error("Invalid property expression: unterminated expression");
|
||||
}
|
||||
if (start < length) {
|
||||
parts.push(str.substring(start));
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
function getMessageProperty(msg,expr) {
|
||||
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) {
|
||||
result = (typeof obj[key] !== "undefined" ? obj[key] : undefined);
|
||||
return result;
|
||||
}, msg);
|
||||
return result;
|
||||
}
|
||||
|
||||
function setMessageProperty(msg,prop,value,createMissing) {
|
||||
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;
|
||||
var obj = msg;
|
||||
var key;
|
||||
for (var i=0;i<length-1;i++) {
|
||||
key = msgPropParts[i];
|
||||
if (typeof key === 'string' || (typeof key === 'number' && !Array.isArray(obj))) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
obj = obj[key];
|
||||
} else if (createMissing) {
|
||||
if (typeof msgPropParts[i+1] === 'string') {
|
||||
obj[key] = {};
|
||||
} else {
|
||||
obj[key] = [];
|
||||
}
|
||||
obj = obj[key];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else if (typeof key === 'number') {
|
||||
// obj is an array
|
||||
if (obj[key] === undefined) {
|
||||
if (createMissing) {
|
||||
if (typeof msgPropParts[i+1] === 'string') {
|
||||
obj[key] = {};
|
||||
} else {
|
||||
obj[key] = [];
|
||||
}
|
||||
obj = obj[key];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
obj = obj[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
key = msgPropParts[length-1];
|
||||
if (typeof value === "undefined") {
|
||||
if (typeof key === 'number' && Array.isArray(obj)) {
|
||||
obj.splice(key,1);
|
||||
} else {
|
||||
delete obj[key]
|
||||
}
|
||||
} else {
|
||||
obj[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
function evaluateEnvProperty(value) {
|
||||
if (/^\${[^}]+}$/.test(value)) {
|
||||
// ${ENV_VAR}
|
||||
value = value.substring(2,value.length-1);
|
||||
value = process.env.hasOwnProperty(value)?process.env[value]:""
|
||||
} else if (!/\${\S+}/.test(value)) {
|
||||
// ENV_VAR
|
||||
value = process.env.hasOwnProperty(value)?process.env[value]:""
|
||||
} else {
|
||||
// FOO${ENV_VAR}BAR
|
||||
value = value.replace(/\${([^}]+)}/g, function(match, v) {
|
||||
return process.env.hasOwnProperty(v)?process.env[v]:""
|
||||
});
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
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') {
|
||||
result = ""+value;
|
||||
} else if (type === 'num') {
|
||||
result = Number(value);
|
||||
} else if (type === 'json') {
|
||||
result = JSON.parse(value);
|
||||
} else if (type === 're') {
|
||||
result = new RegExp(value);
|
||||
} else if (type === 'date') {
|
||||
result = Date.now();
|
||||
} else if (type === 'bin') {
|
||||
var data = JSON.parse(value);
|
||||
result = Buffer.from(data);
|
||||
} else if (type === 'msg' && msg) {
|
||||
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') {
|
||||
result = /^true$/i.test(value);
|
||||
} else if (type === 'jsonata') {
|
||||
var expr = prepareJSONataExpression(value,node);
|
||||
result = evaluateJSONataExpression(expr,msg);
|
||||
} else if (type === 'env') {
|
||||
result = evaluateEnvProperty(value);
|
||||
}
|
||||
if (callback) {
|
||||
callback(null,result);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
function prepareJSONataExpression(value,node) {
|
||||
var expr = jsonata(value);
|
||||
expr.assign('flowContext',function(val) {
|
||||
return node.context().flow.get(val);
|
||||
});
|
||||
expr.assign('globalContext',function(val) {
|
||||
return node.context().global.get(val);
|
||||
});
|
||||
expr.assign('env', function(val) {
|
||||
return process.env[val];
|
||||
})
|
||||
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,callback) {
|
||||
var context = msg;
|
||||
if (expr._legacyMode) {
|
||||
context = {msg:msg};
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
function normaliseNodeTypeName(name) {
|
||||
var result = name.replace(/[^a-zA-Z0-9]/g, " ");
|
||||
result = result.trim();
|
||||
result = result.replace(/ +/g, " ");
|
||||
result = result.replace(/ ./g,
|
||||
function(s) {
|
||||
return s.charAt(1).toUpperCase();
|
||||
}
|
||||
);
|
||||
result = result.charAt(0).toLowerCase() + result.slice(1);
|
||||
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,
|
||||
compareObjects: compareObjects,
|
||||
generateId: generateId,
|
||||
getMessageProperty: getMessageProperty,
|
||||
setMessageProperty: setMessageProperty,
|
||||
getObjectProperty: getObjectProperty,
|
||||
setObjectProperty: setObjectProperty,
|
||||
evaluateNodeProperty: evaluateNodeProperty,
|
||||
normalisePropertyExpression: normalisePropertyExpression,
|
||||
normaliseNodeTypeName: normaliseNodeTypeName,
|
||||
prepareJSONataExpression: prepareJSONataExpression,
|
||||
evaluateJSONataExpression: evaluateJSONataExpression,
|
||||
parseContextStore: parseContextStore
|
||||
};
|
Reference in New Issue
Block a user