Merge pull request #205 from vincent99/service-consume-map

Refactor service consumer lookup, edit service link names
This commit is contained in:
Vincent Fiduccia 2015-06-11 15:03:18 -07:00
commit 64e09c190b
19 changed files with 183 additions and 106 deletions

View File

@ -347,26 +347,19 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
}, },
loadBalancerServiceChanged: function(change) { loadBalancerServiceChanged: function(change) {
var service = change.data.resource;
this._includeChanged('environment', 'services', 'environmentId', change.data.resource); this._includeChanged('environment', 'services', 'environmentId', change.data.resource);
service.importLink('consumedservices'); },
dnsServiceChanged: function(change) {
this._includeChanged('environment', 'services', 'environmentId', change.data.resource);
},
externalServiceChanged: function(change) {
this._includeChanged('environment', 'services', 'environmentId', change.data.resource);
}, },
serviceChanged: function(change) { serviceChanged: function(change) {
var service = change.data.resource;
this._includeChanged('environment', 'services', 'environmentId', change.data.resource); this._includeChanged('environment', 'services', 'environmentId', change.data.resource);
// Remove the service from depenedent services
if ( ['removed','purged','purging'].indexOf(service.get('state')) >= 0 )
{
['service','loadBalancerService','dnsService','externalService'].forEach((type) => {
this.get('store').all(type).forEach((otherService) => {
(otherService.get('consumedservices')||[]).removeObject(service);
});
});
}
service.importLink('consumedservices');
}, },
}, },

View File

