mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
implement flows runtime stop/start API and UI
This commit is contained in:
parent
62cd3b2061
commit
68331fc40c
@ -142,6 +142,7 @@ module.exports = function(grunt) {
|
|||||||
"packages/node_modules/@node-red/editor-client/src/js/settings.js",
|
"packages/node_modules/@node-red/editor-client/src/js/settings.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/user.js",
|
"packages/node_modules/@node-red/editor-client/src/js/user.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/comms.js",
|
"packages/node_modules/@node-red/editor-client/src/js/comms.js",
|
||||||
|
"packages/node_modules/@node-red/editor-client/src/js/runtime.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/text/bidi.js",
|
"packages/node_modules/@node-red/editor-client/src/js/text/bidi.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/text/format.js",
|
"packages/node_modules/@node-red/editor-client/src/js/text/format.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/state.js",
|
"packages/node_modules/@node-red/editor-client/src/js/ui/state.js",
|
||||||
|
@ -68,5 +68,28 @@ module.exports = {
|
|||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
apiUtils.rejectHandler(req,res,err);
|
apiUtils.rejectHandler(req,res,err);
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
getState: function(req,res) {
|
||||||
|
const opts = {
|
||||||
|
user: req.user,
|
||||||
|
req: apiUtils.getRequestLogObject(req)
|
||||||
|
}
|
||||||
|
runtimeAPI.flows.getState(opts).then(function(result) {
|
||||||
|
res.json(result);
|
||||||
|
}).catch(function(err) {
|
||||||
|
apiUtils.rejectHandler(req,res,err);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
postState: function(req,res) {
|
||||||
|
const opts = {
|
||||||
|
user: req.user,
|
||||||
|
requestedState: req.get("Node-RED-Flow-Run-State-Change")||"",
|
||||||
|
req: apiUtils.getRequestLogObject(req)
|
||||||
|
}
|
||||||
|
runtimeAPI.flows.setState(opts).then(function(result) {
|
||||||
|
res.json(result);
|
||||||
|
}).catch(function(err) {
|
||||||
|
apiUtils.rejectHandler(req,res,err);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,12 @@ module.exports = {
|
|||||||
adminApp.get("/flows",needsPermission("flows.read"),flows.get,apiUtil.errorHandler);
|
adminApp.get("/flows",needsPermission("flows.read"),flows.get,apiUtil.errorHandler);
|
||||||
adminApp.post("/flows",needsPermission("flows.write"),flows.post,apiUtil.errorHandler);
|
adminApp.post("/flows",needsPermission("flows.write"),flows.post,apiUtil.errorHandler);
|
||||||
|
|
||||||
|
// Flows/state
|
||||||
|
adminApp.get("/flows/state", needsPermission("flows.read"), flows.getState, apiUtil.errorHandler);
|
||||||
|
if (!settings.runtimeState || settings.runtimeState.enabled !== false) {
|
||||||
|
adminApp.post("/flows/state", needsPermission("flows.write"), flows.postState, apiUtil.errorHandler);
|
||||||
|
}
|
||||||
|
|
||||||
// Flow
|
// Flow
|
||||||
adminApp.get("/flow/:id",needsPermission("flows.read"),flow.get,apiUtil.errorHandler);
|
adminApp.get("/flow/:id",needsPermission("flows.read"),flow.get,apiUtil.errorHandler);
|
||||||
adminApp.post("/flow",needsPermission("flows.write"),flow.post,apiUtil.errorHandler);
|
adminApp.post("/flow",needsPermission("flows.write"),flow.post,apiUtil.errorHandler);
|
||||||
|
4
packages/node_modules/@node-red/editor-client/src/images/start.svg
vendored
Normal file
4
packages/node_modules/@node-red/editor-client/src/images/start.svg
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="32" height="32" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path color="#000" fill="#8c101c" d="M0 0h32v32H0z"></path>
|
||||||
|
<path style="fill:#ffffff;stroke:#000000;stroke-width:0" d="M 24,16 8,24 8,8 Z" fill="none" stroke="#000" stroke-width="1.5"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 271 B |
4
packages/node_modules/@node-red/editor-client/src/images/stop.svg
vendored
Normal file
4
packages/node_modules/@node-red/editor-client/src/images/stop.svg
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="32" height="32" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path color="#000" fill="#8c101c" d="M0 0h32v32H0z"></path>
|
||||||
|
<rect style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0;" width="15" height="15" x="8" y="8.5"></rect>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 256 B |
@ -297,6 +297,10 @@ var RED = (function() {
|
|||||||
// handled below
|
// handled below
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (notificationId === "flows-run-state") {
|
||||||
|
// handled in editor-client/src/js/runtime.js
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (notificationId === "project-update") {
|
if (notificationId === "project-update") {
|
||||||
loader.start(RED._("event.loadingProject"), 0);
|
loader.start(RED._("event.loadingProject"), 0);
|
||||||
RED.nodes.clear();
|
RED.nodes.clear();
|
||||||
@ -747,6 +751,7 @@ var RED = (function() {
|
|||||||
RED.keyboard.init(buildMainMenu);
|
RED.keyboard.init(buildMainMenu);
|
||||||
|
|
||||||
RED.nodes.init();
|
RED.nodes.init();
|
||||||
|
RED.runtime.init()
|
||||||
RED.comms.connect();
|
RED.comms.connect();
|
||||||
|
|
||||||
$("#red-ui-main-container").show();
|
$("#red-ui-main-container").show();
|
||||||
|
53
packages/node_modules/@node-red/editor-client/src/js/runtime.js
vendored
Normal file
53
packages/node_modules/@node-red/editor-client/src/js/runtime.js
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
RED.runtime = (function() {
|
||||||
|
let state = ""
|
||||||
|
let settings = {ui: true, enabled: true};
|
||||||
|
const STOPPED = "stopped"
|
||||||
|
const STARTED = "started"
|
||||||
|
return {
|
||||||
|
init: function() {
|
||||||
|
// refresh the current runtime status from server
|
||||||
|
settings = RED.settings.runtimeState;
|
||||||
|
RED.runtime.requestState()
|
||||||
|
|
||||||
|
// {id:"flows-run-state", started: false, state: "stopped", retain:true}
|
||||||
|
RED.comms.subscribe("notification/flows-run-state",function(topic,msg) {
|
||||||
|
RED.events.emit("flows-run-state",msg);
|
||||||
|
RED.runtime.updateState(msg.state);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
get state() {
|
||||||
|
return state
|
||||||
|
},
|
||||||
|
get started() {
|
||||||
|
return state === STARTED
|
||||||
|
},
|
||||||
|
get states() {
|
||||||
|
return { STOPPED, STARTED }
|
||||||
|
},
|
||||||
|
updateState: function(newState) {
|
||||||
|
state = newState;
|
||||||
|
// disable pointer events on node buttons (e.g. inject/debug nodes)
|
||||||
|
$(".red-ui-flow-node-button").toggleClass("red-ui-flow-node-button-stopped", state === STOPPED)
|
||||||
|
// show/hide Start/Stop based on current state
|
||||||
|
if(!RED.settings.runtimeState || RED.settings.runtimeState.ui !== false) {
|
||||||
|
RED.menu.setVisible("deploymenu-item-runtime-stop", state === STARTED)
|
||||||
|
RED.menu.setVisible("deploymenu-item-runtime-start", state === STOPPED)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
requestState: function(callback) {
|
||||||
|
$.ajax({
|
||||||
|
headers: {
|
||||||
|
"Accept":"application/json"
|
||||||
|
},
|
||||||
|
cache: false,
|
||||||
|
url: 'flows/state',
|
||||||
|
success: function(data) {
|
||||||
|
RED.runtime.updateState(data.state)
|
||||||
|
if(callback) {
|
||||||
|
callback(data.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
@ -139,6 +139,9 @@ RED.menu = (function() {
|
|||||||
if (opt.disabled) {
|
if (opt.disabled) {
|
||||||
item.addClass("disabled");
|
item.addClass("disabled");
|
||||||
}
|
}
|
||||||
|
if (opt.visible === false) {
|
||||||
|
item.addClass("hide");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -249,6 +252,14 @@ RED.menu = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setVisible(id,state) {
|
||||||
|
if (!state) {
|
||||||
|
$("#"+id).parent().addClass("hide");
|
||||||
|
} else {
|
||||||
|
$("#"+id).parent().removeClass("hide");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function addItem(id,opt) {
|
function addItem(id,opt) {
|
||||||
var item = createMenuItem(opt);
|
var item = createMenuItem(opt);
|
||||||
if (opt !== null && opt.group) {
|
if (opt !== null && opt.group) {
|
||||||
@ -305,6 +316,7 @@ RED.menu = (function() {
|
|||||||
isSelected: isSelected,
|
isSelected: isSelected,
|
||||||
toggleSelected: toggleSelected,
|
toggleSelected: toggleSelected,
|
||||||
setDisabled: setDisabled,
|
setDisabled: setDisabled,
|
||||||
|
setVisible: setVisible,
|
||||||
addItem: addItem,
|
addItem: addItem,
|
||||||
removeItem: removeItem,
|
removeItem: removeItem,
|
||||||
setAction: setAction,
|
setAction: setAction,
|
||||||
|
@ -63,16 +63,18 @@ RED.deploy = (function() {
|
|||||||
'</a>'+
|
'</a>'+
|
||||||
'<a id="red-ui-header-button-deploy-options" class="red-ui-deploy-button" href="#"><i class="fa fa-caret-down"></i></a>'+
|
'<a id="red-ui-header-button-deploy-options" class="red-ui-deploy-button" href="#"><i class="fa fa-caret-down"></i></a>'+
|
||||||
'</span></li>').prependTo(".red-ui-header-toolbar");
|
'</span></li>').prependTo(".red-ui-header-toolbar");
|
||||||
RED.menu.init({id:"red-ui-header-button-deploy-options",
|
const mainMenuItems = [
|
||||||
options: [
|
|
||||||
{id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.svg",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
|
{id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.svg",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
|
||||||
{id:"deploymenu-item-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.svg",label:RED._("deploy.modifiedFlows"),sublabel:RED._("deploy.modifiedFlowsDesc"), onselect:function(s) {if(s){changeDeploymentType("flows")}}},
|
{id:"deploymenu-item-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.svg",label:RED._("deploy.modifiedFlows"),sublabel:RED._("deploy.modifiedFlowsDesc"), onselect:function(s) {if(s){changeDeploymentType("flows")}}},
|
||||||
{id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.svg",label:RED._("deploy.modifiedNodes"),sublabel:RED._("deploy.modifiedNodesDesc"),onselect:function(s) { if(s){changeDeploymentType("nodes")}}},
|
{id:"deploymenu-item-node",toggle:"deploy-type",icon:"red/images/deploy-nodes.svg",label:RED._("deploy.modifiedNodes"),sublabel:RED._("deploy.modifiedNodesDesc"),onselect:function(s) { if(s){changeDeploymentType("nodes")}}},
|
||||||
null,
|
null
|
||||||
{id:"deploymenu-item-reload", icon:"red/images/deploy-reload.svg",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"},
|
|
||||||
|
|
||||||
]
|
]
|
||||||
});
|
if(!RED.settings.runtimeState || RED.settings.runtimeState.ui !== false) {
|
||||||
|
mainMenuItems.push({id:"deploymenu-item-runtime-start", icon:"red/images/start.svg",label:"Start"/*RED._("deploy.startFlows")*/,sublabel:"Start Flows" /*RED._("deploy.startFlowsDesc")*/,onselect:"core:start-flows", visible:false})
|
||||||
|
mainMenuItems.push({id:"deploymenu-item-runtime-stop", icon:"red/images/stop.svg",label:"Stop"/*RED._("deploy.startFlows")*/,sublabel:"Stop Flows" /*RED._("deploy.startFlowsDesc")*/,onselect:"core:stop-flows", visible:false})
|
||||||
|
}
|
||||||
|
mainMenuItems.push({id:"deploymenu-item-reload", icon:"red/images/deploy-reload.svg",label:RED._("deploy.restartFlows"),sublabel:RED._("deploy.restartFlowsDesc"),onselect:"core:restart-flows"})
|
||||||
|
RED.menu.init({id:"red-ui-header-button-deploy-options", options: mainMenuItems });
|
||||||
} else if (type == "simple") {
|
} else if (type == "simple") {
|
||||||
var label = options.label || RED._("deploy.deploy");
|
var label = options.label || RED._("deploy.deploy");
|
||||||
var icon = 'red/images/deploy-full-o.svg';
|
var icon = 'red/images/deploy-full-o.svg';
|
||||||
@ -100,6 +102,8 @@ RED.deploy = (function() {
|
|||||||
|
|
||||||
RED.actions.add("core:deploy-flows",save);
|
RED.actions.add("core:deploy-flows",save);
|
||||||
if (type === "default") {
|
if (type === "default") {
|
||||||
|
RED.actions.add("core:stop-flows",function() { stopStartFlows("stop") });
|
||||||
|
RED.actions.add("core:start-flows",function() { stopStartFlows("start") });
|
||||||
RED.actions.add("core:restart-flows",restart);
|
RED.actions.add("core:restart-flows",restart);
|
||||||
RED.actions.add("core:set-deploy-type-to-full",function() { RED.menu.setSelected("deploymenu-item-full",true);});
|
RED.actions.add("core:set-deploy-type-to-full",function() { RED.menu.setSelected("deploymenu-item-full",true);});
|
||||||
RED.actions.add("core:set-deploy-type-to-modified-flows",function() { RED.menu.setSelected("deploymenu-item-flow",true); });
|
RED.actions.add("core:set-deploy-type-to-modified-flows",function() { RED.menu.setSelected("deploymenu-item-flow",true); });
|
||||||
@ -270,18 +274,74 @@ RED.deploy = (function() {
|
|||||||
function sanitize(html) {
|
function sanitize(html) {
|
||||||
return html.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")
|
return html.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")
|
||||||
}
|
}
|
||||||
function restart() {
|
|
||||||
var startTime = Date.now();
|
function shadeShow() {
|
||||||
$(".red-ui-deploy-button-content").css('opacity',0);
|
|
||||||
$(".red-ui-deploy-button-spinner").show();
|
|
||||||
var deployWasEnabled = !$("#red-ui-header-button-deploy").hasClass("disabled");
|
|
||||||
$("#red-ui-header-button-deploy").addClass("disabled");
|
|
||||||
deployInflight = true;
|
|
||||||
$("#red-ui-header-shade").show();
|
$("#red-ui-header-shade").show();
|
||||||
$("#red-ui-editor-shade").show();
|
$("#red-ui-editor-shade").show();
|
||||||
$("#red-ui-palette-shade").show();
|
$("#red-ui-palette-shade").show();
|
||||||
$("#red-ui-sidebar-shade").show();
|
$("#red-ui-sidebar-shade").show();
|
||||||
|
}
|
||||||
|
function shadeHide() {
|
||||||
|
$("#red-ui-header-shade").hide();
|
||||||
|
$("#red-ui-editor-shade").hide();
|
||||||
|
$("#red-ui-palette-shade").hide();
|
||||||
|
$("#red-ui-sidebar-shade").hide();
|
||||||
|
}
|
||||||
|
function deployButtonSetBusy(){
|
||||||
|
$(".red-ui-deploy-button-content").css('opacity',0);
|
||||||
|
$(".red-ui-deploy-button-spinner").show();
|
||||||
|
$("#red-ui-header-button-deploy").addClass("disabled");
|
||||||
|
}
|
||||||
|
function deployButtonClearBusy(){
|
||||||
|
$(".red-ui-deploy-button-content").css('opacity',1);
|
||||||
|
$(".red-ui-deploy-button-spinner").hide();
|
||||||
|
}
|
||||||
|
function stopStartFlows(state) {
|
||||||
|
const startTime = Date.now();
|
||||||
|
const deployWasEnabled = !$("#red-ui-header-button-deploy").hasClass("disabled");
|
||||||
|
deployInflight = true;
|
||||||
|
deployButtonSetBusy();
|
||||||
|
shadeShow();
|
||||||
|
RED.runtime.updateState(state);
|
||||||
|
$.ajax({
|
||||||
|
url:"flows/state",
|
||||||
|
type: "POST",
|
||||||
|
data: {state: state},
|
||||||
|
headers: {
|
||||||
|
"Node-RED-Flow-Run-State-Change": state
|
||||||
|
}
|
||||||
|
}).done(function(data,textStatus,xhr) {
|
||||||
|
if (deployWasEnabled) {
|
||||||
|
$("#red-ui-header-button-deploy").removeClass("disabled");
|
||||||
|
}
|
||||||
|
RED.runtime.updateState((data && data.state) || "unknown" )
|
||||||
|
RED.notify('<p>Done</p>',"success");
|
||||||
|
}).fail(function(xhr,textStatus,err) {
|
||||||
|
if (deployWasEnabled) {
|
||||||
|
$("#red-ui-header-button-deploy").removeClass("disabled");
|
||||||
|
}
|
||||||
|
if (xhr.status === 401) {
|
||||||
|
RED.notify("Not authorized" ,"error");
|
||||||
|
} else if (xhr.responseText) {
|
||||||
|
RED.notify("Operation failed: " + xhr.responseText,"error");
|
||||||
|
} else {
|
||||||
|
RED.notify("Operation failed: no response","error");
|
||||||
|
}
|
||||||
|
RED.runtime.requestState()
|
||||||
|
}).always(function() {
|
||||||
|
const delta = Math.max(0,300-(Date.now()-startTime));
|
||||||
|
setTimeout(function() {
|
||||||
|
deployButtonClearBusy();
|
||||||
|
shadeHide()
|
||||||
|
deployInflight = false;
|
||||||
|
},delta);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function restart() {
|
||||||
|
var startTime = Date.now();
|
||||||
|
var deployWasEnabled = !$("#red-ui-header-button-deploy").hasClass("disabled");
|
||||||
|
deployInflight = true;
|
||||||
|
deployButtonSetBusy();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url:"flows",
|
url:"flows",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
@ -307,15 +367,10 @@ RED.deploy = (function() {
|
|||||||
RED.notify(RED._("deploy.deployFailed",{message:RED._("deploy.errors.noResponse")}),"error");
|
RED.notify(RED._("deploy.deployFailed",{message:RED._("deploy.errors.noResponse")}),"error");
|
||||||
}
|
}
|
||||||
}).always(function() {
|
}).always(function() {
|
||||||
deployInflight = false;
|
|
||||||
var delta = Math.max(0,300-(Date.now()-startTime));
|
var delta = Math.max(0,300-(Date.now()-startTime));
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
$(".red-ui-deploy-button-content").css('opacity',1);
|
deployButtonClearBusy();
|
||||||
$(".red-ui-deploy-button-spinner").hide();
|
deployInflight = false;
|
||||||
$("#red-ui-header-shade").hide();
|
|
||||||
$("#red-ui-editor-shade").hide();
|
|
||||||
$("#red-ui-palette-shade").hide();
|
|
||||||
$("#red-ui-sidebar-shade").hide();
|
|
||||||
},delta);
|
},delta);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -450,21 +505,17 @@ RED.deploy = (function() {
|
|||||||
const nns = RED.nodes.createCompleteNodeSet();
|
const nns = RED.nodes.createCompleteNodeSet();
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
$(".red-ui-deploy-button-content").css('opacity', 0);
|
deployButtonSetBusy();
|
||||||
$(".red-ui-deploy-button-spinner").show();
|
|
||||||
$("#red-ui-header-button-deploy").addClass("disabled");
|
|
||||||
|
|
||||||
const data = { flows: nns };
|
const data = { flows: nns };
|
||||||
|
data.runtimeState = RED.runtime.state;
|
||||||
if (!force) {
|
if (data.runtimeState === RED.runtime.states.STOPPED || force) {
|
||||||
|
data._rev = RED.nodes.version();
|
||||||
|
} else {
|
||||||
data.rev = RED.nodes.version();
|
data.rev = RED.nodes.version();
|
||||||
}
|
}
|
||||||
|
|
||||||
deployInflight = true;
|
deployInflight = true;
|
||||||
$("#red-ui-header-shade").show();
|
shadeShow();
|
||||||
$("#red-ui-editor-shade").show();
|
|
||||||
$("#red-ui-palette-shade").show();
|
|
||||||
$("#red-ui-sidebar-shade").show();
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "flows",
|
url: "flows",
|
||||||
type: "POST",
|
type: "POST",
|
||||||
@ -550,15 +601,11 @@ RED.deploy = (function() {
|
|||||||
RED.notify(RED._("deploy.deployFailed", { message: RED._("deploy.errors.noResponse") }), "error");
|
RED.notify(RED._("deploy.deployFailed", { message: RED._("deploy.errors.noResponse") }), "error");
|
||||||
}
|
}
|
||||||
}).always(function () {
|
}).always(function () {
|
||||||
deployInflight = false;
|
|
||||||
const delta = Math.max(0, 300 - (Date.now() - startTime));
|
const delta = Math.max(0, 300 - (Date.now() - startTime));
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
$(".red-ui-deploy-button-content").css('opacity', 1);
|
deployInflight = false;
|
||||||
$(".red-ui-deploy-button-spinner").hide();
|
deployButtonClearBusy()
|
||||||
$("#red-ui-header-shade").hide();
|
shadeHide()
|
||||||
$("#red-ui-editor-shade").hide();
|
|
||||||
$("#red-ui-palette-shade").hide();
|
|
||||||
$("#red-ui-sidebar-shade").hide();
|
|
||||||
}, delta);
|
}, delta);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -4792,6 +4792,9 @@ RED.view = (function() {
|
|||||||
if (d._def.button) {
|
if (d._def.button) {
|
||||||
var buttonEnabled = isButtonEnabled(d);
|
var buttonEnabled = isButtonEnabled(d);
|
||||||
this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-disabled", !buttonEnabled);
|
this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-disabled", !buttonEnabled);
|
||||||
|
if(RED.runtime && Object.hasOwn(RED.runtime,'started')) {
|
||||||
|
this.__buttonGroup__.classList.toggle("red-ui-flow-node-button-stopped", !RED.runtime.started);
|
||||||
|
}
|
||||||
|
|
||||||
var x = d._def.align == "right"?d.w-6:-25;
|
var x = d._def.align == "right"?d.w-6:-25;
|
||||||
if (d._def.button.toggle && !d[d._def.button.toggle]) {
|
if (d._def.button.toggle && !d[d._def.button.toggle]) {
|
||||||
|
@ -176,6 +176,13 @@
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.red-ui-flow-node-button-stopped {
|
||||||
|
opacity: 0.4;
|
||||||
|
.red-ui-flow-node-button-button {
|
||||||
|
cursor: default;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.red-ui-flow-node-button-button {
|
.red-ui-flow-node-button-button {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -73,6 +73,10 @@ var api = module.exports = {
|
|||||||
if (deploymentType === 'reload') {
|
if (deploymentType === 'reload') {
|
||||||
apiPromise = runtime.flows.loadFlows(true);
|
apiPromise = runtime.flows.loadFlows(true);
|
||||||
} else {
|
} else {
|
||||||
|
//ensure the runtime running/stopped state matches the deploying editor. If not, then copy the _rev number to flows.rev
|
||||||
|
if(flows.hasOwnProperty('_rev') && !flows.hasOwnProperty('rev') && (flows.runtimeState !== "stopped" || runtime.flows.started)) {
|
||||||
|
flows.rev = flows._rev
|
||||||
|
}
|
||||||
if (flows.hasOwnProperty('rev')) {
|
if (flows.hasOwnProperty('rev')) {
|
||||||
var currentVersion = runtime.flows.getFlows().rev;
|
var currentVersion = runtime.flows.getFlows().rev;
|
||||||
if (currentVersion !== flows.rev) {
|
if (currentVersion !== flows.rev) {
|
||||||
@ -255,5 +259,83 @@ var api = module.exports = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sendCredentials;
|
return sendCredentials;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Gets running state of runtime flows
|
||||||
|
* @param {Object} opts
|
||||||
|
* @param {User} opts.user - the user calling the api
|
||||||
|
* @param {Object} opts.req - the request to log (optional)
|
||||||
|
* @return {{state:string, started:boolean}} - the current run state of the flows
|
||||||
|
* @memberof @node-red/runtime_flows
|
||||||
|
*/
|
||||||
|
getState: async function(opts) {
|
||||||
|
runtime.log.audit({event: "flows.getState"}, opts.req);
|
||||||
|
const result = {
|
||||||
|
state: runtime.flows.started ? "started" : "stopped",
|
||||||
|
started: !!runtime.flows.started,
|
||||||
|
rev: runtime.flows.getFlows().rev
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Sets running state of runtime flows
|
||||||
|
* @param {Object} opts
|
||||||
|
* @param {Object} opts.req - the request to log (optional)
|
||||||
|
* @param {User} opts.user - the user calling the api
|
||||||
|
* @param {string} opts.requestedState - the requested state. Valid values are "start" and "stop".
|
||||||
|
* @return {Promise<Flow>} - the active flow configuration
|
||||||
|
* @memberof @node-red/runtime_flows
|
||||||
|
*/
|
||||||
|
setState: async function(opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
const makeError = (error, errcode, statusCode) => {
|
||||||
|
const message = typeof error == "object" ? error.message : error
|
||||||
|
const err = typeof error == "object" ? error : new Error(message||"Unexpected Error")
|
||||||
|
err.status = err.status || statusCode || 400;
|
||||||
|
err.code = err.code || errcode || "unexpected_error"
|
||||||
|
runtime.log.audit({
|
||||||
|
event: "flows.setState",
|
||||||
|
state: opts.requestedState || "",
|
||||||
|
error: errcode || "unexpected_error",
|
||||||
|
message: err.code
|
||||||
|
}, opts.req);
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getState = () => {
|
||||||
|
return {
|
||||||
|
state: runtime.flows.started ? "started" : "stopped",
|
||||||
|
started: !!runtime.flows.started,
|
||||||
|
rev: runtime.flows.getFlows().rev,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(runtime.settings.runtimeState ? runtime.settings.runtimeState.enabled === false : false) {
|
||||||
|
throw (makeError("Method Not Allowed", "not_allowed", 405))
|
||||||
|
}
|
||||||
|
switch (opts.requestedState) {
|
||||||
|
case "start":
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
runtime.settings.set('flowsRunStateRequested', opts.requestedState);
|
||||||
|
} catch(err) { }
|
||||||
|
await runtime.flows.startFlows("full")
|
||||||
|
return getState()
|
||||||
|
} catch (err) {
|
||||||
|
throw (makeError(err, err.code, 500))
|
||||||
|
}
|
||||||
|
case "stop":
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
runtime.settings.set('flowsRunStateRequested', opts.requestedState);
|
||||||
|
} catch(err) { }
|
||||||
|
await runtime.flows.stopFlows("full")
|
||||||
|
return getState()
|
||||||
|
} catch (err) {
|
||||||
|
throw (makeError(err, err.code, 500))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw (makeError("Cannot set runtime state. Invalid state", "invalid_run_state", 400))
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -148,6 +148,18 @@ var api = module.exports = {
|
|||||||
enabled: (runtime.settings.diagnostics && runtime.settings.diagnostics.enabled === false) ? false : true,
|
enabled: (runtime.settings.diagnostics && runtime.settings.diagnostics.enabled === false) ? false : true,
|
||||||
ui: (runtime.settings.diagnostics && runtime.settings.diagnostics.ui === false) ? false : true
|
ui: (runtime.settings.diagnostics && runtime.settings.diagnostics.ui === false) ? false : true
|
||||||
}
|
}
|
||||||
|
if(safeSettings.diagnostics.enabled === false) {
|
||||||
|
safeSettings.diagnostics.ui = false; // cannot have UI without endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
safeSettings.runtimeState = {
|
||||||
|
//unless runtimeState.ui and runtimeState.enabled are explicitly false, they will default to true.
|
||||||
|
enabled: (runtime.settings.runtimeState && runtime.settings.runtimeState.enabled === false) ? false : true,
|
||||||
|
ui: (runtime.settings.runtimeState && runtime.settings.runtimeState.ui === false) ? false : true
|
||||||
|
}
|
||||||
|
if(safeSettings.runtimeState.enabled === false) {
|
||||||
|
safeSettings.runtimeState.ui = false; // cannot have UI without endpoint
|
||||||
|
}
|
||||||
|
|
||||||
runtime.settings.exportNodeSettings(safeSettings);
|
runtime.settings.exportNodeSettings(safeSettings);
|
||||||
runtime.plugins.exportPluginSettings(safeSettings);
|
runtime.plugins.exportPluginSettings(safeSettings);
|
||||||
|
@ -261,6 +261,7 @@ function getFlows() {
|
|||||||
|
|
||||||
async function start(type,diff,muteLog) {
|
async function start(type,diff,muteLog) {
|
||||||
type = type||"full";
|
type = type||"full";
|
||||||
|
let reallyStarted = started
|
||||||
started = true;
|
started = true;
|
||||||
var i;
|
var i;
|
||||||
// If there are missing types, report them, emit the necessary runtime event and return
|
// If there are missing types, report them, emit the necessary runtime event and return
|
||||||
@ -365,11 +366,18 @@ async function start(type,diff,muteLog) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Having created or updated all flows, now start them.
|
// Having created or updated all flows, now start them.
|
||||||
|
let startFlows = true
|
||||||
|
try {
|
||||||
|
startFlows = settings.get('flowsRunStateRequested');
|
||||||
|
} catch(err) {
|
||||||
|
}
|
||||||
|
startFlows = (startFlows !== "stop");
|
||||||
|
|
||||||
|
if (startFlows) {
|
||||||
for (id in activeFlows) {
|
for (id in activeFlows) {
|
||||||
if (activeFlows.hasOwnProperty(id)) {
|
if (activeFlows.hasOwnProperty(id)) {
|
||||||
try {
|
try {
|
||||||
activeFlows[id].start(diff);
|
activeFlows[id].start(diff);
|
||||||
|
|
||||||
// Create a map of node id to flow id and also a subflowInstance lookup map
|
// Create a map of node id to flow id and also a subflowInstance lookup map
|
||||||
var activeNodes = activeFlows[id].getActiveNodes();
|
var activeNodes = activeFlows[id].getActiveNodes();
|
||||||
Object.keys(activeNodes).forEach(function(nid) {
|
Object.keys(activeNodes).forEach(function(nid) {
|
||||||
@ -380,9 +388,20 @@ async function start(type,diff,muteLog) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
reallyStarted = true;
|
||||||
events.emit("flows:started", {config: activeConfig, type: type, diff: diff});
|
events.emit("flows:started", {config: activeConfig, type: type, diff: diff});
|
||||||
// Deprecated event
|
// Deprecated event
|
||||||
events.emit("nodes-started");
|
events.emit("nodes-started");
|
||||||
|
} else {
|
||||||
|
started = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
started: reallyStarted,
|
||||||
|
state: reallyStarted ? "started" : "stopped",
|
||||||
|
}
|
||||||
|
events.emit("runtime-event",{id:"flows-run-state", payload: state, retain:true});
|
||||||
|
|
||||||
|
|
||||||
if (credentialsPendingReset === true) {
|
if (credentialsPendingReset === true) {
|
||||||
credentialsPendingReset = false;
|
credentialsPendingReset = false;
|
||||||
@ -390,7 +409,7 @@ async function start(type,diff,muteLog) {
|
|||||||
events.emit("runtime-event",{id:"runtime-state",retain:true});
|
events.emit("runtime-event",{id:"runtime-state",retain:true});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!muteLog) {
|
if (!muteLog && reallyStarted) {
|
||||||
if (type !== "full") {
|
if (type !== "full") {
|
||||||
log.info(log._("nodes.flows.started-modified-"+type));
|
log.info(log._("nodes.flows.started-modified-"+type));
|
||||||
} else {
|
} else {
|
||||||
@ -471,6 +490,7 @@ function stop(type,diff,muteLog) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
events.emit("flows:stopped",{config: activeConfig, type: type, diff: diff});
|
events.emit("flows:stopped",{config: activeConfig, type: type, diff: diff});
|
||||||
|
events.emit("runtime-event",{id:"flows-run-state", payload: {started: false, state: "stopped"}, retain:true});
|
||||||
// Deprecated event
|
// Deprecated event
|
||||||
events.emit("nodes-stopped");
|
events.emit("nodes-stopped");
|
||||||
});
|
});
|
||||||
|
15
packages/node_modules/node-red/settings.js
vendored
15
packages/node_modules/node-red/settings.js
vendored
@ -242,6 +242,7 @@ module.exports = {
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Runtime Settings
|
* Runtime Settings
|
||||||
* - lang
|
* - lang
|
||||||
|
* - runtimeState
|
||||||
* - diagnostics
|
* - diagnostics
|
||||||
* - logging
|
* - logging
|
||||||
* - contextStorage
|
* - contextStorage
|
||||||
@ -267,7 +268,19 @@ module.exports = {
|
|||||||
/** enable or disable diagnostics display in the node-red editor. Must be set to `false` to disable */
|
/** enable or disable diagnostics display in the node-red editor. Must be set to `false` to disable */
|
||||||
ui: true,
|
ui: true,
|
||||||
},
|
},
|
||||||
|
/** Configure runtimeState options
|
||||||
|
* - enabled: When `enabled` is `true` (or unset), runtime Start/Stop will
|
||||||
|
* be available at http://localhost:1880/flows/state
|
||||||
|
* - ui: When `ui` is `true` (or unset), the action `core:start-flows` and
|
||||||
|
* `core:stop-flows` be available to logged in users of node-red editor
|
||||||
|
* Also, the deploy menu (when set to default) will show a stop or start button
|
||||||
|
*/
|
||||||
|
runtimeState: {
|
||||||
|
/** enable or disable flows/state endpoint. Must be set to `false` to disable */
|
||||||
|
enabled: true,
|
||||||
|
/** show or hide runtime stop/start options in the node-red editor. Must be set to `false` to hide */
|
||||||
|
ui: true,
|
||||||
|
},
|
||||||
/** Configure the logging output */
|
/** Configure the logging output */
|
||||||
logging: {
|
logging: {
|
||||||
/** Only console logging is currently supported */
|
/** Only console logging is currently supported */
|
||||||
|
@ -32,7 +32,9 @@ describe("api/admin/flows", function() {
|
|||||||
app = express();
|
app = express();
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
app.get("/flows",flows.get);
|
app.get("/flows",flows.get);
|
||||||
|
app.get("/flows/state",flows.getState);
|
||||||
app.post("/flows",flows.post);
|
app.post("/flows",flows.post);
|
||||||
|
app.post("/flows/state",flows.postState);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns flow - v1', function(done) {
|
it('returns flow - v1', function(done) {
|
||||||
@ -208,4 +210,99 @@ describe("api/admin/flows", function() {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('returns flows run state', function (done) {
|
||||||
|
var setFlows = sinon.spy(function () { return Promise.resolve(); });
|
||||||
|
flows.init({
|
||||||
|
flows: {
|
||||||
|
setFlows,
|
||||||
|
getState: async function () {
|
||||||
|
return { started: true, state: "started" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
request(app)
|
||||||
|
.get('/flows/state')
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Node-RED-Deployment-Type', 'reload')
|
||||||
|
.expect(200)
|
||||||
|
.end(function (err, res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
res.body.should.have.a.property('started', true);
|
||||||
|
res.body.should.have.a.property('state', "started");
|
||||||
|
done();
|
||||||
|
} catch (e) {
|
||||||
|
return done(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('sets flows run state - stopped', function (done) {
|
||||||
|
var setFlows = sinon.spy(function () { return Promise.resolve(); });
|
||||||
|
flows.init({
|
||||||
|
flows: {
|
||||||
|
setFlows: setFlows,
|
||||||
|
getState: async function () {
|
||||||
|
return { started: true, state: "started" };
|
||||||
|
},
|
||||||
|
setState: async function () {
|
||||||
|
return { started: false, state: "stopped" };
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
request(app)
|
||||||
|
.post('/flows/state')
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Node-RED-Flow-Run-State-Change', 'stop')
|
||||||
|
.expect(200)
|
||||||
|
.end(function (err, res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
res.body.should.have.a.property('started', false);
|
||||||
|
res.body.should.have.a.property('state', "stopped");
|
||||||
|
done();
|
||||||
|
} catch (e) {
|
||||||
|
return done(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('sets flows run state - bad value', function (done) {
|
||||||
|
var setFlows = sinon.spy(function () { return Promise.resolve(); });
|
||||||
|
const makeError = (error, errcode, statusCode) => {
|
||||||
|
const message = typeof error == "object" ? error.message : error
|
||||||
|
const err = typeof error == "object" ? error : new Error(message||"Unexpected Error")
|
||||||
|
err.status = err.status || statusCode || 400;
|
||||||
|
err.code = err.code || errcode || "unexpected_error"
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
flows.init({
|
||||||
|
flows: {
|
||||||
|
setFlows: setFlows,
|
||||||
|
getState: async function () {
|
||||||
|
return { started: true, state: "started" };
|
||||||
|
},
|
||||||
|
setState: async function () {
|
||||||
|
var err = (makeError("Cannot set runtime state. Invalid state", "invalid_run_state", 400))
|
||||||
|
var p = Promise.reject(err);
|
||||||
|
p.catch(()=>{});
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
request(app)
|
||||||
|
.post('/flows/state')
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Node-RED-Flow-Run-State-Change', 'bad-state')
|
||||||
|
.expect(400)
|
||||||
|
.end(function(err,res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
res.body.should.have.property("code","invalid_run_state");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -427,4 +427,126 @@ describe("runtime-api/flows", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("flow run state", function() {
|
||||||
|
var startFlows, stopFlows, runtime;
|
||||||
|
beforeEach(function() {
|
||||||
|
let flowsStarted = true;
|
||||||
|
let flowsState = "started";
|
||||||
|
startFlows = sinon.spy(function(type) {
|
||||||
|
if (type !== "full") {
|
||||||
|
var err = new Error();
|
||||||
|
// TODO: quirk of internal api - uses .code for .status
|
||||||
|
err.code = 400;
|
||||||
|
var p = Promise.reject(err);
|
||||||
|
p.catch(()=>{});
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
flowsStarted = true;
|
||||||
|
flowsState = "started";
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
stopFlows = sinon.spy(function(type) {
|
||||||
|
if (type !== "full") {
|
||||||
|
var err = new Error();
|
||||||
|
// TODO: quirk of internal api - uses .code for .status
|
||||||
|
err.code = 400;
|
||||||
|
var p = Promise.reject(err);
|
||||||
|
p.catch(()=>{});
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
flowsStarted = false;
|
||||||
|
flowsState = "stopped";
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
runtime = {
|
||||||
|
log: mockLog(),
|
||||||
|
settings: {
|
||||||
|
runtimeState: {
|
||||||
|
enabled: true,
|
||||||
|
ui: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
flows: {
|
||||||
|
get started() {
|
||||||
|
return flowsStarted;
|
||||||
|
},
|
||||||
|
startFlows,
|
||||||
|
stopFlows,
|
||||||
|
getFlows: function() { return {rev:"currentRev",flows:[]} },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("gets flows run state", async function() {
|
||||||
|
flows.init(runtime);
|
||||||
|
const state = await flows.getState({})
|
||||||
|
state.should.have.property("started", true)
|
||||||
|
state.should.have.property("state", "started")
|
||||||
|
});
|
||||||
|
it("permits getting flows run state when setting disabled", async function() {
|
||||||
|
runtime.settings.runtimeState.enabled = false;
|
||||||
|
flows.init(runtime);
|
||||||
|
const state = await flows.getState({})
|
||||||
|
state.should.have.property("started", true)
|
||||||
|
state.should.have.property("state", "started")
|
||||||
|
});
|
||||||
|
it("start flows", async function() {
|
||||||
|
flows.init(runtime);
|
||||||
|
const state = await flows.setState({requestedState:"start"})
|
||||||
|
state.should.have.property("started", true)
|
||||||
|
state.should.have.property("state", "started")
|
||||||
|
stopFlows.called.should.not.be.true();
|
||||||
|
startFlows.called.should.be.true();
|
||||||
|
});
|
||||||
|
it("stop flows", async function() {
|
||||||
|
flows.init(runtime);
|
||||||
|
const state = await flows.setState({requestedState:"stop"})
|
||||||
|
state.should.have.property("started", false)
|
||||||
|
state.should.have.property("state", "stopped")
|
||||||
|
stopFlows.called.should.be.true();
|
||||||
|
startFlows.called.should.not.be.true();
|
||||||
|
});
|
||||||
|
it("rejects starting flows when setting disabled", async function() {
|
||||||
|
let err;
|
||||||
|
runtime.settings.runtimeState.enabled = false;
|
||||||
|
flows.init(runtime);
|
||||||
|
try {
|
||||||
|
await flows.setState({requestedState:"start"})
|
||||||
|
} catch (error) {
|
||||||
|
err = error
|
||||||
|
}
|
||||||
|
stopFlows.called.should.not.be.true();
|
||||||
|
startFlows.called.should.not.be.true();
|
||||||
|
should(err).have.property("code", "not_allowed")
|
||||||
|
should(err).have.property("status", 405)
|
||||||
|
});
|
||||||
|
it("rejects stopping flows when setting disabled", async function() {
|
||||||
|
let err;
|
||||||
|
runtime.settings.runtimeState.enabled = false;
|
||||||
|
flows.init(runtime);
|
||||||
|
try {
|
||||||
|
await flows.setState({requestedState:"stop"})
|
||||||
|
} catch (error) {
|
||||||
|
err = error
|
||||||
|
}
|
||||||
|
stopFlows.called.should.not.be.true();
|
||||||
|
startFlows.called.should.not.be.true();
|
||||||
|
should(err).have.property("code", "not_allowed")
|
||||||
|
should(err).have.property("status", 405)
|
||||||
|
});
|
||||||
|
it("rejects setting invalid flows run state", async function() {
|
||||||
|
let err;
|
||||||
|
flows.init(runtime);
|
||||||
|
try {
|
||||||
|
await flows.setState({requestedState:"bad-state"})
|
||||||
|
} catch (error) {
|
||||||
|
err = error
|
||||||
|
}
|
||||||
|
stopFlows.called.should.not.be.true();
|
||||||
|
startFlows.called.should.not.be.true();
|
||||||
|
should(err).have.property("code", "invalid_run_state")
|
||||||
|
should(err).have.property("status", 400)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -131,7 +131,7 @@ describe('flows/index', function() {
|
|||||||
// eventsOn.calledOnce.should.be.true();
|
// eventsOn.calledOnce.should.be.true();
|
||||||
// });
|
// });
|
||||||
// });
|
// });
|
||||||
|
/*
|
||||||
describe('#setFlows', function() {
|
describe('#setFlows', function() {
|
||||||
it('sets the full flow', function(done) {
|
it('sets the full flow', function(done) {
|
||||||
var originalConfig = [
|
var originalConfig = [
|
||||||
@ -300,6 +300,7 @@ describe('flows/index', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
describe('#startFlows', function() {
|
describe('#startFlows', function() {
|
||||||
it('starts the loaded config', function(done) {
|
it('starts the loaded config', function(done) {
|
||||||
@ -321,6 +322,87 @@ describe('flows/index', function() {
|
|||||||
return flows.startFlows();
|
return flows.startFlows();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('emits runtime-event "flows-run-state" "started"', async function () {
|
||||||
|
var originalConfig = [
|
||||||
|
{ id: "t1-1", x: 10, y: 10, z: "t1", type: "test", wires: [] },
|
||||||
|
{ id: "t1", type: "tab" }
|
||||||
|
];
|
||||||
|
storage.getFlows = function () {
|
||||||
|
return Promise.resolve({ flows: originalConfig });
|
||||||
|
}
|
||||||
|
let receivedEvent = null;
|
||||||
|
const handleEvent = (data) => {
|
||||||
|
console.log(data)
|
||||||
|
if(data && data.id === 'flows-run-state') {
|
||||||
|
receivedEvent = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
events.on('runtime-event', handleEvent);
|
||||||
|
flows.init({ log: mockLog, settings: {}, storage: storage });
|
||||||
|
await flows.load()
|
||||||
|
await flows.startFlows()
|
||||||
|
events.removeListener("runtime-event", handleEvent);
|
||||||
|
|
||||||
|
//{id:"flows-run-state", payload: {started: true, state: "started"}
|
||||||
|
should(receivedEvent).not.be.null()
|
||||||
|
receivedEvent.should.have.property("id", "flows-run-state")
|
||||||
|
receivedEvent.should.have.property("payload", { started: true, state: "started" })
|
||||||
|
receivedEvent.should.have.property("retain", true)
|
||||||
|
});
|
||||||
|
it('emits runtime-event "flows-run-state" "stopped"', async function () {
|
||||||
|
const originalConfig = [
|
||||||
|
{ id: "t1-1", x: 10, y: 10, z: "t1", type: "test", wires: [] },
|
||||||
|
{ id: "t1", type: "tab" }
|
||||||
|
];
|
||||||
|
storage.getFlows = function () {
|
||||||
|
return Promise.resolve({ flows: originalConfig });
|
||||||
|
}
|
||||||
|
let receivedEvent = null;
|
||||||
|
const handleEvent = (data) => {
|
||||||
|
if(data && data.id === 'flows-run-state') {
|
||||||
|
receivedEvent = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
events.on('runtime-event', handleEvent);
|
||||||
|
flows.init({ log: mockLog, settings: {}, storage: storage });
|
||||||
|
await flows.load()
|
||||||
|
await flows.startFlows()
|
||||||
|
await flows.stopFlows()
|
||||||
|
events.removeListener("runtime-event", handleEvent);
|
||||||
|
|
||||||
|
//{id:"flows-run-state", payload: {started: true, state: "started"}
|
||||||
|
should(receivedEvent).not.be.null()
|
||||||
|
receivedEvent.should.have.property("id", "flows-run-state")
|
||||||
|
receivedEvent.should.have.property("payload", { started: false, state: "stopped" })
|
||||||
|
receivedEvent.should.have.property("retain", true)
|
||||||
|
});
|
||||||
|
// it('raises error when invalid flows run state requested', async function () {
|
||||||
|
// const originalConfig = [
|
||||||
|
// { id: "t1-1", x: 10, y: 10, z: "t1", type: "test", wires: [] },
|
||||||
|
// { id: "t1", type: "tab" }
|
||||||
|
// ];
|
||||||
|
// storage.getFlows = function () {
|
||||||
|
// return Promise.resolve({ flows: originalConfig });
|
||||||
|
// }
|
||||||
|
// let receivedEvent = null;
|
||||||
|
// const handleEvent = (data) => {
|
||||||
|
// if(data && data.id === 'flows-run-state') {
|
||||||
|
// receivedEvent = data;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// events.on('runtime-event', handleEvent);
|
||||||
|
// flows.init({ log: mockLog, settings: {}, storage: storage });
|
||||||
|
// await flows.load()
|
||||||
|
// await flows.startFlows()
|
||||||
|
// await flows.stopFlows()
|
||||||
|
// events.removeListener("runtime-event", handleEvent);
|
||||||
|
|
||||||
|
// //{id:"flows-run-state", payload: {started: true, state: "started"}
|
||||||
|
// should(receivedEvent).not.be.null()
|
||||||
|
// receivedEvent.should.have.property("id", "flows-run-state")
|
||||||
|
// receivedEvent.should.have.property("payload", { started: false, state: "stopped" })
|
||||||
|
// receivedEvent.should.have.property("retain", true)
|
||||||
|
// });
|
||||||
it('does not start if nodes missing', function(done) {
|
it('does not start if nodes missing', function(done) {
|
||||||
var originalConfig = [
|
var originalConfig = [
|
||||||
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||||
@ -415,7 +497,7 @@ describe('flows/index', function() {
|
|||||||
describe.skip('#get',function() {
|
describe.skip('#get',function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
/*
|
||||||
describe('#eachNode', function() {
|
describe('#eachNode', function() {
|
||||||
it('iterates the flow nodes', function(done) {
|
it('iterates the flow nodes', function(done) {
|
||||||
var originalConfig = [
|
var originalConfig = [
|
||||||
@ -582,7 +664,7 @@ describe('flows/index', function() {
|
|||||||
];
|
];
|
||||||
flows.init({log:mockLog, settings:{},storage:storage});
|
flows.init({log:mockLog, settings:{},storage:storage});
|
||||||
flows.setFlows(originalConfig).then(function() {
|
flows.setFlows(originalConfig).then(function() {
|
||||||
/*jshint immed: false */
|
|
||||||
try {
|
try {
|
||||||
flows.checkTypeInUse("used-module");
|
flows.checkTypeInUse("used-module");
|
||||||
done("type_in_use error not thrown");
|
done("type_in_use error not thrown");
|
||||||
@ -666,4 +748,5 @@ describe('flows/index', function() {
|
|||||||
describe('#enableFlow', function() {
|
describe('#enableFlow', function() {
|
||||||
it.skip("enableFlow");
|
it.skip("enableFlow");
|
||||||
})
|
})
|
||||||
|
*/
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user