diff --git a/app/controllers/project/index.js b/app/controllers/project/index.js index 4f457cd..80f931b 100644 --- a/app/controllers/project/index.js +++ b/app/controllers/project/index.js @@ -49,7 +49,7 @@ export default Ember.Controller.extend({ submitNew() { // verify properties var properties = this.getProperties('name'); - if (properties['name'] == null || properties['name'] == "") { + if (properties['name'] == null || properties['name'] === "") { this.set('errorMessage', 'Visualization name is missing'); return; } @@ -79,7 +79,7 @@ export default Ember.Controller.extend({ submitEdit() { // verify properties var properties = this.getProperties('name'); - if (properties['name'] == null || properties['name'] == "") { + if (properties['name'] == null || properties['name'] === "") { this.set('errorMessage', 'Visualization name is missing'); return; } diff --git a/app/controllers/projects.js b/app/controllers/projects.js index 1a5dcfd..c77ae2e 100644 --- a/app/controllers/projects.js +++ b/app/controllers/projects.js @@ -19,6 +19,14 @@ export default Ember.Controller.extend({ errorMessage: null, project: null, + projectSimulation: null, + + _updateSimulations: function() { + if (this.get('model.simulations') != null && this.get('model.simulations.length') > 0) { + var simulations = this.get('model.simulations'); + this.set('projectSimulation', simulations.toArray()[0]); + } + }.observes('model'), actions: { showNewModal() { @@ -35,6 +43,7 @@ export default Ember.Controller.extend({ this.set('errorMessage', null); this.set('project', project); this.set('name', project.get('name')); + this.set('projectSimulation', project.get('simulation')); // show the dialog this.set('isShowingEditModal', true); @@ -51,17 +60,24 @@ export default Ember.Controller.extend({ submitNew() { // verify properties var properties = this.getProperties('name'); - if (properties['name'] == null || properties['name'] == "") { + if (properties['name'] == null || properties['name'] === "") { this.set('errorMessage', 'Project name is missing'); return; } - // set owner properties + // set owner property var user = this.get('sessionUser.user'); properties['owner'] = user; + // set simulation property + properties['simulation'] = this.get('projectSimulation.id'); + // create new project var project = this.store.createRecord('project', properties); + + // this change will not be saved, but it is nessecary otherwise ember will omit the simulation's id in the post request + this.get('projectSimulation.projects').pushObject(project); + var controller = this; project.save().then(function() { @@ -78,14 +94,22 @@ export default Ember.Controller.extend({ submitEdit() { // verify properties var properties = this.getProperties('name'); - if (properties['name'] == null || properties['name'] == "") { + if (properties['name'] == null || properties['name'] === "") { this.set('errorMessage', 'Project name is missing'); return; } + // remove from old simulation + + // save properties + properties['simulation'] = this.get('projectSimulation.id'); + this.get('project').setProperties(properties); + // this change will not be saved, but it is nessecary otherwise ember will omit the simulation's id in the post request + this.get('projectSimulation.projects').pushObject(this.get('project')); + var controller = this; this.get('project').save().then(function() { @@ -110,6 +134,18 @@ export default Ember.Controller.extend({ cancelDelete() { this.set('isShowingDeleteModal', false); + }, + + selectSimulation(simulationName) { + // get simulation by name + var simulations = this.get('model.simulations'); + var controller = this; + + simulations.forEach(function(simulation) { + if (simulation.get('name') === simulationName) { + controller.set('projectSimulation', simulation); + } + }); } } }); diff --git a/app/controllers/simulation/index.js b/app/controllers/simulation/index.js index 2208780..d201f3a 100644 --- a/app/controllers/simulation/index.js +++ b/app/controllers/simulation/index.js @@ -43,11 +43,11 @@ export default Ember.Controller.extend({ this.set('name', simulationModel.get('name')); var simulators = this.get('model.simulators'); - var simulatorId = simulationModel.get('simulator'); + var simulatorId = simulationModel.get('simulator.id'); var simulatorName = null; simulators.forEach(function(simulator) { - if (simulator.get('simulatorid') == simulatorId) { + if (simulator.get('id') === simulatorId) { simulatorName = simulator.get('name'); } }); @@ -69,33 +69,25 @@ export default Ember.Controller.extend({ submitNew() { // verify properties var properties = this.getProperties('name'); - if (properties['name'] == null || properties['name'] == "") { + if (properties['name'] == null || properties['name'] === "") { this.set('errorMessage', 'Simulation model name is missing'); return; } // set simuatlion properties var simulation = this.get('model.simulation'); - properties['simulation'] = simulation.get('id');; + properties['simulation'] = simulation; // get the simulator id by simulator name var simulators = this.get('model.simulators'); - var simulatorId = null; var simulatorName = this.get('simulatorName'); simulators.forEach(function(simulator) { if (simulator.get('name') === simulatorName) { - simulatorId = simulator.get('simulatorid'); + properties['simulator'] = simulator; } }); - if (simulatorId == null) { - Ember.debug('Unable to find simulator by name'); - return; - } - - properties['simulator'] = simulatorId; - // create new model var simulationModel = this.store.createRecord('simulation-model', properties); @@ -118,14 +110,14 @@ export default Ember.Controller.extend({ submitEdit() { // verify properties var properties = this.getProperties('name'); - if (properties['name'] == null || properties['name'] == "") { + if (properties['name'] == null || properties['name'] === "") { this.set('errorMessage', 'Simulation model name is missing'); return; } // set simuatlion properties var simulation = this.get('model.simulation'); - properties['simulation'] = simulation.get('id');; + properties['simulation'] = simulation.get('id'); // get the simulator id by simulator name var simulators = this.get('model.simulators'); diff --git a/app/controllers/simulations.js b/app/controllers/simulations.js index 9eaea92..0be9ecf 100644 --- a/app/controllers/simulations.js +++ b/app/controllers/simulations.js @@ -14,11 +14,12 @@ export default Ember.Controller.extend({ isShowingNewModal: false, isShowingEditModal: false, - isShowingEditModal: false, + isShowingDeleteModal: false, errorMessage: null, simulation: null, + simulationRunning: true, actions: { showNewModal() { @@ -48,10 +49,19 @@ export default Ember.Controller.extend({ this.set('isShowingDeleteModal', true); }, + showRunningModal(simulation) { + // set properties + this.set('simulation', simulation); + this.set('simulationRunning', simulation.get('running')); + + // show the dialog + this.set('isShowingRunningModal', true); + }, + submitNew() { // verify properties var properties = this.getProperties('name'); - if (properties['name'] == null || properties['name'] == "") { + if (properties['name'] == null || properties['name'] === "") { this.set('errorMessage', 'Simulation name is missing'); return; } @@ -78,7 +88,7 @@ export default Ember.Controller.extend({ submitEdit() { // verify properties var properties = this.getProperties('name'); - if (properties['name'] == null || properties['name'] == "") { + if (properties['name'] == null || properties['name'] === "") { this.set('errorMessage', 'Simulation name is missing'); return; } @@ -110,6 +120,34 @@ export default Ember.Controller.extend({ cancelDelete() { this.set('isShowingDeleteModal', false); + }, + + confirmRunningSimulation() { + // set the property + var simulation = this.get('simulation'); + simulation.set('running', this.get('simulationRunning')); + + // save property + var controller = this; + + simulation.save().then(function() { + controller.set('isShowingRunningModal', false); + }, function() { + Ember.debug('Error saving running simulation'); + }); + }, + + cancelRunningSimulation() { + this.set('isShowingRunningModal', false); + }, + + selectRunning(running) { + // NOTE: running is a string and not a boolean value + if (running === 'true') { + this.set('simulationRunning', true); + } else { + this.set('simulationRunning', false); + } } } }); diff --git a/app/controllers/simulators.js b/app/controllers/simulators.js index af9cb28..2163e3c 100644 --- a/app/controllers/simulators.js +++ b/app/controllers/simulators.js @@ -13,12 +13,14 @@ export default Ember.Controller.extend({ isShowingNewModal: false, isShowingDeleteModal: false, isShowingEditModal: false, + isShowingRunningModal: false, simulatorid: 1, errorMessage: null, simulator: null, simulatorName: null, simulatorEdit: null, + simulatorRunning: true, actions: { showNewModal() { @@ -49,6 +51,15 @@ export default Ember.Controller.extend({ this.set('isShowingEditModal', true); }, + showRunningModal(simulator) { + // set properties + this.set('simulator', simulator); + this.set('simulatorRunning', simulator.get('running')); + + // show the dialog + this.set('isShowingRunningModal', true); + }, + newSimulator() { // verify properties var properties = this.getProperties('name', 'simulatorid', 'endpoint'); @@ -114,6 +125,34 @@ export default Ember.Controller.extend({ cancelEditSimulator() { this.set('isShowingEditModal', false); + }, + + confirmRunningSimulation() { + // set the property + var simulator = this.get('simulator'); + simulator.set('running', this.get('simulatorRunning')); + + // save property + var controller = this; + + simulator.save().then(function() { + controller.set('isShowingRunningModal', false); + }, function() { + Ember.debug('Error saving running simulator'); + }); + }, + + cancelRunningSimulation() { + this.set('isShowingRunningModal', false); + }, + + selectRunning(running) { + // NOTE: running is a string and not a boolean value + if (running === 'true') { + this.set('simulatorRunning', true); + } else { + this.set('simulatorRunning', false); + } } } }); diff --git a/app/mixins/websocket-live-stream-mixin.js b/app/mixins/websocket-live-stream-mixin.js index c225d6a..72d2200 100644 --- a/app/mixins/websocket-live-stream-mixin.js +++ b/app/mixins/websocket-live-stream-mixin.js @@ -13,43 +13,95 @@ import ENV from '../config/environment'; const { service } = Ember.inject; export default Ember.Mixin.create({ - host: 'ws://' + ENV.APP.LIVE_HOST, - namespace: '', - runningSimulation: service('running-simulation'), + runningSimulations: service('running-simulations'), + store: service(), - socket: null, + sockets: [], init() { this._super(...arguments); - // start simulation service - this.get('runningSimulation').loadRunningSimulation(); + // load simulators + var self = this; + + this.store.findAll('simulator').then(function() { + // start simulators service + self.get('runningSimulations').loadRunningSimulations(); + }); }, - _runningSimulationChanged: function() { - // called each time running simulation did change - var simulation = this.get('runningSimulation.simulation'); - if (simulation !== null) { - if (this.socket === null) { - // create new socket connection - this.socket = new WebSocket(this.host + this.namespace); - this.socket.binaryType = 'arraybuffer'; + _runningSimulationsChanged: function() { + // called each time running simulations did change + var self = this; - // register callbacks - var self = this; - this.socket.onopen = function(event) { self.onopen.apply(self, [event]); }; - this.socket.onclose = function(event) { self.onclose.apply(self, [event]); }; - this.socket.onmessage = function(event) { self.onmessage.apply(self, [event]); }; - this.socket.onerror = function(event) { self.onerror.apply(self, [event]); }; + this.get('runningSimulations.simulationModels').forEach(function(simulationModel) { + //console.log('Model: ' + simulationModel.get('name') + ' (' + simulationModel.get('simulator.name') + ')'); + + // get socket for simulation model + let modelid = simulationModel.get('id'); + var socket = self._socketForSimulationModel(modelid); + + // create new socket for simulation model if not running yet + if (socket == null) { + // try to create new socket + socket = new WebSocket('ws://' + simulationModel.get('simulator.endpoint')); + console.log('opened ' + simulationModel.get('simulator.endpoint')); + + if (socket != null) { + socket.binaryType = 'arraybuffer'; + + // register callbacks + socket.onopen = function(event) { self.onopen.apply(self, [event]); }; + socket.onclose = function(event) { self.onclose.apply(self, [event]); }; + socket.onmessage = function(event) { self.onmessage.apply(self, [event]); }; + socket.onerror = function(event) { self.onerror.apply(self, [event]); }; + + // save socket + self._addSocketForSimulationModel(socket, modelid); + + console.log('simulation model \'' + simulationModel.get('name') + '\' started'); + } } - } else { - // stop stream if still opened - if (this.socket !== null) { - this.socket.close(); - this.socket = null; + }); + }.observes('runningSimulations.simulationModels.@each.mod'), + + _socketForSimulationModel(modelid) { + this.get('sockets').forEach(function(s) { + if (s.id === modelid) { + return s.socket; + } + }); + + return null; + }, + + _addSocketForSimulationModel(socket, modelid) { + // search for existing socket to replace + this.get('sockets').forEach(function(s) { + if (s.id === modelid) { + s.socket = socket; + return; + } + }); + + // add new socket + this.get('sockets').pushObject({ id: modelid, socket: socket }); + }, + + _removeSocketForSimulationModel(modelid) { + var sockets = this.get('sockets'); + var i = 0; + + while (i < sockets.get('length')) { + if (sockets[i].id === modelid) { + // remove object from array + sockets.slice(i, 1); + } else { + // only increase index if no object was removed + i++; } } - }.observes('runningSimulation.simulation'), + }, onopen(/*event*/) { Ember.debug('websocket opened'); diff --git a/app/models/simulation-model.js b/app/models/simulation-model.js index 9493dd9..199991a 100644 --- a/app/models/simulation-model.js +++ b/app/models/simulation-model.js @@ -13,7 +13,7 @@ import { belongsTo/*, hasMany*/ } from 'ember-data/relationships'; export default Model.extend({ name: attr('string'), - simulator: attr('number'), + simulator: belongsTo('simulator', { async: true }), length: attr('number'), mapping: attr('array'), simulation: belongsTo('simulation', { async: true }) diff --git a/app/routes/projects.js b/app/routes/projects.js index cf7f030..9c31feb 100644 --- a/app/routes/projects.js +++ b/app/routes/projects.js @@ -14,8 +14,12 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, { sessionUser: Ember.inject.service('session-user'), model() { - // get projects for current user + // get projects for current user, simulations are needed for the simulation picker var user = this.get('sessionUser.user'); - return user.get('projects'); + + return Ember.RSVP.hash({ + projects: user.get('projects'), + simulations: this.store.findAll('simulation') + }); } }); diff --git a/app/routes/visualization/index.js b/app/routes/visualization/index.js index 35a7beb..346c076 100644 --- a/app/routes/visualization/index.js +++ b/app/routes/visualization/index.js @@ -12,6 +12,9 @@ import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-rout export default Ember.Route.extend(AuthenticatedRouteMixin, { model(params) { - return this.store.findRecord('visualization', params.visualizationid); + return Ember.RSVP.hash({ + /*simulation: this.store.findRecord('simulation', params.simulationid),*/ + visualization: this.store.findRecord('visualization', params.visualizationid) + }); } }); diff --git a/app/services/running-simulation.js b/app/services/running-simulation.js deleted file mode 100644 index 2d97597..0000000 --- a/app/services/running-simulation.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * File: running-simulation.js - * Author: Markus Grigull - * Date: 26.07.2016 - * Copyright: 2016, Institute for Automation of Complex Power Systems, EONERC - * This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential. - * Unauthorized copying of this file, via any medium is strictly prohibited. - **********************************************************************************/ - -import Ember from 'ember'; - -const { - inject: { service } -} = Ember; - -export default Ember.Service.extend({ - session: service('session'), - store: service(), - - loadRunningSimulation: function() { - var self = this; - - // check every second for running simulation - /* - setInterval(function() { - // check if running simulation did changed - self.get('store').findAll('simulation').then(function(simulations) { - var newSimulation = null; - - simulations.forEach(function(simulation) { - if (simulation.get('running') === true) { - newSimulation = simulation; - } - }); - - if (newSimulation !== self.get('simulation')) { - self.set('simulation', newSimulation); - } - }); - }, 1000);*/ - } -}); diff --git a/app/services/running-simulations.js b/app/services/running-simulations.js new file mode 100644 index 0000000..8aef65c --- /dev/null +++ b/app/services/running-simulations.js @@ -0,0 +1,64 @@ +/** + * File: running-simulations.js + * Author: Markus Grigull + * Date: 26.07.2016 + * Copyright: 2016, Institute for Automation of Complex Power Systems, EONERC + * This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential. + * Unauthorized copying of this file, via any medium is strictly prohibited. + **********************************************************************************/ + +import Ember from 'ember'; + +const { + inject: { service } +} = Ember; + +export default Ember.Service.extend({ + session: service('session'), + sessionUser: Ember.inject.service('session-user'), + store: service(), + + simulationModels: [], + + loadRunningSimulations: function() { + var self = this; + + // check for running simulations + setInterval(function() { + if (self.get('sessionUser.user') != null) { + // check if running simulations did changed + self.get('store').findAll('simulation').then(function(simulations) { + // search for running simulations + simulations.forEach(function(simulation) { + if (simulation.get('running') === true) { + // get all models of the simulation + simulation.get('models').forEach(function(model) { + self.get('store').findRecord('simulation-model', model.get('id')).then(function(m) { + // add to array + self._addSimulationModel(m); + }); + }); + } else { + // clear all models of the simulation + } + }); + }); + } + }, 3000); + }, + + _addSimulationModel(simulationModel) { + // check if the model is already in the array + var models = this.get('simulationModels'); + var length = models.get('length'); + + for (var i = 0; i < length; i++) { + if (models[i].get('id') === simulationModel.get('id')) { + return; + } + } + + // not found, so add to the array + this.get('simulationModels').pushObject(simulationModel); + } +}); diff --git a/app/templates/projects.hbs b/app/templates/projects.hbs index b4346fd..f544a78 100644 --- a/app/templates/projects.hbs +++ b/app/templates/projects.hbs @@ -4,13 +4,17 @@ + - {{#each model as |project|}} + {{#each model.projects as |project|}} + + + + + + + + +
NameSimulation
{{#link-to "project.index" project.id}}{{project.name}}{{/link-to}} + {{project.simulation.name}} +
Edit @@ -40,6 +44,18 @@ {{input id='name' placeholder='Enter project name' value=name}}
+ + + +
@@ -69,6 +85,18 @@ {{input id='name' placeholder='Enter project name' value=name}}
+ + + +
diff --git a/app/templates/simulation/index.hbs b/app/templates/simulation/index.hbs index 6a2efd5..3804575 100644 --- a/app/templates/simulation/index.hbs +++ b/app/templates/simulation/index.hbs @@ -6,7 +6,7 @@ - + {{#each model.simulation.models as |simulationModel|}} @@ -14,8 +14,8 @@ - - + {{#each model as |simulation|}} @@ -17,8 +17,7 @@ + {{#each model as |simulator|}}
NameSimulatorSimulator
{{#link-to "simulation-model.index" simulationModel.id}}{{simulationModel.name}}{{/link-to}} - {{simulationModel.simulator}} + + {{simulationModel.simulator.name}}
diff --git a/app/templates/simulations.hbs b/app/templates/simulations.hbs index d94b085..fee2ede 100644 --- a/app/templates/simulations.hbs +++ b/app/templates/simulations.hbs @@ -5,7 +5,7 @@
Name Running
@@ -100,3 +99,21 @@ {{/modal-dialog}} {{/if}} + +{{#if isShowingRunningModal}} + {{#modal-dialog attachment="middle center" translucentOverlay=true}} +

Simulation running

+ + {{simulation.name}}: + + + +
+ + + + {{/modal-dialog}} +{{/if}} diff --git a/app/templates/simulators.hbs b/app/templates/simulators.hbs index ed099fd..d610d92 100644 --- a/app/templates/simulators.hbs +++ b/app/templates/simulators.hbs @@ -7,12 +7,12 @@
ID Running Endpoint
- {{simulator.name}} @@ -26,8 +26,7 @@
- - + Edit Delete
@@ -141,3 +140,21 @@ {{/if}} {{/modal-dialog}} {{/if}} + +{{#if isShowingRunningModal}} + {{#modal-dialog attachment="middle center" translucentOverlay=true}} +

Simulator running

+ + {{simulator.name}}: + + + +
+ + + + {{/modal-dialog}} +{{/if}} diff --git a/app/templates/visualization/index.hbs b/app/templates/visualization/index.hbs index b85b398..e7ff8f8 100644 --- a/app/templates/visualization/index.hbs +++ b/app/templates/visualization/index.hbs @@ -1,8 +1,9 @@ -

{{model.name}}

+{{#link-to 'project.index' project.id}}Back to {{model.project.name}}{{/link-to}} -{{plot-container plots=model.plots}} +

{{model.visualization.name}}

+ +{{plot-container plots=model.visualization.plots}}

- {{#link-to "visualization.edit" model.id}}Edit visualization{{/link-to}} - {{#link-to "visualization.delete" model.id}}Delete visualization{{/link-to}} + {{#link-to "visualization.edit" model.visualization.id}}Edit layout{{/link-to}}

diff --git a/todo.md b/todo.md index b0982cf..f730b0e 100644 --- a/todo.md +++ b/todo.md @@ -3,10 +3,10 @@ - Don't log out on unauthorized access (admin level lower than required) - Move plot attributes/styling from plot-container into actual plots - Move drag-n-drop to mixins - - Websocket node is working in develop branch - - Add API host to config/environment.js - - Empty visualization after delete - Go into edit mode if visualization is empty + - Auto-detect if simulators are running + - Remove running socket if it's not in the updated list + - Real relationship between simulation-model and simulator websocketserverip/config.json websocketserverip/nodes.json