diff --git a/package-lock.json b/package-lock.json index 28f8bab..4479160 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5575,7 +5575,7 @@ }, "encoding": { "version": "0.1.12", - "resolved": false, + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", "requires": { "iconv-lite": "~0.4.13" @@ -8071,9 +8071,9 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "internal-ip": { "version": "4.3.0", diff --git a/src/common/api/rest-api.js b/src/common/api/rest-api.js index 4a837dc..4e554c7 100644 --- a/src/common/api/rest-api.js +++ b/src/common/api/rest-api.js @@ -159,6 +159,28 @@ class RestAPI { }); } + apiDownload(url, token) { + return new Promise(function (resolve, reject) { + var req = request.get(url).buffer(true).responseType("blob"); + + if (token != null) { + req.set('Authorization', "Bearer " + token); + } + + req.end(function (error, res) { + if (res == null || res.status !== 200) { + if (req.url !== prevURL) error.handled = isNetworkError(error); + prevURL = req.url; + reject(error); + } else { + let parts = url.split("/"); + resolve({data: res.body, type: res.type, id: parts[parts.length-1]}) + } + }); + }); + } + + } export default new RestAPI(); diff --git a/src/ic/confirm-command.js b/src/ic/confirm-command.js new file mode 100644 index 0000000..43b6885 --- /dev/null +++ b/src/ic/confirm-command.js @@ -0,0 +1,48 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +import React from 'react'; +import { Button, Modal} from 'react-bootstrap'; + +class ConfirmCommand extends React.Component { + onModalKeyPress = (event) => { + if (event.key === 'Enter') { + event.preventDefault(); + + this.props.onClose(false); + } + } + + render() { + return this.props.onClose(false)} onKeyPress={this.onModalKeyPress}> + + Confirm {this.props.command} + + + + Are you sure you want to {this.props.command} '{this.props.name}'? + + + + this.props.onClose(true)}>Cancel + this.props.onClose(false)}>Confirm + + ; + } +} + +export default ConfirmCommand; diff --git a/src/ic/ic-data-data-manager.js b/src/ic/ic-data-data-manager.js index 0dcc853..087b294 100644 --- a/src/ic/ic-data-data-manager.js +++ b/src/ic/ic-data-data-manager.js @@ -17,6 +17,7 @@ import WebsocketAPI from '../common/api/websocket-api'; import AppDispatcher from '../common/app-dispatcher'; +import RestAPI from "../common/api/rest-api"; const OFFSET_TYPE = 2; const OFFSET_VERSION = 4; @@ -43,6 +44,84 @@ class IcDataDataManager { } } + getStatus(url,socketname,token,icid,ic){ + RestAPI.get(url, null).then(response => { + let tempIC = ic; + tempIC.state = response.state; + AppDispatcher.dispatch({ + type: 'ic-status/status-received', + data: response, + token: token, + socketname: socketname, + icid: icid, + ic: ic + }); + if(!ic.managedexternally){ + AppDispatcher.dispatch({ + type: 'ics/start-edit', + data: tempIC, + token: token, + }); + } + }).catch(error => { + AppDispatcher.dispatch({ + type: 'ic-status/status-error', + error: error + }) + }) + } + + getGraph(url,socketname,token,icid){ + RestAPI.apiDownload(url, null).then(response => { + AppDispatcher.dispatch({ + type: 'ic-graph/graph-received', + data: response, + token: token, + socketname: socketname, + icid: icid, + }); + }).catch(error => { + AppDispatcher.dispatch({ + type: 'ic-graph/graph-error', + error: error + }) + }) + } + + restart(url,socketname,token){ + RestAPI.post(url, null).then(response => { + AppDispatcher.dispatch({ + type: 'ic-status/restart-successful', + data: response, + token: token, + socketname: socketname, + }); + }).catch(error => { + AppDispatcher.dispatch({ + type: 'ic-status/restart-error', + error: error + }) + }) + } + + shutdown(url,socketname,token){ + RestAPI.post(url, null).then(response => { + AppDispatcher.dispatch({ + type: 'ic-status/shutdown-successful', + data: response, + token: token, + socketname: socketname, + }); + }).catch(error => { + AppDispatcher.dispatch({ + type: 'ic-status/shutdown-error', + error: error + }) + }) + } + + + closeAll() { // close every open socket for (var identifier in this._sockets) { diff --git a/src/ic/ic-dialog.js b/src/ic/ic-dialog.js index b134f95..591db53 100644 --- a/src/ic/ic-dialog.js +++ b/src/ic/ic-dialog.js @@ -1,6 +1,10 @@ import React from 'react'; -import {FormLabel} from 'react-bootstrap'; +import {Button, Row, Col} from 'react-bootstrap'; import Dialog from '../common/dialogs/dialog'; +import Icon from "../common/icon"; +import ConfirmCommand from './confirm-command'; +import JsonView from 'react-json-view'; +import FileSaver from 'file-saver'; class ICDialog extends React.Component { @@ -10,10 +14,20 @@ class ICDialog extends React.Component { super(props); this.state = { - ic: props.ic + confirmCommand: false, + command: '', + icStatus: {} }; } + static getDerivedStateFromProps(props, state){ + if(typeof props.icStatus !== 'undefined'){ + return {icStatus: props.icStatus} + } else { + return {} + } + } + onClose(canceled) { this.props.onClose(); } @@ -22,22 +36,93 @@ class ICDialog extends React.Component { } + showFurtherInfo(key){ + if(typeof this.state[key] === 'undefined') this.setState({[key]: false}); + this.setState({[key]: !this.state[key]}); + } + graphError(e){ + console.log("graph error"); + } + + closeConfirmModal(canceled){ + if(!canceled){ + this.props.sendControlCommand(this.state.command,this.props.ic); + } + + this.setState({confirmCommand: false, command: ''}); + } + + downloadGraph(url){ + FileSaver.saveAs(url, this.props.ic.name + ".svg"); +} + + render() { + + let icStatus = this.state.icStatus; + delete icStatus['icID']; + + let objectURL='' + if(typeof this.props.icGraph !== "undefined") { + objectURL = this.props.icGraph.objectURL + } return ( this.onClose(c)} valid={true} - size='lg' + size='xl' + blendOutCancel={true} > - Infos and Controls + + + Status: + + + + + + + + this.downloadGraph(objectURL)}> + + Graph: + + {objectURL !== '' ? ( + this.graphError(e)} alt={"Error"} src={objectURL} /> + ) : ( + + )} + + + {this.props.userRole === "Admin" ? ( + + Controls: + + this.setState({ confirmCommand: true, command: 'restart' })}>Restart + this.setState({ confirmCommand: true, command: 'shutdown' })}>Shutdown + + ) + : ()} + + this.closeConfirmModal(c)} /> + + + ); } } diff --git a/src/ic/ic-graph-store.js b/src/ic/ic-graph-store.js new file mode 100644 index 0000000..cb44812 --- /dev/null +++ b/src/ic/ic-graph-store.js @@ -0,0 +1,62 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +import ArrayStore from '../common/array-store'; +import ICDataDataManager from './ic-data-data-manager'; + +class ICGraphStore extends ArrayStore { + constructor() { + super('ic-graph', ICDataDataManager); + } + + saveGraph(state, action){ + + let icID = parseInt(action.icid); + const dublicate = state.some(element => element.icID === icID); + if(dublicate){ + return state + } + let icGraph = {}; + icGraph["icID"] = icID; + icGraph["data"] = new Blob([action.data.data], {type: action.data.type}); + icGraph["type"] = action.data.type; + icGraph["objectURL"] = URL.createObjectURL(icGraph["data"]); + + this.__emitChange(); + return this.updateElements(state, [icGraph]); + } + + reduce(state, action) { + switch(action.type) { + + case 'ic-graph/get-graph': + ICDataDataManager.getGraph(action.url, action.socketname, action.token, action.icid); + return super.reduce(state, action); + + case 'ic-graph/graph-received': + return this.saveGraph(state, action); + + case 'ic-graph/graph-error': + return super.reduce(state, action); + + default: + return super.reduce(state, action); + } + } +} + +export default new ICGraphStore(); diff --git a/src/ic/ic-status-store.js b/src/ic/ic-status-store.js new file mode 100644 index 0000000..4895a8d --- /dev/null +++ b/src/ic/ic-status-store.js @@ -0,0 +1,72 @@ +/** + * This file is part of VILLASweb. + * + * VILLASweb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * VILLASweb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with VILLASweb. If not, see . + ******************************************************************************/ + +import ArrayStore from '../common/array-store'; +import ICDataDataManager from './ic-data-data-manager'; + +class ICStatusStore extends ArrayStore { + constructor() { + super('ic-status', ICDataDataManager); + } + + + reduce(state, action) { + switch(action.type) { + + case 'ic-status/get-status': + ICDataDataManager.getStatus(action.url, action.socketname, action.token, action.icid, action.ic); + return super.reduce(state, action); + + case 'ic-status/status-received': + let tempData = action.data; + tempData.icID = action.icid; + + return this.updateElements(state, [tempData]); + + case 'ic-status/status-error': + console.log("status error"); + return state; + + case 'ic-status/restart': + ICDataDataManager.restart(action.url, action.socketname, action.token); + return state; + + case 'ic-status/restart-successful': + return state; + + case 'ic-status/restart-error': + console.log("restart error"); + return state; + + case 'ic-status/shutdown': + ICDataDataManager.shutdown(action.url, action.socketname, action.token); + return state; + + case 'ic-status/shutdown-successful': + return state; + + case 'ic-status/shutdown-error': + console.log("shutdown error"); + return state; + + default: + return super.reduce(state, action); + } + } +} + +export default new ICStatusStore(); diff --git a/src/ic/ic-store.js b/src/ic/ic-store.js index 456aba8..7210477 100644 --- a/src/ic/ic-store.js +++ b/src/ic/ic-store.js @@ -20,6 +20,7 @@ import ICsDataManager from './ics-data-manager'; import ICDataDataManager from './ic-data-data-manager'; import NotificationsDataManager from "../common/data-managers/notifications-data-manager"; import NotificationsFactory from "../common/data-managers/notifications-factory"; +import AppDispatcher from '../common/app-dispatcher'; class InfrastructureComponentStore extends ArrayStore { constructor() { @@ -29,6 +30,27 @@ class InfrastructureComponentStore extends ArrayStore { reduce(state, action) { switch(action.type) { case 'ics/loaded': + action.data.forEach(ic => { + if (ic.type === "villas-node" || ic.type === "villas-relay") { + let splitWebsocketURL = ic.websocketurl.split("/"); + AppDispatcher.dispatch({ + type: 'ic-status/get-status', + url: ic.apiurl + "/status", + socketname: splitWebsocketURL[splitWebsocketURL.length - 1], + token: action.token, + icid: ic.id, + ic: ic + }); + + AppDispatcher.dispatch({ + type: 'ic-graph/get-graph', + url: ic.apiurl + "/graph.svg", + socketname: splitWebsocketURL[splitWebsocketURL.length - 1], + token: action.token, + icid: ic.id, + }); + } + }) return super.reduce(state, action); diff --git a/src/ic/ics.js b/src/ic/ics.js index 0d43077..b96cf89 100644 --- a/src/ic/ics.js +++ b/src/ic/ics.js @@ -24,6 +24,8 @@ import moment from 'moment' import AppDispatcher from '../common/app-dispatcher'; import InfrastructureComponentStore from './ic-store'; +import ICStatusStore from './ic-status-store'; +import ICGraphStore from './ic-graph-store'; import Icon from '../common/icon'; import Table from '../common/table'; @@ -38,7 +40,7 @@ import DeleteDialog from '../common/dialogs/delete-dialog'; class InfrastructureComponents extends Component { static getStores() { - return [ InfrastructureComponentStore ]; + return [ InfrastructureComponentStore, ICStatusStore, ICGraphStore ]; } static statePrio(state) { @@ -74,10 +76,17 @@ class InfrastructureComponents extends Component { } }); + const icStatus = ICStatusStore.getState(); + const icGraph = ICGraphStore.getState(); + return { sessionToken: localStorage.getItem("token"), ics: ics, + icStatus: icStatus, + icGraph: icGraph, modalIC: {}, + modalICStatus: {}, + modalICGraph: {}, deleteModal: false, icModal: false, selectedICs: [], @@ -91,7 +100,7 @@ class InfrastructureComponents extends Component { token: this.state.sessionToken, }); - // Start timer for periodic refresh + // Start timer for periodic refresh this.timer = window.setInterval(() => this.refresh(), 10000); } @@ -109,6 +118,7 @@ class InfrastructureComponents extends Component { type: 'ics/start-load', token: this.state.sessionToken, }); + } } @@ -218,12 +228,12 @@ class InfrastructureComponents extends Component { } static isICOutdated(component) { - if (!component.stateUpdatedAt) + if (!component.stateUpdateAt) return true; const fiveMinutes = 5 * 60 * 1000; - return Date.now() - new Date(component.stateUpdatedAt) > fiveMinutes; + return Date.now() - new Date(component.stateUpdateAt) > fiveMinutes; } stateLabelStyle(state, component){ @@ -302,7 +312,7 @@ class InfrastructureComponents extends Component { if(managedExternally){ return } else { - return + return "" } } @@ -318,14 +328,44 @@ class InfrastructureComponents extends Component { modifyNameColumn(name){ let ic = this.state.ics.find(ic => ic.name === name); - let index = this.state.ics.indexOf(ic); + if(ic.type === "villas-node" || ic.type === "villas-relay"){ - return this.setState({ icModal: true, modalIC: ic, modalIndex: index })}>{name} } + return this.openICStatus(ic)}>{name} } else{ return {name} } } + openICStatus(ic){ + + let index = this.state.ics.indexOf(ic); + let icStatus = this.state.icStatus.find(status => status.icID === ic.id); + let icGraph = this.state.icGraph.find(graph => graph.icID === ic.id); + + this.setState({ icModal: true, modalIC: ic, modalICStatus: icStatus, modalICGraph: icGraph, modalIndex: index }) + } + + sendControlCommand(command,ic){ + let splitWebsocketURL = ic.websocketurl.split("/"); + + if(command === "restart"){ + AppDispatcher.dispatch({ + type: 'ic-status/restart', + url: ic.apiurl + "/restart", + socketname: splitWebsocketURL[splitWebsocketURL.length - 1], + token: this.state.sessionToken, + }); + }else if(command === "shutdown"){ + AppDispatcher.dispatch({ + type: 'ic-status/shutdown', + url: ic.apiurl + "/shutdown", + socketname: splitWebsocketURL[splitWebsocketURL.length - 1], + token: this.state.sessionToken, + }); + } + + } + render() { const buttonStyle = { marginLeft: '10px' @@ -396,7 +436,7 @@ class InfrastructureComponents extends Component { this.closeNewModal(data)} /> this.closeEditModal(data)} ic={this.state.modalIC} /> this.closeImportModal(data)} /> - this.closeICModal(data)} ic={this.state.modalIC} token={this.state.sessionToken} /> + this.closeICModal(data)} ic={this.state.modalIC} token={this.state.sessionToken} userRole={this.state.currentUser.role} icStatus={this.state.modalICStatus} icGraph={this.state.modalICGraph} sendControlCommand={(command, ic) => this.sendControlCommand(command, ic)}/> this.closeDeleteModal(e)} />