@ -13,11 +13,22 @@
</div> </div>
</div> </div>
{{#if (or model.consumedservices.length model.externalIpAddresses.length)}} {{#if (or model.consumedServicesWithNames.length model.externalIpAddresses.length)}}
<div class="pod-info clearfix"> <div class="pod-info clearfix">
<div class="pod-info-line"> <div class="pod-info-line">
{{#each item in model.consumedservices itemController="service"}} {{#each map in model.consumedServicesWithNames}}
<div class="pod-info-item">{{#link-to "service" item.environmentId item.id}}<i class="ss-layergroup"></i> {{item.displayName}}{{/link-to}}</div> {{#with map.service as service controller="service"}}
<div class="pod-info-item">
{{#link-to "service" service.environmentId service.id}}
<i {{bind-attr class="service.activeIcon"}}></i>
{{#if (eq map.name service.displayName)}}
{{map.name}}
{{else}}
{{service.displayName}}{{#if map.name}} <span class="text-muted">(as {{map.name}})</span>{{/if}}
{{/if}}
{{/link-to}}
</div>
{{/with}}
{{/each}} {{/each}}
</div> </div>
{{#if model.externalIpAddresses.length}} {{#if model.externalIpAddresses.length}}

View File

@ -1,35 +1,11 @@
import Cattle from 'ui/utils/cattle'; import Service from 'ui/service/model';
var DnsService = Cattle.TransitioningResource.extend({ var DnsService = Service.extend({
type: 'service', type: 'dnsService',
consumedServicesUpdated: 0,
onConsumedServicesChanged: function() {
this.incrementProperty('consumedServicesUpdated');
}.observes('consumedservices.@each.{id,name,state}'),
healthState: function() { healthState: function() {
return 'healthy'; return 'healthy';
}.property(), }.property(),
combinedState: function() {
var service = this.get('state');
var health = this.get('healthState');
if ( ['active','updating-active'].indexOf(service) === -1 )
{
// If the service isn't active, return its state
return service;
}
if ( health === 'healthy' )
{
return service;
}
else
{
return 'degraded';
}
}.property('state', 'healthState'),
}); });
DnsService.reopenClass({ DnsService.reopenClass({

View File

@ -1,6 +1,7 @@
import Ember from 'ember'; import Ember from 'ember';
import Util from 'ui/utils/util'; import Util from 'ui/utils/util';
import ThrottledResize from 'ui/mixins/throttled-resize'; import ThrottledResize from 'ui/mixins/throttled-resize';
import { activeIcon } from 'ui/service/controller';
export default Ember.View.extend(ThrottledResize,{ export default Ember.View.extend(ThrottledResize,{
classNames: ['environment-graph'], classNames: ['environment-graph'],
@ -72,7 +73,7 @@ export default Ember.View.extend(ThrottledResize,{
var color = (service.get('state') === 'active' ? 'green' : (service.get('state') === 'inactive' ? 'red' : 'yellow')); var color = (service.get('state') === 'active' ? 'green' : (service.get('state') === 'inactive' ? 'red' : 'yellow'));
var instances = service.get('instances.length')||'No'; var instances = service.get('instances.length')||'No';
var html = '<i class="icon ss-layergroup"></i>' + var html = '<i class="icon '+ activeIcon(service) +'"></i>' +
'<h4 class="clip">'+ Util.escapeHtml(service.get('name')) + '</h4>' + '<h4 class="clip">'+ Util.escapeHtml(service.get('name')) + '</h4>' +
'<h6 class="count"><b>' + instances + '</b> container' + (instances === 1 ? '' : 's') + '</h6>' + '<h6 class="count"><b>' + instances + '</b> container' + (instances === 1 ? '' : 's') + '</h6>' +
'<h6><span class="state '+ color +'">' + Util.escapeHtml(Util.ucFirst(service.get('state'))) + '</span></h6>'; '<h6><span class="state '+ color +'">' + Util.escapeHtml(Util.ucFirst(service.get('state'))) + '</span></h6>';
@ -89,15 +90,24 @@ export default Ember.View.extend(ThrottledResize,{
unremovedServices.forEach(function(service) { unremovedServices.forEach(function(service) {
var serviceId = service.get('id'); var serviceId = service.get('id');
(service.get('consumedservices')||[]).map(function(target) { (service.get('consumedServicesWithNames')||[]).map(function(map) {
var target = map.get('service');
var targetId = target.get('id'); var targetId = target.get('id');
var color = (target.get('state') === 'active' ? 'green' : (target.get('state') === 'inactive' ? 'red' : 'yellow')); var color = (target.get('state') === 'active' ? 'green' : (target.get('state') === 'inactive' ? 'red' : 'yellow'));
g.setEdge(serviceId, targetId, { var edgeOpts = {
arrowhead: 'vee', arrowhead: 'vee',
lineInterpolate: 'bundle', lineInterpolate: 'bundle',
class: color, class: color,
}); };
var mapName = map.get('name');
if ( mapName && mapName !== target.get('name') )
{
edgeOpts.label = mapName;
}
g.setEdge(serviceId, targetId, edgeOpts);
var existing = unexpectedEdges.filter(function(edge) { var existing = unexpectedEdges.filter(function(edge) {
return edge.v === serviceId && edge.w === targetId; return edge.v === serviceId && edge.w === targetId;

View File

@ -8,9 +8,10 @@ export default Ember.Route.extend({
filter: { filter: {
environmentId: env.get('id'), environmentId: env.get('id'),
}, },
include: ['consumedservices','instances'] include: ['instances']
}).then((services) => { }).then((services) => {
env.set('services', services||[]); env.set('services', services||[]);
env.set('services.sortProperties', ['name','id']);
return env; return env;
}); });
}); });

View File

@ -3,14 +3,21 @@ import Ember from 'ember';
export default Ember.Route.extend({ export default Ember.Route.extend({
model: function() { model: function() {
var store = this.get('store'); var store = this.get('store');
return store.findAllUnremoved('environment').then((environments) => {
var promises = [
store.findAllUnremoved('environment'),
];
return Ember.RSVP.all(promises).then((results) => {
var environments = results[0];
var promises = []; var promises = [];
environments.forEach((env) => { environments.forEach((env) => {
var promise = store.find('service', null, { var promise = store.find('service', null, {
filter: { filter: {
environmentId: env.get('id'), environmentId: env.get('id'),
}, },
include: ['consumedservices','instances'] include: ['instances']
}).then((services) => { }).then((services) => {
env.set('services', services||[]); env.set('services', services||[]);
env.set('services.sortProperties', ['name','id']); env.set('services.sortProperties', ['name','id']);

View File

@ -1,35 +1,11 @@
import Cattle from 'ui/utils/cattle'; import Service from 'ui/service/model';
var ExternalService = Cattle.TransitioningResource.extend({ var ExternalService = Service.extend({
type: 'service', type: 'externalService',
consumedServicesUpdated: 0,
onConsumedServicesChanged: function() {
this.incrementProperty('consumedServicesUpdated');
}.observes('consumedservices.@each.{id,name,state}'),
healthState: function() { healthState: function() {
return 'healthy'; return 'healthy';
}.property(), }.property(),
combinedState: function() {
var service = this.get('state');
var health = this.get('healthState');
if ( ['active','updating-active'].indexOf(service) === -1 )
{
// If the service isn't active, return its state
return service;
}
if ( health === 'healthy' )
{
return service;
}
else
{
return 'degraded';
}
}.property('state', 'healthState'),
}); });
ExternalService.reopenClass({ ExternalService.reopenClass({

View File

@ -50,26 +50,27 @@ export default Ember.Mixin.create(EditLabels, {
serviceLinksAsMap: null, serviceLinksAsMap: null,
initServiceLinks: function() { initServiceLinks: function() {
var out = []; var out = [];
var links = this.get('service.consumedservices')||[]; var links;
if ( this.get('service.id') )
links.forEach(function(value) {
// Objects, from edit
var id;
if ( typeof value === 'object' )
{ {
id = Ember.get(value,'id'); // Edit
if ( id ) links = this.get('service.consumedServicesWithNames')||[];
{
out.push(Ember.Object.create({
obj: value,
serviceId: id,
}));
}
} }
else else
{ {
out.push(Ember.Object.create({serviceId: value})); // New / Clone
links = this.get('service.serviceLinks')||[];
} }
links.forEach(function(obj) {
var linkName = obj.get('name');
var service = obj.get('service');
out.push(Ember.Object.create({
linkName: (linkName === service.get('name') ? '' : linkName),
obj: service,
serviceId: service.get('id'),
}));
}); });
this.set('serviceLinksArray', out); this.set('serviceLinksArray', out);

View File

@ -114,9 +114,13 @@ var ServiceController = Cattle.TransitioningResourceController.extend(ReadLabels
}.property('type'), }.property('type'),
state: Ember.computed.alias('model.combinedState'), state: Ember.computed.alias('model.combinedState'),
activeIcon: function() {
return activeIcon(this.get('model'));
}.property('type'),
}); });
function activeIcon(service) export function activeIcon(service)
{ {
var out = 'ss-layergroup'; var out = 'ss-layergroup';
switch ( service.get('type').toLowerCase() ) switch ( service.get('type').toLowerCase() )

View File

@ -1,13 +1,85 @@
import Ember from 'ember';
import Cattle from 'ui/utils/cattle'; import Cattle from 'ui/utils/cattle';
import C from 'ui/utils/constants'; import C from 'ui/utils/constants';
var _allMaps;
var _allServices;
var _allLbServices;
var _allExternalServices;
var _allDnsServices;
var Service = Cattle.TransitioningResource.extend({ var Service = Cattle.TransitioningResource.extend({
type: 'service', type: 'service',
_allMaps: null,
consumedServicesUpdated: 0, consumedServicesUpdated: 0,
serviceLinks: null, // Used for clone
reservedKeys: ['_allMaps','consumedServicesUpdated','serviceLinks'],
init: function() {
this._super();
// Hack: keep only one copy of all the services and serviceconsumemaps
// But you have to load service and serviceconsumemap beforehand somewhere...
// Bonus hack: all('services') doesn't include the other kinds of services, so load all those too.
if ( !_allMaps )
{
_allMaps = this.get('store').allUnremoved('serviceconsumemap');
}
this.set('_allMaps', _allMaps);
if ( !_allServices )
{
_allServices = this.get('store').allUnremoved('service');
}
if ( !_allLbServices )
{
_allLbServices = this.get('store').allUnremoved('loadbalancerservice');
}
if ( !_allExternalServices )
{
_allExternalServices = this.get('store').allUnremoved('externalservice');
}
if ( !_allDnsServices )
{
_allDnsServices = this.get('store').allUnremoved('dnsservice');
}
},
consumedServicesWithNames: function() {
var all = [_allServices, _allLbServices, _allExternalServices, _allDnsServices];
return this.get('_allMaps').filterProperty('serviceId', this.get('id')).map((map) => {
var i = 0;
var service = null;
while ( i < all.length && !service )
{
service = all[i].filterProperty('id', map.get('consumedServiceId'))[0];
i++;
}
return Ember.Object.create({
name: map.get('name'),
service: service
});
}).filter((obj) => {
return obj.get('service.id');
});
}.property('id','_allMaps.@each.{name,serviceId,consumedServiceId}'),
consumedServices: function() {
return this.get('consumedServicesWithNames').map((obj) => {
return obj.get('service');
});
}.property('consumedServicesWithNames.@each.service'),
onConsumedServicesChanged: function() { onConsumedServicesChanged: function() {
this.incrementProperty('consumedServicesUpdated'); this.incrementProperty('consumedServicesUpdated');
}.observes('consumedservices.@each.{id,name,state}'), }.observes('consumedServicesWithNames.@each.{name,service}'),
healthState: function() { healthState: function() {
var isGlobal = Object.keys(this.get('labels')||{}).indexOf(C.LABEL.SCHED_GLOBAL) >= 0; var isGlobal = Object.keys(this.get('labels')||{}).indexOf(C.LABEL.SCHED_GLOBAL) >= 0;

View File

@ -34,7 +34,7 @@ export default Ember.ObjectController.extend(Cattle.NewOrEditMixin, {
targetsArray: null, targetsArray: null,
initTargets: function() { initTargets: function() {
var existing = this.get('dns.consumedservices'); var existing = this.get('dns.consumedServices');
var out = []; var out = [];
if ( existing ) if ( existing )
{ {

View File

@ -13,7 +13,7 @@ export default Ember.Route.extend({
if ( params.serviceId ) if ( params.serviceId )
{ {
dependencies.pushObject(store.find('service', params.serviceId, {include: ['consumedservices']})); dependencies.pushObject(store.find('service', params.serviceId));
} }
return Ember.RSVP.all(dependencies, 'Load dependencies').then(function(results) { return Ember.RSVP.all(dependencies, 'Load dependencies').then(function(results) {

View File

@ -45,7 +45,7 @@ export default Ember.ObjectController.extend(Cattle.NewOrEditMixin, EditLoadBala
targetsArray: null, targetsArray: null,
initTargets: function() { initTargets: function() {
var existing = this.get('balancer.consumedservices'); var existing = this.get('balancer.consumedServices');
var out = []; var out = [];
if ( existing ) if ( existing )
{ {

View File

@ -13,7 +13,7 @@ export default Ember.Route.extend({
if ( params.serviceId ) if ( params.serviceId )
{ {
dependencies.pushObject(store.find('service', params.serviceId, {include: ['loadbalancerlisteners','consumedservices']})); dependencies.pushObject(store.find('service', params.serviceId, {include: ['loadbalancerlisteners']}));
} }
return Ember.RSVP.all(dependencies, 'Load dependencies').then(function(results) { return Ember.RSVP.all(dependencies, 'Load dependencies').then(function(results) {

View File

@ -30,6 +30,7 @@ export default Ember.Route.extend({
var allHosts = results[0]; var allHosts = results[0];
var environment = results[1]; var environment = results[1];
var serviceOrContainer = results[2]; var serviceOrContainer = results[2];
var serviceLinks = [];
var instanceData, serviceData, healthCheckData; var instanceData, serviceData, healthCheckData;
if ( serviceOrContainer ) if ( serviceOrContainer )
@ -37,6 +38,7 @@ export default Ember.Route.extend({
if ( serviceOrContainer.get('type') === 'service' ) if ( serviceOrContainer.get('type') === 'service' )
{ {
serviceData = serviceOrContainer.serializeForNew(); serviceData = serviceOrContainer.serializeForNew();
serviceLinks = serviceOrContainer.get('consumedServicesWithNames');
instanceData = serviceData.launchConfig; instanceData = serviceData.launchConfig;
delete serviceData.launchConfig; delete serviceData.launchConfig;
delete serviceData.instances; delete serviceData.instances;
@ -80,7 +82,10 @@ export default Ember.Route.extend({
} }
var instance = this.get('store').createRecord(instanceData); var instance = this.get('store').createRecord(instanceData);
var service = store.createRecord(serviceData); var service = store.createRecord(serviceData);
service.set('serviceLinks', serviceLinks);
var healthCheck = store.createRecord(healthCheckData); var healthCheck = store.createRecord(healthCheckData);
instance.set('healthCheck', healthCheck); instance.set('healthCheck', healthCheck);
service.set('launchConfig', instance); // Creating a service needs the isntance definition here service.set('launchConfig', instance); // Creating a service needs the isntance definition here

View File

@ -0,0 +1,10 @@
import Cattle from 'ui/utils/cattle';
var ServiceConsumeMap = Cattle.TransitioningResource.extend({
type: 'serviceConsumeMap',
});
ServiceConsumeMap.reopenClass({
});
export default ServiceConsumeMap;

12
app/services/route.js Normal file
View File

@ -0,0 +1,12 @@
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
var store = this.get('store');
return Ember.RSVP.all([
store.findAllUnremoved('environment'),
store.findAllUnremoved('service'),
store.findAllUnremoved('serviceconsumemap'),
]);
}
});

View File

@ -49,7 +49,6 @@ module.exports = function(environment) {
'&include=loadBalancerTargets' + '&include=loadBalancerTargets' +
'&include=loadBalancerListeners' + '&include=loadBalancerListeners' +
'&include=instanceLinks' + '&include=instanceLinks' +
'&include=consumedservices' +
'&include=ipAddresses', '&include=ipAddresses',
baseAssets: '', baseAssets: '',
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "ui", "name": "ui",
"version": "0.25.0", "version": "0.26.0",
"private": true, "private": true,
"directories": { "directories": {
"doc": "doc", "doc": "doc",