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-use-json-schema
This commit is contained in:
commit
9e4859786b
32 changed files with 935 additions and 807 deletions
3
package-lock.json
generated
3
package-lock.json
generated
|
@ -17872,7 +17872,8 @@
|
|||
},
|
||||
"ssri": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
|
||||
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
|
||||
"requires": {
|
||||
"figgy-pudding": "^3.5.1"
|
||||
}
|
||||
|
|
110
src/common/color-picker.js
Normal file
110
src/common/color-picker.js
Normal file
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* 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 { Form } from 'react-bootstrap';
|
||||
import { SketchPicker } from 'react-color';
|
||||
import Dialog from './dialogs/dialog';
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
class ColorPicker extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
rgbColor: {},
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<P>, prevState: Readonly<S>, snapshot: SS) {
|
||||
|
||||
if(this.props.show !== prevProps.show){
|
||||
// update color if show status of color picker has changed
|
||||
this.setState({rgbColor: ColorPicker.hexToRgb(this.props.hexcolor,this.props.opacity)})
|
||||
}
|
||||
}
|
||||
|
||||
static hexToRgb(hex,opacity) {
|
||||
let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result ? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16),
|
||||
a: opacity
|
||||
} : null;
|
||||
}
|
||||
|
||||
handleChangeComplete = (color) => {
|
||||
this.setState({rgbColor: color.rgb})
|
||||
};
|
||||
|
||||
onClose = canceled => {
|
||||
if (canceled) {
|
||||
if (this.props.onClose != null) {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.props.onClose != null) {
|
||||
|
||||
const rgbToHex = (r, g, b) => '#' + [r, g, b].map(x => {
|
||||
const hex = x.toString(16)
|
||||
return hex.length === 1 ? '0' + hex : hex
|
||||
}).join('')
|
||||
|
||||
let data = {
|
||||
hexcolor: rgbToHex(this.state.rgbColor.r, this.state.rgbColor.g,this.state.rgbColor.b),
|
||||
opacity: this.state.rgbColor.a,
|
||||
}
|
||||
|
||||
this.props.onClose(data);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
return <Dialog
|
||||
show={this.props.show}
|
||||
title='Color Picker'
|
||||
buttonTitle='Save'
|
||||
onClose={(c) => this.onClose(c)}
|
||||
valid={true}
|
||||
>
|
||||
<Form>
|
||||
<SketchPicker
|
||||
color={this.state.rgbColor}
|
||||
disableAlpha={this.props.disableOpacity}
|
||||
onChangeComplete={ this.handleChangeComplete }
|
||||
width={300}
|
||||
/>
|
||||
</Form>
|
||||
</Dialog>;
|
||||
}
|
||||
}
|
||||
|
||||
ColorPicker.propTypes = {
|
||||
onClose: PropTypes.func,
|
||||
disableOpacity: PropTypes.bool,
|
||||
show: PropTypes.bool,
|
||||
hexcolor: PropTypes.string,
|
||||
opacity: PropTypes.number
|
||||
}
|
||||
|
||||
export default ColorPicker;
|
|
@ -186,17 +186,17 @@ 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) {
|
||||
// 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;
|
||||
}
|
||||
// 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;
|
||||
})
|
||||
return result;
|
||||
})
|
||||
|
||||
|
||||
if (relevantICs.length > 0) {
|
||||
console.log("Starting to open IC websockets:", relevantICs);
|
||||
AppDispatcher.dispatch({
|
||||
|
|
|
@ -110,6 +110,7 @@ class ICDataStore extends ReduceStore {
|
|||
state[action.ic].input.timestamp = Date.now();
|
||||
state[action.ic].input.sequence++;
|
||||
state[action.ic].input.values[action.signal-1] = action.data;
|
||||
state[action.ic].input.length = state[action.ic].input.values.length
|
||||
|
||||
// copy of state needed because changes are not yet propagated
|
||||
let input = JSON.parse(JSON.stringify(state[action.ic].input));
|
||||
|
|
|
@ -114,15 +114,21 @@ class InfrastructureComponentStore extends ArrayStore {
|
|||
|
||||
case 'ics/status-received':
|
||||
let tempIC = action.ic;
|
||||
if(!tempIC.managedexternally){
|
||||
if (!tempIC.managedexternally) {
|
||||
tempIC.state = action.data.state;
|
||||
tempIC.uptime = action.data.time_now - action.data.time_started;
|
||||
tempIC.statusupdateraw = action.data
|
||||
tempIC.uuid = action.data.uuid
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/start-edit',
|
||||
data: tempIC,
|
||||
token: action.token,
|
||||
});
|
||||
|
||||
if(tempIC.type === "villas-node"){
|
||||
ICsDataManager.getConfig(action.url, action.token, tempIC);
|
||||
}
|
||||
|
||||
}
|
||||
return super.reduce(state, action);
|
||||
|
||||
|
@ -130,9 +136,48 @@ class InfrastructureComponentStore extends ArrayStore {
|
|||
console.log("status error:", action.error);
|
||||
return super.reduce(state, action);
|
||||
|
||||
case 'ics/nodestats-received':
|
||||
case 'ics/config-error':
|
||||
console.log("config error:", action.error);
|
||||
return super.reduce(state, action);
|
||||
|
||||
case 'ics/config-received':
|
||||
let temp = action.ic;
|
||||
if (!temp.managedexternally) {
|
||||
if (temp.statusupdateraw === null || temp.statusupdateraw === undefined) {
|
||||
temp.statusupdateraw = {};
|
||||
}
|
||||
temp.statusupdateraw["config"] = action.data;
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/start-edit',
|
||||
data: temp,
|
||||
token: action.token,
|
||||
});
|
||||
|
||||
if(temp.type === "villas-node") {
|
||||
ICsDataManager.getStatistics(action.url, action.token, temp);
|
||||
}
|
||||
|
||||
}
|
||||
return super.reduce(state, action);
|
||||
|
||||
case 'ics/statistics-error':
|
||||
if (action.error.status === 400){
|
||||
// in case of bad request add the error message to the raw status
|
||||
// most likely the statistics collection is disabled for this node
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/statistics-received',
|
||||
data: action.error.response.text,
|
||||
token: action.token,
|
||||
ic: action.ic
|
||||
});
|
||||
} else {
|
||||
console.log("statistics error:", action.error);
|
||||
}
|
||||
return super.reduce(state, action);
|
||||
|
||||
case 'ics/statistics-received':
|
||||
let tempIC2 = action.ic;
|
||||
if(!tempIC2.managedexternally){
|
||||
if (!tempIC2.managedexternally) {
|
||||
if (tempIC2.statusupdateraw === null || tempIC2.statusupdateraw === undefined) {
|
||||
tempIC2.statusupdateraw = {};
|
||||
}
|
||||
|
@ -145,10 +190,6 @@ class InfrastructureComponentStore extends ArrayStore {
|
|||
}
|
||||
return super.reduce(state, action);
|
||||
|
||||
case 'ics/nodestats-error':
|
||||
console.log("nodestats error:", action.error);
|
||||
return super.reduce(state, action);
|
||||
|
||||
case 'ics/restart':
|
||||
ICsDataManager.restart(action.url, action.token);
|
||||
return super.reduce(state, action);
|
||||
|
|
483
src/ic/ic.js
483
src/ic/ic.js
|
@ -16,225 +16,320 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import InfrastructureComponentStore from './ic-store';
|
||||
import ICstore from './ic-store';
|
||||
import ICdataStore from './ic-data-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";
|
||||
import IconButton from '../common/icon-button';
|
||||
import FileSaver from 'file-saver';
|
||||
|
||||
|
||||
|
||||
class InfrastructureComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
confirmCommand: false,
|
||||
command: '',
|
||||
};
|
||||
this.state = {
|
||||
confirmCommand: false,
|
||||
command: '',
|
||||
sessionToken: localStorage.getItem("token"),
|
||||
currentUser: JSON.parse(localStorage.getItem("currentUser")),
|
||||
};
|
||||
}
|
||||
|
||||
static getStores() {
|
||||
return [ICstore, ICdataStore];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
return {
|
||||
ic: ICstore.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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
refresh() {
|
||||
// get status of VILLASnode and VILLASrelay ICs
|
||||
if (this.state.ic.category === "gateway" && (this.state.ic.type === "villas-node" || this.state.ic.type === "villas-relay")
|
||||
&& this.state.ic.apiurl !== '' && this.state.ic.apiurl !== undefined && this.state.ic.apiurl !== null && !this.state.ic.managedexternally) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/get-status',
|
||||
url: this.state.ic.apiurl,
|
||||
token: this.state.sessionToken,
|
||||
ic: this.state.ic
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
static getStores() {
|
||||
return [InfrastructureComponentStore];
|
||||
this.setState({confirmCommand: false, command: ''});
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
if (this.state.ic === undefined) {
|
||||
return <h1>Loading Infrastructure Component...</h1>;
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
let graphURL = ""
|
||||
if (this.state.ic.apiurl !== "") {
|
||||
graphURL = this.state.ic.apiurl + "/graph.svg"
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let icID = parseInt(this.props.match.params.ic, 10);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/start-load',
|
||||
data: icID,
|
||||
token: this.state.sessionToken,
|
||||
});
|
||||
const buttonStyle = {
|
||||
marginLeft: '5px',
|
||||
}
|
||||
|
||||
isJSON(data) {
|
||||
if (data === undefined || data === null) {
|
||||
return false;
|
||||
}
|
||||
let str = JSON.stringify(data);
|
||||
try {
|
||||
JSON.parse(str)
|
||||
}
|
||||
catch (ex) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
const iconStyle = {
|
||||
height: '25px',
|
||||
width: '25px'
|
||||
}
|
||||
|
||||
async downloadGraph(url) {
|
||||
let blob = await fetch(url).then(r => r.blob())
|
||||
FileSaver.saveAs(blob, this.state.ic.name + ".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>
|
||||
{this.isJSON(this.state.ic.startparameterschema) ?
|
||||
<ReactJson
|
||||
src={this.state.ic.startparameterschema}
|
||||
name={false}
|
||||
displayDataTypes={false}
|
||||
displayObjectSize={false}
|
||||
enableClipboard={false}
|
||||
collapsed={0}
|
||||
/> : <div>No Start parameter schema JSON available.</div>}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
</Col>
|
||||
<Col>
|
||||
{this.state.category ==="gateway" && this.state.ic.type === "villas-node" ?
|
||||
<>
|
||||
<div className='section-buttons-group-right'>
|
||||
<IconButton
|
||||
childKey={0}
|
||||
tooltip='Download Graph'
|
||||
onClick={() => this.downloadGraph(graphURL)}
|
||||
icon='download'
|
||||
buttonStyle={buttonStyle}
|
||||
iconStyle={iconStyle}
|
||||
/>
|
||||
</div>
|
||||
<hr/>
|
||||
<b>Graph:</b>
|
||||
<div>
|
||||
<img alt={"Graph image download failed and/or incorrect image API URL"} src={graphURL} />
|
||||
</div>
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
{this.state.currentUser.role === "Admin" ?
|
||||
<div>
|
||||
<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 />}
|
||||
|
||||
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");
|
||||
}
|
||||
{this.state.category ==="gateway" && this.state.ic.type === "villas-relay" ?
|
||||
<>
|
||||
<div className='section-buttons-group-right'>
|
||||
<IconButton
|
||||
childKey={1}
|
||||
tooltip='Refresh'
|
||||
onClick={() => this.refresh()}
|
||||
icon='sync-alt'
|
||||
buttonStyle={buttonStyle}
|
||||
iconStyle={iconStyle}
|
||||
/>
|
||||
</div>
|
||||
<hr/>
|
||||
<b>Raw Status</b>
|
||||
{this.state.ic.statusupdateraw !== null && this.isJSON(this.state.ic.statusupdateraw) ?
|
||||
<ReactJson
|
||||
src={this.state.ic.statusupdateraw}
|
||||
name={false}
|
||||
displayDataTypes={false}
|
||||
displayObjectSize={false}
|
||||
enableClipboard={false}
|
||||
collapsed={1}
|
||||
/> : <div>No valid JSON raw data available.</div>}
|
||||
</>
|
||||
:
|
||||
<div />}
|
||||
</Col>
|
||||
</Row>
|
||||
{this.state.category ==="gateway" && this.state.ic.type === "villas-node" ?
|
||||
<>
|
||||
<div className='section-buttons-group-right'>
|
||||
<IconButton
|
||||
childKey={2}
|
||||
tooltip='Refresh'
|
||||
onClick={() => this.refresh()}
|
||||
icon='sync-alt'
|
||||
buttonStyle={buttonStyle}
|
||||
iconStyle={iconStyle}
|
||||
/>
|
||||
</div>
|
||||
<Row>
|
||||
|
||||
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>;
|
||||
}
|
||||
<Col>
|
||||
<b>Raw Status</b>
|
||||
{this.state.ic.statusupdateraw !== null && this.isJSON(this.state.ic.statusupdateraw) ?
|
||||
<ReactJson
|
||||
src={this.state.ic.statusupdateraw}
|
||||
name={false}
|
||||
displayDataTypes={false}
|
||||
displayObjectSize={false}
|
||||
enableClipboard={false}
|
||||
collapsed={1}
|
||||
/> : <div>No valid JSON raw data available.</div>}
|
||||
</Col>
|
||||
<Col>
|
||||
<b>Raw Config</b>
|
||||
{this.state.ic.statusupdateraw && this.isJSON(this.state.ic.statusupdateraw["config"]) ?
|
||||
<ReactJson
|
||||
src={this.state.ic.statusupdateraw["config"]}
|
||||
name={false}
|
||||
displayDataTypes={false}
|
||||
displayObjectSize={false}
|
||||
enableClipboard={false}
|
||||
collapsed={1}
|
||||
/> : <div>No valid config JSON raw data available.</div>}
|
||||
</Col>
|
||||
<Col>
|
||||
<b>Raw Statistics</b>
|
||||
{this.state.ic.statusupdateraw && this.isJSON(this.state.ic.statusupdateraw["statistics"]) ?
|
||||
<ReactJson
|
||||
src={this.state.ic.statusupdateraw["statistics"]}
|
||||
name={false}
|
||||
displayDataTypes={false}
|
||||
displayObjectSize={false}
|
||||
enableClipboard={false}
|
||||
collapsed={1}
|
||||
/> : <div>No valid statistics JSON raw data available.</div>}
|
||||
</Col>
|
||||
|
||||
</Row>
|
||||
</>: <div />}
|
||||
</Container>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
let fluxContainerConverter = require('../common/FluxContainerConverter');
|
||||
export default FluxContainer.create(fluxContainerConverter.convert(InfrastructureComponent), { withProps: true });
|
||||
export default FluxContainer.create(fluxContainerConverter.convert(InfrastructureComponent), { withProps: true });
|
||||
|
|
|
@ -102,12 +102,17 @@ class IcsDataManager extends RestDataManager {
|
|||
}
|
||||
|
||||
getStatus(url,token,ic){
|
||||
RestAPI.get(url + "/status", null).then(response => {
|
||||
let requestURL = url;
|
||||
if(ic.type === "villas-node"){
|
||||
requestURL += "/status";
|
||||
}
|
||||
RestAPI.get(requestURL, null).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/status-received',
|
||||
data: response,
|
||||
token: token,
|
||||
ic: ic
|
||||
ic: ic,
|
||||
url: url
|
||||
});
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
|
@ -116,24 +121,51 @@ class IcsDataManager extends RestDataManager {
|
|||
})
|
||||
})
|
||||
|
||||
// get name of websocket
|
||||
/*let ws_api = ic.websocketurl.split("/")
|
||||
let ws_name = ws_api[ws_api.length-1] // websocket name is the last element in the websocket url
|
||||
}
|
||||
|
||||
getConfig(url,token,ic){
|
||||
|
||||
// get the name of the node
|
||||
let ws_api = ic.websocketurl.split("/")
|
||||
let ws_name = ws_api[ws_api.length-1] // name is the last element in the websocket url
|
||||
|
||||
RestAPI.get(url + "/node/" + ws_name, null).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/config-received',
|
||||
data: response,
|
||||
token: token,
|
||||
ic: ic,
|
||||
url: url
|
||||
});
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/config-error',
|
||||
error: error
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
getStatistics(url,token,ic){
|
||||
|
||||
// get the name of the node
|
||||
let ws_api = ic.websocketurl.split("/")
|
||||
let ws_name = ws_api[ws_api.length-1] // name is the last element in the websocket url
|
||||
|
||||
RestAPI.get(url + "/node/" + ws_name + "/stats", null).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/nodestats-received',
|
||||
type: 'ics/statistics-received',
|
||||
data: response,
|
||||
token: token,
|
||||
ic: ic
|
||||
});
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/nodestats-error',
|
||||
error: error
|
||||
type: 'ics/statistics-error',
|
||||
error: error,
|
||||
token: token,
|
||||
ic: ic
|
||||
})
|
||||
})*/
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
restart(url,token){
|
||||
|
|
|
@ -133,20 +133,21 @@ class InfrastructureComponents extends Component {
|
|||
token: this.state.sessionToken,
|
||||
});
|
||||
|
||||
// get status of VILLASnode and VILLASrelay ICs
|
||||
this.state.ics.forEach(ic => {
|
||||
if ((ic.type === "villas-node" || ic.type === "villas-relay")
|
||||
&& ic.apiurl !== '' && ic.apiurl !== undefined && ic.apiurl !== null && !ic.managedexternally) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/get-status',
|
||||
url: ic.apiurl,
|
||||
token: this.state.sessionToken,
|
||||
ic: ic
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// get status of VILLASnode and VILLASrelay ICs
|
||||
this.state.ics.forEach(ic => {
|
||||
if (ic.category === "gateway" && (ic.type === "villas-node" || ic.type === "villas-relay")
|
||||
&& ic.apiurl !== '' && ic.apiurl !== undefined && ic.apiurl !== null && !ic.managedexternally) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/get-status',
|
||||
url: ic.apiurl,
|
||||
token: this.state.sessionToken,
|
||||
ic: ic
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
closeNewModal(data) {
|
||||
|
@ -201,9 +202,6 @@ class InfrastructureComponents extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
closeICModal(data){
|
||||
this.setState({ icModal : false });
|
||||
}
|
||||
|
||||
closeDeleteModal(confirmDelete){
|
||||
this.setState({ deleteModal: false });
|
||||
|
@ -345,14 +343,6 @@ class InfrastructureComponents extends Component {
|
|||
return dateTime.fromNow()
|
||||
}
|
||||
|
||||
modifyManagedExternallyColumn(managedExternally, component){
|
||||
if(managedExternally){
|
||||
return <Icon icon='check' />
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
modifyUptimeColumn(uptime, component){
|
||||
if(uptime >= 0){
|
||||
let momentDurationFormatSetup = require("moment-duration-format");
|
||||
|
@ -366,11 +356,6 @@ class InfrastructureComponents extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
openICStatus(ic){
|
||||
let index = this.state.ics.indexOf(ic);
|
||||
this.setState({ icModal: true, modalIC: ic, modalIndex: index })
|
||||
}
|
||||
|
||||
isLocalIC(index, ics){
|
||||
let ic = ics[index]
|
||||
return !ic.managedexternally
|
||||
|
@ -450,10 +435,9 @@ class InfrastructureComponents extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
|
||||
const buttonStyle = {
|
||||
marginLeft: '10px'
|
||||
};
|
||||
marginLeft: '10px',
|
||||
}
|
||||
|
||||
const iconStyle = {
|
||||
height: '30px',
|
||||
|
|
|
@ -30,6 +30,7 @@ import SignalStore from '../signal/signal-store'
|
|||
import FileStore from "../file/file-store"
|
||||
import WidgetStore from "../widget/widget-store";
|
||||
import ResultStore from "../result/result-store"
|
||||
import UsersStore from "../user/users-store"
|
||||
|
||||
import DashboardTable from '../dashboard/dashboard-table'
|
||||
import ResultTable from "../result/result-table";
|
||||
|
@ -67,7 +68,7 @@ class Scenario extends React.Component {
|
|||
}
|
||||
|
||||
static getStores() {
|
||||
return [ScenarioStore, ConfigStore, DashboardStore, ICStore, SignalStore, FileStore, WidgetStore, ResultStore];
|
||||
return [ScenarioStore, ConfigStore, DashboardStore, ICStore, SignalStore, FileStore, WidgetStore, ResultStore, UsersStore];
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
|
|
@ -18,21 +18,19 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import FileSaver from 'file-saver';
|
||||
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
import ScenarioStore from './scenario-store';
|
||||
import DashboardStore from '../dashboard/dashboard-store';
|
||||
import WidgetStore from "../widget/widget-store";
|
||||
import ConfigStore from '../componentconfig/config-store';
|
||||
import SignalStore from '../signal/signal-store'
|
||||
|
||||
import Icon from '../common/icon';
|
||||
import ResultStore from '../result/result-store'
|
||||
import FileStore from '../file/file-store'
|
||||
import Table from '../common/table';
|
||||
import TableColumn from '../common/table-column';
|
||||
import NewScenarioDialog from './new-scenario';
|
||||
import EditScenarioDialog from './edit-scenario';
|
||||
import ImportScenarioDialog from './import-scenario';
|
||||
|
||||
import DeleteDialog from '../common/dialogs/delete-dialog';
|
||||
import IconButton from '../common/icon-button';
|
||||
|
||||
|
@ -40,7 +38,7 @@ import IconButton from '../common/icon-button';
|
|||
class Scenarios extends Component {
|
||||
|
||||
static getStores() {
|
||||
return [ScenarioStore, DashboardStore, WidgetStore, ConfigStore, SignalStore];
|
||||
return [ScenarioStore, DashboardStore, WidgetStore, ConfigStore, SignalStore, ResultStore, FileStore];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
|
|
|
@ -95,7 +95,6 @@ class EditSignalMappingDialog extends React.Component {
|
|||
let signals = this.state.signals;
|
||||
let modifiedSignals = this.state.modifiedSignalIDs;
|
||||
|
||||
console.log("HandleMappingChange", row, column)
|
||||
if (column === 2) { // Name change
|
||||
signals[row].name = event.target.value;
|
||||
if (modifiedSignals.find(id => id === signals[row].id) === undefined) {
|
||||
|
@ -112,7 +111,6 @@ class EditSignalMappingDialog extends React.Component {
|
|||
modifiedSignals.push(signals[row].id);
|
||||
}
|
||||
} else if (column === 1) { //index change
|
||||
console.log("Index change")
|
||||
signals[row].index =parseInt(event.target.value, 10);
|
||||
if (modifiedSignals.find(id => id === signals[row].id) === undefined) {
|
||||
modifiedSignals.push(signals[row].id);
|
||||
|
|
|
@ -71,7 +71,7 @@ class SignalsDataManager extends RestDataManager{
|
|||
let configured = false;
|
||||
let error = false;
|
||||
for(let nodeConfig of nodes){
|
||||
console.log("parsing node config: ", nodeConfig)
|
||||
//console.log("parsing node config: ", nodeConfig)
|
||||
if(!nodeConfig.hasOwnProperty("name")){
|
||||
console.warn("Could not parse the following node config because it lacks a name parameter:", nodeConfig);
|
||||
} else if(nodeConfig.name === socketname){
|
||||
|
@ -128,7 +128,6 @@ class SignalsDataManager extends RestDataManager{
|
|||
for (let outSig of nodeConfig.out.signals) {
|
||||
|
||||
if (outSig.enabled) {
|
||||
console.log("adding output signal:", outSig);
|
||||
let newSignal = {
|
||||
configID: configID,
|
||||
direction: 'out',
|
||||
|
|
|
@ -19,17 +19,16 @@ import React from 'react';
|
|||
import { Form, Col } from 'react-bootstrap';
|
||||
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
import NotificationsDataManager from "../common/data-managers/notifications-data-manager";
|
||||
import NotificationsFactory from "../common/data-managers/notifications-factory";
|
||||
|
||||
|
||||
class EditOwnUserDialog extends React.Component {
|
||||
valid: true;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
username: this.props.user.username,
|
||||
id: this.props.user.id,
|
||||
username: "",
|
||||
mail: this.props.user.mail,
|
||||
password: '',
|
||||
oldPassword: '',
|
||||
|
@ -39,9 +38,29 @@ class EditOwnUserDialog extends React.Component {
|
|||
|
||||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (this.valid) {
|
||||
this.props.onClose(this.state);
|
||||
|
||||
let user = {};
|
||||
user.id = this.props.user.id;
|
||||
user.password = this.state.password;
|
||||
user.oldPassword = this.state.oldPassword;
|
||||
user.confirmPassword = this.state.confirmPassword
|
||||
|
||||
if (this.state.username != null && this.state.username !== this.props.user.username){
|
||||
user.username = this.state.username;
|
||||
}
|
||||
|
||||
if (this.state.mail != null && this.state.mail !== this.props.user.mail){
|
||||
user.mail = this.state.mail;
|
||||
}
|
||||
|
||||
if (this.state.password !== '' && this.state.oldPassword !== '' && this.state.password === this.state.confirmPassword ) {
|
||||
user.password = this.state.password;
|
||||
user.oldPassword = this.state.oldPassword;
|
||||
} else if (this.state.password !== '' && this.state.password !== this.state.confirmPassword) {
|
||||
NotificationsDataManager.addNotification(NotificationsFactory.UPDATE_ERROR('New password not correctly confirmed'));
|
||||
}
|
||||
|
||||
this.props.onClose(user);
|
||||
} else {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
@ -49,42 +68,11 @@ class EditOwnUserDialog extends React.Component {
|
|||
|
||||
handleChange(e) {
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
|
||||
// check all controls
|
||||
let username = true;
|
||||
let mail = true;
|
||||
let pw = true;
|
||||
let oldPassword = true;
|
||||
let confirmPassword = true;
|
||||
|
||||
if (this.state.username === '') {
|
||||
username = false;
|
||||
}
|
||||
|
||||
if (this.state.mail === '') {
|
||||
mail = false;
|
||||
}
|
||||
|
||||
if (this.state.password === '') {
|
||||
pw = false;
|
||||
}
|
||||
|
||||
if (this.state.oldPassword === '') {
|
||||
oldPassword = false;
|
||||
}
|
||||
|
||||
if (this.state.confirmPassword === '') {
|
||||
confirmPassword = false;
|
||||
}
|
||||
|
||||
// form is valid if the following condition is met
|
||||
this.valid = username || mail || (oldPassword && pw && confirmPassword);
|
||||
}
|
||||
|
||||
resetState() {
|
||||
this.setState({
|
||||
username: this.props.user.username,
|
||||
id: this.props.user.id,
|
||||
mail: this.props.user.mail,
|
||||
oldPassword: '',
|
||||
confirmPassword: '',
|
||||
|
@ -94,28 +82,28 @@ class EditOwnUserDialog extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Edit user" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<Dialog show={this.props.show} title="Edit user" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={true}>
|
||||
<Form>
|
||||
<Form.Group as={Col} controlId="username">
|
||||
<Form.Label>Username</Form.Label>
|
||||
<Form.Control type="text" value={this.state.username} onChange={(e) => this.handleChange(e)} autocomplete="username" />
|
||||
<Form.Control type="text" placeholder={this.props.user.username} value={this.state.username} onChange={(e) => this.handleChange(e)} autoComplete="username" />
|
||||
<Form.Control.Feedback />
|
||||
</Form.Group>
|
||||
<Form.Group as={Col} controlId="mail">
|
||||
<Form.Label>E-mail</Form.Label>
|
||||
<Form.Control type="text" value={this.state.mail} onChange={(e) => this.handleChange(e)} autocomplete="email" />
|
||||
<Form.Control type="text" placeholder={this.props.user.mail} value={this.state.mail} onChange={(e) => this.handleChange(e)} autoComplete="email" />
|
||||
</Form.Group>
|
||||
<Form.Group as={Col} controlId="oldPassword">
|
||||
<Form.Label>Old Password</Form.Label>
|
||||
<Form.Control type="password" placeholder="Enter current password" value={this.state.oldPassword} onChange={(e) => this.handleChange(e)} autocomplete="current-password" />
|
||||
<Form.Control type="password" placeholder="Enter current password" value={this.state.oldPassword} onChange={(e) => this.handleChange(e)} autoComplete="current-password" />
|
||||
</Form.Group>
|
||||
<Form.Group as={Col} controlId="password">
|
||||
<Form.Label>New Password</Form.Label>
|
||||
<Form.Control type="password" placeholder="Enter new password" value={this.state.password} onChange={(e) => this.handleChange(e)} autocomplete="new-password" />
|
||||
<Form.Control type="password" placeholder="Enter new password" value={this.state.password} onChange={(e) => this.handleChange(e)} autoComplete="new-password" />
|
||||
</Form.Group>
|
||||
<Form.Group as={Col} controlId="confirmPassword">
|
||||
<Form.Label>Confirm New Password</Form.Label>
|
||||
<Form.Control type="password" placeholder="Repeat new password" value={this.state.confirmPassword} onChange={(e) => this.handleChange(e)} autocomplete="new-password" />
|
||||
<Form.Control type="password" placeholder="Repeat new password" value={this.state.confirmPassword} onChange={(e) => this.handleChange(e)} autoComplete="new-password" />
|
||||
</Form.Group>
|
||||
</Form>
|
||||
</Dialog>
|
||||
|
|
|
@ -19,30 +19,55 @@ import React from 'react';
|
|||
import { Form, Col } from 'react-bootstrap';
|
||||
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
import NotificationsDataManager from "../common/data-managers/notifications-data-manager";
|
||||
import NotificationsFactory from "../common/data-managers/notifications-factory";
|
||||
|
||||
class EditUserDialog extends React.Component {
|
||||
valid: true;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
username: props.user.username,
|
||||
mail: props.user.mail,
|
||||
role: props.user.role,
|
||||
id: props.user.id,
|
||||
active: props.user.active,
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
oldPassword: ''
|
||||
username: '',
|
||||
mail: '',
|
||||
role: '',
|
||||
active: '',
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
oldPassword: "",
|
||||
}
|
||||
}
|
||||
|
||||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (this.valid) {
|
||||
this.props.onClose(this.state);
|
||||
|
||||
let user = {}
|
||||
user.id = this.props.user.id;
|
||||
|
||||
if (this.state.username != null && this.state.username !== this.props.user.username){
|
||||
user.username = this.state.username
|
||||
}
|
||||
|
||||
if (this.state.mail != null && this.state.mail !== this.props.user.mail){
|
||||
user.mail = this.state.mail
|
||||
}
|
||||
|
||||
if (this.state.role != null && this.state.role !== this.props.user.role){
|
||||
user.role = this.state.role
|
||||
}
|
||||
|
||||
if (this.state.active != null && this.state.active !== this.props.user.active){
|
||||
user.active = this.state.active
|
||||
}
|
||||
|
||||
if (this.state.password !== '' && this.state.oldPassword !== '' && this.state.password === this.state.confirmPassword) {
|
||||
user.password = this.state.password;
|
||||
user.oldpassword = this.state.oldPassword
|
||||
} else if (this.state.password !== '' && this.state.password !== this.state.confirmPassword){
|
||||
NotificationsDataManager.addNotification(NotificationsFactory.UPDATE_ERROR("New password not correctly confirmed"))
|
||||
}
|
||||
|
||||
this.props.onClose(user);
|
||||
|
||||
} else {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
@ -50,46 +75,6 @@ class EditUserDialog extends React.Component {
|
|||
|
||||
handleChange(e) {
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
|
||||
// check all controls
|
||||
var username = true;
|
||||
var role = true;
|
||||
var mail = true;
|
||||
var pw = true;
|
||||
var active = true;
|
||||
var confirmPassword = true;
|
||||
var oldPassword = true;
|
||||
|
||||
if (this.state.username === '') {
|
||||
username = false;
|
||||
}
|
||||
|
||||
if (this.state.role === '') {
|
||||
role = false;
|
||||
}
|
||||
|
||||
if (this.state.mail === '') {
|
||||
mail = false;
|
||||
}
|
||||
|
||||
if (this.state.password === '') {
|
||||
pw = false;
|
||||
}
|
||||
|
||||
if (this.state.active === '') {
|
||||
active = false;
|
||||
}
|
||||
|
||||
if (this.state.confirmPassword === '') {
|
||||
confirmPassword = false;
|
||||
}
|
||||
|
||||
if (this.state.oldPassword === '') {
|
||||
oldPassword = false;
|
||||
}
|
||||
|
||||
// form is valid if any of the fields contain somethig
|
||||
this.valid = username || role || mail || pw || active || confirmPassword || oldPassword;
|
||||
}
|
||||
|
||||
resetState() {
|
||||
|
@ -97,38 +82,40 @@ class EditUserDialog extends React.Component {
|
|||
username: this.props.user.username,
|
||||
mail: this.props.user.mail,
|
||||
role: this.props.user.role,
|
||||
id: this.props.user.id
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
oldPassword: "",
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Edit user" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<Dialog show={this.props.show} title="Edit user" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={true}>
|
||||
<Form>
|
||||
<Form.Group as={Col} controlId="username">
|
||||
<Form.Label>Username</Form.Label>
|
||||
<Form.Control type="text" value={this.state.username} onChange={(e) => this.handleChange(e)} />
|
||||
<Form.Control type="text" placeholder={this.props.user.username} value={this.state.username} onChange={(e) => this.handleChange(e)} />
|
||||
<Form.Control.Feedback />
|
||||
</Form.Group>
|
||||
<Form.Group as={Col} controlId="mail">
|
||||
<Form.Label>E-mail</Form.Label>
|
||||
<Form.Control type="text" value={this.state.mail} onChange={(e) => this.handleChange(e)} />
|
||||
<Form.Control type="text" placeholder={this.props.user.mail} value={this.state.mail} onChange={(e) => this.handleChange(e)} />
|
||||
</Form.Group>
|
||||
<Form.Group as={Col} controlId="oldPassword">
|
||||
<Form.Label>Admin Password</Form.Label>
|
||||
<Form.Control type="password" placeholder="Enter admin password" value={this.state.oldPassword} onChange={(e) => this.handleChange(e)} />
|
||||
</Form.Group>
|
||||
<Form.Group as={Col} controlId="password">
|
||||
<Form.Label>Password</Form.Label>
|
||||
<Form.Label>New User Password</Form.Label>
|
||||
<Form.Control type="password" placeholder="Enter password" value={this.state.password} onChange={(e) => this.handleChange(e)} />
|
||||
</Form.Group>
|
||||
<Form.Group as={Col} controlId="confirmPassword">
|
||||
<Form.Label>Confirm New Password</Form.Label>
|
||||
<Form.Label>Confirm new Password</Form.Label>
|
||||
<Form.Control type="password" placeholder="Enter password" value={this.state.confirmPassword} onChange={(e) => this.handleChange(e)} />
|
||||
</Form.Group>
|
||||
<Form.Group as={Col} controlId="role">
|
||||
<Form.Label>Role</Form.Label>
|
||||
<Form.Control as="select" placeholder="Select role" value={this.state.role} onChange={(e) => this.handleChange(e)}>
|
||||
<Form.Control as="select" placeholder={this.props.user.role} value={this.state.role} onChange={(e) => this.handleChange(e)}>
|
||||
<option key='1' value='Admin'>Administrator</option>
|
||||
<option key='2' value='User'>User</option>
|
||||
<option key='3' value='Guest'>Guest</option>
|
||||
|
|
|
@ -66,11 +66,14 @@ class LoginStore extends ReduceStore {
|
|||
return Object.assign({}, state, { token: null, currentUser: null, loginMessage: null});
|
||||
|
||||
case 'users/logged-in':
|
||||
// save login in local storage
|
||||
localStorage.setItem('token', action.token);
|
||||
// save login data in local storage and loginStore
|
||||
let newState = state
|
||||
if (action.token != null){
|
||||
localStorage.setItem('token', action.token);
|
||||
newState = Object.assign({}, state, {token: action.token})
|
||||
}
|
||||
localStorage.setItem('currentUser', JSON.stringify(action.currentUser));
|
||||
|
||||
return Object.assign({}, state, { token: action.token, currentUser: action.currentUser});
|
||||
return Object.assign({}, newState, { currentUser: action.currentUser});
|
||||
|
||||
case 'users/login-error':
|
||||
if (action.error && !action.error.handled) {
|
||||
|
|
128
src/user/user.js
128
src/user/user.js
|
@ -15,100 +15,74 @@
|
|||
* 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 { Button, Form, Row, Col } from 'react-bootstrap';
|
||||
|
||||
import { Form, Row, Col } from 'react-bootstrap';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
import UsersStore from './users-store';
|
||||
|
||||
import Icon from '../common/icon';
|
||||
import EditOwnUserDialog from './edit-own-user'
|
||||
import NotificationsDataManager from "../common/data-managers/notifications-data-manager"
|
||||
import NotificationsFactory from "../common/data-managers/notifications-factory";
|
||||
import IconButton from "../common/icon-button";
|
||||
import LoginStore from './login-store'
|
||||
|
||||
class User extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
token: LoginStore.getState().token,
|
||||
editModal: false,
|
||||
}
|
||||
}
|
||||
|
||||
class User extends Component {
|
||||
static getStores() {
|
||||
return [ UsersStore ];
|
||||
return [ LoginStore ];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
prevState = prevState || {};
|
||||
|
||||
let currentUserID = JSON.parse(localStorage.getItem("currentUser")).id;
|
||||
let currentUser = UsersStore.getState().find(user => user.id === parseInt(currentUserID, 10));
|
||||
|
||||
return {
|
||||
currentUser,
|
||||
token: localStorage.getItem("token"),
|
||||
editModal: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let currentUserID = JSON.parse(localStorage.getItem("currentUser")).id;
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/start-load',
|
||||
data: parseInt(currentUserID, 10),
|
||||
token: this.state.token
|
||||
});
|
||||
currentUser: LoginStore.getState().currentUser
|
||||
}
|
||||
}
|
||||
|
||||
closeEditModal(data) {
|
||||
this.setState({ editModal: false });
|
||||
|
||||
let updatedData = {}
|
||||
let updatedUser = this.state.currentUser;
|
||||
let hasChanged = false;
|
||||
let pwChanged = false;
|
||||
|
||||
updatedData.id = this.state.currentUser.id;
|
||||
|
||||
if (data) {
|
||||
if (data.username !== this.state.currentUser.username) {
|
||||
hasChanged = true;
|
||||
updatedData.username = data.username;
|
||||
updatedUser.username = data.username
|
||||
}
|
||||
|
||||
if (data.mail !== this.state.currentUser.mail) {
|
||||
hasChanged = true;
|
||||
updatedData.mail = data.mail;
|
||||
updatedUser.mail = data.mail;
|
||||
}
|
||||
|
||||
if (data.password !== '' && data.oldPassword !== '' && data.password === data.confirmPassword ) {
|
||||
pwChanged = true;
|
||||
updatedData.password = data.password;
|
||||
updatedData.oldPassword = data.oldPassword;
|
||||
} else if (data.password !== '' && data.password !== data.confirmPassword) {
|
||||
NotificationsDataManager.addNotification(NotificationsFactory.UPDATE_ERROR('New password not correctly confirmed'));
|
||||
return
|
||||
}
|
||||
|
||||
if (hasChanged || pwChanged) {
|
||||
if (hasChanged){
|
||||
this.setState({ currentUser: updatedUser })
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/start-edit',
|
||||
data: updatedData,
|
||||
token: this.state.token
|
||||
});
|
||||
} else {
|
||||
NotificationsDataManager.addNotification(NotificationsFactory.UPDATE_WARNING('No update requested, no input data'));
|
||||
}
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/start-edit',
|
||||
data: data,
|
||||
token: this.state.token,
|
||||
currentUser: this.state.currentUser,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let user = this.state.currentUser;
|
||||
|
||||
const buttonStyle = {
|
||||
marginLeft: '10px',
|
||||
}
|
||||
|
||||
const iconStyle = {
|
||||
height: '30px',
|
||||
width: '30px'
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Account</h1>
|
||||
<h1>Account
|
||||
<span className='icon-button'>
|
||||
|
||||
<IconButton
|
||||
childKey={0}
|
||||
tooltip='Edit Account'
|
||||
onClick={() => this.setState({ editModal: true })}
|
||||
icon='edit'
|
||||
buttonStyle={buttonStyle}
|
||||
iconStyle={iconStyle}
|
||||
/>
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{user ?
|
||||
<>
|
||||
|
@ -116,30 +90,28 @@ class User extends Component {
|
|||
<Form.Group as={Row} controlId="username">
|
||||
<Form.Label column sm={2}>Username</Form.Label>
|
||||
<Col sm={10}>
|
||||
<Form.Control plaintext readOnly defaultValue={user.username} />
|
||||
<Form.Control plaintext readOnly value={user.username} />
|
||||
</Col>
|
||||
</Form.Group>
|
||||
<Form.Group as={Row} controlId="mail">
|
||||
<Form.Label column sm={2}>E-mail</Form.Label>
|
||||
<Col sm={10}>
|
||||
<Form.Control plaintext readOnly defaultValue={user.mail} type="email" />
|
||||
<Form.Control plaintext readOnly value={user.mail} type="email" />
|
||||
</Col>
|
||||
</Form.Group>
|
||||
<Form.Group as={Row} controlId="role">
|
||||
<Form.Label column sm={2}>Role</Form.Label>
|
||||
<Col sm={10}>
|
||||
<Form.Control plaintext readOnly defaultValue={user.role} />
|
||||
<Form.Control plaintext readOnly value={user.role} />
|
||||
</Col>
|
||||
</Form.Group>
|
||||
<Form.Group as={Row} controlId="formBasicEmail">
|
||||
<Form.Label column sm={2}>Created at</Form.Label>
|
||||
<Col sm={10}>
|
||||
<Form.Control plaintext readOnly defaultValue={user.createdAt} />
|
||||
<Form.Control plaintext readOnly value={user.createdAt} />
|
||||
</Col>
|
||||
</Form.Group>
|
||||
<Button variant="primary" onClick={() => this.setState({ editModal: true })}>
|
||||
<Icon icon='edit' /> Edit
|
||||
</Button>
|
||||
|
||||
</Form>
|
||||
|
||||
<EditOwnUserDialog
|
||||
|
|
|
@ -19,10 +19,13 @@ import ArrayStore from '../common/array-store';
|
|||
import UsersDataManager from './users-data-manager';
|
||||
import NotificationsDataManager from '../common/data-managers/notifications-data-manager';
|
||||
import NotificationsFactory from "../common/data-managers/notifications-factory";
|
||||
import AppDispatcher from "../common/app-dispatcher";
|
||||
|
||||
class UsersStore extends ArrayStore {
|
||||
constructor() {
|
||||
super('users', UsersDataManager);
|
||||
|
||||
this.currentUser = null;
|
||||
}
|
||||
|
||||
reduce(state, action) {
|
||||
|
@ -46,6 +49,33 @@ class UsersStore extends ArrayStore {
|
|||
}
|
||||
return super.reduce(state, action);
|
||||
|
||||
case this.type + '/start-edit':
|
||||
|
||||
// save current user on user edit
|
||||
this.currentUser = action.currentUser;
|
||||
return super.reduce(state, action)
|
||||
|
||||
case this.type + '/edited':
|
||||
|
||||
let currentUserID = this.currentUser.id;
|
||||
|
||||
// check if own user was updated
|
||||
for (let u of [action.data]){
|
||||
if (u.id === currentUserID){
|
||||
console.log("Detected update of current user, updating login store")
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/logged-in',
|
||||
token: null,
|
||||
currentUser: u,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return super.reduce(state, action)
|
||||
|
||||
|
||||
default:
|
||||
return super.reduce(state, action);
|
||||
}
|
||||
|
|
|
@ -17,11 +17,10 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
import UsersStore from './users-store';
|
||||
import LoginStore from './login-store';
|
||||
import ScenarioStore from '../scenario/scenario-store';
|
||||
|
||||
import Icon from '../common/icon';
|
||||
import IconButton from '../common/icon-button';
|
||||
import { Dropdown, DropdownButton } from 'react-bootstrap';
|
||||
|
@ -30,14 +29,11 @@ import TableColumn from '../common/table-column';
|
|||
import NewUserDialog from './new-user';
|
||||
import EditUserDialog from './edit-user';
|
||||
import UsersToScenarioDialog from './users-to-scenario';
|
||||
|
||||
import DeleteDialog from '../common/dialogs/delete-dialog';
|
||||
import NotificationsDataManager from "../common/data-managers/notifications-data-manager";
|
||||
import NotificationsFactory from "../common/data-managers/notifications-factory";
|
||||
|
||||
class Users extends Component {
|
||||
static getStores() {
|
||||
return [UsersStore, ScenarioStore];
|
||||
return [UsersStore, ScenarioStore, LoginStore];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
|
@ -106,15 +102,12 @@ class Users extends Component {
|
|||
this.setState({ editModal: false });
|
||||
|
||||
if (data) {
|
||||
if (data.password === data.confirmPassword) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/start-edit',
|
||||
data: data,
|
||||
token: this.state.token
|
||||
});
|
||||
} else {
|
||||
NotificationsDataManager.addNotification(NotificationsFactory.UPDATE_ERROR("New password not correctly confirmed"))
|
||||
}
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/start-edit',
|
||||
data: data,
|
||||
token: this.state.token,
|
||||
currentUser: this.state.currentUser,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
/**
|
||||
* 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 { Form } from 'react-bootstrap';
|
||||
import { SketchPicker } from 'react-color';
|
||||
import Dialog from '../../common/dialogs/dialog';
|
||||
|
||||
|
||||
class ColorPicker extends React.Component {
|
||||
valid = true;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
widget: {}
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
return {
|
||||
widget: props.widget
|
||||
};
|
||||
}
|
||||
|
||||
hexToRgb = (hex,opacity) => {
|
||||
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result ? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16),
|
||||
a: opacity
|
||||
} : null;
|
||||
}
|
||||
|
||||
handleChangeComplete = (color) => {
|
||||
let temp = this.state.widget;
|
||||
|
||||
if (this.props.controlId === 'strokeStyle'){
|
||||
temp.customProperties.zones[this.props.zoneIndex]['strokeStyle'] = color.hex;
|
||||
}
|
||||
else if (this.props.controlId === 'lineColor'){
|
||||
temp.customProperties.lineColors[this.props.lineIndex] = color.hex;
|
||||
}
|
||||
else {
|
||||
let parts = this.props.controlId.split('.');
|
||||
let isCustomProperty = true;
|
||||
|
||||
if (parts.length === 1){
|
||||
isCustomProperty = false;
|
||||
}
|
||||
|
||||
isCustomProperty ? temp[parts[0]][parts[1]] = color.hex : temp[this.props.controlId] = color.hex;
|
||||
isCustomProperty ? temp[parts[0]][parts[1] + "_opacity"] = color.rgb.a : temp[this.props.controlId +"_opacity"] = color.rgb.a;
|
||||
}
|
||||
|
||||
this.setState({ widget: temp });
|
||||
};
|
||||
|
||||
onClose = canceled => {
|
||||
if (canceled) {
|
||||
if (this.props.onClose != null) {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.valid && this.props.onClose != null) {
|
||||
this.props.onClose(this.state.widget);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
let hexColor;
|
||||
let opacity = 1;
|
||||
let parts = this.props.controlId.split('.');
|
||||
let isCustomProperty = true;
|
||||
if (parts.length === 1) {
|
||||
isCustomProperty = false;
|
||||
}
|
||||
|
||||
if (this.props.controlId === 'strokeStyle') {
|
||||
if (typeof this.state.widget.customProperties.zones[this.props.zoneIndex] !== 'undefined') {
|
||||
hexColor = this.state.widget.customProperties.zones[this.props.zoneIndex]['strokeStyle'];
|
||||
}
|
||||
}
|
||||
else if (this.props.controlId === 'lineColor') {
|
||||
if (typeof this.state.widget.customProperties.lineColors[this.props.lineIndex] !== 'undefined') {
|
||||
hexColor = this.state.widget.customProperties.lineColors[this.props.lineIndex];
|
||||
}
|
||||
}
|
||||
else{
|
||||
hexColor = isCustomProperty ? this.state.widget[parts[0]][parts[1]]: this.state.widget[this.props.controlId];
|
||||
opacity = isCustomProperty ? this.state.widget[parts[0]][parts[1] + "_opacity"]: this.state.widget[this.props.controlId + "_opacity"];
|
||||
}
|
||||
|
||||
let rgbColor = this.hexToRgb(hexColor, opacity);
|
||||
return <Dialog
|
||||
show={this.props.show}
|
||||
title='Color Picker'
|
||||
buttonTitle='Save'
|
||||
onClose={(c) => this.onClose(c)}
|
||||
valid={true}
|
||||
>
|
||||
<Form>
|
||||
<SketchPicker
|
||||
color={rgbColor}
|
||||
disableAlpha={this.props.disableOpacity}
|
||||
onChangeComplete={ this.handleChangeComplete }
|
||||
width={300}
|
||||
/>
|
||||
</Form>
|
||||
</Dialog>;
|
||||
}
|
||||
}
|
||||
|
||||
export default ColorPicker;
|
|
@ -21,21 +21,19 @@ import { Form } from 'react-bootstrap';
|
|||
class EditWidgetCheckboxControl extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
let parts = this.props.controlId.split('.');
|
||||
let isCustomProperty = true;
|
||||
if (parts.length ===1){
|
||||
isCustomProperty = false;
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
let parts = props.controlId.split('.');
|
||||
let isChecked;
|
||||
if (isCustomProperty){
|
||||
isChecked = this.props.widget[parts[0]][parts[1]]
|
||||
} else{
|
||||
isChecked = this.props.widget[this.props.controlId]
|
||||
|
||||
if (parts.length ===1){
|
||||
isChecked = props.widget[props.controlId]
|
||||
} else {
|
||||
isChecked = props.widget[parts[0]][parts[1]]
|
||||
}
|
||||
|
||||
this.state = {
|
||||
return {
|
||||
isChecked
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
import { Form, OverlayTrigger, Tooltip, Button, Col } from 'react-bootstrap';
|
||||
import ColorPicker from './color-picker'
|
||||
import ColorPicker from '../../common/color-picker'
|
||||
import Icon from "../../common/icon";
|
||||
|
||||
// schemeCategory20 no longer available in d3
|
||||
|
@ -28,55 +28,51 @@ class EditWidgetColorControl extends Component {
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
widget: {},
|
||||
color: null,
|
||||
opacity: null,
|
||||
showColorPicker: false,
|
||||
originalColor: null
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
let parts = props.controlId.split('.');
|
||||
let isCustomProperty = true;
|
||||
if (parts.length === 1){
|
||||
isCustomProperty = false;
|
||||
}
|
||||
|
||||
let color = (isCustomProperty ? props.widget[parts[0]][parts[1]] : props.widget[props.controlId]);
|
||||
let opacity = (isCustomProperty ? props.widget[parts[0]][parts[1] + "_opacity"] : props.widget[props.controlId + "_opacity"]);
|
||||
|
||||
return {
|
||||
widget: props.widget
|
||||
color: color,
|
||||
opacity: opacity,
|
||||
};
|
||||
}
|
||||
|
||||
openColorPicker = () =>{
|
||||
let parts = this.props.controlId.split('.');
|
||||
let isCustomProperty = true;
|
||||
if (parts.length === 1){
|
||||
isCustomProperty = false;
|
||||
}
|
||||
let color = (isCustomProperty ? this.props.widget[parts[0]][parts[1]] : this.props.widget[this.props.controlId]);
|
||||
|
||||
this.setState({showColorPicker: true, originalColor: color});
|
||||
this.setState({showColorPicker: true, originalColor: this.state.color});
|
||||
}
|
||||
|
||||
closeEditModal = (data) => {
|
||||
closeColorPickerEditModal = (data) => {
|
||||
this.setState({showColorPicker: false})
|
||||
if(typeof data === 'undefined'){
|
||||
let parts = this.props.controlId.split('.');
|
||||
let isCustomProperty = true;
|
||||
if (parts.length === 1) {
|
||||
isCustomProperty = false;
|
||||
}
|
||||
|
||||
let temp = this.state.widget;
|
||||
isCustomProperty ? temp[parts[0]][parts[1]] = this.state.originalColor : temp[this.props.controlId] = this.state.originalColor;
|
||||
this.setState({ widget: temp });
|
||||
if(typeof data === 'undefined'){
|
||||
|
||||
this.setState({ color: this.state.originalColor });
|
||||
} else {
|
||||
// color picker with result data {hexcolor, opacity}
|
||||
this.setState({color: data.hexcolor, opacity: data.opacity})
|
||||
this.props.handleChange({target: { id: this.props.controlId, value: data.hexcolor} })
|
||||
this.props.handleChange({target: { id: this.props.controlId + "_opacity", value: data.opacity} })
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let parts = this.props.controlId.split('.');
|
||||
let isCustomProperty = true;
|
||||
if (parts.length === 1){
|
||||
isCustomProperty = false;
|
||||
}
|
||||
let color = (isCustomProperty ? this.props.widget[parts[0]][parts[1]] : this.props.widget[this.props.controlId]);
|
||||
let opacity = (isCustomProperty ? this.props.widget[parts[0]][parts[1] + "_opacity"] : this.props.widget[this.props.controlId + "_opacity"]);
|
||||
let style = {
|
||||
backgroundColor: color,
|
||||
opacity: opacity,
|
||||
backgroundColor: this.state.color,
|
||||
opacity: this.state.opacity,
|
||||
width: '80px',
|
||||
height: '40px',
|
||||
}
|
||||
|
@ -85,8 +81,6 @@ class EditWidgetColorControl extends Component {
|
|||
if(this.props.disableOpacity){
|
||||
tooltipText = "Change border color";
|
||||
}
|
||||
|
||||
|
||||
return ( <Form.Row>
|
||||
<Form.Group as={Col}>
|
||||
<Form.Label>{this.props.label}</Form.Label>
|
||||
|
@ -100,7 +94,13 @@ class EditWidgetColorControl extends Component {
|
|||
</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}/>
|
||||
<ColorPicker
|
||||
show={this.state.showColorPicker}
|
||||
onClose={(data) => this.closeColorPickerEditModal(data)}
|
||||
hexcolor={this.state.color}
|
||||
opacity={this.state.opacity}
|
||||
disableOpacity={this.props.disableOpacity}
|
||||
/>
|
||||
</Form.Group>
|
||||
</Form.Row>
|
||||
);
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { Form, Table, Button, Tooltip, OverlayTrigger } from 'react-bootstrap';
|
||||
import ColorPicker from './color-picker'
|
||||
|
||||
import ColorPicker from '../../common/color-picker'
|
||||
import Icon from '../../common/icon';
|
||||
import { Collapse } from 'react-collapse';
|
||||
|
||||
|
@ -27,12 +26,7 @@ class EditWidgetColorZonesControl extends React.Component {
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
widget: {
|
||||
customProperties:{
|
||||
zones: []
|
||||
}
|
||||
},
|
||||
selectedZone: null,
|
||||
colorZones: [],
|
||||
selectedIndex: null,
|
||||
showColorPicker: false,
|
||||
originalColor: null,
|
||||
|
@ -43,89 +37,88 @@ class EditWidgetColorZonesControl extends React.Component {
|
|||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
return {
|
||||
widget: props.widget
|
||||
colorZones: props.widget.customProperties.zones
|
||||
};
|
||||
}
|
||||
|
||||
addZone = () => {
|
||||
// add row
|
||||
const widget = this.state.widget;
|
||||
widget.customProperties.zones.push({ strokeStyle: '#d3cbcb', min: 0, max: 100 });
|
||||
const zones = JSON.parse(JSON.stringify(this.state.colorZones));
|
||||
zones.push({ strokeStyle: '#d3cbcb', min: 0, max: 100 });
|
||||
|
||||
if(widget.customProperties.zones.length > 0){
|
||||
let length = widget.customProperties.zones.length
|
||||
if(zones.length > 0){
|
||||
let length = zones.length
|
||||
|
||||
for(let i= 0 ; i < length; i++){
|
||||
widget.customProperties.zones[i].min = i* 100/length;
|
||||
widget.customProperties.zones[i].max = (i+1)* 100/length;
|
||||
}
|
||||
for(let i= 0 ; i < length; i++){
|
||||
zones[i].min = i* 100/length;
|
||||
zones[i].max = (i+1)* 100/length;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ widget, selectedZone: null, selectedIndex: null });
|
||||
|
||||
this.sendEvent(widget);
|
||||
this.setState({ colorZones: zones, selectedIndex: null });
|
||||
this.props.handleChange({target: { id: this.props.controlId, value: zones}})
|
||||
}
|
||||
|
||||
removeZones = () => {
|
||||
|
||||
let temp = this.state.widget;
|
||||
|
||||
temp.customProperties.zones.splice(this.state.selectedIndex, 1);
|
||||
|
||||
if(temp.customProperties.zones.length > 0){
|
||||
let length = temp.customProperties.zones.length
|
||||
|
||||
let zones = JSON.parse(JSON.stringify(this.state.colorZones));
|
||||
zones.splice(this.state.selectedIndex, 1);
|
||||
if(zones.length > 0){
|
||||
let length = zones.length
|
||||
for(let i= 0 ; i < length; i++){
|
||||
temp.customProperties.zones[i].min = i* 100/length;
|
||||
temp.customProperties.zones[i].max = (i+1)* 100/length;
|
||||
zones[i].min = i* 100/length;
|
||||
zones[i].max = (i+1)* 100/length;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({widget: temp,selectedZone: null, selectedIndex: null});
|
||||
|
||||
}
|
||||
this.setState({colorZones: zones, selectedIndex: null});
|
||||
this.props.handleChange({target: { id: this.props.controlId, value: zones}})
|
||||
}
|
||||
|
||||
changeCell = (event, row, column) => {
|
||||
// change row
|
||||
const widget = this.state.widget;
|
||||
const zones = JSON.parse(JSON.stringify(this.state.colorZones))
|
||||
|
||||
if (column === 1) {
|
||||
widget.customProperties.zones[row].strokeStyle = event.target.value;
|
||||
zones[row].strokeStyle = event.target.value;
|
||||
} else if (column === 2) {
|
||||
widget.customProperties.zones[row].min = event.target.value;
|
||||
zones[row].min = event.target.value;
|
||||
} else if (column === 3) {
|
||||
widget.customProperties.zones[row].max = event.target.value;
|
||||
zones[row].max = event.target.value;
|
||||
}
|
||||
|
||||
this.setState({ widget });
|
||||
|
||||
this.sendEvent(widget);
|
||||
this.setState({ colorZones: zones });
|
||||
this.props.handleChange({target: { id: this.props.controlId, value: zones}})
|
||||
}
|
||||
|
||||
editColorZone = (index) => {
|
||||
if(this.state.selectedIndex !== index){
|
||||
this.setState({selectedZone: this.state.widget.customProperties.zones[index], selectedIndex: index , minValue: this.state.widget.customProperties.zones[index].min, maxValue: this.state.widget.customProperties.zones[index].max});
|
||||
this.setState({
|
||||
selectedIndex: index ,
|
||||
minValue: this.state.colorZones[index].min,
|
||||
maxValue: this.state.colorZones[index].max}
|
||||
);
|
||||
}
|
||||
else{
|
||||
this.setState({selectedZone: null, selectedIndex: null});
|
||||
this.setState({selectedIndex: null});
|
||||
}
|
||||
}
|
||||
|
||||
openColorPicker = () => {
|
||||
|
||||
let color = this.state.selectedZone.strokeStyle;
|
||||
|
||||
let color = this.state.colorZones[this.state.selectedIndex].strokeStyle;
|
||||
this.setState({showColorPicker: true, originalColor: color});
|
||||
}
|
||||
|
||||
closeEditModal = (data) => {
|
||||
closeColorPickerEditModal = (data) => {
|
||||
this.setState({showColorPicker: false})
|
||||
let zones = JSON.parse(JSON.stringify(this.state.colorZones))
|
||||
if(typeof data === 'undefined'){
|
||||
|
||||
let temp = this.state.selectedZone;
|
||||
temp.strokeStyle = this.state.originalColor;
|
||||
|
||||
this.setState({ selectedZone : temp });
|
||||
zones[this.state.selectedIndex].strokeStyle = this.state.originalColor
|
||||
this.setState({ colorZones : zones });
|
||||
} else {
|
||||
// color picker with result data {hexcolor, opacity}
|
||||
zones[this.state.selectedIndex].strokeStyle = data.hexcolor
|
||||
this.setState({ colorZones : zones });
|
||||
this.props.handleChange({target: { id: this.props.controlId, value: zones}})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,14 +127,15 @@ class EditWidgetColorZonesControl extends React.Component {
|
|||
if(e.target.value < 0) return;
|
||||
this.setState({minValue: e.target.value});
|
||||
|
||||
let temp = this.state.widget;
|
||||
temp.customProperties.zones[this.state.selectedIndex]['min'] = e.target.value;
|
||||
let zones = JSON.parse(JSON.stringify(this.state.colorZones));
|
||||
zones[this.state.selectedIndex]['min'] = e.target.value;
|
||||
|
||||
if(this.state.selectedIndex !== 0){
|
||||
temp.customProperties.zones[this.state.selectedIndex - 1]['max'] = e.target.value
|
||||
zones[this.state.selectedIndex - 1]['max'] = e.target.value
|
||||
}
|
||||
|
||||
this.setState({ widget: temp });
|
||||
this.setState({ colorZones: zones });
|
||||
this.props.handleChange({target: { id: this.props.controlId, value: zones}})
|
||||
}
|
||||
|
||||
handleMaxChange = (e) => {
|
||||
|
@ -149,32 +143,19 @@ class EditWidgetColorZonesControl extends React.Component {
|
|||
if(e.target.value > 100) return;
|
||||
this.setState({maxValue: e.target.value});
|
||||
|
||||
let temp = this.state.widget;
|
||||
temp.customProperties.zones[this.state.selectedIndex]['max'] = e.target.value;
|
||||
let zones = JSON.parse(JSON.stringify(this.state.colorZones));
|
||||
zones[this.state.selectedIndex]['max'] = e.target.value;
|
||||
|
||||
if(this.state.selectedIndex !== this.state.widget.customProperties.zones.length -1){
|
||||
temp.customProperties.zones[this.state.selectedIndex + 1]['min'] = e.target.value
|
||||
if(this.state.selectedIndex !== zones.length -1){
|
||||
zones[this.state.selectedIndex + 1]['min'] = e.target.value
|
||||
}
|
||||
|
||||
this.setState({ widget: temp });
|
||||
}
|
||||
|
||||
sendEvent(widget) {
|
||||
// create event
|
||||
const event = {
|
||||
target: {
|
||||
id: 'zones',
|
||||
value: widget.customProperties.zones
|
||||
}
|
||||
};
|
||||
|
||||
this.props.handleChange(event);
|
||||
this.setState({ colorZones: zones });
|
||||
this.props.handleChange({target: { id: this.props.controlId, value: zones}})
|
||||
}
|
||||
|
||||
|
||||
|
||||
render() {
|
||||
|
||||
const buttonStyle = {
|
||||
marginBottom: '10px',
|
||||
marginLeft: '120px',
|
||||
|
@ -187,9 +168,9 @@ class EditWidgetColorZonesControl extends React.Component {
|
|||
|
||||
let tempColor = 'FFFFFF';
|
||||
let collapse = false;
|
||||
if(this.state.selectedZone !== null){
|
||||
if(this.state.selectedIndex !== null){
|
||||
collapse = true;
|
||||
tempColor = this.state.selectedZone.strokeStyle;
|
||||
tempColor = this.state.colorZones[this.state.selectedIndex].strokeStyle;
|
||||
}
|
||||
|
||||
let pickerStyle = {
|
||||
|
@ -213,7 +194,7 @@ class EditWidgetColorZonesControl extends React.Component {
|
|||
</span>
|
||||
<div>
|
||||
{
|
||||
this.state.widget.customProperties.zones.map((zone, idx) => {
|
||||
this.state.colorZones.map((zone, idx) => {
|
||||
let color = zone.strokeStyle;
|
||||
let width = (zone.max - zone.min)*(260/100);
|
||||
let style = {
|
||||
|
@ -221,13 +202,24 @@ class EditWidgetColorZonesControl extends React.Component {
|
|||
width: width,
|
||||
height: '40px'
|
||||
}
|
||||
return (<Button
|
||||
style={style} key={idx} onClick={i => this.editColorZone(idx)} disabled={!this.props.widget.customProperties.colorZones}><Icon icon="pen" /></Button>
|
||||
return (
|
||||
<span>
|
||||
<OverlayTrigger key={idx} placement={'right'} overlay={<Tooltip id={`tooltip-${"color-edit"}`}>Edit zone</Tooltip>} >
|
||||
<Button
|
||||
style={style}
|
||||
key={idx}
|
||||
onClick={i => this.editColorZone(idx)}
|
||||
disabled={!this.props.widget.customProperties.colorZones}>
|
||||
<Icon icon="pen" />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
</span>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
<Collapse isOpened={collapse}>
|
||||
<Form.Label>Edit selected color zone:</Form.Label>
|
||||
<OverlayTrigger key={0} placement={'right'} overlay={<Tooltip id={`tooltip-${"color"}`}>Change color</Tooltip>} >
|
||||
<Button key={0} style={pickerStyle} onClick={this.openColorPicker.bind(this)} >
|
||||
<Icon icon="paint-brush"/>
|
||||
|
@ -258,10 +250,22 @@ class EditWidgetColorZonesControl extends React.Component {
|
|||
</tbody>
|
||||
</Table>
|
||||
<span className='icon-button'>
|
||||
<Button variant='light' onClick={this.removeZones} ><Icon style={iconStyle} classname='icon-color' icon="trash-alt" /></Button>
|
||||
<OverlayTrigger key={1} placement={'right'} overlay={<Tooltip id={`tooltip-${"color-delete"}`}>Remove zone</Tooltip>} >
|
||||
<Button
|
||||
variant='light'
|
||||
onClick={this.removeZones} >
|
||||
<Icon style={iconStyle} classname='icon-color' icon="trash-alt" />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
</span>
|
||||
</Collapse>
|
||||
<ColorPicker show={this.state.showColorPicker} onClose={(data) => this.closeEditModal(data)} widget={this.state.widget} zoneIndex={this.state.selectedIndex} controlId={'strokeStyle'} />
|
||||
<ColorPicker
|
||||
show={this.state.showColorPicker}
|
||||
onClose={(data) => this.closeColorPickerEditModal(data)}
|
||||
hexcolor={tempColor}
|
||||
opacity={1}
|
||||
disableOpacity={this.props.disableOpacity}
|
||||
/>
|
||||
</Form.Group>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,7 +74,8 @@ export default function CreateControls(widgetType = null, widget = null, session
|
|||
<EditWidgetSignalsControl key={1} widget={widget} controlId={'signalIDs'} signals={signals} handleChange={(e) => handleChange(e)} direction={'out'}/>,
|
||||
<EditWidgetPlotColorsControl key={2} widget={widget} controlId={'customProperties.lineColors'} signals={signals} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetTextControl key={3} widget={widget} controlId={'customProperties.ylabel'} label={'Y-Axis name'} placeholder={'Enter a name for the y-axis'} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetMinMaxControl key={4} widget={widget} controlId="customProperties.y" handleChange={e => handleChange(e)} />
|
||||
<EditWidgetCheckboxControl key={4} widget={widget} controlId={'customProperties.showUnit'} input text="Show unit" handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetMinMaxControl key={5} widget={widget} controlId="customProperties.y" handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'Table':
|
||||
|
@ -96,7 +97,7 @@ export default function CreateControls(widgetType = null, widget = null, session
|
|||
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetSignalControl key={1} widget={widget} controlId={'signalIDs'} signals={signals} handleChange={(e) => handleChange(e)} direction={'out'}/>,
|
||||
<EditWidgetCheckboxControl key={2} widget={widget} controlId="customProperties.colorZones" input text="Show color zones" handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetColorZonesControl key={3} widget={widget} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetColorZonesControl key={3} widget={widget} controlId="customProperties.zones" handleChange={e => handleChange(e)} disableOpacity={true}/>,
|
||||
<EditWidgetMinMaxControl key={4} widget={widget} controlId="customProperties.value" handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
|
|
|
@ -17,12 +17,9 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
import { OverlayTrigger, Tooltip , Button, Form } from 'react-bootstrap';
|
||||
import ColorPicker from './color-picker'
|
||||
import ColorPicker from '../../common/color-picker'
|
||||
import Icon from "../../common/icon";
|
||||
import { scaleOrdinal } from "d3-scale";
|
||||
import { schemeCategory10 } from "d3-scale-chromatic";
|
||||
|
||||
// schemeCategory20 no longer available in d3
|
||||
import {schemeCategory10} from "d3-scale-chromatic";
|
||||
|
||||
class EditWidgetPlotColorsControl extends Component {
|
||||
|
||||
|
@ -30,51 +27,78 @@ class EditWidgetPlotColorsControl extends Component {
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
widget: {},
|
||||
showColorPicker: false,
|
||||
originalColor: null,
|
||||
selectedIndex: null
|
||||
selectedIndex: null,
|
||||
lineColors: [],
|
||||
signalIDs: []
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state){
|
||||
|
||||
let widget = props.widget;
|
||||
if(widget.customProperties.lineColors === undefined || widget.customProperties.lineColors === null){
|
||||
// for backwards compatibility with old plots
|
||||
widget.customProperties.lineColors = []
|
||||
|
||||
const newLineColor = scaleOrdinal(schemeCategory10);
|
||||
for (let signalID of widget.signalIDs){
|
||||
widget.customProperties.lineColors.push(newLineColor(signalID))
|
||||
}
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
return {
|
||||
widget: widget
|
||||
lineColors: props.widget.customProperties.lineColors,
|
||||
signalIDs: props.widget.signalIDs,
|
||||
};
|
||||
}
|
||||
|
||||
//same here
|
||||
componentDidUpdate(prevProps: Readonly<P>, prevState: Readonly<S>, snapshot: SS) {
|
||||
|
||||
closeEditModal = (data) => {
|
||||
let lineColorsChanged = false;
|
||||
|
||||
if (JSON.stringify(this.state.signalIDs) !== JSON.stringify(prevState.signalIDs)){
|
||||
// if there was a change to the signal IDs
|
||||
let tempLineColors = JSON.parse(JSON.stringify(this.state.lineColors));
|
||||
let oldNoSignals = tempLineColors.length
|
||||
|
||||
if (this.state.signalIDs.length > prevState.signalIDs.length){
|
||||
// more signals than before
|
||||
let diff = this.state.signalIDs.length - prevState.signalIDs.length
|
||||
for (let i = 0; i<diff; i++){
|
||||
tempLineColors.push(schemeCategory10[oldNoSignals+i % 10])
|
||||
lineColorsChanged = true;
|
||||
}
|
||||
|
||||
} else if (this.state.signalIDs.length < prevState.signalIDs.length){
|
||||
// less signals than before
|
||||
let diff = prevState.signalIDs.length - this.state.signalIDs.length
|
||||
for (let i = 0; i<diff; i++){
|
||||
tempLineColors.pop()
|
||||
lineColorsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({lineColors: tempLineColors})
|
||||
if (lineColorsChanged){
|
||||
this.props.handleChange({target: { id: this.props.controlId, value: tempLineColors} })
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
closeColorPickerEditModal = (data) => {
|
||||
this.setState({showColorPicker: false})
|
||||
let tempLineColors = JSON.parse(JSON.stringify(this.state.lineColors));
|
||||
if(typeof data === 'undefined'){
|
||||
|
||||
let temp = this.state.widget;
|
||||
temp.customProperties.lineColors[this.state.selectedIndex] = this.state.originalColor;
|
||||
this.setState({ widget: temp });
|
||||
// Color picker canceled
|
||||
tempLineColors[this.state.selectedIndex] = this.state.originalColor;
|
||||
this.setState({lineColors: tempLineColors})
|
||||
} else {
|
||||
// color picker with result data {hexcolor, opacity}
|
||||
tempLineColors[this.state.selectedIndex] = data.hexcolor
|
||||
this.setState({lineColors: tempLineColors})
|
||||
this.props.handleChange({target: { id: this.props.controlId, value: tempLineColors} })
|
||||
}
|
||||
}
|
||||
|
||||
editLineColor = (index) => {
|
||||
if(this.state.selectedIndex !== index){
|
||||
let color = this.state.widget.customProperties.lineColors[index];
|
||||
this.setState({selectedIndex: index, showColorPicker: true, originalColor: color});
|
||||
}
|
||||
else{
|
||||
this.setState({selectedIndex: null});
|
||||
}
|
||||
let color = typeof this.state.lineColors[index] === "undefined" ? schemeCategory10[index % 10] : this.state.lineColors[index];
|
||||
this.setState({selectedIndex: index, showColorPicker: true, originalColor: color});
|
||||
}
|
||||
else{
|
||||
this.setState({selectedIndex: null});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -84,15 +108,15 @@ class EditWidgetPlotColorsControl extends Component {
|
|||
<Form.Label>Line Colors</Form.Label>
|
||||
<div>
|
||||
{
|
||||
this.state.widget.signalIDs.map((signalID, idx) => {
|
||||
let color = this.state.widget.customProperties.lineColors[signalID];
|
||||
let width = 260 / this.state.widget.signalIDs.length;
|
||||
this.props.widget.signalIDs.map((signalID, idx) => {
|
||||
|
||||
let color = typeof this.state.lineColors[idx] === "undefined" ? schemeCategory10[idx % 10] : this.state.lineColors[idx];
|
||||
let width = 260 / this.props.widget.signalIDs.length;
|
||||
let style = {
|
||||
backgroundColor: color,
|
||||
width: width,
|
||||
height: '40px'
|
||||
}
|
||||
|
||||
let signal = this.props.signals.find(signal => signal.id === signalID);
|
||||
|
||||
return <OverlayTrigger
|
||||
|
@ -103,16 +127,23 @@ class EditWidgetPlotColorsControl extends Component {
|
|||
<Button
|
||||
style={style}
|
||||
key={idx}
|
||||
onClick={i => this.editLineColor(signalID)}
|
||||
onClick={i => this.editLineColor(idx)}
|
||||
>
|
||||
<Icon icon="pen" />
|
||||
</Button>
|
||||
|
||||
</OverlayTrigger>;
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
<ColorPicker show={this.state.showColorPicker} onClose={(data) => this.closeEditModal(data)} widget={this.state.widget} lineIndex={this.state.selectedIndex} controlId={'lineColor'} disableOpacity={true}/>
|
||||
<ColorPicker
|
||||
show={this.state.showColorPicker}
|
||||
onClose={(data) => this.closeColorPickerEditModal(data)}
|
||||
hexcolor={this.state.lineColors[this.state.selectedIndex]}
|
||||
opacity={1}
|
||||
disableOpacity={true}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
)
|
||||
|
|
|
@ -90,6 +90,7 @@ class WidgetFactory {
|
|||
widget.customProperties.yMax = 10;
|
||||
widget.customProperties.yUseMinMax = false;
|
||||
widget.customProperties.lineColors = [];
|
||||
widget.customProperties.showUnit = false;
|
||||
break;
|
||||
case 'Table':
|
||||
widget.minWidth = 200;
|
||||
|
@ -150,7 +151,7 @@ class WidgetFactory {
|
|||
widget.customProperties.rangeMin = 0;
|
||||
widget.customProperties.rangeMax = 200;
|
||||
widget.customProperties.rangeUseMinMax = true;
|
||||
widget.customProperties.showUnit = true;
|
||||
widget.customProperties.showUnit = false;
|
||||
widget.customProperties.continous_update = false;
|
||||
widget.customProperties.default_value = '0';
|
||||
widget.customProperties.value = '';
|
||||
|
|
|
@ -24,26 +24,15 @@ function Legend(props){
|
|||
const signal = props.sig;
|
||||
const hasScalingFactor = (signal.scalingFactor !== 1);
|
||||
|
||||
const newLineColor = scaleOrdinal(schemeCategory10);
|
||||
let color = typeof props.lineColor === "undefined" ? schemeCategory10[props.index % 10] : props.lineColor;
|
||||
|
||||
let color = typeof props.lineColor === "undefined" ? newLineColor(signal.id) : props.lineColor;
|
||||
|
||||
if(hasScalingFactor){
|
||||
return (
|
||||
<li key={signal.id} className="signal-legend" style={{ color: color }}>
|
||||
<span className="signal-legend-name">{signal.name}</span>
|
||||
<span style={{ marginLeft: '0.3em' }} className="signal-unit">{signal.unit}</span>
|
||||
<span style={{ marginLeft: '0.3em' }} className="signal-scale">{signal.scalingFactor}</span>
|
||||
</li>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<li key={signal.id} className="signal-legend" style={{ color: color }}>
|
||||
<span className="signal-legend-name">{signal.name}</span>
|
||||
<span style={{ marginLeft: '0.3em' }} className="signal-unit">{signal.unit}</span>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<li key={signal.id} className="signal-legend" style={{ color: color }}>
|
||||
<span className="signal-legend-name">{signal.name}</span>
|
||||
{props.showUnit ? <span style={{marginLeft: '0.3em'}} className="signal-unit">{signal.unit}</span> : <></> }
|
||||
{hasScalingFactor ? <span style={{ marginLeft: '0.3em' }} className="signal-scale">{signal.scalingFactor}</span> : <></>}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
class PlotLegend extends React.Component {
|
||||
|
@ -52,11 +41,11 @@ class PlotLegend extends React.Component {
|
|||
return <div className="plot-legend">
|
||||
<ul>
|
||||
{ this.props.lineColors !== undefined && this.props.lineColors != null ? (
|
||||
this.props.signals.map( signal =>
|
||||
<Legend key={signal.id} sig={signal} lineColor={this.props.lineColors[signal.id]}/>
|
||||
this.props.signals.map( (signal, idx) =>
|
||||
<Legend key={signal.id} sig={signal} showUnit={this.props.showUnit} index={idx} lineColor={this.props.lineColors[idx]}/>
|
||||
)) : (
|
||||
this.props.signals.map( signal =>
|
||||
<Legend key={signal.id} sig={signal} lineColor={"undefined"}/>
|
||||
this.props.signals.map( (signal, idx) =>
|
||||
<Legend key={signal.id} sig={signal} showUnit={this.props.showUnit} index={idx} lineColor={"undefined"}/>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
|
|
|
@ -199,7 +199,6 @@ class Plot extends React.Component {
|
|||
|
||||
// generate paths from data
|
||||
const sparkLine = line().x(p => xScale(p.x)).y(p => yScale(p.y));
|
||||
const newLineColor = scaleOrdinal(schemeCategory10);
|
||||
|
||||
const lines = this.state.data.map((values, index) => {
|
||||
let signalID = this.props.signalIDs[index];
|
||||
|
@ -208,10 +207,10 @@ class Plot extends React.Component {
|
|||
this.props.lineColors = [] // for backwards compatibility
|
||||
}
|
||||
|
||||
if (typeof this.props.lineColors[signalID] === "undefined") {
|
||||
this.props.lineColors[signalID] = newLineColor(signalID);
|
||||
if (typeof this.props.lineColors[index] === "undefined") {
|
||||
this.props.lineColors[index] = schemeCategory10[index % 10];
|
||||
}
|
||||
return <path d={sparkLine(values)} key={index} style={{ fill: 'none', stroke: this.props.lineColors[signalID] }} />
|
||||
return <path d={sparkLine(values)} key={index} style={{ fill: 'none', stroke: this.props.lineColors[index] }} />
|
||||
});
|
||||
|
||||
this.setState({ lines, xAxis, yAxis });
|
||||
|
|
|
@ -94,18 +94,20 @@ class Widget extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
inputDataChanged(widget, data, controlID, controlValue) {
|
||||
inputDataChanged(widget, data, controlID, controlValue, isFinalChange) {
|
||||
// controlID is the path to the widget customProperty that is changed (for example 'value')
|
||||
|
||||
// modify the widget customProperty
|
||||
if (controlID !== '') {
|
||||
let updatedWidget = JSON.parse(JSON.stringify(widget));
|
||||
updatedWidget.customProperties[controlID] = controlValue;
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-edit',
|
||||
token: this.state.sessionToken,
|
||||
data: updatedWidget
|
||||
});
|
||||
if(isFinalChange) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-edit',
|
||||
token: this.state.sessionToken,
|
||||
data: updatedWidget
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// The following assumes that a widget modifies/ uses exactly one signal
|
||||
|
@ -185,21 +187,21 @@ class Widget extends React.Component {
|
|||
return <WidgetButton
|
||||
widget={widget}
|
||||
editing={this.props.editing}
|
||||
onInputChanged={(value, controlID, controlValue) => this.inputDataChanged(widget, value, controlID, controlValue)}
|
||||
onInputChanged={(value, controlID, controlValue, isFinalChange) => this.inputDataChanged(widget, value, controlID, controlValue, isFinalChange)}
|
||||
signals={this.state.signals}
|
||||
/>
|
||||
} else if (widget.type === 'NumberInput') {
|
||||
return <WidgetInput
|
||||
widget={widget}
|
||||
editing={this.props.editing}
|
||||
onInputChanged={(value, controlID, controlValue) => this.inputDataChanged(widget, value, controlID, controlValue)}
|
||||
onInputChanged={(value, controlID, controlValue, isFinalChange) => this.inputDataChanged(widget, value, controlID, controlValue, isFinalChange)}
|
||||
signals={this.state.signals}
|
||||
/>
|
||||
} else if (widget.type === 'Slider') {
|
||||
return <WidgetSlider
|
||||
widget={widget}
|
||||
editing={this.props.editing}
|
||||
onInputChanged={(value, controlID, controlValue) => this.inputDataChanged(widget, value, controlID, controlValue)}
|
||||
onInputChanged={(value, controlID, controlValue, isFinalChange) => this.inputDataChanged(widget, value, controlID, controlValue, isFinalChange)}
|
||||
signals={this.state.signals}
|
||||
/>
|
||||
} else if (widget.type === 'Gauge') {
|
||||
|
|
|
@ -54,14 +54,14 @@ class WidgetButton extends Component {
|
|||
|
||||
valueChanged(newValue, pressed) {
|
||||
if (this.props.onInputChanged) {
|
||||
this.props.onInputChanged(newValue, 'pressed', pressed);
|
||||
this.props.onInputChanged(newValue, 'pressed', pressed, true);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const buttonStyle = {
|
||||
backgroundColor: this.props.widget.customProperties.background_color,
|
||||
backgroundColor: this.props.widget.customProperties.background_color,
|
||||
borderColor: this.props.widget.customProperties.border_color,
|
||||
color: this.props.widget.customProperties.font_color,
|
||||
opacity: this.props.widget.customProperties.background_color_opacity
|
||||
|
|
|
@ -73,7 +73,7 @@ class WidgetInput extends Component {
|
|||
|
||||
valueChanged(newValue) {
|
||||
if (this.props.onInputChanged) {
|
||||
this.props.onInputChanged(Number(newValue), 'value', Number(newValue));
|
||||
this.props.onInputChanged(Number(newValue), 'value', Number(newValue), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -107,7 +107,10 @@ class WidgetPlot extends React.Component {
|
|||
signalIDs={this.props.widget.signalIDs}
|
||||
/>
|
||||
</div>
|
||||
<PlotLegend signals={this.state.signals} lineColors={this.props.widget.customProperties.lineColors} />
|
||||
<PlotLegend
|
||||
signals={this.state.signals}
|
||||
lineColors={this.props.widget.customProperties.lineColors}
|
||||
showUnit={this.props.widget.customProperties.showUnit} />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,14 +79,14 @@ class WidgetSlider extends Component {
|
|||
valueIsChanging(newValue) {
|
||||
this.props.widget.customProperties.value = newValue;
|
||||
if (this.props.widget.customProperties.continous_update)
|
||||
this.valueChanged(newValue);
|
||||
this.valueChanged(newValue, false);
|
||||
|
||||
this.setState({ value: newValue });
|
||||
}
|
||||
|
||||
valueChanged(newValue) {
|
||||
valueChanged(newValue, isFinalChange) {
|
||||
if (this.props.onInputChanged) {
|
||||
this.props.onInputChanged(newValue, 'value', newValue);
|
||||
this.props.onInputChanged(newValue, 'value', newValue, isFinalChange);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,7 @@ class WidgetSlider extends Component {
|
|||
let isVertical = this.props.widget.customProperties.orientation === WidgetSlider.OrientationTypes.VERTICAL.value;
|
||||
let fields = {
|
||||
name: this.props.widget.name,
|
||||
control: <Slider min={ this.props.widget.customProperties.rangeMin } max={ this.props.widget.customProperties.rangeMax } step={ this.props.widget.customProperties.step } value={ this.state.value } disabled={ this.props.editing } vertical={ isVertical } onChange={ (v) => this.valueIsChanging(v) } onAfterChange={ (v) => this.valueChanged(v) }/>,
|
||||
control: <Slider min={ this.props.widget.customProperties.rangeMin } max={ this.props.widget.customProperties.rangeMax } step={ this.props.widget.customProperties.step } value={ this.state.value } disabled={ this.props.editing } vertical={ isVertical } onChange={ (v) => this.valueIsChanging(v) } onAfterChange={ (v) => this.valueChanged(v, true) }/>,
|
||||
value: <span>{ format('.2f')(Number.parseFloat(this.state.value)) }</span>,
|
||||
unit: <span className="signal-unit">{ this.state.unit }</span>
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue