diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..afdd7a7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +version: "2" + +services: + frontend: + image: nginx:stable + volumes: + - ./nginx:/etc/nginx/conf.d/ + - ./build:/www + links: + - backend + ports: + - "80:80" + - "443:443" + + backend: + image: villasweb-backend + links: + - database + + database: + image: mongo:latest + volumes: + - /opt/database:/data/db + diff --git a/nginx/villas.conf b/nginx/villas.conf new file mode 100644 index 0000000..f3417de --- /dev/null +++ b/nginx/villas.conf @@ -0,0 +1,32 @@ +server { + listen 80 default_server; + server_name VILLASweb; + + # backend location + location /api/ { + proxy_redirect off; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # rewrite url to exclude /api on context broker side + rewrite ^/api/?(.*) /api/v1/$1 break; + + proxy_pass http://backend:4000/; + } + + # frontend location + location / { + root /www; + } + + # error pages + error_page 404 /404.html; + location = /404.html { + root /usr/share/nginx/html; + } + + error_page 500 502 503 504 50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/src/components/dialog/new-project.js b/src/components/dialog/new-project.js index 6abc0c8..ae08024 100644 --- a/src/components/dialog/new-project.js +++ b/src/components/dialog/new-project.js @@ -43,21 +43,30 @@ class NewProjectDialog extends Component { } resetState() { - this.setState({ name: '', simulation: this.props.simulations[0]._id }); + 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; } - this.valid = name; + 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() { @@ -69,7 +78,7 @@ class NewProjectDialog extends Component { this.handleChange(e)} /> - + Simulation this.handleChange(e)}> {this.props.simulations.map(simulation => ( diff --git a/src/components/dialog/new-simulation-model.js b/src/components/dialog/new-simulation-model.js index cb3ca63..e8cf8aa 100644 --- a/src/components/dialog/new-simulation-model.js +++ b/src/components/dialog/new-simulation-model.js @@ -72,28 +72,39 @@ class NewSimulationModelDialog extends Component { } resetState() { - this.setState({ name: '', simulator: this.props.simulators[0]._id, length: '1', mapping: [ { name: 'Signal', type: 'Type' } ] }); + this.setState({ + name: '', + simulator: this.props.simulators[0] != null ? this.props.simulators[0]._id : '', + length: '1', + mapping: [ { name: 'Signal', type: 'Type' } ] + }); } validateForm(target) { // check all controls var name = true; var length = true; + var simulator = true; if (this.state.name === '') { name = false; } + if (this.state.simulator === '') { + simulator = false; + } + // test if simulatorid is a number (in a string, not type of number) if (!/^\d+$/.test(this.state.length)) { length = false; } - this.valid = name && length; + this.valid = name && length && simulator; // return state to control if (target === 'name') return name ? "success" : "error"; else if (target === 'length') return length ? "success" : "error"; + else if (target === 'simulator') return simulator ? "success" : "error"; } render() { diff --git a/src/containers/project.js b/src/containers/project.js index 48c42ba..896db4f 100644 --- a/src/containers/project.js +++ b/src/containers/project.js @@ -15,7 +15,7 @@ import AppDispatcher from '../app-dispatcher'; import ProjectStore from '../stores/project-store'; import VisualizationStore from '../stores/visualization-store'; -import Table from '../components/table'; +import CustomTable from '../components/table'; import TableColumn from '../components/table-column'; import NewVisualzationDialog from '../components/dialog/new-visualization'; import EditVisualizationDialog from '../components/dialog/edit-visualization'; @@ -25,66 +25,77 @@ class Visualizations extends Component { return [ ProjectStore, VisualizationStore ]; } - static calculateState(prevState) { + static calculateState(prevState, props) { + + let currentProjects = ProjectStore.getState(); + let currentVisualizations = VisualizationStore.getState(); + if (prevState) { + var projectUpdate = prevState.project; + + // Compare content of the visualizations array, reload projects if changed + if (JSON.stringify(prevState.visualizations) !== JSON.stringify(currentVisualizations)) { + Visualizations.loadProjects(); + } + + // Compare content of the projects array, update visualizations if changed + if (JSON.stringify(prevState.projects) !== JSON.stringify(currentProjects)) { + projectUpdate = Visualizations.findProjectInState(currentProjects, props.params.project); + Visualizations.loadVisualizations(projectUpdate.visualizations); + } + return { - projects: ProjectStore.getState(), - visualizations: VisualizationStore.getState(), + projects: currentProjects, + visualizations: currentVisualizations, newModal: prevState.newModal, deleteModal: prevState.deleteModal, editModal: prevState.editModal, modalData: prevState.modalData, - project: prevState.project, - reload: prevState.reload + project: projectUpdate }; } else { + + let initialProject = Visualizations.findProjectInState(currentProjects, props.params.project); + // If projects have been loaded already but visualizations not (redirect from Projects page) + if (initialProject && (!currentVisualizations || currentVisualizations.length === 0)) { + Visualizations.loadVisualizations(initialProject.visualizations); + } + return { - projects: ProjectStore.getState(), - visualizations: VisualizationStore.getState(), + projects: currentProjects, + visualizations: currentVisualizations, newModal: false, deleteModal: false, editModal: false, modalData: {}, - project: {}, - reload: false + project: initialProject || {} }; } } - componentWillMount() { + static findProjectInState(projects, projectId) { + return projects.find((project) => project._id === projectId); + } + + static loadProjects() { AppDispatcher.dispatch({ type: 'projects/start-load' }); } - componentDidUpdate() { - if (this.state.project._id !== this.props.params.project /*|| this.state.reload*/) { - this.reloadProject(); - - if (this.state.reload) { - this.setState({ reload: false }); - } - } + static loadVisualizations(visualizations) { + AppDispatcher.dispatch({ + type: 'visualizations/start-load', + data: visualizations + }); } - reloadProject() { - // select project by param id - this.state.projects.forEach((project) => { - if (project._id === this.props.params.project) { - // JSON.parse(JSON.stringify(obj)) = deep clone to make also copy of widget objects inside - this.setState({ project: JSON.parse(JSON.stringify(project)) }); - - // load visualizations - AppDispatcher.dispatch({ - type: 'visualizations/start-load', - data: project.visualizations - }); - } - }); + componentWillMount() { + Visualizations.loadProjects(); } closeNewModal(data) { @@ -98,7 +109,7 @@ class Visualizations extends Component { }); } - this.setState({ newModal: false, reload: data != null }); + this.setState({ newModal: false }); } confirmDeleteModal() { @@ -106,7 +117,7 @@ class Visualizations extends Component { AppDispatcher.dispatch({ type: 'visualizations/start-remove', - data: this.state.modalVisualization + data: this.state.modalData }); } @@ -124,25 +135,22 @@ class Visualizations extends Component { render() { // get visualizations for this project var visualizations = []; - if (this.state.visualizations && this.state.project.visualizations) { - this.state.visualizations.forEach((visualization) => { - this.state.project.visualizations.forEach((id) => { - if (visualization._id === id) { - visualizations.push(visualization); - } - }); - }); + visualizations = this.state.visualizations.filter( + (visualization) => this.state.project.visualizations.includes(visualization._id) + ).sort( + (visA, visB) => visA.name.localeCompare(visB.name) + ); } return (

{this.state.project.name}

- + - this.setState({ editModal: true, modalData: this.state.visualizations[index] })} onDelete={index => this.setState({ deleteModal: true, modalData: this.state.visualizations[index] })} /> -
+ this.setState({ editModal: true, modalData: visualizations[index] })} onDelete={(index) => this.setState({ deleteModal: true, modalData: visualizations[index] })} /> + @@ -169,4 +177,4 @@ class Visualizations extends Component { } } -export default Container.create(Visualizations); +export default Container.create(Visualizations, {withProps: true});