diff --git a/app/adapters/application.js b/app/adapters/application.js index 3e8a768ec2ab16f1af3c589c0b7e44fbc889d197..cf5ce3159cc68e943260e0a28280a67f843becf9 100644 --- a/app/adapters/application.js +++ b/app/adapters/application.js @@ -12,7 +12,7 @@ export default BaseAdapter.extend(UrlTemplates, { urlSegments: { systemId() { - return this.get('session.systemId'); + return this.get('session.system.id'); } } }); \ No newline at end of file diff --git a/app/models/baseentity.js b/app/models/baseentity.js index bba66d488563f0fc41e28a5232e741307d78d81c..a9403e2ef09cf08025d5405926ee6725cdbe1920 100644 --- a/app/models/baseentity.js +++ b/app/models/baseentity.js @@ -4,5 +4,8 @@ import attr from 'ember-data/attr'; export default Model.extend({ // id: attr('string') - not allowed to be listed by ember systemId: attr('string'), - type: attr('string') + type: attr('string'), + revisionNumber: attr('number'), + changelogSequence: attr('number'), + lastUpdate: attr('date') }); diff --git a/app/models/revision.js b/app/models/revision.js new file mode 100644 index 0000000000000000000000000000000000000000..30dad4269d0473833d648d77ab287d68b01cf678 --- /dev/null +++ b/app/models/revision.js @@ -0,0 +1,11 @@ +import BaseEntity from './baseentity'; +import attr from 'ember-data/attr'; + +const Model = BaseEntity.extend({ + technology: attr('string'), + sourceId: attr('string'), + targetId: attr('string'), + workload: attr('number') +}); + +export default Model; \ No newline at end of file diff --git a/app/models/system.js b/app/models/system.js index 037318cd56e3ad7c567603a96cbe0df0d2e4075a..6beb1bd9a346422380d9c0547e425d692c4eed9e 100644 --- a/app/models/system.js +++ b/app/models/system.js @@ -1,8 +1,10 @@ import BaseEntity from './baseentity'; import attr from 'ember-data/attr'; +import { memberAction } from 'ember-api-actions'; const Model = BaseEntity.extend({ - name: attr('string') + name: attr('string'), + getRevision: memberAction({ path: 'revision', type: 'GET', urlType: 'findRecord'}) }); Model.reopenClass({ diff --git a/app/routes/deployments/single.js b/app/routes/deployments/single.js index 495b417bfbf84c42e63ca2bacc78149bf88a6832..1c9363a354ccf3dafa6dd2f3619ad42063222a03 100644 --- a/app/routes/deployments/single.js +++ b/app/routes/deployments/single.js @@ -5,9 +5,7 @@ export default Ember.Route.extend({ changelogStream: Ember.inject.service(), model(params) { const systemId = params.systemId; - this.set('session.systemId', systemId); // add the system to all requests - const changelogStream = this.get('changelogStream'); // lazy loaded, requires session id - changelogStream.connect(systemId); + /* * note that findAll returns an Observable Array which automatically @@ -19,8 +17,47 @@ export default Ember.Route.extend({ * We also load all the data, so that the transformation strategies can assume that the whole * meta model is cached. This also allowes that the architecture view is only an alias */ + return this.loadSystem(systemId) + .then(this.loadRevision.bind(this)) + .then((revision) => { + return Ember.RSVP.resolve() + .then(this.loadMetaModel.bind(this)) + .then(models => this.verifyRevision(revision, models)) + .then((models) => { + this.debug('loaded models', models); + return { + systemId: systemId, + revision: revision, + instances: models, + mode: this.get('routeName').split('.')[0] // deployments/architectures + }; + }); + }) + .catch(err => { + if(err === 'outdated') { + // wait a bit to avoid DDOS, TODO: exponential backoff? + this.refresh(); + } else { + console.error('could not load models', err); + } + throw err; // TODO: handle errors in UI + }); + }, + loadSystem(systemId) { + return this.store.findRecord('system', systemId) + .then((system) => { + this.debug('loaded system', system); + this.set('session.system', system); // add the system to all requests + const changelogStream = this.get('changelogStream'); // lazy loaded, requires session id + changelogStream.connect(system.id); + return system; + }); + }, + loadRevision(system) { + return system.getRevision(); + }, + loadMetaModel(system) { const load = (type) => this.store.findAll(type); - return Ember.RSVP.hash({ nodes: load('node'), nodeGroups: load('nodegroup'), @@ -28,16 +65,25 @@ export default Ember.Route.extend({ serviceInstances: load('serviceinstance'), communications: load('communication'), communicationInstances: load('communicationinstance') - }).then((models) => { - this.debug('loaded models', models); - return { - systemId: systemId, - instances: models, - mode: this.get('routeName').split('.')[0] - }; }); }, + verifyRevision(revision, models) { + const outdatedRecords = Object + .keys(models) + .map(key => models[key]) + .filter(instances => { + return instances.filter(record => + record.get('revisionNumber') >= revision.revisionNumber && record.get('changelogSequence') > revision.changelogSequence + ).length > 0; + }); + if(outdatedRecords.length > 0) { + this.debug('records loaded from server seem to have changed during loading, refreshing data!', revision, models); + return Ember.RSVP.Promise.reject('outdated'); + } + + return models; + }, actions: { loadDetails(rawEntity) { this.debug('loadDetails action', rawEntity); @@ -63,6 +109,7 @@ export default Ember.Route.extend({ // do not disconnect if transitioning to a child route (details) if (transition.targetName.indexOf(this.get('routeName')) !== 0) { this.get('changelogStream').disconnect(); + clearTimeout(this.get('refreshTimeout')); } } } diff --git a/app/services/changelog-stream.js b/app/services/changelog-stream.js index 52849dacbf9ea72a9430e45df467aad8f2911e8d..d0432b243603ee151c937a8319f4023f221c5f42 100644 --- a/app/services/changelog-stream.js +++ b/app/services/changelog-stream.js @@ -9,6 +9,11 @@ export default Ember.Service.extend({ this.debug('session', this.get('systemId')); }, connect(systemId) { + if(this.get('socket')) { + this.debug('already connected, disconnecting first'); + this.disconnect(); + } + this.set('shouldClose', false); this.debug('setting up websocket', systemId); @@ -40,6 +45,7 @@ export default Ember.Service.extend({ this.debug('disconnect'); this.set('shouldClose', true); this.get('socket').close(); + this.set('socket', null); // just in case it disconnected right before disconnect() was called. clearTimeout(this.get('reconnectionTimeout')); diff --git a/app/services/session.js b/app/services/session.js index 03aee72c2091526ae661f76e0ae486b17f53ff8d..6247afa114d4a27cbe018eaa06b41f068ac18edf 100644 --- a/app/services/session.js +++ b/app/services/session.js @@ -8,10 +8,10 @@ import Ember from 'ember'; * @public */ export default Ember.Service.extend({ - /** systemId store the id of the system which is currently used for + /** system stores the system which is currently used for * loading metamodel components - * @type {String} - * @property systemId + * @type {System} + * @property system */ - systemId: null + system: null }); diff --git a/package.json b/package.json index 26a5e728f0844242610caf1dea160f699c2cd8e1..246c296e5effb128f735c6c72286cc83cf14e123 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "devDependencies": { "broccoli-asset-rev": "^2.2.0", "ember-ajax": "0.7.1", + "ember-api-actions": "0.1.6", "ember-browserify": "^1.1.8", "ember-cli": "2.4.2", "ember-cli-app-version": "^1.0.0",