mirror of
https://git.rwth-aachen.de/acs/public/villas/web/
synced 2025-03-09 00:00:01 +01:00
WIP: rework AMQP commands
This commit is contained in:
parent
db1058625b
commit
04916aa9d6
5 changed files with 291 additions and 91 deletions
|
@ -17,6 +17,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { Button, DropdownButton, Dropdown, InputGroup, FormControl } from 'react-bootstrap';
|
||||
import AppDispatcher from "../common/app-dispatcher";
|
||||
|
||||
class ICAction extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -47,9 +48,178 @@ class ICAction extends React.Component {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
runAction(action, when) {
|
||||
|
||||
console.log("configs", this.props.configs)
|
||||
console.log("selectedConfigs", this.props.selectedConfigs)
|
||||
console.log("ics", this.props.ics)
|
||||
console.log("selectedICs", this.props.selectedICs)
|
||||
console.log("action", action)
|
||||
console.log("when", when)
|
||||
|
||||
if (action.data.action === 'none') {
|
||||
console.warn("No command selected. Nothing was sent.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.props.hasConfigs){
|
||||
let newAction = {};
|
||||
newAction["action"] = action.data.action
|
||||
newAction["when"] = when
|
||||
|
||||
for (let index of this.props.selectedICs) {
|
||||
let ic = this.props.ics[index];
|
||||
let icID = ic.id;
|
||||
|
||||
/* VILLAScontroller protocol
|
||||
see: https://villas.fein-aachen.org/doc/controller-protocol.html
|
||||
|
||||
RESET SHUTDOWN
|
||||
{
|
||||
"action": "reset/shutdown/stop/pause/resume"
|
||||
"when": "1234567"
|
||||
}
|
||||
|
||||
DELETE
|
||||
{
|
||||
"action": "delete"
|
||||
"parameters":{
|
||||
"uuid": "uuid-of-the-manager-for-this-IC"
|
||||
}
|
||||
"when": "1234567"
|
||||
}
|
||||
|
||||
CREATE is not possible within ICAction (see add IC)
|
||||
*/
|
||||
|
||||
if (newAction.action === "delete"){
|
||||
// prepare parameters for delete incl. correct IC id
|
||||
newAction["parameters"] = {};
|
||||
newAction.parameters["uuid"] = ic.id;
|
||||
icID = ic.manager; // send delete action to manager of IC
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/start-action',
|
||||
icid: ic.id,
|
||||
action: newAction,
|
||||
result: null,
|
||||
token: this.props.token
|
||||
});
|
||||
|
||||
} // end for loop over selected ICs
|
||||
} else {
|
||||
|
||||
/*VILLAScontoller protocol
|
||||
see: https://villas.fein-aachen.org/doc/controller-protocol.html
|
||||
*
|
||||
* STOP PAUSE RESUME
|
||||
{
|
||||
"action": "reset/shutdown/stop/pause/resume"
|
||||
"when": "1234567"
|
||||
}
|
||||
*
|
||||
* START
|
||||
{
|
||||
"action": "start"
|
||||
"when": 1234567
|
||||
"parameters": {
|
||||
Start parameters for this IC as configured in the component config
|
||||
}
|
||||
"model": {
|
||||
"type": "url"
|
||||
"url": "https://villas.k8s.eonerc.rwth-aachen.de/api/v2/files/{fileID}" where fileID is the model file configured in the component config
|
||||
"token": "asessiontoken"
|
||||
}
|
||||
"results":{
|
||||
"type": "url"
|
||||
"url" : "https://villas.k8s.eonerc.rwth-aachen.de/api/v2/results/{resultID}/file" where resultID is the ID of the result created for this run
|
||||
"token": "asessiontoken"
|
||||
}
|
||||
}
|
||||
*
|
||||
*
|
||||
* */
|
||||
|
||||
|
||||
let newActions = [];
|
||||
for (let config of this.props.selectedConfigs) {
|
||||
let newAction = {}
|
||||
newAction["action"] = action.data.action
|
||||
newAction["when"] = when
|
||||
|
||||
// get IC for component config
|
||||
let ic = null;
|
||||
for (let component of this.props.ics) {
|
||||
if (component.id === config.icID) {
|
||||
ic = component;
|
||||
}
|
||||
}
|
||||
|
||||
if (ic == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// the following is not required by the protocol; it is an internal help
|
||||
newAction["icid"] = ic.id
|
||||
|
||||
if (newAction.action === 'start') {
|
||||
newAction["parameters"] = config.startParameters;
|
||||
newAction["model"] = {}
|
||||
|
||||
if (config.fileIDs.length > 0){
|
||||
newAction.model["type"] = "url"
|
||||
newAction.model["token"] = this.props.token
|
||||
// TODO do not default to the first file of the config
|
||||
newAction.model["url"] = "/files/" + config.fileIDs[0].toString()
|
||||
}
|
||||
|
||||
newAction["results"] = {}
|
||||
newAction.results["type"] = "url"
|
||||
newAction.results["token"] = this.props.token
|
||||
newAction.results["url"] = "/results/RESULTID/file" // RESULTID serves as placeholder and is replaced later
|
||||
|
||||
}
|
||||
|
||||
// add the new action
|
||||
newActions.push(newAction);
|
||||
console.log("New actions in loop", newAction, newActions)
|
||||
|
||||
} // end for loop over selected configs
|
||||
|
||||
|
||||
let newResult = {}
|
||||
newResult["result"] = {}
|
||||
if (action.data.action === 'start') {
|
||||
|
||||
let configSnapshots = [];
|
||||
// create config snapshots in case action is start
|
||||
for (let config of this.props.selectedConfigs) {
|
||||
let index = this.props.configs.indexOf(config)
|
||||
configSnapshots.push(this.props.snapshotConfig(index));
|
||||
}
|
||||
|
||||
// create new result for new run
|
||||
newResult.result["description"] = "Placeholder for description"
|
||||
newResult.result["scenarioID"] = this.props.selectedConfigs[0].scenarioID
|
||||
newResult.result["configSnapshots"] = configSnapshots
|
||||
}
|
||||
|
||||
|
||||
console.log("Dispatching actions for configs", newActions, newResult)
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/start-action',
|
||||
action: newActions,
|
||||
result: newResult,
|
||||
token: this.props.token
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setAction = id => {
|
||||
// search action
|
||||
for (let action of this.props.actions) {
|
||||
|
@ -65,7 +235,13 @@ class ICAction extends React.Component {
|
|||
|
||||
render() {
|
||||
|
||||
let sendCommandDisabled = this.props.runDisabled || this.state.selectedAction == null || this.state.selectedAction.id === "-1"
|
||||
let sendCommandDisabled = false;
|
||||
if (!this.props.hasConfigs && this.props.selectedICs.length === 0 || this.state.selectedAction == null || this.state.selectedAction.id === "-1"){
|
||||
sendCommandDisabled = true;
|
||||
}
|
||||
if (this.props.hasConfigs && this.props.selectedConfigs.length === 0|| this.state.selectedAction == null || this.state.selectedAction.id === "-1"){
|
||||
sendCommandDisabled = true;
|
||||
}
|
||||
|
||||
let time = this.state.time.getFullYear().pad(4) + '-' +
|
||||
this.state.time.getMonth().pad(2) + '-' +
|
||||
|
@ -98,7 +274,7 @@ class ICAction extends React.Component {
|
|||
<Button
|
||||
variant="secondary"
|
||||
disabled={sendCommandDisabled}
|
||||
onClick={() => this.props.runAction(this.state.selectedAction, this.state.time)}>Run</Button>
|
||||
onClick={() => this.runAction(this.state.selectedAction, this.state.time)}>Run</Button>
|
||||
</InputGroup>
|
||||
<small className="text-muted">Select time for synced command execution</small>
|
||||
</div>;
|
||||
|
|
|
@ -66,16 +66,41 @@ class InfrastructureComponentStore extends ArrayStore {
|
|||
return state;
|
||||
|
||||
case 'ics/start-action':
|
||||
if (!Array.isArray(action.data))
|
||||
action.data = [ action.data ]
|
||||
if (!Array.isArray(action.action))
|
||||
action.action = [ action.action ]
|
||||
|
||||
ICsDataManager.doActions(action.ic, action.data, action.token);
|
||||
ICsDataManager.doActions(action.icid, action.action, action.token, action.result);
|
||||
return state;
|
||||
|
||||
case 'ics/action-error':
|
||||
console.log(action.error);
|
||||
return state;
|
||||
|
||||
case 'ics/action-result-added':
|
||||
|
||||
for (let a of action.actions){
|
||||
let icid = Object.assign({}, a.icid)
|
||||
|
||||
if (a.results !== undefined && a.results != null){
|
||||
// adapt URL for newly created result ID
|
||||
a.results.url = a.results.url.replace("RESULTID", action.data.result.id);
|
||||
a.results.url = ICsDataManager.makeURL(a.results.url);
|
||||
a.results.url = window.location.host + a.results.url;
|
||||
}
|
||||
if (a.model !== undefined && a.model != null) {
|
||||
// adapt URL for model file
|
||||
a.model.url = ICsDataManager.makeURL(a.model.url);
|
||||
a.model.url = window.location.host + a.model.url;
|
||||
}
|
||||
delete a.icid
|
||||
ICsDataManager.doActions(icid, [a], action.token)
|
||||
}
|
||||
return state;
|
||||
|
||||
case 'ics/action-result-add-error':
|
||||
console.log(action.error);
|
||||
return state
|
||||
|
||||
case 'ics/get-status':
|
||||
ICsDataManager.getStatus(action.url, action.token, action.ic);
|
||||
return super.reduce(state, action);
|
||||
|
|
|
@ -24,24 +24,79 @@ class IcsDataManager extends RestDataManager {
|
|||
super('ic', '/ic');
|
||||
}
|
||||
|
||||
doActions(ic, actions, token = null) {
|
||||
doActions(icid, actions, token = null, result=null) {
|
||||
for (let action of actions) {
|
||||
if (action.when)
|
||||
// Send timestamp as Unix Timestamp
|
||||
action.when = Math.round(action.when.getTime() / 1000);
|
||||
}
|
||||
|
||||
RestAPI.post(this.makeURL(this.url + '/' + ic.id + '/action'), actions, token).then(response => {
|
||||
if (icid !== undefined && icid != null) {
|
||||
|
||||
console.log("doActions, icid:", icid)
|
||||
// sending action to a specific IC via IC list
|
||||
|
||||
RestAPI.post(this.makeURL(this.url + '/' + icid + '/action'), actions, token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/action-started',
|
||||
data: response
|
||||
type: 'ics/action-started',
|
||||
data: response
|
||||
});
|
||||
}).catch(error => {
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/action-error',
|
||||
type: 'ics/action-error',
|
||||
error
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// sending the same action to multiple ICs via scenario controls
|
||||
|
||||
// distinguish between "start" action and any other
|
||||
|
||||
if (actions[0].action !== "start"){
|
||||
for (let a of actions){
|
||||
console.log("doActions, a.icid:", a.icid)
|
||||
icid = JSON.parse(JSON.stringify(a.icid))
|
||||
delete a.icid
|
||||
console.log("doActions, icid:", icid)
|
||||
// sending action to a specific IC via IC list
|
||||
|
||||
RestAPI.post(this.makeURL(this.url + '/' + icid + '/action'), [a], token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/action-started',
|
||||
data: response
|
||||
});
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/action-error',
|
||||
error
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
} else{
|
||||
// for start actions procedure is different
|
||||
// first a result needs to be created, then the start actions can be sent
|
||||
|
||||
RestAPI.post(this.makeURL( '/results'), result, token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/action-result-added',
|
||||
data: response,
|
||||
actions: actions,
|
||||
token: token,
|
||||
});
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/action-result-add-error',
|
||||
error
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
getStatus(url,token,ic){
|
||||
|
|
|
@ -240,18 +240,7 @@ class InfrastructureComponents extends Component {
|
|||
this.setState({ selectedICs: selectedICs });
|
||||
}
|
||||
|
||||
runAction(action, when) {
|
||||
for (let index of this.state.selectedICs) {
|
||||
action.when = when;
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/start-action',
|
||||
ic: this.state.ics[index],
|
||||
data: action.data,
|
||||
token: this.state.sessionToken,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static isICOutdated(component) {
|
||||
if (!component.stateUpdateAt)
|
||||
|
@ -420,19 +409,19 @@ class InfrastructureComponents extends Component {
|
|||
modifier={(stateUpdateAt, component) => this.stateUpdateModifier(stateUpdateAt, component)}
|
||||
/>
|
||||
|
||||
{this.state.currentUser.role === "Admin" && editable ?
|
||||
{this.state.currentUser.role === "Admin" ?
|
||||
<TableColumn
|
||||
width='200'
|
||||
editButton
|
||||
width='150'
|
||||
editButton = {(index) => !this.isExternalIC(index)}
|
||||
exportButton
|
||||
deleteButton
|
||||
deleteButton = {(index) => !this.isExternalIC(index)}
|
||||
onEdit={index => this.setState({editModal: true, modalIC: ics[index], modalIndex: index})}
|
||||
onExport={index => this.exportIC(index)}
|
||||
onDelete={index => this.setState({deleteModal: true, modalIC: ics[index], modalIndex: index})}
|
||||
/>
|
||||
:
|
||||
<TableColumn
|
||||
width='100'
|
||||
width='50'
|
||||
exportButton
|
||||
onExport={index => this.exportIC(index)}
|
||||
/>
|
||||
|
@ -496,12 +485,15 @@ class InfrastructureComponents extends Component {
|
|||
{this.state.currentUser.role === "Admin" && this.state.numberOfExternalICs > 0 ?
|
||||
<div style={{float: 'left'}}>
|
||||
<ICAction
|
||||
runDisabled={this.state.selectedICs.length === 0}
|
||||
runAction={(action, when) => this.runAction(action, when)}
|
||||
hasConfigs = {false}
|
||||
ics={this.state.ics}
|
||||
selectedICs={this.state.selectedICs}
|
||||
token={this.state.sessionToken}
|
||||
actions={[
|
||||
{id: '-1', title: 'Action', data: {action: 'none'}},
|
||||
{id: '0', title: 'Reset', data: {action: 'reset'}},
|
||||
{id: '1', title: 'Shutdown', data: {action: 'shutdown'}},
|
||||
{id: '2', title: 'Delete', data: {action: 'delete'}}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -302,6 +302,7 @@ class Scenario extends React.Component {
|
|||
}
|
||||
|
||||
copyConfig(index) {
|
||||
console.log("index", index, "copyConfig: ", this.state.configs[index])
|
||||
let config = JSON.parse(JSON.stringify(this.state.configs[index]));
|
||||
|
||||
let signals = JSON.parse(JSON.stringify(SignalStore.getState().filter(s => s.configID === parseInt(config.id, 10))));
|
||||
|
@ -394,60 +395,6 @@ class Scenario extends React.Component {
|
|||
|
||||
}
|
||||
|
||||
runAction(action, when) {
|
||||
if (action.data.action === 'none') {
|
||||
console.warn("No command selected. Nothing was sent.");
|
||||
return;
|
||||
}
|
||||
|
||||
let configs = [];
|
||||
for (let index of this.state.selectedConfigs) {
|
||||
// get IC for component config
|
||||
let ic = null;
|
||||
for (let component of this.state.ics) {
|
||||
if (component.id === this.state.configs[index].icID) {
|
||||
ic = component;
|
||||
}
|
||||
}
|
||||
|
||||
if (ic == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (action.data.action === 'start') {
|
||||
configs.push(this.copyConfig(index));
|
||||
action.data.parameters = this.state.configs[index].startParameters;
|
||||
}
|
||||
|
||||
action.data.when = when;
|
||||
|
||||
console.log("Sending action: ", action.data)
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/start-action',
|
||||
ic: ic,
|
||||
data: action.data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
if (configs.length !== 0) { //create result (only if command was 'start')
|
||||
let componentConfigs = {};
|
||||
componentConfigs["configs"] = configs;
|
||||
let data = {};
|
||||
data["Description"] = "Run " + this.state.scenario.name; // default description, to be change by user later
|
||||
data["ResultFileIDs"] = [];
|
||||
data["scenarioID"] = this.state.scenario.id;
|
||||
data["ConfigSnapshots"] = JSON.stringify(componentConfigs, null, 2);
|
||||
AppDispatcher.dispatch({
|
||||
type: 'results/start-add',
|
||||
data,
|
||||
token: this.state.sessionToken,
|
||||
})
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
getICName(icID) {
|
||||
for (let ic of this.state.ics) {
|
||||
if (ic.id === icID) {
|
||||
|
@ -710,7 +657,7 @@ class Scenario extends React.Component {
|
|||
}
|
||||
|
||||
openResultConfigSnaphots(result) {
|
||||
if (!result.configSnapshots || result.configSnapshots == "") {
|
||||
if (result.configSnapshots === null || result.configSnapshots === undefined) {
|
||||
this.setState({
|
||||
modalResultConfigs: {"configs": []},
|
||||
modalResultConfigsIndex: result.id,
|
||||
|
@ -881,7 +828,8 @@ class Scenario extends React.Component {
|
|||
<Table data={this.state.configs}>
|
||||
<TableColumn
|
||||
checkbox
|
||||
checkboxDisabled={(index) => this.usesExternalIC(index)}
|
||||
// checkboxDisabled={(index) => this.usesExternalIC(index)}
|
||||
checkboxDistabled={false}
|
||||
onChecked={(index, event) => this.onConfigChecked(index, event)}
|
||||
width='30' />
|
||||
<TableColumn title='Name' dataKey='name' />
|
||||
|
@ -925,11 +873,15 @@ class Scenario extends React.Component {
|
|||
/>
|
||||
</Table>
|
||||
|
||||
{this.state.ExternalICInUse ? (
|
||||
{/*{this.state.ExternalICInUse ? (*/}
|
||||
<div style={{ float: 'left' }}>
|
||||
<ICAction
|
||||
runDisabled={this.state.selectedConfigs.length === 0}
|
||||
runAction={(action, when) => this.runAction(action, when)}
|
||||
hasConfigs={true}
|
||||
ics={this.state.ics}
|
||||
configs={this.state.configs}
|
||||
selectedConfigs = {this.state.selectedConfigs}
|
||||
snapshotConfig = {(index) => this.copyConfig(index)}
|
||||
token = {this.state.sessionToken}
|
||||
actions={[
|
||||
{ id: '-1', title: 'Action', data: { action: 'none' } },
|
||||
{ id: '0', title: 'Start', data: { action: 'start' } },
|
||||
|
@ -938,8 +890,8 @@ class Scenario extends React.Component {
|
|||
{ id: '3', title: 'Resume', data: { action: 'resume' } }
|
||||
]} />
|
||||
</div>
|
||||
) : (<div />)
|
||||
}
|
||||
{/*) : (<div />)*/}
|
||||
{/*}*/}
|
||||
|
||||
< div style={{ clear: 'both' }} />
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue