mirror of
https://git.rwth-aachen.de/acs/public/villas/web/
synced 2025-03-09 00:00:01 +01:00
Merge branch 'master' into feature-whitelabel
# Conflicts: # package-lock.json # package.json # src/app.js # src/common/icon-button.js # src/ic/ics.js # src/scenario/scenario.js # src/scenario/scenarios.js # src/user/users.js
This commit is contained in:
commit
f2d0006021
31 changed files with 2560 additions and 2062 deletions
|
@ -19,7 +19,7 @@ stages:
|
|||
|
||||
build:
|
||||
stage: build
|
||||
image: node:12.2
|
||||
image: node:14.16
|
||||
before_script:
|
||||
- mkdir -p build
|
||||
script:
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
# along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ******************************************************************************
|
||||
|
||||
FROM node:12.2 AS builder
|
||||
FROM node:14.16 AS builder
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
|
|
894
package-lock.json
generated
894
package-lock.json
generated
File diff suppressed because it is too large
Load diff
39
package.json
39
package.json
|
@ -3,28 +3,29 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.34",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.2",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||
"@fortawesome/react-fontawesome": "^0.1.14",
|
||||
"@rjsf/core": "^2.5.1",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"bootstrap": "^4.6.0",
|
||||
"classnames": "^2.2.6",
|
||||
"d3": "^6.6.0",
|
||||
"d3-array": "^2.12.0",
|
||||
"classnames": "^2.3.1",
|
||||
"d3": "^6.7.0",
|
||||
"d3-array": "^2.12.1",
|
||||
"d3-axis": "^2.1.0",
|
||||
"d3-scale": "^3.2.3",
|
||||
"d3-scale": "^3.3.0",
|
||||
"d3-scale-chromatic": "^2.0.0",
|
||||
"d3-selection": "^2.0.0",
|
||||
"d3-shape": "^2.1.0",
|
||||
"d3-time-format": "^3.0.0",
|
||||
"es6-promise": "^4.2.8",
|
||||
"file-saver": "^2.0.5",
|
||||
"flux": "^3.1.3",
|
||||
"flux": "^4.0.1",
|
||||
"gaugeJS": "^1.3.7",
|
||||
"handlebars": "^4.7.6",
|
||||
"jquery": "^3.5.1",
|
||||
"handlebars": "^4.7.7",
|
||||
"jquery": "^3.6.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jszip": "^3.5.0",
|
||||
"jszip": "^3.6.0",
|
||||
"libcimsvg": "git+https://git.rwth-aachen.de/acs/public/cim/pintura-npm-package.git",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.1",
|
||||
|
@ -32,17 +33,17 @@
|
|||
"multiselect-react-dropdown": "^1.6.11",
|
||||
"popper.js": "^1.16.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"rc-slider": "^9.7.1",
|
||||
"react": "^16.14.0",
|
||||
"rc-slider": "^9.7.2",
|
||||
"react": "^17.0.2",
|
||||
"react-bootstrap": "^1.5.2",
|
||||
"react-collapse": "^5.1.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-contexify": "^4.1.1",
|
||||
"react-dnd": "^10.0.2",
|
||||
"react-dnd-html5-backend": "^10.0.2",
|
||||
"react-dom": "^16.14.0",
|
||||
"react-contexify": "^5.0.0",
|
||||
"react-dnd": "^13.1.1",
|
||||
"react-dnd-html5-backend": "^12.1.1",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-fullscreenable": "^2.5.1-0",
|
||||
"react-grid-system": "^7.1.1",
|
||||
"react-grid-system": "^7.1.2",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-notification-system": "^0.4.0",
|
||||
"react-rnd": "^10.2.4",
|
||||
|
@ -51,8 +52,8 @@
|
|||
"react-svg-pan-zoom": "^3.10.0",
|
||||
"react-trafficlight": "^5.2.1",
|
||||
"superagent": "^6.1.0",
|
||||
"swagger-ui-react": "^3.45.0",
|
||||
"typescript": "^4.2.3"
|
||||
"swagger-ui-react": "^3.47.1",
|
||||
"typescript": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
|
|
21
src/app.js
21
src/app.js
|
@ -17,7 +17,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import HTML5Backend from 'react-dnd-html5-backend';
|
||||
import { HTML5Backend }from 'react-dnd-html5-backend';
|
||||
import NotificationSystem from 'react-notification-system';
|
||||
import { Redirect, Route } from 'react-router-dom';
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
@ -30,12 +30,14 @@ import Header from './common/header';
|
|||
import Menu from './common/menu';
|
||||
|
||||
import InfrastructureComponents from './ic/ics';
|
||||
import InfrastructureComponent from './ic/ic';
|
||||
import Dashboard from './dashboard/dashboard';
|
||||
import Scenarios from './scenario/scenarios';
|
||||
import Scenario from './scenario/scenario';
|
||||
import Users from './user/users';
|
||||
import User from './user/user';
|
||||
import APIBrowser from './common/api-browser';
|
||||
import LoginStore from './user/login-store'
|
||||
|
||||
|
||||
import './styles/app.css';
|
||||
|
@ -48,16 +50,20 @@ class App extends React.Component {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'config/load',
|
||||
});
|
||||
this.state = {}
|
||||
}
|
||||
|
||||
this.state = {}
|
||||
static getStores() {
|
||||
return [LoginStore]
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
NotificationsDataManager.setSystem(this.refs.notificationSystem);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'config/load',
|
||||
});
|
||||
|
||||
// if token stored locally, we are already logged-in
|
||||
let token = localStorage.getItem("token");
|
||||
if (token != null && token !== '') {
|
||||
|
@ -70,7 +76,7 @@ class App extends React.Component {
|
|||
});
|
||||
} else {
|
||||
let currentUser = JSON.parse(localStorage.getItem("currentUser"));
|
||||
console.log("Already logged-in")
|
||||
console.log("Logged-in as user ", currentUser.username)
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/logged-in',
|
||||
token: token,
|
||||
|
@ -115,7 +121,8 @@ class App extends React.Component {
|
|||
{ pages.scenarios ? <Route exact path="/scenarios" component={Scenarios} /> : '' }
|
||||
{ pages.scenarios ? <Route path="/scenarios/:scenario" component={Scenario} /> : '' }
|
||||
{ pages.scenarios ? <Route path="/dashboards/:dashboard" component={Dashboard} /> : '' }
|
||||
{ pages.infrastructure ? <Route path="/infrastructure" component={InfrastructureComponents} /> : '' }
|
||||
{ pages.infrastructure ? <Route exact path="/infrastructure" component={InfrastructureComponents} /> : '' }
|
||||
{ pages.infrastructure ? <Route path="/infrastructure/:ic" component={InfrastructureComponent} /> : '' }
|
||||
{ pages.account ? <Route path="/account" component={User} /> : '' }
|
||||
{ pages.users ? <Route path="/users" component={Users} /> : '' }
|
||||
{ pages.api ? <Route path="/api" component={APIBrowser} /> : '' }
|
||||
|
|
|
@ -39,7 +39,7 @@ class APIBrowser extends React.Component {
|
|||
return spec;
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
this._asyncRequest = RestAPI.get('/api/v2/openapi')
|
||||
.then((spec) => {
|
||||
this._asyncRequest = null;
|
||||
|
|
|
@ -25,31 +25,32 @@ import Icon from '../common/icon';
|
|||
class IconButton extends React.Component {
|
||||
|
||||
render() {
|
||||
const altButtonStyle = {
|
||||
marginLeft: '10px',
|
||||
let btn = <Button
|
||||
variant={this.props.variant ? this.props.variant : 'light'}
|
||||
disabled={this.props.disabled}
|
||||
onClick={this.props.onClick}
|
||||
style={this.props.buttonStyle}
|
||||
>
|
||||
<Icon
|
||||
icon={this.props.icon}
|
||||
classname={'icon-color'}
|
||||
style={this.props.iconStyle}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
let button;
|
||||
if (!this.props.tooltip || this.props.hidetooltip) {
|
||||
button = btn;
|
||||
} else {
|
||||
button = <OverlayTrigger
|
||||
key={this.props.ikey}
|
||||
placement={this.props.tipPlacement ? this.props.tipPlacement : 'top'}
|
||||
overlay={<Tooltip id={`tooltip-${this.props.ikey}`}>{this.props.tooltip}</Tooltip>} >
|
||||
{btn}
|
||||
</OverlayTrigger>
|
||||
}
|
||||
|
||||
const iconStyle = {
|
||||
height: '30px',
|
||||
width: '30px'
|
||||
}
|
||||
|
||||
return <OverlayTrigger
|
||||
key={this.props.overlaykey}
|
||||
placement={'top'}
|
||||
overlay={<Tooltip id={`tooltip-${"add"}`}>{this.props.tooltip}</Tooltip>} >
|
||||
<Button
|
||||
variant='light'
|
||||
onClick={this.props.onClick}
|
||||
style={altButtonStyle}
|
||||
>
|
||||
<Icon
|
||||
icon={this.props.icon}
|
||||
classname={'icon-color'}
|
||||
style={iconStyle}
|
||||
/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
62
src/common/icon-toggle-button.js
Normal file
62
src/common/icon-toggle-button.js
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { ToggleButton, ButtonGroup, Tooltip, OverlayTrigger } from 'react-bootstrap';
|
||||
|
||||
import Icon from './icon';
|
||||
|
||||
|
||||
class IconToggleButton extends React.Component {
|
||||
|
||||
render() {
|
||||
let tooltip = this.props.checked ? this.props.tooltipChecked : this.props.tooltipUnchecked;
|
||||
|
||||
return <OverlayTrigger
|
||||
key={this.props.ikey}
|
||||
placement={'top'}
|
||||
overlay={<Tooltip id={`tooltip-${this.props.ikey}`}>{tooltip}</Tooltip>} >
|
||||
<ButtonGroup toggle>
|
||||
<ToggleButton
|
||||
variant={this.props.variant ? this.props.variant : 'light'}
|
||||
type='checkbox'
|
||||
onChange={this.props.onChange}
|
||||
style={this.props.buttonStyle}
|
||||
disabled={this.props.disabled}
|
||||
checked={this.props.checked}
|
||||
>
|
||||
{this.props.checked ?
|
||||
<Icon
|
||||
icon={this.props.checkedIcon}
|
||||
classname={'icon-color'}
|
||||
style={this.props.iconStyle}
|
||||
/>
|
||||
:
|
||||
<Icon
|
||||
icon={this.props.uncheckedIcon}
|
||||
classname={'icon-color'}
|
||||
style={this.props.iconStyle}
|
||||
/>
|
||||
}
|
||||
</ToggleButton>
|
||||
</ButtonGroup>
|
||||
</OverlayTrigger>
|
||||
}
|
||||
}
|
||||
|
||||
export default IconToggleButton;
|
|
@ -59,11 +59,7 @@ class SidebarMenu extends React.Component {
|
|||
AppDispatcher.dispatch({
|
||||
type: 'users/logout'
|
||||
});
|
||||
// The Login Store is deleted automatically
|
||||
|
||||
// discard login token and current User
|
||||
localStorage.setItem('token', '');
|
||||
localStorage.setItem('currentUser', '');
|
||||
// The Login Store and local storage are deleted automatically
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -27,7 +27,10 @@ class TableColumn extends Component {
|
|||
deleteButton: false,
|
||||
showDeleteButton: null,
|
||||
exportButton: false,
|
||||
signalButton: false,
|
||||
duplicateButton: false,
|
||||
isLocked: null,
|
||||
locked: false,
|
||||
link: '/',
|
||||
linkKey: '',
|
||||
dataIndex: false,
|
||||
|
|
|
@ -20,6 +20,9 @@ import _ from 'lodash';
|
|||
import { Table, Button, Form, Tooltip, OverlayTrigger } from 'react-bootstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Icon from './icon';
|
||||
import IconToggleButton from './icon-toggle-button';
|
||||
import IconButton from '../common/icon-button';
|
||||
|
||||
|
||||
class CustomTable extends Component {
|
||||
constructor(props) {
|
||||
|
@ -45,6 +48,7 @@ class CustomTable extends Component {
|
|||
static addCell(data, index, child) {
|
||||
// add data to cell
|
||||
let content = null;
|
||||
let childkey = 0;
|
||||
|
||||
if ('dataKeys' in child.props) {
|
||||
for (let key of child.props.dataKeys) {
|
||||
|
@ -90,7 +94,7 @@ class CustomTable extends Component {
|
|||
onClick={() => child.props.onDownload(contentkey)}
|
||||
disabled={child.props.onDownload == null}>
|
||||
{contentkey + ' '}
|
||||
<Icon icon='file-download' />
|
||||
<Icon icon='file-download' classname={'icon-color'}/>
|
||||
</Button>
|
||||
</OverlayTrigger>);
|
||||
});
|
||||
|
@ -125,26 +129,26 @@ class CustomTable extends Component {
|
|||
cell.push(index);
|
||||
}
|
||||
|
||||
let isLocked = child.props.locked || (child.props.isLocked != null && child.props.isLocked(index));
|
||||
|
||||
// add buttons
|
||||
let showEditButton = child.props.showEditButton !== null && child.props.showEditButton !== undefined
|
||||
let showEditButton = child.props.showEditButton !== null && child.props.showEditButton !== undefined
|
||||
? child.props.showEditButton(index)
|
||||
: true;
|
||||
|
||||
if (child.props.editButton && showEditButton) {
|
||||
cell.push(
|
||||
<OverlayTrigger
|
||||
key={0}
|
||||
placement={'bottom'}
|
||||
overlay={<Tooltip id={`tooltip-${"edit"}`}> Edit </Tooltip>}
|
||||
>
|
||||
<Button
|
||||
variant='table-control-button'
|
||||
onClick={() => child.props.onEdit(index)}
|
||||
disabled={child.props.onEdit == null} >
|
||||
<Icon icon='edit' />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
<IconButton
|
||||
key={childkey++}
|
||||
ikey={childkey++}
|
||||
icon={'edit'}
|
||||
disabled={child.props.onEdit == null || isLocked}
|
||||
hidetooltip={isLocked}
|
||||
tooltip={"Edit"}
|
||||
tipPlacement={'bottom'}
|
||||
onClick={() => child.props.onEdit(index)}
|
||||
variant={'table-control-button'}
|
||||
/>)
|
||||
}
|
||||
|
||||
if (child.props.checkbox) {
|
||||
|
@ -164,89 +168,112 @@ class CustomTable extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
if (child.props.lockButton) {
|
||||
cell.push(
|
||||
<IconToggleButton
|
||||
ikey={childkey++}
|
||||
onChange={() => child.props.onChangeLock(index)}
|
||||
checked={isLocked}
|
||||
checkedIcon='lock'
|
||||
uncheckedIcon='lock-open'
|
||||
tooltipChecked='Scenario is locked, cannot be edited'
|
||||
tooltipUnchecked='Scenario is unlocked, can be edited'
|
||||
disabled={false}
|
||||
variant={'table-control-button'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (child.props.exportButton) {
|
||||
cell.push(
|
||||
<OverlayTrigger
|
||||
key={1}
|
||||
placement={'bottom'}
|
||||
overlay={<Tooltip id={`tooltip-${"export"}`}> Export </Tooltip>}
|
||||
>
|
||||
<Button
|
||||
variant='table-control-button'
|
||||
onClick={() => child.props.onExport(index)}
|
||||
disabled={child.props.onExport == null}>
|
||||
<Icon icon='download' />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
<IconButton
|
||||
key={childkey++}
|
||||
ikey={childkey++}
|
||||
icon={'download'}
|
||||
disabled={child.props.onExport == null}
|
||||
tooltip={"Export"}
|
||||
tipPlacement={'bottom'}
|
||||
onClick={() => child.props.onExport(index)}
|
||||
variant={'table-control-button'}
|
||||
/>);
|
||||
}
|
||||
|
||||
if (child.props.signalButton) {
|
||||
cell.push(
|
||||
<IconButton
|
||||
key={childkey++}
|
||||
ikey={childkey++}
|
||||
icon={'wave-square'}
|
||||
disabled={child.props.onAutoConf == null || child.props.locked }
|
||||
hidetooltip={isLocked}
|
||||
tooltip={"Autoconfigure Signals"}
|
||||
tipPlacement={'bottom'}
|
||||
onClick={() => child.props.onAutoConf(index)}
|
||||
variant={'table-control-button'}
|
||||
/>);
|
||||
}
|
||||
|
||||
if (child.props.duplicateButton) {
|
||||
cell.push(
|
||||
<OverlayTrigger
|
||||
key={2}
|
||||
placement={'bottom'}
|
||||
overlay={<Tooltip id={`tooltip-${"duplicate"}`}> Duplicate </Tooltip>} >
|
||||
<Button
|
||||
variant='table-control-button'
|
||||
onClick={() => child.props.onDuplicate(index)}
|
||||
disabled={child.props.onDuplicate == null}>
|
||||
<Icon icon='copy' />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
<IconButton
|
||||
key={childkey++}
|
||||
ikey={childkey++}
|
||||
icon={'copy'}
|
||||
disabled={child.props.onDuplicate == null || child.props.locked}
|
||||
hidetooltip={isLocked}
|
||||
tooltip={"Duplicate"}
|
||||
tipPlacement={'bottom'}
|
||||
onClick={() => child.props.onDuplicate(index)}
|
||||
variant={'table-control-button'}
|
||||
/>);
|
||||
}
|
||||
|
||||
if (child.props.addRemoveFilesButton) {
|
||||
cell.push(
|
||||
<OverlayTrigger
|
||||
key={3}
|
||||
placement={'bottom'}
|
||||
overlay={<Tooltip id={`tooltip-${"export"}`}>Add/remove File(s)</Tooltip>} >
|
||||
<Button
|
||||
variant='table-control-button'
|
||||
onClick={() => child.props.onAddRemove(index)}
|
||||
disabled={child.props.onAddRemove == null}>
|
||||
<Icon icon='file' />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
<IconButton
|
||||
key={childkey++}
|
||||
ikey={childkey++}
|
||||
icon={'file'}
|
||||
disabled={child.props.onAddRemove == null || isLocked}
|
||||
hidetooltip={isLocked}
|
||||
tooltip={"Add/remove File(s)"}
|
||||
tipPlacement={'bottom'}
|
||||
onClick={() => child.props.onAddRemove(index)}
|
||||
variant={'table-control-button'}
|
||||
/>);
|
||||
}
|
||||
|
||||
if (child.props.downloadAllButton) {
|
||||
cell.push(
|
||||
<OverlayTrigger
|
||||
key={4}
|
||||
placement={'bottom'}
|
||||
overlay={<Tooltip id={`tooltip-${"export"}`}>Download All Files</Tooltip>} >
|
||||
<Button
|
||||
variant='table-control-button'
|
||||
onClick={() => child.props.onDownloadAll(index)}
|
||||
disabled={child.props.onDownloadAll == null}>
|
||||
<Icon icon='file-download' />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
<IconButton
|
||||
key={childkey++}
|
||||
ikey={childkey++}
|
||||
icon={'file-download'}
|
||||
disabled={child.props.onDownloadAll == null}
|
||||
tooltip={"Download All Files"}
|
||||
tipPlacement={'bottom'}
|
||||
onClick={() => child.props.onDownloadAll(index)}
|
||||
variant={'table-control-button'}
|
||||
/>);
|
||||
}
|
||||
|
||||
let showDeleteButton = child.props.showDeleteButton !== null && child.props.showDeleteButton !== undefined
|
||||
let showDeleteButton = child.props.showDeleteButton !== null && child.props.showDeleteButton !== undefined
|
||||
? child.props.showDeleteButton(index)
|
||||
: true;
|
||||
|
||||
if (child.props.deleteButton && showDeleteButton) {
|
||||
cell.push(
|
||||
<OverlayTrigger
|
||||
key={5}
|
||||
placement={'bottom'}
|
||||
overlay={<Tooltip id={`tooltip-${"delete"}`}> Delete </Tooltip>} >
|
||||
<Button
|
||||
variant='table-control-button'
|
||||
onClick={() => child.props.onDelete(index)}
|
||||
disabled={child.props.onDelete == null}>
|
||||
<Icon icon='trash' />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
<IconButton
|
||||
key={childkey++}
|
||||
ikey={childkey++}
|
||||
icon={'trash'}
|
||||
disabled={child.props.onDelete == null || isLocked}
|
||||
hidetooltip={isLocked}
|
||||
tooltip={"Delete"}
|
||||
tipPlacement={'bottom'}
|
||||
onClick={() => child.props.onDelete(index)}
|
||||
variant={'table-control-button'}
|
||||
/>);
|
||||
}
|
||||
|
||||
return cell;
|
||||
|
@ -290,7 +317,10 @@ class CustomTable extends Component {
|
|||
const row = [];
|
||||
|
||||
for (let child of props.children) {
|
||||
row.push(CustomTable.addCell(data, index, child));
|
||||
// check whether empty <></> object has been given
|
||||
if (Object.keys(child.props).length !== 0) {
|
||||
row.push(CustomTable.addCell(data, index, child));
|
||||
}
|
||||
}
|
||||
|
||||
return row;
|
||||
|
@ -348,14 +378,14 @@ class CustomTable extends Component {
|
|||
value={cell}
|
||||
onChange={(event) => children[cellIndex].props.onInlineChange(event, rowIndex, cellIndex)}
|
||||
ref={ref => { this.activeInput = ref; }} />
|
||||
: <span>
|
||||
: <span>
|
||||
{
|
||||
cell.map((element, elementIndex) =>
|
||||
<span key={elementIndex}>{element}</span>
|
||||
)
|
||||
}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
</td>
|
||||
})
|
||||
}
|
||||
|
|
462
src/componentconfig/config-table.js
Normal file
462
src/componentconfig/config-table.js
Normal file
|
@ -0,0 +1,462 @@
|
|||
/**
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, {Component} from "react";
|
||||
import FileSaver from 'file-saver';
|
||||
import IconButton from "../common/icon-button";
|
||||
import Table from "../common/table";
|
||||
import TableColumn from "../common/table-column";
|
||||
import DeleteDialog from "../common/dialogs/delete-dialog";
|
||||
import AppDispatcher from "../common/app-dispatcher";
|
||||
import NotificationsDataManager from "../common/data-managers/notifications-data-manager";
|
||||
import ICAction from "../ic/ic-action";
|
||||
import EditConfigDialog from "./edit-config";
|
||||
import ImportConfigDialog from "./import-config";
|
||||
import EditSignalMappingDialog from "../signal/edit-signal-mapping";
|
||||
|
||||
class ConfigTable extends Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
editConfigModal: false,
|
||||
modalConfigData: {},
|
||||
modalConfigIndex: 0,
|
||||
deleteConfigModal: false,
|
||||
importConfigModal: false,
|
||||
newConfig: false,
|
||||
selectedConfigs: [],
|
||||
ExternalICInUse: false,
|
||||
editOutputSignalsModal: false,
|
||||
editInputSignalsModal: false,
|
||||
}
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
|
||||
let ExternalICInUse = false
|
||||
|
||||
for (let config of props.configs){
|
||||
for (let component of props.ics) {
|
||||
if ((config.icID === component.id) && (component.managedexternally === true)) {
|
||||
ExternalICInUse = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ExternalICInUse: ExternalICInUse
|
||||
};
|
||||
}
|
||||
|
||||
addConfig() {
|
||||
const config = {
|
||||
scenarioID: this.props.scenario.id,
|
||||
name: 'New Component Configuration',
|
||||
icID: this.props.ics.length > 0 ? this.props.ics[0].id : null,
|
||||
startParameters: {},
|
||||
};
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'configs/start-add',
|
||||
data: config,
|
||||
token: this.props.sessionToken
|
||||
});
|
||||
|
||||
this.setState({ newConfig: true });
|
||||
|
||||
}
|
||||
|
||||
closeEditConfigModal(data) {
|
||||
this.setState({ editConfigModal: false, newConfig: false });
|
||||
|
||||
if (data) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'configs/start-edit',
|
||||
data: data,
|
||||
token: this.props.sessionToken,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
closeDeleteConfigModal(confirmDelete) {
|
||||
this.setState({ deleteConfigModal: false });
|
||||
|
||||
if (confirmDelete === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'configs/start-remove',
|
||||
data: this.state.modalConfigData,
|
||||
token: this.props.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
importConfig(data) {
|
||||
this.setState({ importConfigModal: false });
|
||||
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newConfig = JSON.parse(JSON.stringify(data.config))
|
||||
|
||||
newConfig["scenarioID"] = this.props.scenario.id;
|
||||
newConfig.name = data.name;
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'configs/start-add',
|
||||
data: newConfig,
|
||||
token: this.props.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
copyConfig(index) {
|
||||
let config = JSON.parse(JSON.stringify(this.props.configs[index]));
|
||||
|
||||
let signals = JSON.parse(JSON.stringify(this.props.signals.filter(s => s.configID === parseInt(config.id, 10))));
|
||||
signals.forEach((signal) => {
|
||||
delete signal.configID;
|
||||
delete signal.id;
|
||||
})
|
||||
|
||||
// two separate lists for inputMapping and outputMapping
|
||||
let inputSignals = signals.filter(s => s.direction === 'in');
|
||||
let outputSignals = signals.filter(s => s.direction === 'out');
|
||||
|
||||
// add signal mappings to config
|
||||
config["inputMapping"] = inputSignals;
|
||||
config["outputMapping"] = outputSignals;
|
||||
|
||||
delete config.id;
|
||||
delete config.scenarioID;
|
||||
delete config.inputLength;
|
||||
delete config.outputLength;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
exportConfig(index) {
|
||||
let config = this.copyConfig(index);
|
||||
|
||||
// show save dialog
|
||||
const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });
|
||||
FileSaver.saveAs(blob, 'config-' + config.name + '.json');
|
||||
}
|
||||
|
||||
duplicateConfig(index) {
|
||||
let newConfig = this.copyConfig(index);
|
||||
newConfig["scenarioID"] = this.props.scenario.id;
|
||||
newConfig.name = newConfig.name + '_copy';
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'configs/start-add',
|
||||
data: newConfig,
|
||||
token: this.props.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
onConfigChecked(index, event) {
|
||||
const selectedConfigs = Object.assign([], this.state.selectedConfigs);
|
||||
for (let key in selectedConfigs) {
|
||||
if (selectedConfigs[key] === index) {
|
||||
// update existing entry
|
||||
if (event.target.checked) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedConfigs.splice(key, 1);
|
||||
|
||||
this.setState({ selectedConfigs: selectedConfigs });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// add new entry
|
||||
if (event.target.checked === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedConfigs.push(index);
|
||||
this.setState({ selectedConfigs: selectedConfigs });
|
||||
}
|
||||
|
||||
usesExternalIC(index) {
|
||||
for (let component of this.props.ics) {
|
||||
if (component.id === this.props.configs[index].icID) {
|
||||
if (component.managedexternally === true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
getICName(icID) {
|
||||
for (let ic of this.props.ics) {
|
||||
if (ic.id === icID) {
|
||||
return ic.name || ic.uuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ##############################################
|
||||
* Signal modification methods
|
||||
############################################## */
|
||||
|
||||
closeEditSignalsModal(direction) {
|
||||
|
||||
// reload the config
|
||||
AppDispatcher.dispatch({
|
||||
type: 'configs/start-load',
|
||||
data: this.state.modalConfigData.id,
|
||||
token: this.props.sessionToken
|
||||
});
|
||||
|
||||
if (direction === "in") {
|
||||
this.setState({ editInputSignalsModal: false });
|
||||
} else if (direction === "out") {
|
||||
this.setState({ editOutputSignalsModal: false });
|
||||
}
|
||||
}
|
||||
|
||||
signalsAutoConf(index) {
|
||||
let componentConfig = this.props.configs[index];
|
||||
// determine apiurl of infrastructure component
|
||||
let ic = this.props.ics.find(ic => ic.id === componentConfig.icID)
|
||||
if (!ic.type.includes("villas-node")) {
|
||||
let message = "Cannot autoconfigure signals for IC type " + ic.type + " of category " + ic.category + ". This is only possible for gateway ICs of type 'VILLASnode'."
|
||||
console.warn(message);
|
||||
|
||||
const SIGNAL_AUTOCONF_WARN_NOTIFICATION = {
|
||||
title: 'Failed to load signal config for IC ' + ic.name,
|
||||
message: message,
|
||||
level: 'warning'
|
||||
};
|
||||
NotificationsDataManager.addNotification(SIGNAL_AUTOCONF_WARN_NOTIFICATION);
|
||||
return;
|
||||
}
|
||||
|
||||
let splitWebsocketURL = ic.websocketurl.split("/")
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'signals/start-autoconfig',
|
||||
url: ic.apiurl + "/nodes",
|
||||
socketname: splitWebsocketURL[splitWebsocketURL.length - 1],
|
||||
token: this.props.sessionToken,
|
||||
configID: componentConfig.id
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
startPintura(configIndex) {
|
||||
let config = this.props.configs[configIndex];
|
||||
|
||||
// get xml / CIM file
|
||||
let files = []
|
||||
for (let id of config.fileIDs) {
|
||||
for (let file of this.props.files) {
|
||||
if (file.id === id && ['xml'].some(e => file.type.includes(e))) {
|
||||
files.push(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (files.length > 1) {
|
||||
// more than one CIM file...
|
||||
console.warn("There is more than one CIM file selected in this component configuration. I will open them all in a separate tab.")
|
||||
}
|
||||
|
||||
let baseURL = 'aaa.bbb.ccc.ddd/api/v2/files/'
|
||||
for (let file of files) {
|
||||
// endpoint param serves for download and upload of CIM file, token is required for authentication
|
||||
let params = {
|
||||
token: this.props.sessionToken,
|
||||
endpoint: baseURL + String(file.id),
|
||||
}
|
||||
|
||||
// TODO start Pintura for editing CIM/ XML file from here
|
||||
console.warn("Starting Pintura... and nothing happens so far :-) ", params)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
marginLeft: '10px',
|
||||
}
|
||||
|
||||
const iconStyle = {
|
||||
height: '30px',
|
||||
width: '30px'
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/*Component Configurations table*/}
|
||||
<h2 style={this.props.tableHeadingStyle}>Component Configurations
|
||||
<span className='icon-button'>
|
||||
<IconButton
|
||||
ikey={0}
|
||||
tooltip='Add Component Configuration'
|
||||
onClick={() => this.addConfig()}
|
||||
icon='plus'
|
||||
disabled={this.props.locked}
|
||||
hidetooltip={this.props.locked}
|
||||
buttonStyle={buttonStyle}
|
||||
iconStyle={iconStyle}
|
||||
/>
|
||||
<IconButton
|
||||
ikey={1}
|
||||
tooltip='Import Component Configuration'
|
||||
onClick={() => this.setState({ importConfigModal: true })}
|
||||
icon='upload'
|
||||
disabled={this.props.locked}
|
||||
hidetooltip={this.props.locked}
|
||||
buttonStyle={buttonStyle}
|
||||
iconStyle={iconStyle}
|
||||
/>
|
||||
</span>
|
||||
</h2>
|
||||
<Table data={this.props.configs}>
|
||||
<TableColumn
|
||||
checkbox
|
||||
checkboxDisabled={(index) => !this.usesExternalIC(index)}
|
||||
onChecked={(index, event) => this.onConfigChecked(index, event)}
|
||||
width={20}
|
||||
/>
|
||||
{this.props.currentUser.role === "Admin" ?
|
||||
<TableColumn
|
||||
title='ID'
|
||||
dataKey='id'
|
||||
width={70}
|
||||
/>
|
||||
: <></>
|
||||
}
|
||||
<TableColumn
|
||||
title='Name'
|
||||
dataKey='name'
|
||||
width={250}
|
||||
/>
|
||||
<TableColumn
|
||||
title='# Output Signals'
|
||||
dataKey='outputLength'
|
||||
editButton
|
||||
onEdit={index => this.setState({ editOutputSignalsModal: true, modalConfigData: this.props.configs[index], modalConfigIndex: index })}
|
||||
width={150}
|
||||
locked={this.props.locked}
|
||||
/>
|
||||
<TableColumn
|
||||
title='# Input Signals'
|
||||
dataKey='inputLength'
|
||||
editButton
|
||||
onEdit={index => this.setState({ editInputSignalsModal: true, modalConfigData: this.props.configs[index], modalConfigIndex: index })}
|
||||
width={150}
|
||||
locked={this.props.locked}
|
||||
/>
|
||||
<TableColumn
|
||||
title='Autoconfigure Signals'
|
||||
signalButton
|
||||
onAutoConf={(index) => this.signalsAutoConf(index)}
|
||||
width={170}
|
||||
locked={this.props.locked}
|
||||
/>
|
||||
<TableColumn
|
||||
title='Infrastructure Component'
|
||||
dataKey='icID'
|
||||
modifier={(icID) => this.getICName(icID)}
|
||||
width={200}
|
||||
/>
|
||||
<TableColumn
|
||||
title=''
|
||||
width={200}
|
||||
align='right'
|
||||
editButton
|
||||
deleteButton
|
||||
exportButton
|
||||
duplicateButton
|
||||
onEdit={index => this.setState({ editConfigModal: true, modalConfigData: this.props.configs[index], modalConfigIndex: index })}
|
||||
onDelete={(index) => this.setState({ deleteConfigModal: true, modalConfigData: this.props.configs[index], modalConfigIndex: index })}
|
||||
onExport={index => this.exportConfig(index)}
|
||||
onDuplicate={index => this.duplicateConfig(index)}
|
||||
locked={this.props.locked}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
{this.state.ExternalICInUse ?
|
||||
<div style={{ float: 'left' }}>
|
||||
<ICAction
|
||||
ics={this.props.ics}
|
||||
configs={this.props.configs}
|
||||
selectedConfigs = {this.state.selectedConfigs}
|
||||
snapshotConfig = {(index) => this.copyConfig(index)}
|
||||
token = {this.props.sessionToken}
|
||||
actions={[
|
||||
{ id: '0', title: 'Start', data: { action: 'start' } },
|
||||
{ id: '1', title: 'Stop', data: { action: 'stop' } },
|
||||
{ id: '2', title: 'Pause', data: { action: 'pause' } },
|
||||
{ id: '3', title: 'Resume', data: { action: 'resume' } }
|
||||
]} />
|
||||
</div>
|
||||
: <div />
|
||||
}
|
||||
|
||||
<div style={{ clear: 'both' }} />
|
||||
|
||||
<EditConfigDialog
|
||||
show={this.state.editConfigModal}
|
||||
onClose={data => this.closeEditConfigModal(data)}
|
||||
config={this.state.modalConfigData}
|
||||
ics={this.props.ics}
|
||||
files={this.props.files}
|
||||
sessionToken={this.props.sessionToken}
|
||||
/>
|
||||
<ImportConfigDialog
|
||||
show={this.state.importConfigModal}
|
||||
onClose={data => this.importConfig(data)}
|
||||
ics={this.props.ics}
|
||||
/>
|
||||
<DeleteDialog
|
||||
title="component configuration"
|
||||
name={this.state.modalConfigData.name}
|
||||
show={this.state.deleteConfigModal}
|
||||
onClose={(c) => this.closeDeleteConfigModal(c)}
|
||||
/>
|
||||
<EditSignalMappingDialog
|
||||
show={this.state.editOutputSignalsModal}
|
||||
onCloseEdit={(direction) => this.closeEditSignalsModal(direction)}
|
||||
direction="Output"
|
||||
signals={this.props.signals}
|
||||
configID={this.state.modalConfigData.id}
|
||||
sessionToken={this.props.sessionToken}
|
||||
/>
|
||||
<EditSignalMappingDialog
|
||||
show={this.state.editInputSignalsModal}
|
||||
onCloseEdit={(direction) => this.closeEditSignalsModal(direction)}
|
||||
direction="Input"
|
||||
signals={this.props.signals}
|
||||
configID={this.state.modalConfigData.id}
|
||||
sessionToken={this.props.sessionToken}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ConfigTable;
|
|
@ -17,115 +17,77 @@
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button,OverlayTrigger, Tooltip } from 'react-bootstrap';
|
||||
import Icon from "../common/icon";
|
||||
import IconButton from '../common/icon-button';
|
||||
|
||||
const buttonStyle = {
|
||||
marginLeft: '12px',
|
||||
height: '44px',
|
||||
width: '35px',
|
||||
};
|
||||
|
||||
const iconStyle = {
|
||||
height: '25px',
|
||||
width: '25px'
|
||||
}
|
||||
|
||||
let buttonkey = 0;
|
||||
|
||||
class DashboardButtonGroup extends React.Component {
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
marginLeft: '12px',
|
||||
height: '44px',
|
||||
width : '35px',
|
||||
};
|
||||
|
||||
const iconStyle = {
|
||||
height: '25px',
|
||||
width : '25px'
|
||||
getBtn(icon, tooltip, clickFn, locked = false) {
|
||||
if (locked) {
|
||||
return <IconButton
|
||||
key={buttonkey++}
|
||||
ikey={buttonkey}
|
||||
icon={icon}
|
||||
disabled={true}
|
||||
hidetooltip={true}
|
||||
tooltip={tooltip}
|
||||
tipPlacement={'bottom'}
|
||||
onClick={clickFn}
|
||||
buttonStyle={buttonStyle}
|
||||
iconStyle={iconStyle}
|
||||
/>
|
||||
} else {
|
||||
return <IconButton
|
||||
key={buttonkey++}
|
||||
ikey={buttonkey}
|
||||
icon={icon}
|
||||
tooltip={tooltip}
|
||||
tipPlacement={'bottom'}
|
||||
onClick={clickFn}
|
||||
buttonStyle={buttonStyle}
|
||||
iconStyle={iconStyle}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const buttons = [];
|
||||
let key = 0;
|
||||
|
||||
/*if (this.props.fullscreen) {
|
||||
return null;
|
||||
}*/
|
||||
buttonkey = 0;
|
||||
|
||||
if (this.props.editing) {
|
||||
buttons.push(
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"save"}`}> Save changes </Tooltip>} >
|
||||
<Button variant= 'light' size="lg" key={key} onClick={this.props.onSave} style={buttonStyle}>
|
||||
<Icon icon="save" classname='icon-color' style={iconStyle} />
|
||||
</Button>
|
||||
</OverlayTrigger>,
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"cancel"}`}> Discard changes </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onCancel} style={buttonStyle}>
|
||||
<Icon icon="times" classname='icon-color' style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
buttons.push(this.getBtn("save", "Save changes", this.props.onSave));
|
||||
buttons.push(this.getBtn("times", "Discard changes", this.props.onCancel));
|
||||
} else {
|
||||
if (this.props.fullscreen !== true) {
|
||||
buttons.push(
|
||||
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"expand"}`}> Change to fullscreen view </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onFullscreen} style={buttonStyle}>
|
||||
<Icon icon="expand" classname='icon-color' style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
buttons.push(this.getBtn("expand", "Change to fullscreen view", this.props.onFullscreen));
|
||||
} else {
|
||||
buttons.push(
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"compress"}`}> Back to normal view </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onFullscreen} style={buttonStyle}>
|
||||
<Icon icon="compress" classname='icon-color' style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
buttons.push(this.getBtn("compress", "Back to normal view", this.props.onFullscreen));
|
||||
}
|
||||
|
||||
if (this.props.paused) {
|
||||
buttons.push(
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"play"}`}> Continue simulation </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onUnpause} style={buttonStyle}>
|
||||
<Icon icon="play" classname='icon-color' style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
buttons.push(this.getBtn("play", "Continue simulation", this.props.onUnpause));
|
||||
} else {
|
||||
buttons.push(
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"pause"}`}> Pause simulation </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onPause} style={buttonStyle}>
|
||||
<Icon icon="pause" classname='icon-color' style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
buttons.push(this.getBtn("pause", "Pause simulation", this.props.onPause));
|
||||
}
|
||||
|
||||
if (this.props.fullscreen !== true) {
|
||||
buttons.push(
|
||||
<OverlayTrigger key={key++} placement={'bottom'}
|
||||
overlay={<Tooltip id={`tooltip-${"file"}`}> Add, edit or delete files of scenario </Tooltip>}>
|
||||
<Button key={key} variant='light' size="lg" onClick={this.props.onEditFiles} style={buttonStyle}>
|
||||
<Icon icon="file" classname='icon-color' style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
|
||||
buttons.push(
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"file"}`}> Add, edit or delete input signals </Tooltip>}>
|
||||
<Button key={key} variant='light' size="lg" onClick={this.props.onEditInputSignals} style={buttonStyle}>
|
||||
<Icon icon="sign-in-alt" classname='icon-color' style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
|
||||
buttons.push(
|
||||
<OverlayTrigger key={key++} placement={'bottom'}
|
||||
overlay={<Tooltip id={`tooltip-${"file"}`}> Add, edit or delete output signals </Tooltip>}>
|
||||
<Button key={key} variant='light' size="lg" onClick={this.props.onEditOutputSignals} style={buttonStyle}>
|
||||
<Icon icon="sign-out-alt" classname='icon-color' style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
|
||||
buttons.push(
|
||||
<OverlayTrigger key={key++} placement={'bottom'}
|
||||
overlay={<Tooltip id={`tooltip-${"layout"}`}> Add widgets and edit layout </Tooltip>}>
|
||||
<Button key={key} variant='light' size="lg" onClick={this.props.onEdit} style={buttonStyle}>
|
||||
<Icon icon="pen" classname='icon-color' style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
let tooltip = this.props.locked ? "View files of scenario" : "Add, edit or delete files of scenario";
|
||||
buttons.push(this.getBtn("file", tooltip, this.props.onEditFiles));
|
||||
buttons.push(this.getBtn("sign-in-alt", "Add, edit or delete input signals", this.props.onEditInputSignals, this.props.locked));
|
||||
buttons.push(this.getBtn("sign-out-alt", "Add, edit or delete output signals", this.props.onEditOutputSignals, this.props.locked));
|
||||
buttons.push(this.getBtn("pen", "Add widgets and edit layout", this.props.onEdit, this.props.locked));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
246
src/dashboard/dashboard-table.js
Normal file
246
src/dashboard/dashboard-table.js
Normal file
|
@ -0,0 +1,246 @@
|
|||
/**
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, {Component} from "react";
|
||||
import FileSaver from 'file-saver';
|
||||
import IconButton from "../common/icon-button";
|
||||
import Table from "../common/table";
|
||||
import TableColumn from "../common/table-column";
|
||||
import NewDashboardDialog from "./new-dashboard";
|
||||
import EditDashboardDialog from "./edit-dashboard";
|
||||
import ImportDashboardDialog from "./import-dashboard";
|
||||
import DeleteDialog from "../common/dialogs/delete-dialog";
|
||||
import AppDispatcher from "../common/app-dispatcher";
|
||||
|
||||
class DashboardTable extends Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
newDashboardModal: false,
|
||||
deleteDashboardModal: false,
|
||||
importDashboardModal: false,
|
||||
modalDashboardData: {},
|
||||
dashboardEditModal: false,
|
||||
}
|
||||
}
|
||||
|
||||
closeNewDashboardModal(data) {
|
||||
this.setState({ newDashboardModal: false });
|
||||
if (data) {
|
||||
// TODO: 'newDashboard' not used, check this
|
||||
let newDashboard = data;
|
||||
// add default grid value and scenarioID
|
||||
newDashboard["grid"] = 15;
|
||||
newDashboard["scenarioID"] = this.props.scenario.id;
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'dashboards/start-add',
|
||||
data,
|
||||
token: this.props.sessionToken,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
closeEditDashboardModal(data) {
|
||||
this.setState({ dashboardEditModal: false });
|
||||
|
||||
let editDashboard = this.state.modalDashboardData;
|
||||
|
||||
if (data != null) {
|
||||
editDashboard.name = data.name;
|
||||
AppDispatcher.dispatch({
|
||||
type: 'dashboards/start-edit',
|
||||
data: editDashboard,
|
||||
token: this.props.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
closeDeleteDashboardModal(confirmDelete) {
|
||||
this.setState({ deleteDashboardModal: false });
|
||||
|
||||
if (confirmDelete === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'dashboards/start-remove',
|
||||
data: this.state.modalDashboardData,
|
||||
token: this.props.sessionToken,
|
||||
});
|
||||
}
|
||||
|
||||
closeImportDashboardModal(data) {
|
||||
this.setState({ importDashboardModal: false });
|
||||
|
||||
if (data) {
|
||||
let newDashboard = JSON.parse(JSON.stringify(data));
|
||||
newDashboard["scenarioID"] = this.props.scenario.id;
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'dashboards/start-add',
|
||||
data: newDashboard,
|
||||
token: this.props.sessionToken,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
copyDashboard(index) {
|
||||
let dashboard = JSON.parse(JSON.stringify(this.props.dashboards[index]));
|
||||
|
||||
let widgets = JSON.parse(JSON.stringify(this.props.widgets.filter(w => w.dashboardID === parseInt(dashboard.id, 10))));
|
||||
widgets.forEach((widget) => {
|
||||
delete widget.dashboardID;
|
||||
delete widget.id;
|
||||
})
|
||||
dashboard["widgets"] = widgets;
|
||||
delete dashboard.scenarioID;
|
||||
delete dashboard.id;
|
||||
|
||||
return dashboard;
|
||||
}
|
||||
|
||||
exportDashboard(index) {
|
||||
let dashboard = this.copyDashboard(index);
|
||||
|
||||
// show save dialog
|
||||
const blob = new Blob([JSON.stringify(dashboard, null, 2)], { type: 'application/json' });
|
||||
FileSaver.saveAs(blob, 'dashboard - ' + dashboard.name + '.json');
|
||||
}
|
||||
|
||||
duplicateDashboard(index) {
|
||||
let newDashboard = this.copyDashboard(index);
|
||||
newDashboard.scenarioID = this.props.scenario.id;
|
||||
newDashboard.name = newDashboard.name + '_copy';
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'dashboards/start-add',
|
||||
data: newDashboard,
|
||||
token: this.props.sessionToken,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
marginLeft: '10px',
|
||||
}
|
||||
|
||||
const iconStyle = {
|
||||
height: '30px',
|
||||
width: '30px'
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/*Dashboard table*/}
|
||||
<h2 style={this.props.tableHeadingStyle}>Dashboards
|
||||
<span className='icon-button'>
|
||||
<IconButton
|
||||
ikey={0}
|
||||
tooltip='Add Dashboard'
|
||||
onClick={() => this.setState({newDashboardModal: true})}
|
||||
icon='plus'
|
||||
disabled={this.props.locked}
|
||||
hidetooltip={this.props.locked}
|
||||
buttonStyle={buttonStyle}
|
||||
iconStyle={iconStyle}
|
||||
/>
|
||||
<IconButton
|
||||
ikey={1}
|
||||
tooltip='Import Dashboard'
|
||||
onClick={() => this.setState({importDashboardModal: true})}
|
||||
icon='upload'
|
||||
disabled={this.props.locked}
|
||||
hidetooltip={this.props.locked}
|
||||
buttonStyle={buttonStyle}
|
||||
iconStyle={iconStyle}
|
||||
/>
|
||||
</span>
|
||||
</h2>
|
||||
<Table data={this.props.dashboards}>
|
||||
{this.props.currentUser.role === "Admin" ?
|
||||
<TableColumn
|
||||
title='ID'
|
||||
dataKey='id'
|
||||
width={70}
|
||||
/>
|
||||
: <></>
|
||||
}
|
||||
<TableColumn
|
||||
title='Name'
|
||||
dataKey='name'
|
||||
link='/dashboards/'
|
||||
linkKey='id'
|
||||
width={300}
|
||||
/>
|
||||
<TableColumn
|
||||
title='Grid'
|
||||
dataKey='grid'
|
||||
width={100}
|
||||
/>
|
||||
|
||||
<TableColumn
|
||||
title=''
|
||||
width={200}
|
||||
align='right'
|
||||
editButton
|
||||
deleteButton
|
||||
exportButton
|
||||
duplicateButton
|
||||
onEdit={index => this.setState({
|
||||
dashboardEditModal: true,
|
||||
modalDashboardData: this.props.dashboards[index]
|
||||
})}
|
||||
onDelete={(index) => this.setState({
|
||||
deleteDashboardModal: true,
|
||||
modalDashboardData: this.props.dashboards[index],
|
||||
modalDashboardIndex: index
|
||||
})}
|
||||
onExport={index => this.exportDashboard(index)}
|
||||
onDuplicate={index => this.duplicateDashboard(index)}
|
||||
locked={this.props.locked}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
<NewDashboardDialog
|
||||
show={this.state.newDashboardModal}
|
||||
onClose={data => this.closeNewDashboardModal(data)}
|
||||
/>
|
||||
<EditDashboardDialog
|
||||
show={this.state.dashboardEditModal}
|
||||
dashboard={this.state.modalDashboardData}
|
||||
onClose={data => this.closeEditDashboardModal(data)}
|
||||
/>
|
||||
<ImportDashboardDialog
|
||||
show={this.state.importDashboardModal}
|
||||
onClose={data => this.closeImportDashboardModal(data)}
|
||||
/>
|
||||
<DeleteDialog
|
||||
title="dashboard"
|
||||
name={this.state.modalDashboardData.name}
|
||||
show={this.state.deleteDashboardModal}
|
||||
onClose={(e) => this.closeDeleteDashboardModal(e)}
|
||||
/>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default DashboardTable;
|
|
@ -27,6 +27,7 @@ import WidgetContextMenu from '../widget/widget-context-menu';
|
|||
import WidgetToolbox from '../widget/widget-toolbox';
|
||||
import WidgetArea from '../widget/widget-area';
|
||||
import DashboardButtonGroup from './dashboard-button-group';
|
||||
import IconToggleButton from '../common/icon-toggle-button';
|
||||
|
||||
import DashboardStore from './dashboard-store';
|
||||
import SignalStore from '../signal/signal-store'
|
||||
|
@ -35,6 +36,8 @@ import WidgetStore from '../widget/widget-store';
|
|||
import ICStore from '../ic/ic-store'
|
||||
import ConfigStore from '../componentconfig/config-store'
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
import ScenarioStore from '../scenario/scenario-store';
|
||||
|
||||
|
||||
import 'react-contexify/dist/ReactContexify.min.css';
|
||||
import WidgetContainer from '../widget/widget-container';
|
||||
|
@ -45,7 +48,7 @@ class Dashboard extends Component {
|
|||
static lastWidgetKey = 0;
|
||||
static webSocketsOpened = false;
|
||||
static getStores() {
|
||||
return [DashboardStore, FileStore, WidgetStore, SignalStore, ConfigStore, ICStore];
|
||||
return [DashboardStore, FileStore, WidgetStore, SignalStore, ConfigStore, ICStore, ScenarioStore];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
|
@ -80,9 +83,14 @@ class Dashboard extends Component {
|
|||
// filter component configurations to the ones that belong to this scenario
|
||||
let configs = [];
|
||||
let files = [];
|
||||
let locked = false;
|
||||
if (dashboard !== undefined) {
|
||||
configs = ConfigStore.getState().filter(config => config.scenarioID === dashboard.scenarioID);
|
||||
files = FileStore.getState().filter(file => file.scenarioID === dashboard.scenarioID);
|
||||
let scenario = ScenarioStore.getState().find(s => s.id === dashboard.scenarioID);
|
||||
if (scenario) {
|
||||
locked = scenario.isLocked;
|
||||
}
|
||||
if (dashboard.height === 0) {
|
||||
dashboard.height = 400;
|
||||
}
|
||||
|
@ -144,6 +152,7 @@ class Dashboard extends Component {
|
|||
widgetOrigIDs: prevState.widgetOrigIDs || [],
|
||||
|
||||
maxWidgetHeight: maxHeight || null,
|
||||
locked,
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -177,11 +186,22 @@ class Dashboard extends Component {
|
|||
componentDidUpdate(prevProps: Readonly<P>, prevState: Readonly<S>, snapshot: SS) {
|
||||
// open web sockets if ICs are already known and sockets are not opened yet
|
||||
if (this.state.ics !== undefined && !Dashboard.webSocketsOpened) {
|
||||
if (this.state.ics.length > 0) {
|
||||
console.log("Starting to open IC websockets:", this.state.ics);
|
||||
// only open sockets of ICs with configured input or output signals
|
||||
let relevantICs = this.state.ics.filter(ic => {
|
||||
let result = false;
|
||||
this.state.configs.forEach(config => {
|
||||
if(ic.id === config.icID && (config.inputLength !== 0 || config.outputLength !== 0)){
|
||||
result = true;
|
||||
}
|
||||
})
|
||||
return result;
|
||||
})
|
||||
|
||||
if (relevantICs.length > 0) {
|
||||
console.log("Starting to open IC websockets:", relevantICs);
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/open-sockets',
|
||||
data: this.state.ics
|
||||
data: relevantICs
|
||||
});
|
||||
|
||||
Dashboard.webSocketsOpened = true;
|
||||
|
@ -205,6 +225,13 @@ class Dashboard extends Component {
|
|||
param: '?scenarioID=' + this.state.dashboard.scenarioID,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
// load scenario for 'isLocked' value
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/start-load',
|
||||
data: this.state.dashboard.scenarioID,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -471,66 +498,65 @@ class Dashboard extends Component {
|
|||
return <div className="section-title"> <span>{"Loading Dashboard..."}</span> </div>
|
||||
}
|
||||
|
||||
const buttonStyle = {
|
||||
marginLeft: '10px',
|
||||
}
|
||||
|
||||
const iconStyle = {
|
||||
height: '25px',
|
||||
width: '25px'
|
||||
}
|
||||
|
||||
const grid = this.state.dashboard.grid;
|
||||
const boxClasses = classNames('section', 'box', { 'fullscreen-padding': this.props.isFullscreen });
|
||||
let draggable = this.state.editing;
|
||||
let dropZoneHeight = this.state.dashboard.height;
|
||||
return <div className={boxClasses} >
|
||||
return (<div className={boxClasses} >
|
||||
<div className='section-header box-header'>
|
||||
<div className="section-title">
|
||||
<h2>{this.state.dashboard.name}</h2>
|
||||
<h2>
|
||||
{this.state.dashboard.name}
|
||||
<span className='icon-button'>
|
||||
<IconToggleButton
|
||||
ikey={0}
|
||||
checked={this.state.locked}
|
||||
checkedIcon='lock'
|
||||
uncheckedIcon='lock-open'
|
||||
tooltipChecked='Dashboard is locked, cannot be edited'
|
||||
tooltipUnchecked='Dashboard is unlocked, can be edited'
|
||||
disabled={true}
|
||||
buttonStyle={buttonStyle}
|
||||
iconStyle={iconStyle}
|
||||
/>
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<DashboardButtonGroup
|
||||
editing={this.state.editing}
|
||||
onEdit={this.startEditing.bind(this)}
|
||||
fullscreen={this.props.isFullscreen}
|
||||
paused={this.state.paused}
|
||||
onSave={this.saveEditing.bind(this)}
|
||||
onCancel={this.cancelEditing.bind(this)}
|
||||
onFullscreen={this.props.toggleFullscreen}
|
||||
onPause={this.pauseData.bind(this)}
|
||||
onUnpause={this.unpauseData.bind(this)}
|
||||
onEditFiles={this.startEditFiles.bind(this)}
|
||||
onEditOutputSignals={this.editOutputSignals.bind(this)}
|
||||
onEditInputSignals={this.editInputSignals.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
<DashboardButtonGroup
|
||||
locked={this.state.locked}
|
||||
editing={this.state.editing}
|
||||
onEdit={this.startEditing.bind(this)}
|
||||
fullscreen={this.props.isFullscreen}
|
||||
paused={this.state.paused}
|
||||
onSave={this.saveEditing.bind(this)}
|
||||
onCancel={this.cancelEditing.bind(this)}
|
||||
onFullscreen={this.props.toggleFullscreen}
|
||||
onPause={this.pauseData.bind(this)}
|
||||
onUnpause={this.unpauseData.bind(this)}
|
||||
onEditFiles={this.startEditFiles.bind(this)}
|
||||
onEditOutputSignals={this.editOutputSignals.bind(this)}
|
||||
onEditInputSignals={this.editInputSignals.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="box box-content" onContextMenu={(e) => e.preventDefault()}>
|
||||
{this.state.editing &&
|
||||
<WidgetToolbox grid={grid} onGridChange={this.setGrid.bind(this)} dashboard={this.state.dashboard} onDashboardSizeChange={this.setDashboardSize.bind(this)} widgets={this.state.widgets} />
|
||||
}
|
||||
{!draggable ? (
|
||||
<WidgetArea widgets={this.state.widgets} dropZoneHeight={dropZoneHeight} editing={this.state.editing} grid={grid} onWidgetAdded={this.handleDrop.bind(this)}>
|
||||
{this.state.widgets != null && Object.keys(this.state.widgets).map(widgetKey => (
|
||||
<WidgetContainer widget={this.state.widgets[widgetKey]} key={widgetKey}>
|
||||
<WidgetContextMenu
|
||||
key={widgetKey}
|
||||
index={parseInt(widgetKey, 10)}
|
||||
widget={this.state.widgets[widgetKey]}
|
||||
onEdit={this.editWidget.bind(this)}
|
||||
onDuplicate={this.duplicateWidget.bind(this)}
|
||||
onDelete={this.deleteWidget.bind(this)}
|
||||
onChange={this.widgetChange.bind(this)}
|
||||
|
||||
onWidgetChange={this.widgetChange.bind(this)}
|
||||
editing={this.state.editing}
|
||||
grid={grid}
|
||||
paused={this.state.paused}
|
||||
/>
|
||||
</WidgetContainer>
|
||||
))}
|
||||
</WidgetArea>
|
||||
) : (
|
||||
<WidgetArea widgets={this.state.widgets} editing={this.state.editing} dropZoneHeight={dropZoneHeight} grid={grid} onWidgetAdded={this.handleDrop.bind(this)}>
|
||||
<div className="box box-content" onContextMenu={(e) => e.preventDefault()}>
|
||||
{this.state.editing &&
|
||||
<WidgetToolbox grid={grid} onGridChange={this.setGrid.bind(this)} dashboard={this.state.dashboard} onDashboardSizeChange={this.setDashboardSize.bind(this)} widgets={this.state.widgets} />
|
||||
}
|
||||
{!draggable ? (
|
||||
<WidgetArea widgets={this.state.widgets} dropZoneHeight={dropZoneHeight} editing={this.state.editing} grid={grid} onWidgetAdded={this.handleDrop.bind(this)}>
|
||||
{this.state.widgets != null && Object.keys(this.state.widgets).map(widgetKey => (
|
||||
<EditableWidgetContainer
|
||||
widget={this.state.widgets[widgetKey]}
|
||||
key={widgetKey}
|
||||
grid={grid}
|
||||
index={parseInt(widgetKey, 10)}
|
||||
onWidgetChange={this.widgetChange.bind(this)}>
|
||||
<WidgetContainer widget={this.state.widgets[widgetKey]} key={widgetKey}>
|
||||
<WidgetContextMenu
|
||||
key={widgetKey}
|
||||
index={parseInt(widgetKey, 10)}
|
||||
|
@ -542,53 +568,83 @@ class Dashboard extends Component {
|
|||
|
||||
onWidgetChange={this.widgetChange.bind(this)}
|
||||
editing={this.state.editing}
|
||||
grid={grid}
|
||||
paused={this.state.paused}
|
||||
/>
|
||||
</EditableWidgetContainer>
|
||||
</WidgetContainer>
|
||||
))}
|
||||
</WidgetArea>
|
||||
) : (
|
||||
<WidgetArea widgets={this.state.widgets} editing={this.state.editing} dropZoneHeight={dropZoneHeight} grid={grid} onWidgetAdded={this.handleDrop.bind(this)}>
|
||||
{this.state.widgets != null && Object.keys(this.state.widgets).map(widgetKey => (
|
||||
<EditableWidgetContainer
|
||||
widget={this.state.widgets[widgetKey]}
|
||||
key={widgetKey}
|
||||
grid={grid}
|
||||
index={parseInt(widgetKey, 10)}
|
||||
onWidgetChange={this.widgetChange.bind(this)}>
|
||||
<WidgetContextMenu
|
||||
key={widgetKey}
|
||||
index={parseInt(widgetKey, 10)}
|
||||
widget={this.state.widgets[widgetKey]}
|
||||
onEdit={this.editWidget.bind(this)}
|
||||
onDuplicate={this.duplicateWidget.bind(this)}
|
||||
onDelete={this.deleteWidget.bind(this)}
|
||||
onChange={this.widgetChange.bind(this)}
|
||||
|
||||
)}
|
||||
onWidgetChange={this.widgetChange.bind(this)}
|
||||
editing={this.state.editing}
|
||||
paused={this.state.paused}
|
||||
/>
|
||||
</EditableWidgetContainer>
|
||||
))}
|
||||
</WidgetArea>
|
||||
|
||||
<EditWidget
|
||||
sessionToken={this.state.sessionToken}
|
||||
show={this.state.editModal}
|
||||
onClose={this.closeEdit.bind(this)}
|
||||
widget={this.state.modalData}
|
||||
signals={this.state.signals}
|
||||
files={this.state.files}
|
||||
ics={this.state.ics}
|
||||
/>
|
||||
)}
|
||||
|
||||
<EditFilesDialog
|
||||
sessionToken={this.state.sessionToken}
|
||||
show={this.state.filesEditModal}
|
||||
onClose={this.closeEditFiles.bind(this)}
|
||||
signals={this.state.signals}
|
||||
files={this.state.files}
|
||||
scenarioID={this.state.dashboard.scenarioID}
|
||||
/>
|
||||
<EditWidget
|
||||
sessionToken={this.state.sessionToken}
|
||||
show={this.state.editModal}
|
||||
onClose={this.closeEdit.bind(this)}
|
||||
widget={this.state.modalData}
|
||||
signals={this.state.signals}
|
||||
files={this.state.files}
|
||||
ics={this.state.ics}
|
||||
/>
|
||||
|
||||
<EditSignalMappingDialog
|
||||
show={this.state.editOutputSignalsModal}
|
||||
onCloseEdit={(direction) => this.closeEditSignalsModal(direction)}
|
||||
direction="Output"
|
||||
signals={this.state.signals}
|
||||
configID={null}
|
||||
configs={this.state.configs}
|
||||
sessionToken={this.state.sessionToken}
|
||||
/>
|
||||
<EditSignalMappingDialog
|
||||
show={this.state.editInputSignalsModal}
|
||||
onCloseEdit={(direction) => this.closeEditSignalsModal(direction)}
|
||||
direction="Input"
|
||||
signals={this.state.signals}
|
||||
configID={null}
|
||||
configs={this.state.configs}
|
||||
sessionToken={this.state.sessionToken}
|
||||
/>
|
||||
<EditFilesDialog
|
||||
sessionToken={this.state.sessionToken}
|
||||
show={this.state.filesEditModal}
|
||||
onClose={this.closeEditFiles.bind(this)}
|
||||
signals={this.state.signals}
|
||||
files={this.state.files}
|
||||
scenarioID={this.state.dashboard.scenarioID}
|
||||
locked={this.state.locked}
|
||||
/>
|
||||
|
||||
<EditSignalMappingDialog
|
||||
show={this.state.editOutputSignalsModal}
|
||||
onCloseEdit={(direction) => this.closeEditSignalsModal(direction)}
|
||||
direction="Output"
|
||||
signals={this.state.signals}
|
||||
configID={null}
|
||||
configs={this.state.configs}
|
||||
sessionToken={this.state.sessionToken}
|
||||
/>
|
||||
<EditSignalMappingDialog
|
||||
show={this.state.editInputSignalsModal}
|
||||
onCloseEdit={(direction) => this.closeEditSignalsModal(direction)}
|
||||
direction="Input"
|
||||
signals={this.state.signals}
|
||||
configID={null}
|
||||
configs={this.state.configs}
|
||||
sessionToken={this.state.sessionToken}
|
||||
/>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -106,10 +106,12 @@ class EditFilesDialog extends React.Component {
|
|||
marginTop: '-40px'
|
||||
};
|
||||
|
||||
let title = this.props.locked ? "View files of scenario" : "Edit Files of Scenario";
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
show={this.props.show}
|
||||
title="Edit Files of Scenario"
|
||||
title={title}
|
||||
buttonTitle="Close"
|
||||
onClose={() => this.onClose()}
|
||||
blendOutCancel = {true}
|
||||
|
@ -139,6 +141,7 @@ class EditFilesDialog extends React.Component {
|
|||
onDelete={(index) => this.deleteFile(index)}
|
||||
editButton
|
||||
onEdit={index => this.setState({ editModal: true, modalFile: this.props.files[index] })}
|
||||
locked={this.props.locked}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
|
@ -146,13 +149,17 @@ class EditFilesDialog extends React.Component {
|
|||
<h5>Add file</h5>
|
||||
<Row>
|
||||
<Col xs lg="4">
|
||||
<Form.Control type='file' onChange={(event) => this.selectUploadFile(event)} />
|
||||
<Form.Control
|
||||
type='file'
|
||||
onChange={(event) => this.selectUploadFile(event)}
|
||||
disabled={this.props.locked}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs lg="2">
|
||||
<span className='solid-button'>
|
||||
<Button
|
||||
variant='secondary'
|
||||
disabled={this.state.uploadFile === null}
|
||||
disabled={this.state.uploadFile === null || this.props.locked}
|
||||
onClick={() => this.startFileUpload()}>
|
||||
Upload
|
||||
</Button>
|
||||
|
|
|
@ -1,173 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Button, Row, Col } from 'react-bootstrap';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
import Icon from "../common/icon";
|
||||
import ConfirmCommand from './confirm-command';
|
||||
import ReactJson from 'react-json-view';
|
||||
import FileSaver from 'file-saver';
|
||||
import moment from 'moment';
|
||||
|
||||
class ICDialog extends React.Component {
|
||||
valid = true;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
confirmCommand: false,
|
||||
command: '',
|
||||
};
|
||||
}
|
||||
|
||||
onClose(canceled) {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
|
||||
}
|
||||
|
||||
showFurtherInfo(key){
|
||||
if(typeof this.state[key] === 'undefined') this.setState({[key]: false});
|
||||
this.setState({[key]: !this.state[key]});
|
||||
}
|
||||
|
||||
closeConfirmModal(canceled){
|
||||
if(!canceled){
|
||||
this.props.sendControlCommand(this.state.command,this.props.ic);
|
||||
}
|
||||
|
||||
this.setState({confirmCommand: false, command: ''});
|
||||
}
|
||||
|
||||
async downloadGraph(url) {
|
||||
|
||||
let blob = await fetch(url).then(r => r.blob())
|
||||
FileSaver.saveAs(blob, this.props.ic.name + ".svg");
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let graphURL = ""
|
||||
if (this.props.ic.apiurl !== ""){
|
||||
graphURL = this.props.ic.apiurl + "/graph.svg"
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
show={this.props.show}
|
||||
title={this.props.ic.name}
|
||||
buttonTitle="Close"
|
||||
onClose={(c) => this.onClose(c)}
|
||||
valid={true}
|
||||
size='xl'
|
||||
blendOutCancel={true}
|
||||
>
|
||||
<form>
|
||||
<Row>
|
||||
<Col>
|
||||
<Row>
|
||||
<Col><b>Name</b></Col>
|
||||
<Col>{this.props.ic.name}</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col><b>UUID</b></Col>
|
||||
<Col>{this.props.ic.uuid}</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col><b>State</b></Col>
|
||||
<Col>{this.props.ic.state}</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col><b>Category</b></Col>
|
||||
<Col>{this.props.ic.category}</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col><b>Type</b></Col>
|
||||
<Col>{this.props.ic.type}</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col><b>Uptime</b></Col>
|
||||
<Col>{moment.duration(this.props.ic.uptime, "seconds").humanize()}</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col><b>Location</b></Col>
|
||||
<Col>{this.props.ic.location}</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col><b>Description</b></Col>
|
||||
<Col>{this.props.ic.description}</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col><b>Websocket URL</b></Col>
|
||||
<Col>{this.props.ic.websocketurl}</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col><b>API URL</b></Col>
|
||||
<Col>{this.props.ic.apiurl}</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col><b>Start parameter schema</b></Col>
|
||||
<Col>
|
||||
<ReactJson
|
||||
src={this.props.ic.startParameterSchema}
|
||||
name={false}
|
||||
displayDataTypes={false}
|
||||
displayObjectSize={false}
|
||||
enableClipboard={false}
|
||||
collapsed={0}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
|
||||
<Col>
|
||||
<h5>Raw Status:</h5>
|
||||
<ReactJson
|
||||
src={this.props.ic.statusupdateraw}
|
||||
name={false}
|
||||
displayDataTypes={false}
|
||||
displayObjectSize={false}
|
||||
enableClipboard={false}
|
||||
collapsed={1}
|
||||
/>
|
||||
|
||||
{this.props.ic.type === "villas-node" ?
|
||||
<>
|
||||
<div className='section-buttons-group-right'>
|
||||
<Button style={{margin: '5px'}} size='sm' onClick={() => this.downloadGraph(graphURL)}><Icon
|
||||
icon="download"/></Button>
|
||||
</div>
|
||||
<h5>Graph:</h5>
|
||||
<div>
|
||||
<img alt={"Graph image download failed and/or incorrect image API URL"} src={graphURL}/>
|
||||
</div>
|
||||
|
||||
{this.props.user.role === "Admin" ?
|
||||
<div>
|
||||
<h5>Controls:</h5>
|
||||
<div className='solid-button'>
|
||||
<Button variant='secondary' style={{margin: '5px'}} size='lg'
|
||||
onClick={() => this.setState({confirmCommand: true, command: 'restart'})}>Restart</Button>
|
||||
<Button variant='secondary' style={{margin: '5px'}} size='lg' onClick={() => this.setState({
|
||||
confirmCommand: true,
|
||||
command: 'shutdown'
|
||||
})}>Shutdown</Button>
|
||||
</div>
|
||||
</div>
|
||||
: <div/>
|
||||
}
|
||||
<ConfirmCommand show={this.state.confirmCommand} command={this.state.command} name={this.props.ic.name}
|
||||
onClose={c => this.closeConfirmModal(c)}/>
|
||||
</>
|
||||
: <div/>
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ICDialog;
|
240
src/ic/ic.js
Normal file
240
src/ic/ic.js
Normal file
|
@ -0,0 +1,240 @@
|
|||
/**
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import InfrastructureComponentStore from './ic-store';
|
||||
import { Container as FluxContainer } from 'flux/utils';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
import { Container, Col, Row, Table, Button } from 'react-bootstrap';
|
||||
import moment from 'moment';
|
||||
import ReactJson from 'react-json-view';
|
||||
import ConfirmCommand from './confirm-command';
|
||||
import Icon from "../common/icon";
|
||||
|
||||
|
||||
|
||||
class InfrastructureComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
confirmCommand: false,
|
||||
command: '',
|
||||
};
|
||||
}
|
||||
|
||||
static getStores() {
|
||||
return [InfrastructureComponentStore];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
if (prevState == null) {
|
||||
prevState = {};
|
||||
}
|
||||
|
||||
return {
|
||||
sessionToken: localStorage.getItem("token"),
|
||||
currentUser: JSON.parse(localStorage.getItem("currentUser")),
|
||||
ic: InfrastructureComponentStore.getState().find(ic => ic.id === parseInt(props.match.params.ic, 10))
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let icID = parseInt(this.props.match.params.ic, 10);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/start-load',
|
||||
data: icID,
|
||||
token: this.state.sessionToken,
|
||||
});
|
||||
}
|
||||
|
||||
isJSON(data) {
|
||||
if (data === undefined || data === null) {
|
||||
return false;
|
||||
}
|
||||
let str = JSON.stringify(data);
|
||||
try {
|
||||
JSON.parse(str)
|
||||
}
|
||||
catch (ex) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
async downloadGraph(url) {
|
||||
let blob = await fetch(url).then(r => r.blob())
|
||||
FileSaver.saveAs(blob, this.state.ic.name + ".svg");
|
||||
}
|
||||
|
||||
sendControlCommand() {
|
||||
if (this.state.command === "restart") {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/restart',
|
||||
url: this.state.ic.apiurl + "/restart",
|
||||
token: this.state.sessionToken,
|
||||
});
|
||||
} else if (this.state.command === "shutdown") {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/shutdown',
|
||||
url: this.state.ic.apiurl + "/shutdown",
|
||||
token: this.state.sessionToken,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
confirmCommand(canceled){
|
||||
if(!canceled){
|
||||
this.sendControlCommand();
|
||||
}
|
||||
|
||||
this.setState({confirmCommand: false, command: ''});
|
||||
}
|
||||
|
||||
async downloadGraph(url) {
|
||||
|
||||
let blob = await fetch(url).then(r => r.blob())
|
||||
FileSaver.saveAs(blob, this.props.ic.name + ".svg");
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.ic === undefined) {
|
||||
return <h1>Loading Infrastructure Component...</h1>;
|
||||
}
|
||||
|
||||
let graphURL = ""
|
||||
if (this.state.ic.apiurl !== "") {
|
||||
graphURL = this.state.ic.apiurl + "/graph.svg"
|
||||
}
|
||||
|
||||
return <div className='section'>
|
||||
<h1>{this.state.ic.name}</h1>
|
||||
<Container>
|
||||
<Row>
|
||||
<Col>
|
||||
<Table striped size="sm">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>{this.state.ic.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>{this.state.ic.description}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>UUID</td>
|
||||
<td>{this.state.ic.uuid}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>State</td>
|
||||
<td>{this.state.ic.state}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Category</td>
|
||||
<td>{this.state.ic.category}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Type</td>
|
||||
<td>{this.state.ic.type}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Uptime</td>
|
||||
<td>{moment.duration(this.state.ic.uptime, "seconds").humanize()}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Location</td>
|
||||
<td>{this.state.ic.location}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Websocket URL</td>
|
||||
<td>{this.state.ic.websocketurl}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>API URL</td>
|
||||
<td>{this.state.ic.apiurl}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Start parameter schema</td>
|
||||
<td>
|
||||
<ReactJson
|
||||
src={this.state.ic.startparameterschema}
|
||||
name={false}
|
||||
displayDataTypes={false}
|
||||
displayObjectSize={false}
|
||||
enableClipboard={false}
|
||||
collapsed={0}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
</Col>
|
||||
<Col><b>Raw Status</b>
|
||||
{this.isJSON(this.state.ic.statusupdateraw) ?
|
||||
<ReactJson
|
||||
src={this.state.ic.statusupdateraw}
|
||||
name={false}
|
||||
displayDataTypes={false}
|
||||
displayObjectSize={false}
|
||||
enableClipboard={false}
|
||||
collapsed={0}
|
||||
/> : <div>No valid JSON raw data available.</div>}
|
||||
|
||||
{this.state.ic.type === "villas-node" ?
|
||||
<>
|
||||
<div className='section-buttons-group-right'>
|
||||
<Button style={{ margin: '5px' }} size='sm' onClick={() => this.downloadGraph(graphURL)}><Icon
|
||||
icon="download" /></Button>
|
||||
</div>
|
||||
<hr></hr>
|
||||
<b>Graph:</b>
|
||||
<div>
|
||||
<img alt={"Graph image download failed and/or incorrect image API URL"} src={graphURL} />
|
||||
</div>
|
||||
|
||||
{this.state.currentUser.role === "Admin" ?
|
||||
<div>
|
||||
<hr></hr>
|
||||
<b>Controls:</b>
|
||||
<div className='solid-button'>
|
||||
<Button variant='secondary' style={{ margin: '5px' }} size='lg'
|
||||
onClick={() => this.setState({ confirmCommand: true, command: 'restart' })}>Restart</Button>
|
||||
<Button variant='secondary' style={{ margin: '5px' }} size='lg' onClick={() => this.setState({
|
||||
confirmCommand: true,
|
||||
command: 'shutdown'
|
||||
})}>Shutdown</Button>
|
||||
</div>
|
||||
</div>
|
||||
: <div />
|
||||
}
|
||||
<ConfirmCommand show={this.state.confirmCommand} command={this.state.command} name={this.state.ic.name}
|
||||
onClose={c => this.confirmCommand(c)} />
|
||||
</>
|
||||
: <div />}
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</div>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let fluxContainerConverter = require('../common/FluxContainerConverter');
|
||||
export default FluxContainer.create(fluxContainerConverter.convert(InfrastructureComponent), { withProps: true });
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { Button, Badge } from 'react-bootstrap';
|
||||
import { Badge } from 'react-bootstrap';
|
||||
import FileSaver from 'file-saver';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment'
|
||||
|
@ -32,7 +32,6 @@ import TableColumn from '../common/table-column';
|
|||
import NewICDialog from './new-ic';
|
||||
import EditICDialog from './edit-ic';
|
||||
import ImportICDialog from './import-ic';
|
||||
import ICDialog from './ic-dialog';
|
||||
|
||||
import ICAction from './ic-action';
|
||||
import DeleteDialog from '../common/dialogs/delete-dialog';
|
||||
|
@ -367,32 +366,11 @@ class InfrastructureComponents extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
modifyNameColumn(name, component){
|
||||
let index = this.state.ics.indexOf(component);
|
||||
return <Button variant="link" onClick={() => this.openICStatus(component)}>{name}</Button>
|
||||
}
|
||||
|
||||
openICStatus(ic){
|
||||
let index = this.state.ics.indexOf(ic);
|
||||
this.setState({ icModal: true, modalIC: ic, modalIndex: index })
|
||||
}
|
||||
|
||||
sendControlCommand(command,ic){
|
||||
if(command === "restart"){
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/restart',
|
||||
url: ic.apiurl + "/restart",
|
||||
token: this.state.sessionToken,
|
||||
});
|
||||
}else if(command === "shutdown"){
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/shutdown',
|
||||
url: ic.apiurl + "/shutdown",
|
||||
token: this.state.sessionToken,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
isLocalIC(index, ics){
|
||||
let ic = ics[index]
|
||||
return !ic.managedexternally
|
||||
|
@ -419,7 +397,8 @@ class InfrastructureComponents extends Component {
|
|||
<TableColumn
|
||||
title='Name'
|
||||
dataKeys={['name']}
|
||||
modifier={(name, component) => this.modifyNameColumn(name, component)}
|
||||
link='/infrastructure/'
|
||||
linkKey='id'
|
||||
/>
|
||||
<TableColumn
|
||||
title='State'
|
||||
|
@ -493,13 +472,13 @@ class InfrastructureComponents extends Component {
|
|||
{this.state.currentUser.role === "Admin" ?
|
||||
<span className='icon-button'>
|
||||
<IconButton
|
||||
overlaykey={1}
|
||||
ikey={1}
|
||||
tooltip='Add Infrastructure Component'
|
||||
onClick={() => this.setState({newModal: true})}
|
||||
icon='plus'
|
||||
/>
|
||||
<IconButton
|
||||
overlaykey={2}
|
||||
ikey={1}
|
||||
tooltip='Import Infrastructure Component'
|
||||
onClick={() => this.setState({importModal: true})}
|
||||
icon='upload'
|
||||
|
@ -538,13 +517,6 @@ class InfrastructureComponents extends Component {
|
|||
<EditICDialog show={this.state.editModal} onClose={data => this.closeEditModal(data)} ic={this.state.modalIC} />
|
||||
<ImportICDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} />
|
||||
<DeleteDialog title="infrastructure-component" name={this.state.modalIC.name || 'Unknown'} show={this.state.deleteModal} onClose={(e) => this.closeDeleteModal(e)} />
|
||||
<ICDialog
|
||||
show={this.state.icModal}
|
||||
onClose={data => this.closeICModal(data)}
|
||||
ic={this.state.modalIC}
|
||||
token={this.state.sessionToken}
|
||||
user={this.state.currentUser}
|
||||
sendControlCommand={(command, ic) => this.sendControlCommand(command, ic)}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -56,6 +56,12 @@ class NewICDialog extends React.Component {
|
|||
const data = {
|
||||
managedexternally: this.state.managedexternally,
|
||||
manager: this.state.manager,
|
||||
name: this.state.name,
|
||||
type: this.state.type,
|
||||
category: this.state.category,
|
||||
uuid: this.state.uuid,
|
||||
description: this.state.description,
|
||||
location: this.state.location,
|
||||
parameters: parameters
|
||||
};
|
||||
|
||||
|
@ -65,10 +71,12 @@ class NewICDialog extends React.Component {
|
|||
|
||||
if (this.state.websocketurl != null && this.state.websocketurl !== "") {
|
||||
parameters.websocketurl = this.state.websocketurl;
|
||||
data.websocketurl = this.state.websocketurl;
|
||||
}
|
||||
|
||||
if (this.state.apiurl != null && this.state.apiurl !== "") {
|
||||
parameters.apiurl = this.state.apiurl;
|
||||
data.apiurl = this.state.apiurl;
|
||||
}
|
||||
|
||||
this.props.onClose(data);
|
||||
|
|
257
src/result/result-table.js
Normal file
257
src/result/result-table.js
Normal file
|
@ -0,0 +1,257 @@
|
|||
/**
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, {Component} from "react";
|
||||
import JSZip from 'jszip';
|
||||
import {Button} from "react-bootstrap";
|
||||
import FileSaver from 'file-saver';
|
||||
import AppDispatcher from "../common/app-dispatcher";
|
||||
import IconButton from "../common/icon-button";
|
||||
import Table from "../common/table";
|
||||
import TableColumn from "../common/table-column";
|
||||
import DeleteDialog from "../common/dialogs/delete-dialog";
|
||||
import EditResultDialog from "./edit-result";
|
||||
import ResultConfigDialog from "./result-configs-dialog";
|
||||
import NewResultDialog from "./new-result";
|
||||
|
||||
class ResultTable extends Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
editResultsModal: false,
|
||||
modalResultsData: {},
|
||||
modalResultsIndex: 0,
|
||||
newResultModal: false,
|
||||
filesToDownload: [],
|
||||
zipfiles: false,
|
||||
resultNodl: 0,
|
||||
resultConfigsModal: false,
|
||||
modalResultConfigs: {},
|
||||
modalResultConfigsIndex: 0,
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
// check whether file data has been loaded
|
||||
if (this.state.filesToDownload && this.state.filesToDownload.length > 0) {
|
||||
if (this.props.files !== prevProps.files) {
|
||||
if (!this.state.zipfiles) {
|
||||
let fileToDownload = this.props.files.filter(file => file.id === this.state.filesToDownload[0])
|
||||
if (fileToDownload.length === 1 && fileToDownload[0].data) {
|
||||
const blob = new Blob([fileToDownload[0].data], { type: fileToDownload[0].type });
|
||||
FileSaver.saveAs(blob, fileToDownload[0].name);
|
||||
this.setState({ filesToDownload: [] });
|
||||
}
|
||||
} else { // zip and save one or more files (download all button)
|
||||
let filesToDownload = this.props.files.filter(file => this.state.filesToDownload.includes(file.id) && file.data);
|
||||
if (filesToDownload.length === this.state.filesToDownload.length) { // all requested files have been loaded
|
||||
var zip = new JSZip();
|
||||
filesToDownload.forEach(file => {
|
||||
zip.file(file.name, file.data);
|
||||
});
|
||||
let zipname = "result_" + this.state.resultNodl + "_" + (new Date()).toISOString();
|
||||
zip.generateAsync({ type: "blob" }).then(function (content) {
|
||||
saveAs(content, zipname);
|
||||
});
|
||||
this.setState({ filesToDownload: [] });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closeNewResultModal(data) {
|
||||
this.setState({ newResultModal: false });
|
||||
if (data) {
|
||||
data["scenarioID"] = this.props.scenario.id;
|
||||
AppDispatcher.dispatch({
|
||||
type: 'results/start-add',
|
||||
data,
|
||||
token: this.props.sessionToken,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
closeEditResultsModal() {
|
||||
this.setState({ editResultsModal: false });
|
||||
}
|
||||
|
||||
downloadResultData(param) {
|
||||
let toDownload = [];
|
||||
let zip = false;
|
||||
|
||||
if (typeof (param) === 'object') { // download all files
|
||||
toDownload = param.resultFileIDs;
|
||||
zip = true;
|
||||
this.setState({ filesToDownload: toDownload, zipfiles: zip, resultNodl: param.id });
|
||||
} else { // download one file
|
||||
toDownload.push(param);
|
||||
this.setState({ filesToDownload: toDownload, zipfiles: zip });
|
||||
}
|
||||
|
||||
toDownload.forEach(fileid => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-download',
|
||||
data: fileid,
|
||||
token: this.props.sessionToken
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
closeDeleteResultsModal(confirmDelete) {
|
||||
this.setState({ deleteResultsModal: false });
|
||||
|
||||
if (confirmDelete === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'results/start-remove',
|
||||
data: this.state.modalResultsData,
|
||||
token: this.props.sessionToken,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
openResultConfigSnapshots(result) {
|
||||
if (result.configSnapshots === null || result.configSnapshots === undefined) {
|
||||
this.setState({
|
||||
modalResultConfigs: {"configs": []},
|
||||
modalResultConfigsIndex: result.id,
|
||||
resultConfigsModal: true
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
modalResultConfigs: result.configSnapshots,
|
||||
modalResultConfigsIndex: result.id,
|
||||
resultConfigsModal: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
closeResultConfigSnapshots() {
|
||||
this.setState({ resultConfigsModal: false });
|
||||
}
|
||||
|
||||
modifyResultNoColumn(id, result) {
|
||||
return <Button variant="link" style={{ color: '#047cab' }} onClick={() => this.openResultConfigSnapshots(result)}>{id}</Button>
|
||||
}
|
||||
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
marginLeft: '10px',
|
||||
}
|
||||
|
||||
const iconStyle = {
|
||||
height: '30px',
|
||||
width: '30px'
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/*Result table*/}
|
||||
<h2 style={this.props.tableHeadingStyle}>Results
|
||||
<span className='icon-button'>
|
||||
<IconButton
|
||||
ikey={1}
|
||||
tooltip='Add Result'
|
||||
onClick={() => this.setState({ newResultModal: true })}
|
||||
icon='plus'
|
||||
disabled={this.props.locked}
|
||||
hidetooltip={this.props.locked}
|
||||
buttonStyle={buttonStyle}
|
||||
iconStyle={iconStyle}
|
||||
/>
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<Table data={this.props.results}>
|
||||
<TableColumn
|
||||
title='ID'
|
||||
dataKey='id'
|
||||
modifier={(id, result) => this.modifyResultNoColumn(id, result)}
|
||||
width={70}
|
||||
/>
|
||||
<TableColumn
|
||||
title='Description'
|
||||
dataKey='description'
|
||||
width={300}
|
||||
/>
|
||||
<TableColumn
|
||||
title='Created at'
|
||||
dataKey='createdAt'
|
||||
width={200}
|
||||
/>
|
||||
<TableColumn
|
||||
title='Last update'
|
||||
dataKey='updatedAt'
|
||||
width={200}
|
||||
/>
|
||||
<TableColumn
|
||||
title='Files'
|
||||
dataKey='resultFileIDs'
|
||||
linkKey='filebuttons'
|
||||
data={this.props.files}
|
||||
onDownload={(index) => this.downloadResultData(index)}
|
||||
width={300}
|
||||
/>
|
||||
<TableColumn
|
||||
width={200}
|
||||
align='right'
|
||||
editButton
|
||||
downloadAllButton
|
||||
deleteButton
|
||||
onEdit={index => this.setState({ editResultsModal: true, modalResultsIndex: index })}
|
||||
onDownloadAll={(index) => this.downloadResultData(this.props.results[index])}
|
||||
onDelete={(index) => this.setState({ deleteResultsModal: true, modalResultsData: this.props.results[index], modalResultsIndex: index })}
|
||||
locked={this.props.locked}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
<EditResultDialog
|
||||
sessionToken={this.props.sessionToken}
|
||||
show={this.state.editResultsModal}
|
||||
files={this.props.files}
|
||||
results={this.props.results}
|
||||
resultId={this.state.modalResultsIndex}
|
||||
scenarioID={this.props.scenario.id}
|
||||
onClose={this.closeEditResultsModal.bind(this)}
|
||||
/>
|
||||
<DeleteDialog
|
||||
title="result"
|
||||
name={this.state.modalResultsData.id}
|
||||
show={this.state.deleteResultsModal}
|
||||
onClose={(e) => this.closeDeleteResultsModal(e)}
|
||||
/>
|
||||
<ResultConfigDialog
|
||||
show={this.state.resultConfigsModal}
|
||||
configs={this.state.modalResultConfigs}
|
||||
resultNo={this.state.modalResultConfigsIndex}
|
||||
onClose={this.closeResultConfigSnapshots.bind(this)}
|
||||
/>
|
||||
<NewResultDialog
|
||||
show={this.state.newResultModal}
|
||||
onClose={data => this.closeNewResultModal(data)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ResultTable;
|
|
@ -26,6 +26,7 @@ import Scenarios from './scenario/scenarios';
|
|||
import Scenario from './scenario/scenario';
|
||||
import Dashboard from './dashboard/dashboard'
|
||||
import InfrastructureComponents from './ic/ics';
|
||||
import InfrastructureComponent from './ic/ic';
|
||||
import Users from './user/users';
|
||||
import User from "./user/user";
|
||||
import LoginComplete from './user/login-complete'
|
||||
|
@ -46,6 +47,7 @@ class Root extends React.Component {
|
|||
<Route path='/scenarios/:scenario' component={Scenario} />
|
||||
<Route path='/dashboards/:dashboard' component={Dashboard} />
|
||||
<Route path='/infrastructure' component={InfrastructureComponents} />
|
||||
<Route path='/infrastructure/:ic' component={InfrastructureComponent} />
|
||||
<Route path='/users' component={Users} />
|
||||
<Route path='/account' component={User} />
|
||||
|
||||
|
|
162
src/scenario/scenario-users-table.js
Normal file
162
src/scenario/scenario-users-table.js
Normal file
|
@ -0,0 +1,162 @@
|
|||
/**
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, {Component} from "react";
|
||||
import { Form, InputGroup} from "react-bootstrap";
|
||||
import {Redirect} from "react-router-dom";
|
||||
import Table from "../common/table";
|
||||
import TableColumn from "../common/table-column";
|
||||
import IconButton from "../common/icon-button";
|
||||
import DeleteDialog from "../common/dialogs/delete-dialog";
|
||||
import AppDispatcher from "../common/app-dispatcher";
|
||||
|
||||
class ScenarioUsersTable extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
userToAdd: '',
|
||||
deleteUserName: '',
|
||||
deleteUserModal: false,
|
||||
goToScenarios: false
|
||||
}
|
||||
}
|
||||
|
||||
onUserInputChange(e) {
|
||||
this.setState({ userToAdd: e.target.value });
|
||||
}
|
||||
|
||||
addUser() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/add-user',
|
||||
data: this.props.scenario.id,
|
||||
username: this.state.userToAdd,
|
||||
token: this.props.sessionToken
|
||||
});
|
||||
|
||||
this.setState({ userToAdd: '' });
|
||||
}
|
||||
|
||||
closeDeleteUserModal() {
|
||||
let scenarioID = this.props.scenario.id;
|
||||
if (this.state.deleteUserName === this.props.currentUser.username) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/remove-user',
|
||||
data: scenarioID,
|
||||
username: this.state.deleteUserName,
|
||||
token: this.props.sessionToken,
|
||||
ownuser: true
|
||||
});
|
||||
this.setState({ goToScenarios: true });
|
||||
} else {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/remove-user',
|
||||
data: scenarioID,
|
||||
username: this.state.deleteUserName,
|
||||
token: this.props.sessionToken,
|
||||
ownuser: false
|
||||
});
|
||||
}
|
||||
this.setState({ deleteUserModal: false });
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
if (this.state.goToScenarios) {
|
||||
console.log("redirect to scenario overview")
|
||||
return (<Redirect to="/scenarios" />);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/*Scenario Users table*/}
|
||||
<h2 style={this.props.tableHeadingStyle}>Users sharing this scenario</h2>
|
||||
<Table data={this.props.scenario.users}>
|
||||
{this.props.currentUser.role === "Admin" ?
|
||||
<TableColumn
|
||||
title='ID'
|
||||
dataKey='id'
|
||||
width={70}
|
||||
/>
|
||||
: <></>
|
||||
}
|
||||
<TableColumn
|
||||
title='Name'
|
||||
dataKey='username'
|
||||
width={300}
|
||||
/>
|
||||
<TableColumn
|
||||
title='Role'
|
||||
dataKey='role'
|
||||
width={100}
|
||||
/>
|
||||
<TableColumn
|
||||
title=''
|
||||
width={30}
|
||||
align='right'
|
||||
deleteButton
|
||||
onDelete={(index) => this.setState({
|
||||
deleteUserModal: true,
|
||||
deleteUserName: this.props.scenario.users[index].username,
|
||||
modalUserIndex: index
|
||||
})}
|
||||
locked={this.props.locked}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
<InputGroup
|
||||
style={{
|
||||
width: 400,
|
||||
float: 'right'
|
||||
}}
|
||||
>
|
||||
<Form.Control
|
||||
placeholder="Username"
|
||||
onChange={(e) => this.onUserInputChange(e)}
|
||||
value={this.state.userToAdd}
|
||||
type="text"
|
||||
/>
|
||||
<InputGroup.Append>
|
||||
<span className='icon-button'>
|
||||
<IconButton
|
||||
ikey={1}
|
||||
tooltip='Add User to Scenario'
|
||||
onClick={() => this.addUser()}
|
||||
icon='plus'
|
||||
disabled={this.props.locked}
|
||||
hidetooltip={this.props.locked}
|
||||
/>
|
||||
</span>
|
||||
</InputGroup.Append>
|
||||
</InputGroup>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<DeleteDialog
|
||||
title="Delete user from scenario"
|
||||
name={this.state.deleteUserName}
|
||||
show={this.state.deleteUserModal}
|
||||
onClose={(c) => this.closeDeleteUserModal(c)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ScenarioUsersTable;
|
||||
|
File diff suppressed because it is too large
Load diff
|
@ -73,7 +73,7 @@ class Scenarios extends Component {
|
|||
}
|
||||
|
||||
closeNewModal(data) {
|
||||
if(data) {
|
||||
if (data) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/start-add',
|
||||
data: data,
|
||||
|
@ -218,7 +218,7 @@ class Scenarios extends Component {
|
|||
jsonObj["configs"] = this.getConfigs(scenario.id);
|
||||
jsonObj["dashboards"] = this.getDashboards(scenario.id);
|
||||
|
||||
if(jsonObj) {
|
||||
if (jsonObj) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/start-add',
|
||||
data: jsonObj,
|
||||
|
@ -227,68 +227,99 @@ class Scenarios extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
modifyRunningColumn(running){
|
||||
return <Icon icon={ running ? 'check' : 'times' } />
|
||||
isLocked(index) {
|
||||
return this.state.scenarios[index].isLocked;
|
||||
}
|
||||
|
||||
onLock(index) {
|
||||
let data = {};
|
||||
data.id = this.state.scenarios[index].id;
|
||||
data.isLocked = !this.state.scenarios[index].isLocked;
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'scenarios/start-edit',
|
||||
data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
marginLeft: '10px',
|
||||
}
|
||||
|
||||
const iconStyle = {
|
||||
height: '30px',
|
||||
width: '30px'
|
||||
}
|
||||
|
||||
return <div className='section'>
|
||||
<h1>Scenarios
|
||||
<h1>Scenarios
|
||||
<span className='icon-button'>
|
||||
<IconButton
|
||||
overlaykey={0}
|
||||
tooltip='Add Scenario'
|
||||
onClick={() => this.setState({ newModal: true })}
|
||||
icon='plus'
|
||||
/>
|
||||
<IconButton
|
||||
overlaykey={1}
|
||||
tooltip='Import Scenario'
|
||||
onClick={() => this.setState({ importModal: true })}
|
||||
icon='upload'
|
||||
/>
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<Table data={this.state.scenarios}>
|
||||
{this.state.currentUser.role === "Admin" ?
|
||||
<TableColumn
|
||||
title='ID'
|
||||
dataKey='id'
|
||||
/>
|
||||
: <></>
|
||||
}
|
||||
<TableColumn
|
||||
title='Name'
|
||||
dataKey='name'
|
||||
link='/scenarios/'
|
||||
linkKey='id'
|
||||
<IconButton
|
||||
ikey={0}
|
||||
tooltip='Add Scenario'
|
||||
onClick={() => this.setState({ newModal: true })}
|
||||
icon='plus'
|
||||
buttonStyle={buttonStyle}
|
||||
iconStyle={iconStyle}
|
||||
/>
|
||||
<TableColumn
|
||||
title='Running'
|
||||
dataKey='running'
|
||||
modifier={(running) => this.modifyRunningColumn(running)}
|
||||
<IconButton
|
||||
ikey={1}
|
||||
tooltip='Import Scenario'
|
||||
onClick={() => this.setState({ importModal: true })}
|
||||
icon='upload'
|
||||
buttonStyle={buttonStyle}
|
||||
iconStyle={iconStyle}
|
||||
/>
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<Table data={this.state.scenarios}>
|
||||
{this.state.currentUser.role === "Admin" ?
|
||||
<TableColumn
|
||||
width='200'
|
||||
align='right'
|
||||
editButton
|
||||
deleteButton
|
||||
exportButton
|
||||
duplicateButton
|
||||
onEdit={index => this.setState({ editModal: true, modalScenario: this.state.scenarios[index] })}
|
||||
onDelete={index => this.setState({ deleteModal: true, modalScenario: this.state.scenarios[index] })}
|
||||
onExport={index => this.exportScenario(index)}
|
||||
onDuplicate={index => this.duplicateScenario(index)}
|
||||
title='ID'
|
||||
dataKey='id'
|
||||
/>
|
||||
</Table>
|
||||
: <></>
|
||||
}
|
||||
<TableColumn
|
||||
title='Name'
|
||||
dataKey='name'
|
||||
link='/scenarios/'
|
||||
linkKey='id'
|
||||
/>
|
||||
{this.state.currentUser.role === "Admin" ?
|
||||
<TableColumn
|
||||
title='Locked'
|
||||
lockButton
|
||||
checkboxKey='isLocked'
|
||||
onChangeLock={(index, event) => this.onLock(index)}
|
||||
isLocked={index => this.isLocked(index)}
|
||||
/>
|
||||
: <></>
|
||||
}
|
||||
<TableColumn
|
||||
width='200'
|
||||
align='right'
|
||||
editButton
|
||||
deleteButton
|
||||
exportButton
|
||||
duplicateButton
|
||||
onEdit={index => this.setState({ editModal: true, modalScenario: this.state.scenarios[index] })}
|
||||
onDelete={index => this.setState({ deleteModal: true, modalScenario: this.state.scenarios[index] })}
|
||||
onExport={index => this.exportScenario(index)}
|
||||
onDuplicate={index => this.duplicateScenario(index)}
|
||||
isLocked={index => this.isLocked(index)}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
<NewScenarioDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} />
|
||||
<EditScenarioDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} scenario={this.state.modalScenario} />
|
||||
<ImportScenarioDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} nodes={this.state.nodes} />
|
||||
<NewScenarioDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} />
|
||||
<EditScenarioDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} scenario={this.state.modalScenario} />
|
||||
<ImportScenarioDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} nodes={this.state.nodes} />
|
||||
|
||||
<DeleteDialog title="scenario" name={this.state.modalScenario.name} show={this.state.deleteModal} onClose={(e) => this.closeDeleteModal(e)} />
|
||||
</div>;
|
||||
<DeleteDialog title="scenario" name={this.state.modalScenario.name} show={this.state.deleteModal} onClose={(e) => this.closeDeleteModal(e)} />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { NavbarBrand } from 'react-bootstrap';
|
||||
import NotificationSystem from 'react-notification-system';
|
||||
|
@ -29,15 +29,10 @@ import AppDispatcher from '../common/app-dispatcher';
|
|||
import branding from '../branding/branding';
|
||||
|
||||
|
||||
class Login extends Component {
|
||||
class Login extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Load config in case the user goes directly to /login
|
||||
// otherwise it will be loaded in app constructor
|
||||
AppDispatcher.dispatch({
|
||||
type: 'config/load',
|
||||
});
|
||||
}
|
||||
|
||||
static getStores() {
|
||||
|
@ -57,6 +52,12 @@ class Login extends Component {
|
|||
|
||||
componentDidMount() {
|
||||
NotificationsDataManager.setSystem(this.refs.notificationSystem);
|
||||
|
||||
// load config in case the user goes directly to /login
|
||||
// otherwise it will be loaded in app constructor
|
||||
AppDispatcher.dispatch({
|
||||
type: 'config/load',
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -26,11 +26,7 @@ class Logout extends React.Component {
|
|||
type: 'users/logout'
|
||||
});
|
||||
|
||||
// The Login Store is deleted automatically
|
||||
|
||||
// discard login token and current User
|
||||
localStorage.setItem('token', '');
|
||||
localStorage.setItem('currentUser', '');
|
||||
// The Login Store and local storage are deleted automatically
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -40,4 +36,4 @@ class Logout extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default Logout;
|
||||
export default Logout;
|
||||
|
|
|
@ -119,7 +119,7 @@ class Users extends Component {
|
|||
<h1>Users
|
||||
<span className='icon-button'>
|
||||
<IconButton
|
||||
overlaykey={0}
|
||||
ikey={0}
|
||||
tooltip='Add User'
|
||||
onClick={() => this.setState({ newModal: true })}
|
||||
icon='plus'
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Form, OverlayTrigger, Tooltip, Button } from 'react-bootstrap';
|
||||
import { Form, OverlayTrigger, Tooltip, Button, Col } from 'react-bootstrap';
|
||||
import ColorPicker from './color-picker'
|
||||
import Icon from "../../common/icon";
|
||||
|
||||
|
@ -77,8 +77,8 @@ class EditWidgetColorControl extends Component {
|
|||
let style = {
|
||||
backgroundColor: color,
|
||||
opacity: opacity,
|
||||
width: '260px',
|
||||
height: '40px'
|
||||
width: '80px',
|
||||
height: '40px',
|
||||
}
|
||||
|
||||
let tooltipText = "Change color and opacity";
|
||||
|
@ -87,19 +87,23 @@ class EditWidgetColorControl extends Component {
|
|||
}
|
||||
|
||||
|
||||
return <Form.Group>
|
||||
<Form.Label>{this.props.label}</Form.Label>
|
||||
return ( <Form.Row>
|
||||
<Form.Group as={Col}>
|
||||
<Form.Label>{this.props.label}</Form.Label>
|
||||
</Form.Group>
|
||||
|
||||
<div className='section-buttons-group-right'>
|
||||
<OverlayTrigger key={0} placement={'right'} overlay={<Tooltip id={`tooltip-${"color"}`}> {tooltipText} </Tooltip>} >
|
||||
<Button key={2} style={style} onClick={this.openColorPicker.bind(this)} >
|
||||
<Icon icon="paint-brush"/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
|
||||
<ColorPicker show={this.state.showColorPicker} onClose={(data) => this.closeEditModal(data)} widget={this.state.widget} controlId={this.props.controlId} disableOpacity={this.props.disableOpacity}/>
|
||||
</Form.Group>;
|
||||
<Form.Group as={Col}>
|
||||
<div className='section-buttons-group-right'>
|
||||
<OverlayTrigger key={0} placement={'right'} overlay={<Tooltip id={`tooltip-${"color"}`}> {tooltipText} </Tooltip>} >
|
||||
<Button style={style} onClick={this.openColorPicker.bind(this)} >
|
||||
<Icon icon="paint-brush"/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
<ColorPicker show={this.state.showColorPicker} onClose={(data) => this.closeEditModal(data)} widget={this.state.widget} controlId={this.props.controlId} disableOpacity={this.props.disableOpacity}/>
|
||||
</Form.Group>
|
||||
</Form.Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -174,6 +174,7 @@ class EditWidgetDialog extends React.Component {
|
|||
onClose={(c) => this.onClose(c)}
|
||||
onReset={() => this.resetState()}
|
||||
valid={this.valid}
|
||||
size={'sm'}
|
||||
>
|
||||
<Form encType='multipart/form-data'>
|
||||
{ controls || '' }
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Menu, Item, Separator, MenuProvider } from 'react-contexify';
|
||||
import { Menu, Item, Separator, contextMenu } from 'react-contexify';
|
||||
import Widget from './widget';
|
||||
|
||||
class WidgetContextMenu extends React.Component {
|
||||
|
@ -93,45 +93,64 @@ class WidgetContextMenu extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
showMenu = e => {
|
||||
let index = this.props.index
|
||||
if (this.props.editing){
|
||||
contextMenu.show({
|
||||
event: e,
|
||||
id: 'widgetMenu' + index,
|
||||
position: {
|
||||
x: 'inherit',
|
||||
y: 'inherit',
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
contextMenu.show({
|
||||
event: e,
|
||||
id: 'widgetMenu' + index,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const isLocked = this.props.widget.locked;
|
||||
const ContextMenu = () => (
|
||||
<Menu id={'widgetMenu'+ this.props.index} style={{zIndex: 1000, display: 'inline-block'}}>
|
||||
<Item disabled={isLocked} onClick={this.editWidget}>Edit</Item>
|
||||
<Item disabled={isLocked} onClick={this.duplicateWidget}>Duplicate</Item>
|
||||
<Item disabled={isLocked} onClick={this.deleteWidget}>Delete</Item>
|
||||
|
||||
<Separator />
|
||||
let dim = {
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
};
|
||||
|
||||
<Item disabled={isLocked} onClick={this.moveAbove}>Move above</Item>
|
||||
<Item disabled={isLocked} onClick={this.moveToFront}>Move to front</Item>
|
||||
<Item disabled={isLocked} onClick={this.moveUnderneath}>Move underneath</Item>
|
||||
<Item disabled={isLocked} onClick={this.moveToBack}>Move to back</Item>
|
||||
return (
|
||||
|
||||
<Separator />
|
||||
<div style={dim} onContextMenu={this.showMenu}>
|
||||
<Widget
|
||||
data={this.props.widget}
|
||||
onWidgetChange={this.props.onWidgetChange}
|
||||
editing={this.props.editing}
|
||||
index={this.props.index}
|
||||
paused={this.props.paused}
|
||||
/>
|
||||
|
||||
<Item disabled={isLocked} onClick={this.lockWidget}>Lock</Item>
|
||||
<Item disabled={isLocked === false} onClick={this.unlockWidget}>Unlock</Item>
|
||||
</Menu>
|
||||
);
|
||||
<Menu id={'widgetMenu' + this.props.index}>
|
||||
<Item disabled={isLocked} onClick={this.editWidget}>Edit</Item>
|
||||
<Item disabled={isLocked} onClick={this.duplicateWidget}>Duplicate</Item>
|
||||
<Item disabled={isLocked} onClick={this.deleteWidget}>Delete</Item>
|
||||
|
||||
let dim = {
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
};
|
||||
<Separator />
|
||||
|
||||
return <div style={dim}>
|
||||
<MenuProvider id={'widgetMenu'+ this.props.index} style={dim}>
|
||||
<Widget
|
||||
data={this.props.widget}
|
||||
onWidgetChange={this.props.onWidgetChange}
|
||||
editing={this.props.editing}
|
||||
index={this.props.index}
|
||||
paused={this.props.paused}
|
||||
/>
|
||||
</MenuProvider>
|
||||
<ContextMenu />
|
||||
</div>
|
||||
<Item disabled={isLocked} onClick={this.moveAbove}>Move above</Item>
|
||||
<Item disabled={isLocked} onClick={this.moveToFront}>Move to front</Item>
|
||||
<Item disabled={isLocked} onClick={this.moveUnderneath}>Move underneath</Item>
|
||||
<Item disabled={isLocked} onClick={this.moveToBack}>Move to back</Item>
|
||||
|
||||
<Separator />
|
||||
|
||||
<Item disabled={isLocked} onClick={this.lockWidget}>Lock</Item>
|
||||
<Item disabled={isLocked === false} onClick={this.unlockWidget}>Unlock</Item>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue