2015-10-11 21:37:11 +02:00
|
|
|
/**
|
2017-01-11 16:24:33 +01:00
|
|
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
2015-10-11 21:37:11 +02:00
|
|
|
*
|
|
|
|
* 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');
|
|
|
|
|
2018-08-04 23:23:06 +02:00
|
|
|
var typeRegistry = require("@node-red/registry");
|
2018-04-26 13:32:05 +02:00
|
|
|
var deprecated = typeRegistry.deprecated;
|
|
|
|
|
|
|
|
|
2015-12-29 23:16:51 +01:00
|
|
|
var context = require("../context")
|
2015-10-11 21:37:11 +02:00
|
|
|
var credentials = require("../credentials");
|
|
|
|
|
|
|
|
var flowUtil = require("./util");
|
2018-04-23 15:24:51 +02:00
|
|
|
var log;
|
2015-10-11 21:37:11 +02:00
|
|
|
var events = require("../../events");
|
2018-08-17 23:10:54 +02:00
|
|
|
var redUtil = require("@node-red/util").util;
|
2015-10-11 21:37:11 +02:00
|
|
|
|
|
|
|
var storage = null;
|
|
|
|
var settings = null;
|
|
|
|
|
|
|
|
var activeConfig = null;
|
|
|
|
var activeFlowConfig = null;
|
|
|
|
|
|
|
|
var activeFlows = {};
|
|
|
|
var started = false;
|
2018-04-15 12:51:26 +02:00
|
|
|
var credentialsPendingReset = false;
|
2015-10-11 21:37:11 +02:00
|
|
|
|
|
|
|
var activeNodesToFlow = {};
|
|
|
|
|
2015-11-02 16:38:16 +01:00
|
|
|
var typeEventRegistered = false;
|
|
|
|
|
2016-09-21 11:22:04 +02:00
|
|
|
function init(runtime) {
|
2015-11-02 16:38:16 +01:00
|
|
|
if (started) {
|
|
|
|
throw new Error("Cannot init without a stop");
|
|
|
|
}
|
2016-09-21 11:22:04 +02:00
|
|
|
settings = runtime.settings;
|
|
|
|
storage = runtime.storage;
|
2018-04-23 15:24:51 +02:00
|
|
|
log = runtime.log;
|
2015-10-11 21:37:11 +02:00
|
|
|
started = false;
|
2015-11-02 16:38:16 +01:00
|
|
|
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) {
|
2017-07-08 18:27:45 +02:00
|
|
|
events.emit("runtime-event",{id:"runtime-state",retain: true});
|
2015-11-02 16:38:16 +01:00
|
|
|
start();
|
|
|
|
}
|
2015-10-11 21:37:11 +02:00
|
|
|
}
|
|
|
|
}
|
2015-11-02 16:38:16 +01:00
|
|
|
});
|
|
|
|
typeEventRegistered = true;
|
|
|
|
}
|
2018-04-23 15:24:51 +02:00
|
|
|
Flow.init(runtime);
|
2019-03-07 23:54:20 +01:00
|
|
|
flowUtil.init(runtime);
|
2015-10-11 21:37:11 +02:00
|
|
|
}
|
2016-10-09 23:02:24 +02:00
|
|
|
|
|
|
|
function loadFlows() {
|
2018-04-15 12:51:26 +02:00
|
|
|
var config;
|
|
|
|
return storage.getFlows().then(function(_config) {
|
|
|
|
config = _config;
|
2017-01-10 15:20:51 +01:00
|
|
|
log.debug("loaded flow revision: "+config.rev);
|
2016-09-21 22:58:50 +02:00
|
|
|
return credentials.load(config.credentials).then(function() {
|
2017-09-20 11:30:07 +02:00
|
|
|
events.emit("runtime-event",{id:"runtime-state",retain:true});
|
2016-10-09 23:02:24 +02:00
|
|
|
return config;
|
2015-10-11 21:37:11 +02:00
|
|
|
});
|
2017-10-17 00:23:50 +02:00
|
|
|
}).catch(function(err) {
|
2018-04-15 12:51:26 +02:00
|
|
|
if (err.code === "credentials_load_failed" && !storage.projects) {
|
|
|
|
// project disabled, credential load failed
|
|
|
|
credentialsPendingReset = true;
|
2018-01-24 22:54:18 +01:00
|
|
|
log.warn(log._("nodes.flows.error",{message:err.toString()}));
|
2018-04-15 12:51:26 +02:00
|
|
|
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;
|
2018-01-24 22:54:18 +01:00
|
|
|
}
|
2015-10-11 21:37:11 +02:00
|
|
|
});
|
|
|
|
}
|
2017-09-20 11:30:07 +02:00
|
|
|
function load(forceStart) {
|
2018-09-25 12:20:50 +02:00
|
|
|
if (forceStart && settings.safeMode) {
|
|
|
|
// This is a force reload from the API - disable safeMode
|
|
|
|
delete settings.safeMode;
|
|
|
|
}
|
2020-02-13 17:44:48 +01:00
|
|
|
return setFlows(null,null,"load",false,forceStart);
|
2016-10-09 23:02:24 +02:00
|
|
|
}
|
2015-10-11 21:37:11 +02:00
|
|
|
|
2016-10-09 23:02:24 +02:00
|
|
|
/*
|
|
|
|
* _config - new node array configuration
|
2020-02-13 17:44:48 +01:00
|
|
|
* _credentials - new credentials configuration (optional)
|
2016-10-09 23:02:24 +02:00
|
|
|
* type - full/nodes/flows/load (default full)
|
|
|
|
* muteLog - don't emit the standard log messages (used for individual flow api)
|
|
|
|
*/
|
2020-02-13 17:44:48 +01:00
|
|
|
function setFlows(_config,_credentials,type,muteLog,forceStart) {
|
|
|
|
if (typeof _credentials === "string") {
|
|
|
|
type = _credentials;
|
|
|
|
_credentials = null;
|
|
|
|
}
|
2015-10-11 21:37:11 +02:00
|
|
|
type = type||"full";
|
2018-09-25 12:20:50 +02:00
|
|
|
if (settings.safeMode) {
|
|
|
|
if (type !== "load") {
|
|
|
|
// If in safeMode, the flows are stopped. We cannot do a modified nodes/flows
|
|
|
|
// type deploy as nothing is running. Can only do a "load" or "full" deploy.
|
|
|
|
// The "load" case is already handled in `load()` to distinguish between
|
|
|
|
// startup-load and api-request-load.
|
|
|
|
type = "full";
|
|
|
|
delete settings.safeMode;
|
|
|
|
}
|
|
|
|
}
|
2015-10-11 21:37:11 +02:00
|
|
|
|
|
|
|
var configSavePromise = null;
|
2016-10-09 23:02:24 +02:00
|
|
|
var config = null;
|
2015-11-02 16:38:16 +01:00
|
|
|
var diff;
|
2016-10-09 23:02:24 +02:00
|
|
|
var newFlowConfig;
|
2017-01-10 15:20:51 +01:00
|
|
|
var isLoad = false;
|
2016-10-09 23:02:24 +02:00
|
|
|
if (type === "load") {
|
2017-01-10 15:20:51 +01:00
|
|
|
isLoad = true;
|
2016-10-09 23:02:24 +02:00
|
|
|
configSavePromise = loadFlows().then(function(_config) {
|
|
|
|
config = clone(_config.flows);
|
|
|
|
newFlowConfig = flowUtil.parseConfig(clone(config));
|
|
|
|
type = "full";
|
|
|
|
return _config.rev;
|
|
|
|
});
|
2015-10-11 21:37:11 +02:00
|
|
|
} else {
|
2019-01-11 15:53:21 +01:00
|
|
|
// Clone the provided config so it can be manipulated
|
2016-10-09 23:02:24 +02:00
|
|
|
config = clone(_config);
|
2019-01-11 15:53:21 +01:00
|
|
|
// Parse the configuration
|
2016-10-09 23:02:24 +02:00
|
|
|
newFlowConfig = flowUtil.parseConfig(clone(config));
|
2019-01-11 15:53:21 +01:00
|
|
|
// Generate a diff to identify what has changed
|
2017-04-22 00:36:21 +02:00
|
|
|
diff = flowUtil.diffConfigs(activeFlowConfig,newFlowConfig);
|
2018-04-16 16:36:23 +02:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
2020-02-13 17:44:48 +01:00
|
|
|
var credsDirty;
|
|
|
|
|
|
|
|
if (_credentials) {
|
|
|
|
// A full set of credentials have been provided. Use those instead
|
|
|
|
configSavePromise = credentials.load(_credentials);
|
|
|
|
credsDirty = true;
|
|
|
|
} else {
|
|
|
|
// Allow the credential store to remove anything no longer needed
|
|
|
|
credentials.clean(config);
|
2018-04-16 16:36:23 +02:00
|
|
|
|
2020-02-13 17:44:48 +01:00
|
|
|
// Remember whether credentials need saving or not
|
|
|
|
var credsDirty = credentials.dirty();
|
2019-01-11 15:53:21 +01:00
|
|
|
|
2020-02-13 17:44:48 +01:00
|
|
|
configSavePromise = Promise.resolve();
|
|
|
|
}
|
2019-01-11 15:53:21 +01:00
|
|
|
|
|
|
|
// Get the latest credentials and ask storage to save them (if needed)
|
|
|
|
// as well as the new flow configuration.
|
2020-02-13 17:44:48 +01:00
|
|
|
configSavePromise = configSavePromise.then(function() {
|
|
|
|
return credentials.export()
|
|
|
|
}).then(function(creds) {
|
2016-09-21 22:58:50 +02:00
|
|
|
var saveConfig = {
|
|
|
|
flows: config,
|
|
|
|
credentialsDirty:credsDirty,
|
|
|
|
credentials: creds
|
|
|
|
}
|
2016-10-09 23:02:24 +02:00
|
|
|
return storage.saveFlows(saveConfig);
|
2015-10-11 21:37:11 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return configSavePromise
|
2016-10-09 23:02:24 +02:00
|
|
|
.then(function(flowRevision) {
|
2017-01-10 15:20:51 +01:00
|
|
|
if (!isLoad) {
|
|
|
|
log.debug("saved flow revision: "+flowRevision);
|
|
|
|
}
|
2016-10-09 23:02:24 +02:00
|
|
|
activeConfig = {
|
|
|
|
flows:config,
|
|
|
|
rev:flowRevision
|
|
|
|
};
|
2015-11-02 16:38:16 +01:00
|
|
|
activeFlowConfig = newFlowConfig;
|
2017-09-20 11:30:07 +02:00
|
|
|
if (forceStart || started) {
|
2019-01-11 15:53:21 +01:00
|
|
|
// Flows are running (or should be)
|
|
|
|
|
|
|
|
// Stop the active flows (according to deploy type and the diff)
|
|
|
|
return stop(type,diff,muteLog).then(() => {
|
|
|
|
// Once stopped, allow context to remove anything no longer needed
|
|
|
|
return context.clean(activeFlowConfig)
|
|
|
|
}).then(() => {
|
|
|
|
// Start the active flows
|
|
|
|
start(type,diff,muteLog).then(() => {
|
|
|
|
events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true});
|
2017-03-17 22:29:03 +01:00
|
|
|
});
|
2019-01-11 15:53:21 +01:00
|
|
|
// Return the new revision asynchronously to the actual start
|
|
|
|
return flowRevision;
|
|
|
|
}).catch(function(err) { })
|
2017-03-17 22:29:03 +01:00
|
|
|
} else {
|
2017-07-08 18:27:45 +02:00
|
|
|
events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true});
|
2016-09-21 22:58:50 +02:00
|
|
|
}
|
2015-10-11 21:37:11 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getNode(id) {
|
|
|
|
var node;
|
2016-03-16 16:37:44 +01:00
|
|
|
if (activeNodesToFlow[id] && activeFlows[activeNodesToFlow[id]]) {
|
2019-01-29 22:49:20 +01:00
|
|
|
return activeFlows[activeNodesToFlow[id]].getNode(id,true);
|
2015-10-11 21:37:11 +02:00
|
|
|
}
|
|
|
|
for (var flowId in activeFlows) {
|
|
|
|
if (activeFlows.hasOwnProperty(flowId)) {
|
2019-01-29 22:49:20 +01:00
|
|
|
node = activeFlows[flowId].getNode(id,true);
|
2015-10-11 21:37:11 +02:00
|
|
|
if (node) {
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function eachNode(cb) {
|
2015-11-02 16:38:16 +01:00
|
|
|
for (var id in activeFlowConfig.allNodes) {
|
|
|
|
if (activeFlowConfig.allNodes.hasOwnProperty(id)) {
|
|
|
|
cb(activeFlowConfig.allNodes[id]);
|
2015-10-11 21:37:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-09 23:02:24 +02:00
|
|
|
function getFlows() {
|
2015-10-11 21:37:11 +02:00
|
|
|
return activeConfig;
|
|
|
|
}
|
|
|
|
|
2015-12-10 18:02:09 +01:00
|
|
|
function start(type,diff,muteLog) {
|
2015-10-11 21:37:11 +02:00
|
|
|
type = type||"full";
|
|
|
|
started = true;
|
|
|
|
var i;
|
2019-01-11 15:53:21 +01:00
|
|
|
// If there are missing types, report them, emit the necessary runtime event and return
|
2015-10-11 21:37:11 +02:00
|
|
|
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);
|
|
|
|
}
|
2018-01-18 23:17:48 +01:00
|
|
|
events.emit("runtime-event",{id:"runtime-state",payload:{error:"missing-types", type:"warning",text:"notification.warnings.missing-types",types:activeFlowConfig.missingTypes},retain:true});
|
2019-01-11 15:53:21 +01:00
|
|
|
return Promise.resolve();
|
2015-10-11 21:37:11 +02:00
|
|
|
}
|
2019-01-11 15:53:21 +01:00
|
|
|
|
|
|
|
// In safe mode, don't actually start anything, emit the necessary runtime event and return
|
2018-09-25 12:20:50 +02:00
|
|
|
if (settings.safeMode) {
|
|
|
|
log.info("*****************************************************************")
|
|
|
|
log.info(log._("nodes.flows.safe-mode"));
|
|
|
|
log.info("*****************************************************************")
|
|
|
|
events.emit("runtime-event",{id:"runtime-state",payload:{error:"safe-mode", type:"warning",text:"notification.warnings.safe-mode"},retain:true});
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
2019-01-11 15:53:21 +01:00
|
|
|
|
2015-12-10 18:02:09 +01:00
|
|
|
if (!muteLog) {
|
2017-04-22 00:36:21 +02:00
|
|
|
if (type !== "full") {
|
2015-12-10 18:02:09 +01:00
|
|
|
log.info(log._("nodes.flows.starting-modified-"+type));
|
|
|
|
} else {
|
|
|
|
log.info(log._("nodes.flows.starting-flows"));
|
|
|
|
}
|
2015-10-11 21:37:11 +02:00
|
|
|
}
|
2019-01-11 15:53:21 +01:00
|
|
|
|
2015-10-11 21:37:11 +02:00
|
|
|
var id;
|
2017-04-22 00:36:21 +02:00
|
|
|
if (type === "full") {
|
2019-01-11 15:53:21 +01:00
|
|
|
// A full start means everything should
|
|
|
|
|
|
|
|
// Check the 'global' flow is running
|
2015-12-09 22:51:46 +01:00
|
|
|
if (!activeFlows['global']) {
|
2017-03-06 16:23:31 +01:00
|
|
|
log.debug("red/nodes/flows.start : starting flow : global");
|
2019-01-16 17:27:19 +01:00
|
|
|
activeFlows['global'] = Flow.create(flowAPI,activeFlowConfig);
|
2015-12-09 22:51:46 +01:00
|
|
|
}
|
2019-01-11 15:53:21 +01:00
|
|
|
|
|
|
|
// Check each flow in the active configuration
|
2015-10-11 21:37:11 +02:00
|
|
|
for (id in activeFlowConfig.flows) {
|
|
|
|
if (activeFlowConfig.flows.hasOwnProperty(id)) {
|
2017-03-06 16:23:31 +01:00
|
|
|
if (!activeFlowConfig.flows[id].disabled && !activeFlows[id]) {
|
2019-01-11 15:53:21 +01:00
|
|
|
// This flow is not disabled, nor is it currently active, so create it
|
2019-01-16 17:27:19 +01:00
|
|
|
activeFlows[id] = Flow.create(flowAPI,activeFlowConfig,activeFlowConfig.flows[id]);
|
2017-03-06 16:23:31 +01:00
|
|
|
log.debug("red/nodes/flows.start : starting flow : "+id);
|
|
|
|
} else {
|
|
|
|
log.debug("red/nodes/flows.start : not starting disabled flow : "+id);
|
2015-12-09 22:51:46 +01:00
|
|
|
}
|
2015-10-11 21:37:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2019-01-11 15:53:21 +01:00
|
|
|
// A modified-type deploy means restarting things that have changed
|
|
|
|
|
|
|
|
// Update the global flow
|
2015-12-09 22:51:46 +01:00
|
|
|
activeFlows['global'].update(activeFlowConfig,activeFlowConfig);
|
2015-10-11 21:37:11 +02:00
|
|
|
for (id in activeFlowConfig.flows) {
|
|
|
|
if (activeFlowConfig.flows.hasOwnProperty(id)) {
|
2017-03-06 16:23:31 +01:00
|
|
|
if (!activeFlowConfig.flows[id].disabled) {
|
|
|
|
if (activeFlows[id]) {
|
2019-01-11 15:53:21 +01:00
|
|
|
// This flow exists and is not disabled, so update it
|
2017-03-06 16:23:31 +01:00
|
|
|
activeFlows[id].update(activeFlowConfig,activeFlowConfig.flows[id]);
|
|
|
|
} else {
|
2019-01-11 15:53:21 +01:00
|
|
|
// This flow didn't previously exist, so create it
|
2019-01-16 17:27:19 +01:00
|
|
|
activeFlows[id] = Flow.create(flowAPI,activeFlowConfig,activeFlowConfig.flows[id]);
|
2017-03-06 16:23:31 +01:00
|
|
|
log.debug("red/nodes/flows.start : starting flow : "+id);
|
|
|
|
}
|
2015-10-11 21:37:11 +02:00
|
|
|
} else {
|
2017-03-06 16:23:31 +01:00
|
|
|
log.debug("red/nodes/flows.start : not starting disabled flow : "+id);
|
2015-10-11 21:37:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-01-11 15:53:21 +01:00
|
|
|
// Having created or updated all flows, now start them.
|
2015-10-11 21:37:11 +02:00
|
|
|
for (id in activeFlows) {
|
|
|
|
if (activeFlows.hasOwnProperty(id)) {
|
2019-01-16 17:27:19 +01:00
|
|
|
try {
|
|
|
|
activeFlows[id].start(diff);
|
2015-11-02 21:41:59 +01:00
|
|
|
|
2019-01-16 17:27:19 +01:00
|
|
|
// Create a map of node id to flow id and also a subflowInstance lookup map
|
|
|
|
var activeNodes = activeFlows[id].getActiveNodes();
|
|
|
|
Object.keys(activeNodes).forEach(function(nid) {
|
|
|
|
activeNodesToFlow[nid] = id;
|
|
|
|
});
|
|
|
|
} catch(err) {
|
|
|
|
console.log(err.stack);
|
|
|
|
}
|
2015-10-11 21:37:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
events.emit("nodes-started");
|
2018-04-15 12:51:26 +02:00
|
|
|
|
|
|
|
if (credentialsPendingReset === true) {
|
|
|
|
credentialsPendingReset = false;
|
|
|
|
} else {
|
|
|
|
events.emit("runtime-event",{id:"runtime-state",retain:true});
|
|
|
|
}
|
2016-12-05 15:39:34 +01:00
|
|
|
|
2015-12-10 18:02:09 +01:00
|
|
|
if (!muteLog) {
|
2017-04-22 00:36:21 +02:00
|
|
|
if (type !== "full") {
|
2015-12-10 18:02:09 +01:00
|
|
|
log.info(log._("nodes.flows.started-modified-"+type));
|
|
|
|
} else {
|
|
|
|
log.info(log._("nodes.flows.started-flows"));
|
|
|
|
}
|
2015-10-11 21:37:11 +02:00
|
|
|
}
|
2019-01-11 15:53:21 +01:00
|
|
|
return Promise.resolve();
|
2015-10-11 21:37:11 +02:00
|
|
|
}
|
|
|
|
|
2015-12-10 18:02:09 +01:00
|
|
|
function stop(type,diff,muteLog) {
|
2017-09-20 11:30:07 +02:00
|
|
|
if (!started) {
|
2019-01-17 14:18:26 +01:00
|
|
|
return Promise.resolve();
|
2017-09-20 11:30:07 +02:00
|
|
|
}
|
2015-10-11 21:37:11 +02:00
|
|
|
type = type||"full";
|
2017-04-22 00:36:21 +02:00
|
|
|
diff = diff||{
|
|
|
|
added:[],
|
|
|
|
changed:[],
|
|
|
|
removed:[],
|
|
|
|
rewired:[],
|
|
|
|
linked:[]
|
|
|
|
};
|
2015-12-10 18:02:09 +01:00
|
|
|
if (!muteLog) {
|
2017-04-22 00:36:21 +02:00
|
|
|
if (type !== "full") {
|
2015-12-10 18:02:09 +01:00
|
|
|
log.info(log._("nodes.flows.stopping-modified-"+type));
|
|
|
|
} else {
|
|
|
|
log.info(log._("nodes.flows.stopping-flows"));
|
|
|
|
}
|
2015-10-11 21:37:11 +02:00
|
|
|
}
|
|
|
|
started = false;
|
|
|
|
var promises = [];
|
|
|
|
var stopList;
|
2017-04-22 00:36:21 +02:00
|
|
|
var removedList = diff.removed;
|
2015-10-11 21:37:11 +02:00
|
|
|
if (type === 'nodes') {
|
|
|
|
stopList = diff.changed.concat(diff.removed);
|
|
|
|
} else if (type === 'flows') {
|
|
|
|
stopList = diff.changed.concat(diff.removed).concat(diff.linked);
|
|
|
|
}
|
2017-04-22 00:36:21 +02:00
|
|
|
|
2015-10-11 21:37:11 +02:00
|
|
|
for (var id in activeFlows) {
|
|
|
|
if (activeFlows.hasOwnProperty(id)) {
|
2017-03-06 16:23:31 +01:00
|
|
|
var flowStateChanged = diff && (diff.added.indexOf(id) !== -1 || diff.removed.indexOf(id) !== -1);
|
2017-07-21 12:15:40 +02:00
|
|
|
log.debug("red/nodes/flows.stop : stopping flow : "+id);
|
2019-02-27 21:56:58 +01:00
|
|
|
promises.push(activeFlows[id].stop(flowStateChanged?null:stopList,removedList));
|
2017-04-22 00:36:21 +02:00
|
|
|
if (type === "full" || flowStateChanged || diff.removed.indexOf(id)!==-1) {
|
2015-10-11 21:37:11 +02:00
|
|
|
delete activeFlows[id];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-27 21:56:58 +01:00
|
|
|
return Promise.all(promises).then(function() {
|
2019-01-17 14:18:26 +01:00
|
|
|
for (id in activeNodesToFlow) {
|
|
|
|
if (activeNodesToFlow.hasOwnProperty(id)) {
|
|
|
|
if (!activeFlows[activeNodesToFlow[id]]) {
|
2015-11-02 21:41:59 +01:00
|
|
|
delete activeNodesToFlow[id];
|
2015-12-10 18:02:09 +01:00
|
|
|
}
|
2015-10-11 21:37:11 +02:00
|
|
|
}
|
2019-01-17 14:18:26 +01:00
|
|
|
}
|
|
|
|
if (stopList) {
|
|
|
|
stopList.forEach(function(id) {
|
|
|
|
delete activeNodesToFlow[id];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (!muteLog) {
|
|
|
|
if (type !== "full") {
|
|
|
|
log.info(log._("nodes.flows.stopped-modified-"+type));
|
|
|
|
} else {
|
|
|
|
log.info(log._("nodes.flows.stopped-flows"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
events.emit("nodes-stopped");
|
2015-10-11 21:37:11 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-11-09 12:29:48 +01:00
|
|
|
function checkTypeInUse(id) {
|
|
|
|
var nodeInfo = typeRegistry.getNodeInfo(id);
|
|
|
|
if (!nodeInfo) {
|
|
|
|
throw new Error(log._("nodes.index.unrecognised-id", {id:id}));
|
|
|
|
} else {
|
|
|
|
var inUse = {};
|
2016-10-09 23:02:24 +02:00
|
|
|
var config = getFlows();
|
|
|
|
config.flows.forEach(function(n) {
|
2015-11-09 12:29:48 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-10-11 21:37:11 +02:00
|
|
|
|
2015-12-09 22:51:46 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-06 23:49:51 +01:00
|
|
|
function addFlow(flow) {
|
2015-12-10 18:02:09 +01:00
|
|
|
var i,node;
|
2016-01-14 15:57:13 +01:00
|
|
|
if (!flow.hasOwnProperty('nodes')) {
|
|
|
|
throw new Error('missing nodes property');
|
|
|
|
}
|
2015-12-09 22:51:46 +01:00
|
|
|
flow.id = redUtil.generateId();
|
2015-12-06 23:49:51 +01:00
|
|
|
|
2018-05-23 23:41:39 +02:00
|
|
|
var tabNode = {
|
2015-12-10 18:02:09 +01:00
|
|
|
type:'tab',
|
|
|
|
label:flow.label,
|
|
|
|
id:flow.id
|
2018-05-23 23:41:39 +02:00
|
|
|
}
|
|
|
|
if (flow.hasOwnProperty('info')) {
|
|
|
|
tabNode.info = flow.info;
|
|
|
|
}
|
|
|
|
if (flow.hasOwnProperty('disabled')) {
|
|
|
|
tabNode.disabled = flow.disabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
var nodes = [tabNode];
|
2015-12-10 18:02:09 +01:00
|
|
|
|
2015-12-09 22:51:46 +01:00
|
|
|
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'));
|
|
|
|
}
|
2015-12-14 11:46:54 +01:00
|
|
|
if (node.type === 'tab' || node.type === 'subflow') {
|
|
|
|
return when.reject(new Error('invalid node type: '+node.type));
|
|
|
|
}
|
2015-12-09 22:51:46 +01:00
|
|
|
node.z = flow.id;
|
2015-12-10 18:02:09 +01:00
|
|
|
nodes.push(node);
|
2015-12-09 22:51:46 +01:00
|
|
|
}
|
|
|
|
if (flow.configs) {
|
|
|
|
for (i=0;i<flow.configs.length;i++) {
|
|
|
|
node = flow.configs[i];
|
2015-12-06 23:49:51 +01:00
|
|
|
if (activeFlowConfig.allNodes[node.id]) {
|
|
|
|
// TODO nls
|
2015-12-09 22:51:46 +01:00
|
|
|
return when.reject(new Error('duplicate id'));
|
2015-12-06 23:49:51 +01:00
|
|
|
}
|
2015-12-14 11:46:54 +01:00
|
|
|
if (node.type === 'tab' || node.type === 'subflow') {
|
|
|
|
return when.reject(new Error('invalid node type: '+node.type));
|
|
|
|
}
|
2015-12-06 23:49:51 +01:00
|
|
|
node.z = flow.id;
|
2015-12-10 18:02:09 +01:00
|
|
|
nodes.push(node);
|
2015-12-06 23:49:51 +01:00
|
|
|
}
|
2015-12-09 22:51:46 +01:00
|
|
|
}
|
2016-10-09 23:02:24 +02:00
|
|
|
var newConfig = clone(activeConfig.flows);
|
2015-12-10 18:02:09 +01:00
|
|
|
newConfig = newConfig.concat(nodes);
|
2015-12-09 22:51:46 +01:00
|
|
|
|
2020-02-13 17:44:48 +01:00
|
|
|
return setFlows(newConfig,null,'flows',true).then(function() {
|
2015-12-10 18:02:09 +01:00
|
|
|
log.info(log._("nodes.flows.added-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
|
|
|
|
return flow.id;
|
2015-12-09 22:51:46 +01:00
|
|
|
});
|
2015-12-06 23:49:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function getFlow(id) {
|
2015-12-09 22:51:46 +01:00
|
|
|
var flow;
|
|
|
|
if (id === 'global') {
|
|
|
|
flow = activeFlowConfig;
|
|
|
|
} else {
|
|
|
|
flow = activeFlowConfig.flows[id];
|
|
|
|
}
|
2015-12-06 23:49:51 +01:00
|
|
|
if (!flow) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
var result = {
|
2015-12-09 22:51:46 +01:00
|
|
|
id: id
|
2015-12-06 23:49:51 +01:00
|
|
|
};
|
2015-12-09 22:51:46 +01:00
|
|
|
if (flow.label) {
|
|
|
|
result.label = flow.label;
|
|
|
|
}
|
2020-04-07 09:41:49 +02:00
|
|
|
if (flow.hasOwnProperty('disabled')) {
|
2018-05-23 23:41:39 +02:00
|
|
|
result.disabled = flow.disabled;
|
|
|
|
}
|
|
|
|
if (flow.hasOwnProperty('info')) {
|
|
|
|
result.info = flow.info;
|
|
|
|
}
|
2016-01-14 15:57:13 +01:00
|
|
|
if (id !== 'global') {
|
|
|
|
result.nodes = [];
|
|
|
|
}
|
2015-12-09 22:51:46 +01:00
|
|
|
if (flow.nodes) {
|
|
|
|
var nodeIds = Object.keys(flow.nodes);
|
|
|
|
if (nodeIds.length > 0) {
|
|
|
|
result.nodes = nodeIds.map(function(nodeId) {
|
2016-05-17 10:16:58 +02:00
|
|
|
var node = clone(flow.nodes[nodeId]);
|
|
|
|
if (node.type === 'link out') {
|
|
|
|
delete node.wires;
|
|
|
|
}
|
|
|
|
return node;
|
2015-12-09 22:51:46 +01:00
|
|
|
})
|
2015-12-06 23:49:51 +01:00
|
|
|
}
|
|
|
|
}
|
2015-12-09 22:51:46 +01:00
|
|
|
if (flow.configs) {
|
|
|
|
var configIds = Object.keys(flow.configs);
|
|
|
|
result.configs = configIds.map(function(configId) {
|
|
|
|
return clone(flow.configs[configId]);
|
|
|
|
})
|
2016-01-14 15:57:13 +01:00
|
|
|
if (result.configs.length === 0) {
|
|
|
|
delete result.configs;
|
|
|
|
}
|
2015-12-09 22:51:46 +01:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
});
|
2016-01-14 15:57:13 +01:00
|
|
|
if (result.subflows.length === 0) {
|
|
|
|
delete result.subflows;
|
|
|
|
}
|
2015-12-09 22:51:46 +01:00
|
|
|
}
|
2015-12-06 23:49:51 +01:00
|
|
|
return result;
|
|
|
|
}
|
2015-12-10 18:02:09 +01:00
|
|
|
|
2015-12-09 22:51:46 +01:00
|
|
|
function updateFlow(id,newFlow) {
|
2016-01-14 15:57:13 +01:00
|
|
|
var label = id;
|
|
|
|
if (id !== 'global') {
|
|
|
|
if (!activeFlowConfig.flows[id]) {
|
|
|
|
var e = new Error();
|
|
|
|
e.code = 404;
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
label = activeFlowConfig.flows[id].label;
|
2015-12-09 22:51:46 +01:00
|
|
|
}
|
2016-10-09 23:02:24 +02:00
|
|
|
var newConfig = clone(activeConfig.flows);
|
2016-01-14 15:57:13 +01:00
|
|
|
var nodes;
|
2015-12-09 22:51:46 +01:00
|
|
|
|
2016-01-14 15:57:13 +01:00
|
|
|
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
|
|
|
|
}
|
2018-05-23 23:41:39 +02:00
|
|
|
if (newFlow.hasOwnProperty('info')) {
|
|
|
|
tabNode.info = newFlow.info;
|
|
|
|
}
|
|
|
|
if (newFlow.hasOwnProperty('disabled')) {
|
|
|
|
tabNode.disabled = newFlow.disabled;
|
|
|
|
}
|
|
|
|
|
2016-01-14 15:57:13 +01:00
|
|
|
nodes = [tabNode].concat(newFlow.nodes||[]).concat(newFlow.configs||[]);
|
|
|
|
nodes.forEach(function(n) {
|
|
|
|
n.z = id;
|
|
|
|
});
|
2015-12-09 22:51:46 +01:00
|
|
|
}
|
|
|
|
|
2016-01-14 15:57:13 +01:00
|
|
|
newConfig = newConfig.concat(nodes);
|
2020-02-13 17:44:48 +01:00
|
|
|
return setFlows(newConfig,null,'flows',true).then(function() {
|
2016-01-14 15:57:13 +01:00
|
|
|
log.info(log._("nodes.flows.updated-flow",{label:(label?label+" ":"")+"["+id+"]"}));
|
2015-12-10 18:02:09 +01:00
|
|
|
})
|
2015-12-09 22:51:46 +01:00
|
|
|
}
|
2015-12-10 18:02:09 +01:00
|
|
|
|
2015-12-09 22:51:46 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-10-09 23:02:24 +02:00
|
|
|
var newConfig = clone(activeConfig.flows);
|
2015-12-10 18:02:09 +01:00
|
|
|
newConfig = newConfig.filter(function(node) {
|
2015-12-09 22:51:46 +01:00
|
|
|
return node.z !== id && node.id !== id;
|
|
|
|
});
|
|
|
|
|
2020-02-13 17:44:48 +01:00
|
|
|
return setFlows(newConfig,null,'flows',true).then(function() {
|
2015-12-10 18:02:09 +01:00
|
|
|
log.info(log._("nodes.flows.removed-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
|
|
|
|
});
|
2015-12-09 22:51:46 +01:00
|
|
|
}
|
2015-12-10 18:02:09 +01:00
|
|
|
|
2019-01-16 17:27:19 +01:00
|
|
|
const flowAPI = {
|
|
|
|
getNode: getNode,
|
|
|
|
handleError: () => false,
|
2019-01-16 23:38:04 +01:00
|
|
|
handleStatus: () => false,
|
2019-03-07 23:54:20 +01:00
|
|
|
getSetting: k => flowUtil.getEnvVar(k)
|
2019-01-16 17:27:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-10-11 21:37:11 +02:00
|
|
|
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
|
|
|
|
*/
|
2016-10-09 23:02:24 +02:00
|
|
|
getFlows: getFlows,
|
2015-10-11 21:37:11 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2016-10-09 23:02:24 +02:00
|
|
|
setFlows: setFlows,
|
2015-10-11 21:37:11 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Starts the current flow configuration
|
|
|
|
*/
|
|
|
|
startFlows: start,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stops the current flow configuration
|
|
|
|
* @return a promise for the stopping of the flow
|
|
|
|
*/
|
|
|
|
stopFlows: stop,
|
|
|
|
|
2015-12-06 23:49:51 +01:00
|
|
|
get started() { return started },
|
2015-10-11 21:37:11 +02:00
|
|
|
|
2019-01-16 17:27:19 +01:00
|
|
|
// handleError: handleError,
|
|
|
|
// handleStatus: handleStatus,
|
2015-11-09 12:29:48 +01:00
|
|
|
|
2015-12-06 23:49:51 +01:00
|
|
|
checkTypeInUse: checkTypeInUse,
|
|
|
|
|
|
|
|
addFlow: addFlow,
|
|
|
|
getFlow: getFlow,
|
2015-12-09 22:51:46 +01:00
|
|
|
updateFlow: updateFlow,
|
|
|
|
removeFlow: removeFlow,
|
2015-12-06 23:49:51 +01:00
|
|
|
disableFlow:null,
|
2019-07-09 00:23:33 +02:00
|
|
|
enableFlow:null,
|
|
|
|
isDeliveryModeAsync: function() {
|
|
|
|
// If settings is null, this is likely being run by unit tests
|
|
|
|
return !settings || !settings.runtimeSyncDelivery
|
|
|
|
}
|
2015-11-09 12:29:48 +01:00
|
|
|
|
2015-10-11 21:37:11 +02:00
|
|
|
};
|