mirror of
https://git.rwth-aachen.de/acs/public/villas/web/
synced 2025-03-09 00:00:01 +01:00
Merge branch 'develop' into 'master'
Develop --> Master See merge request acs/public/villas/web!66
This commit is contained in:
commit
55ed652ed6
190 changed files with 22601 additions and 14008 deletions
|
@ -1,7 +1,7 @@
|
|||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: normal
|
||||
DOCKER_TAG: ${CI_COMMIT_REF_NAME}
|
||||
DOCKER_IMAGE_DEV: villas/web-dev:${CI_COMMIT_REF_NAME}
|
||||
DOCKER_TAG: ${CI_COMMIT_SHORT_SHA}
|
||||
DOCKER_IMAGE: ${CI_REGISTRY_IMAGE}
|
||||
|
||||
cache:
|
||||
untracked: true
|
||||
|
@ -13,54 +13,38 @@ cache:
|
|||
- .yarn
|
||||
|
||||
stages:
|
||||
- prepare
|
||||
- build
|
||||
- test
|
||||
# - deploy
|
||||
|
||||
prepare:
|
||||
stage: prepare
|
||||
script:
|
||||
- docker build -t ${DOCKER_IMAGE_DEV} -f packaging/docker/Dockerfile.dev .
|
||||
tags:
|
||||
- linux
|
||||
- shell
|
||||
- deploy
|
||||
|
||||
build_job:
|
||||
build:
|
||||
stage: build
|
||||
image: node:12.2
|
||||
before_script:
|
||||
- mkdir -p build
|
||||
script:
|
||||
- npm install
|
||||
- npm run build
|
||||
image: ${DOCKER_IMAGE_DEV}
|
||||
artifacts:
|
||||
paths:
|
||||
- build/
|
||||
expire_in: 1 week
|
||||
tags:
|
||||
- docker
|
||||
|
||||
test_job:
|
||||
stage: test
|
||||
deploy:
|
||||
stage: deploy
|
||||
image:
|
||||
name: gcr.io/kaniko-project/executor:debug
|
||||
entrypoint: [ "" ]
|
||||
script:
|
||||
- npm test
|
||||
image: ${DOCKER_IMAGE_DEV}
|
||||
dependencies:
|
||||
- build_job
|
||||
tags:
|
||||
- docker
|
||||
|
||||
#deploy:docker:
|
||||
# stage: deploy
|
||||
# script:
|
||||
# - docker build -t ${DOCKER_IMAGE} -f packaging/docker/Dockerfile .
|
||||
# - docker tag villas/web:${DOCKER_TAG} villas/web:latest
|
||||
# - docker push villas/web:${DOCKER_TAG}
|
||||
# - docker push villas/web:latest
|
||||
# tags:
|
||||
# - shell
|
||||
# - linux
|
||||
# only:
|
||||
# refs:
|
||||
# - master
|
||||
- mkdir -p /kaniko/.docker
|
||||
- echo "{\"auths\":{\"${CI_REGISTRY}\":{\"username\":\"${CI_REGISTRY_USER}\",\"password\":\"${CI_REGISTRY_PASSWORD}\"}}}" > /kaniko/.docker/config.json
|
||||
- /kaniko/executor
|
||||
--context ${CI_PROJECT_DIR}
|
||||
--dockerfile ${CI_PROJECT_DIR}/Dockerfile
|
||||
--destination ${DOCKER_IMAGE}:${DOCKER_TAG}
|
||||
--snapshotMode=redo
|
||||
--cache=true
|
||||
--cache-ttl=12h
|
||||
only:
|
||||
refs:
|
||||
- master
|
||||
- develop
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
packaging/docker/Dockerfile
|
35
Dockerfile
Normal file
35
Dockerfile
Normal file
|
@ -0,0 +1,35 @@
|
|||
#
|
||||
# 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/>.
|
||||
# ******************************************************************************
|
||||
|
||||
FROM node:12.2 AS builder
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# use changes to package.json to force Docker not to use the cache
|
||||
# when we change our application's nodejs dependencies:
|
||||
ADD package.json /usr/src/app
|
||||
RUN npm install
|
||||
|
||||
# Install app dependencies
|
||||
COPY . /usr/src/app
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx
|
||||
|
||||
COPY --from=builder /usr/src/app/build /usr/share/nginx/html
|
12
REACT.md
12
REACT.md
|
@ -538,7 +538,7 @@ Inside `index.html`, you can use it like this:
|
|||
|
||||
Only files inside the `public` folder will be accessible by `%PUBLIC_URL%` prefix. If you need to use a file from `src` or `node_modules`, you’ll have to copy it there to explicitly specify your intention to make this file a part of the build.
|
||||
|
||||
When you run `npm run build`, Create React App will substitute `%PUBLIC_URL%` with a correct absolute path so your project works even if you use client-side routing or host it at a non-root URL.
|
||||
When you run `npm run build`, Create React App will substitute `%PUBLIC_URL%` with a correct absolute path so your project works even if you use client-side routing or websocketurl it at a non-root URL.
|
||||
|
||||
In JavaScript code, you can use `process.env.PUBLIC_URL` for similar purposes:
|
||||
|
||||
|
@ -789,7 +789,7 @@ You can find the companion GitHub repository [here](https://github.com/fullstack
|
|||
|
||||
>Note: this feature is available with `react-scripts@0.2.3` and higher.
|
||||
|
||||
People often serve the front-end React app from the same host and port as their backend implementation.<br>
|
||||
People often serve the front-end React app from the same websocketurl and port as their backend implementation.<br>
|
||||
For example, a production setup might look like this after the app is deployed:
|
||||
|
||||
```
|
||||
|
@ -798,7 +798,7 @@ For example, a production setup might look like this after the app is deployed:
|
|||
/api/todos - server handles any /api/* requests using the backend implementation
|
||||
```
|
||||
|
||||
Such setup is **not** required. However, if you **do** have a setup like this, it is convenient to write requests like `fetch('/api/todos')` without worrying about redirecting them to another host or port during development.
|
||||
Such setup is **not** required. However, if you **do** have a setup like this, it is convenient to write requests like `fetch('/api/todos')` without worrying about redirecting them to another websocketurl or port during development.
|
||||
|
||||
To tell the development server to proxy any unknown requests to your API server in development, add a `proxy` field to your `package.json`, for example:
|
||||
|
||||
|
@ -820,7 +820,7 @@ The `proxy` option supports HTTP, HTTPS and WebSocket connections.<br>
|
|||
If the `proxy` option is **not** flexible enough for you, alternatively you can:
|
||||
|
||||
* Enable CORS on your server ([here’s how to do it for Express](http://enable-cors.org/server_expressjs.html)).
|
||||
* Use [environment variables](#adding-custom-environment-variables) to inject the right server host and port into your app.
|
||||
* Use [environment variables](#adding-custom-environment-variables) to inject the right server websocketurl and port into your app.
|
||||
|
||||
## Using HTTPS in Development
|
||||
|
||||
|
@ -1512,10 +1512,10 @@ You can adjust various development and production settings by setting environmen
|
|||
Variable | Development | Production | Usage
|
||||
:--- | :---: | :---: | :---
|
||||
BROWSER | :white_check_mark: | :x: | By default, Create React App will open the default system browser, favoring Chrome on macOS. Specify a [browser](https://github.com/sindresorhus/opn#app) to override this behavior, or set it to `none` to disable it completely.
|
||||
HOST | :white_check_mark: | :x: | By default, the development web server binds to `localhost`. You may use this variable to specify a different host.
|
||||
HOST | :white_check_mark: | :x: | By default, the development web server binds to `localhost`. You may use this variable to specify a different websocketurl.
|
||||
PORT | :white_check_mark: | :x: | By default, the development web server will attempt to listen on port 3000 or prompt you to attempt the next available port. You may use this variable to specify a different port.
|
||||
HTTPS | :white_check_mark: | :x: | When set to `true`, Create React App will run the development server in `https` mode.
|
||||
PUBLIC_URL | :x: | :white_check_mark: | Create React App assumes your application is hosted at the serving web server's root or a subpath as specified in [`package.json` (`homepage`)](#building-for-relative-paths). Normally, Create React App ignores the hostname. You may use this variable to force assets to be referenced verbatim to the url you provide (hostname included). This may be particularly useful when using a CDN to host your application.
|
||||
PUBLIC_URL | :x: | :white_check_mark: | Create React App assumes your application is hosted at the serving web server's root or a subpath as specified in [`package.json` (`homepage`)](#building-for-relative-paths). Normally, Create React App ignores the hostname. You may use this variable to force assets to be referenced verbatim to the url you provide (hostname included). This may be particularly useful when using a CDN to websocketurl your application.
|
||||
CI | :large_orange_diamond: | :white_check_mark: | When set to `true`, Create React App treats warnings as failures in the build. It also makes the test runner non-watching. Most CIs set this flag by default.
|
||||
|
||||
## Troubleshooting
|
||||
|
|
40
README.md
40
README.md
|
@ -1,39 +1,42 @@
|
|||
# <img src="doc/pictures/villas_web.png" width=40 /> VILLASweb
|
||||
|
||||
## Description
|
||||
|
||||
This is VILLASweb, the website displaying and processing simulation data in the web browser. The term __frontend__ refers to this project, the actual website.
|
||||
|
||||
The frontend connects to __two__ backends: _VILLASweb-backend_ and _VILLASnode_.
|
||||
|
||||
VILLASnode provides actual simulation data via websockets. VILLASweb-backend provides any other data like user acounts, simulation configuration etc.
|
||||
|
||||
This is VILLASweb, the website to configure real-time co-simulations and display simulation real-time data in the web browser.
|
||||
The term **frontend** refers to this project, the actual website.
|
||||
The frontend connects to **two** backends: [VILLASweb-backend-go](https://git.rwth-aachen.de/acs/public/villas/web-backend-go) and [VILLASnode](https://git.rwth-aachen.de/acs/public/villas/node).
|
||||
VILLASnode provides actual simulation data via websockets. VILLASweb-backend-go provides any other data such as user accounts, infrastructure components and configurations, dashboards etc.
|
||||
For more information on the backends see their repositories.
|
||||
|
||||
## Frameworks
|
||||
|
||||
The frontend is build upon [ReactJS](https://facebook.github.io/react/) and [Flux](https://facebook.github.io/flux/).
|
||||
|
||||
React is responsible for rendering the UI and Flux for handling the data and communication with the backends. For more information also have a look at REACT.md
|
||||
Additional libraries are used, for a complete list see the file `package.json`.
|
||||
|
||||
Additional libraries are used, for a complete list see package.json.
|
||||
## Data model
|
||||

|
||||
|
||||
## Quick start
|
||||
|
||||
We recommend Docker to get started quickly:
|
||||
|
||||
```bash
|
||||
$ git clone --recursive git@git.rwth-aachen.de:VILLASframework/VILLASweb.git
|
||||
$ cd VILLASweb
|
||||
$ git clone --recursive https://git.rwth-aachen.de/acs/public/villas/web.git
|
||||
$ cd web
|
||||
$ npm install
|
||||
$ npm start
|
||||
```
|
||||
We recommend to start the VILLASweb-backend-go before the frontend.
|
||||
If you want to use test data (including some test users), you can start the backend with the parameter `-mode=test`.
|
||||
Please check the repository of the VILLASweb-backend-go to find information on the test user login names and passwords.
|
||||
The testing mode is NOT intended for production deployments.
|
||||
|
||||
The default user and password are configured in the `config.js` file of the _backend_. By default they are: __admin__ / __admin__.
|
||||
## Documentation
|
||||
|
||||
More details on the setup and usage of VILLASweb is available here:
|
||||
- [Requirements](doc/Requirements.md)
|
||||
- [Structure and datamodel](doc/Structure.md)
|
||||
- [Development setup](doc/development.md)
|
||||
- [Production setup](doc/Production.md)
|
||||
|
||||
## Copyright
|
||||
|
||||
2017, Institute for Automation of Complex Power Systems, EONERC
|
||||
2020, Institute for Automation of Complex Power Systems, EONERC
|
||||
|
||||
## License
|
||||
|
||||
|
@ -66,6 +69,7 @@ For other licensing options please consult [Prof. Antonello Monti](mailto:amonti
|
|||
[](http://www.acs.eonerc.rwth-aachen.de)
|
||||
|
||||
- Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
- Sonja Happ <sonja.happ@eonerc.rwth-aachen.de>
|
||||
|
||||
[Institute for Automation of Complex Power Systems (ACS)](http://www.acs.eonerc.rwth-aachen.de)
|
||||
[EON Energy Research Center (EONERC)](http://www.eonerc.rwth-aachen.de)
|
||||
|
|
82
doc/Production.md
Normal file
82
doc/Production.md
Normal file
|
@ -0,0 +1,82 @@
|
|||
# Production Setup {#web-production}
|
||||
|
||||
## Setting up VILLASweb for production
|
||||
|
||||
For development setup instructions see @ref web-development.
|
||||
The production setup is based on docker.
|
||||
Clone the [frontend](https://git.rwth-aachen.de/acs/public/villas/web) and [backend](https://git.rwth-aachen.de/acs/public/villas/web-backend-go) repositories on your computer and build the Docker images for both:
|
||||
|
||||
### Frontend
|
||||
- `cd VILLASweb`
|
||||
- `docker build -t villasweb-frontend .`
|
||||
|
||||
### Backend
|
||||
- `cd ..\VILLASweb-backend-go`
|
||||
- `docker build -t villasweb-backend .`
|
||||
|
||||
### WIP Docker compose and/or Kubernetes
|
||||
Run the production docker-compose file:
|
||||
- `docker-compose -f docker-compose-production.yml up -d`
|
||||
|
||||
|
||||
## Configure VILLASnode to get data into VILLASweb
|
||||
|
||||
### Install VILLASnode
|
||||
|
||||
See: @ref node-installation
|
||||
|
||||
### Create a VILLASnode demo data source
|
||||
|
||||
1. Create a new empty configuration file with the following contents and save it as `webdemo.conf`:
|
||||
|
||||
> WIP this example configuration requires revision!
|
||||
```
|
||||
nodes = {
|
||||
sine = {
|
||||
type = "signal"
|
||||
|
||||
signal = "mixed"
|
||||
values = 5
|
||||
rate = 25
|
||||
frequency = 5
|
||||
}
|
||||
|
||||
web = {
|
||||
type = "websocket"
|
||||
|
||||
destinations = [
|
||||
"TODO"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
paths = (
|
||||
{
|
||||
in = "sine"
|
||||
out = "web"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The node `sine` is a software signal generator for 5 signals.
|
||||
The node `web` is the websocket interface to stream the data generated by the `sine` node to the browser.
|
||||
|
||||
> Note: If you do not want to use your local system as the destination for the websocket node,
|
||||
>change the option `destinations` of the `web` node to the destination of your production environment, for example `https://my.production.environment/ws/webdemo`.
|
||||
|
||||
### Start the VILLASnode gateway
|
||||
|
||||
Run the following command on your system:
|
||||
|
||||
```bash
|
||||
villas node webdemo.conf
|
||||
```
|
||||
> Note: Change the path to the configuration file accordingly. The `villas` command will only work if VILLASnode is installed on your system.
|
||||
|
||||
### Visualize real-time data in VILLASweb Dashboards
|
||||
1. Use the VILLASweb frontend to create a new infrastructure component for the VILLASnode gateway from above (Admin user required).
|
||||
2. Set the `websocketurl` parameter of the component to the target you used as the `web.destinations` parameter in the configuration from above.
|
||||
3. Create a new scenario in VILLASweb and within that scenario create a new component configuration that uses the infrastructure component you created under 2.
|
||||
4. WIP: Use the signal auto-configure function to retrieve the signal configuration of the VILLASnode automatically.
|
||||
5. Create a new dashboard with widgets of your choice and link these widgets to the signals received from the infrastructure component.
|
||||
6. Enjoy what you see.
|
12
doc/Requirements.md
Normal file
12
doc/Requirements.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Requirements {#web-requirements}
|
||||
|
||||
## Services and tools required for development
|
||||
- [NodeJS with npm](https://nodejs.org/en/): Runs VILLASweb frontend
|
||||
- [Go](https://golang.org/): Runs VILLASweb backend
|
||||
- [PostgreSQL database](https://www.postgresql.org/) (min version 11): Backend database
|
||||
- [swag](https://github.com/swaggo/swag): For automated API documentation creation
|
||||
- [Docker](https://www.docker.com/): Container management system
|
||||
|
||||
## Additional requirements for productive use
|
||||
- [NGinX](https://www.nginx.com/): Webserver and reverse proxy for backends
|
||||
|
79
doc/Structure.md
Normal file
79
doc/Structure.md
Normal file
|
@ -0,0 +1,79 @@
|
|||
# VILLASweb data structure {#web-datastructure}
|
||||
|
||||
This document describes how data (scenarios, infrastructure components, users etc., not only live data) is structured in VILLASweb.
|
||||
|
||||
## Data model
|
||||
|
||||

|
||||
|
||||
VILLASweb features the following data classes:
|
||||
|
||||
- Users
|
||||
- Infrastructure Components
|
||||
- Scenarios
|
||||
* Component Configurations and Signals
|
||||
* Dashboards and Widgets
|
||||
* Files
|
||||
|
||||
### Users
|
||||
- You need a username and a password to authenticate in VILLASweb
|
||||
- There exist three categories of users: Guest, User, and Admin
|
||||
- Guests have only read access and cannot modify anything
|
||||
- Users are normal users, they have access to their scenarios, can see available infrastructure components, and modify their accounts (except for their role)
|
||||
- Admin users have full access to everything, they are the only users that can create new users or change the role of existing users. Only admin users can add or modify infrastructure components.
|
||||
|
||||
### Infrastructure Components
|
||||
- Components of research infrastructure
|
||||
- Category: for example simulator, gateway, amplifier, database, etc.
|
||||
- Type: for example RTDS, OpalRT, VILLASnode, Cassandra
|
||||
- Can only be added/ modified by admin users
|
||||
|
||||
### Scenarios
|
||||
- A collection of component configurations, dashboards, and files for a specific experiment
|
||||
- Users can have access to multiple scenarios
|
||||
- Users can be added to and removed from scenarios
|
||||
|
||||
### Component Configurations and Signals
|
||||
- Configure an infrastructure component for the use in a specific scenario
|
||||
- Input signals: Signals that can be modified in VILLASweb
|
||||
- Output signals: Signals that can be visualized on dashboards of VILLASweb
|
||||
- Parameters: Additional configuration parameters of the infrastructure component
|
||||
- Signals are the actual live data that is displayed or modified through VILLASweb dashboards
|
||||
|
||||
### Dashboards and Widgets
|
||||
- Visualize ongoing experiments in real-time
|
||||
- Interact with ongoing experiments in real-time
|
||||
- Use widgets to design the dashboard according to the needs
|
||||
|
||||
### Files
|
||||
- Files can be added to scenarios optionally
|
||||
- Can be images, model files, CIM xml files
|
||||
- Can be used in widgets or component configurations
|
||||
|
||||
## Setup strategy
|
||||
|
||||
The easiest way to start from scratch is the following (assuming the infrastructure components are already configured by an admin user, see below):
|
||||
|
||||
1. Create a new scenario.
|
||||
2. Create and configure a new component configuration and link it with an available infrastructure component.
|
||||
3. Configure the input and output signals of the component configuration according to the signals provided by the selected infrastructure component. The number of signals and their order (index starting at 1) must match.
|
||||
4. Create a new dashboard and add widgets as desired. Configure the widgets by right-clicking to open the edit menu
|
||||
5. If needed, files can be added to the scenario and used by component configurations or widgets (models, images, CIM-files, etc.)
|
||||
6. For collaboration with other users, users can be added to a scenario
|
||||
|
||||
### Setup of infrastructure components
|
||||
|
||||
In the "Infrastructure Components" menu point admin users can create and edit components to be used in experiments. Normal uses can view the available components, but not edit them.
|
||||
The components are global at any time and are shared among all users of VILLASweb.
|
||||
|
||||
To create a new infrastructure component, you need to provide:
|
||||
- Name
|
||||
- Category (see above for examples)
|
||||
- Type (see above for examples)
|
||||
- Location
|
||||
- Host (network address of the component)
|
||||
|
||||
At the moment, you need to know the input and output signals of the infrastructure component a priori to be able to create compatible component configurations by hand.
|
||||
An auto-detection mechanism for signals is planned for future releases.
|
||||
|
||||
> Hint: At least one infrastructure component is required to receive data in VILLASweb.
|
70
doc/development.md
Normal file
70
doc/development.md
Normal file
|
@ -0,0 +1,70 @@
|
|||
# Development {#web-development}
|
||||
|
||||
- @subpage web-datastructure
|
||||
|
||||
In order to get started with VILLASweb, you might also want to check our our [demo project](https://git.rwth-aachen.de/acs/public/villas/Demo) which is simple to setup using Docker Compose.
|
||||
|
||||
## Frontend
|
||||
|
||||
### Description
|
||||
|
||||
The website itself based on the [React JavaScript framework](https://reactjs.org/) and the [Flux library](https://facebook.github.io/flux/).
|
||||
|
||||
### Required
|
||||
|
||||
- [NodeJS with npm](https://nodejs.org/en/)
|
||||
|
||||
### Setup
|
||||
|
||||
- `git clone git@git.rwth-aachen.de/acs/public/villas/web.git` to copy the project on your computer
|
||||
- `cd VILLASweb`
|
||||
- `npm install`
|
||||
|
||||
### Running
|
||||
|
||||
- `npm start`
|
||||
|
||||
This runs the development server for the website on your local computer at port 3000.
|
||||
The backend must be running to make the website work.
|
||||
Type `http://localhost:3000/` in the address field of your browser to open the website.
|
||||
|
||||
## Backend
|
||||
|
||||
### Description
|
||||
|
||||
The backend of VILLASweb uses the programming language Go and a PostgreSQL database.
|
||||
|
||||
### Required
|
||||
|
||||
- [Go](https://golang.org/) (min version 1.11)
|
||||
- [PostgreSQL database](https://www.postgresql.org/) (min version 11)
|
||||
- [swag](https://github.com/swaggo/swag)
|
||||
|
||||
### Setup and Running
|
||||
|
||||
- `git clone git@git.rwth-aachen.de/acs/public/villas/web-backend-go.git` to copy the project on your computer
|
||||
- `cd VILLASweb-backend-go`
|
||||
- `go mod tidy`
|
||||
- `go run start.go [params]`
|
||||
|
||||
To obtain a list of available parameters use `go run start.go --help`.
|
||||
To run the tests use `go test $(go list ./... ) -p 1` in the top-level folder of the repo.
|
||||
|
||||
Running the backend will only work if the PostgreSQL database is setup properly. Otherwise, you will get error messages.
|
||||
|
||||
### Auto-generate the API documentation
|
||||
|
||||
The documentation of the VILLASweb API in the OpenAPI format can be auto-generated from the source code documentation using the tool swag.
|
||||
To do this run the following in the top-level folder of the repo:
|
||||
|
||||
- `go mod tidy`
|
||||
- `go install github.com/swaggo/swag/cmd/swag`
|
||||
- `swag init -p pascalcase -g "start.go" -o "./doc/api/"`
|
||||
|
||||
The `.yaml` and `.json` files in OpenAPI swagger format are created in the output folder `doc/api`.
|
||||
|
||||
### PostgreSQL database setup
|
||||
|
||||
Please check the [Readme file in the backend repository](https://git.rwth-aachen.de/acs/public/villas/web-backend-go) for some useful hints on the local setup of the PostreSQL database.
|
||||
|
||||
|
17981
package-lock.json
generated
17981
package-lock.json
generated
File diff suppressed because it is too large
Load diff
90
package.json
90
package.json
|
@ -3,47 +3,65 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.8",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.5.0",
|
||||
"@fortawesome/react-fontawesome": "^0.1.3",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.32",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.1",
|
||||
"@fortawesome/react-fontawesome": "^0.1.12",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"bootstrap": "^3.3.7",
|
||||
"bootstrap": "^4.5.3",
|
||||
"bufferutil": "^4.0.1",
|
||||
"canvas": "^2.6.1",
|
||||
"classnames": "^2.2.6",
|
||||
"d3-array": "^1.2.4",
|
||||
"d3-axis": "^1.0.12",
|
||||
"d3-scale": "^1.0.6",
|
||||
"d3-selection": "^1.3.2",
|
||||
"d3-shape": "^1.2.2",
|
||||
"d3-time-format": "^2.1.3",
|
||||
"es6-promise": "^4.2.5",
|
||||
"file-saver": "^1.3.8",
|
||||
"flux": "^3.1.2",
|
||||
"gaugeJS": "^1.3.2",
|
||||
"handlebars": "^4.1.1",
|
||||
"immutable": "^3.8.1",
|
||||
"jszip": "^3.2.0",
|
||||
"d3-array": "^2.8.0",
|
||||
"d3-axis": "^2.0.0",
|
||||
"d3-scale": "^3.2.3",
|
||||
"d3-scale-chromatic": "^2.0.0",
|
||||
"d3-selection": "^2.0.0",
|
||||
"d3-shape": "^2.0.0",
|
||||
"d3-time-format": "^3.0.0",
|
||||
"es6-promise": "^4.2.8",
|
||||
"fibers": "^5.0.0",
|
||||
"file-saver": "^2.0.2",
|
||||
"flux": "^3.1.3",
|
||||
"gaugeJS": "^1.3.7",
|
||||
"handlebars": "^4.7.6",
|
||||
"jquery": "^3.5.1",
|
||||
"jszip": "^3.5.0",
|
||||
"libcimsvg": "git+https://git.rwth-aachen.de/acs/public/cim/pintura-npm-package.git",
|
||||
"lodash": "^4.17.11",
|
||||
"prop-types": "^15.6.2",
|
||||
"rc-slider": "^8.6.3",
|
||||
"react": "^16.6.3",
|
||||
"react-bootstrap": "^0.31.1",
|
||||
"react-contexify": "^3.0.3",
|
||||
"lodash": "^4.17.20",
|
||||
"moment": "^2.29.1",
|
||||
"multiselect-react-dropdown": "^1.6.1",
|
||||
"node-sass": "^4.14.1",
|
||||
"popper.js": "^1.16.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"rc-slider": "^9.6.0",
|
||||
"react": "^16.14.0",
|
||||
"react-bootstrap": "^1.4.0",
|
||||
"react-bootstrap-time-picker": "^2.0.1",
|
||||
"react-collapse": "^5.0.1",
|
||||
"react-color": "^2.19.3",
|
||||
"react-contexify": "^4.1.1",
|
||||
"react-d3": "^0.4.0",
|
||||
"react-dnd": "^2.6.0",
|
||||
"react-dnd-html5-backend": "^2.6.0",
|
||||
"react-dom": "^16.6.3",
|
||||
"react-fullscreenable": "^2.5.0",
|
||||
"react-dnd": "^10.0.2",
|
||||
"react-dnd-html5-backend": "^10.0.2",
|
||||
"react-dom": "^16.14.0",
|
||||
"react-fullscreenable": "^2.5.1-0",
|
||||
"react-grid-system": "^7.1.1",
|
||||
"react-json-view": "^1.19.1",
|
||||
"react-notification-system": "^0.2.17",
|
||||
"react-rnd": "^7.4.3",
|
||||
"react-router": "^4.3.1",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-scripts": "^3.0.1",
|
||||
"react-sortable-tree": "^0.1.19",
|
||||
"react-svg-pan-zoom": "^2.18.0",
|
||||
"superagent": "^3.8.3",
|
||||
"validator": "^10.9.0"
|
||||
"react-notification-system": "^0.4.0",
|
||||
"react-rnd": "^10.2.3",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "^4.0.0",
|
||||
"react-svg-pan-zoom": "^3.8.1",
|
||||
"sass": "^1.28.0",
|
||||
"superagent": "^6.1.0",
|
||||
"ts-node": "^9.0.0",
|
||||
"type-fest": "^0.13.1",
|
||||
"typescript": "^4.0.5",
|
||||
"utf-8-validate": "^5.0.2",
|
||||
"validator": "^13.1.17",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-plugin-serve": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.2.0"
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
FROM node:12.2 AS builder
|
||||
|
||||
RUN apt-get install -y \
|
||||
git
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# use changes to package.json to force Docker not to use the cache
|
||||
# when we change our application's nodejs dependencies:
|
||||
ADD package.json /usr/src/app
|
||||
RUN npm install
|
||||
|
||||
# Install app dependencies
|
||||
COPY . /usr/src/app
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx
|
||||
|
||||
COPY --from=builder /usr/src/app/build /usr/share/nginx/html
|
|
@ -1,4 +0,0 @@
|
|||
FROM node:12.2
|
||||
|
||||
RUN apt-get install -y \
|
||||
git
|
|
@ -1,53 +0,0 @@
|
|||
|
||||
import { expect } from 'chai';
|
||||
|
||||
import createControls from '../../../components/dialogs/edit-widget-control-creator';
|
||||
import EditWidgetTextControl from '../../../components/dialogs/edit-widget-text-control';
|
||||
import EditWidgetColorControl from '../../../components/dialogs/edit-widget-color-control';
|
||||
import EditWidgetTimeControl from '../../../components/dialogs/edit-widget-time-control';
|
||||
import EditImageWidgetControl from '../../../components/dialogs/edit-widget-image-control';
|
||||
import EditWidgetSimulationControl from '../../../components/dialogs/edit-widget-simulation-control';
|
||||
import EditWidgetSignalControl from '../../../components/dialogs/edit-widget-signal-control';
|
||||
import EditWidgetSignalsControl from '../../../components/dialogs/edit-widget-signals-control';
|
||||
import EditWidgetOrientation from '../../../components/dialogs/edit-widget-orientation';
|
||||
import EditWidgetTextSizeControl from '../../../components/dialogs/edit-widget-text-size-control';
|
||||
import EditWidgetAspectControl from '../../../components/dialogs/edit-widget-aspect-control';
|
||||
import EditWidgetCheckboxControl from '../../../components/dialogs/edit-widget-checkbox-control';
|
||||
import EditWidgetMinMaxControl from '../../../components/dialogs/edit-widget-min-max-control';
|
||||
import EditWidgetColorZonesControl from '../../../components/dialogs/edit-widget-color-zones-control';
|
||||
import EditWidgetHTMLContent from '../../../components/dialogs/edit-widget-html-content';
|
||||
import EditWidgetNumberControl from '../../../components/dialogs/edit-widget-number-control';
|
||||
|
||||
describe('edit widget control creator', () => {
|
||||
it('should not return null', () => {
|
||||
let controls = createControls('Value', null, null, null, null, null, null);
|
||||
expect(controls).to.be.not.undefined;
|
||||
});
|
||||
|
||||
var runs = [
|
||||
{ args: { widgetType: 'Lamp' }, result: { controlNumber: 5, controlTypes: [EditWidgetSimulationControl, EditWidgetSignalControl, EditWidgetTextControl, EditWidgetColorControl, EditWidgetColorControl] } },
|
||||
{ args: { widgetType: 'Value' }, result: { controlNumber: 5, controlTypes: [EditWidgetTextControl, EditWidgetSimulationControl, EditWidgetSignalControl, EditWidgetTextSizeControl, EditWidgetCheckboxControl] } },
|
||||
{ args: { widgetType: 'Plot' }, result: { controlNumber: 5, controlTypes: [EditWidgetTimeControl, EditWidgetSimulationControl, EditWidgetSignalsControl, EditWidgetTextControl, EditWidgetMinMaxControl] } },
|
||||
{ args: { widgetType: 'Table' }, result: { controlNumber: 2, controlTypes: [EditWidgetSimulationControl, EditWidgetCheckboxControl] } },
|
||||
{ args: { widgetType: 'Image' }, result: { controlNumber: 2, controlTypes: [EditImageWidgetControl, EditWidgetAspectControl] } },
|
||||
{ args: { widgetType: 'Gauge' }, result: { controlNumber: 6, controlTypes: [EditWidgetTextControl, EditWidgetSimulationControl, EditWidgetSignalControl, EditWidgetCheckboxControl, EditWidgetColorZonesControl, EditWidgetMinMaxControl] } },
|
||||
{ args: { widgetType: 'PlotTable' }, result: { controlNumber: 5, controlTypes: [EditWidgetSimulationControl, EditWidgetSignalsControl, EditWidgetTextControl, EditWidgetTimeControl, EditWidgetMinMaxControl] } },
|
||||
{ args: { widgetType: 'Slider' }, result: { controlNumber: 9, controlTypes: [EditWidgetTextControl, EditWidgetOrientation, EditWidgetSimulationControl, EditWidgetSignalControl, EditWidgetCheckboxControl, EditWidgetCheckboxControl, EditWidgetMinMaxControl, EditWidgetNumberControl, EditWidgetNumberControl] } },
|
||||
{ args: { widgetType: 'Button' }, result: { controlNumber: 6, controlTypes: [EditWidgetTextControl, EditWidgetSimulationControl, EditWidgetSignalControl, EditWidgetCheckboxControl, EditWidgetNumberControl, EditWidgetNumberControl] } },
|
||||
{ args: { widgetType: 'Box' }, result: { controlNumber: 2, controlTypes: [EditWidgetColorControl, EditWidgetColorControl] } },
|
||||
{ args: { widgetType: 'Label' }, result: { controlNumber: 3, controlTypes: [EditWidgetTextControl, EditWidgetTextSizeControl, EditWidgetColorControl] } },
|
||||
{ args: { widgetType: 'HTML' }, result: { controlNumber: 1, controlTypes: [EditWidgetHTMLContent] } },
|
||||
{ args: { widgetType: 'Input'}, result: { controlNumber: 3, controlTypes: [EditWidgetTextControl, EditWidgetSimulationControl, EditWidgetSignalControl] } }
|
||||
];
|
||||
|
||||
runs.forEach( (run) => {
|
||||
let itMsg = run.args.widgetType + ' widget edit model should have correct controls';
|
||||
it(itMsg, () => {
|
||||
let controls = createControls(run.args.widgetType, null, null, null, null, null, null);
|
||||
|
||||
expect(controls).to.have.lengthOf(run.result.controlNumber);
|
||||
|
||||
controls.forEach( (control) => expect(control.type).to.be.oneOf(run.result.controlTypes))
|
||||
});
|
||||
});
|
||||
});
|
51
src/__tests__/widget/edit-widget-control-creator.js
Normal file
51
src/__tests__/widget/edit-widget-control-creator.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
|
||||
import { expect } from 'chai';
|
||||
|
||||
import createControls from '../../widget/edit-widget/edit-widget-control-creator';
|
||||
import EditWidgetTextControl from '../../widget/edit-widget/edit-widget-text-control';
|
||||
import EditWidgetColorControl from '../../widget/edit-widget/edit-widget-color-control';
|
||||
import EditWidgetTimeControl from '../../widget/edit-widget/edit-widget-time-control';
|
||||
import EditFileWidgetControl from '../../widget/edit-widget/edit-widget-file-control';
|
||||
import EditWidgetSignalControl from '../../widget/edit-widget/edit-widget-signal-control';
|
||||
import EditWidgetSignalsControl from '../../widget/edit-widget/edit-widget-signals-control';
|
||||
import EditWidgetOrientation from '../../widget/edit-widget/edit-widget-orientation';
|
||||
import EditWidgetTextSizeControl from '../../widget/edit-widget/edit-widget-text-size-control';
|
||||
import EditWidgetAspectControl from '../../widget/edit-widget/edit-widget-aspect-control';
|
||||
import EditWidgetCheckboxControl from '../../widget/edit-widget/edit-widget-checkbox-control';
|
||||
import EditWidgetMinMaxControl from '../../widget/edit-widget/edit-widget-min-max-control';
|
||||
import EditWidgetColorZonesControl from '../../widget/edit-widget/edit-widget-color-zones-control';
|
||||
import EditWidgetHTMLContent from '../../widget/edit-widget/edit-widget-html-content';
|
||||
import EditWidgetNumberControl from '../../widget/edit-widget/edit-widget-number-control';
|
||||
|
||||
describe('edit widget control creator', () => {
|
||||
it('should not return null', () => {
|
||||
let controls = createControls('Value', null, null, null, null, null, null);
|
||||
expect(controls).to.be.not.undefined;
|
||||
});
|
||||
|
||||
var runs = [
|
||||
{ args: { widgetType: 'Lamp' }, result: { controlNumber: 5, controlTypes: [EditWidgetSignalControl, EditWidgetTextControl, EditWidgetColorControl, EditWidgetColorControl] } },
|
||||
{ args: { widgetType: 'Value' }, result: { controlNumber: 5, controlTypes: [EditWidgetTextControl, EditWidgetSignalControl, EditWidgetTextSizeControl, EditWidgetCheckboxControl] } },
|
||||
{ args: { widgetType: 'Plot' }, result: { controlNumber: 5, controlTypes: [EditWidgetTimeControl, EditWidgetSignalsControl, EditWidgetTextControl, EditWidgetMinMaxControl] } },
|
||||
{ args: { widgetType: 'Table' }, result: { controlNumber: 2, controlTypes: [EditWidgetCheckboxControl] } },
|
||||
{ args: { widgetType: 'Image' }, result: { controlNumber: 2, controlTypes: [EditFileWidgetControl, EditWidgetAspectControl] } },
|
||||
{ args: { widgetType: 'Gauge' }, result: { controlNumber: 6, controlTypes: [EditWidgetTextControl, EditWidgetSignalControl, EditWidgetCheckboxControl, EditWidgetColorZonesControl, EditWidgetMinMaxControl] } },
|
||||
{ args: { widgetType: 'Slider' }, result: { controlNumber: 9, controlTypes: [EditWidgetTextControl, EditWidgetOrientation, EditWidgetSignalControl, EditWidgetCheckboxControl, EditWidgetCheckboxControl, EditWidgetMinMaxControl, EditWidgetNumberControl, EditWidgetNumberControl] } },
|
||||
{ args: { widgetType: 'Button' }, result: { controlNumber: 6, controlTypes: [EditWidgetTextControl, EditWidgetSignalControl, EditWidgetCheckboxControl, EditWidgetNumberControl, EditWidgetNumberControl] } },
|
||||
{ args: { widgetType: 'Box' }, result: { controlNumber: 2, controlTypes: [EditWidgetColorControl, EditWidgetColorControl] } },
|
||||
{ args: { widgetType: 'Label' }, result: { controlNumber: 3, controlTypes: [EditWidgetTextControl, EditWidgetTextSizeControl, EditWidgetColorControl] } },
|
||||
{ args: { widgetType: 'HTML' }, result: { controlNumber: 1, controlTypes: [EditWidgetHTMLContent] } },
|
||||
{ args: { widgetType: 'Input'}, result: { controlNumber: 3, controlTypes: [EditWidgetTextControl, EditWidgetSignalControl] } }
|
||||
];
|
||||
|
||||
runs.forEach( (run) => {
|
||||
let itMsg = run.args.widgetType + ' widget edit should have correct controls';
|
||||
it(itMsg, () => {
|
||||
let controls = createControls(run.args.widgetType, null, null, null, null, null, null);
|
||||
|
||||
expect(controls).to.have.lengthOf(run.result.controlNumber);
|
||||
|
||||
controls.forEach( (control) => expect(control.type).to.be.oneOf(run.result.controlTypes))
|
||||
});
|
||||
});
|
||||
});
|
132
src/app.js
Normal file
132
src/app.js
Normal file
|
@ -0,0 +1,132 @@
|
|||
/**
|
||||
* 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 { DndProvider } from 'react-dnd';
|
||||
import HTML5Backend from 'react-dnd-html5-backend';
|
||||
import NotificationSystem from 'react-notification-system';
|
||||
import { Redirect, Route } from 'react-router-dom';
|
||||
import { Col } from 'react-bootstrap';
|
||||
import { Hidden } from 'react-grid-system'
|
||||
|
||||
import AppDispatcher from './common/app-dispatcher';
|
||||
import NotificationsDataManager from './common/data-managers/notifications-data-manager';
|
||||
|
||||
import Home from './common/home';
|
||||
import Header from './common/header';
|
||||
import Footer from './common/footer';
|
||||
import SidebarMenu from './common/menu-sidebar';
|
||||
import HeaderMenu from './common/header-menu';
|
||||
|
||||
import InfrastructureComponents from './ic/ics';
|
||||
import Dashboard from './dashboard/dashboard';
|
||||
import Scenarios from './scenario/scenarios';
|
||||
import Scenario from './scenario/scenario';
|
||||
import Users from './user/users';
|
||||
import User from './user/user';
|
||||
|
||||
import './styles/app.css';
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showSidebarMenu: false,
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
NotificationsDataManager.setSystem(this.refs.notificationSystem);
|
||||
|
||||
// if token stored locally, we are already logged-in
|
||||
let token = localStorage.getItem("token");
|
||||
if (token != null && token !== '') {
|
||||
let currentUser = JSON.parse(localStorage.getItem("currentUser"));
|
||||
console.log("Already logged-in")
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/logged-in',
|
||||
token: token,
|
||||
currentUser: currentUser
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showSidebarMenu = () => {
|
||||
this.setState({ showSidebarMenu: true });
|
||||
};
|
||||
|
||||
hideSidebarMenu = () => {
|
||||
this.setState({ showSidebarMenu: false });
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
let token = localStorage.getItem("token");
|
||||
let currentUserRaw = localStorage.getItem("currentUser");
|
||||
|
||||
if (token == null || token === "" || currentUserRaw == null || currentUserRaw === "") {
|
||||
console.log("APP redirecting to logout/ login")
|
||||
return (<Redirect to="/logout" />);
|
||||
}
|
||||
|
||||
let currentUser = JSON.parse(currentUserRaw);
|
||||
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend} >
|
||||
<div>
|
||||
{/*
|
||||
<Col style={{ width: this.state.showSidebarMenu ? '280px' : '0px' }} smHidden mdHidden lgHidden className="sidenav">
|
||||
*/}
|
||||
<Hidden sm md lg xl xxl>
|
||||
<Col style={{ width: this.state.showSidebarMenu ? '280px' : '0px' }} className="sidenav">
|
||||
<HeaderMenu onClose={this.hideSidebarMenu} currentRole={currentUser.role} />
|
||||
</Col>
|
||||
</Hidden>
|
||||
|
||||
<div className="app">
|
||||
<NotificationSystem ref="notificationSystem" />
|
||||
|
||||
<Header onMenuButton={this.showSidebarMenu} showMenuButton={false} />
|
||||
|
||||
<div className={`app-body app-body-spacing`} >
|
||||
<Col xs={false}>
|
||||
<SidebarMenu currentRole={currentUser.role} />
|
||||
</Col>
|
||||
|
||||
<div className={`app-content app-content-margin-left`}>
|
||||
<Route exact path="/" component={Home} />
|
||||
<Route path="/home" component={Home} />
|
||||
<Route exact path="/scenarios" component={Scenarios} />
|
||||
<Route path="/scenarios/:scenario" component={Scenario} />
|
||||
<Route path="/dashboards/:dashboard" component={Dashboard} />
|
||||
<Route path="/infrastructure" component={InfrastructureComponents} />
|
||||
<Route path="/account" component={User} />
|
||||
<Route path="/users" component={Users} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
</DndProvider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default App
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: visualizations-data-manager.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 03.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
|
@ -19,6 +15,20 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import RestDataManager from './rest-data-manager';
|
||||
/// FluxContainerConverter.js
|
||||
/// ATTENTION!!! This is an ugly workaround found here https://github.com/facebook/flux/issues/351 to make Flux Containers work with ES6
|
||||
|
||||
|
||||
function convert(containerClass) {
|
||||
const tmp = containerClass;
|
||||
containerClass = function(...args) {
|
||||
return new tmp(...args);
|
||||
};
|
||||
containerClass.prototype = tmp.prototype;
|
||||
containerClass.getStores = tmp.getStores;
|
||||
containerClass.calculateState = tmp.calculateState;
|
||||
return containerClass;
|
||||
}
|
||||
|
||||
export {convert}
|
||||
|
||||
export default new RestDataManager('visualization', '/visualizations');
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: rest-api.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
|
@ -23,7 +19,6 @@ import request from 'superagent/lib/client';
|
|||
import Promise from 'es6-promise';
|
||||
import NotificationsDataManager from '../data-managers/notifications-data-manager';
|
||||
|
||||
|
||||
// TODO: Add this to a central pool of notifications
|
||||
const SERVER_NOT_REACHABLE_NOTIFICATION = {
|
||||
title: 'Server not reachable',
|
||||
|
@ -37,32 +32,36 @@ const REQUEST_TIMEOUT_NOTIFICATION = {
|
|||
level: 'error'
|
||||
};
|
||||
|
||||
// Check if the error was due to network failure, timeouts, etc.
|
||||
// Check if the error was due to network failure, timeouts, etc.
|
||||
// Can be used for the rest of requests
|
||||
function isNetworkError(err) {
|
||||
let result = false;
|
||||
|
||||
// If not status nor response fields, it is a network error. TODO: Handle timeouts
|
||||
if (err.status == null || err.response == null) {
|
||||
if (err.status == null || err.status === 500 || err.response == null) {
|
||||
result = true;
|
||||
|
||||
|
||||
let notification = err.timeout? REQUEST_TIMEOUT_NOTIFICATION : SERVER_NOT_REACHABLE_NOTIFICATION;
|
||||
NotificationsDataManager.addNotification(notification);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
let prevURL = null;
|
||||
|
||||
class RestAPI {
|
||||
get(url, token) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var req = request.get(url);
|
||||
|
||||
if (token != null) {
|
||||
req.set('x-access-token', token);
|
||||
req.set('Authorization', "Bearer " + token);
|
||||
}
|
||||
|
||||
req.end(function (error, res) {
|
||||
if (res == null || res.status !== 200) {
|
||||
if (req.url !== prevURL) error.handled = isNetworkError(error);
|
||||
prevURL = req.url;
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(JSON.parse(res.text));
|
||||
|
@ -76,9 +75,9 @@ class RestAPI {
|
|||
var req = request.post(url).send(body).timeout({ response: 5000 }); // Simple response start timeout (3s)
|
||||
|
||||
if (token != null) {
|
||||
req.set('x-access-token', token);
|
||||
req.set('Authorization', "Bearer " + token);
|
||||
}
|
||||
|
||||
|
||||
req.end(function (error, res) {
|
||||
if (res == null || res.status !== 200) {
|
||||
|
||||
|
@ -97,11 +96,12 @@ class RestAPI {
|
|||
var req = request.delete(url);
|
||||
|
||||
if (token != null) {
|
||||
req.set('x-access-token', token);
|
||||
req.set('Authorization', "Bearer " + token);
|
||||
}
|
||||
|
||||
req.end(function (error, res) {
|
||||
if (res == null || res.status !== 200) {
|
||||
error.handled = isNetworkError(error);
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(JSON.parse(res.text));
|
||||
|
@ -115,11 +115,12 @@ class RestAPI {
|
|||
var req = request.put(url).send(body);
|
||||
|
||||
if (token != null) {
|
||||
req.set('x-access-token', token);
|
||||
req.set('Authorization', "Bearer " + token);
|
||||
}
|
||||
|
||||
req.end(function (error, res) {
|
||||
if (res == null || res.status !== 200) {
|
||||
error.handled = isNetworkError(error);
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(JSON.parse(res.text));
|
||||
|
@ -128,16 +129,17 @@ class RestAPI {
|
|||
});
|
||||
}
|
||||
|
||||
upload(url, data, token, progressCallback) {
|
||||
upload(url, data, token, progressCallback, scenarioID) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const req = request.post(url).send(data).on('progress', progressCallback);
|
||||
const req = request.post(url + "?scenarioID=" + scenarioID).send(data).on('progress', progressCallback);
|
||||
|
||||
if (token != null) {
|
||||
req.set('x-access-token', token);
|
||||
req.set('Authorization', "Bearer " + token);
|
||||
}
|
||||
|
||||
req.end(function (error, res) {
|
||||
if (res == null || res.status !== 200) {
|
||||
error.handled = isNetworkError(error);
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(JSON.parse(res.text));
|
||||
|
@ -145,6 +147,29 @@ class RestAPI {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
download(url, token, fileID) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
let req = request.get(url + "/" + fileID).buffer(true).responseType("blob")
|
||||
// use blob response type and buffer
|
||||
if (token != null) {
|
||||
req.set('Authorization', "Bearer " + token);
|
||||
}
|
||||
|
||||
req.end(function (error, res) {
|
||||
if (error !== null || res.status !== 200) {
|
||||
error.handled = isNetworkError(error);
|
||||
reject(error);
|
||||
} else {
|
||||
// file data is contained in res.body (because of blob response type)
|
||||
let parts = url.split("/");
|
||||
resolve({data: res.body, type: res.type, id: parts[parts.length-1]})
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new RestAPI();
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: websocket-api.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 03.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
|
@ -20,19 +16,19 @@
|
|||
******************************************************************************/
|
||||
|
||||
class WebsocketAPI {
|
||||
constructor(endpoint, callbacks) {
|
||||
this.endpoint = endpoint;
|
||||
constructor(websocketurl, callbacks) {
|
||||
this.websocketurl = websocketurl;
|
||||
this.callbacks = callbacks;
|
||||
|
||||
this.wasConnected = false;
|
||||
this.isClosing = false;
|
||||
|
||||
this.connect(endpoint, callbacks);
|
||||
this.connect(websocketurl, callbacks);
|
||||
}
|
||||
|
||||
connect(endpoint, callbacks) {
|
||||
connect(websocketurl, callbacks) {
|
||||
// create web socket client
|
||||
this.socket = new WebSocket(WebsocketAPI.getURL(endpoint), 'live');
|
||||
this.socket = new WebSocket(WebsocketAPI.getURL(websocketurl), 'live');
|
||||
this.socket.binaryType = 'arraybuffer';
|
||||
this.socket.onclose = this.onClose;
|
||||
this.socket.onopen = this.onOpen;
|
||||
|
@ -44,12 +40,12 @@ class WebsocketAPI {
|
|||
}
|
||||
|
||||
reconnect() {
|
||||
//console.log("Reconnecting: " + this.endpoint);
|
||||
this.connect(this.endpoint, this.callbacks);
|
||||
//console.log("Reconnecting: " + this.websocketurl);
|
||||
this.connect(this.websocketurl, this.callbacks);
|
||||
}
|
||||
|
||||
get url() {
|
||||
return WebsocketAPI.getURL(this.endpoint);
|
||||
return WebsocketAPI.getURL(this.websocketurl);
|
||||
}
|
||||
|
||||
send(data) {
|
||||
|
@ -62,7 +58,7 @@ class WebsocketAPI {
|
|||
}
|
||||
|
||||
onError = e => {
|
||||
console.error('Error on WebSocket connection to: ' + this.endpoint + ':', e);
|
||||
console.error('Error on WebSocket connection to: ' + this.websocketurl + ':', e);
|
||||
|
||||
if ('onError' in this.callbacks)
|
||||
this.callbacks.onError(e);
|
||||
|
@ -82,16 +78,16 @@ class WebsocketAPI {
|
|||
}
|
||||
else {
|
||||
if (this.wasConnected) {
|
||||
console.log("Connection to " + this.endpoint + " dropped. Attempt reconnect in 1 sec");
|
||||
console.log("Connection to " + this.websocketurl + " dropped. Attempt reconnect in 1 sec");
|
||||
window.setTimeout(() => { this.reconnect(); }, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static getURL(endpoint) {
|
||||
static getURL(websocketurl) {
|
||||
// create an anchor element (note: no need to append this element to the document)
|
||||
var link = document.createElement('a');
|
||||
link.href = endpoint;
|
||||
link.href = websocketurl;
|
||||
|
||||
if (link.protocol === 'https:')
|
||||
link.protocol = 'wss:';
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: app-dispatcher.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: array-store.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 03.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
|
@ -21,7 +17,8 @@
|
|||
|
||||
import { ReduceStore } from 'flux/utils';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import AppDispatcher from './app-dispatcher';
|
||||
import NotificationsDataManager from '../common/data-managers/notifications-data-manager';
|
||||
|
||||
class ArrayStore extends ReduceStore {
|
||||
constructor(type, dataManager) {
|
||||
|
@ -39,7 +36,7 @@ class ArrayStore extends ReduceStore {
|
|||
// search for existing element to update
|
||||
state.forEach((element, index, array) => {
|
||||
newElements = newElements.filter((updateElement, newIndex) => {
|
||||
if (element._id === updateElement._id) {
|
||||
if (element.id === updateElement.id) {
|
||||
// update each property
|
||||
for (var key in updateElement) {
|
||||
if (updateElement.hasOwnProperty(key)) {
|
||||
|
@ -69,10 +66,10 @@ class ArrayStore extends ReduceStore {
|
|||
case this.type + '/start-load':
|
||||
if (Array.isArray(action.data)) {
|
||||
action.data.forEach((id) => {
|
||||
this.dataManager.load(id, action.token);
|
||||
this.dataManager.load(id, action.token,action.param);
|
||||
});
|
||||
} else {
|
||||
this.dataManager.load(action.data, action.token);
|
||||
this.dataManager.load(action.data, action.token,action.param);
|
||||
}
|
||||
return state;
|
||||
|
||||
|
@ -84,43 +81,73 @@ class ArrayStore extends ReduceStore {
|
|||
}
|
||||
|
||||
case this.type + '/load-error':
|
||||
// TODO: Add error message
|
||||
return state;
|
||||
if (action.error && !action.error.handled && action.error.response) {
|
||||
|
||||
const USER_LOAD_ERROR_NOTIFICATION = {
|
||||
title: 'Failed to load',
|
||||
message: action.error.response.body.message,
|
||||
level: 'error'
|
||||
};
|
||||
NotificationsDataManager.addNotification(USER_LOAD_ERROR_NOTIFICATION);
|
||||
|
||||
}
|
||||
return super.reduce(state, action);
|
||||
|
||||
case this.type + '/start-add':
|
||||
this.dataManager.add(action.data, action.token);
|
||||
this.dataManager.add(action.data, action.token,action.param);
|
||||
return state;
|
||||
|
||||
case this.type + '/added':
|
||||
if(typeof action.data.managedexternally !== "undefined" && action.data.managedexternally === true ) return state;
|
||||
return this.updateElements(state, [action.data]);
|
||||
|
||||
case this.type + '/add-error':
|
||||
// TODO: Add error message
|
||||
return state;
|
||||
|
||||
return state;
|
||||
|
||||
|
||||
case this.type + '/start-remove':
|
||||
this.dataManager.remove(action.data, action.token);
|
||||
this.dataManager.remove(action.data, action.token,action.param);
|
||||
return state;
|
||||
|
||||
case this.type + '/removed':
|
||||
return state.filter((item) => {
|
||||
return (item !== action.original);
|
||||
});
|
||||
|
||||
if (action.original) {
|
||||
return state.filter((item) => {
|
||||
return (item !== action.original);
|
||||
});
|
||||
} else {
|
||||
return state.filter((item) => {
|
||||
return (item.id !== action.data);
|
||||
});
|
||||
}
|
||||
|
||||
case this.type + '/remove-error':
|
||||
// TODO: Add error message
|
||||
return state;
|
||||
if (action.error && !action.error.handled && action.error.response) {
|
||||
|
||||
const USER_REMOVE_ERROR_NOTIFICATION = {
|
||||
title: 'Failed to add remove ',
|
||||
message: action.error.response.body.message,
|
||||
level: 'error'
|
||||
};
|
||||
NotificationsDataManager.addNotification(USER_REMOVE_ERROR_NOTIFICATION);
|
||||
|
||||
}
|
||||
return super.reduce(state, action);
|
||||
|
||||
case this.type + '/start-edit':
|
||||
this.dataManager.update(action.data, action.token);
|
||||
if(action.id){
|
||||
this.dataManager.update(action.data, action.token,action.param,action.id);
|
||||
}
|
||||
else{
|
||||
this.dataManager.update(action.data, action.token,action.param);
|
||||
}
|
||||
return state;
|
||||
|
||||
case this.type + '/edited':
|
||||
return this.updateElements(state, [action.data]);
|
||||
|
||||
case this.type + '/edit-error':
|
||||
// TODO: Add error message
|
||||
return state;
|
||||
return state;
|
||||
|
||||
default:
|
||||
return state;
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: notifications-data-manager.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 21.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: simulation-data-manager.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 04.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
|
@ -19,6 +15,17 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import RestDataManager from './rest-data-manager';
|
||||
class NotificationsFactory {
|
||||
|
||||
export default new RestDataManager('simulation', '/simulations', [ '_id', 'name', 'projects', 'models', 'startParameters' ]);
|
||||
// This is an example
|
||||
static get EXAMPLE_NOTIFICATION() {
|
||||
return {
|
||||
title: 'Example notification',
|
||||
message: 'Write something here that describes what happend.',
|
||||
level: 'warning'
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default NotificationsFactory;
|
229
src/common/data-managers/rest-data-manager.js
Normal file
229
src/common/data-managers/rest-data-manager.js
Normal file
|
@ -0,0 +1,229 @@
|
|||
/**
|
||||
* 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 RestAPI from '../api/rest-api';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
|
||||
const API_URL = '/api/v2';
|
||||
|
||||
class RestDataManager {
|
||||
constructor(type, url, keyFilter) {
|
||||
this.url = url;
|
||||
this.type = type;
|
||||
this.keyFilter = keyFilter;
|
||||
this.onLoad = null;
|
||||
}
|
||||
|
||||
makeURL(part) {
|
||||
return API_URL + part;
|
||||
}
|
||||
|
||||
filterKeys(object) {
|
||||
// don't change anything if no filter is set
|
||||
if (this.keyFilter == null || Array.isArray(this.keyFilter) === false) {
|
||||
return object;
|
||||
}
|
||||
|
||||
// remove all keys not in the filter
|
||||
Object.keys(object).filter(key => {
|
||||
return this.keyFilter.indexOf(key) === -1;
|
||||
}).forEach(key => {
|
||||
delete object[key];
|
||||
});
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
requestURL(form, id, param, object = null){
|
||||
switch(form){
|
||||
case 'load/add':
|
||||
if (param === null){
|
||||
if(id != null){
|
||||
return this.makeURL(this.url + '/' + id);
|
||||
}
|
||||
else {
|
||||
return this.makeURL(this.url);
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(id != null){
|
||||
return this.makeURL(this.url + '/' + id + param);
|
||||
}
|
||||
else {
|
||||
return this.makeURL(this.url + param)
|
||||
}
|
||||
}
|
||||
case 'remove/update':
|
||||
if(id !== null){
|
||||
return this.makeURL(this.url + '/' + id);
|
||||
|
||||
}
|
||||
else if(param === null){
|
||||
return this.makeURL(this.url + '/' + object.id);
|
||||
}
|
||||
else{
|
||||
return this.makeURL(this.url + '/' + object.id + param);
|
||||
}
|
||||
default:
|
||||
console.log("something went wrong");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
load(id, token = null,param = null) {
|
||||
if (id != null) {
|
||||
// load single object
|
||||
RestAPI.get(this.requestURL('load/add',id,param), token).then(response => {
|
||||
let data;
|
||||
if (response.hasOwnProperty(this.type)) {
|
||||
data = this.filterKeys(response[this.type]);
|
||||
}else{
|
||||
// loaded file
|
||||
data = response;
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/loaded',
|
||||
data: data,
|
||||
token: token
|
||||
});
|
||||
|
||||
if (this.onLoad != null) {
|
||||
this.onLoad(data, token);
|
||||
}
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/load-error',
|
||||
error: error
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// load all objects
|
||||
RestAPI.get(this.requestURL('load/add',id,param), token).then(response => {
|
||||
const data = response[this.type + 's'].map(element => {
|
||||
return this.filterKeys(element);
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/loaded',
|
||||
data: data,
|
||||
token: token,
|
||||
});
|
||||
|
||||
if (this.onLoad != null) {
|
||||
this.onLoad(data, token);
|
||||
}
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/load-error',
|
||||
error: error
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
add(object, token = null, param = null, subObjects = null) {
|
||||
var obj = {};
|
||||
obj[this.type] = this.filterKeys(object);
|
||||
RestAPI.post(this.requestURL('load/add',null,param), obj, token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/added',
|
||||
data: response[this.type],
|
||||
token: token
|
||||
});
|
||||
|
||||
// check if POST is done for import of object and issue dispatches of sub-objects
|
||||
if (subObjects !== null){
|
||||
// there are sub-objects to be added for an import
|
||||
for (let objectType of subObjects){
|
||||
let type = Object.keys(objectType) // type can be dashboards, configs, widgets, ...
|
||||
type = type[0];
|
||||
for (let newObj of objectType[type]){
|
||||
|
||||
// set the ID of the object that the sub-object shall be associated with
|
||||
if(type === "configs" || type === "dashboards"){
|
||||
// the main object is a scenario
|
||||
newObj.scenarioID = response[this.type].id
|
||||
} else if (type === "widgets") {
|
||||
// the main object is a dashboard
|
||||
newObj.dashboardID = response[this.type].id
|
||||
} else if (type === "signals") {
|
||||
// the main object is a component configuration
|
||||
newObj.configID = response[this.type].id
|
||||
}
|
||||
|
||||
// iterate over all objects of type 'type' add issue add dispatch
|
||||
AppDispatcher.dispatch({
|
||||
type: type + '/start-add',
|
||||
data: newObj,
|
||||
token: token
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/add-error',
|
||||
error: error
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
remove(object, token = null, param = null) {
|
||||
RestAPI.delete(this.requestURL('remove/update',null,param,object), token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/removed',
|
||||
data: response[this.type],
|
||||
original: object,
|
||||
token: token
|
||||
});
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/remove-error',
|
||||
error: error
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
update(object, token = null, param = null, id = null) {
|
||||
var obj = {};
|
||||
obj[this.type] = this.filterKeys(object);
|
||||
|
||||
RestAPI.put(this.requestURL('remove/update',id,param,object), obj, token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/edited',
|
||||
data: response[this.type]
|
||||
});
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/edit-error',
|
||||
error: error
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
export default RestDataManager;
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: edit-user.js
|
||||
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
|
||||
* Date: 02.05.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
|
@ -20,13 +16,14 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { Button, Modal } from 'react-bootstrap';
|
||||
import { Button, Modal, FormLabel } from 'react-bootstrap';
|
||||
import {Collapse} from 'react-collapse';
|
||||
|
||||
class DeleteDialog extends React.Component {
|
||||
onModalKeyPress = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
|
||||
|
||||
this.props.onClose(false);
|
||||
}
|
||||
}
|
||||
|
@ -39,11 +36,14 @@ class DeleteDialog extends React.Component {
|
|||
|
||||
<Modal.Body>
|
||||
Are you sure you want to delete the {this.props.title} <strong>'{this.props.name}'</strong>?
|
||||
<Collapse isOpened={this.props.managedexternally} >
|
||||
<FormLabel size="sm">The IC will be deleted if the respective VILLAScontroller sends "gone" state and no component config is using the IC anymore</FormLabel>
|
||||
</Collapse>
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
<Button onClick={() => this.props.onClose(false)}>Cancel</Button>
|
||||
<Button bsStyle="danger" onClick={() => this.props.onClose(true)}>Delete</Button>
|
||||
<Button variant="danger" onClick={() => this.props.onClose(true)}>Delete</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>;
|
||||
}
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: dialog.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 03.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
|
@ -50,7 +46,7 @@ class Dialog extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<Modal keyboard show={this.props.show} onEnter={this.props.onReset} onHide={this.cancelModal} onKeyPress={this.onKeyPress}>
|
||||
<Modal size={this.props.size || 'sm'} keyboard show={this.props.show} onEnter={this.props.onReset} onHide={this.cancelModal} onKeyPress={this.onKeyPress}>
|
||||
<Modal.Header>
|
||||
<Modal.Title>{this.props.title}</Modal.Title>
|
||||
</Modal.Header>
|
||||
|
@ -60,7 +56,7 @@ class Dialog extends React.Component {
|
|||
</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
<Button onClick={this.cancelModal}>Cancel</Button>
|
||||
{this.props.blendOutCancel? <div></div>: <Button onClick={this.cancelModal}>Cancel</Button>}
|
||||
<Button onClick={this.closeModal} disabled={!this.props.valid}>{this.props.buttonTitle}</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: header.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 25.05.2018
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
|
@ -18,7 +14,7 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
// TODO remove this file (not used!)
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormControl, Button } from 'react-bootstrap';
|
||||
|
@ -36,8 +32,10 @@ class EditableHeader extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.setState({ title: nextProps.title });
|
||||
static getDerivedStateFromProps(props, state){
|
||||
return {
|
||||
title: props.title
|
||||
};
|
||||
}
|
||||
|
||||
edit = () => {
|
||||
|
@ -66,7 +64,7 @@ class EditableHeader extends React.Component {
|
|||
};
|
||||
|
||||
const iconStyle = {
|
||||
float: 'left',
|
||||
float: 'right',
|
||||
|
||||
marginLeft: '10px',
|
||||
marginTop: '25px',
|
||||
|
@ -82,11 +80,11 @@ class EditableHeader extends React.Component {
|
|||
|
||||
return <div>
|
||||
<form style={wrapperStyle}>
|
||||
<FormControl type='text' bsSize='large' value={this.state.title} onChange={this.onChange} style={editStyle} autoFocus />
|
||||
<FormControl type='text' size='large' value={this.state.title} onChange={this.onChange} style={editStyle} autoFocus />
|
||||
</form>
|
||||
|
||||
<Button bsStyle="link" onClick={this.save}><Icon icon='check' style={iconStyle} /></Button>
|
||||
<Button bsStyle="link" onClick={this.cancel}><Icon icon='times' style={iconStyle} /></Button>
|
||||
<Button onClick={this.save}><Icon icon='check' style={iconStyle} /></Button>
|
||||
<Button onClick={this.cancel}><Icon icon='times' style={iconStyle} /></Button>
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
@ -95,7 +93,7 @@ class EditableHeader extends React.Component {
|
|||
{this.state.title}
|
||||
</h1>
|
||||
|
||||
<Button bsStyle="link" onClick={this.edit}><Icon icon='edit' style={iconStyle} /></Button>
|
||||
<Button onClick={this.edit}><Icon icon='edit' style={iconStyle} /></Button>
|
||||
</div>;
|
||||
}
|
||||
}
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: footer.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: header-menu.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 17.08.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
|
@ -26,16 +22,17 @@ import { NavLink } from 'react-router-dom';
|
|||
export default class HeaderMenu extends React.Component {
|
||||
render() {
|
||||
return <div>
|
||||
<Button className="closeButton" bsStyle="link" onClick={this.props.onClose}>×</Button>
|
||||
|
||||
<Button className="closeButton" variant="link" onClick={this.props.onClose}>×</Button>
|
||||
|
||||
<ul>
|
||||
<li><NavLink to="/home" activeClassName="active" title="Home" onClick={this.props.onClose}>Home</NavLink></li>
|
||||
<li><NavLink to="/projects" activeClassName="active" title="Projects" onClick={this.props.onClose}>Projects</NavLink></li>
|
||||
<li><NavLink to="/simulations" activeClassName="active" title="Simulations" onClick={this.props.onClose}>Simulations</NavLink></li>
|
||||
<li><NavLink to="/simulators" activeClassName="active" title="Simulators" onClick={this.props.onClose}>Simulators</NavLink></li>
|
||||
{ this.props.currentRole === 'admin' ?
|
||||
<li><NavLink to="/scenarios" activeClassName="active" title="Scenarios" onClick={this.props.onClose}>Scenarios</NavLink></li>
|
||||
<li><NavLink to="/infrastructure" activeClassName="active" title="Infrastructure Components" onClick={this.props.onClose}>Infrastructure Components</NavLink></li>
|
||||
{ this.props.currentRole === 'Admin' ?
|
||||
<li><NavLink to="/users" activeClassName="active" title="User Management" onClick={this.props.onClose}>User Management</NavLink></li> : ''
|
||||
}
|
||||
<li><NavLink to="/account" title="Account">Account</NavLink></li>
|
||||
<li><NavLink to="/logout" title="Logout" onClick={this.props.onClose}>Logout</NavLink></li>
|
||||
</ul>
|
||||
</div>;
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: header.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
|
@ -21,20 +17,26 @@
|
|||
|
||||
import React from 'react';
|
||||
import { Col, Button } from 'react-bootstrap';
|
||||
import { Hidden } from 'react-grid-system'
|
||||
import Icon from './icon';
|
||||
import config from '../config';
|
||||
|
||||
class Header extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<header className="app-header">
|
||||
<Col xs={10} smOffset={2} sm={8}>
|
||||
<h1>VILLASweb</h1>
|
||||
</Col>
|
||||
<Col xs={2} smHidden mdHidden lgHidden style={{ paddingLeft: 'auto', paddingRight: 0 }}>
|
||||
{this.props.showMenuButton &&
|
||||
<Button bsStyle="link" onClick={this.props.onMenuButton} style={{ float: 'right', marginRight: '10px' }}><Icon size="3x" icon="bars" className="menu-icon" /></Button>
|
||||
}
|
||||
<Col xs={{span: 10}} sm={{span: 8, offset: 2}}>
|
||||
<h1>{config.instance} - {config.subtitle}</h1>
|
||||
</Col>
|
||||
<Hidden sm md lg xl>
|
||||
<Col xs={2} style={{ paddingLeft: 'auto', paddingRight: 0 }}>
|
||||
{this.props.showMenuButton &&
|
||||
<Button variant="link" onClick={this.props.onMenuButton} style={{ float: 'right', marginRight: '10px' }}>
|
||||
<Icon size="3x" icon="bars" className="menu-icon" />
|
||||
</Button>
|
||||
}
|
||||
</Col>
|
||||
</Hidden>
|
||||
</header>
|
||||
);
|
||||
}
|
140
src/common/home.js
Normal file
140
src/common/home.js
Normal file
|
@ -0,0 +1,140 @@
|
|||
/**
|
||||
* 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 config from '../config';
|
||||
import {Redirect} from "react-router-dom";
|
||||
|
||||
class Home extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// create url for API documentation, distinguish between localhost and production deployment
|
||||
let docs_url = "";
|
||||
let docs_location = "/swagger/index.html";
|
||||
let base_url = window.location.origin;
|
||||
if (base_url.search("localhost") === -1){
|
||||
docs_url = base_url + docs_location;
|
||||
} else {
|
||||
// useful for local testing, replace port 3000 with port 4000 (port of backend)
|
||||
docs_url = base_url.replace("3000", "4000") + docs_location;
|
||||
}
|
||||
|
||||
this.state = {
|
||||
docs_url: docs_url
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
getCounts(type) {
|
||||
if (this.state.hasOwnProperty('counts'))
|
||||
return this.state.counts[type];
|
||||
else
|
||||
return '?';
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let currentUser = JSON.parse(localStorage.getItem("currentUser"));
|
||||
if (currentUser == null){
|
||||
console.log("HOME redirecting to logout/ login")
|
||||
return (<Redirect to="/logout" />);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="home-container">
|
||||
<img style={{height: 120, float: 'right'}} src={require('../img/villas_web.svg').default} alt="Logo VILLASweb" />
|
||||
<h1>Home</h1>
|
||||
<p>
|
||||
Welcome to <b>{config.instance}</b> hosted by <a href={"mailto:" + config.admin.mail}>{config.admin.name}</a>!<br />
|
||||
</p>
|
||||
<p>
|
||||
You are logged in as user <b>{currentUser.username}</b> with <b>ID {currentUser.id}</b> and role <b>{currentUser.role}</b>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
An interactive documentation of the VILLASweb API is available <a href={this.state.docs_url} target="_blank" rel="noopener noreferrer">here</a>.
|
||||
</p>
|
||||
|
||||
|
||||
<h3>Data Model</h3>
|
||||
<img height={400} src={require('../img/datamodel.png').default} alt="Datamodel VILLASweb" />
|
||||
|
||||
<h3>Terminology </h3>
|
||||
|
||||
<h5>Infrastructure Component</h5>
|
||||
<ul>
|
||||
<li>A component of research infrastructure</li>
|
||||
<li>Category: for example simulator, gateway, amplifier, database, etc.</li>
|
||||
<li>Type: for example RTDS, OpalRT, VILLASnode, Cassandra</li>
|
||||
</ul>
|
||||
|
||||
<h5>Component Configuration</h5>
|
||||
<ul>
|
||||
<li>Input signals: Signals that can be modified in VILLASweb</li>
|
||||
<li>Output signals: Signals that can be visualized on dashboards of VILLASweb</li>
|
||||
<li>Parameters: Further configuration parameters of the infrastructure component</li>
|
||||
</ul>
|
||||
|
||||
<h5>Dashboard</h5>
|
||||
<ul>
|
||||
<li>Visualize ongoing experiments in real-time</li>
|
||||
<li>Interact with ongoing experiments in real-time</li>
|
||||
</ul>
|
||||
|
||||
<h5>Scenario</h5>
|
||||
<ul>
|
||||
<li>A collection of component configurations and dashboards for a specific experiment</li>
|
||||
<li>Users can have access to multiple scenarios</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
<h3>Credits</h3>
|
||||
<p>VILLASweb is developed by the <a href="http://acs.eonerc.rwth-aachen.de">Institute for Automation of Complex Power Systems</a> at the <a href="https;//www.rwth-aachen.de">RWTH Aachen University</a>.</p>
|
||||
<ul>
|
||||
<li><a href="mailto:stvogel@eonerc.rwth-aachen.de">Steffen Vogel</a></li>
|
||||
<li><a href="mailto:sonja.happ@eonerc.rwth-aachen.de">Sonja Happ</a></li>
|
||||
</ul>
|
||||
<h3>Links</h3>
|
||||
<ul>
|
||||
<li><a href="http://fein-aachen.org/projects/villas-framework/">Project Page</a></li>
|
||||
<li><a href="https://villas.fein-aachen.org/doc/web.html">Documentation</a></li>
|
||||
<li><a href="https://git.rwth-aachen.de/acs/public/villas/web">Source Code</a></li>
|
||||
</ul>
|
||||
<h3>Funding</h3>
|
||||
<p>The development of <a href="http://fein-aachen.org/projects/villas-framework/">VILLASframework</a> projects have received funding from</p>
|
||||
<ul>
|
||||
<li><a href="http://www.acs.eonerc.rwth-aachen.de/cms/E-ON-ERC-ACS/Forschung/Forschungsprojekte/Gruppe-Real-Time-Simulation-and-Hardware/~qxvw/Urban-Energy-Lab-4/">Urban Energy Lab 4.0</a> a project funded by OP EFRE NRW (European Regional Development Fund) for the setup of a novel energy research infrastructure.</li>
|
||||
<li><a href="http://www.re-serve.eu">RESERVE</a> a European Union’s Horizon 2020 research and innovation programme under grant agreement No 727481</li>
|
||||
<li><a href="http://www.jara.org/en/research/energy">JARA-ENERGY</a>. Jülich-Aachen Research Alliance (JARA) is an initiative of RWTH Aachen University and Forschungszentrum Jülich.</li>
|
||||
</ul>
|
||||
<img height={100} src={require('../img/european_commission.svg').default} alt="Logo EU" />
|
||||
<img height={70} src={require('../img/reserve.svg').default} alt="Logo EU" />
|
||||
<img height={70} src={require('../img/uel_efre.jpeg').default} alt="Logo UEL OP EFRE NRW" />
|
||||
<img height={70} src={require('../img/uel.png').default} alt="Logo UEL" />
|
||||
<img height={60} src={require('../img/eonerc_rwth.svg').default} alt="Logo ACS" />
|
||||
{
|
||||
//<img height={70} src={require('../img/jara.svg').default} alt="Logo JARA" />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Home;
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: icon.js
|
||||
* Author: Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* Date: 09.06.2018
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: menu-sidebar.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
|
@ -30,12 +26,12 @@ class SidebarMenu extends React.Component {
|
|||
|
||||
<ul>
|
||||
<li><NavLink to="/home" activeClassName="active" title="Home">Home</NavLink></li>
|
||||
<li><NavLink to="/projects" activeClassName="active" title="Projects">Projects</NavLink></li>
|
||||
<li><NavLink to="/simulations" activeClassName="active" title="Simulations">Simulations</NavLink></li>
|
||||
<li><NavLink to="/simulators" activeClassName="active" title="Simulators">Simulators</NavLink></li>
|
||||
{ this.props.currentRole === 'admin' ?
|
||||
<li><NavLink to="/users" activeClassName="active" title="User Management">Users</NavLink></li> : ''
|
||||
<li><NavLink to="/scenarios" activeClassName="active" title="Scenarios">Scenarios</NavLink></li>
|
||||
<li><NavLink to="/infrastructure" activeClassName="active" title="Infrastructure Components">Infrastructure Components</NavLink></li>
|
||||
{ this.props.currentRole === 'Admin' ?
|
||||
<li><NavLink to="/users" activeClassName="active" title="User Management">User Management</NavLink></li> : ''
|
||||
}
|
||||
<li><NavLink to="/account" title="Account">Account</NavLink></li>
|
||||
<li><NavLink to="/logout" title="Logout">Logout</NavLink></li>
|
||||
</ul>
|
||||
</div>
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: header.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 06.06.2018
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: table-column.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 06.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
|
@ -29,14 +25,19 @@ class TableColumn extends Component {
|
|||
editButton: false,
|
||||
deleteButton: false,
|
||||
exportButton: false,
|
||||
duplicateButton: false,
|
||||
link: '/',
|
||||
linkKey: '',
|
||||
dataIndex: false,
|
||||
inlineEditable: false,
|
||||
inputType: 'text',
|
||||
clickable: false,
|
||||
labelKey: null,
|
||||
checkbox: false,
|
||||
checkboxKey: ''
|
||||
checkboxKey: '',
|
||||
labelStyle: null,
|
||||
labelModifier: null
|
||||
|
||||
};
|
||||
|
||||
render() {
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: table.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
|
@ -21,7 +17,7 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { Table, Button, FormControl, Label, Checkbox } from 'react-bootstrap';
|
||||
import { Table, Button, FormControl, FormLabel, FormCheck, Tooltip, OverlayTrigger } from 'react-bootstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Icon from './icon';
|
||||
|
||||
|
@ -32,7 +28,7 @@ class CustomTable extends Component {
|
|||
this.activeInput = null;
|
||||
|
||||
this.state = {
|
||||
rows: this.getRows(props),
|
||||
rows: CustomTable.getRows(props),
|
||||
editCell: [ -1, -1 ]
|
||||
};
|
||||
}
|
||||
|
@ -45,7 +41,7 @@ class CustomTable extends Component {
|
|||
this.setState({ editCell: [ column, row ]}); // x, y
|
||||
}
|
||||
|
||||
addCell(data, index, child) {
|
||||
static addCell(data, index, child) {
|
||||
// add data to cell
|
||||
let content = null;
|
||||
|
||||
|
@ -67,14 +63,14 @@ class CustomTable extends Component {
|
|||
|
||||
let cell = [];
|
||||
if (content != null) {
|
||||
content = content.toString();
|
||||
//content = content.toString();
|
||||
|
||||
// check if cell should be a link
|
||||
const linkKey = child.props.linkKey;
|
||||
if (linkKey && data[linkKey] != null) {
|
||||
cell.push(<Link to={child.props.link + data[linkKey]}>{content}</Link>);
|
||||
} else if (child.props.clickable) {
|
||||
cell.push(<Button bsStyle="link" onClick={() => child.props.onClick(index)}>{content}</Button>);
|
||||
cell.push(<Button variant="link" onClick={() => child.props.onClick(index)}>{content}</Button>);
|
||||
} else {
|
||||
cell.push(content);
|
||||
}
|
||||
|
@ -83,45 +79,63 @@ class CustomTable extends Component {
|
|||
// add label to content
|
||||
const labelKey = child.props.labelKey;
|
||||
if (labelKey && data[labelKey] != null) {
|
||||
var labelContent = data[labelKey];
|
||||
let labelContent = data[labelKey];
|
||||
|
||||
if (child.props.labelModifier) {
|
||||
labelContent = child.props.labelModifier(labelContent, data);
|
||||
}
|
||||
|
||||
cell.push(<span> <Label bsClass={child.props.labelStyle(data[labelKey], data)}>{labelContent.toString()}</Label></span>);
|
||||
let labelStyle = child.props.labelStyle(data[labelKey], data)
|
||||
|
||||
cell.push(<span>
|
||||
|
||||
<FormLabel column={false} className={labelStyle}>
|
||||
{labelContent}
|
||||
</FormLabel>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (child.props.dataIndex) {
|
||||
cell.push(index);
|
||||
}
|
||||
|
||||
// add buttons
|
||||
if (child.props.editButton) {
|
||||
cell.push(<Button bsClass='table-control-button' onClick={() => child.props.onEdit(index)} disabled={child.props.onEdit == null}><Icon icon='edit' /></Button>);
|
||||
let disable = (typeof data.managedexternally !== "undefined" && data.managedexternally);
|
||||
cell.push(<OverlayTrigger key={0} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"edit"}`}>{disable? "Externally managed ICs cannot be edited" : "edit"} </Tooltip>} >
|
||||
<Button variant='table-control-button' onClick={() => child.props.onEdit(index)} disabled={disable || child.props.onEdit == null}><Icon icon='edit' /></Button></OverlayTrigger>);
|
||||
}
|
||||
|
||||
if (child.props.deleteButton) {
|
||||
cell.push(<Button bsClass='table-control-button' onClick={() => child.props.onDelete(index)} disabled={child.props.onDelete == null}><Icon icon='trash' /></Button>);
|
||||
cell.push(<OverlayTrigger key={1} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"delete"}`}> Delete </Tooltip>} >
|
||||
<Button variant='table-control-button' onClick={() => child.props.onDelete(index)} disabled={child.props.onDelete == null}><Icon icon='trash' /></Button></OverlayTrigger>);
|
||||
}
|
||||
|
||||
if (child.props.checkbox) {
|
||||
const checkboxKey = this.props.checkboxKey;
|
||||
const checkboxKey = child.props.checkboxKey;
|
||||
|
||||
cell.push(<Checkbox className="table-control-checkbox" inline checked={checkboxKey ? data[checkboxKey] : null} onChange={e => child.props.onChecked(index, e)} />);
|
||||
cell.push(<FormCheck className="table-control-checkbox" inline checked={checkboxKey ? data[checkboxKey] : null} onChange={e => child.props.onChecked(index, e)} />);
|
||||
}
|
||||
|
||||
if (child.props.exportButton) {
|
||||
cell.push(<Button bsClass='table-control-button' onClick={() => child.props.onExport(index)} disabled={child.props.onExport == null}><Icon icon='download' /></Button>);
|
||||
cell.push(<OverlayTrigger key={2} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"export"}`}> Export </Tooltip>} >
|
||||
<Button variant='table-control-button' onClick={() => child.props.onExport(index)} disabled={child.props.onExport == null}><Icon icon='download' /></Button></OverlayTrigger>);
|
||||
}
|
||||
|
||||
if (child.props.duplicateButton) {
|
||||
cell.push(<OverlayTrigger key={3} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"duplicate"}`}> Duplicate </Tooltip>} >
|
||||
<Button variant='table-control-button' onClick={() => child.props.onDuplicate(index)} disabled={child.props.onDuplicate == null}><Icon icon='copy' /></Button></OverlayTrigger>);
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
} // addCell
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const rows = this.getRows(nextProps);
|
||||
static getDerivedStateFromProps(props, state){
|
||||
const rows = CustomTable.getRows(props);
|
||||
|
||||
this.setState({ rows });
|
||||
return { rows };
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
|
@ -141,7 +155,7 @@ class CustomTable extends Component {
|
|||
this.setState({ editCell: [ -1, -1 ] });
|
||||
}
|
||||
|
||||
getRows(props) {
|
||||
static getRows(props) {
|
||||
if (props.data == null) {
|
||||
return [];
|
||||
}
|
||||
|
@ -150,13 +164,13 @@ class CustomTable extends Component {
|
|||
// check if multiple columns
|
||||
if (Array.isArray(props.children) === false) {
|
||||
// table only has a single column
|
||||
return [ this.addCell(data, index, props.children) ];
|
||||
return [ CustomTable.addCell(data, index, props.children) ];
|
||||
}
|
||||
|
||||
const row = [];
|
||||
|
||||
for (let child of props.children) {
|
||||
row.push(this.addCell(data, index, child));
|
||||
row.push(CustomTable.addCell(data, index, child));
|
||||
}
|
||||
|
||||
return row;
|
||||
|
@ -200,7 +214,7 @@ class CustomTable extends Component {
|
|||
|
||||
return (<td key={cellIndex} tabIndex={tabIndex} onClick={ evtHdls.onCellClick } onFocus={ evtHdls.onCellFocus } onBlur={ evtHdls.onCellBlur }>
|
||||
{(this.state.editCell[0] === cellIndex && this.state.editCell[1] === rowIndex ) ? (
|
||||
<FormControl type="text" value={cell} onChange={(event) => children[cellIndex].props.onInlineChange(event, rowIndex, cellIndex)} inputRef={ref => { this.activeInput = ref; }} />
|
||||
<FormControl as='input' type={children[cellIndex].props.inputType} value={cell} onChange={(event) => children[cellIndex].props.onInlineChange(event, rowIndex, cellIndex)} ref={ref => { this.activeInput = ref; }} />
|
||||
) : (
|
||||
<span>
|
||||
{cell.map((element, elementIndex) => (
|
69
src/componentconfig/config-store.js
Normal file
69
src/componentconfig/config-store.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* 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 ArrayStore from '../common/array-store';
|
||||
import ConfigsDataManager from './configs-data-manager';
|
||||
|
||||
class ConfigStore extends ArrayStore {
|
||||
|
||||
constructor() {
|
||||
super('configs', ConfigsDataManager);
|
||||
}
|
||||
|
||||
reduce(state, action) {
|
||||
switch (action.type) {
|
||||
|
||||
case 'configs/loaded':
|
||||
return super.reduce(state, action);
|
||||
|
||||
case 'configs/start-add':
|
||||
// Check if this is a recursive component config import or not
|
||||
if (action.data.hasOwnProperty("outputMapping") || action.data.hasOwnProperty("inputMapping")) {
|
||||
// import
|
||||
let subObjects = []
|
||||
let outputMapping = {}
|
||||
let inputMapping = {}
|
||||
|
||||
if (action.data.hasOwnProperty("outputMapping")){
|
||||
outputMapping["signals"] = action.data.outputMapping
|
||||
subObjects.push(outputMapping)
|
||||
delete action.data.outputMapping; // remove outputMapping signals from config object
|
||||
}
|
||||
if (action.data.hasOwnProperty("inputMapping")){
|
||||
inputMapping["signals"] = action.data.inputMapping
|
||||
subObjects.push(inputMapping)
|
||||
delete action.data.inputMapping; // remove inputMapping signals from config object
|
||||
}
|
||||
|
||||
// action.data should now contain the config and no sub-objects
|
||||
// sub-objects are treated in add method of RestDataManager
|
||||
this.dataManager.add(action.data, action.token,action.param, subObjects);
|
||||
return state
|
||||
|
||||
} else {
|
||||
// no import
|
||||
return super.reduce(state, action);
|
||||
}
|
||||
|
||||
default:
|
||||
return super.reduce(state, action);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new ConfigStore();
|
60
src/componentconfig/configs-data-manager.js
Normal file
60
src/componentconfig/configs-data-manager.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* 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 RestDataManager from '../common/data-managers/rest-data-manager';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
|
||||
class ConfigDataManager extends RestDataManager {
|
||||
constructor() {
|
||||
super('config', '/configs');
|
||||
|
||||
this.onLoad = this.onConfigsLoad;
|
||||
}
|
||||
|
||||
onConfigsLoad(data, token) {
|
||||
if (!Array.isArray(data))
|
||||
data = [ data ];
|
||||
|
||||
for (let config of data) {
|
||||
|
||||
// prepare IC data
|
||||
AppDispatcher.dispatch({
|
||||
type: 'icData/prepare',
|
||||
inputLength: parseInt(config.inputLength, 10),
|
||||
outputLength: parseInt(config.outputLength, 10),
|
||||
id: config.icID
|
||||
});
|
||||
|
||||
// request in signals
|
||||
AppDispatcher.dispatch({
|
||||
type: 'signals/start-load',
|
||||
token: token,
|
||||
param: '?direction=in&configID=' + config.id,
|
||||
});
|
||||
|
||||
// request out signals
|
||||
AppDispatcher.dispatch({
|
||||
type: 'signals/start-load',
|
||||
token: token,
|
||||
param: '?direction=out&configID=' + config.id,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default new ConfigDataManager();
|
191
src/componentconfig/edit-config.js
Normal file
191
src/componentconfig/edit-config.js
Normal file
|
@ -0,0 +1,191 @@
|
|||
/**
|
||||
* 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 {FormGroup, FormControl, FormLabel} from 'react-bootstrap';
|
||||
import { Multiselect } from 'multiselect-react-dropdown'
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
import ParametersEditor from '../common/parameters-editor';
|
||||
|
||||
class EditConfigDialog extends React.Component {
|
||||
valid = false;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
name: '',
|
||||
icID: '',
|
||||
startParameters: {},
|
||||
selectedFiles: [] // list of selected files {name, id}, this is not the fileIDs list of the config!
|
||||
};
|
||||
}
|
||||
|
||||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (this.valid) {
|
||||
let data = this.props.config;
|
||||
if (this.state.name !== '' && this.props.config.name !== this.state.name) {
|
||||
data.name = this.state.name;
|
||||
}
|
||||
if (this.state.icID !== '' && this.props.config.icID !== parseInt(this.state.icID)) {
|
||||
data.icID = parseInt(this.state.icID, 10);
|
||||
}
|
||||
if(this.state.startParameters !== {} &&
|
||||
JSON.stringify(this.props.config.startParameters) !== JSON.stringify(this.state.startParameters)){
|
||||
data.startParameters = this.state.startParameters;
|
||||
}
|
||||
|
||||
let IDs = []
|
||||
for(let e of this.state.selectedFiles){
|
||||
IDs.push(e.id)
|
||||
}
|
||||
if(this.props.config.fileIDs !== null && this.props.config.fileIDs !== undefined) {
|
||||
if (JSON.stringify(IDs) !== JSON.stringify(this.props.config.fileIDs)) {
|
||||
data.fileIDs = IDs;
|
||||
}
|
||||
}
|
||||
else{
|
||||
data.fileIDs = IDs
|
||||
}
|
||||
|
||||
//forward modified config to callback function
|
||||
this.props.onClose(data)
|
||||
}
|
||||
} else {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
this.valid = this.isValid()
|
||||
}
|
||||
|
||||
handleParameterChange(data) {
|
||||
if (data) {
|
||||
this.setState({startParameters: data});
|
||||
}
|
||||
this.valid = this.isValid()
|
||||
}
|
||||
|
||||
onFileChange(selectedList, changedItem) {
|
||||
this.setState({
|
||||
selectedFiles: selectedList
|
||||
})
|
||||
this.valid = this.isValid()
|
||||
}
|
||||
|
||||
|
||||
isValid() {
|
||||
// input is valid if at least one element has changed from its initial value
|
||||
return this.state.name !== ''
|
||||
|| this.state.icID !== ''
|
||||
|| this.state.startParameters !== {}
|
||||
}
|
||||
|
||||
resetState() {
|
||||
|
||||
// determine list of selected files incl id and filename
|
||||
let selectedFiles = []
|
||||
if(this.props.config.fileIDs !== null && this.props.config.fileIDs !== undefined) {
|
||||
for (let selectedFileID of this.props.config.fileIDs) {
|
||||
for (let file of this.props.files) {
|
||||
if (file.id === selectedFileID) {
|
||||
selectedFiles.push({name: file.name, id: file.id})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
name: this.props.config.name,
|
||||
icID: this.props.config.icID,
|
||||
startParameters: this.props.config.startParameters,
|
||||
selectedFiles: selectedFiles,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const ICOptions = this.props.ics.map(s =>
|
||||
<option key={s.id} value={s.id}>{s.name}</option>
|
||||
);
|
||||
|
||||
let configFileOptions = [];
|
||||
for(let file of this.props.files) {
|
||||
configFileOptions.push(
|
||||
{name: file.name, id: file.id}
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
show={this.props.show}
|
||||
title="Edit Component Configuration"
|
||||
buttonTitle="Save"
|
||||
onClose={(c) => this.onClose(c)}
|
||||
onReset={() => this.resetState()}
|
||||
valid={this.valid}
|
||||
>
|
||||
<form>
|
||||
<FormGroup controlId="name">
|
||||
<FormLabel column={false}>Name</FormLabel>
|
||||
<FormControl
|
||||
type="text"
|
||||
placeholder={this.props.config.name}
|
||||
value={this.state.name}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
/>
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup controlId="icID">
|
||||
<FormLabel column={false}> Infrastructure Component </FormLabel>
|
||||
<FormControl
|
||||
as="select"
|
||||
placeholder='Select infrastructure component'
|
||||
value={this.state.icID}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
>
|
||||
{ICOptions}
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
|
||||
<Multiselect
|
||||
options={configFileOptions}
|
||||
showCheckbox={true}
|
||||
selectedValues={this.state.selectedFiles}
|
||||
onSelect={(selectedList, selectedItem) => this.onFileChange(selectedList, selectedItem)}
|
||||
onRemove={(selectedList, removedItem) => this.onFileChange(selectedList, removedItem)}
|
||||
displayValue={'name'}
|
||||
placeholder={'Select file(s)...'}
|
||||
/>
|
||||
|
||||
<FormGroup controlId='startParameters'>
|
||||
<FormLabel> Start Parameters </FormLabel>
|
||||
<ParametersEditor
|
||||
content={this.state.startParameters}
|
||||
onChange={(data) => this.handleParameterChange(data)}
|
||||
/>
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditConfigDialog;
|
129
src/componentconfig/import-config.js
Normal file
129
src/componentconfig/import-config.js
Normal file
|
@ -0,0 +1,129 @@
|
|||
/**
|
||||
* 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 { FormGroup, FormControl, FormLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
|
||||
class ImportConfigDialog extends React.Component {
|
||||
imported = false;
|
||||
valid = false;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
config: {},
|
||||
name: '',
|
||||
};
|
||||
}
|
||||
|
||||
onClose(canceled){
|
||||
if (canceled) {
|
||||
this.props.onClose();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onClose(this.state);
|
||||
}
|
||||
|
||||
resetState = () => {
|
||||
this.setState({
|
||||
config: {},
|
||||
name: ''
|
||||
});
|
||||
|
||||
this.imported = false;
|
||||
}
|
||||
|
||||
loadFile = event => {
|
||||
// get file
|
||||
const file = event.target.files[0];
|
||||
if (file.type.match('application/json') === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// create file reader
|
||||
let reader = new FileReader();
|
||||
let self = this;
|
||||
|
||||
reader.onload = event => {
|
||||
const config = JSON.parse(event.target.result);
|
||||
|
||||
self.imported = true;
|
||||
self.valid = true;
|
||||
this.setState({name: config.name, config: config });
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
handleChange(e, index) {
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
}
|
||||
|
||||
validateForm(target) {
|
||||
// check all controls
|
||||
let name = true;
|
||||
|
||||
if (this.state.name === '') {
|
||||
name = false;
|
||||
}
|
||||
this.valid = name;
|
||||
|
||||
// return state to control
|
||||
if (target === 'name'){
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dialog
|
||||
show={this.props.show}
|
||||
title="Import Component Configuration"
|
||||
buttonTitle="Import"
|
||||
onClose={(c) => this.onClose(c)}
|
||||
onReset={() => this.resetState()}
|
||||
valid={this.valid} >
|
||||
<form>
|
||||
<FormGroup controlId='file'>
|
||||
<FormLabel>Component Configuration File</FormLabel>
|
||||
<FormControl type='file' onChange={this.loadFile} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup controlId="name" >
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl
|
||||
readOnly={!this.imported}
|
||||
isValid={this.validateForm('name')}
|
||||
type="text"
|
||||
placeholder="Enter name"
|
||||
value={this.state.name}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
/>
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ImportConfigDialog;
|
|
@ -1,101 +0,0 @@
|
|||
/**
|
||||
* File: edit-project.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 07.03.2017
|
||||
*
|
||||
* 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 { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
|
||||
class EditProjectDialog extends React.Component {
|
||||
valid: true;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: '',
|
||||
simulation: '',
|
||||
_id: ''
|
||||
}
|
||||
}
|
||||
|
||||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (this.valid) {
|
||||
this.props.onClose(this.state);
|
||||
}
|
||||
} else {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
}
|
||||
|
||||
resetState() {
|
||||
this.setState({
|
||||
name: this.props.project.name,
|
||||
simulation: this.props.project.simulation,
|
||||
_id: this.props.project._id
|
||||
});
|
||||
}
|
||||
|
||||
validateForm(target) {
|
||||
// check all controls
|
||||
var name = true;
|
||||
|
||||
if (this.state.name === '') {
|
||||
name = false;
|
||||
}
|
||||
|
||||
this.valid = name;
|
||||
|
||||
// return state to control
|
||||
if (target === 'name') return name ? "success" : "error";
|
||||
|
||||
return "success";
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Edit Simulation" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="simulation">
|
||||
<ControlLabel>Simulation</ControlLabel>
|
||||
<FormControl componentClass="select" placeholder="Select simulation" value={this.state.simulation} onChange={(e) => this.handleChange(e)}>
|
||||
{this.props.simulations.map(simulation => (
|
||||
<option key={simulation._id} value={simulation._id}>{simulation.name}</option>
|
||||
))}
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditProjectDialog;
|
|
@ -1,103 +0,0 @@
|
|||
/**
|
||||
* File: new-simulation.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 04.03.2017
|
||||
*
|
||||
* 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 { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import ParametersEditor from '../parameters-editor';
|
||||
|
||||
class EditSimulationDialog extends React.Component {
|
||||
valid = true;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: '',
|
||||
_id: '',
|
||||
startParameters: {}
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
handleChange = event => {
|
||||
this.setState({ [event.target.id]: event.target.value });
|
||||
}
|
||||
|
||||
resetState = () => {
|
||||
this.setState({
|
||||
name: this.props.simulation.name,
|
||||
_id: this.props.simulation._id,
|
||||
startParameters: this.props.simulation.startParameters || {}
|
||||
});
|
||||
}
|
||||
|
||||
handleStartParametersChange = startParameters => {
|
||||
this.setState({ startParameters });
|
||||
}
|
||||
|
||||
validateForm(target) {
|
||||
let name = true;
|
||||
|
||||
if (this.state.name === '') {
|
||||
name = false;
|
||||
}
|
||||
|
||||
this.valid = name;
|
||||
|
||||
// return state to control
|
||||
if (target === 'name') return name ? 'success' : 'error';
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Dialog show={this.props.show} title='Edit Simulation' buttonTitle='Save' onClose={this.onClose} onReset={this.resetState} valid={true}>
|
||||
<form>
|
||||
<FormGroup controlId='name' validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormControl type='text' placeholder='Enter name' value={this.state.name} onChange={this.handleChange} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup controlId='startParameters'>
|
||||
<ControlLabel>Start Parameters</ControlLabel>
|
||||
|
||||
<ParametersEditor content={this.state.startParameters} onChange={this.handleStartParametersChange} />
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>;
|
||||
}
|
||||
}
|
||||
|
||||
export default EditSimulationDialog;
|
|
@ -1,96 +0,0 @@
|
|||
/**
|
||||
* File: new-simulator.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
*
|
||||
* 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 { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import ParametersEditor from '../parameters-editor';
|
||||
|
||||
class EditSimulatorDialog extends React.Component {
|
||||
valid = true;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: '',
|
||||
endpoint: ''
|
||||
};
|
||||
}
|
||||
|
||||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (this.valid) {
|
||||
let data = {};
|
||||
|
||||
if (this.state.name != null && this.state.name !== "" && this.state.name !== _.get(this.props.simulator, 'rawProperties.name')) {
|
||||
data.name = this.state.name;
|
||||
}
|
||||
|
||||
if (this.state.endpoint != null && this.state.endpoint !== "" && this.state.endpoint !== "http://" && this.state.endpoint !== _.get(this.props.simulator, 'rawProperties.endpoint')) {
|
||||
data.endpoint = this.state.endpoint;
|
||||
}
|
||||
|
||||
this.props.onClose(data);
|
||||
}
|
||||
} else {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
}
|
||||
|
||||
resetState() {
|
||||
this.setState({
|
||||
name: _.get(this.props.simulator, 'properties.name') || _.get(this.props.simulator, 'rawProperties.name'),
|
||||
endpoint: _.get(this.props.simulator, 'properties.endpoint') || _.get(this.props.simulator, 'rawProperties.endpoint')
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Edit Simulator" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="name">
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormControl type="text" placeholder={_.get(this.props.simulator, 'rawProperties.name')} value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="endpoint">
|
||||
<ControlLabel>Endpoint</ControlLabel>
|
||||
<FormControl type="text" placeholder={_.get(this.props.simulator, 'rawProperties.endpoint')} value={this.state.endpoint || 'http://' } onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId='properties'>
|
||||
<ControlLabel>Properties</ControlLabel>
|
||||
<ParametersEditor content={_.merge({}, this.props.simulator.rawProperties, this.props.simulator.properties)} disabled={true} />
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditSimulatorDialog;
|
|
@ -1,107 +0,0 @@
|
|||
/**
|
||||
* File: edit-user.js
|
||||
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
|
||||
* Date: 02.05.2017
|
||||
*
|
||||
* 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 { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
|
||||
class EditUserDialog extends React.Component {
|
||||
valid: true;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
username: '',
|
||||
mail: '',
|
||||
role: '',
|
||||
_id: ''
|
||||
}
|
||||
}
|
||||
|
||||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (this.valid) {
|
||||
this.props.onClose(this.state);
|
||||
}
|
||||
} else {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
}
|
||||
|
||||
resetState() {
|
||||
this.setState({
|
||||
username: this.props.user.username,
|
||||
mail: this.props.user.mail,
|
||||
role: this.props.user.role,
|
||||
_id: this.props.user._id
|
||||
});
|
||||
}
|
||||
|
||||
validateForm(target) {
|
||||
// check all controls
|
||||
var username = true;
|
||||
|
||||
if (this.state.username === '') {
|
||||
username = false;
|
||||
}
|
||||
|
||||
this.valid = username;
|
||||
|
||||
// return state to control
|
||||
if (target === 'username') return username ? "success" : "error";
|
||||
|
||||
return "success";
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Edit user" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="username" validationState={this.validateForm('username')}>
|
||||
<ControlLabel>Username</ControlLabel>
|
||||
<FormControl type="text" placeholder="Enter username" value={this.state.username} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="mail">
|
||||
<ControlLabel>E-mail</ControlLabel>
|
||||
<FormControl type="text" placeholder="Enter e-mail" value={this.state.mail} onChange={(e) => this.handleChange(e)} />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="role" validationState={this.validateForm('role')}>
|
||||
<ControlLabel>Role</ControlLabel>
|
||||
<FormControl componentClass="select" placeholder="Select 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>
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditUserDialog;
|
|
@ -1,83 +0,0 @@
|
|||
/**
|
||||
* File: edit-widget-color-control.js
|
||||
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
|
||||
* Date: 24.04.2017
|
||||
* Copyright: 2018, Institute for Automation of Complex Power Systems, EONERC
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { FormGroup, Col, Row, Radio, ControlLabel } from 'react-bootstrap';
|
||||
import classNames from 'classnames';
|
||||
import { scaleOrdinal, schemeCategory20 } from 'd3-scale';
|
||||
|
||||
class EditWidgetColorControl extends Component {
|
||||
|
||||
static get ColorPalette() {
|
||||
let colorCount = 0;
|
||||
const colors = [];
|
||||
const colorScale = scaleOrdinal(schemeCategory20);
|
||||
while (colorCount < 20) { colors.push(colorScale(colorCount)); colorCount++; }
|
||||
colors.unshift('#000', '#FFF'); // include black and white
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
widget: {}
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// Update state's widget with props
|
||||
this.setState({ widget: nextProps.widget });
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<FormGroup bsClass="color-control">
|
||||
<Row>
|
||||
<Col componentClass={ControlLabel} style={{whiteSpace: 'nowrap' }} sm={2}>
|
||||
{ this.props.label }
|
||||
</Col>
|
||||
<Col sm={10} bsClass='colors-column'>
|
||||
{
|
||||
EditWidgetColorControl.ColorPalette.map( (color, idx ) => {
|
||||
let colorStyle = {
|
||||
background: color,
|
||||
borderColor: color
|
||||
};
|
||||
|
||||
let checkedClass = classNames({
|
||||
'checked': idx === this.state.widget[this.props.controlId]
|
||||
});
|
||||
|
||||
return (<Radio key={idx} name={this.props.controlId} style={colorStyle} className={checkedClass} value={idx} inline onChange={(e) => this.props.handleChange({target: { id: this.props.controlId, value: idx}})} />)
|
||||
}
|
||||
)
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
</FormGroup> )
|
||||
}
|
||||
}
|
||||
|
||||
export default EditWidgetColorControl;
|
|
@ -1,133 +0,0 @@
|
|||
/**
|
||||
* File: edit-widget-color-zones-control.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 20.08.2017
|
||||
*
|
||||
* 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 { FormGroup, ControlLabel, Button } from 'react-bootstrap';
|
||||
|
||||
|
||||
import Icon from '../icon';
|
||||
import Table from '../table';
|
||||
import TableColumn from '../table-column';
|
||||
|
||||
class EditWidgetColorZonesControl extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
widget: {
|
||||
zones: []
|
||||
},
|
||||
selectedZones: []
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.setState({ widget: nextProps.widget });
|
||||
}
|
||||
|
||||
addZone = () => {
|
||||
// add row
|
||||
const widget = this.state.widget;
|
||||
widget.zones.push({ strokeStyle: 'ffffff', min: 0, max: 100 });
|
||||
|
||||
this.setState({ widget });
|
||||
|
||||
this.sendEvent(widget);
|
||||
}
|
||||
|
||||
removeZones = () => {
|
||||
// remove zones
|
||||
const widget = this.state.widget;
|
||||
|
||||
this.state.selectedZones.forEach(row => {
|
||||
widget.zones.splice(row, 1);
|
||||
});
|
||||
|
||||
this.setState({ selectedZones: [], widget });
|
||||
|
||||
this.sendEvent(widget);
|
||||
}
|
||||
|
||||
changeCell = (event, row, column) => {
|
||||
// change row
|
||||
const widget = this.state.widget;
|
||||
|
||||
if (column === 1) {
|
||||
widget.zones[row].strokeStyle = event.target.value;
|
||||
} else if (column === 2) {
|
||||
widget.zones[row].min = event.target.value;
|
||||
} else if (column === 3) {
|
||||
widget.zones[row].max = event.target.value;
|
||||
}
|
||||
|
||||
this.setState({ widget });
|
||||
|
||||
this.sendEvent(widget);
|
||||
}
|
||||
|
||||
sendEvent(widget) {
|
||||
// create event
|
||||
const event = {
|
||||
target: {
|
||||
id: 'zones',
|
||||
value: widget.zones
|
||||
}
|
||||
};
|
||||
|
||||
this.props.handleChange(event);
|
||||
}
|
||||
|
||||
checkedCell = (row, event) => {
|
||||
// update selected rows
|
||||
const selectedZones = this.state.selectedZones;
|
||||
|
||||
if (event.target.checked) {
|
||||
if (selectedZones.indexOf(row) === -1) {
|
||||
selectedZones.push(row);
|
||||
}
|
||||
} else {
|
||||
let index = selectedZones.indexOf(row);
|
||||
if (row > -1) {
|
||||
selectedZones.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ selectedZones });
|
||||
}
|
||||
|
||||
render() {
|
||||
return <FormGroup>
|
||||
<ControlLabel>Color zones</ControlLabel>
|
||||
|
||||
<Table data={this.state.widget.zones}>
|
||||
<TableColumn width="20" checkbox onChecked={this.checkedCell} />
|
||||
<TableColumn title="Color" dataKey="strokeStyle" inlineEditable onInlineChange={this.changeCell} />
|
||||
<TableColumn title="Minimum" dataKey="min" inlineEditable onInlineChange={this.changeCell} />
|
||||
<TableColumn title="Maximum" dataKey="max" inlineEditable onInlineChange={this.changeCell} />
|
||||
</Table>
|
||||
|
||||
<Button onClick={this.addZone} disabled={!this.props.widget.colorZones}><Icon icon="plus" /> Add</Button>
|
||||
<Button onClick={this.removeZones} disabled={!this.props.widget.colorZones}><Icon icon="minus" /> Remove</Button>
|
||||
</FormGroup>;
|
||||
}
|
||||
}
|
||||
|
||||
export default EditWidgetColorZonesControl;
|
|
@ -1,202 +0,0 @@
|
|||
/**
|
||||
* File: edit-widget-control-creator.js
|
||||
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
|
||||
* Date: 23.05.2017
|
||||
*
|
||||
* 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 EditWidgetTextControl from './edit-widget-text-control';
|
||||
import EditWidgetNumberControl from './edit-widget-number-control';
|
||||
import EditWidgetColorControl from './edit-widget-color-control';
|
||||
import EditWidgetTimeControl from './edit-widget-time-control';
|
||||
import EditImageWidgetControl from './edit-widget-image-control';
|
||||
import EditWidgetSimulationControl from './edit-widget-simulation-control';
|
||||
import EditWidgetSignalControl from './edit-widget-signal-control';
|
||||
import EditWidgetSignalsControl from './edit-widget-signals-control';
|
||||
import EditWidgetOrientation from './edit-widget-orientation';
|
||||
import EditWidgetAspectControl from './edit-widget-aspect-control';
|
||||
import EditWidgetTextSizeControl from './edit-widget-text-size-control';
|
||||
import EditWidgetCheckboxControl from './edit-widget-checkbox-control';
|
||||
import EditWidgetColorZonesControl from './edit-widget-color-zones-control';
|
||||
import EditWidgetMinMaxControl from './edit-widget-min-max-control';
|
||||
import EditWidgetHTMLContent from './edit-widget-html-content';
|
||||
import EditWidgetParametersControl from './edit-widget-parameters-control';
|
||||
|
||||
export default function createControls(widgetType = null, widget = null, sessionToken = null, files = null, validateForm, simulationModels, handleChange) {
|
||||
// Use a list to concatenate the controls according to the widget type
|
||||
var dialogControls = [];
|
||||
|
||||
switch(widgetType) {
|
||||
case 'CustomAction':
|
||||
dialogControls.push(
|
||||
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} validate={id => validateForm(id)} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetTextControl key={1} widget={widget} controlId={'icon'} label={'Icon'} placeholder={'Enter an awesome font icon name'} validate={id => validateForm(id)} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetSimulationControl key={2} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetParametersControl key={3} widget={widget} controlId={'actions'} label={'Actions'} handleChange={(e) => handleChange(e)} />
|
||||
)
|
||||
break;
|
||||
case 'Action':
|
||||
dialogControls.push(
|
||||
<EditWidgetSimulationControl key={0} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />
|
||||
)
|
||||
break;
|
||||
case 'Value':
|
||||
let valueBoundOnChange = (e) => {
|
||||
handleChange([e, {target: {id: 'signal', value: 0}}]);
|
||||
}
|
||||
dialogControls.push(
|
||||
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} validate={id => validateForm(id)} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetSimulationControl key={1} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => valueBoundOnChange(e)} />,
|
||||
<EditWidgetSignalControl key={2} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetTextSizeControl key={3} widget={widget} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetCheckboxControl key={4} widget={widget} controlId={'showUnit'} text="Show unit" handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'Lamp':
|
||||
let lampBoundOnChange = (e) => {
|
||||
handleChange([e, {target: {id: 'signal', value: 0}}]);
|
||||
}
|
||||
dialogControls.push(
|
||||
<EditWidgetSimulationControl key={0} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => lampBoundOnChange(e)} />,
|
||||
<EditWidgetSignalControl key={1} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetTextControl key={2} widget={widget} controlId={'threshold'} label={'Threshold'} placeholder={'0.5'} validate={id => validateForm(id)} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetColorControl key={3} widget={widget} controlId={'on_color'} label={'Color On'} validate={(id) => validateForm(id)} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetColorControl key={4} widget={widget} controlId={'off_color'} label={'Color Off'} validate={(id) => validateForm(id)} handleChange={(e) => handleChange(e)} />,
|
||||
);
|
||||
break;
|
||||
case 'Plot':
|
||||
let plotBoundOnChange = (e) => {
|
||||
handleChange([e, {target: {id: 'signals', value: []}}]);
|
||||
}
|
||||
dialogControls.push(
|
||||
<EditWidgetTimeControl key={0} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetSimulationControl key={1} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => plotBoundOnChange(e)} />,
|
||||
<EditWidgetSignalsControl key={2} controlId={'signals'} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetTextControl key={3} controlId={'ylabel'} label={'Y-Axis name'} placeholder={'Enter a name for the y-axis'} widget={widget} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetMinMaxControl key={4} widget={widget} controlId="y" handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'Table':
|
||||
dialogControls.push(
|
||||
<EditWidgetSimulationControl key={0} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetCheckboxControl key={1} widget={widget} controlId={'showUnit'} text="Show unit" handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'Image':
|
||||
// Restrict to only image file types (MIME)
|
||||
let imageControlFiles = files == null? [] : files.filter(file => file.type.includes('image'));
|
||||
dialogControls.push(
|
||||
<EditImageWidgetControl key={0} sessionToken={sessionToken} widget={widget} files={imageControlFiles} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetAspectControl key={1} widget={widget} handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'Gauge':
|
||||
let gaugeBoundOnChange = (e) => {
|
||||
handleChange([e, {target: {id: 'signal', value: ''}}]);
|
||||
}
|
||||
dialogControls.push(
|
||||
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} validate={id => validateForm(id)} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetSimulationControl key={1} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => gaugeBoundOnChange(e) } />,
|
||||
<EditWidgetSignalControl key={2} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetCheckboxControl key={3} widget={widget} controlId="colorZones" text="Show color zones" handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetColorZonesControl key={4} widget={widget} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetMinMaxControl key={5} widget={widget} controlId="value" handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'PlotTable':
|
||||
let plotTableBoundOnChange = (e) => {
|
||||
handleChange([e, {target: {id: 'preselectedSignals', value: []}}]);
|
||||
}
|
||||
dialogControls.push(
|
||||
<EditWidgetSimulationControl key={0} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => plotTableBoundOnChange(e)} />,
|
||||
<EditWidgetSignalsControl key={1} controlId={'preselectedSignals'} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetTextControl key={2} controlId={'ylabel'} label={'Y-Axis'} placeholder={'Enter a name for the Y-axis'} widget={widget} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetTimeControl key={3} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetMinMaxControl key={4} widget={widget} controlId="y" handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'Slider':
|
||||
dialogControls.push(
|
||||
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} handleChange={e => handleChange(e)} validate={id => validateForm(id)} />,
|
||||
<EditWidgetOrientation key={1} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetSimulationControl key={2} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetSignalControl key={3} widget={widget} input validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetCheckboxControl key={4} text={'Continous Update'} controlId={'continous_update'} widget={widget} input simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetCheckboxControl key={5} widget={widget} controlId={'showUnit'} text="Show unit" handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetMinMaxControl key={6} widget={widget} controlId={'range'} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetNumberControl key={7} widget={widget} controlId={'step'} label={'Step Size'} defaultValue={0.1} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetNumberControl key={8} widget={widget} controlId={'default_value'} label={'Default Value'} defaultValue={50} handleChange={(e) => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'Button':
|
||||
let buttonBoundOnChange = (e) => {
|
||||
handleChange([e, {target: {id: 'signal', value: 0}}]);
|
||||
}
|
||||
dialogControls.push(
|
||||
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} handleChange={e => handleChange(e)} validate={id => validateForm(id)} />,
|
||||
<EditWidgetSimulationControl key={1} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => buttonBoundOnChange(e)} />,
|
||||
<EditWidgetSignalControl key={2} widget={widget} controlId={'signal'} input validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetCheckboxControl key={3} widget={widget} controlId={'toggle'} text="Toggle" handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetNumberControl key={4} widget={widget} controlId={'on_value'} label={'On Value'} defaultValue={1} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetNumberControl key={5} widget={widget} controlId={'off_value'} label={'Off Value'} defaultValue={0} handleChange={(e) => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'Box':
|
||||
dialogControls.push(
|
||||
<EditWidgetColorControl key={0} widget={widget} controlId={'border_color'} label={'Border color'} validate={(id) => validateForm(id)} handleChange={(e) => handleChange(e)} />,
|
||||
<EditWidgetColorControl key={1} widget={widget} controlId={'background_color'} label={'Background color'} handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'Label':
|
||||
dialogControls.push(
|
||||
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} handleChange={e => handleChange(e)} validate={id => validateForm(id)} />,
|
||||
<EditWidgetTextSizeControl key={1} widget={widget} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetColorControl key={2} widget={widget} controlId={'fontColor'} label={'Text color'} handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'HTML':
|
||||
dialogControls.push(
|
||||
<EditWidgetHTMLContent key={0} widget={widget} placeholder='HTML Code' controlId='content' handleChange={e => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
case 'Topology':
|
||||
// Restrict to only xml files (MIME)
|
||||
let topologyControlFiles = files == null? [] : files.filter( file => file.type.includes('xml'));
|
||||
dialogControls.push(
|
||||
<EditImageWidgetControl key={0} sessionToken={sessionToken} widget={widget} files={topologyControlFiles} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
|
||||
case 'Input':
|
||||
let inputBoundOnChange = (e) => {
|
||||
handleChange([e, {target: {id: 'signal', value: 0}}]);
|
||||
}
|
||||
dialogControls.push(
|
||||
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} validate={id => validateForm(id)} handleChange={e => handleChange(e)} />,
|
||||
<EditWidgetSimulationControl key={1} widget={widget} validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => inputBoundOnChange(e)} />,
|
||||
<EditWidgetSignalControl key={2} widget={widget} controlId={'signal'} input validate={(id) => validateForm(id)} simulationModels={simulationModels} handleChange={(e) => handleChange(e)} />
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('Non-valid widget type: ' + widgetType);
|
||||
}
|
||||
|
||||
return dialogControls;
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
/**
|
||||
* File: edit-widget-html-content.js
|
||||
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
|
||||
* Date: 03.09.2017
|
||||
*
|
||||
* 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 { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
|
||||
class EditWidgetHTMLContent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
widget: {}
|
||||
};
|
||||
}
|
||||
|
||||
handleKeyIgnore(event){
|
||||
// This function prevents a keystroke from beeing handled by dialog.js
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// Update state's widget with props
|
||||
this.setState({ widget: nextProps.widget });
|
||||
}
|
||||
|
||||
render() {
|
||||
return <FormGroup controlId={this.props.controlId}>
|
||||
<ControlLabel>HTML Content</ControlLabel>
|
||||
<FormControl onKeyPress={this.handleKeyIgnore} componentClass="textarea" style={{ height: 200 }} placeholder={this.props.placeholder} value={this.state.widget[this.props.controlId] || ''} onChange={e => this.props.handleChange(e)} />
|
||||
</FormGroup>;
|
||||
}
|
||||
}
|
||||
|
||||
export default EditWidgetHTMLContent;
|
|
@ -1,101 +0,0 @@
|
|||
/**
|
||||
* File: edit-widget-image-control.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 04.03.2017
|
||||
*
|
||||
* 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 { FormGroup, FormControl, ControlLabel, Button, ProgressBar } from 'react-bootstrap';
|
||||
|
||||
import AppDispatcher from '../../app-dispatcher';
|
||||
|
||||
class EditImageWidgetControl extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
widget: {
|
||||
file: ''
|
||||
},
|
||||
fileList: null,
|
||||
progress: 0
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.setState({ widget: nextProps.widget });
|
||||
}
|
||||
|
||||
startFileUpload = () => {
|
||||
// get selected file
|
||||
let formData = new FormData();
|
||||
|
||||
for (let key in this.state.fileList) {
|
||||
if (this.state.fileList.hasOwnProperty(key) && this.state.fileList[key] instanceof File) {
|
||||
formData.append(key, this.state.fileList[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// upload files
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-upload',
|
||||
data: formData,
|
||||
token: this.props.sessionToken,
|
||||
progressCallback: this.uploadProgress,
|
||||
finishedCallback: this.clearProgress
|
||||
});
|
||||
}
|
||||
|
||||
uploadProgress = (e) => {
|
||||
this.setState({ progress: Math.round(e.percent) });
|
||||
}
|
||||
|
||||
clearProgress = () => {
|
||||
this.setState({ progress: 0 });
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<FormGroup controlId="file">
|
||||
<ControlLabel>Image</ControlLabel>
|
||||
<FormControl componentClass="select" value={this.state.widget.file} onChange={(e) => this.props.handleChange(e)}>
|
||||
{this.props.files.length === 0 ? (
|
||||
<option disabled value style={{ display: 'none' }}>No images found, please upload one first.</option>
|
||||
) : (
|
||||
this.props.files.reduce((entries, file, index) => {
|
||||
entries.push(<option key={++index} value={file._id}>{file.name}</option>);
|
||||
return entries;
|
||||
}, [
|
||||
<option key={0} value=''>Please select one image</option>
|
||||
])
|
||||
)}
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup controlId="upload">
|
||||
<ControlLabel>Upload</ControlLabel>
|
||||
<FormControl type="file" onChange={(e) => this.setState({ fileList: e.target.files }) } />
|
||||
</FormGroup>
|
||||
|
||||
<ProgressBar striped active now={this.state.progress} label={`${this.state.progress}%`} />
|
||||
<Button bsSize="small" onClick={this.startFileUpload}>Upload</Button>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default EditImageWidgetControl;
|
|
@ -1,64 +0,0 @@
|
|||
/**
|
||||
* File: edit-widget-min-max-control.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 30.08.2017
|
||||
*
|
||||
* 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 { FormGroup, FormControl, ControlLabel, Checkbox, Table } from 'react-bootstrap';
|
||||
|
||||
class EditWidgetMinMaxControl extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const widget = {};
|
||||
widget[props.controlID + "UseMinMax"] = false;
|
||||
widget[props.controlId + "Min"] = 0;
|
||||
widget[props.controlId + "Max"] = 100;
|
||||
|
||||
this.state = {
|
||||
widget
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.setState({ widget: nextProps.widget });
|
||||
}
|
||||
|
||||
render() {
|
||||
return <FormGroup>
|
||||
<ControlLabel>{this.props.label}</ControlLabel>
|
||||
<Checkbox id={this.props.controlId + "UseMinMax"} checked={this.state.widget[this.props.controlId + "UseMinMax"] || ''} onChange={e => this.props.handleChange(e)}>Enable min-max</Checkbox>
|
||||
|
||||
<Table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
Min: <FormControl type="number" step="any" id={this.props.controlId + "Min"} disabled={!this.state.widget[this.props.controlId + "UseMinMax"]} placeholder="Minimum value" value={this.state.widget[this.props.controlId + 'Min']} onChange={e => this.props.handleChange(e)} />
|
||||
</td>
|
||||
<td>
|
||||
Max: <FormControl type="number" step="any" id={this.props.controlId + "Max"} disabled={!this.state.widget[this.props.controlId + "UseMinMax"]} placeholder="Maximum value" value={this.state.widget[this.props.controlId + 'Max']} onChange={e => this.props.handleChange(e)} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
</FormGroup>;
|
||||
}
|
||||
}
|
||||
|
||||
export default EditWidgetMinMaxControl;
|
|
@ -1,83 +0,0 @@
|
|||
/**
|
||||
* File: edit-widget-signals-control.js
|
||||
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
|
||||
* Date: 03.04.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
**********************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { FormGroup, Checkbox, ControlLabel, FormControl } from 'react-bootstrap';
|
||||
|
||||
class EditWidgetSignalsControl extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
widget: {
|
||||
simulator: {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// Update state's widget with props
|
||||
this.setState({ widget: nextProps.widget });
|
||||
}
|
||||
|
||||
handleSignalChange(checked, index) {
|
||||
var signals = this.state.widget[this.props.controlId];
|
||||
var new_signals;
|
||||
|
||||
if (checked) {
|
||||
// add signal
|
||||
new_signals = signals.concat(index);
|
||||
} else {
|
||||
// remove signal
|
||||
new_signals = signals.filter( (idx) => idx !== index );
|
||||
}
|
||||
|
||||
this.props.handleChange({ target: { id: this.props.controlId, value: new_signals } });
|
||||
}
|
||||
|
||||
render() {
|
||||
const simulationModel = this.props.simulationModels.find(m => m._id === this.state.widget.simulationModel);
|
||||
|
||||
let signalsToRender = [];
|
||||
|
||||
if (simulationModel != null) {
|
||||
// If simulation model update the signals to render
|
||||
signalsToRender = simulationModel ? simulationModel.outputMapping : [];
|
||||
}
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
<ControlLabel>Signals</ControlLabel>
|
||||
{
|
||||
signalsToRender.length === 0 || !this.state.widget.hasOwnProperty(this.props.controlId)? (
|
||||
<FormControl.Static>No signals available.</FormControl.Static>
|
||||
) : (
|
||||
signalsToRender.map((signal, index) => (
|
||||
<Checkbox key={index} checked={this.state.widget[this.props.controlId].indexOf(index) !== -1} onChange={(e) => this.handleSignalChange(e.target.checked, index)}>{signal.name}</Checkbox>
|
||||
))
|
||||
)
|
||||
}
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditWidgetSignalsControl;
|
|
@ -1,60 +0,0 @@
|
|||
/**
|
||||
* File: edit-widget-simulation-control.js
|
||||
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
|
||||
* Date: 03.04.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
**********************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
|
||||
class EditWidgetSimulationControl extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
widget: {
|
||||
simulationModel: ''
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// Update state's widget with props
|
||||
this.setState({ widget: nextProps.widget });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FormGroup controlId="simulationModel">
|
||||
<ControlLabel>Simulation Model</ControlLabel>
|
||||
<FormControl componentClass="select" placeholder="Select simulation model" value={this.state.widget.simulationModel || '' } onChange={(e) => this.props.handleChange(e)}>
|
||||
{
|
||||
this.props.simulationModels.length === 0 ? (
|
||||
<option disabled value style={{ display: 'none' }}> No simulation models available. </option>
|
||||
) : (
|
||||
this.props.simulationModels.map((model, index) => (
|
||||
<option key={index} value={model._id}>{model.name}</option>
|
||||
)))
|
||||
}
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditWidgetSimulationControl;
|
|
@ -1,146 +0,0 @@
|
|||
/**
|
||||
* File: edit-widget.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 08.03.2017
|
||||
*
|
||||
* 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 { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
|
||||
import createControls from './edit-widget-control-creator';
|
||||
|
||||
class EditWidgetDialog extends React.Component {
|
||||
valid = true;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
temporal: {
|
||||
name: '',
|
||||
simulationModel: '',
|
||||
signal: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (this.valid) {
|
||||
this.props.onClose(this.state.temporal);
|
||||
}
|
||||
} else {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
assignAspectRatio(changeObject, fileId) {
|
||||
// get aspect ratio of file
|
||||
const file = this.props.files.find(element => element._id === fileId);
|
||||
|
||||
// scale width to match aspect
|
||||
const aspectRatio = file.dimensions.width / file.dimensions.height;
|
||||
changeObject.width = this.state.temporal.height * aspectRatio;
|
||||
|
||||
return changeObject;
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
if (e.constructor === Array) {
|
||||
// Every property in the array will be updated
|
||||
let changes = e.reduce( (changesObject, event) => {
|
||||
changesObject[event.target.id] = event.target.value;
|
||||
|
||||
return changesObject;
|
||||
}, {});
|
||||
|
||||
this.setState({ temporal: Object.assign({}, this.state.temporal, changes ) });
|
||||
} else {
|
||||
let changeObject = {};
|
||||
if (e.target.id === 'lockAspect') {
|
||||
changeObject[e.target.id] = e.target.checked;
|
||||
|
||||
// correct image aspect if turned on
|
||||
if (e.target.checked) {
|
||||
changeObject = this.assignAspectRatio(changeObject, this.state.temporal.file);
|
||||
}
|
||||
} else if (e.target.id === 'file') {
|
||||
changeObject[e.target.id] = e.target.value;
|
||||
|
||||
// get file and update size (if it's an image)
|
||||
if ('lockAspect' in this.state.temporal && this.state.temporal.lockAspect) {
|
||||
changeObject = this.assignAspectRatio(changeObject, e.target.value);
|
||||
}
|
||||
} else if (e.target.type === 'checkbox') {
|
||||
changeObject[e.target.id] = e.target.checked;
|
||||
} else if (e.target.type === 'number') {
|
||||
changeObject[e.target.id] = Number(e.target.value);
|
||||
} else {
|
||||
changeObject[e.target.id] = e.target.value;
|
||||
}
|
||||
|
||||
this.setState({ temporal: Object.assign({}, this.state.temporal, changeObject ) });
|
||||
}
|
||||
}
|
||||
|
||||
resetState() {
|
||||
var widget_data = Object.assign({}, this.props.widget);
|
||||
this.setState({ temporal: widget_data });
|
||||
}
|
||||
|
||||
validateForm(target) {
|
||||
// check all controls
|
||||
var name = true;
|
||||
|
||||
if (this.state.name === '') {
|
||||
name = false;
|
||||
}
|
||||
|
||||
//this.valid = name;
|
||||
this.valid = true;
|
||||
|
||||
// return state to control
|
||||
if (target === 'name') return name ? "success" : "error";
|
||||
}
|
||||
|
||||
render() {
|
||||
let controls = null;
|
||||
if (this.props.widget) {
|
||||
controls = createControls(
|
||||
this.props.widget.type,
|
||||
this.state.temporal,
|
||||
this.props.sessionToken,
|
||||
this.props.files,
|
||||
(id) => this.validateForm(id),
|
||||
this.props.simulationModels,
|
||||
(e) => this.handleChange(e));
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Edit Widget" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form encType='multipart/form-data'>
|
||||
{ controls || '' }
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditWidgetDialog;
|
|
@ -1,112 +0,0 @@
|
|||
/**
|
||||
* File: import-simulation-model.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 03.09.2017
|
||||
*
|
||||
* 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 { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Dialog from './dialog';
|
||||
|
||||
class ImportSimulationModelDialog extends React.Component {
|
||||
imported = false;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
model: {}
|
||||
};
|
||||
}
|
||||
|
||||
onClose = canceled => {
|
||||
if (canceled) {
|
||||
this.props.onClose();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onClose(this.state.model);
|
||||
}
|
||||
|
||||
resetState = () => {
|
||||
this.setState({
|
||||
model: {}
|
||||
});
|
||||
|
||||
this.imported = false;
|
||||
}
|
||||
|
||||
loadFile = event => {
|
||||
// get file
|
||||
const file = event.target.files[0];
|
||||
if (file.type.match('application/json') === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// create file reader
|
||||
const reader = new FileReader();
|
||||
const self = this;
|
||||
|
||||
reader.onload = event => {
|
||||
const model = JSON.parse(event.target.result);
|
||||
|
||||
model.simulator = this.props.simulators.length > 0 ? this.props.simulators[0]._id : null;
|
||||
|
||||
self.imported = true;
|
||||
|
||||
this.setState({ model });
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
handleSimulatorChange = event => {
|
||||
const model = this.state.model;
|
||||
|
||||
model.simulator = event.target.value;
|
||||
|
||||
this.setState({ model });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Import Simulation Model" buttonTitle="Import" onClose={this.onClose} onReset={this.resetState} valid={this.imported}>
|
||||
<form>
|
||||
<FormGroup controlId='file'>
|
||||
<ControlLabel>Simulation Model File</ControlLabel>
|
||||
<FormControl type='file' onChange={this.loadFile} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup controlId='simulator'>
|
||||
<ControlLabel>Simulator</ControlLabel>
|
||||
<FormControl disabled={this.imported === false} componentClass='select' placeholder='Select simulator' value={this.state.model.simulator} onChange={this.handleSimulatorChange}>
|
||||
{this.props.simulators.map(simulator => (
|
||||
<option key={simulator._id} value={simulator._id}>{_.get(simulator, 'properties.name') || _.get(simulator, 'rawProperties.name')}</option>
|
||||
))}
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ImportSimulationModelDialog;
|
|
@ -1,155 +0,0 @@
|
|||
/**
|
||||
* File: import-simulation.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 03.09.2017
|
||||
*
|
||||
* 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 { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import ParametersEditor from '../parameters-editor';
|
||||
|
||||
class ImportSimulationDialog extends React.Component {
|
||||
valid = false;
|
||||
imported = false;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: '',
|
||||
models: [],
|
||||
startParameters: {}
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(e, index) {
|
||||
if (e.target.id === 'simulator') {
|
||||
const models = this.state.models;
|
||||
models[index].simulator = JSON.parse(e.target.value);
|
||||
|
||||
this.setState({ models });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
}
|
||||
|
||||
resetState = () => {
|
||||
this.setState({ name: '', models: [], startParameters: {} });
|
||||
|
||||
this.imported = false;
|
||||
}
|
||||
|
||||
loadFile = event => {
|
||||
const file = event.target.files[0];
|
||||
|
||||
if (!file.type.match('application/json')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// create file reader
|
||||
const reader = new FileReader();
|
||||
const self = this;
|
||||
|
||||
reader.onload = onloadEvent => {
|
||||
const simulation = JSON.parse(onloadEvent.target.result);
|
||||
|
||||
// simulation.models.forEach(model => {
|
||||
// model.simulator = {
|
||||
// node: self.props.nodes[0]._id,
|
||||
// simulator: 0
|
||||
// };
|
||||
// });
|
||||
|
||||
self.imported = true;
|
||||
self.valid = true;
|
||||
self.setState({ name: simulation.name, models: simulation.models, startParameters: simulation.startParameters });
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
validateForm(target) {
|
||||
// check all controls
|
||||
let name = true;
|
||||
|
||||
if (this.state.name === '') {
|
||||
name = false;
|
||||
}
|
||||
|
||||
this.valid = name;
|
||||
|
||||
// return state to control
|
||||
if (target === 'name') return name ? "success" : "error";
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Dialog show={this.props.show} title="Import Simulation" buttonTitle="Import" onClose={this.onClose} onReset={this.resetState} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="file">
|
||||
<ControlLabel>Simulation File</ControlLabel>
|
||||
<FormControl type="file" onChange={this.loadFile} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormControl readOnly={this.imported === false} type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<ControlLabel>Start Parameters</ControlLabel>
|
||||
|
||||
<ParametersEditor content={this.state.startParameters} onChange={this.handleStartParametersChange} disabled={this.imported === false} />
|
||||
</FormGroup>
|
||||
|
||||
{/* {this.state.models.map((model, index) => (
|
||||
<FormGroup controlId="simulator" key={index}>
|
||||
<ControlLabel>{model.name} - Simulator</ControlLabel>
|
||||
<FormControl componentClass="select" placeholder="Select simulator" value={JSON.stringify({ node: model.simulator.node, simulator: model.simulator.simulator})} onChange={(e) => this.handleChange(e, index)}>
|
||||
{this.props.nodes.map(node => (
|
||||
node.simulators.map((simulator, index) => (
|
||||
<option key={node._id + index} value={JSON.stringify({ node: node._id, simulator: index })}>{node.name}/{simulator.name}</option>
|
||||
))
|
||||
))}
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
))} */}
|
||||
</form>
|
||||
</Dialog>;
|
||||
}
|
||||
}
|
||||
|
||||
export default ImportSimulationDialog;
|
|
@ -1,103 +0,0 @@
|
|||
/**
|
||||
* File: new-project.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 07.03.2017
|
||||
*
|
||||
* 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 { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
|
||||
class NewProjectDialog extends React.Component {
|
||||
valid: false;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: '',
|
||||
simulation: ''
|
||||
};
|
||||
}
|
||||
|
||||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (this.valid) {
|
||||
this.props.onClose(this.state);
|
||||
}
|
||||
} else {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
}
|
||||
|
||||
resetState() {
|
||||
this.setState({
|
||||
name: '',
|
||||
simulation: this.props.simulations[0] != null ? this.props.simulations[0]._id : ''
|
||||
});
|
||||
}
|
||||
|
||||
validateForm(target) {
|
||||
// check all controls
|
||||
var name = true;
|
||||
var simulation = true;
|
||||
|
||||
if (this.state.name === '') {
|
||||
name = false;
|
||||
}
|
||||
|
||||
if (this.state.simulation === '') {
|
||||
simulation = false;
|
||||
}
|
||||
|
||||
this.valid = name && simulation;
|
||||
|
||||
// return state to control
|
||||
if (target === 'name') return name ? "success" : "error";
|
||||
else if (target === 'simulation') return simulation ? "success" : "error";
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="New Project" buttonTitle="Add" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="simulation" validationState={this.validateForm('simulation')}>
|
||||
<ControlLabel>Simulation</ControlLabel>
|
||||
<FormControl componentClass="select" placeholder="Select simulation" value={this.state.simulation} onChange={(e) => this.handleChange(e)}>
|
||||
{this.props.simulations.map(simulation => (
|
||||
<option key={simulation._id} value={simulation._id}>{simulation.name}</option>
|
||||
))}
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NewProjectDialog;
|
|
@ -1,99 +0,0 @@
|
|||
/**
|
||||
* File: new-simulation.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 04.03.2017
|
||||
*
|
||||
* 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 { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import ParametersEditor from '../parameters-editor';
|
||||
|
||||
class NewSimulationDialog extends React.Component {
|
||||
valid = false;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: '',
|
||||
startParameters: {}
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
handleChange = event => {
|
||||
this.setState({ [event.target.id]: event.target.value });
|
||||
}
|
||||
|
||||
resetState = () => {
|
||||
this.setState({ name: '', startParameters: {} });
|
||||
}
|
||||
|
||||
handleStartParametersChange = startParameters => {
|
||||
this.setState({ startParameters });
|
||||
}
|
||||
|
||||
validateForm(target) {
|
||||
// check all controls
|
||||
let name = true;
|
||||
|
||||
if (this.state.name === '') {
|
||||
name = false;
|
||||
}
|
||||
|
||||
this.valid = name;
|
||||
|
||||
// return state to control
|
||||
if (target === 'name') return name ? "success" : "error";
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Dialog show={this.props.show} title="New Simulation" buttonTitle="Add" onClose={this.onClose} onReset={this.resetState} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={this.handleChange} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<ControlLabel>Start Parameters</ControlLabel>
|
||||
|
||||
<ParametersEditor content={this.state.startParameters} onChange={this.handleStartParametersChange} />
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>;
|
||||
}
|
||||
}
|
||||
|
||||
export default NewSimulationDialog;
|
|
@ -1,122 +0,0 @@
|
|||
/**
|
||||
* File: new-simulator.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
*
|
||||
* 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 { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
|
||||
class NewSimulatorDialog extends React.Component {
|
||||
valid = false;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: '',
|
||||
endpoint: '',
|
||||
uuid: ''
|
||||
};
|
||||
}
|
||||
|
||||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (this.valid) {
|
||||
const data = {
|
||||
properties: {
|
||||
name: this.state.name
|
||||
},
|
||||
uuid: this.state.uuid
|
||||
};
|
||||
|
||||
if (this.state.endpoint != null && this.state.endpoint !== "" && this.state.endpoint !== 'http://') {
|
||||
data.properties.endpoint = this.state.endpoint;
|
||||
}
|
||||
|
||||
this.props.onClose(data);
|
||||
}
|
||||
} else {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
}
|
||||
|
||||
resetState() {
|
||||
this.setState({ name: '', endpoint: 'http://', uuid: this.uuidv4()});
|
||||
}
|
||||
|
||||
validateForm(target) {
|
||||
// check all controls
|
||||
let name = true;
|
||||
let uuid = true;
|
||||
|
||||
if (this.state.name === '') {
|
||||
name = false;
|
||||
}
|
||||
|
||||
if (this.state.uuid === '') {
|
||||
uuid = false;
|
||||
}
|
||||
|
||||
this.valid = name && uuid;
|
||||
|
||||
// return state to control
|
||||
if (target === 'name') return name ? "success" : "error";
|
||||
if (target === 'uuid') return uuid ? "success" : "error";
|
||||
}
|
||||
|
||||
uuidv4() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
// eslint-disable-next-line
|
||||
var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="New Simulator" buttonTitle="Add" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="endpoint">
|
||||
<ControlLabel>Endpoint</ControlLabel>
|
||||
<FormControl type="text" placeholder="Enter endpoint" value={this.state.endpoint} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="uuid" validationState={this.validateForm('uuid')}>
|
||||
<ControlLabel>UUID</ControlLabel>
|
||||
<FormControl type="text" placeholder="Enter uuid" value={this.state.uuid} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NewSimulatorDialog;
|
|
@ -1,92 +0,0 @@
|
|||
/**
|
||||
* File: home.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
*
|
||||
* 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 { Link } from 'react-router-dom';
|
||||
|
||||
import RestAPI from '../api/rest-api';
|
||||
|
||||
import config from '../config';
|
||||
|
||||
class Home extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
getCounts(type) {
|
||||
if (this.state.hasOwnProperty('counts'))
|
||||
return this.state.counts[type];
|
||||
else
|
||||
return '?';
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
RestAPI.get('/api/v1/counts').then(response => {
|
||||
this.setState({ counts: response });
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="home-container">
|
||||
<img style={{height: 120, float: 'right'}} src={require('../img/villas_web.svg')} alt="Logo VILLASweb" />
|
||||
<h1>Home</h1>
|
||||
<p>
|
||||
Welcome to <b>{config.instance}</b>!<br />
|
||||
VILLASweb is a frontend for distributed real-time simulation hosted by <a href={"mailto:" + config.admin.mail}>{config.admin.name}</a>.
|
||||
</p>
|
||||
<p>
|
||||
This instance is hosting <Link to="/projects" title="Projects">{this.getCounts('projects')} projects</Link> consisting of <Link to="/simulators" title="Simulators">{this.getCounts('simulators')} simulators</Link>, {this.getCounts('visualizations')} visualizations and <Link to="/simulations" title="Simulations">{this.getCounts('simulations')} simulations</Link>.
|
||||
A total of <Link to="/users" title="Users">{this.getCounts('users')} users</Link> are registered.<br />
|
||||
</p>
|
||||
<h3>Credits</h3>
|
||||
<p>VILLASweb is developed by the <a href="http://acs.eonerc.rwth-aachen.de">Institute for Automation of Complex Power Systems</a> at the <a href="https;//www.rwth-aachen.de">RWTH Aachen University</a>.</p>
|
||||
<ul>
|
||||
<li><a href="mailto:mgrigull@eonerc.rwth-aachen.de">Markus Grigull</a></li>
|
||||
<li><a href="mailto:stvogel@eonerc.rwth-aachen.de">Steffen Vogel</a></li>
|
||||
<li><a href="mailto:mstevic@eonerc.rwth-aachen.de">Marija Stevic</a></li>
|
||||
</ul>
|
||||
<h3>Links</h3>
|
||||
<ul>
|
||||
<li><a href="http://fein-aachen.org/projects/villas-framework/">Project Page</a></li>
|
||||
<li><a href="https://villas.fein-aachen.org/doc/web.html">Documentation</a></li>
|
||||
<li><a href="https://git.rwth-aachen.de/VILLASframework/VILLASweb">Source Code</a></li>
|
||||
</ul>
|
||||
<h3>Funding</h3>
|
||||
<p>The development of <a href="http://fein-aachen.org/projects/villas-framework/">VILLASframework</a> projects have received funding from</p>
|
||||
<ul>
|
||||
<li><a href="http://www.re-serve.eu">RESERVE</a> a European Union’s Horizon 2020 research and innovation programme under grant agreement No 727481</li>
|
||||
<li><a href="http://www.jara.org/en/research/energy">JARA-ENERGY</a>. Jülich-Aachen Research Alliance (JARA) is an initiative of RWTH Aachen University and Forschungszentrum Jülich.</li>
|
||||
</ul>
|
||||
<img height={100} src={require('../img/european_commission.svg')} alt="Logo EU" />
|
||||
<img height={70} src={require('../img/reserve.svg')} alt="Logo EU" />
|
||||
<img height={60} src={require('../img/eonerc_rwth.svg')} alt="Logo ACS" />
|
||||
{
|
||||
//<img height={70} src={require('../img/jara.svg')} alt="Logo JARA" />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Home;
|
|
@ -1,131 +0,0 @@
|
|||
/**
|
||||
* File: signalMapping.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 10.08.2018
|
||||
*
|
||||
* 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 PropTypes from 'prop-types';
|
||||
import { FormGroup, FormControl, ControlLabel, HelpBlock } from 'react-bootstrap';
|
||||
import validator from 'validator';
|
||||
|
||||
import Table from './table';
|
||||
import TableColumn from './table-column';
|
||||
|
||||
class SignalMapping extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
var length = props.length;
|
||||
if (length === undefined)
|
||||
length = 1;
|
||||
|
||||
this.state = {
|
||||
length: length,
|
||||
signals: props.signals
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.length === this.state.length && nextProps.signals === this.state.signals) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ length: nextProps.length, signals: nextProps.signals });
|
||||
}
|
||||
|
||||
validateLength(){
|
||||
const valid = validator.isInt(this.state.length + '', { min: 1, max: 100 });
|
||||
|
||||
return valid ? 'success' : 'error';
|
||||
}
|
||||
|
||||
handleLengthChange = event => {
|
||||
const length = event.target.value;
|
||||
|
||||
// update signals to represent length
|
||||
const signals = this.state.signals;
|
||||
|
||||
if (this.state.length < length) {
|
||||
while (signals.length < length) {
|
||||
signals.push({ name: 'Signal', type: 'Type' });
|
||||
}
|
||||
} else {
|
||||
signals.splice(length, signals.length - length);
|
||||
}
|
||||
|
||||
// save updated state
|
||||
this.setState({ length, signals });
|
||||
|
||||
if (this.props.onChange != null) {
|
||||
this.props.onChange(length, signals);
|
||||
}
|
||||
}
|
||||
|
||||
handleMappingChange = (event, row, column) => {
|
||||
const signals = this.state.signals;
|
||||
|
||||
const length = this.state.length;
|
||||
|
||||
if (column === 1) {
|
||||
signals[row].name = event.target.value;
|
||||
} else if (column === 2) {
|
||||
signals[row].type = event.target.value;
|
||||
}
|
||||
|
||||
this.setState({ length, signals });
|
||||
|
||||
if (this.props.onChange != null) {
|
||||
this.props.onChange(this.state.length, signals);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>
|
||||
<FormGroup validationState={this.validateLength()}>
|
||||
<ControlLabel>{this.props.name} Length</ControlLabel>
|
||||
<FormControl name='length' type='number' placeholder='Enter length' defaultValue={this.state.length} min='1' onBlur={this.handleLengthChange} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<ControlLabel>{this.props.name} Mapping</ControlLabel>
|
||||
<HelpBlock>Click <i>name</i> or <i>type</i> cell to edit</HelpBlock>
|
||||
<Table data={this.props.signals}>
|
||||
<TableColumn title='ID' width='60' dataIndex />
|
||||
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={this.handleMappingChange} />
|
||||
<TableColumn title='Type' dataKey='type' inlineEditable onInlineChange={this.handleMappingChange} />
|
||||
</Table>
|
||||
</FormGroup>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
SignalMapping.propTypes = {
|
||||
name: PropTypes.string,
|
||||
length: PropTypes.number,
|
||||
signals: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired
|
||||
})
|
||||
),
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
export default SignalMapping;
|
|
@ -1,68 +0,0 @@
|
|||
/**
|
||||
* File: simulator-actionm.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 12.04.2018
|
||||
*
|
||||
* 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 { Button, DropdownButton, MenuItem } from 'react-bootstrap';
|
||||
|
||||
class SimulatorAction extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selectedAction: null
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.state.selectedAction == null) {
|
||||
if (nextProps.actions != null && nextProps.actions.length > 0) {
|
||||
this.setState({ selectedAction: nextProps.actions[0] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setAction = id => {
|
||||
// search action
|
||||
for (let action of this.props.actions) {
|
||||
if (action.id === id) {
|
||||
this.setState({ selectedAction: action });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const actionList = this.props.actions.map(action => (
|
||||
<MenuItem key={action.id} eventKey={action.id} active={this.state.selectedAction === action.id}>
|
||||
{action.title}
|
||||
</MenuItem>
|
||||
));
|
||||
|
||||
return <div>
|
||||
<DropdownButton title={this.state.selectedAction != null ? this.state.selectedAction.title : ''} id="action-dropdown" onSelect={this.setAction}>
|
||||
{actionList}
|
||||
</DropdownButton>
|
||||
|
||||
<Button style={{ marginLeft: '5px' }} disabled={this.props.runDisabled} onClick={() => this.props.runAction(this.state.selectedAction)}>Run</Button>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default SimulatorAction;
|
|
@ -1,43 +0,0 @@
|
|||
/**
|
||||
* File: plot-legend.js
|
||||
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
|
||||
* Date: 10.04.2017
|
||||
* Copyright: 2018, Institute for Automation of Complex Power Systems, EONERC
|
||||
*
|
||||
* 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 { scaleOrdinal, schemeCategory10 } from 'd3-scale';
|
||||
|
||||
class PlotLegend extends React.Component {
|
||||
render() {
|
||||
const colorScale = scaleOrdinal(schemeCategory10);
|
||||
|
||||
return <div className="plot-legend">
|
||||
<ul>
|
||||
{this.props.signals.map(signal =>
|
||||
<li key={signal.index} className="signal-legend" style={{ color: colorScale(signal.index) }}>
|
||||
<span className="signal-legend-name">{signal.name}</span>
|
||||
<span style={{ marginLeft: '0.3em' }} className="signal-unit">{signal.type}</span>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default PlotLegend;
|
|
@ -1,214 +0,0 @@
|
|||
/**
|
||||
* File: gauge.js
|
||||
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
|
||||
* Date: 31.03.2017
|
||||
* Copyright: 2018, Institute for Automation of Complex Power Systems, EONERC
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Gauge } from 'gaugeJS';
|
||||
|
||||
class WidgetGauge extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.gaugeCanvas = null;
|
||||
this.gauge = null;
|
||||
|
||||
this.state = {
|
||||
value: 0,
|
||||
minValue: null,
|
||||
maxValue: null
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.gauge = new Gauge(this.gaugeCanvas).setOptions(this.computeGaugeOptions(this.props.widget));
|
||||
//this.gauge.maxValue = this.state.maxValue;
|
||||
//this.gauge.setMinValue(this.state.minValue);
|
||||
this.gauge.animationSpeed = 30;
|
||||
//this.gauge.set(this.state.value);
|
||||
|
||||
//this.updateLabels(this.state.minValue, this.state.maxValue);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.simulationModel == null) {
|
||||
this.setState({ value: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
const simulator = nextProps.simulationModel.simulator;
|
||||
|
||||
// update value
|
||||
if (nextProps.data == null || nextProps.data[simulator] == null
|
||||
|| nextProps.data[simulator].output == null
|
||||
|| nextProps.data[simulator].output.values == null
|
||||
|| nextProps.data[simulator].output.values.length === 0
|
||||
|| nextProps.data[simulator].output.values[0].length === 0) {
|
||||
this.setState({ value: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
// check if value has changed
|
||||
const signal = nextProps.data[simulator].output.values[nextProps.widget.signal];
|
||||
// Take just 3 decimal positions
|
||||
// Note: Favor this method over Number.toFixed(n) in order to avoid a type conversion, since it returns a String
|
||||
if (signal != null) {
|
||||
const value = Math.round(signal[signal.length - 1].y * 1e3) / 1e3;
|
||||
if (this.state.value !== value && value != null) {
|
||||
this.setState({ value });
|
||||
|
||||
// update min-max if needed
|
||||
let updateLabels = false;
|
||||
let minValue = this.state.minValue;
|
||||
let maxValue = this.state.maxValue;
|
||||
|
||||
if (minValue == null) {
|
||||
minValue = value - 0.5;
|
||||
updateLabels = true;
|
||||
|
||||
this.setState({ minValue });
|
||||
this.gauge.setMinValue(minValue);
|
||||
}
|
||||
|
||||
if (maxValue == null) {
|
||||
maxValue = value + 0.5;
|
||||
updateLabels = true;
|
||||
|
||||
this.setState({ maxValue });
|
||||
this.gauge.maxValue = maxValue;
|
||||
}
|
||||
|
||||
if (nextProps.widget.valueUseMinMax) {
|
||||
if (this.state.minValue > nextProps.widget.valueMin) {
|
||||
minValue = nextProps.widget.valueMin;
|
||||
|
||||
this.setState({ minValue });
|
||||
this.gauge.setMinValue(minValue);
|
||||
|
||||
updateLabels = true;
|
||||
}
|
||||
|
||||
if (this.state.maxValue < nextProps.widget.valueMax) {
|
||||
maxValue = nextProps.widget.valueMax;
|
||||
|
||||
this.setState({ maxValue });
|
||||
this.gauge.maxValue = maxValue;
|
||||
|
||||
updateLabels = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (updateLabels === false) {
|
||||
// check if min/max changed
|
||||
if (minValue > this.gauge.minValue) {
|
||||
minValue = this.gauge.minValue;
|
||||
updateLabels = true;
|
||||
|
||||
this.setState({ minValue });
|
||||
}
|
||||
|
||||
if (maxValue < this.gauge.maxValue) {
|
||||
maxValue = this.gauge.maxValue;
|
||||
updateLabels = true;
|
||||
|
||||
this.setState({ maxValue });
|
||||
}
|
||||
}
|
||||
|
||||
if (updateLabels) {
|
||||
this.updateLabels(minValue, maxValue);
|
||||
}
|
||||
|
||||
// update gauge's value
|
||||
this.gauge.set(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateLabels(minValue, maxValue, force) {
|
||||
// calculate labels
|
||||
const labels = [];
|
||||
const labelCount = 5;
|
||||
const labelStep = (maxValue - minValue) / (labelCount - 1);
|
||||
|
||||
for (let i = 0; i < labelCount; i++) {
|
||||
labels.push(minValue + labelStep * i);
|
||||
}
|
||||
|
||||
// calculate zones
|
||||
let zones = this.props.widget.colorZones ? this.props.widget.zones : null;
|
||||
if (zones != null) {
|
||||
// adapt range 0-100 to actual min-max
|
||||
const step = (maxValue - minValue) / 100;
|
||||
|
||||
zones = zones.map(zone => {
|
||||
return Object.assign({}, zone, { min: (zone.min * step) + +minValue, max: zone.max * step + +minValue, strokeStyle: '#' + zone.strokeStyle });
|
||||
});
|
||||
}
|
||||
|
||||
this.gauge.setOptions({
|
||||
staticLabels: {
|
||||
font: '10px "Helvetica Neue"',
|
||||
labels,
|
||||
color: "#000000",
|
||||
fractionDigits: 1
|
||||
},
|
||||
staticZones: zones
|
||||
});
|
||||
}
|
||||
|
||||
computeGaugeOptions(widget) {
|
||||
return {
|
||||
angle: -0.25,
|
||||
lineWidth: 0.2,
|
||||
pointer: {
|
||||
length: 0.6,
|
||||
strokeWidth: 0.035
|
||||
},
|
||||
radiusScale: 0.8,
|
||||
colorStart: '#6EA2B0',
|
||||
colorStop: '#6EA2B0',
|
||||
strokeColor: '#E0E0E0',
|
||||
highDpiSupport: true,
|
||||
limitMax: false,
|
||||
limitMin: false
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const componentClass = this.props.editing ? "gauge-widget editing" : "gauge-widget";
|
||||
let signalType = null;
|
||||
|
||||
if (this.props.simulationModel != null) {
|
||||
signalType = (this.props.simulationModel != null && this.props.simulationModel.outputLength > 0 && this.props.widget.signal < this.props.simulationModel.outputLength) ? this.props.simulationModel.outputMapping[this.props.widget.signal].type : '';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={componentClass}>
|
||||
<div className="gauge-name">{this.props.widget.name}</div>
|
||||
<canvas ref={node => this.gaugeCanvas = node} />
|
||||
<div className="gauge-unit">{signalType}</div>
|
||||
<div className="gauge-value">{this.state.value}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetGauge;
|
|
@ -1,55 +0,0 @@
|
|||
/**
|
||||
* File: image.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 14.03.2017
|
||||
*
|
||||
* 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 AppDispatcher from '../../app-dispatcher';
|
||||
import config from '../../config';
|
||||
|
||||
class WidgetImage extends React.Component {
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// Query the image referenced by the widget
|
||||
let widgetFile = nextProps.widget.file;
|
||||
if (widgetFile && !nextProps.files.find(file => file._id === widgetFile)) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-load',
|
||||
data: widgetFile,
|
||||
token: nextProps.token
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const file = this.props.files.find(file => file._id === this.props.widget.file);
|
||||
|
||||
return (
|
||||
<div className="full">
|
||||
{file ? (
|
||||
<img className="full" alt={file.name} src={'/' + config.publicPathBase + file.path} onDragStart={e => e.preventDefault()} />
|
||||
) : (
|
||||
<img className="full" alt="questionmark" src={'/' + config.publicPathBase + 'missing-image.png'} onDragStart={e => e.preventDefault()} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetImage;
|
|
@ -1,94 +0,0 @@
|
|||
/**
|
||||
* File: input.js
|
||||
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
|
||||
* Date: 29.03.2017
|
||||
* Copyright: 2018, Institute for Automation of Complex Power Systems, EONERC
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Form, FormGroup, Col, ControlLabel, FormControl, InputGroup } from 'react-bootstrap';
|
||||
|
||||
class WidgetInput extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: '',
|
||||
unit: ''
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.simulationModel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update value
|
||||
if (nextProps.widget.default_value && this.state.value === undefined) {
|
||||
this.setState({
|
||||
value: nextProps.widget.default_value
|
||||
});
|
||||
}
|
||||
|
||||
// Update unit
|
||||
if (nextProps.widget.simulationModel && nextProps.simulationModel.inputMapping && this.state.unit !== nextProps.simulationModel.inputMapping[nextProps.widget.signal].type) {
|
||||
this.setState({
|
||||
unit: nextProps.simulationModel.inputMapping[nextProps.widget.signal].type
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
valueIsChanging(newValue) {
|
||||
this.setState({ value: newValue });
|
||||
}
|
||||
|
||||
valueChanged(newValue) {
|
||||
if (this.props.onInputChanged) {
|
||||
this.props.onInputChanged(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyPress(e) {
|
||||
if(e.charCode === 13) {
|
||||
this.valueChanged(this.state.value)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="number-input-widget full">
|
||||
<Form componentClass="fieldset" horizontal>
|
||||
<FormGroup>
|
||||
<Col componentClass={ControlLabel} xs={3}>
|
||||
{this.props.widget.name}
|
||||
</Col>
|
||||
<Col xs={9}>
|
||||
<InputGroup>
|
||||
<FormControl type="number" step="any" disabled={ this.props.editing } onKeyPress={ (e) => this.handleKeyPress(e) } onBlur={ (e) => this.valueChanged(this.state.value) } onChange={ (e) => this.valueIsChanging(e.target.value) } placeholder="Enter value" value={ this.state.value } />
|
||||
<InputGroup.Addon>{this.state.unit}</InputGroup.Addon>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetInput;
|
|
@ -1,78 +0,0 @@
|
|||
/**
|
||||
* File: lamp.js
|
||||
* Author: Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* Date: 20.09.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import EditWidgetColorControl from '../dialogs/edit-widget-color-control';
|
||||
|
||||
class WidgetLamp extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: '',
|
||||
threshold: 0
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.simulationModel == null) {
|
||||
this.setState({ value: '' });
|
||||
return;
|
||||
}
|
||||
|
||||
const simulator = nextProps.simulationModel.simulator;
|
||||
|
||||
// update value
|
||||
if (nextProps.data == null || nextProps.data[simulator] == null || nextProps.data[simulator].output == null || nextProps.data[simulator].output.values == null) {
|
||||
this.setState({ value: '' });
|
||||
return;
|
||||
}
|
||||
|
||||
// check if value has changed
|
||||
const signal = nextProps.data[simulator].output.values[nextProps.widget.signal];
|
||||
if (signal != null && this.state.value !== signal[signal.length - 1].y) {
|
||||
this.setState({ value: signal[signal.length - 1].y });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let colors = EditWidgetColorControl.ColorPalette;
|
||||
let color;
|
||||
|
||||
if (Number(this.state.value) > Number(this.props.widget.threshold))
|
||||
color = colors[this.props.widget.on_color];
|
||||
else
|
||||
color = colors[this.props.widget.off_color];
|
||||
|
||||
let style = {
|
||||
backgroundColor: color,
|
||||
width: this.props.widget.width,
|
||||
height: this.props.widget.height
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="lamp-widget" style={style} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetLamp;
|
|
@ -1,171 +0,0 @@
|
|||
/**
|
||||
* File: plot-table.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 15.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { FormGroup, Checkbox } from 'react-bootstrap';
|
||||
|
||||
import Plot from '../widget-plot/plot';
|
||||
import PlotLegend from '../widget-plot/plot-legend';
|
||||
|
||||
class WidgetPlotTable extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
preselectedSignals: [],
|
||||
signals: []
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.simulationModel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update internal selected signals state with props (different array objects)
|
||||
if (this.props.widget.signals !== nextProps.widget.signals) {
|
||||
this.setState( {signals: nextProps.widget.signals});
|
||||
}
|
||||
|
||||
// Identify if there was a change in the preselected signals
|
||||
if (JSON.stringify(nextProps.widget.preselectedSignals) !== JSON.stringify(this.props.widget.preselectedSignals) || this.state.preselectedSignals.length === 0) {
|
||||
// Update the currently selected signals by intersecting with the preselected signals
|
||||
// Do the same with the plot values
|
||||
var intersection = this.computeIntersection(nextProps.widget.preselectedSignals, nextProps.widget.signals);
|
||||
this.setState({ signals: intersection });
|
||||
|
||||
this.updatePreselectedSignalsState(nextProps);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the intersection of the lists, alternatively could be done with Sets ensuring unique values
|
||||
computeIntersection(preselectedSignals, selectedSignals) {
|
||||
return preselectedSignals.filter( s => selectedSignals.includes(s));
|
||||
}
|
||||
|
||||
updatePreselectedSignalsState(nextProps) {
|
||||
// Create checkboxes using the signal indices from simulation model
|
||||
const preselectedSignals = nextProps.simulationModel.outputMapping.reduce(
|
||||
// Loop through simulation model signals
|
||||
(accum, model_signal, signal_index) => {
|
||||
// Append them if they belong to the current selected type
|
||||
if (nextProps.widget.preselectedSignals.indexOf(signal_index) > -1) {
|
||||
accum.push(
|
||||
{
|
||||
index: signal_index,
|
||||
name: model_signal.name,
|
||||
type: model_signal.type
|
||||
}
|
||||
)
|
||||
}
|
||||
return accum;
|
||||
}, []);
|
||||
|
||||
this.setState({ preselectedSignals });
|
||||
}
|
||||
|
||||
updateSignalSelection(signal_index, checked) {
|
||||
// Update the selected signals and propagate to parent component
|
||||
var new_widget = Object.assign({}, this.props.widget, {
|
||||
signals: checked? this.state.signals.concat(signal_index) : this.state.signals.filter( (idx) => idx !== signal_index )
|
||||
});
|
||||
this.props.onWidgetChange(new_widget);
|
||||
}
|
||||
|
||||
render() {
|
||||
let checkBoxes = [];
|
||||
|
||||
// Data passed to plot
|
||||
if (this.props.simulationModel == null) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
const simulator = this.props.simulationModel.simulator;
|
||||
let simulatorData = [];
|
||||
|
||||
if (this.props.data[simulator] != null && this.props.data[simulator].output != null && this.props.data[simulator].output.values != null) {
|
||||
simulatorData = this.props.data[simulator].output.values.filter((values, index) => (
|
||||
this.props.widget.signals.findIndex(value => value === index) !== -1
|
||||
));
|
||||
}
|
||||
|
||||
if (this.state.preselectedSignals && this.state.preselectedSignals.length > 0) {
|
||||
// Create checkboxes using the signal indices from simulation model
|
||||
checkBoxes = this.state.preselectedSignals.map( (signal) => {
|
||||
var checked = this.state.signals.indexOf(signal.index) > -1;
|
||||
var chkBxClasses = classNames({
|
||||
'btn': true,
|
||||
'btn-default': true,
|
||||
'active': checked
|
||||
});
|
||||
return <Checkbox key={signal.index} className={chkBxClasses} checked={checked} disabled={ this.props.editing } onChange={(e) => this.updateSignalSelection(signal.index, e.target.checked) } > { signal.name } </Checkbox>
|
||||
});
|
||||
}
|
||||
|
||||
// Prepare an array with the signals to show in the legend
|
||||
var legendSignals = this.state.preselectedSignals.reduce( (accum, signal, i) => {
|
||||
if (this.state.signals.includes(signal.index)) {
|
||||
accum.push({
|
||||
index: signal.index,
|
||||
name: signal.name,
|
||||
type: signal.type
|
||||
});
|
||||
}
|
||||
return accum;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="plot-table-widget" ref="wrapper">
|
||||
<div className="content">
|
||||
<div className="table-plot-row">
|
||||
<div className="widget-table">
|
||||
{ checkBoxes.length > 0 ? (
|
||||
<FormGroup className="btn-group-vertical">
|
||||
{ checkBoxes }
|
||||
</FormGroup>
|
||||
) : ( <small>No signal has been pre-selected.</small> )
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className="widget-plot">
|
||||
<Plot
|
||||
data={simulatorData}
|
||||
time={this.props.widget.time}
|
||||
width={this.props.widget.width - 100}
|
||||
height={this.props.widget.height - 55}
|
||||
yMin={this.props.widget.yMin}
|
||||
yMax={this.props.widget.yMax}
|
||||
yUseMinMax={this.props.widget.yUseMinMax}
|
||||
paused={this.props.paused}
|
||||
yLabel={this.props.widget.ylabel}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<PlotLegend signals={legendSignals} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetPlotTable;
|
|
@ -1,88 +0,0 @@
|
|||
/**
|
||||
* File: plot.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 08.03.2017
|
||||
*
|
||||
* 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 Plot from '../widget-plot/plot';
|
||||
import PlotLegend from '../widget-plot/plot-legend';
|
||||
|
||||
class WidgetPlot extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
data: [],
|
||||
legend: []
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.simulationModel == null) {
|
||||
this.setState({ data: [], legend: [] });
|
||||
return;
|
||||
}
|
||||
|
||||
const simulator = nextProps.simulationModel.simulator;
|
||||
|
||||
// Proceed if a simulation with models and a simulator are available
|
||||
if (simulator && nextProps.data[simulator] != null && nextProps.data[simulator] != null && nextProps.data[simulator].output != null && nextProps.data[simulator].output.values != null) {
|
||||
const chosenSignals = nextProps.widget.signals;
|
||||
|
||||
const data = nextProps.data[simulator].output.values.filter((values, index) => (
|
||||
nextProps.widget.signals.findIndex(value => value === index) !== -1
|
||||
));
|
||||
|
||||
// Query the signals that will be displayed in the legend
|
||||
const legend = nextProps.simulationModel.outputMapping.reduce( (accum, model_signal, signal_index) => {
|
||||
if (chosenSignals.includes(signal_index)) {
|
||||
accum.push({ index: signal_index, name: model_signal.name, type: model_signal.type });
|
||||
}
|
||||
|
||||
return accum;
|
||||
}, []);
|
||||
|
||||
this.setState({ data, legend });
|
||||
} else {
|
||||
this.setState({ data: [], legend: [] });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className="plot-widget" ref="wrapper">
|
||||
<div className="widget-plot">
|
||||
<Plot
|
||||
data={this.state.data}
|
||||
height={this.props.widget.height - 55}
|
||||
width={this.props.widget.width - 20}
|
||||
time={this.props.widget.time}
|
||||
yMin={this.props.widget.yMin}
|
||||
yMax={this.props.widget.yMax}
|
||||
yUseMinMax={this.props.widget.yUseMinMax}
|
||||
paused={this.props.paused}
|
||||
yLabel={this.props.widget.ylabel}
|
||||
/>
|
||||
</div>
|
||||
<PlotLegend signals={this.state.legend} />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetPlot;
|
|
@ -1,132 +0,0 @@
|
|||
/**
|
||||
* File: slider.js
|
||||
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
|
||||
* Date: 30.03.2017
|
||||
* Copyright: 2018, Institute for Automation of Complex Power Systems, EONERC
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { format } from 'd3';
|
||||
import classNames from 'classnames';
|
||||
import Slider from 'rc-slider';
|
||||
import 'rc-slider/assets/index.css';
|
||||
|
||||
class WidgetSlider extends Component {
|
||||
|
||||
static get OrientationTypes() {
|
||||
return ({
|
||||
HORIZONTAL: {value: 0, name: 'Horizontal'},
|
||||
VERTICAL: {value: 1, name: 'Vertical'}
|
||||
})
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
unit: ''
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.simulationModel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update value
|
||||
if (nextProps.widget.default_value && this.state.value === undefined) {
|
||||
this.setState({
|
||||
value: nextProps.widget.default_value,
|
||||
});
|
||||
}
|
||||
|
||||
// Update unit
|
||||
if (nextProps.widget.simulationModel && nextProps.simulationModel.inputMapping && this.state.unit !== nextProps.simulationModel.inputMapping[nextProps.widget.signal].type) {
|
||||
this.setState({
|
||||
unit: nextProps.simulationModel.inputMapping[nextProps.widget.signal].type
|
||||
});
|
||||
}
|
||||
|
||||
// Check if the orientation changed, update the size if it did
|
||||
if (this.props.widget.orientation !== nextProps.widget.orientation) {
|
||||
let baseWidget = nextProps.widget;
|
||||
|
||||
// Exchange dimensions and constraints
|
||||
let newWidget = Object.assign({}, baseWidget, {
|
||||
width: baseWidget.height,
|
||||
height: baseWidget.width,
|
||||
minWidth: baseWidget.minHeight,
|
||||
minHeight: baseWidget.minWidth,
|
||||
maxWidth: baseWidget.maxHeight,
|
||||
maxHeight: baseWidget.maxWidth
|
||||
});
|
||||
|
||||
nextProps.onWidgetChange(newWidget);
|
||||
}
|
||||
}
|
||||
|
||||
valueIsChanging(newValue) {
|
||||
if (this.props.widget.continous_update)
|
||||
this.valueChanged(newValue);
|
||||
|
||||
this.setState({ value: newValue });
|
||||
}
|
||||
|
||||
valueChanged(newValue) {
|
||||
if (this.props.onInputChanged) {
|
||||
this.props.onInputChanged(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let isVertical = this.props.widget.orientation === WidgetSlider.OrientationTypes.VERTICAL.value;
|
||||
|
||||
let fields = {
|
||||
name: this.props.widget.name,
|
||||
control: <Slider min={ this.props.widget.rangeMin } max={ this.props.widget.rangeMax } step={ this.props.widget.step } value={ this.state.value } disabled={ this.props.editing } vertical={ isVertical } onChange={ (v) => this.valueIsChanging(v) } onAfterChange={ (v) => this.valueChanged(v) }/>,
|
||||
value: <span>{ format('.3s')(Number.parseFloat(this.state.value)) }</span>,
|
||||
unit: <span className="signal-unit">{ this.state.unit }</span>
|
||||
}
|
||||
|
||||
var widgetClasses = classNames({
|
||||
'slider-widget': true,
|
||||
'full': true,
|
||||
'vertical': isVertical,
|
||||
'horizontal': !isVertical
|
||||
});
|
||||
|
||||
return (
|
||||
this.props.widget.orientation === WidgetSlider.OrientationTypes.HORIZONTAL.value? (
|
||||
<div className={widgetClasses}>
|
||||
<label>{ fields.name }</label>
|
||||
<div className='slider'>{ fields.control }</div>
|
||||
<span>{ fields.value }</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className={widgetClasses}>
|
||||
<label>{ fields.name }</label>
|
||||
{ fields.control }
|
||||
{ fields.value }
|
||||
{ this.props.widget.showUnit && fields.unit }
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetSlider;
|
|
@ -1,99 +0,0 @@
|
|||
/**
|
||||
* File: table.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 14.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { format } from 'd3';
|
||||
|
||||
import Table from '../table';
|
||||
import TableColumn from '../table-column';
|
||||
|
||||
class WidgetTable extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
rows: [],
|
||||
sequence: null,
|
||||
showUnit: false
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.simulationModel == null) {
|
||||
this.setState({ rows: [], sequence: null });
|
||||
return;
|
||||
}
|
||||
|
||||
const simulator = nextProps.simulationModel.simulator;
|
||||
|
||||
// check data
|
||||
if (nextProps.data == null
|
||||
|| nextProps.data[simulator] == null
|
||||
|| nextProps.data[simulator].output == null
|
||||
|| nextProps.data[simulator].output.values.length === 0
|
||||
|| nextProps.data[simulator].output.values[0].length === 0) {
|
||||
|
||||
// clear values
|
||||
this.setState({ rows: [], sequence: null, showUnit: false });
|
||||
return;
|
||||
}
|
||||
|
||||
// check if new data, otherwise skip
|
||||
/*if (this.state.sequence >= nextProps.data[simulator.node][simulator.simulator].sequence) {
|
||||
return;
|
||||
}*/
|
||||
|
||||
// get rows
|
||||
const rows = [];
|
||||
|
||||
nextProps.data[simulator].output.values.forEach((signal, index) => {
|
||||
if (index < nextProps.simulationModel.outputMapping.length) {
|
||||
rows.push({
|
||||
name: nextProps.simulationModel.outputMapping[index].name,
|
||||
unit: nextProps.simulationModel.outputMapping[index].type,
|
||||
value: signal[signal.length - 1].y
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({ showUnit: nextProps.showUnit, rows: rows, sequence: nextProps.data[simulator].output.sequence });
|
||||
}
|
||||
|
||||
render() {
|
||||
var columns = [
|
||||
<TableColumn key={1} title="Signal" dataKey="name" width={120} />,
|
||||
<TableColumn key={2} title="Value" dataKey="value" modifier={format('.4s')} />
|
||||
];
|
||||
|
||||
if (this.props.widget.showUnit)
|
||||
columns.push(<TableColumn key={3} title="Unit" dataKey="unit" />)
|
||||
|
||||
return (
|
||||
<div className="table-widget">
|
||||
<Table data={this.state.rows}>
|
||||
{ columns }
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetTable;
|
|
@ -1,185 +0,0 @@
|
|||
/**
|
||||
* File: topology.js
|
||||
* Author: Ricardo Hernandez-Montoya <rhernandez@gridhound.de>
|
||||
* Date: 08.01.2018
|
||||
*
|
||||
* 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 {ReactSVGPanZoom} from 'react-svg-pan-zoom';
|
||||
import config from '../../config';
|
||||
import '../../styles/simple-spinner.css';
|
||||
import { cimsvg } from 'libcimsvg';
|
||||
|
||||
// Do not show Pintura's grid
|
||||
const pinturaGridStyle = {
|
||||
display: 'none'
|
||||
}
|
||||
|
||||
// Avoid another color in the frontend
|
||||
const pinturaBackingStyle = {
|
||||
fill: 'transparent'
|
||||
}
|
||||
|
||||
// Center spinner
|
||||
const spinnerContainerStyle = {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}
|
||||
|
||||
// Topology failed message
|
||||
const msgContainerStyle = Object.assign({
|
||||
backgroundColor: '#ececec'
|
||||
},spinnerContainerStyle)
|
||||
|
||||
const msgStyle = {
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
|
||||
// Initialize functions
|
||||
function attachComponentEvents() {
|
||||
window.onMouseOver = (e) => show(textSibling(e));
|
||||
window.onMouseLeave = (e) => hide(textSibling(e));
|
||||
}
|
||||
|
||||
function textSibling(e) {
|
||||
// Find sibling text element and toggle its visibility
|
||||
let gParent = e.target.parentElement;
|
||||
return gParent.getElementsByTagName('text')[0];
|
||||
}
|
||||
|
||||
function show(element) {
|
||||
element.style.visibility = 'inherit';
|
||||
}
|
||||
|
||||
function hide(element) {
|
||||
element.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
// De-initialize functions
|
||||
function detachComponentEvents() {
|
||||
window.onMouseOver = null;
|
||||
window.onMouseLeave = null;
|
||||
}
|
||||
|
||||
class WidgetTopology extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.svgElem = React.createRef();
|
||||
this.Viewer = null;
|
||||
|
||||
this.state = {
|
||||
visualizationState: 'initial'
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.svgElem) {
|
||||
window.onMouseLeave = function() {};
|
||||
window.onMouseOver = function() {};
|
||||
window.onMouseLeave = function() {};
|
||||
window.onMouseUp = function() {};
|
||||
window.onMouseDown = function() {};
|
||||
window.onMouseMove = function() {};
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
detachComponentEvents();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const file = nextProps.files.find(file => file._id === nextProps.widget.file);
|
||||
// Ensure model is requested only once or a different was selected
|
||||
if (this.props.widget.file !== nextProps.widget.file || (this.state.visualizationState === 'initial' && file)) {
|
||||
this.setState({'visualizationState': 'loading' });
|
||||
if (file) {
|
||||
fetch(new Request('/' + config.publicPathBase + file.path))
|
||||
.then( response => {
|
||||
if (response.status === 200) {
|
||||
this.setState({'visualizationState': 'ready' });
|
||||
return response.text().then( contents => {
|
||||
if (this.svgElem) {
|
||||
let cimsvgInstance = new cimsvg(this.svgElem.current);
|
||||
cimsvg.setCimsvg(cimsvgInstance);
|
||||
cimsvgInstance.setFileCount(1);
|
||||
cimsvgInstance.loadFile(contents);
|
||||
//cimsvgInstance.fit();
|
||||
attachComponentEvents();
|
||||
}
|
||||
else {
|
||||
console.error("The svgElem variable is not initialized before the attempt to create the cimsvg instance.");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new Error('Request failed');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
this.setState({
|
||||
'visualizationState': 'show_message',
|
||||
'message': 'Topology could not be loaded.'});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// No file has been selected
|
||||
if (!nextProps.widget.file) {
|
||||
this.setState({
|
||||
'visualizationState': 'show_message',
|
||||
'message': 'Select a topology model first.'});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
var markup = null;
|
||||
|
||||
switch(this.state.visualizationState) {
|
||||
case 'loading':
|
||||
markup = <div style={spinnerContainerStyle}><div className="loader" /></div>; break;
|
||||
case 'show_message':
|
||||
markup = <div style={msgContainerStyle}><div style={msgStyle}>{ this.state.message }</div></div>; break;
|
||||
default:
|
||||
markup = (<div>
|
||||
<ReactSVGPanZoom
|
||||
ref={Viewer => this.Viewer = Viewer}
|
||||
style={{outline: "1px solid grey"}}
|
||||
detectAutoPan={false}
|
||||
miniaturePosition="none"
|
||||
toolbarPosition="none"
|
||||
background="white"
|
||||
tool="pan"
|
||||
width={this.props.widget.width-2} height={this.props.widget.height-2} >
|
||||
<svg width={this.props.widget.width} height={this.props.widget.height}>
|
||||
<svg ref={ this.svgElem } width={this.props.widget.width} height={this.props.widget.height}>
|
||||
<rect className="backing" style={pinturaBackingStyle} />
|
||||
<g className="grid" style={pinturaGridStyle} />
|
||||
<g className="diagrams"/>
|
||||
</svg>
|
||||
</svg>
|
||||
</ReactSVGPanZoom>
|
||||
</div>);
|
||||
}
|
||||
return markup;
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetTopology;
|
|
@ -1,72 +0,0 @@
|
|||
/**
|
||||
* File: value.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 04.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { format } from 'd3';
|
||||
|
||||
class WidgetValue extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: '',
|
||||
unit: ''
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.simulationModel == null) {
|
||||
this.setState({ value: '' });
|
||||
return;
|
||||
}
|
||||
|
||||
const simulator = nextProps.simulationModel.simulator;
|
||||
|
||||
// update value
|
||||
if (nextProps.data == null || nextProps.data[simulator] == null || nextProps.data[simulator].output == null || nextProps.data[simulator].output.values == null) {
|
||||
this.setState({ value: '' });
|
||||
return;
|
||||
}
|
||||
|
||||
const unit = nextProps.simulationModel.outputMapping[nextProps.widget.signal].type;
|
||||
|
||||
// check if value has changed
|
||||
const signal = nextProps.data[simulator].output.values[nextProps.widget.signal];
|
||||
if (signal != null && this.state.value !== signal[signal.length - 1].y) {
|
||||
this.setState({ value: signal[signal.length - 1].y, unit });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
var value_to_render = Number(this.state.value);
|
||||
return (
|
||||
<div className="single-value-widget">
|
||||
<strong style={{ fontSize: this.props.widget.textSize + 'px' }}>{this.props.widget.name}</strong>
|
||||
<span style={{ fontSize: this.props.widget.textSize + 'px' }}>{Number.isNaN(value_to_render) ? NaN : format('.3s')(value_to_render)}</span>
|
||||
{this.props.widget.showUnit &&
|
||||
<span style={{ fontSize: this.props.widget.textSize + 'px' }}>[{this.state.unit}]</span>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetValue;
|
|
@ -1,11 +1,28 @@
|
|||
/**
|
||||
* 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/>.
|
||||
******************************************************************************/
|
||||
|
||||
const config = {
|
||||
publicPathBase: 'public/',
|
||||
instance: 'frontend of the Global RT-SuperLab Demonstration',
|
||||
instance: 'VILLASweb',
|
||||
subtitle: 'ACS',
|
||||
admin: {
|
||||
name: 'Steffen Vogel',
|
||||
name: 'Institute for Automation of Complex Power Systems (ACS), RWTH Aachen University, Germany',
|
||||
mail: 'stvogel@eonerc.rwth-aachen.de'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default config
|
||||
|
|
|
@ -1,151 +0,0 @@
|
|||
/**
|
||||
* File: app.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
*
|
||||
* 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 { Container } from 'flux/utils';
|
||||
import { DragDropContext } from 'react-dnd';
|
||||
import HTML5Backend from 'react-dnd-html5-backend';
|
||||
import NotificationSystem from 'react-notification-system';
|
||||
import { Redirect, Route } from 'react-router-dom';
|
||||
import { Col } from 'react-bootstrap';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import SimulationStore from '../stores/simulation-store';
|
||||
import SimulatorStore from '../stores/simulator-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
import NotificationsDataManager from '../data-managers/notifications-data-manager';
|
||||
|
||||
import Home from '../components/home';
|
||||
import Header from '../components/header';
|
||||
import Footer from '../components/footer';
|
||||
import SidebarMenu from '../components/menu-sidebar';
|
||||
import HeaderMenu from '../components/header-menu';
|
||||
|
||||
import Projects from './projects';
|
||||
import Project from './project';
|
||||
import Simulators from './simulators';
|
||||
import Visualization from './visualization';
|
||||
import Simulations from './simulations';
|
||||
import Simulation from './simulation';
|
||||
import SimulationModel from './simulation-model';
|
||||
import Users from './users';
|
||||
|
||||
import '../styles/app.css';
|
||||
|
||||
class App extends React.Component {
|
||||
static getStores() {
|
||||
return [ SimulatorStore, UserStore, SimulationStore ];
|
||||
}
|
||||
|
||||
static calculateState(prevState) {
|
||||
let currentUser = UserStore.getState().currentUser;
|
||||
|
||||
return {
|
||||
simulators: SimulatorStore.getState(),
|
||||
simulations: SimulationStore.getState(),
|
||||
currentRole: currentUser ? currentUser.role : '',
|
||||
token: UserStore.getState().token,
|
||||
|
||||
showSidebarMenu: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// if token stored locally, request user
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
if (token != null && token !== '') {
|
||||
// save token so we dont logout
|
||||
this.setState({ token });
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/logged-in',
|
||||
token: token
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// load all simulators and simulations to fetch simulation data
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-load',
|
||||
token: this.state.token
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-load',
|
||||
token: this.state.token
|
||||
});
|
||||
|
||||
NotificationsDataManager.setSystem(this.refs.notificationSystem);
|
||||
}
|
||||
|
||||
showSidebarMenu = () => {
|
||||
this.setState({ showSidebarMenu: true });
|
||||
}
|
||||
|
||||
hideSidebarMenu = () => {
|
||||
this.setState({ showSidebarMenu: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.token == null) {
|
||||
return (<Redirect to="/login" />);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Col style={{ width: this.state.showSidebarMenu ? '280px' : '0px' }} smHidden mdHidden lgHidden className="sidenav">
|
||||
<HeaderMenu onClose={this.hideSidebarMenu} currentRole={this.state.currentRole} />
|
||||
</Col>
|
||||
|
||||
<div className="app">
|
||||
<NotificationSystem ref="notificationSystem" />
|
||||
|
||||
<Header onMenuButton={this.showSidebarMenu} showMenuButton={this.state.token != null} />
|
||||
|
||||
<div className={`app-body app-body-spacing`} >
|
||||
<Col xsHidden>
|
||||
<SidebarMenu currentRole={this.state.currentRole} />
|
||||
</Col>
|
||||
|
||||
<div className={`app-content app-content-margin-left`}>
|
||||
<Route exact path="/" component={Home} />
|
||||
<Route path="/home" component={Home} />
|
||||
<Route exact path="/projects" component={Projects} />
|
||||
<Route path="/projects/:project" component={Project} />
|
||||
<Route path="/visualizations/:visualization" component={Visualization} />
|
||||
<Route exact path="/simulations" component={Simulations} />
|
||||
<Route path="/simulations/:simulation" component={Simulation} />
|
||||
<Route path="/simulationModel/:simulationModel" component={SimulationModel} />
|
||||
<Route path="/simulators" component={Simulators} />
|
||||
<Route path="/users" component={Users} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DragDropContext(HTML5Backend)(Container.create(App));
|
|
@ -1,96 +0,0 @@
|
|||
/**
|
||||
* File: login.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 15.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { PageHeader } from 'react-bootstrap';
|
||||
import NotificationSystem from 'react-notification-system';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
import LoginForm from '../components/login-form';
|
||||
import Header from '../components/header';
|
||||
import Footer from '../components/footer';
|
||||
import NotificationsDataManager from '../data-managers/notifications-data-manager';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import UserStore from '../stores/user-store';
|
||||
|
||||
class Login extends Component {
|
||||
static getStores() {
|
||||
return [ UserStore ];
|
||||
}
|
||||
|
||||
static calculateState() {
|
||||
return {
|
||||
currentUser: UserStore.getState().currentUser,
|
||||
token: UserStore.getState().token,
|
||||
loginMessage: UserStore.getState().loginMessage
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
NotificationsDataManager.setSystem(this.refs.notificationSystem);
|
||||
}
|
||||
|
||||
componentWillUpdate(nextProps, nextState) {
|
||||
// if token stored locally, request user
|
||||
if (nextState.token == null) {
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
if (token != null && token !== '' && nextState.currentUser == null) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'users/logged-in',
|
||||
token: token
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// check if logged in
|
||||
if (nextState.currentUser != null) {
|
||||
// save login in local storage
|
||||
localStorage.setItem('token', nextState.token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.currentUser != null) {
|
||||
return (<Redirect to="/" />);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<NotificationSystem ref="notificationSystem" />
|
||||
|
||||
<Header />
|
||||
|
||||
<div className="login-container">
|
||||
<PageHeader>Login</PageHeader>
|
||||
|
||||
<LoginForm loginMessage={this.state.loginMessage} />
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(Login);
|
|
@ -1,234 +0,0 @@
|
|||
/**
|
||||
* File: project.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 03.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import FileSaver from 'file-saver';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import ProjectStore from '../stores/project-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
import VisualizationStore from '../stores/visualization-store';
|
||||
import SimulationStore from '../stores/simulation-store';
|
||||
|
||||
import Icon from '../components/icon';
|
||||
import CustomTable from '../components/table';
|
||||
import TableColumn from '../components/table-column';
|
||||
import NewVisualzationDialog from '../components/dialogs/new-visualization';
|
||||
import EditVisualizationDialog from '../components/dialogs/edit-visualization';
|
||||
import ImportVisualizationDialog from '../components/dialogs/import-visualization';
|
||||
|
||||
import DeleteDialog from '../components/dialogs/delete-dialog';
|
||||
|
||||
class Visualizations extends Component {
|
||||
static getStores() {
|
||||
return [ ProjectStore, VisualizationStore, UserStore, SimulationStore ];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
prevState = prevState || {};
|
||||
|
||||
// load project
|
||||
const sessionToken = UserStore.getState().token;
|
||||
|
||||
let project = ProjectStore.getState().find(project => project._id === props.match.params.project);
|
||||
if (project == null) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'projects/start-load',
|
||||
data: props.match.params.project,
|
||||
token: sessionToken
|
||||
});
|
||||
|
||||
project = {};
|
||||
}
|
||||
|
||||
// load simulation
|
||||
let simulation = {};
|
||||
|
||||
if (project.simulation != null) {
|
||||
simulation = SimulationStore.getState().find(simulation => simulation._id === project.simulation);
|
||||
}
|
||||
|
||||
// load visualizations
|
||||
let visualizations = [];
|
||||
|
||||
if (project.visualizations != null) {
|
||||
visualizations = VisualizationStore.getState().filter(visualization => project.visualizations.includes(visualization._id));
|
||||
}
|
||||
|
||||
return {
|
||||
visualizations,
|
||||
project,
|
||||
simulation,
|
||||
sessionToken,
|
||||
|
||||
newModal: prevState.newModal || false,
|
||||
deleteModal: prevState.deleteModal || false,
|
||||
editModal: prevState.editModal || false,
|
||||
importModal: prevState.importModal || false,
|
||||
modalData: prevState.modalData || {}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
closeNewModal(data) {
|
||||
this.setState({ newModal: false });
|
||||
|
||||
if (data) {
|
||||
// add project to visualization
|
||||
data.project = this.state.project._id;
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-add',
|
||||
data: data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
this.setState({ project: {} }, () => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'projects/start-load',
|
||||
data: this.props.match.params.project,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
closeDeleteModal = confirmDelete => {
|
||||
this.setState({ deleteModal: false });
|
||||
|
||||
if (confirmDelete === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-remove',
|
||||
data: this.state.modalData,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
closeEditModal(data) {
|
||||
this.setState({ editModal : false });
|
||||
|
||||
if (data) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-edit',
|
||||
data: data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
closeImportModal(data) {
|
||||
this.setState({ importModal: false });
|
||||
|
||||
if (data) {
|
||||
data.project = this.state.project._id;
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-add',
|
||||
data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
this.setState({ project: {} }, () => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'projects/start-load',
|
||||
data: this.props.match.params.project,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
exportVisualization(index) {
|
||||
// filter properties
|
||||
let visualization = Object.assign({}, this.state.visualizations[index]);
|
||||
delete visualization._id;
|
||||
delete visualization.project;
|
||||
delete visualization.user;
|
||||
|
||||
visualization.widgets.forEach(widget => {
|
||||
delete widget.simulator;
|
||||
});
|
||||
|
||||
// show save dialog
|
||||
const blob = new Blob([JSON.stringify(visualization, null, 2)], { type: 'application/json' });
|
||||
FileSaver.saveAs(blob, 'visualization - ' + visualization.name + '.json');
|
||||
}
|
||||
|
||||
onModalKeyPress = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
|
||||
this.confirmDeleteModal();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
marginRight: '10px'
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='section'>
|
||||
<h1>{this.state.project.name}</h1>
|
||||
|
||||
<CustomTable data={this.state.visualizations}>
|
||||
<TableColumn title='Name' dataKey='name' link='/visualizations/' linkKey='_id' />
|
||||
<TableColumn
|
||||
width='100'
|
||||
editButton
|
||||
deleteButton
|
||||
exportButton
|
||||
onEdit={(index) => this.setState({ editModal: true, modalData: this.state.visualizations[index] })}
|
||||
onDelete={(index) => this.setState({ deleteModal: true, modalData: this.state.visualizations[index] })}
|
||||
onExport={index => this.exportVisualization(index)}
|
||||
/>
|
||||
</CustomTable>
|
||||
|
||||
<Button onClick={() => this.setState({ newModal: true })} style={buttonStyle}><Icon icon="plus" /> Visualization</Button>
|
||||
<Button onClick={() => this.setState({ importModal: true })} style={buttonStyle}><Icon icon="upload" /> Import</Button>
|
||||
|
||||
<NewVisualzationDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} />
|
||||
<EditVisualizationDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} visualization={this.state.modalData} />
|
||||
<ImportVisualizationDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} simulation={this.state.simulation} />
|
||||
|
||||
<DeleteDialog title="visualization" name={this.state.modalData.name} show={this.state.deleteModal} onClose={this.closeDeleteModal} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(Visualizations, {withProps: true});
|
|
@ -1,159 +0,0 @@
|
|||
/**
|
||||
* File: projects.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
*
|
||||
* 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 { Container } from 'flux/utils';
|
||||
import { Button } from 'react-bootstrap';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import ProjectStore from '../stores/project-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
import SimulationStore from '../stores/simulation-store';
|
||||
|
||||
import Icon from '../components/icon';
|
||||
import Table from '../components/table';
|
||||
import TableColumn from '../components/table-column';
|
||||
import NewProjectDialog from '../components/dialogs/new-project';
|
||||
import EditProjectDialog from '../components/dialogs/edit-project';
|
||||
|
||||
import DeleteDialog from '../components/dialogs/delete-dialog';
|
||||
|
||||
class Projects extends React.Component {
|
||||
static getStores() {
|
||||
return [ ProjectStore, SimulationStore, UserStore ];
|
||||
}
|
||||
|
||||
static calculateState() {
|
||||
return {
|
||||
projects: ProjectStore.getState(),
|
||||
simulations: SimulationStore.getState(),
|
||||
sessionToken: UserStore.getState().token,
|
||||
|
||||
newModal: false,
|
||||
editModal: false,
|
||||
deleteModal: false,
|
||||
modalData: {}
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'projects/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
closeNewModal(data) {
|
||||
this.setState({ newModal: false });
|
||||
|
||||
if (data) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'projects/start-add',
|
||||
data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
closeDeleteModal = confirmDelete => {
|
||||
this.setState({ deleteModal: false });
|
||||
|
||||
if (confirmDelete === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'projects/start-remove',
|
||||
data: this.state.modalData,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
closeEditModal(data) {
|
||||
this.setState({ editModal: false });
|
||||
|
||||
if (data) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'projects/start-edit',
|
||||
data: data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getSimulationName(id) {
|
||||
for (var i = 0; i < this.state.simulations.length; i++) {
|
||||
if (this.state.simulations[i]._id === id) {
|
||||
return this.state.simulations[i].name;
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
hasValidSimulation() {
|
||||
const simulations = this.state.simulations.filter(simulation => {
|
||||
return simulation.models.length > 0;
|
||||
});
|
||||
|
||||
return simulations.length > 0;
|
||||
}
|
||||
|
||||
onModalKeyPress = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
|
||||
this.confirmDeleteModal();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='section'>
|
||||
<h1>Projects</h1>
|
||||
|
||||
<Table data={this.state.projects}>
|
||||
<TableColumn title='Name' dataKey='name' link='/projects/' linkKey='_id' />
|
||||
<TableColumn title='Simulation' dataKey='simulation' modifier={(id) => this.getSimulationName(id)} />
|
||||
<TableColumn width='70' editButton deleteButton onEdit={index => this.setState({ editModal: true, modalData: this.state.projects[index] })} onDelete={index => this.setState({ deleteModal: true, modalData: this.state.projects[index] })} />
|
||||
</Table>
|
||||
|
||||
<Button onClick={() => this.setState({ newModal: true })} disabled={!this.hasValidSimulation()}><Icon icon='plus' /> Project</Button>
|
||||
|
||||
{!this.hasValidSimulation() &&
|
||||
<span><i> Simulation with at least one simulation-model required!</i></span>
|
||||
}
|
||||
|
||||
<NewProjectDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} simulations={this.state.simulations} />
|
||||
<EditProjectDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} project={this.state.modalData} simulations={this.state.simulations} />
|
||||
|
||||
<DeleteDialog title="project" name={this.state.modalData.name} show={this.state.deleteModal} onClose={this.closeDeleteModal} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(Projects);
|
|
@ -1,151 +0,0 @@
|
|||
/**
|
||||
* File: selectFile.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 10.05.2018
|
||||
*
|
||||
* 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 { Container } from 'flux/utils';
|
||||
import { FormGroup, FormControl, ControlLabel, Button, ProgressBar, Col } from 'react-bootstrap';
|
||||
|
||||
import FileStore from '../stores/file-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
|
||||
class SelectFile extends React.Component {
|
||||
static getStores() {
|
||||
return [ FileStore, UserStore ];
|
||||
}
|
||||
|
||||
static calculateState() {
|
||||
return {
|
||||
files: FileStore.getState(),
|
||||
sessionToken: UserStore.getState().token,
|
||||
selectedFile: '',
|
||||
uploadFile: null,
|
||||
uploadProgress: 0
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.value === this.state.selectedSimulator) {
|
||||
return;
|
||||
}
|
||||
|
||||
let selectedSimulator = nextProps.value;
|
||||
if (selectedSimulator == null) {
|
||||
if (this.state.simulators.length > 0) {
|
||||
selectedSimulator = this.state.simulators[0]._id;
|
||||
} else {
|
||||
selectedSimulator = '';
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ selectedSimulator });
|
||||
}
|
||||
|
||||
handleChange = event => {
|
||||
this.setState({ selectedFile: event.target.value });
|
||||
|
||||
// send file to callback
|
||||
if (this.props.onChange != null) {
|
||||
const file = this.state.files.find(f => f._id === event.target.value);
|
||||
|
||||
this.props.onChange(file);
|
||||
}
|
||||
}
|
||||
|
||||
selectUploadFile = event => {
|
||||
this.setState({ uploadFile: event.target.files[0] });
|
||||
}
|
||||
|
||||
startFileUpload = () => {
|
||||
// upload file
|
||||
const formData = new FormData();
|
||||
formData.append(0, this.state.uploadFile);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-upload',
|
||||
data: formData,
|
||||
token: this.state.sessionToken,
|
||||
progressCallback: this.updateUploadProgress,
|
||||
finishedCallback: this.clearProgress
|
||||
});
|
||||
}
|
||||
|
||||
updateUploadProgress = event => {
|
||||
this.setState({ uploadProgress: parseInt(event.percent.toFixed(), 10) });
|
||||
}
|
||||
|
||||
clearProgress = () => {
|
||||
// select uploaded file
|
||||
const selectedFile = this.state.files[this.state.files.length - 1]._id;
|
||||
this.setState({ selectedFile, uploadProgress: 0 });
|
||||
}
|
||||
|
||||
render() {
|
||||
const fileOptions = this.state.files.map(f =>
|
||||
<option key={f._id} value={f._id}>{f.name}</option>
|
||||
);
|
||||
|
||||
const progressBarStyle = {
|
||||
marginLeft: '100px',
|
||||
marginTop: '-25px'
|
||||
};
|
||||
|
||||
return <div>
|
||||
<FormGroup>
|
||||
<Col componentClass={ControlLabel} sm={3} md={2}>
|
||||
{this.props.name}
|
||||
</Col>
|
||||
|
||||
<Col sm={9} md={10}>
|
||||
<FormControl disabled={this.props.disabled} componentClass='select' placeholder='Select file' onChange={this.handleChange}>
|
||||
{fileOptions}
|
||||
</FormControl>
|
||||
</Col>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<Col sm={9} md={10} smOffset={3} mdOffset={2}>
|
||||
<FormControl disabled={this.props.disabled} type='file' onChange={this.selectUploadFile} />
|
||||
</Col>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<Col sm={9} md={10} smOffset={3} mdOffset={2}>
|
||||
<Button disabled={this.props.disabled} bsSize='small' onClick={this.startFileUpload}>
|
||||
Upload file
|
||||
</Button>
|
||||
|
||||
<ProgressBar striped active now={this.state.uploadProgress} label={this.state.uploadProgress + '%'} style={progressBarStyle} />
|
||||
</Col>
|
||||
</FormGroup>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(SelectFile);
|
|
@ -1,88 +0,0 @@
|
|||
/**
|
||||
* File: selectSimulator.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 10.05.2018
|
||||
*
|
||||
* 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 { Container } from 'flux/utils';
|
||||
import { FormGroup, FormControl, ControlLabel, Col } from 'react-bootstrap';
|
||||
import _ from 'lodash';
|
||||
|
||||
import SimulatorStore from '../stores/simulator-store';
|
||||
|
||||
class SelectSimulator extends React.Component {
|
||||
static getStores() {
|
||||
return [ SimulatorStore ];
|
||||
}
|
||||
|
||||
static calculateState() {
|
||||
return {
|
||||
simulators: SimulatorStore.getState(),
|
||||
selectedSimulator: ''
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.value === this.state.selectedSimulator) {
|
||||
return;
|
||||
}
|
||||
|
||||
let selectedSimulator = nextProps.value;
|
||||
if (selectedSimulator == null) {
|
||||
if (this.state.simulators.length > 0) {
|
||||
selectedSimulator = this.state.simulators[0]._id;
|
||||
} else {
|
||||
selectedSimulator = '';
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ selectedSimulator });
|
||||
}
|
||||
|
||||
handleChange = event => {
|
||||
this.setState({ selectedSimulator: event.target.value });
|
||||
|
||||
// send complete simulator to callback
|
||||
if (this.props.onChange != null) {
|
||||
const simulator = this.state.simulators.find(s => s._id === event.target.value);
|
||||
|
||||
this.props.onChange(simulator);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const simulatorOptions = this.state.simulators.map(s =>
|
||||
<option key={s._id} value={s._id}>{_.get(s, 'properties.name') || _.get(s, 'rawProperties.name') || s.uuid}</option>
|
||||
);
|
||||
|
||||
return <FormGroup>
|
||||
<Col componentClass={ControlLabel} sm={3} md={2}>
|
||||
Simulator
|
||||
</Col>
|
||||
|
||||
<Col sm={9} md={10}>
|
||||
<FormControl componentClass='select' placeholder='Select simulator' value={this.state.selectedSimulator} onChange={this.handleChange}>
|
||||
{simulatorOptions}
|
||||
</FormControl>
|
||||
</Col>
|
||||
</FormGroup>;
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(SelectSimulator);
|
|
@ -1,171 +0,0 @@
|
|||
/**
|
||||
* File: simulationModel.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 10.05.2018
|
||||
*
|
||||
* 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 { Container } from 'flux/utils';
|
||||
import { Button, Col, Form, ControlLabel } from 'react-bootstrap';
|
||||
|
||||
import SimulationModelStore from '../stores/simulation-model-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
|
||||
import SelectSimulator from './select-simulator';
|
||||
import SelectFile from './select-file';
|
||||
import SignalMapping from '../components/signal-mapping';
|
||||
import EditableHeader from '../components/editable-header';
|
||||
import ParametersEditor from '../components/parameters-editor';
|
||||
|
||||
class SimulationModel extends React.Component {
|
||||
static getStores() {
|
||||
return [ SimulationModelStore, UserStore ];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
const simulationModel = SimulationModelStore.getState().find(m => m._id === props.match.params.simulationModel);
|
||||
|
||||
return {
|
||||
simulationModel: simulationModel || {},
|
||||
sessionToken: UserStore.getState().token
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-load',
|
||||
data: this.props.match.params.simulationModel,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
submitForm = event => {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
saveChanges = () => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-edit',
|
||||
data: this.state.simulationModel,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
this.props.history.push('/simulations/' + this.state.simulationModel.simulation);
|
||||
}
|
||||
|
||||
discardChanges = () => {
|
||||
this.props.history.push('/simulations/' + this.state.simulationModel.simulation);
|
||||
}
|
||||
|
||||
handleSimulatorChange = simulator => {
|
||||
const simulationModel = this.state.simulationModel;
|
||||
|
||||
simulationModel.simulator = simulator;
|
||||
|
||||
this.setState({ simulationModel });
|
||||
}
|
||||
|
||||
handleModelChange = file => {
|
||||
console.log(file);
|
||||
}
|
||||
|
||||
handleConfigurationChange = file => {
|
||||
console.log(file);
|
||||
}
|
||||
|
||||
handleOutputMappingChange = (length, signals) => {
|
||||
const simulationModel = this.state.simulationModel;
|
||||
|
||||
simulationModel.outputMapping = signals;
|
||||
simulationModel.outputLength = length;
|
||||
|
||||
this.setState({ simulationModel });
|
||||
}
|
||||
|
||||
handleInputMappingChange = (length, signals) => {
|
||||
const simulationModel = this.state.simulationModel;
|
||||
|
||||
simulationModel.inputMapping = signals;
|
||||
simulationModel.inputLength = length;
|
||||
|
||||
this.setState({ simulationModel });
|
||||
}
|
||||
|
||||
handleTitleChange = title => {
|
||||
const simulationModel = this.state.simulationModel;
|
||||
|
||||
simulationModel.name = title;
|
||||
|
||||
this.setState({ simulationModel });
|
||||
}
|
||||
|
||||
handleStartParametersChange = startParameters => {
|
||||
const simulationModel = this.state.simulationModel;
|
||||
|
||||
simulationModel.startParameters = startParameters;
|
||||
|
||||
this.setState({ simulationModel });
|
||||
}
|
||||
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
marginRight: '10px'
|
||||
};
|
||||
|
||||
return <div className='section'>
|
||||
<EditableHeader title={this.state.simulationModel.name} onChange={this.handleTitleChange} />
|
||||
|
||||
<Form horizontal onSubmit={this.submitForm}>
|
||||
<Col xs={12} sm={12}>
|
||||
<SelectSimulator onChange={this.handleSimulatorChange} value={this.state.simulationModel.simulator} />
|
||||
|
||||
<SelectFile disabled type='model' name='Model' onChange={this.handleModelChange} value={this.state.simulationModel.model} />
|
||||
|
||||
<SelectFile disabled type='configuration' name='Configuration' onChange={this.handleConfigurationChange} value={this.state.simulationModel.configuration} />
|
||||
|
||||
<div>
|
||||
<Col componentClass={ControlLabel} sm={3} md={2}>
|
||||
Start Parameters
|
||||
</Col>
|
||||
|
||||
<Col sm={9} md={10}>
|
||||
<ParametersEditor content={this.state.simulationModel.startParameters} onChange={this.handleStartParametersChange} />
|
||||
</Col>
|
||||
</div>
|
||||
|
||||
</Col>
|
||||
|
||||
<Col xs={12} sm={6}>
|
||||
<SignalMapping name='Output' length={this.state.simulationModel.outputLength} signals={this.state.simulationModel.outputMapping} onChange={this.handleOutputMappingChange} />
|
||||
</Col>
|
||||
|
||||
<Col xs={12} sm={6}>
|
||||
<SignalMapping name='Input' length={this.state.simulationModel.inputLength} signals={this.state.simulationModel.inputMapping} onChange={this.handleInputMappingChange} />
|
||||
</Col>
|
||||
|
||||
<div style={{ clear: 'both' }}></div>
|
||||
|
||||
<Button onClick={this.discardChanges} style={buttonStyle}>Cancel</Button>
|
||||
<Button bsStyle='primary' onClick={this.saveChanges} style={buttonStyle}>Save</Button>
|
||||
</Form>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(SimulationModel, { withProps: true });
|
|
@ -1,288 +0,0 @@
|
|||
/**
|
||||
* File: simulation.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 04.03.2017
|
||||
*
|
||||
* 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 { Container } from 'flux/utils';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import FileSaver from 'file-saver';
|
||||
import _ from 'lodash';
|
||||
|
||||
import SimulationStore from '../stores/simulation-store';
|
||||
import SimulatorStore from '../stores/simulator-store';
|
||||
import SimulationModelStore from '../stores/simulation-model-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
|
||||
import Icon from '../components/icon';
|
||||
import Table from '../components/table';
|
||||
import TableColumn from '../components/table-column';
|
||||
import ImportSimulationModelDialog from '../components/dialogs/import-simulation-model';
|
||||
|
||||
import SimulatorAction from '../components/simulator-action';
|
||||
import DeleteDialog from '../components/dialogs/delete-dialog';
|
||||
|
||||
class Simulation extends React.Component {
|
||||
static getStores() {
|
||||
return [ SimulationStore, SimulatorStore, SimulationModelStore, UserStore ];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
// get selected simulation
|
||||
const sessionToken = UserStore.getState().token;
|
||||
|
||||
let simulation = SimulationStore.getState().find(s => s._id === props.match.params.simulation);
|
||||
if (simulation == null) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-load',
|
||||
data: props.match.params.simulation,
|
||||
token: sessionToken
|
||||
});
|
||||
|
||||
simulation = {};
|
||||
}
|
||||
|
||||
// load models
|
||||
let simulationModels = [];
|
||||
if (simulation.models != null) {
|
||||
simulationModels = SimulationModelStore.getState().filter(m => m != null && simulation.models.includes(m._id));
|
||||
}
|
||||
|
||||
return {
|
||||
simulationModels,
|
||||
simulation,
|
||||
|
||||
simulators: SimulatorStore.getState(),
|
||||
sessionToken,
|
||||
|
||||
deleteModal: false,
|
||||
importModal: false,
|
||||
modalData: {},
|
||||
|
||||
selectedSimulationModels: []
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
addSimulationModel = () => {
|
||||
const simulationModel = {
|
||||
simulation: this.state.simulation._id,
|
||||
name: 'New Simulation Model',
|
||||
simulator: this.state.simulators.length > 0 ? this.state.simulators[0]._id : null,
|
||||
outputLength: 1,
|
||||
outputMapping: [{ name: 'Signal', type: 'Type' }],
|
||||
inputLength: 1,
|
||||
inputMapping: [{ name: 'Signal', type: 'Type' }]
|
||||
};
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-add',
|
||||
data: simulationModel,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
this.setState({ simulation: {} }, () => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-load',
|
||||
data: this.props.match.params.simulation,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
closeDeleteModal = confirmDelete => {
|
||||
this.setState({ deleteModal: false });
|
||||
|
||||
if (confirmDelete === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-remove',
|
||||
data: this.state.modalData,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
importSimulationModel = simulationModel => {
|
||||
this.setState({ importModal: false });
|
||||
|
||||
if (simulationModel == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
simulationModel.simulation = this.state.simulation._id;
|
||||
|
||||
console.log(simulationModel);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-add',
|
||||
data: simulationModel,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
this.setState({ simulation: {} }, () => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-load',
|
||||
data: this.props.match.params.simulation,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getSimulatorName(simulatorId) {
|
||||
for (let simulator of this.state.simulators) {
|
||||
if (simulator._id === simulatorId) {
|
||||
return _.get(simulator, 'properties.name') || _.get(simulator, 'rawProperties.name') || simulator.uuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exportModel(index) {
|
||||
// filter properties
|
||||
const model = Object.assign({}, this.state.simulationModels[index]);
|
||||
|
||||
delete model.simulator;
|
||||
delete model.simulation;
|
||||
|
||||
// show save dialog
|
||||
const blob = new Blob([JSON.stringify(model, null, 2)], { type: 'application/json' });
|
||||
FileSaver.saveAs(blob, 'simulation model - ' + model.name + '.json');
|
||||
}
|
||||
|
||||
onSimulationModelChecked(index, event) {
|
||||
const selectedSimulationModels = Object.assign([], this.state.selectedSimulationModels);
|
||||
for (let key in selectedSimulationModels) {
|
||||
if (selectedSimulationModels[key] === index) {
|
||||
// update existing entry
|
||||
if (event.target.checked) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedSimulationModels.splice(key, 1);
|
||||
|
||||
this.setState({ selectedSimulationModels });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// add new entry
|
||||
if (event.target.checked === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedSimulationModels.push(index);
|
||||
this.setState({ selectedSimulationModels });
|
||||
}
|
||||
|
||||
runAction = action => {
|
||||
for (let index of this.state.selectedSimulationModels) {
|
||||
// get simulator for model
|
||||
let simulator = null;
|
||||
for (let sim of this.state.simulators) {
|
||||
if (sim._id === this.state.simulationModels[index].simulator) {
|
||||
simulator = sim;
|
||||
}
|
||||
}
|
||||
|
||||
if (simulator == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (action.data.action === 'start') {
|
||||
action.data.parameters = this.state.simulationModels[index].startParameters;
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-action',
|
||||
simulator,
|
||||
data: action.data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
marginLeft: '10px'
|
||||
};
|
||||
|
||||
return <div className='section'>
|
||||
<h1>{this.state.simulation.name}</h1>
|
||||
|
||||
<Table data={this.state.simulationModels}>
|
||||
<TableColumn checkbox onChecked={(index, event) => this.onSimulationModelChecked(index, event)} width='30' />
|
||||
<TableColumn title='Name' dataKey='name' link='/simulationModel/' linkKey='_id' />
|
||||
<TableColumn title='Simulator' dataKey='simulator' modifier={(simulator) => this.getSimulatorName(simulator)} />
|
||||
<TableColumn title='Output' dataKey='outputLength' width='100' />
|
||||
<TableColumn title='Input' dataKey='inputLength' width='100' />
|
||||
<TableColumn
|
||||
title=''
|
||||
width='70'
|
||||
deleteButton
|
||||
exportButton
|
||||
onDelete={(index) => this.setState({ deleteModal: true, modalData: this.state.simulationModels[index], modalIndex: index })}
|
||||
onExport={index => this.exportModel(index)}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
<div style={{ float: 'left' }}>
|
||||
<SimulatorAction
|
||||
runDisabled={this.state.selectedSimulationModels.length === 0}
|
||||
runAction={this.runAction}
|
||||
actions={[
|
||||
{ id: '0', title: 'Start', data: { action: 'start' } },
|
||||
{ id: '1', title: 'Stop', data: { action: 'stop' } },
|
||||
{ id: '2', title: 'Pause', data: { action: 'pause' } },
|
||||
{ id: '3', title: 'Resume', data: { action: 'resume' } }
|
||||
]}/>
|
||||
</div>
|
||||
|
||||
<div style={{ float: 'right' }}>
|
||||
<Button onClick={this.addSimulationModel} style={buttonStyle}><Icon icon="plus" /> Simulation Model</Button>
|
||||
<Button onClick={() => this.setState({ importModal: true })} style={buttonStyle}><Icon icon="upload" /> Import</Button>
|
||||
</div>
|
||||
|
||||
<div style={{ clear: 'both' }} />
|
||||
|
||||
<ImportSimulationModelDialog show={this.state.importModal} onClose={this.importSimulationModel} simulators={this.state.simulators} />
|
||||
|
||||
<DeleteDialog title="simulation model" name={this.state.modalData.name} show={this.state.deleteModal} onClose={this.closeDeleteModal} />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(Simulation, { withProps: true });
|
|
@ -1,328 +0,0 @@
|
|||
/**
|
||||
* File: simulations.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 04.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import FileSaver from 'file-saver';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import SimulationStore from '../stores/simulation-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
import SimulatorStore from '../stores/simulator-store';
|
||||
import SimulationModelStore from '../stores/simulation-model-store';
|
||||
|
||||
import Icon from '../components/icon';
|
||||
import Table from '../components/table';
|
||||
import TableColumn from '../components/table-column';
|
||||
import NewSimulationDialog from '../components/dialogs/new-simulation';
|
||||
import EditSimulationDialog from '../components/dialogs/edit-simulation';
|
||||
import ImportSimulationDialog from '../components/dialogs/import-simulation';
|
||||
|
||||
import SimulatorAction from '../components/simulator-action';
|
||||
import DeleteDialog from '../components/dialogs/delete-dialog';
|
||||
|
||||
class Simulations extends Component {
|
||||
static getStores() {
|
||||
return [ SimulationStore, UserStore, SimulatorStore, SimulationModelStore ];
|
||||
}
|
||||
|
||||
static calculateState() {
|
||||
const simulations = SimulationStore.getState();
|
||||
const simulationModels = SimulationModelStore.getState();
|
||||
const simulators = SimulatorStore.getState();
|
||||
|
||||
const sessionToken = UserStore.getState().token;
|
||||
|
||||
return {
|
||||
simulations,
|
||||
simulationModels,
|
||||
simulators,
|
||||
sessionToken,
|
||||
|
||||
newModal: false,
|
||||
deleteModal: false,
|
||||
editModal: false,
|
||||
importModal: false,
|
||||
modalSimulation: {},
|
||||
|
||||
selectedSimulations: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const simulationModelIds = [];
|
||||
const simulatorIds = [];
|
||||
|
||||
for (let simulation of this.state.simulations) {
|
||||
for (let modelId of simulation.models) {
|
||||
const model = this.state.simulationModels.find(m => m != null && m._id === modelId);
|
||||
|
||||
if (model == null) {
|
||||
simulationModelIds.push(modelId);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.state.simulators.includes(s => s._id === model.simulator) === false) {
|
||||
simulatorIds.push(model.simulator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (simulationModelIds.length > 0) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-load',
|
||||
data: simulationModelIds,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
if (simulatorIds.length > 0) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-load',
|
||||
data: simulatorIds,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
closeNewModal(data) {
|
||||
this.setState({ newModal : false });
|
||||
|
||||
if (data) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-add',
|
||||
data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showDeleteModal(id) {
|
||||
// get simulation by id
|
||||
var deleteSimulation;
|
||||
|
||||
this.state.simulations.forEach((simulation) => {
|
||||
if (simulation._id === id) {
|
||||
deleteSimulation = simulation;
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({ deleteModal: true, modalSimulation: deleteSimulation });
|
||||
}
|
||||
|
||||
closeDeleteModal = confirmDelete => {
|
||||
this.setState({ deleteModal: false });
|
||||
|
||||
if (confirmDelete === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-remove',
|
||||
data: this.state.modalSimulation,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
showEditModal(id) {
|
||||
// get simulation by id
|
||||
var editSimulation;
|
||||
|
||||
this.state.simulations.forEach((simulation) => {
|
||||
if (simulation._id === id) {
|
||||
editSimulation = simulation;
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({ editModal: true, modalSimulation: editSimulation });
|
||||
}
|
||||
|
||||
closeEditModal(data) {
|
||||
this.setState({ editModal : false });
|
||||
|
||||
if (data != null) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-edit',
|
||||
data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
closeImportModal(data) {
|
||||
this.setState({ importModal: false });
|
||||
|
||||
if (data) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-add',
|
||||
data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onModalKeyPress = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
|
||||
this.confirmDeleteModal();
|
||||
}
|
||||
}
|
||||
|
||||
exportSimulation(index) {
|
||||
// filter properties
|
||||
let simulation = Object.assign({}, this.state.simulations[index]);
|
||||
delete simulation._id;
|
||||
delete simulation.projects;
|
||||
delete simulation.running;
|
||||
delete simulation.user;
|
||||
|
||||
simulation.models.forEach(model => {
|
||||
delete model.simulator;
|
||||
});
|
||||
|
||||
// show save dialog
|
||||
const blob = new Blob([JSON.stringify(simulation, null, 2)], { type: 'application/json' });
|
||||
FileSaver.saveAs(blob, 'simulation - ' + simulation.name + '.json');
|
||||
}
|
||||
|
||||
onSimulationChecked(index, event) {
|
||||
const selectedSimulations = Object.assign([], this.state.selectedSimulations);
|
||||
for (let key in selectedSimulations) {
|
||||
if (selectedSimulations[key] === index) {
|
||||
// update existing entry
|
||||
if (event.target.checked) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedSimulations.splice(key, 1);
|
||||
|
||||
this.setState({ selectedSimulations });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// add new entry
|
||||
if (event.target.checked === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedSimulations.push(index);
|
||||
this.setState({ selectedSimulations });
|
||||
}
|
||||
|
||||
runAction = action => {
|
||||
for (let index of this.state.selectedSimulations) {
|
||||
for (let model of this.state.simulations[index].models) {
|
||||
// get simulation model
|
||||
const simulationModel = this.state.simulationModels.find(m => m != null && m._id === model);
|
||||
if (simulationModel == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get simulator for model
|
||||
let simulator = null;
|
||||
for (let sim of this.state.simulators) {
|
||||
if (sim._id === simulationModel.simulator) {
|
||||
simulator = sim;
|
||||
}
|
||||
}
|
||||
|
||||
if (simulator == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (action.data.action === 'start') {
|
||||
action.data.parameters = Object.assign({}, this.state.simulations[index].startParameters, simulationModel.startParameters);
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-action',
|
||||
simulator,
|
||||
data: action.data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
marginLeft: '10px'
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='section'>
|
||||
<h1>Simulations</h1>
|
||||
|
||||
<Table data={this.state.simulations}>
|
||||
<TableColumn checkbox onChecked={(index, event) => this.onSimulationChecked(index, event)} width='30' />
|
||||
<TableColumn title='Name' dataKey='name' link='/simulations/' linkKey='_id' />
|
||||
<TableColumn
|
||||
width='100'
|
||||
editButton
|
||||
deleteButton
|
||||
exportButton
|
||||
onEdit={index => this.setState({ editModal: true, modalSimulation: this.state.simulations[index] })}
|
||||
onDelete={index => this.setState({ deleteModal: true, modalSimulation: this.state.simulations[index] })}
|
||||
onExport={index => this.exportSimulation(index)}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
<div style={{ float: 'left' }}>
|
||||
<SimulatorAction
|
||||
runDisabled={this.state.selectedSimulations.length === 0}
|
||||
runAction={this.runAction}
|
||||
actions={[
|
||||
{ id: '0', title: 'Start', data: { action: 'start' } },
|
||||
{ id: '1', title: 'Stop', data: { action: 'stop' } },
|
||||
{ id: '2', title: 'Pause', data: { action: 'pause' } },
|
||||
{ id: '3', title: 'Resume', data: { action: 'resume' } }
|
||||
]}/>
|
||||
</div>
|
||||
|
||||
<div style={{ float: 'right' }}>
|
||||
<Button onClick={() => this.setState({ newModal: true })} style={buttonStyle}><Icon icon="plus" /> Simulation</Button>
|
||||
<Button onClick={() => this.setState({ importModal: true })} style={buttonStyle}><Icon icon="upload" /> Import</Button>
|
||||
</div>
|
||||
|
||||
<div style={{ clear: 'both' }} />
|
||||
|
||||
<NewSimulationDialog show={this.state.newModal} onClose={(data) => this.closeNewModal(data)} />
|
||||
<EditSimulationDialog show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} simulation={this.state.modalSimulation} />
|
||||
<ImportSimulationDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} nodes={this.state.nodes} />
|
||||
|
||||
<DeleteDialog title="simulation" name={this.state.modalSimulation.name} show={this.state.deleteModal} onClose={this.closeDeleteModal} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(Simulations);
|
|
@ -1,313 +0,0 @@
|
|||
/**
|
||||
* File: simulators.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import FileSaver from 'file-saver';
|
||||
import _ from 'lodash';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import SimulatorStore from '../stores/simulator-store';
|
||||
import UserStore from '../stores/user-store';
|
||||
|
||||
import Icon from '../components/icon';
|
||||
import Table from '../components/table';
|
||||
import TableColumn from '../components/table-column';
|
||||
import NewSimulatorDialog from '../components/dialogs/new-simulator';
|
||||
import EditSimulatorDialog from '../components/dialogs/edit-simulator';
|
||||
import ImportSimulatorDialog from '../components/dialogs/import-simulator';
|
||||
|
||||
import SimulatorAction from '../components/simulator-action';
|
||||
import DeleteDialog from '../components/dialogs/delete-dialog';
|
||||
|
||||
class Simulators extends Component {
|
||||
static getStores() {
|
||||
return [ UserStore, SimulatorStore ];
|
||||
}
|
||||
|
||||
static statePrio(state) {
|
||||
switch (state) {
|
||||
case 'running':
|
||||
case 'starting':
|
||||
return 1;
|
||||
case 'paused':
|
||||
case 'pausing':
|
||||
case 'resuming':
|
||||
return 2;
|
||||
case 'idle':
|
||||
return 3;
|
||||
case 'shutdown':
|
||||
return 4;
|
||||
case 'error':
|
||||
return 10;
|
||||
default:
|
||||
return 99;
|
||||
}
|
||||
}
|
||||
|
||||
static calculateState() {
|
||||
const simulators = SimulatorStore.getState().sort((a, b) => {
|
||||
if (a.state !== b.state)
|
||||
return this.statePrio(a.state) > this.statePrio(b.state);
|
||||
else if (a.name !== b.name)
|
||||
return a.name < b.name;
|
||||
else
|
||||
return a.stateUpdatedAt < b.stateUpdatedAt;
|
||||
});
|
||||
|
||||
return {
|
||||
sessionToken: UserStore.getState().token,
|
||||
simulators,
|
||||
modalSimulator: {},
|
||||
deleteModal: false,
|
||||
|
||||
selectedSimulators: []
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
// Start timer for periodic refresh
|
||||
this.timer = window.setInterval(() => this.refresh(), 1000);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.clearInterval(this.timer);
|
||||
}
|
||||
|
||||
refresh() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
closeNewModal(data) {
|
||||
this.setState({ newModal : false });
|
||||
|
||||
if (data) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-add',
|
||||
data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
closeEditModal(data) {
|
||||
this.setState({ editModal : false });
|
||||
|
||||
if (data) {
|
||||
let simulator = this.state.simulators[this.state.modalIndex];
|
||||
simulator.properties = data;
|
||||
this.setState({ simulator });
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-edit',
|
||||
data: simulator,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
closeDeleteModal = confirmDelete => {
|
||||
this.setState({ deleteModal: false });
|
||||
|
||||
if (confirmDelete === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-remove',
|
||||
data: this.state.modalSimulator,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
exportSimulator(index) {
|
||||
// filter properties
|
||||
let simulator = Object.assign({}, this.state.simulators[index]);
|
||||
delete simulator._id;
|
||||
|
||||
// show save dialog
|
||||
const blob = new Blob([JSON.stringify(simulator, null, 2)], { type: 'application/json' });
|
||||
FileSaver.saveAs(blob, 'simulator - ' + (_.get(simulator, 'properties.name') || _.get(simulator, 'rawProperties.name') || 'undefined') + '.json');
|
||||
}
|
||||
|
||||
closeImportModal(data) {
|
||||
this.setState({ importModal: false });
|
||||
|
||||
if (data) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-add',
|
||||
data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onSimulatorChecked(index, event) {
|
||||
const selectedSimulators = Object.assign([], this.state.selectedSimulators);
|
||||
for (let key in selectedSimulators) {
|
||||
if (selectedSimulators[key] === index) {
|
||||
// update existing entry
|
||||
if (event.target.checked) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedSimulators.splice(key, 1);
|
||||
|
||||
this.setState({ selectedSimulators });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// add new entry
|
||||
if (event.target.checked === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedSimulators.push(index);
|
||||
this.setState({ selectedSimulators });
|
||||
}
|
||||
|
||||
runAction = action => {
|
||||
for (let index of this.state.selectedSimulators) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-action',
|
||||
simulator: this.state.simulators[index],
|
||||
data: action.data,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
isSimulatorOutdated(simulator) {
|
||||
if (!simulator.stateUpdatedAt)
|
||||
return true;
|
||||
|
||||
const fiveMinutes = 5 * 60 * 1000;
|
||||
|
||||
return Date.now() - new Date(simulator.stateUpdatedAt) > fiveMinutes;
|
||||
}
|
||||
|
||||
stateLabelStyle = (state, simulator) => {
|
||||
var style = [ 'label' ];
|
||||
|
||||
if (this.isSimulatorOutdated(simulator) && state !== 'shutdown') {
|
||||
style.push('label-outdated');
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case 'running':
|
||||
style.push('label-success');
|
||||
break;
|
||||
|
||||
case 'paused':
|
||||
style.push('label-info');
|
||||
break;
|
||||
|
||||
case 'idle':
|
||||
style.push('label-primary');
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
style.push('label-danger');
|
||||
break;
|
||||
|
||||
case 'shutdown':
|
||||
style.push('label-warning');
|
||||
break;
|
||||
|
||||
default:
|
||||
style.push('label-default');
|
||||
}
|
||||
|
||||
return style.join(' ');
|
||||
}
|
||||
|
||||
stateUpdateModifier = updatedAt => {
|
||||
const date = new Date(updatedAt);
|
||||
|
||||
return date.toLocaleString('de-DE');
|
||||
}
|
||||
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
marginLeft: '10px'
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='section'>
|
||||
<h1>Simulators</h1>
|
||||
|
||||
<Table data={this.state.simulators}>
|
||||
<TableColumn checkbox onChecked={(index, event) => this.onSimulatorChecked(index, event)} width='30' />
|
||||
<TableColumn title='Name' dataKeys={['properties.name', 'rawProperties.name']} />
|
||||
<TableColumn title='State' labelKey='state' tooltipKey='error' labelModifier={this.stateLabelModifier} labelStyle={this.stateLabelStyle} />
|
||||
<TableColumn title='Category' dataKeys={['properties.category', 'rawProperties.category']} />
|
||||
<TableColumn title='Type' dataKeys={['properties.type', 'rawProperties.type']} />
|
||||
<TableColumn title='Location' dataKeys={['properties.location', 'rawProperties.location']} />
|
||||
{/* <TableColumn title='Realm' dataKeys={['properties.realm', 'rawProperties.realm']} /> */}
|
||||
<TableColumn title='Host' dataKey='host' />
|
||||
<TableColumn title='Last Update' dataKey='stateUpdatedAt' modifier={this.stateUpdateModifier} />
|
||||
<TableColumn
|
||||
width='100'
|
||||
editButton
|
||||
exportButton
|
||||
deleteButton
|
||||
onEdit={index => this.setState({ editModal: true, modalSimulator: this.state.simulators[index], modalIndex: index })}
|
||||
onExport={index => this.exportSimulator(index)}
|
||||
onDelete={index => this.setState({ deleteModal: true, modalSimulator: this.state.simulators[index], modalIndex: index })}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
<div style={{ float: 'left' }}>
|
||||
<SimulatorAction
|
||||
runDisabled={this.state.selectedSimulators.length === 0}
|
||||
runAction={this.runAction}
|
||||
actions={[ { id: '0', title: 'Reset', data: { action: 'reset' } }, { id: '1', title: 'Shutdown', data: { action: 'shutdown' } } ]}/>
|
||||
</div>
|
||||
|
||||
<div style={{ float: 'right' }}>
|
||||
<Button onClick={() => this.setState({ newModal: true })} style={buttonStyle}><Icon icon="plus" /> Simulator</Button>
|
||||
<Button onClick={() => this.setState({ importModal: true })} style={buttonStyle}><Icon icon="upload" /> Import</Button>
|
||||
</div>
|
||||
|
||||
<div style={{ clear: 'both' }} />
|
||||
|
||||
<NewSimulatorDialog show={this.state.newModal} onClose={data => this.closeNewModal(data)} />
|
||||
<EditSimulatorDialog show={this.state.editModal} onClose={data => this.closeEditModal(data)} simulator={this.state.modalSimulator} />
|
||||
<ImportSimulatorDialog show={this.state.importModal} onClose={data => this.closeImportModal(data)} />
|
||||
|
||||
<DeleteDialog title="simulator" name={_.get(this.state.modalSimulator, 'properties.name') || _.get(this.state.modalSimulator, 'rawProperties.name') || 'Unknown'} show={this.state.deleteModal} onClose={this.closeDeleteModal} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(Simulators);
|
|
@ -1,550 +0,0 @@
|
|||
/**
|
||||
* File: visualization.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
*
|
||||
* 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 { Container } from 'flux/utils';
|
||||
import { Button, ButtonToolbar } from 'react-bootstrap';
|
||||
import { ContextMenu, Item, Separator } from 'react-contexify';
|
||||
import Fullscreenable from 'react-fullscreenable';
|
||||
import Slider from 'rc-slider';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import Icon from '../components/icon';
|
||||
import WidgetFactory from '../components/widget-factory';
|
||||
import ToolboxItem from '../components/toolbox-item';
|
||||
import Dropzone from '../components/dropzone';
|
||||
import Widget from './widget';
|
||||
import EditWidget from '../components/dialogs/edit-widget';
|
||||
import Grid from '../components/grid';
|
||||
|
||||
import UserStore from '../stores/user-store';
|
||||
import VisualizationStore from '../stores/visualization-store';
|
||||
import ProjectStore from '../stores/project-store';
|
||||
import SimulationStore from '../stores/simulation-store';
|
||||
import SimulationModelStore from '../stores/simulation-model-store';
|
||||
import FileStore from '../stores/file-store';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import NotificationsDataManager from '../data-managers/notifications-data-manager';
|
||||
import NotificationsFactory from '../data-managers/notifications-factory';
|
||||
|
||||
import 'react-contexify/dist/ReactContexify.min.css';
|
||||
|
||||
class Visualization extends React.Component {
|
||||
static getStores() {
|
||||
return [ VisualizationStore, ProjectStore, SimulationStore, SimulationModelStore, FileStore, UserStore ];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
if (prevState == null) {
|
||||
prevState = {};
|
||||
}
|
||||
|
||||
let simulationModels = [];
|
||||
if (prevState.simulation != null) {
|
||||
simulationModels = SimulationModelStore.getState().filter(m => prevState.simulation.models.includes(m._id));
|
||||
}
|
||||
|
||||
return {
|
||||
sessionToken: UserStore.getState().token,
|
||||
visualizations: VisualizationStore.getState(),
|
||||
projects: ProjectStore.getState(),
|
||||
simulations: SimulationStore.getState(),
|
||||
files: FileStore.getState(),
|
||||
|
||||
visualization: prevState.visualization || {},
|
||||
project: prevState.project || null,
|
||||
simulation: prevState.simulation || null,
|
||||
simulationModels,
|
||||
editing: prevState.editing || false,
|
||||
paused: prevState.paused || false,
|
||||
|
||||
editModal: prevState.editModal || false,
|
||||
modalData: prevState.modalData || null,
|
||||
modalIndex: prevState.modalIndex || null,
|
||||
|
||||
maxWidgetHeight: prevState.maxWidgetHeight || 0,
|
||||
dropZoneHeight: prevState.dropZoneHeight || 0
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// TODO: Don't fetch token from local, use user-store!
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
//document.addEventListener('keydown', this.handleKeydown.bind(this));
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-load',
|
||||
token
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
//document.removeEventListener('keydown', this.handleKeydown.bind(this));
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.state.visualization._id !== this.props.match.params.visualization) {
|
||||
this.reloadVisualization();
|
||||
}
|
||||
|
||||
// load depending project
|
||||
if (this.state.project == null && this.state.projects) {
|
||||
this.state.projects.forEach((project) => {
|
||||
if (project._id === this.state.visualization.project) {
|
||||
this.setState({ project: project, simulation: null });
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-load',
|
||||
data: project.simulation,
|
||||
token
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// load depending simulation
|
||||
if (this.state.simulation == null && this.state.simulations && this.state.project) {
|
||||
this.state.simulations.forEach((simulation) => {
|
||||
if (simulation._id === this.state.project.simulation) {
|
||||
this.setState({ simulation: simulation });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*handleKeydown(e) {
|
||||
switch (e.key) {
|
||||
case ' ':
|
||||
case 'p':
|
||||
this.setState({ paused: !this.state.paused });
|
||||
break;
|
||||
case 'e':
|
||||
this.setState({ editing: !this.state.editing });
|
||||
break;
|
||||
case 'f':
|
||||
this.props.toggleFullscreen();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}*/
|
||||
|
||||
transformToWidgetsDict(widgets) {
|
||||
var widgetsDict = {};
|
||||
// Create a new key and make a copy of the widget object
|
||||
var key = 0;
|
||||
widgets.forEach( (widget) => widgetsDict[key++] = Object.assign({}, widget) );
|
||||
return widgetsDict;
|
||||
}
|
||||
|
||||
transformToWidgetsList(widgets) {
|
||||
return Object.keys(widgets).map( (key) => widgets[key]);
|
||||
}
|
||||
|
||||
reloadVisualization() {
|
||||
// select visualization by param id
|
||||
this.state.visualizations.forEach((tempVisualization) => {
|
||||
if (tempVisualization._id === this.props.match.params.visualization) {
|
||||
|
||||
// convert widgets list to a dictionary
|
||||
var visualization = Object.assign({}, tempVisualization, {
|
||||
widgets: tempVisualization.widgets ? this.transformToWidgetsDict(tempVisualization.widgets) : {}
|
||||
});
|
||||
|
||||
this.computeHeightWithWidgets(visualization.widgets);
|
||||
|
||||
this.setState({ visualization: visualization, project: null });
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'projects/start-load',
|
||||
data: visualization.project,
|
||||
token
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
snapToGrid(value) {
|
||||
if (this.state.visualization.grid === 1) return value;
|
||||
|
||||
return Math.round(value / this.state.visualization.grid) * this.state.visualization.grid;
|
||||
}
|
||||
|
||||
handleDrop(item, position) {
|
||||
|
||||
let widget = null;
|
||||
let defaultSimulationModel = null;
|
||||
|
||||
if (this.state.simulation.models && this.state.simulation.models.length === 0) {
|
||||
NotificationsDataManager.addNotification(NotificationsFactory.NO_SIM_MODEL_AVAILABLE);
|
||||
} else {
|
||||
defaultSimulationModel = this.state.simulation.models[0];
|
||||
}
|
||||
|
||||
// snap position to grid
|
||||
position.x = this.snapToGrid(position.x);
|
||||
position.y = this.snapToGrid(position.y);
|
||||
|
||||
// create new widget
|
||||
widget = WidgetFactory.createWidgetOfType(item.name, position, defaultSimulationModel);
|
||||
|
||||
var new_widgets = this.state.visualization.widgets;
|
||||
var new_key = Object.keys(new_widgets).length;
|
||||
|
||||
new_widgets[new_key] = widget;
|
||||
|
||||
var visualization = Object.assign({}, this.state.visualization, {
|
||||
widgets: new_widgets
|
||||
});
|
||||
|
||||
this.increaseHeightWithWidget(widget);
|
||||
this.setState({ visualization: visualization });
|
||||
}
|
||||
|
||||
widgetStatusChange(updated_widget, key) {
|
||||
// Widget changed internally, make changes effective then save them
|
||||
this.widgetChange(updated_widget, key, this.saveChanges);
|
||||
}
|
||||
|
||||
widgetChange(updated_widget, key, callback = null) {
|
||||
var widgets_update = {};
|
||||
widgets_update[key] = updated_widget;
|
||||
var new_widgets = Object.assign({}, this.state.visualization.widgets, widgets_update);
|
||||
|
||||
var visualization = Object.assign({}, this.state.visualization, {
|
||||
widgets: new_widgets
|
||||
});
|
||||
|
||||
// Check if the height needs to be increased, the section may have shrunk if not
|
||||
if (!this.increaseHeightWithWidget(updated_widget)) {
|
||||
this.computeHeightWithWidgets(visualization.widgets);
|
||||
}
|
||||
this.setState({ visualization: visualization }, callback);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the initial height state based on the existing widgets
|
||||
*/
|
||||
computeHeightWithWidgets(widgets) {
|
||||
// Compute max height from widgets
|
||||
let maxHeight = Object.keys(widgets).reduce( (maxHeightSoFar, widgetKey) => {
|
||||
let thisWidget = widgets[widgetKey];
|
||||
let thisWidgetHeight = thisWidget.y + thisWidget.height;
|
||||
|
||||
return thisWidgetHeight > maxHeightSoFar? thisWidgetHeight : maxHeightSoFar;
|
||||
}, 0);
|
||||
|
||||
this.setState({
|
||||
maxWidgetHeight: maxHeight,
|
||||
dropZoneHeight: maxHeight + 80
|
||||
});
|
||||
}
|
||||
/*
|
||||
* Adapt the area's height with the position of the new widget.
|
||||
* Return true if the height increased, otherwise false.
|
||||
*/
|
||||
increaseHeightWithWidget(widget) {
|
||||
let increased = false;
|
||||
let thisWidgetHeight = widget.y + widget.height;
|
||||
if (thisWidgetHeight > this.state.maxWidgetHeight) {
|
||||
increased = true;
|
||||
this.setState({
|
||||
maxWidgetHeight: thisWidgetHeight,
|
||||
dropZoneHeight: thisWidgetHeight + 40
|
||||
});
|
||||
}
|
||||
return increased;
|
||||
}
|
||||
|
||||
editWidget(e, data) {
|
||||
this.setState({ editModal: true, modalData: this.state.visualization.widgets[data.key], modalIndex: data.key });
|
||||
}
|
||||
|
||||
closeEdit(data) {
|
||||
if (data) {
|
||||
// save changes temporarily
|
||||
var widgets_update = {};
|
||||
widgets_update[this.state.modalIndex] = data;
|
||||
|
||||
var new_widgets = Object.assign({}, this.state.visualization.widgets, widgets_update);
|
||||
|
||||
var visualization = Object.assign({}, this.state.visualization, {
|
||||
widgets: new_widgets
|
||||
});
|
||||
|
||||
this.setState({ editModal: false, visualization: visualization });
|
||||
} else {
|
||||
this.setState({ editModal: false });
|
||||
}
|
||||
}
|
||||
|
||||
deleteWidget(e, data) {
|
||||
delete this.state.visualization.widgets[data.key];
|
||||
var visualization = Object.assign({}, this.state.visualization, {
|
||||
widgets: this.state.visualization.widgets
|
||||
});
|
||||
this.setState({ visualization: visualization });
|
||||
}
|
||||
|
||||
stopEditing() {
|
||||
// Provide the callback so it can be called when state change is applied
|
||||
this.setState({ editing: false }, this.saveChanges );
|
||||
}
|
||||
|
||||
saveChanges() {
|
||||
// Transform to a list
|
||||
var visualization = Object.assign({}, this.state.visualization, {
|
||||
widgets: this.transformToWidgetsList(this.state.visualization.widgets)
|
||||
});
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-edit',
|
||||
data: visualization,
|
||||
token
|
||||
});
|
||||
}
|
||||
|
||||
discardChanges() {
|
||||
this.setState({ editing: false, visualization: {} });
|
||||
|
||||
this.reloadVisualization();
|
||||
}
|
||||
|
||||
moveWidget(e, data, applyDirection) {
|
||||
var widget = this.state.visualization.widgets[data.key];
|
||||
var updated_widgets = {};
|
||||
updated_widgets[data.key] = applyDirection(widget);
|
||||
var new_widgets = Object.assign({}, this.state.visualization.widgets, updated_widgets);
|
||||
|
||||
var visualization = Object.assign({}, this.state.visualization, {
|
||||
widgets: new_widgets
|
||||
});
|
||||
|
||||
this.setState({ visualization: visualization });
|
||||
}
|
||||
|
||||
moveAbove(widget) {
|
||||
// increase z-Order
|
||||
widget.z++;
|
||||
return widget;
|
||||
}
|
||||
|
||||
moveToFront(widget) {
|
||||
// increase z-Order
|
||||
widget.z = 100;
|
||||
return widget;
|
||||
}
|
||||
|
||||
moveUnderneath(widget) {
|
||||
// decrease z-Order
|
||||
widget.z--;
|
||||
if (widget.z < 0) {
|
||||
widget.z = 0;
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
|
||||
moveToBack(widget) {
|
||||
// increase z-Order
|
||||
widget.z = 0;
|
||||
return widget;
|
||||
}
|
||||
|
||||
setGrid(value) {
|
||||
// value 0 would block all widgets, set 1 as 'grid disabled'
|
||||
if (value === 0) {
|
||||
value = 1;
|
||||
}
|
||||
|
||||
let visualization = Object.assign({}, this.state.visualization, {
|
||||
grid: value
|
||||
});
|
||||
|
||||
this.setState({ visualization });
|
||||
}
|
||||
|
||||
lockWidget(data) {
|
||||
// lock the widget
|
||||
let widget = this.state.visualization.widgets[data.key];
|
||||
widget.locked = true;
|
||||
|
||||
// update visualization
|
||||
let widgets = {};
|
||||
widgets[data.key] = widget;
|
||||
widgets = Object.assign({}, this.state.visualization.widgets, widgets);
|
||||
|
||||
const visualization = Object.assign({}, this.state.visualization, { widgets });
|
||||
this.setState({ visualization });
|
||||
}
|
||||
|
||||
unlockWidget(data) {
|
||||
// lock the widget
|
||||
let widget = this.state.visualization.widgets[data.key];
|
||||
widget.locked = false;
|
||||
|
||||
// update visualization
|
||||
let widgets = {};
|
||||
widgets[data.key] = widget;
|
||||
widgets = Object.assign({}, this.state.visualization.widgets, widgets);
|
||||
|
||||
const visualization = Object.assign({}, this.state.visualization, { widgets });
|
||||
this.setState({ visualization });
|
||||
}
|
||||
|
||||
pauseData = () => {
|
||||
this.setState({ paused: true });
|
||||
}
|
||||
|
||||
unpauseData = () => {
|
||||
this.setState({ paused: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
const current_widgets = this.state.visualization.widgets;
|
||||
|
||||
let boxClasses = classNames('section', 'box', { 'fullscreen-container': this.props.isFullscreen });
|
||||
|
||||
let buttons = []
|
||||
let editingControls = [];
|
||||
let gridControl = {};
|
||||
|
||||
if (this.state.editing) {
|
||||
buttons.push({ click: () => this.stopEditing(), icon: 'save', text: 'Save' });
|
||||
buttons.push({ click: () => this.discardChanges(), icon: 'ban', text: 'Cancel' });
|
||||
|
||||
gridControl = <div key={editingControls.length}>
|
||||
<span>Grid: {this.state.visualization.grid > 1 ? this.state.visualization.grid : 'Disabled'}</span>
|
||||
<Slider value={this.state.visualization.grid} style={{ width: '80px' }} step={5} onChange={value => this.setGrid(value)} />
|
||||
</div>
|
||||
}
|
||||
|
||||
if (!this.props.isFullscreen) {
|
||||
buttons.push({ click: this.props.toggleFullscreen, icon: 'expand', text: 'Fullscreen' });
|
||||
buttons.push({ click: this.state.paused ? this.unpauseData : this.pauseData, icon: this.state.paused ? 'play' : 'pause', text: this.state.paused ? 'Live' : 'Pause' });
|
||||
|
||||
if (!this.state.editing)
|
||||
buttons.push({ click: () => this.setState({ editing: true }), icon: 'edit', text: 'Edit' });
|
||||
}
|
||||
|
||||
const buttonList = buttons.map((btn, idx) =>
|
||||
<Button key={idx} bsStyle="info" onClick={btn.click} style={{ marginLeft: '8px' }}>
|
||||
<Icon icon={btn.icon} /> {btn.text}
|
||||
</Button>
|
||||
);
|
||||
|
||||
// Only one topology widget at the time is supported
|
||||
let thereIsTopologyWidget = current_widgets && Object.values(current_widgets).filter( widget => widget.type === 'Topology').length > 0;
|
||||
let topologyItemMsg = !thereIsTopologyWidget? '' : 'Currently only one is supported';
|
||||
|
||||
return (
|
||||
<div className={boxClasses} >
|
||||
<div className='section-header box-header'>
|
||||
<div className="section-title">
|
||||
<span>{this.state.visualization.name}</span>
|
||||
</div>
|
||||
|
||||
<div className="section-buttons-group-right">
|
||||
{ this.state.editing && gridControl }
|
||||
{ buttonList }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="box box-content" onContextMenu={ (e) => e.preventDefault() }>
|
||||
{this.state.editing &&
|
||||
<div className="toolbar">
|
||||
<ButtonToolbar className="section-buttons-group-right">
|
||||
{ editingControls }
|
||||
</ButtonToolbar>
|
||||
<ButtonToolbar className="toolbox box-header">
|
||||
<ToolboxItem icon="star" name="CustomAction" type="widget" />
|
||||
<ToolboxItem icon="play" name="Action" type="widget" disabled={true} />
|
||||
<ToolboxItem icon="lightbulb" name="Lamp" type="widget" />
|
||||
<ToolboxItem icon="font" name="Value" type="widget" />
|
||||
<ToolboxItem icon="chart-area" name="Plot" type="widget" />
|
||||
<ToolboxItem icon="table" name="Table" type="widget" />
|
||||
<ToolboxItem icon="tag" name="Label" type="widget" />
|
||||
<ToolboxItem icon="image" name="Image" type="widget" />
|
||||
<ToolboxItem icon="table" name="PlotTable" type="widget" />
|
||||
<ToolboxItem icon="dot-circle" name="Button" type="widget" />
|
||||
<ToolboxItem icon="i-cursor" name="Input" type="widget" />
|
||||
<ToolboxItem icon="sliders-h" name="Slider" type="widget" />
|
||||
<ToolboxItem icon="tachometer-alt" name="Gauge" type="widget" />
|
||||
<ToolboxItem icon="square" name="Box" type="widget" />
|
||||
<ToolboxItem icon="code" name="HTML" type="html" />
|
||||
<ToolboxItem icon="project-diagram" name="Topology" type="widget" disabled={thereIsTopologyWidget} title={topologyItemMsg}/>
|
||||
</ButtonToolbar>
|
||||
</div>
|
||||
}
|
||||
|
||||
<Dropzone height={this.state.dropZoneHeight} onDrop={(item, position) => this.handleDrop(item, position)} editing={this.state.editing}>
|
||||
{current_widgets != null &&
|
||||
Object.keys(current_widgets).map(widget_key => (
|
||||
<Widget
|
||||
key={widget_key}
|
||||
data={current_widgets[widget_key]}
|
||||
simulation={this.state.simulation}
|
||||
onWidgetChange={(w, k) => this.widgetChange(w, k)}
|
||||
onWidgetStatusChange={(w, k) => this.widgetStatusChange(w, k)}
|
||||
editing={this.state.editing}
|
||||
index={widget_key}
|
||||
grid={this.state.visualization.grid}
|
||||
paused={this.state.paused}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Grid size={this.state.visualization.grid} disabled={this.state.visualization.grid === 1 || !this.state.editing} />
|
||||
</Dropzone>
|
||||
|
||||
{current_widgets != null &&
|
||||
Object.keys(current_widgets).map(widget_key => {
|
||||
const data = { key: widget_key };
|
||||
|
||||
const locked = this.state.visualization.widgets[widget_key].locked;
|
||||
const disabledMove = locked || this.state.visualization.widgets[widget_key].type === 'Box';
|
||||
|
||||
return <ContextMenu style={{zIndex: 100}} id={'widgetMenu'+ widget_key} key={widget_key}>
|
||||
<Item disabled={locked} onClick={e => this.editWidget(e, data)}>Edit</Item>
|
||||
<Item disabled={locked} onClick={e => this.deleteWidget(e, data)}>Delete</Item>
|
||||
<Separator />
|
||||
<Item disabled={disabledMove} onClick={e => this.moveWidget(e, data, this.moveAbove)}>Move above</Item>
|
||||
<Item disabled={disabledMove} onClick={e => this.moveWidget(e, data, this.moveToFront)}>Move to front</Item>
|
||||
<Item disabled={disabledMove} onClick={e => this.moveWidget(e, data, this.moveUnderneath)}>Move underneath</Item>
|
||||
<Item disabled={disabledMove} onClick={e => this.moveWidget(e, data, this.moveToBack)}>Move to back</Item>
|
||||
<Separator />
|
||||
<Item disabled={locked} onClick={e => this.lockWidget(data)}>Lock</Item>
|
||||
<Item disabled={!locked} onClick={e => this.unlockWidget(data)}>Unlock</Item>
|
||||
</ContextMenu>
|
||||
})}
|
||||
|
||||
<EditWidget sessionToken={this.state.sessionToken} show={this.state.editModal} onClose={(data) => this.closeEdit(data)} widget={this.state.modalData} simulationModels={this.state.simulationModels} files={this.state.files} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Fullscreenable()(Container.create(Visualization, { withProps: true }));
|
|
@ -1,297 +0,0 @@
|
|||
/**
|
||||
* File: widget.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 04.03.2017
|
||||
*
|
||||
* 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 { Container } from 'flux/utils';
|
||||
import { ContextMenuProvider } from 'react-contexify';
|
||||
import Rnd from 'react-rnd';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import UserStore from '../stores/user-store';
|
||||
import SimulatorDataStore from '../stores/simulator-data-store';
|
||||
import SimulationModelStore from '../stores/simulation-model-store';
|
||||
import FileStore from '../stores/file-store';
|
||||
|
||||
import WidgetCustomAction from '../components/widgets/custom-action';
|
||||
import WidgetAction from '../components/widgets/action';
|
||||
import WidgetLamp from '../components/widgets/lamp';
|
||||
import WidgetValue from '../components/widgets/value';
|
||||
import WidgetPlot from '../components/widgets/plot';
|
||||
import WidgetTable from '../components/widgets/table';
|
||||
import WidgetLabel from '../components/widgets/label';
|
||||
import WidgetPlotTable from '../components/widgets/plot-table';
|
||||
import WidgetImage from '../components/widgets/image';
|
||||
import WidgetButton from '../components/widgets/button';
|
||||
import WidgetInput from '../components/widgets/input';
|
||||
import WidgetSlider from '../components/widgets/slider';
|
||||
import WidgetGauge from '../components/widgets/gauge';
|
||||
import WidgetBox from '../components/widgets/box';
|
||||
import WidgetHTML from '../components/widgets/html';
|
||||
import WidgetTopology from '../components/widgets/topology';
|
||||
|
||||
import '../styles/widgets.css';
|
||||
|
||||
class Widget extends React.Component {
|
||||
static getStores() {
|
||||
return [ SimulatorDataStore, SimulationModelStore, FileStore, UserStore ];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
const sessionToken = UserStore.getState().token;
|
||||
|
||||
let simulatorData = {};
|
||||
|
||||
if (props.paused) {
|
||||
if (prevState && prevState.simulatorData) {
|
||||
simulatorData = JSON.parse(JSON.stringify(prevState.simulatorData));
|
||||
}
|
||||
} else {
|
||||
simulatorData = SimulatorDataStore.getState();
|
||||
}
|
||||
|
||||
if (prevState) {
|
||||
return {
|
||||
sessionToken,
|
||||
simulatorData,
|
||||
files: FileStore.getState(),
|
||||
sequence: prevState.sequence + 1,
|
||||
|
||||
simulationModels: SimulationModelStore.getState()
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
sessionToken,
|
||||
simulatorData,
|
||||
files: FileStore.getState(),
|
||||
sequence: 0,
|
||||
|
||||
simulationModels: SimulationModelStore.getState()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Reference to the context menu element
|
||||
this.contextMenuTriggerViaDraggable = null;
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// If loading for the first time
|
||||
if (this.state.sessionToken) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulationModels/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
snapToGrid(value) {
|
||||
if (this.props.grid === 1)
|
||||
return value;
|
||||
|
||||
return Math.round(value / this.props.grid) * this.props.grid;
|
||||
}
|
||||
|
||||
drag(event, data) {
|
||||
const x = this.snapToGrid(data.x);
|
||||
const y = this.snapToGrid(data.y);
|
||||
|
||||
if (x !== data.x || y !== data.y) {
|
||||
this.rnd.updatePosition({ x, y });
|
||||
}
|
||||
}
|
||||
|
||||
dragStop(event, data) {
|
||||
// update widget
|
||||
let widget = this.props.data;
|
||||
widget.x = this.snapToGrid(data.x);
|
||||
widget.y = this.snapToGrid(data.y);
|
||||
|
||||
this.props.onWidgetChange(widget, this.props.index);
|
||||
}
|
||||
|
||||
resizeStop(direction, delta, event) {
|
||||
// update widget
|
||||
let widget = Object.assign({}, this.props.data);
|
||||
|
||||
// resize depends on direction
|
||||
if (direction === 'left' || direction === 'topLeft' || direction === 'bottomLeft') {
|
||||
widget.x -= delta.width;
|
||||
}
|
||||
|
||||
if (direction === 'top' || direction === 'topLeft' || direction === 'topRight') {
|
||||
widget.y -= delta.height;
|
||||
}
|
||||
|
||||
widget.width += delta.width;
|
||||
widget.height += delta.height;
|
||||
|
||||
this.props.onWidgetChange(widget, this.props.index);
|
||||
}
|
||||
|
||||
borderWasClicked(e) {
|
||||
// check if it was triggered by the right button
|
||||
if (e.button === 2) {
|
||||
// launch the context menu using the reference
|
||||
if(this.contextMenuTriggerViaDraggable) {
|
||||
this.contextMenuTriggerViaDraggable.handleContextClick(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputDataChanged(widget, data) {
|
||||
let simulationModel = null;
|
||||
|
||||
for (let model of this.state.simulationModels) {
|
||||
if (model._id !== widget.simulationModel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
simulationModel = model;
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulatorData/inputChanged',
|
||||
simulator: simulationModel.simulator,
|
||||
signal: widget.signal,
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
// configure grid
|
||||
const grid = [this.props.grid, this.props.grid];
|
||||
|
||||
// get widget element
|
||||
const widget = this.props.data;
|
||||
let borderedWidget = false;
|
||||
let element = null;
|
||||
let zIndex = Number(widget.z);
|
||||
|
||||
let simulationModel = null;
|
||||
|
||||
for (let model of this.state.simulationModels) {
|
||||
if (model._id !== widget.simulationModel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
simulationModel = model;
|
||||
}
|
||||
|
||||
// dummy is passed to widgets to keep updating them while in edit mode
|
||||
if (widget.type === 'CustomAction') {
|
||||
element = <WidgetCustomAction widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
} else if (widget.type === 'Action') {
|
||||
element = <WidgetAction widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
} else if (widget.type === 'Lamp') {
|
||||
element = <WidgetLamp widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
} else if (widget.type === 'Value') {
|
||||
element = <WidgetValue widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
} else if (widget.type === 'Plot') {
|
||||
element = <WidgetPlot widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} paused={this.props.paused} />
|
||||
} else if (widget.type === 'Table') {
|
||||
element = <WidgetTable widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} />
|
||||
} else if (widget.type === 'Label') {
|
||||
element = <WidgetLabel widget={widget} />
|
||||
} else if (widget.type === 'PlotTable') {
|
||||
element = <WidgetPlotTable widget={widget} data={this.state.simulatorData} dummy={this.state.sequence} simulationModel={simulationModel} editing={this.props.editing} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index)} paused={this.props.paused} />
|
||||
} else if (widget.type === 'Image') {
|
||||
element = <WidgetImage widget={widget} files={this.state.files} token={this.state.sessionToken} />
|
||||
} else if (widget.type === 'Button') {
|
||||
element = <WidgetButton widget={widget} editing={this.props.editing} simulationModel={simulationModel} onInputChanged={(value) => this.inputDataChanged(widget, value)} />
|
||||
} else if (widget.type === 'Input') {
|
||||
element = <WidgetInput widget={widget} editing={this.props.editing} simulationModel={simulationModel} onInputChanged={(value) => this.inputDataChanged(widget, value)} />
|
||||
} else if (widget.type === 'Slider') {
|
||||
element = <WidgetSlider widget={widget} editing={this.props.editing} simulationModel={simulationModel} onInputChanged={(value) => this.inputDataChanged(widget, value)} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index) } />
|
||||
} else if (widget.type === 'Gauge') {
|
||||
element = <WidgetGauge widget={widget} data={this.state.simulatorData} editing={this.props.editing} simulationModel={simulationModel} />
|
||||
} else if (widget.type === 'Box') {
|
||||
element = <WidgetBox widget={widget} editing={this.props.editing} />
|
||||
} else if (widget.type === 'HTML') {
|
||||
element = <WidgetHTML widget={widget} editing={this.props.editing} />
|
||||
} else if (widget.type === 'Topology') {
|
||||
element = <WidgetTopology widget={widget} files={this.state.files} />
|
||||
}
|
||||
|
||||
if (widget.type === 'Box')
|
||||
zIndex = 0;
|
||||
|
||||
const widgetClasses = classNames({
|
||||
'widget': !this.props.editing,
|
||||
'editing-widget': this.props.editing,
|
||||
'border': borderedWidget,
|
||||
'unselectable': false,
|
||||
'locked': widget.locked && this.props.editing
|
||||
});
|
||||
|
||||
if (this.props.editing) {
|
||||
const resizing = { bottom: !widget.locked, bottomLeft: !widget.locked, bottomRight: !widget.locked, left: !widget.locked, right: !widget.locked, top: !widget.locked, topLeft: !widget.locked, topRight: !widget.locked};
|
||||
|
||||
return (
|
||||
<Rnd
|
||||
ref={c => { this.rnd = c; }}
|
||||
default={{ x: Number(widget.x), y: Number(widget.y), width: widget.width, height: widget.height }}
|
||||
minWidth={widget.minWidth}
|
||||
minHeight={widget.minHeight}
|
||||
lockAspectRatio={Boolean(widget.lockAspect)}
|
||||
bounds={'parent'}
|
||||
className={ widgetClasses }
|
||||
onResizeStart={(event, direction, ref) => this.borderWasClicked(event)}
|
||||
onResizeStop={(event, direction, ref, delta) => this.resizeStop(direction, delta, event)}
|
||||
onDrag={(event, data) => this.drag(event, data)}
|
||||
onDragStop={(event, data) => this.dragStop(event, data)}
|
||||
dragGrid={grid}
|
||||
resizeGrid={grid}
|
||||
z={zIndex}
|
||||
enableResizing={resizing}
|
||||
disableDragging={widget.locked}
|
||||
>
|
||||
<ContextMenuProvider className={'full'} id={'widgetMenu' + this.props.index}>
|
||||
{element}
|
||||
</ContextMenuProvider>
|
||||
</Rnd>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
className={ widgetClasses }
|
||||
style={{
|
||||
width: Number(widget.width), height: Number(widget.height),
|
||||
left: Number(widget.x), top: Number(widget.y),
|
||||
zIndex: zIndex,
|
||||
position: 'absolute'
|
||||
}}>
|
||||
{element}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Container.create(Widget, { withProps: true });
|
138
src/dashboard/dashboard-button-group.js
Normal file
138
src/dashboard/dashboard-button-group.js
Normal file
|
@ -0,0 +1,138 @@
|
|||
/**
|
||||
* 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 PropTypes from 'prop-types';
|
||||
import { Button,OverlayTrigger, Tooltip } from 'react-bootstrap';
|
||||
import Icon from "../common/icon";
|
||||
|
||||
class DashboardButtonGroup extends React.Component {
|
||||
render() {
|
||||
const buttonStyle = {
|
||||
marginLeft: '12px',
|
||||
height: '44px',
|
||||
width : '35px'
|
||||
};
|
||||
|
||||
const iconStyle = {
|
||||
color: '#007bff',
|
||||
height: '25px',
|
||||
width : '25px'
|
||||
}
|
||||
|
||||
const buttons = [];
|
||||
let key = 0;
|
||||
|
||||
if (this.props.fullscreen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.props.editing) {
|
||||
buttons.push(
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"save"}`}> Save changes </Tooltip>} >
|
||||
<Button variant= 'light' size="lg" key={key} onClick={this.props.onSave} style={buttonStyle}>
|
||||
<Icon icon="save" style={iconStyle} />
|
||||
</Button>
|
||||
</OverlayTrigger>,
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"cancel"}`}> Discard changes </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onCancel} style={buttonStyle}>
|
||||
<Icon icon="times" style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
} else {
|
||||
if (this.props.fullscreen !== true) {
|
||||
buttons.push(
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"expand"}`}> Change to fullscreen view </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onFullscreen} style={buttonStyle}>
|
||||
<Icon icon="expand" style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.paused) {
|
||||
buttons.push(
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"play"}`}> Continue simulation </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onUnpause} style={buttonStyle}>
|
||||
<Icon icon="play" style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
} else {
|
||||
buttons.push(
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"pause"}`}> Pause simulation </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onPause} style={buttonStyle}>
|
||||
<Icon icon="pause" style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
buttons.push(
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"file"}`}> Add, edit or delete files of scenario </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onEditFiles} style={buttonStyle}>
|
||||
<Icon icon="file" style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
|
||||
buttons.push(
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"file"}`}> Add, edit or delete input signals </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onEditInputSignals} style={buttonStyle}>
|
||||
<Icon icon="sign-in-alt" style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
|
||||
buttons.push(
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"file"}`}> Add, edit or delete output signals </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onEditOutputSignals} style={buttonStyle}>
|
||||
<Icon icon="sign-out-alt" style={iconStyle}/>
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
|
||||
buttons.push(
|
||||
<OverlayTrigger key={key++} placement={'bottom'} overlay={<Tooltip id={`tooltip-${"layout"}`}> Add widgets and edit layout </Tooltip>} >
|
||||
<Button key={key} variant= 'light' size="lg" onClick={this.props.onEdit} style={buttonStyle}>
|
||||
<Icon icon="pen" style={iconStyle} />
|
||||
</Button>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
return <div className='section-buttons-group-right'>
|
||||
{buttons}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
DashboardButtonGroup.propTypes = {
|
||||
editing: PropTypes.bool,
|
||||
fullscreen: PropTypes.bool,
|
||||
paused: PropTypes.bool,
|
||||
onEdit: PropTypes.func,
|
||||
onSave: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
onFullscreen: PropTypes.func,
|
||||
onPause: PropTypes.func,
|
||||
onUnpause: PropTypes.func
|
||||
};
|
||||
|
||||
export default DashboardButtonGroup;
|
57
src/dashboard/dashboard-store.js
Normal file
57
src/dashboard/dashboard-store.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* 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 ArrayStore from '../common/array-store';
|
||||
import DashboardsDataManager from './dashboards-data-manager';
|
||||
|
||||
class DashboardStore extends ArrayStore {
|
||||
constructor() {
|
||||
super('dashboards', DashboardsDataManager);
|
||||
}
|
||||
|
||||
reduce(state, action) {
|
||||
|
||||
switch (action.type) {
|
||||
case 'dashboards/start-add':
|
||||
|
||||
// Check if this is a recursive dashboard import or not
|
||||
if (action.data.hasOwnProperty("widgets")) {
|
||||
// import
|
||||
let subObjects = []
|
||||
let widgets = {}
|
||||
widgets["widgets"] = action.data.widgets
|
||||
subObjects.push(widgets)
|
||||
delete action.data.widgets; // remove widgets from dashboard object
|
||||
|
||||
// action.data should now contain the dashboard and no sub-objects
|
||||
// sub-objects are treated in add method of RestDataManager
|
||||
this.dataManager.add(action.data, action.token,action.param, subObjects);
|
||||
return state
|
||||
|
||||
} else {
|
||||
// no import
|
||||
return super.reduce(state, action);
|
||||
}
|
||||
|
||||
default:
|
||||
return super.reduce(state, action);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new DashboardStore();
|
607
src/dashboard/dashboard.js
Normal file
607
src/dashboard/dashboard.js
Normal file
|
@ -0,0 +1,607 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { Container } from 'flux/utils';
|
||||
import Fullscreenable from 'react-fullscreenable';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import EditWidget from '../widget/edit-widget/edit-widget';
|
||||
import EditFiles from '../file/edit-files';
|
||||
import EditSignalMapping from "../signal/edit-signal-mapping";
|
||||
import WidgetContextMenu from '../widget/widget-context-menu';
|
||||
import WidgetToolbox from '../widget/widget-toolbox';
|
||||
import WidgetArea from '../widget/widget-area';
|
||||
import DashboardButtonGroup from './dashboard-button-group';
|
||||
|
||||
import DashboardStore from './dashboard-store';
|
||||
import SignalStore from '../signal/signal-store'
|
||||
import FileStore from '../file/file-store';
|
||||
import WidgetStore from '../widget/widget-store';
|
||||
import ICStore from '../ic/ic-store'
|
||||
import ConfigStore from '../componentconfig/config-store'
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
|
||||
import 'react-contexify/dist/ReactContexify.min.css';
|
||||
import WidgetContainer from '../widget/widget-container';
|
||||
import EditableWidgetContainer from '../widget/editable-widget-container';
|
||||
|
||||
class Dashboard extends Component {
|
||||
|
||||
static lastWidgetKey = 0;
|
||||
static webSocketsOpened = false;
|
||||
static getStores() {
|
||||
return [DashboardStore, FileStore, WidgetStore, SignalStore, ConfigStore, ICStore];
|
||||
}
|
||||
|
||||
static calculateState(prevState, props) {
|
||||
if (prevState == null) {
|
||||
prevState = {};
|
||||
}
|
||||
|
||||
const sessionToken = localStorage.getItem("token");
|
||||
|
||||
let dashboard = DashboardStore.getState().find(d => d.id === parseInt(props.match.params.dashboard, 10));
|
||||
if (dashboard == null) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'dashboards/start-load',
|
||||
data: props.match.params.dashboard,
|
||||
token: sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
// obtain all widgets of this dashboard
|
||||
let widgets = WidgetStore.getState().filter(w => w.dashboardID === parseInt(props.match.params.dashboard, 10));
|
||||
|
||||
// compute max y coordinate
|
||||
let maxHeight = null;
|
||||
maxHeight = Object.keys(widgets).reduce((maxHeightSoFar, widgetKey) => {
|
||||
let thisWidget = widgets[widgetKey];
|
||||
let thisWidgetHeight = thisWidget.y + thisWidget.height;
|
||||
|
||||
return thisWidgetHeight > maxHeightSoFar ? thisWidgetHeight : maxHeightSoFar;
|
||||
}, 0);
|
||||
|
||||
|
||||
// filter component configurations to the ones that belong to this scenario
|
||||
let configs = [];
|
||||
let files = [];
|
||||
if (dashboard !== undefined) {
|
||||
configs = ConfigStore.getState().filter(config => config.scenarioID === dashboard.scenarioID);
|
||||
files = FileStore.getState().filter(file => file.scenarioID === dashboard.scenarioID);
|
||||
if (dashboard.height === 0) {
|
||||
dashboard.height = 400;
|
||||
}
|
||||
else if (maxHeight + 80 > dashboard.height) {
|
||||
dashboard.height = maxHeight + 80;
|
||||
}
|
||||
}
|
||||
|
||||
// filter signals to the ones belonging to the scenario at hand
|
||||
let signals = []
|
||||
let allSignals = SignalStore.getState();
|
||||
let sig, con;
|
||||
for (sig of allSignals) {
|
||||
for (con of configs) {
|
||||
if (sig.configID === con.id) {
|
||||
signals.push(sig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// filter ICs to the ones used by this scenario
|
||||
let ics = []
|
||||
if (configs.length > 0) {
|
||||
ics = ICStore.getState().filter(ic => {
|
||||
let ICused = false;
|
||||
for (let config of configs) {
|
||||
if (ic.id === config.icID) {
|
||||
ICused = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ICused;
|
||||
});
|
||||
}
|
||||
|
||||
let editOutputSignalsModal = prevState.editOutputSignalsModal;
|
||||
let editInputSignalsModal = prevState.editInputSignalsModal;
|
||||
|
||||
return {
|
||||
dashboard,
|
||||
widgets,
|
||||
signals,
|
||||
sessionToken,
|
||||
files,
|
||||
configs,
|
||||
ics,
|
||||
|
||||
editing: prevState.editing || false,
|
||||
paused: prevState.paused || false,
|
||||
|
||||
editModal: prevState.editModal || false,
|
||||
editOutputSignalsModal: editOutputSignalsModal,
|
||||
editInputSignalsModal: editInputSignalsModal,
|
||||
filesEditModal: prevState.filesEditModal || false,
|
||||
filesEditSaveState: prevState.filesEditSaveState || [],
|
||||
modalData: null,
|
||||
modalIndex: null,
|
||||
widgetChangeData: [],
|
||||
widgetOrigIDs: prevState.widgetOrigIDs || [],
|
||||
|
||||
maxWidgetHeight: maxHeight || null,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
static getNewWidgetKey() {
|
||||
const widgetKey = this.lastWidgetKey;
|
||||
this.lastWidgetKey++;
|
||||
|
||||
return widgetKey;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
Dashboard.webSocketsOpened = false;
|
||||
|
||||
// load widgets of dashboard
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-load',
|
||||
token: this.state.sessionToken,
|
||||
param: '?dashboardID=' + parseInt(this.props.match.params.dashboard, 10),
|
||||
});
|
||||
|
||||
// load ICs to enable that component configs and dashboards work with them
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/start-load',
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<P>, prevState: Readonly<S>, snapshot: SS) {
|
||||
// open web sockets if ICs are already known and sockets are not opened yet
|
||||
if (this.state.ics !== undefined && !Dashboard.webSocketsOpened) {
|
||||
if (this.state.ics.length > 0) {
|
||||
console.log("Starting to open IC websockets:", this.state.ics);
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/open-sockets',
|
||||
data: this.state.ics
|
||||
});
|
||||
|
||||
Dashboard.webSocketsOpened = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (prevState.dashboard === undefined && this.state.dashboard !== undefined) {
|
||||
// the dashboard was loaded, so that the scenarioID is available
|
||||
|
||||
// load configs of scenario
|
||||
AppDispatcher.dispatch({
|
||||
type: 'configs/start-load',
|
||||
token: this.state.sessionToken,
|
||||
param: '?scenarioID=' + this.state.dashboard.scenarioID
|
||||
});
|
||||
|
||||
// load files of scenario
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-load',
|
||||
param: '?scenarioID=' + this.state.dashboard.scenarioID,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// close web sockets of ICs
|
||||
console.log("Starting to close all web sockets");
|
||||
AppDispatcher.dispatch({
|
||||
type: 'ics/close-sockets',
|
||||
});
|
||||
}
|
||||
|
||||
handleKeydown(e) {
|
||||
switch (e.key) {
|
||||
case ' ':
|
||||
case 'p':
|
||||
this.setState({ paused: !this.state.paused });
|
||||
break;
|
||||
case 'e':
|
||||
this.setState({ editing: !this.state.editing });
|
||||
break;
|
||||
case 'f':
|
||||
this.props.toggleFullscreen();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
transformToWidgetsList(widgets) {
|
||||
return Object.keys(widgets).map((key) => widgets[key]);
|
||||
}
|
||||
|
||||
handleDrop(widget) {
|
||||
widget.dashboardID = this.state.dashboard.id;
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-add',
|
||||
token: this.state.sessionToken,
|
||||
data: widget
|
||||
});
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
widgetStatusChange(updated_widget, key) {
|
||||
// Widget changed internally, make changes effective then save them
|
||||
this.widgetChange(updated_widget, key, this.saveChanges);
|
||||
}
|
||||
|
||||
widgetChange(widget, index, callback = null) {
|
||||
let temp = this.state.widgetChangeData;
|
||||
temp.push(widget);
|
||||
this.setState({ widgetChangeData: temp });
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
editWidget(widget, index) {
|
||||
this.setState({ editModal: true, modalData: widget, modalIndex: index });
|
||||
};
|
||||
|
||||
duplicateWidget(widget) {
|
||||
let widgetCopy = JSON.parse(JSON.stringify(widget));
|
||||
delete widgetCopy.id;
|
||||
widgetCopy.x = widgetCopy.x + 50;
|
||||
widgetCopy.y = widgetCopy.y + 50;
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-add',
|
||||
token: this.state.sessionToken,
|
||||
data: widgetCopy
|
||||
});
|
||||
};
|
||||
|
||||
startEditFiles() {
|
||||
let tempFiles = [];
|
||||
this.state.files.forEach(file => {
|
||||
tempFiles.push({
|
||||
id: file.id,
|
||||
name: file.name
|
||||
});
|
||||
})
|
||||
this.setState({ filesEditModal: true, filesEditSaveState: tempFiles });
|
||||
}
|
||||
|
||||
closeEditFiles() {
|
||||
this.state.widgets.map(widget => {
|
||||
if(widget.type === "Image"){
|
||||
widget.customProperties.update = true;
|
||||
}
|
||||
})
|
||||
this.setState({ filesEditModal: false });
|
||||
}
|
||||
|
||||
closeEdit(data) {
|
||||
|
||||
if (data == null) {
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-load',
|
||||
token: this.state.sessionToken,
|
||||
param: '?dashboardID=' + this.state.dashboard.id
|
||||
});
|
||||
|
||||
this.setState({ editModal: false });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(data.type === "Image")
|
||||
{
|
||||
data.customProperties.update = true;
|
||||
}
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-edit',
|
||||
token: this.state.sessionToken,
|
||||
data: data
|
||||
});
|
||||
|
||||
this.setState({ editModal: false });
|
||||
};
|
||||
|
||||
|
||||
deleteWidget(widget, index) {
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-remove',
|
||||
data: widget,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
startEditing() {
|
||||
let originalIDs = [];
|
||||
this.state.widgets.forEach(widget => {
|
||||
originalIDs.push(widget.id);
|
||||
if (widget.type === 'Slider' || widget.type === 'NumberInput' || widget.type === 'Button') {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-edit',
|
||||
token: this.state.sessionToken,
|
||||
data: widget
|
||||
});
|
||||
}
|
||||
});
|
||||
this.setState({ editing: true, widgetOrigIDs: originalIDs });
|
||||
};
|
||||
|
||||
saveEditing() {
|
||||
// Provide the callback so it can be called when state change is applied
|
||||
// TODO: Check if callback is needed
|
||||
AppDispatcher.dispatch({
|
||||
type: 'dashboards/start-edit',
|
||||
data: this.state.dashboard,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
|
||||
this.state.widgetChangeData.forEach(widget => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-edit',
|
||||
token: this.state.sessionToken,
|
||||
data: widget
|
||||
});
|
||||
});
|
||||
this.setState({ editing: false, widgetChangeData: [] });
|
||||
};
|
||||
|
||||
saveChanges() {
|
||||
// Transform to a list
|
||||
const dashboard = Object.assign({}, this.state.dashboard.toJS(), {
|
||||
widgets: this.transformToWidgetsList(this.state.widgets)
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'dashboards/start-edit',
|
||||
data: dashboard,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
cancelEditing() {
|
||||
//raw widget has no id -> cannot be deleted in its original form
|
||||
this.state.widgets.forEach(widget => {
|
||||
let tempID = this.state.widgetOrigIDs.find(element => element === widget.id);
|
||||
if (typeof tempID === 'undefined') {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-remove',
|
||||
data: widget,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-load',
|
||||
token: this.state.sessionToken,
|
||||
param: '?dashboardID=' + this.state.dashboard.id
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'dashboards/start-load',
|
||||
data: this.props.match.params.dashboard,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
this.setState({ editing: false, widgetChangeData: [] });
|
||||
|
||||
};
|
||||
|
||||
setGrid(value) {
|
||||
|
||||
let dashboard = this.state.dashboard;
|
||||
dashboard.grid = value;
|
||||
this.setState({ dashboard });
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
setDashboardSize(value) {
|
||||
const maxHeight = Object.values(this.state.widgets).reduce((currentHeight, widget) => {
|
||||
const absolutHeight = widget.y + widget.height;
|
||||
|
||||
return absolutHeight > currentHeight ? absolutHeight : currentHeight;
|
||||
}, 0);
|
||||
let dashboard = this.state.dashboard;
|
||||
|
||||
if (value === -1) {
|
||||
|
||||
let tempHeight = this.state.dashboard.height - 50;
|
||||
|
||||
if (tempHeight >= 400 && tempHeight >= (maxHeight + 80)) {
|
||||
dashboard.height = tempHeight;
|
||||
this.setState({ dashboard });
|
||||
}
|
||||
}
|
||||
else {
|
||||
dashboard.height = this.state.dashboard.height + 50;
|
||||
this.setState({ dashboard });
|
||||
}
|
||||
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
pauseData() {
|
||||
this.setState({ paused: true });
|
||||
};
|
||||
|
||||
unpauseData() {
|
||||
this.setState({ paused: false });
|
||||
};
|
||||
|
||||
editInputSignals() {
|
||||
this.setState({ editInputSignalsModal: true });
|
||||
};
|
||||
|
||||
editOutputSignals() {
|
||||
this.setState({ editOutputSignalsModal: true });
|
||||
};
|
||||
|
||||
closeEditSignalsModal(direction) {
|
||||
if (direction === "in") {
|
||||
this.setState({ editInputSignalsModal: false });
|
||||
} else if (direction === "out") {
|
||||
this.setState({ editOutputSignalsModal: false });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
if (this.state.dashboard === undefined) {
|
||||
return <div className="section-title"> <span>{"Loading Dashboard..."}</span> </div>
|
||||
}
|
||||
|
||||
const grid = this.state.dashboard.grid;
|
||||
const boxClasses = classNames('section', 'box', { 'fullscreen-padding': this.props.isFullscreen });
|
||||
let draggable = this.state.editing;
|
||||
let dropZoneHeight = this.state.dashboard.height;
|
||||
return <div className={boxClasses} >
|
||||
<div className='section-header box-header'>
|
||||
<div className="section-title">
|
||||
<span>{this.state.dashboard.name}</span>
|
||||
</div>
|
||||
|
||||
<DashboardButtonGroup
|
||||
editing={this.state.editing}
|
||||
onEdit={this.startEditing.bind(this)}
|
||||
fullscreen={this.props.isFullscreen}
|
||||
paused={this.state.paused}
|
||||
onSave={this.saveEditing.bind(this)}
|
||||
onCancel={this.cancelEditing.bind(this)}
|
||||
onFullscreen={this.props.toggleFullscreen}
|
||||
onPause={this.pauseData.bind(this)}
|
||||
onUnpause={this.unpauseData.bind(this)}
|
||||
onEditFiles={this.startEditFiles.bind(this)}
|
||||
onEditOutputSignals={this.editOutputSignals.bind(this)}
|
||||
onEditInputSignals={this.editInputSignals.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="box box-content" onContextMenu={(e) => e.preventDefault()}>
|
||||
{this.state.editing &&
|
||||
<WidgetToolbox grid={grid} onGridChange={this.setGrid.bind(this)} dashboard={this.state.dashboard} onDashboardSizeChange={this.setDashboardSize.bind(this)} widgets={this.state.widgets} />
|
||||
}
|
||||
{!draggable ? (
|
||||
<WidgetArea widgets={this.state.widgets} dropZoneHeight={dropZoneHeight} editing={this.state.editing} grid={grid} onWidgetAdded={this.handleDrop.bind(this)}>
|
||||
{this.state.widgets != null && Object.keys(this.state.widgets).map(widgetKey => (
|
||||
<WidgetContainer widget={this.state.widgets[widgetKey]}>
|
||||
<WidgetContextMenu
|
||||
key={widgetKey}
|
||||
index={parseInt(widgetKey, 10)}
|
||||
widget={this.state.widgets[widgetKey]}
|
||||
onEdit={this.editWidget.bind(this)}
|
||||
onDuplicate={this.duplicateWidget.bind(this)}
|
||||
onDelete={this.deleteWidget.bind(this)}
|
||||
onChange={this.widgetChange.bind(this)}
|
||||
|
||||
onWidgetChange={this.widgetChange.bind(this)}
|
||||
onWidgetStatusChange={this.widgetStatusChange.bind(this)}
|
||||
editing={this.state.editing}
|
||||
grid={grid}
|
||||
paused={this.state.paused}
|
||||
/>
|
||||
</WidgetContainer>
|
||||
))}
|
||||
</WidgetArea>
|
||||
) : (
|
||||
<WidgetArea widgets={this.state.widgets} editing={this.state.editing} dropZoneHeight={dropZoneHeight} grid={grid} onWidgetAdded={this.handleDrop.bind(this)}>
|
||||
{this.state.widgets != null && Object.keys(this.state.widgets).map(widgetKey => (
|
||||
<EditableWidgetContainer
|
||||
widget={this.state.widgets[widgetKey]}
|
||||
grid={grid}
|
||||
index={parseInt(widgetKey, 10)}
|
||||
onWidgetChange={this.widgetChange.bind(this)}>
|
||||
<WidgetContextMenu
|
||||
key={widgetKey}
|
||||
index={parseInt(widgetKey, 10)}
|
||||
widget={this.state.widgets[widgetKey]}
|
||||
onEdit={this.editWidget.bind(this)}
|
||||
onDuplicate={this.duplicateWidget.bind(this)}
|
||||
onDelete={this.deleteWidget.bind(this)}
|
||||
onChange={this.widgetChange.bind(this)}
|
||||
|
||||
onWidgetChange={this.widgetChange.bind(this)}
|
||||
onWidgetStatusChange={this.widgetStatusChange.bind(this)}
|
||||
editing={this.state.editing}
|
||||
paused={this.state.paused}
|
||||
/>
|
||||
</EditableWidgetContainer>
|
||||
))}
|
||||
</WidgetArea>
|
||||
|
||||
)}
|
||||
|
||||
<EditWidget
|
||||
sessionToken={this.state.sessionToken}
|
||||
show={this.state.editModal}
|
||||
onClose={this.closeEdit.bind(this)}
|
||||
widget={this.state.modalData}
|
||||
signals={this.state.signals}
|
||||
files={this.state.files}
|
||||
/>
|
||||
|
||||
<EditFiles
|
||||
sessionToken={this.state.sessionToken}
|
||||
show={this.state.filesEditModal}
|
||||
onClose={this.closeEditFiles.bind(this)}
|
||||
signals={this.state.signals}
|
||||
files={this.state.files}
|
||||
scenarioID={this.state.dashboard.scenarioID}
|
||||
/>
|
||||
|
||||
<EditSignalMapping
|
||||
show={this.state.editOutputSignalsModal}
|
||||
onCloseEdit={(direction) => this.closeEditSignalsModal(direction)}
|
||||
direction="Output"
|
||||
signals={this.state.signals}
|
||||
configID={null}
|
||||
configs={this.state.configs}
|
||||
sessionToken={this.state.sessionToken}
|
||||
/>
|
||||
<EditSignalMapping
|
||||
show={this.state.editInputSignalsModal}
|
||||
onCloseEdit={(direction) => this.closeEditSignalsModal(direction)}
|
||||
direction="Input"
|
||||
signals={this.state.signals}
|
||||
configID={null}
|
||||
configs={this.state.configs}
|
||||
sessionToken={this.state.sessionToken}
|
||||
/>
|
||||
|
||||
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let fluxContainerConverter = require('../common/FluxContainerConverter');
|
||||
export default Fullscreenable()(Container.create(fluxContainerConverter.convert(Dashboard), { withProps: true }));
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: project-store.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 07.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
|
@ -19,7 +15,30 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import ArrayStore from './array-store';
|
||||
import ProjectsDataManager from '../data-managers/projects-data-manager';
|
||||
import RestDataManager from '../common/data-managers/rest-data-manager';
|
||||
import AppDispatcher from "../common/app-dispatcher";
|
||||
|
||||
export default new ArrayStore('projects', ProjectsDataManager);
|
||||
class DashboardsDataManager extends RestDataManager{
|
||||
constructor() {
|
||||
super('dashboard', '/dashboards');
|
||||
this.onLoad = this.onDashboardsLoad
|
||||
}
|
||||
|
||||
onDashboardsLoad(data, token){
|
||||
|
||||
if (!Array.isArray(data)) {
|
||||
data = [data];
|
||||
}
|
||||
|
||||
for (let dashboard of data){
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-load',
|
||||
token: token,
|
||||
param: '?dashboardID=' + dashboard.id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new DashboardsDataManager();
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: new-visualization.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 03.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
|
@ -20,18 +16,19 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
|
||||
class NewVisualzationDialog extends React.Component {
|
||||
valid: false;
|
||||
class EditDashboardDialog extends React.Component {
|
||||
valid = true;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: ''
|
||||
name: '',
|
||||
id: ''
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,7 +47,10 @@ class NewVisualzationDialog extends React.Component {
|
|||
}
|
||||
|
||||
resetState() {
|
||||
this.setState({ name: '' });
|
||||
this.setState({
|
||||
name: this.props.dashboard.name,
|
||||
id: this.props.dashboard.id
|
||||
});
|
||||
}
|
||||
|
||||
validateForm(target) {
|
||||
|
@ -71,10 +71,10 @@ class NewVisualzationDialog extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="New Visualization" buttonTitle="Add" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<Dialog show={this.props.show} title="Edit Dashboard" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
@ -84,4 +84,4 @@ class NewVisualzationDialog extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default NewVisualzationDialog;
|
||||
export default EditDashboardDialog;
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: import-simulator.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 04.04.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
|
@ -20,11 +16,11 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
|
||||
class ImportVisualizationDialog extends React.Component {
|
||||
class ImportDashboardDialog extends React.Component {
|
||||
valid = false;
|
||||
imported = false;
|
||||
|
||||
|
@ -68,32 +64,12 @@ class ImportVisualizationDialog extends React.Component {
|
|||
var self = this;
|
||||
|
||||
reader.onload = function(event) {
|
||||
// read simulator
|
||||
const visualization = JSON.parse(event.target.result);
|
||||
|
||||
let defaultSimulator = "";
|
||||
if (self.props.simulation.models != null) {
|
||||
defaultSimulator = self.props.simulation.models[0].simulator;
|
||||
}
|
||||
|
||||
visualization.widgets.forEach(widget => {
|
||||
switch (widget.type) {
|
||||
case 'Value':
|
||||
case 'Plot':
|
||||
case 'Table':
|
||||
case 'PlotTable':
|
||||
case 'Gauge':
|
||||
widget.simulator = defaultSimulator;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
// read IC
|
||||
const dashboard = JSON.parse(event.target.result);
|
||||
|
||||
self.imported = true;
|
||||
self.valid = true;
|
||||
self.setState({ name: visualization.name, widgets: visualization.widgets, grid: visualization.grid });
|
||||
self.setState({ name: dashboard.name, widgets: dashboard.widgets, grid: dashboard.grid });
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
|
@ -110,21 +86,36 @@ class ImportVisualizationDialog extends React.Component {
|
|||
this.valid = name;
|
||||
|
||||
// return state to control
|
||||
if (target === 'name') return name ? "success" : "error";
|
||||
if (target === 'name'){
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Import Visualization" buttonTitle="Import" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<Dialog
|
||||
show={this.props.show}
|
||||
title="Import Dashboard"
|
||||
buttonTitle="Import"
|
||||
onClose={(c) => this.onClose(c)}
|
||||
onReset={() => this.resetState()}
|
||||
valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="file">
|
||||
<ControlLabel>Visualization File</ControlLabel>
|
||||
<FormLabel>Dashboard File</FormLabel>
|
||||
<FormControl type="file" onChange={(e) => this.loadFile(e.target.files)} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormControl readOnly={!this.imported} type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormGroup controlId="name" >
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl
|
||||
readOnly={!this.imported}
|
||||
isValid={this.validateForm('name')}
|
||||
type="text"
|
||||
placeholder="Enter name"
|
||||
value={this.state.name}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
/>
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
</form>
|
||||
|
@ -133,4 +124,4 @@ class ImportVisualizationDialog extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default ImportVisualizationDialog;
|
||||
export default ImportDashboardDialog;
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: new-visualization.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 03.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
|
@ -20,19 +16,18 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React from 'react';
|
||||
import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
|
||||
import { FormGroup, FormControl, FormLabel } from 'react-bootstrap';
|
||||
|
||||
import Dialog from './dialog';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
|
||||
class EditVisualizationDialog extends React.Component {
|
||||
class NewDashboardDialog extends React.Component {
|
||||
valid: false;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: '',
|
||||
_id: ''
|
||||
name: ''
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,10 +46,7 @@ class EditVisualizationDialog extends React.Component {
|
|||
}
|
||||
|
||||
resetState() {
|
||||
this.setState({
|
||||
name: this.props.visualization.name,
|
||||
_id: this.props.visualization._id
|
||||
});
|
||||
this.setState({ name: '' });
|
||||
}
|
||||
|
||||
validateForm(target) {
|
||||
|
@ -75,10 +67,10 @@ class EditVisualizationDialog extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Edit Visualization" buttonTitle="Save" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<Dialog show={this.props.show} title="New Dashboard" buttonTitle="Add" onClose={(c) => this.onClose(c)} onReset={() => this.resetState()} valid={this.valid}>
|
||||
<form>
|
||||
<FormGroup controlId="name" validationState={this.validateForm('name')}>
|
||||
<ControlLabel>Name</ControlLabel>
|
||||
<FormGroup controlId="name" validationstate={this.validateForm('name')}>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl type="text" placeholder="Enter name" value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
|
@ -88,4 +80,4 @@ class EditVisualizationDialog extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
export default EditVisualizationDialog;
|
||||
export default NewDashboardDialog;
|
|
@ -1,149 +0,0 @@
|
|||
/**
|
||||
* File: rest-data-manager.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 03.03.2017
|
||||
*
|
||||
* 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 RestAPI from '../api/rest-api';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
|
||||
const API_URL = '/api/v1';
|
||||
|
||||
class RestDataManager {
|
||||
constructor(type, url, keyFilter) {
|
||||
this.url = url;
|
||||
this.type = type;
|
||||
this.keyFilter = keyFilter;
|
||||
this.onLoad = null;
|
||||
}
|
||||
|
||||
makeURL(part) {
|
||||
return API_URL + part;
|
||||
}
|
||||
|
||||
filterKeys(object) {
|
||||
// don't change anything if no filter is set
|
||||
if (this.keyFilter == null || Array.isArray(this.keyFilter) === false) {
|
||||
return object;
|
||||
}
|
||||
|
||||
// remove all keys not in the filter
|
||||
Object.keys(object).filter(key => {
|
||||
return this.keyFilter.indexOf(key) === -1;
|
||||
}).forEach(key => {
|
||||
delete object[key];
|
||||
});
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
load(id, token = null) {
|
||||
if (id != null) {
|
||||
// load single object
|
||||
RestAPI.get(this.makeURL(this.url + '/' + id), token).then(response => {
|
||||
const data = this.filterKeys(response[this.type]);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/loaded',
|
||||
data: data
|
||||
});
|
||||
|
||||
if (this.onLoad != null) {
|
||||
this.onLoad(data);
|
||||
}
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/load-error',
|
||||
error: error
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// load all objects
|
||||
RestAPI.get(this.makeURL(this.url), token).then(response => {
|
||||
const data = response[this.type + 's'].map(element => {
|
||||
return this.filterKeys(element);
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/loaded',
|
||||
data: data
|
||||
});
|
||||
|
||||
if (this.onLoad != null) {
|
||||
this.onLoad(data);
|
||||
}
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/load-error',
|
||||
error: error
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
add(object, token = null) {
|
||||
var obj = {};
|
||||
obj[this.type] = this.filterKeys(object);
|
||||
|
||||
RestAPI.post(this.makeURL(this.url), obj, token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/added',
|
||||
data: response[this.type]
|
||||
});
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/add-error',
|
||||
error: error
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
remove(object, token = null) {
|
||||
RestAPI.delete(this.makeURL(this.url + '/' + object._id), token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/removed',
|
||||
data: response[this.type],
|
||||
original: object
|
||||
});
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/remove-error',
|
||||
error: error
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
update(object, token = null) {
|
||||
var obj = {};
|
||||
obj[this.type] = this.filterKeys(object);
|
||||
|
||||
RestAPI.put(this.makeURL(this.url + '/' + object._id), obj, token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/edited',
|
||||
data: response[this.type]
|
||||
});
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/edit-error',
|
||||
error: error
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default RestDataManager;
|
|
@ -1,50 +0,0 @@
|
|||
/**
|
||||
* File: simulation-models-data-manager.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 20.04.2018
|
||||
*
|
||||
* 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 RestDataManager from './rest-data-manager';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
|
||||
class SimulationModelDataManager extends RestDataManager {
|
||||
constructor() {
|
||||
super('simulationModel', '/models');
|
||||
|
||||
this.onLoad = this.onModelsLoad;
|
||||
}
|
||||
|
||||
onModelsLoad(data) {
|
||||
if (!Array.isArray(data))
|
||||
data = [ data ];
|
||||
|
||||
for (let model of data)
|
||||
this.loadModelData(model);
|
||||
}
|
||||
|
||||
loadModelData(model) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulatorData/prepare',
|
||||
inputLength: parseInt(model.inputLength, 10),
|
||||
outputLength: parseInt(model.outputLength, 10),
|
||||
id: model.simulator
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new SimulationModelDataManager();
|
82
src/file/edit-file-content.js
Normal file
82
src/file/edit-file-content.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
* 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 {FormGroup, FormControl, Button, Col} from 'react-bootstrap';
|
||||
import AppDispatcher from "../common/app-dispatcher";
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
|
||||
class EditFileContent extends React.Component {
|
||||
valid = true;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
uploadFile: null,
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
selectUploadFile(event) {
|
||||
this.setState({ uploadFile: event.target.files[0] });
|
||||
};
|
||||
|
||||
startEditContent(){
|
||||
const formData = new FormData();
|
||||
formData.append("file", this.state.uploadFile);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-edit',
|
||||
data: formData,
|
||||
token: this.props.sessionToken,
|
||||
id: this.props.file.id
|
||||
});
|
||||
|
||||
this.setState({ uploadFile: null });
|
||||
};
|
||||
|
||||
|
||||
onClose = () => {
|
||||
this.props.onClose();
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
render() {
|
||||
return <Dialog show={this.props.show} title='Edit File Content' buttonTitle='Close' onClose={() => this.onClose()} blendOutCancel = {true} valid={true}>
|
||||
<FormGroup as={Col} >
|
||||
<FormControl
|
||||
disabled={false}
|
||||
type='file'
|
||||
onChange={(event) => this.selectUploadFile(event)} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup as={Col} >
|
||||
<Button
|
||||
disabled={this.state.uploadFile === null}
|
||||
onClick={() => this.startEditContent()}>
|
||||
Upload
|
||||
</Button>
|
||||
</FormGroup>
|
||||
|
||||
</Dialog>;
|
||||
}
|
||||
}
|
||||
|
||||
export default EditFileContent;
|
175
src/file/edit-files.js
Normal file
175
src/file/edit-files.js
Normal file
|
@ -0,0 +1,175 @@
|
|||
/**
|
||||
* 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 {FormGroup, FormControl, Button, Col, ProgressBar} from 'react-bootstrap';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
import AppDispatcher from "../common/app-dispatcher";
|
||||
import Table from "../common/table";
|
||||
import TableColumn from "../common/table-column";
|
||||
import EditFileContent from "./edit-file-content";
|
||||
|
||||
|
||||
class EditFilesDialog extends React.Component {
|
||||
valid = true;
|
||||
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
uploadFile: null,
|
||||
uploadProgress: 0,
|
||||
editModal: false,
|
||||
modalFile: {}
|
||||
};
|
||||
}
|
||||
|
||||
onClose() {
|
||||
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
selectUploadFile(event) {
|
||||
this.setState({ uploadFile: event.target.files[0] });
|
||||
};
|
||||
|
||||
startFileUpload(){
|
||||
// upload file
|
||||
const formData = new FormData();
|
||||
formData.append("file", this.state.uploadFile);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-upload',
|
||||
data: formData,
|
||||
token: this.props.sessionToken,
|
||||
progressCallback: this.updateUploadProgress,
|
||||
finishedCallback: this.clearProgress,
|
||||
scenarioID: this.props.scenarioID,
|
||||
});
|
||||
|
||||
this.setState({ uploadFile: null });
|
||||
};
|
||||
|
||||
updateUploadProgress = (event) => {
|
||||
this.setState({ uploadProgress: parseInt(event.percent.toFixed(), 10) });
|
||||
};
|
||||
|
||||
clearProgress = (newFileID) => {
|
||||
/*if (this.props.onChange != null) {
|
||||
let event = {}
|
||||
event["target"] = {}
|
||||
event.target["value"] = newFileID
|
||||
this.props.onChange(event);
|
||||
}
|
||||
*/
|
||||
this.setState({ uploadProgress: 0 });
|
||||
|
||||
|
||||
};
|
||||
|
||||
closeEditModal(){
|
||||
|
||||
this.setState({editModal: false});
|
||||
}
|
||||
|
||||
deleteFile(index){
|
||||
|
||||
let file = this.props.files[index]
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-remove',
|
||||
data: file,
|
||||
token: this.props.sessionToken
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
|
||||
let fileOptions = [];
|
||||
if (this.props.files.length > 0){
|
||||
fileOptions.push(
|
||||
<option key = {0} default>Select image file</option>
|
||||
)
|
||||
fileOptions.push(this.props.files.map((file, index) => (
|
||||
<option key={index+1} value={file.id}>{file.name}</option>
|
||||
)))
|
||||
} else {
|
||||
fileOptions = <option disabled value style={{ display: 'none' }}>No files found, please upload one first.</option>
|
||||
}
|
||||
|
||||
const progressBarStyle = {
|
||||
marginLeft: '100px',
|
||||
marginTop: '-40px'
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Dialog show={this.props.show} title="Edit Files of scenario" buttonTitle="Close" onClose={() => this.onClose()} blendOutCancel = {true} valid={true} size = 'lg'>
|
||||
<div>
|
||||
|
||||
<Table data={this.props.files}>
|
||||
<TableColumn title='ID' dataKey='id'/>
|
||||
<TableColumn title='Name' dataKey='name'/>
|
||||
<TableColumn title='Size (bytes)' dataKey='size'/>
|
||||
<TableColumn title='Type' dataKey='type'/>
|
||||
<TableColumn
|
||||
title=''
|
||||
deleteButton
|
||||
onDelete={(index) => this.deleteFile(index)}
|
||||
editButton
|
||||
onEdit={index => this.setState({ editModal: true, modalFile: this.props.files[index] })}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
<FormGroup as={Col} >
|
||||
<FormControl
|
||||
disabled={this.props.disabled}
|
||||
type='file'
|
||||
onChange={(event) => this.selectUploadFile(event)} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup as={Col} >
|
||||
<Button
|
||||
disabled={this.state.uploadFile === null}
|
||||
onClick={() => this.startFileUpload()}>
|
||||
Upload
|
||||
</Button>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup as={Col} >
|
||||
<ProgressBar
|
||||
striped={true}
|
||||
animated={true}
|
||||
now={this.state.uploadProgress}
|
||||
label={this.state.uploadProgress + '%'}
|
||||
style={progressBarStyle}
|
||||
/>
|
||||
</FormGroup>
|
||||
<div style={{ clear: 'both' }} />
|
||||
|
||||
<EditFileContent show={this.state.editModal} onClose={(data) => this.closeEditModal(data)} sessionToken={this.props.sessionToken} file={this.state.modalFile} />
|
||||
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditFilesDialog;
|
88
src/file/file-store.js
Normal file
88
src/file/file-store.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
/**
|
||||
* 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 ArrayStore from '../common/array-store';
|
||||
import FilesDataManager from './files-data-manager';
|
||||
|
||||
class FileStore extends ArrayStore {
|
||||
constructor() {
|
||||
super('files', FilesDataManager);
|
||||
}
|
||||
|
||||
saveFile(state, action){
|
||||
|
||||
let fileID = parseInt(action.id)
|
||||
state.forEach((element, index, array) => {
|
||||
if (element.id === fileID) {
|
||||
// save blob object
|
||||
array[index]["data"] = new Blob([action.data.data], {type: action.data.type});
|
||||
// update file type
|
||||
array[index]["type"] = action.data.type;
|
||||
|
||||
if (array[index]["objectURL"] !== ''){
|
||||
// free memory of previously generated object URL
|
||||
URL.revokeObjectURL(array[index]["objectURL"]);
|
||||
}
|
||||
// create an object URL for the file
|
||||
array[index]["objectURL"] = URL.createObjectURL(array[index]["data"])
|
||||
}
|
||||
});
|
||||
|
||||
// announce change to listeners
|
||||
this.__emitChange();
|
||||
return state
|
||||
}
|
||||
|
||||
reduce(state, action) {
|
||||
switch (action.type) {
|
||||
case 'files/start-download':
|
||||
FilesDataManager.download(action)
|
||||
return state
|
||||
|
||||
case 'files/start-upload':
|
||||
FilesDataManager.upload(action.data, action.token, action.progressCallback, action.finishedCallback, action.scenarioID);
|
||||
return state;
|
||||
|
||||
case 'files/uploaded':
|
||||
//console.log('ready uploaded');
|
||||
return state;
|
||||
|
||||
case 'files/upload-error':
|
||||
console.log(action.error);
|
||||
return state;
|
||||
|
||||
case 'files/downloaded':
|
||||
// in this case a file is contained in the response (no JSON)
|
||||
return this.saveFile(state, action);
|
||||
|
||||
case 'files/start-edit':
|
||||
FilesDataManager.update(action.data, action.token, action.id);
|
||||
return state;
|
||||
|
||||
case 'files/edited':
|
||||
return this.updateElements(state, [action.data]);
|
||||
|
||||
case this.type + '/edit-error':
|
||||
return state;
|
||||
|
||||
default:
|
||||
return super.reduce(state, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new FileStore();
|
|
@ -1,8 +1,4 @@
|
|||
/**
|
||||
* File: files-data-manager.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 16.03.2017
|
||||
*
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
|
@ -19,17 +15,17 @@
|
|||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import RestDataManager from './rest-data-manager';
|
||||
import RestAPI from '../api/rest-api';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import RestDataManager from '../common/data-managers/rest-data-manager';
|
||||
import RestAPI from '../common/api/rest-api';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
|
||||
class FilesDataManager extends RestDataManager {
|
||||
constructor() {
|
||||
super('file', '/files');
|
||||
}
|
||||
|
||||
upload(file, token = null, progressCallback = null, finishedCallback = null) {
|
||||
RestAPI.upload(this.makeURL('/upload'), file, token, progressCallback).then(response => {
|
||||
upload(file, token = null, progressCallback = null, finishedCallback = null, scenarioID) {
|
||||
RestAPI.upload(this.makeURL(this.url), file, token, progressCallback, scenarioID).then(response => {
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/uploaded',
|
||||
|
@ -38,11 +34,12 @@ class FilesDataManager extends RestDataManager {
|
|||
// Trigger a files reload
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-load',
|
||||
token
|
||||
param: '?scenarioID=' + scenarioID,
|
||||
token: token
|
||||
});
|
||||
|
||||
if (finishedCallback) {
|
||||
finishedCallback();
|
||||
finishedCallback(response.file.id);
|
||||
}
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
|
@ -51,6 +48,38 @@ class FilesDataManager extends RestDataManager {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
download(action){
|
||||
RestAPI.download(this.makeURL(this.url), action.token, action.data).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/downloaded',
|
||||
data: response,
|
||||
id: action.data,
|
||||
token: action.token
|
||||
});
|
||||
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/load-error',
|
||||
error: error
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
update(file, token, id) {
|
||||
|
||||
RestAPI.put(this.makeURL(this.url + '/' + id), file, token).then(response => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/edited',
|
||||
data: response[this.type]
|
||||
});
|
||||
}).catch(error => {
|
||||
AppDispatcher.dispatch({
|
||||
type: this.type + 's/edit-error',
|
||||
error: error
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new FilesDataManager();
|
137
src/file/select-file.js
Normal file
137
src/file/select-file.js
Normal file
|
@ -0,0 +1,137 @@
|
|||
/**
|
||||
* 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 { FormGroup, FormControl, FormLabel, Button, Col, ProgressBar } from 'react-bootstrap';
|
||||
import AppDispatcher from '../common/app-dispatcher';
|
||||
|
||||
class SelectFile extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
uploadFile: null,
|
||||
uploadProgress: 0
|
||||
}
|
||||
}
|
||||
|
||||
selectUploadFile(event) {
|
||||
this.setState({ uploadFile: event.target.files[0] });
|
||||
};
|
||||
|
||||
startFileUpload(){
|
||||
// upload file
|
||||
const formData = new FormData();
|
||||
formData.append("file", this.state.uploadFile);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-upload',
|
||||
data: formData,
|
||||
token: this.props.sessionToken,
|
||||
progressCallback: this.updateUploadProgress,
|
||||
finishedCallback: this.clearProgress,
|
||||
scenarioID: this.props.scenarioID,
|
||||
});
|
||||
|
||||
// TODO make sure that edit config dialog remains open after clicking "Upload" button
|
||||
};
|
||||
|
||||
updateUploadProgress = (event) => {
|
||||
this.setState({ uploadProgress: parseInt(event.percent.toFixed(), 10) });
|
||||
};
|
||||
|
||||
clearProgress = (newFileID) => {
|
||||
/*if (this.props.onChange != null) {
|
||||
let event = {}
|
||||
event["target"] = {}
|
||||
event.target["value"] = newFileID
|
||||
this.props.onChange(event);
|
||||
}
|
||||
this.setState({ uploadProgress: 0 });
|
||||
*/
|
||||
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
let fileOptions = [];
|
||||
if (this.props.files.length > 0){
|
||||
fileOptions.push(
|
||||
<option key = {0} value={-1}>Select file</option>
|
||||
)
|
||||
|
||||
fileOptions.push(this.props.files.map(f =>
|
||||
<option key={f.id} value={f.id}>{f.name}</option>
|
||||
));
|
||||
} else {
|
||||
fileOptions = <option >No files available</option>
|
||||
}
|
||||
|
||||
const progressBarStyle = {
|
||||
marginLeft: '100px',
|
||||
marginTop: '-40px'
|
||||
};
|
||||
|
||||
return <div>
|
||||
<FormGroup>
|
||||
<FormLabel sm={3} md={2}>
|
||||
{this.props.name}
|
||||
</FormLabel>
|
||||
|
||||
<FormGroup as={Col} sm={9} md={10}>
|
||||
<FormControl
|
||||
as="select"
|
||||
value={this.props.value}
|
||||
disabled={this.props.disabled}
|
||||
placeholder='Select file'
|
||||
onChange={(event) => this.props.onChange(event)}>
|
||||
{fileOptions}
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup as={Col} >
|
||||
<FormControl
|
||||
disabled={this.props.disabled}
|
||||
type='file'
|
||||
onChange={(event) => this.selectUploadFile(event)} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup as={Col} >
|
||||
<Button
|
||||
disabled={this.state.uploadFile === null}
|
||||
onClick={() => this.startFileUpload()}>
|
||||
Upload
|
||||
</Button>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup as={Col} >
|
||||
<ProgressBar
|
||||
striped={true}
|
||||
animated={true}
|
||||
now={this.state.uploadProgress}
|
||||
label={this.state.uploadProgress + '%'}
|
||||
style={progressBarStyle}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectFile;
|
210
src/ic/edit-ic.js
Normal file
210
src/ic/edit-ic.js
Normal file
|
@ -0,0 +1,210 @@
|
|||
/**
|
||||
* 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 { FormGroup, FormControl, FormLabel, FormCheck } from 'react-bootstrap';
|
||||
import _ from 'lodash';
|
||||
import {Collapse} from 'react-collapse';
|
||||
import Dialog from '../common/dialogs/dialog';
|
||||
import ParametersEditor from '../common/parameters-editor';
|
||||
|
||||
class EditICDialog extends React.Component {
|
||||
valid = true;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
name: '',
|
||||
websocketurl: '',
|
||||
apiurl: '',
|
||||
location: '',
|
||||
description: '',
|
||||
type: '',
|
||||
category: '',
|
||||
managedexternally: false,
|
||||
startParameterScheme: {},
|
||||
};
|
||||
}
|
||||
|
||||
onClose(canceled) {
|
||||
if (canceled === false) {
|
||||
if (this.valid) {
|
||||
let data = this.props.ic;
|
||||
|
||||
if (this.state.name != null && this.state.name !== "" && this.state.name !== this.props.ic.name) {
|
||||
data.name = this.state.name;
|
||||
}
|
||||
|
||||
if (this.state.websocketurl != null && this.state.websocketurl !== "" && this.state.websocketurl !== "http://" && this.state.websocketurl !== this.props.ic.websocketurl) {
|
||||
data.websocketurl = this.state.websocketurl;
|
||||
}
|
||||
|
||||
if (this.state.apiurl != null && this.state.apiurl !== "" && this.state.apiurl !== "http://" && this.state.apiurl !== this.props.ic.apiurl) {
|
||||
data.apiurl = this.state.apiurl;
|
||||
}
|
||||
|
||||
if (this.state.location != null && this.state.location !== this.props.ic.location) {
|
||||
data.location = this.state.location;
|
||||
}
|
||||
|
||||
if (this.state.description != null && this.state.description !== this.props.ic.description) {
|
||||
data.description = this.state.description;
|
||||
}
|
||||
|
||||
if (this.state.type != null && this.state.type !== "" && this.state.type !== this.props.ic.type) {
|
||||
data.type = this.state.type;
|
||||
}
|
||||
|
||||
if (this.state.category != null && this.state.category !== "" && this.state.category !== this.props.ic.category) {
|
||||
data.category = this.state.category;
|
||||
}
|
||||
if (this.state.startParameterScheme !== {}) {
|
||||
data.startParameterScheme = this.state.startParameterScheme
|
||||
}
|
||||
|
||||
data.managedexternally = this.state.managedexternally;
|
||||
|
||||
|
||||
this.props.onClose(data);
|
||||
this.setState({managedexternally: false});
|
||||
}
|
||||
} else {
|
||||
this.props.onClose();
|
||||
this.setState({managedexternally: false});
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
if(e.target.id === "managedexternally"){
|
||||
this.setState({ managedexternally : !this.state.managedexternally});
|
||||
}
|
||||
else{
|
||||
this.setState({ [e.target.id]: e.target.value });
|
||||
}
|
||||
}
|
||||
|
||||
handleStartParameterSchemeChange(data) {
|
||||
this.setState({ startParameterScheme: data });
|
||||
}
|
||||
|
||||
resetState() {
|
||||
this.setState({
|
||||
name: this.props.ic.name,
|
||||
websocketurl: this.props.ic.websocketurl,
|
||||
apiurl: this.props.ic.apiurl,
|
||||
type: this.props.ic.type,
|
||||
location: this.props.ic.location,
|
||||
description: this.props.ic.description,
|
||||
category: this.props.ic.category,
|
||||
managedexternally: false,
|
||||
startParameterScheme: this.props.ic.startParameterScheme,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let typeOptions = [];
|
||||
switch(this.state.category){
|
||||
case "simulator":
|
||||
typeOptions = ["dummy","generic","dpsim","rtlab","rscad"];
|
||||
break;
|
||||
case "controller":
|
||||
typeOptions = ["kubernetes","villas-controller"];
|
||||
break;
|
||||
case "gateway":
|
||||
typeOptions = ["villas-node","villas-relay"];
|
||||
break;
|
||||
case "service":
|
||||
typeOptions = ["ems","custom"];
|
||||
break;
|
||||
case "equipment":
|
||||
typeOptions = ["chroma-emulator","chroma-loads","sma-sunnyboy","fleps","sonnenbatterie"];
|
||||
break;
|
||||
default:
|
||||
typeOptions =[];
|
||||
}
|
||||
return (
|
||||
<Dialog
|
||||
show={this.props.show}
|
||||
title="Edit Infrastructure Component"
|
||||
buttonTitle="Save"
|
||||
onClose={(c) => this.onClose(c)}
|
||||
onReset={() => this.resetState()}
|
||||
valid={this.valid}
|
||||
size='lg'
|
||||
>
|
||||
<form>
|
||||
<FormLabel column={false}>UUID: {this.props.ic.uuid}</FormLabel>
|
||||
<FormGroup controlId="name">
|
||||
<FormLabel column={false}>Name</FormLabel>
|
||||
<FormControl type="text" placeholder={this.props.ic.name} value={this.state.name} onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="category">
|
||||
<FormLabel column={false}>Category</FormLabel>
|
||||
<FormControl as="select" value={this.state.category} onChange={(e) => this.handleChange(e)}>
|
||||
<option>simulator</option>
|
||||
<option>controller</option>
|
||||
<option>service</option>
|
||||
<option>gateway</option>
|
||||
<option>equipment</option>
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
<FormGroup controlId="type">
|
||||
<FormLabel column={false}>Type</FormLabel>
|
||||
<FormControl as="select" value={this.state.type} onChange={(e) => this.handleChange(e)}>
|
||||
<option default>Select type</option>
|
||||
{typeOptions.map((name,index) => (
|
||||
<option key={index}>{name}</option>
|
||||
))}
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
<FormGroup controlId="websocketurl">
|
||||
<FormLabel column={false}>Websocket URL</FormLabel>
|
||||
<FormControl type="text" placeholder={this.props.ic.websocketurl} value={this.state.websocketurl || 'http://' } onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="apiurl">
|
||||
<FormLabel column={false}>API URL</FormLabel>
|
||||
<FormControl type="text" placeholder={this.props.ic.apiurl} value={this.state.apiurl || 'http://' } onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="location">
|
||||
<FormLabel column={false}>Location</FormLabel>
|
||||
<FormControl type="text" placeholder={this.props.ic.location} value={this.state.location || '' } onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId="description">
|
||||
<FormLabel column={false}>Description</FormLabel>
|
||||
<FormControl type="text" placeholder={this.props.ic.description} value={this.state.description || '' } onChange={(e) => this.handleChange(e)} />
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<FormGroup controlId='startParameterScheme'>
|
||||
<FormLabel column={false}>Start parameter scheme of IC</FormLabel>
|
||||
<ParametersEditor
|
||||
content={this.state.startParameterScheme}
|
||||
disabled={false}
|
||||
onChange={(data) => this.handleStartParameterSchemeChange(data)}
|
||||
/>
|
||||
</FormGroup>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditICDialog;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue