Refactored topology to fit diagnostic
diff --git a/views/ngXosViews/diagnostic/src/js/config.js b/views/ngXosViews/diagnostic/src/js/config.js
new file mode 100644
index 0000000..91ea0c2
--- /dev/null
+++ b/views/ngXosViews/diagnostic/src/js/config.js
@@ -0,0 +1,21 @@
+(function () {
+  'use strict';
+
+  angular.module('xos.serviceTopology')
+  .constant('serviceTopologyConfig', {
+    widthMargin: 20,
+    heightMargin: 30,
+    duration: 750,
+    circle: {
+      radius: 10,
+      selectedRadius: 15
+    },
+    square: {
+      width: 20,
+      height: 20,
+      x: -10,
+      y: -10
+    }
+  })
+
+}());
\ No newline at end of file
diff --git a/views/ngXosViews/diagnostic/src/js/d3.js b/views/ngXosViews/diagnostic/src/js/d3.js
new file mode 100644
index 0000000..027f08d
--- /dev/null
+++ b/views/ngXosViews/diagnostic/src/js/d3.js
@@ -0,0 +1,261 @@
+(function () {
+  'use strict';
+
+  angular.module('xos.serviceTopology')
+    .factory('d3', function($window){
+      return $window.d3;
+    })
+  .service('TreeLayout', function($window, $log, lodash, ServiceRelation, serviceTopologyConfig){
+
+    const drawLegend = (svg) => {
+      const legendContainer = svg.append('g')
+        .attr({
+          class: 'legend'
+        });
+
+      legendContainer.append('rect')
+      .attr({
+        transform: d => `translate(10, 80)`,
+        width: 100,
+        height: 100
+      });
+
+      // service
+      const service = legendContainer.append('g')
+      .attr({
+        class: 'node service'
+      });
+
+      service.append('circle')
+      .attr({
+        r: serviceTopologyConfig.circle.radius,
+        transform: d => `translate(30, 100)`
+      });
+
+      service.append('text')
+      .attr({
+        transform: d => `translate(45, 100)`,
+        dy: '.35em'
+      })
+      .text('Service')
+        .style('fill-opacity', 1);
+
+      // slice
+      const slice = legendContainer.append('g')
+        .attr({
+          class: 'node slice'
+        });
+
+      slice.append('rect')
+        .attr({
+          width: 20,
+          height: 20,
+          x: -10,
+          y: -10,
+          transform: d => `translate(30, 130)`
+        });
+
+      slice.append('text')
+        .attr({
+          transform: d => `translate(45, 130)`,
+          dy: '.35em'
+        })
+        .text('Slices')
+        .style('fill-opacity', 1);
+
+      // instance
+      const instance = legendContainer.append('g')
+        .attr({
+          class: 'node instance'
+        });
+
+      instance.append('rect')
+        .attr({
+          width: 20,
+          height: 20,
+          x: -10,
+          y: -10,
+          transform: d => `translate(30, 160)`
+        });
+
+      instance.append('text')
+        .attr({
+          transform: d => `translate(45, 160)`,
+          dy: '.35em'
+        })
+        .text('Instances')
+        .style('fill-opacity', 1);
+    };
+
+    var _svg, _layout, _source;
+
+    var i = 0;
+
+    // given a canvas, a layout and a data source, draw a tree layout
+    const updateTree = (svg, layout, source) => {
+
+      //cache data
+      _svg = svg;
+      _layout = layout;
+      _source = source;
+
+      const maxDepth = ServiceRelation.depthOf(source);
+
+      const diagonal = d3.svg.diagonal()
+        .projection(d => [d.y, d.x]);
+
+      // Compute the new tree layout.
+      var nodes = layout.nodes(source).reverse(),
+        links = layout.links(nodes);
+
+      // Normalize for fixed-depth.
+      nodes.forEach(function(d) {
+        // position the child node horizontally
+        const step = (($window.innerWidth - (serviceTopologyConfig.widthMargin * 2)) / maxDepth);
+        d.y = d.depth * step;
+      });
+
+      // Update the nodes…
+      var node = svg.selectAll('g.node')
+        .data(nodes, function(d) { return d.id || (d.id = ++i); });
+
+      // Enter any new nodes at the parent's previous position.
+      var nodeEnter = node.enter().append('g')
+        .attr({
+          class: d => `node ${d.type}`,
+          transform: `translate(${source.y0}, ${source.x0})`
+        });
+
+      const subscriberNodes = nodeEnter.filter('.subscriber');
+      const internetNodes = nodeEnter.filter('.internet');
+      const serviceNodes = nodeEnter.filter('.service');
+      const instanceNodes = nodeEnter.filter('.instance');
+      const sliceNodes = nodeEnter.filter('.slice');
+
+      subscriberNodes.append('rect')
+        .attr(serviceTopologyConfig.square);
+
+      internetNodes.append('rect')
+        .attr(serviceTopologyConfig.square);
+
+      serviceNodes.append('circle')
+        .attr('r', 1e-6)
+        .style('fill', d => d._children ? 'lightsteelblue' : '#fff')
+        .on('click', serviceClick);
+
+      sliceNodes.append('rect')
+        .attr({
+          width: 20,
+          height: 20,
+          x: -10,
+          y: -10
+        });
+
+      instanceNodes.append('rect')
+        .attr({
+          width: 20,
+          height: 20,
+          x: -10,
+          y: -10,
+          class: d => d.active ?'' : 'active'
+        });
+
+      nodeEnter.append('text')
+        .attr({
+          x: d => d.children ? -serviceTopologyConfig.circle.selectedRadius -3 : serviceTopologyConfig.circle.selectedRadius + 3,
+          dy: '.35em',
+          transform: d => {
+            if (d.children && d.parent){
+              if(d.parent.x < d.x){
+                return 'rotate(-30)';
+              }
+              return 'rotate(30)';
+            }
+          },
+          'text-anchor': d => d.children ? 'end' : 'start'
+        })
+        .text(d => d.name)
+        .style('fill-opacity', 1e-6);
+
+      // Transition nodes to their new position.
+      var nodeUpdate = node.transition()
+        .duration(serviceTopologyConfig.duration)
+        .attr({
+          'transform': d => `translate(${d.y},${d.x})`
+        });
+
+      nodeUpdate.select('circle')
+        .attr('r', d => d.selected ? serviceTopologyConfig.circle.selectedRadius : serviceTopologyConfig.circle.radius)
+        .style('fill', d => d.selected ? 'lightsteelblue' : '#fff');
+
+      nodeUpdate.select('text')
+        .style('fill-opacity', 1);
+
+      // Transition exiting nodes to the parent's new position.
+      var nodeExit = node.exit().transition()
+        .duration(serviceTopologyConfig.duration)
+        .remove();
+
+      nodeExit.select('circle')
+        .attr('r', 1e-6);
+
+      nodeExit.select('text')
+        .style('fill-opacity', 1e-6);
+
+      // Update the links…
+      var link = svg.selectAll('path.link')
+        .data(links, function(d) { return d.target.id; });
+
+      // Enter any new links at the parent's previous position.
+      link.enter().insert('path', 'g')
+        .attr('class', d => `link ${d.target.type} ${d.target.active ? '' : 'active'}`)
+        .attr('d', function(d) {
+          var o = {x: source.x0, y: source.y0};
+          return diagonal({source: o, target: o});
+        });
+
+      // Transition links to their new position.
+      link.transition()
+        .duration(serviceTopologyConfig.duration)
+        .attr('d', diagonal);
+
+      // Transition exiting nodes to the parent's new position.
+      link.exit().transition()
+        .duration(serviceTopologyConfig.duration)
+        .attr('d', function(d) {
+          var o = {x: source.x, y: source.y};
+          return diagonal({source: o, target: o});
+        })
+        .remove();
+
+      // Stash the old positions for transition.
+      nodes.forEach(function(d) {
+        d.x0 = d.x;
+        d.y0 = d.y;
+      });
+    };
+
+    const serviceClick = function(d) {
+
+      $log.info('TODO emit an event to highlight VMs');
+
+      if(!d.service){
+        return;
+      }
+
+      // toggling selected status
+      d.selected = !d.selected;
+
+      var selectedNode = d3.select(this);
+
+      selectedNode
+        .transition()
+        .duration(serviceTopologyConfig.duration)
+        .attr('r', serviceTopologyConfig.circle.selectedRadius);
+    };
+
+    this.updateTree = updateTree;
+    this.drawLegend = drawLegend;
+  });
+
+}());
\ No newline at end of file
diff --git a/views/ngXosViews/diagnostic/src/js/diagnostic.js b/views/ngXosViews/diagnostic/src/js/diagnostic.js
new file mode 100644
index 0000000..7f433f6
--- /dev/null
+++ b/views/ngXosViews/diagnostic/src/js/diagnostic.js
@@ -0,0 +1,23 @@
+(function () {
+  'use strict';
+
+  angular.module('xos.serviceTopology')
+  .directive('diagnostic', function(){
+    return {
+      restrict: 'E',
+      templateUrl: 'templates/diagnostic.tpl.html',
+      controllerAs: 'vm',
+      controller: function(Subscribers, ServiceRelation){
+        Subscribers.query().$promise
+        .then((subscribers) => {
+          this.subscribers = subscribers;
+          this.selectedSubscriber = subscribers[0];
+          return ServiceRelation.get(this.selectedSubscriber);
+        })
+        .then((serviceChain) => {
+          this.serviceChain = serviceChain;
+        });
+      }
+    }
+  });
+})();
\ No newline at end of file
diff --git a/views/ngXosViews/diagnostic/src/js/main.js b/views/ngXosViews/diagnostic/src/js/main.js
new file mode 100644
index 0000000..a6e79c5
--- /dev/null
+++ b/views/ngXosViews/diagnostic/src/js/main.js
@@ -0,0 +1,20 @@
+'use strict';
+
+angular.module('xos.serviceTopology', [
+  'ngResource',
+  'ngCookies',
+  'ngLodash',
+  'ngAnimate',
+  'ui.router',
+  'xos.helpers'
+])
+.config(($stateProvider) => {
+  $stateProvider
+  .state('home', {
+    url: '/',
+    template: '<diagnostic></diagnostic>'
+  });
+})
+.config(function($httpProvider){
+  $httpProvider.interceptors.push('NoHyperlinks');
+});
diff --git a/views/ngXosViews/diagnostic/src/js/serviceTopology.js b/views/ngXosViews/diagnostic/src/js/serviceTopology.js
new file mode 100644
index 0000000..d30122c
--- /dev/null
+++ b/views/ngXosViews/diagnostic/src/js/serviceTopology.js
@@ -0,0 +1,64 @@
+(function () {
+  'use strict';
+
+  angular.module('xos.serviceTopology')
+  .directive('serviceTopology', function(){
+    return {
+      restrict: 'E',
+      scope: {
+        serviceChain: '='
+      },
+      bindToController: true,
+      controllerAs: 'vm',
+      template: '',
+      controller: function($element, $window, $scope, d3, serviceTopologyConfig, ServiceRelation, Slice, Instances, Subscribers, TreeLayout){
+
+        const el = $element[0];
+
+        this.instances = [];
+        this.slices = [];
+
+        const width = el.clientWidth - (serviceTopologyConfig.widthMargin * 2);
+        const height = el.clientHeight - (serviceTopologyConfig.heightMargin * 2);
+
+        const treeLayout = d3.layout.tree()
+          .size([height, width]);
+
+        const svg = d3.select($element[0])
+          .append('svg')
+          .style('width', `${el.clientWidth}px`)
+          .style('height', `${el.clientHeight}px`)
+
+        const treeContainer = svg.append('g')
+          .attr('transform', `translate(${serviceTopologyConfig.widthMargin * 4},${serviceTopologyConfig.heightMargin})`);
+
+        var root;
+
+        const draw = (tree) => {
+          root = tree;
+          root.x0 = height / 2;
+          root.y0 = width / 2;
+
+          TreeLayout.updateTree(treeContainer, treeLayout, root);
+        };
+
+        TreeLayout.drawLegend(svg);
+
+        this.getInstances = (slice) => {
+          Instances.query({slice: slice.id}).$promise
+          .then((instances) => {
+            this.selectedSlice = slice;
+            this.instances = instances;
+          })
+        };
+        
+        $scope.$watch(() => this.serviceChain, (chain) => {
+          if(chain){
+            draw(chain);
+          }
+        });
+      }
+    }
+  });
+
+}());
\ No newline at end of file
diff --git a/views/ngXosViews/diagnostic/src/js/services.js b/views/ngXosViews/diagnostic/src/js/services.js
new file mode 100644
index 0000000..60a20d2
--- /dev/null
+++ b/views/ngXosViews/diagnostic/src/js/services.js
@@ -0,0 +1,160 @@
+(function () {
+  'use strict';
+
+  angular.module('xos.serviceTopology')
+  .service('Services', function($resource){
+    return $resource('/xos/services/:id', {id: '@id'});
+  })
+  .service('Tenant', function($resource){
+    return $resource('/xos/tenants');
+  })
+  .service('Slice', function($resource){
+    return $resource('/xos/slices', {id: '@id'});
+  })
+  .service('Instances', function($resource){
+    return $resource('/xos/instances', {id: '@id'});
+  })
+  .service('Subscribers', function($resource){
+    return $resource('/xos/subscribers', {id: '@id'});
+  })
+  .service('ServiceRelation', function($q, lodash, Services, Tenant, Slice, Instances){
+
+    // count the mas depth of an object
+    const depthOf = (obj) => {
+      var depth = 0;
+      if (obj.children) {
+        obj.children.forEach(function (d) {
+          var tmpDepth = depthOf(d);
+          if (tmpDepth > depth) {
+            depth = tmpDepth
+          }
+        })
+      }
+      return 1 + depth
+    };
+
+    // find all the relation defined for a given root
+    const findLevelRelation = (tenants, rootId) => {
+      return lodash.filter(tenants, service => {
+        return service.subscriber_service === rootId;
+      });
+    };
+
+    const findSpecificInformation = (tenants, rootId) => {
+      var tenants = lodash.filter(tenants, service => {
+        return service.provider_service === rootId && service.subscriber_tenant;
+      });
+
+      var info;
+
+      tenants.forEach((tenant) => {
+        if(tenant.service_specific_attribute){
+          info = JSON.parse(tenant.service_specific_attribute);
+        }
+      });
+
+      return info;
+    };
+
+    // find all the service defined by a given array of relations
+    const findLevelServices = (relations, services) => {
+      const levelServices = [];
+      lodash.forEach(relations, (tenant) => {
+        var service = lodash.find(services, {id: tenant.provider_service});
+        levelServices.push(service);
+      });
+      return levelServices;
+    };
+
+    const buildLevel = (tenants, services, rootService, parentName = null) => {
+
+      // build an array of unlinked services
+      // these are the services that should still placed in the tree
+      var unlinkedServices = lodash.difference(services, [rootService]);
+
+      // find all relations relative to this rootElement
+      const levelRelation = findLevelRelation(tenants, rootService.id);
+
+      // find all items related to rootElement
+      const levelServices = findLevelServices(levelRelation, services);
+
+      // remove this item from the list (performance
+      unlinkedServices = lodash.difference(unlinkedServices, levelServices);
+
+      rootService.service_specific_attribute = findSpecificInformation(tenants, rootService.id);
+
+      const tree = {
+        name: rootService.humanReadableName,
+        parent: parentName,
+        type: 'service',
+        service: rootService,
+        children: []
+      };
+
+      lodash.forEach(levelServices, (service) => {
+        tree.children.push(buildLevel(tenants, unlinkedServices, service, rootService.humanReadableName));
+      });
+
+      // if it is the last element append internet
+      if(tree.children.length === 0){
+        tree.children.push({
+          name: 'Internet',
+          type: 'internet',
+          children: []
+        });
+      }
+
+      return tree;
+    };
+
+    const buildServiceTree = (services, tenants, subscriber = {id: 1, name: 'fakeSubs'}) => {
+
+      // find the root service
+      // it is the one attached to subsriber_root
+      // as now we have only one root so this can work
+      const rootServiceId = lodash.find(tenants, {subscriber_root: subscriber.id}).provider_service;
+      const rootService = lodash.find(services, {id: rootServiceId});
+
+      const serviceTree = buildLevel(tenants, services, rootService);
+
+      return {
+        name: subscriber.name,
+        parent: null,
+        type: 'subscriber',
+        children: [serviceTree]
+      };
+
+    };
+
+    const get = (subscriber) => {
+      var deferred = $q.defer();
+      var services, tenants;
+      Services.query().$promise
+      .then((res) => {
+        services = res;
+        return Tenant.query().$promise;
+      })
+      .then((res) => {
+        tenants = res;
+        deferred.resolve(buildServiceTree(services, tenants, subscriber));
+      })
+      .catch((e) => {
+        throw new Error(e);
+      });
+
+      return deferred.promise;
+    };
+
+    // export APIs
+    return {
+      get: get,
+      buildLevel: buildLevel,
+      buildServiceTree: buildServiceTree,
+      findLevelRelation: findLevelRelation,
+      findLevelServices: findLevelServices,
+      depthOf: depthOf,
+      findSpecificInformation: findSpecificInformation
+    }
+  });
+
+}());
\ No newline at end of file