Skip to content
Snippets Groups Projects
Commit 0dc78853 authored by Christoph Dornieden's avatar Christoph Dornieden
Browse files

Added architecture view

parent 8c89ebd9
No related branches found
No related tags found
No related merge requests found
import Ember from 'ember';
const observables = [
'nodes',
'nodeGroups',
'serviceInstances',
'services',
'communicationInstances',
'communications'
];
// we require the use of controllers solely because Ember does not allow routes to pass more than model() to templates
export default Ember.Controller.extend({
changelogQueue: Ember.inject.service(),
init() {
this.debug('initializing controller');
},
transform(){
throw new Error('Implement me!');
//abstract function
//to be implemented
},
// gets automatically updated when any of the instances changes, changes are notified via a pseudo property 'updated'
// using @each will also listen if the array itself changes. Can be quite expensive.
graphModel: Ember.computed(`model.instances.{${observables.join(',')}}.@each._updated`, function() {
const systemId = this.get('model.systemId');
const instances = this.get('model.instances');
/*
* Since we use findAll to not load ALL instances but only for a specific system,
* Ember would cache any instances by any system in the store,
* this might lead to unexpected behavior after navigating to multiple systems/deployments.
* Solution: filter out invalid
*/
const filteredInstances = {};
Object.keys(instances).map((key) => {
filteredInstances[key] = instances[key].filterBy('systemId', systemId);
});
this.debug('creating graph', filteredInstances);
return this.transform(filteredInstances);
}),
actions: {
applyQueueUpdates() {
this.get('changelogQueue').apply();
}
}
});
import VisualisationController from '../_abstract-visualisation-controller';
import Ember from 'ember';
// we require the use of controllers solely because Ember does not allow routes to pass more than model() to templates
export default VisualisationController.extend({
architectureGraphingService: Ember.inject.service(),
transform(filteredInstances){
return this.get('architectureGraphingService').createGraph(filteredInstances); // TODO: update instead of complete recalculation?
}
});
import VisualisationController from '../_abstract-visualisation-controller';
import Ember from 'ember';
// we require the use of controllers solely because Ember does not allow routes to pass more than model() to templates
export default VisualisationController.extend({
deploymentGraphingService: Ember.inject.service(),
transform(filteredInstances){
return this.get('deploymentGraphingService').createGraph(filteredInstances); // TODO: update instead of complete recalculation?
}
});
import Ember from 'ember';
export default Ember.Route.extend({
session: Ember.inject.service(), // loads services/session.js for systemId
model () {
this.set('session.systemId', null); // no system selected, remove from model requests
return this.store.findAll('system').then((systems) => {
this.debug(systems);
return systems;
});
}
});
import Ember from 'ember';
export default Ember.Route.extend({
session: Ember.inject.service(), // loads services/session.js
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
* update whenever new records are pushed into the store.
* The controller can observe this.
* Also note that since we changed the behavior of findAll() to use the systemId
* Ember will probably also update for other systems. These are filtered in the controller
*/
const load = (type) => this.store.findAll(type);
return Ember.RSVP.hash({
nodes: load('node'),
nodeGroups: load('nodegroup'),
services: load('service'),
serviceInstances: load('serviceinstance'),
communications: load('communication'),
communicationInstances: load('communicationinstance')
}).then((models) => {
this.debug('loaded models', models);
return {
systemId: systemId,
instances: models
};
});
},
actions: {
loadDetails(rawEntity) {
this.debug('loadDetails action', rawEntity);
const entityType = rawEntity.type.toLowerCase();
const entityId = rawEntity.id;
/* I would love to not generate the url first, but there seem to be unknown (to me) assumptions about
* passing object parameters to transitionTo which break with the current path variables.
* Otherwise this would use transitionTo('architectures.single.details', {...})
*/
const url = this.router.generate('architectures.single.details', {
systemId: this.get('session.systemId'),
entityType,
entityId
});
this.replaceWith(url);
},
backToSystem() {
this.replaceWith(this.get('routeName'));
},
willTransition(transition) {
this.debug('transition', transition.targetName, this.get('routeName'));
// do not disconnect if transitioning to a child route (details)
if (transition.targetName.indexOf(this.get('routeName')) !== 0) {
this.get('changelogStream').disconnect();
}
}
}
});
import Ember from 'ember';
export default Ember.Route.extend({
visualisationEvents: Ember.inject.service(),
/**
* the duration of the extending sidebar animation, which is configured as transition in _architecture.scss.
* Since we apparently cannot listen to css transitionEnd events, we have to manually wait the time.
* Since this is fragile (since animations might lag on slower machines), we add a bit of buffer time.
*
* @type {Number} in milliseconds
* @property animationDuration
*/
animationDuration: 600, // ms
model(params) {
return this.store.findRecord(params.entityType.toLowerCase(), params.entityId); // TODO: match entity.type with model names
},
activate() {
this.debug('route activated');
Ember.$('body').addClass('extendedSidebar');
this.notifyResize();
},
deactivate() {
this.debug('route deactivated');
Ember.$('body').removeClass('extendedSidebar');
this.notifyResize();
},
notifyResize() {
if(this.animationTimeout) { // in case a user navigated too fast
this.endAnimation();
}
this.get('visualisationEvents').trigger('resize:start');
this.animationTimeout = setTimeout(this.endAnimation.bind(this), this.animationDuration);
},
endAnimation() {
this.get('visualisationEvents').trigger('resize:end');
this.animationTimeout = null;
}
});
import Ember from 'ember';
/**
* parses a list of models and creates stores them
*/
export default Ember.Service.extend({
store: Ember.inject.service(),
createGraph(models) {
this.debug('loaded models', models);
// prepare models by serializing them
const prepared = {};
[ 'services',
'serviceInstances',
'communications'
].forEach((key) => {
const records = models[key];
prepared[key] = records.map(record => record.serialize());
});
// services not used in current view
const {services, serviceInstances, communications} = prepared;
var network = {
nodes: [],
edges: []
};
services.forEach(data => {
data.label = data.name;
network.nodes.push({
data: data
});
});
// serviceInstances.forEach(data => {
// data.label = data.name;
// data.parent = data.serviceId;
//
// network.nodes.push({
// data: data,
// classes: data.status || ''
// });
// });
communications.forEach(data => {
data.source = data.sourceId;
data.target = data.targetId;
data.label = data.technology;
const instanceList = this.get('store').peekAll('communicationinstance')
.filter((instance) => instance.get('communicationId') === data.id );
data.workload = instanceList.reduce((previous, instance) => previous + instance.get('workload'), 0);
this.debug('workload: ', data.workload);
network.edges.push({
data,
classes: data.status || ''
});
});
return network;
}
});
import Ember from 'ember';
/**
* parses a list of models and creates stores them
*/
export default Ember.Service.extend({
store: Ember.inject.service(),
createGraph(models) {
this.debug('loaded models', models);
// prepare models by serializing them
const prepared = {};
[
'serviceInstances',
'communicationInstances',
'nodeGroups',
'nodes'
].forEach((key) => {
const records = models[key];
prepared[key] = records.map(record => record.serialize());
});
// services not used in current view
const {serviceInstances, communicationInstances, nodeGroups, nodes} = prepared;
var network = {
nodes: [],
edges: []
};
nodeGroups.forEach(data => {
data.label = data.name;
network.nodes.push({
data: data
});
});
nodes.forEach(data => {
data.label = data.name;
data.parent = data.nodeGroupId;
network.nodes.push({
data: data,
classes: data.status || ''
});
});
serviceInstances.forEach(data => {
data.label = data.name;
data.parent = data.nodeId;
network.nodes.push({
data: data,
classes: data.status || ''
});
});
communicationInstances.forEach(data => {
data.source = data.sourceId;
data.target = data.targetId;
data.technology = this.get('store').peekRecord('communication', data.communicationId).get('technology');
data.label = data.technology;
network.edges.push({
data,
classes: data.status || ''
});
});
return network;
}
});
<div class="container">
<h3>Available Deployments</h3>
<ul>
{{#each model as |system| }}
<li>{{#link-to 'deployments.single' system.id}}{{system.name}}{{/link-to}}</li>
{{/each}}
</ul>
</div>
{{outlet}}
\ No newline at end of file
{{#architecture-viewer graph=graphModel}}
<button {{action 'applyQueueUpdates'}} type="button" class="btn {{if changelogQueue.empty 'btn-default' 'btn-warning'}}" disabled={{ changelogQueue.empty }} title="Updates are possible when the deployment changed and new information is ready to get visualized!">
<i class="glyphicon glyphicon-refresh"></i> Apply Updates {{#if changelogQueue.available}}({{ changelogQueue.size }}){{/if}}
</button>
{{!-- show entity details in sidebar if subroute (details) is used --}}
{{outlet}}
{{/architecture-viewer}}
\ No newline at end of file
{{entity-details entity=model}}
{{#if model.timeSeries}}
{{time-series timeSeries=model.timeSeries}}
{{else}}
No time series
{{/if}}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment