//Autogenerated, do not edit!!!
'use strict';

(function () {
  'use strict';

  angular.module('xos.diagnostic', ['ngResource', 'ngCookies', 'ngLodash', 'ngAnimate', 'ui.router', 'xos.helpers']).config(["$stateProvider", function ($stateProvider) {
    $stateProvider.state('home', {
      url: '/',
      template: '<diagnostic-container></diagnostic-container>'
    });
  }]).config(["$httpProvider", function ($httpProvider) {
    $httpProvider.interceptors.push('NoHyperlinks');
  }]).run(["$log", function ($log) {
    $log.info('Diagnostic Started');
  }]);
})();
angular.module("xos.diagnostic").run(["$templateCache", function($templateCache) {$templateCache.put("templates/diagnostic.tpl.html","<div class=\"container-fluid\">\n  <div ng-hide=\"vm.error && vm.loader\" style=\"height: 900px\">\n    <div class=\"onethird-height\">\n      <service-topology service-chain=\"vm.serviceChain\"></service-topology>\n    </div>\n    <div class=\"twothird-height\">\n      <logic-topology ng-if=\"vm.subscribers\" subscribers=\"vm.subscribers\" selected=\"vm.selectedSubscriber\"></logic-topology>\n    </div>\n  </div>\n  <div class=\"row\" ng-if=\"vm.error\">\n    <div class=\"col-xs-12\">\n      <div class=\"alert alert-danger\">\n        {{vm.error}}\n      </div>\n    </div>\n  </div>\n  <div class=\"row\" ng-if=\"vm.loader\">\n    <div class=\"col-xs-12\">\n      <div class=\"loader\">Loading</div>\n    </div>\n  </div>\n</div>");
$templateCache.put("templates/logicTopology.tpl.html","<select-subscriber-modal open=\"vm.openSelectSubscriberModal\" subscribers=\"vm.subscribers\"></select-subscriber-modal>\n<subscriber-status-modal open=\"vm.openSubscriberStatusModal\" subscriber=\"vm.currentSubscriber\"></subscriber-status-modal>\n<div class=\"instances-stats animate\" ng-hide=\"vm.hideInstanceStats\">\n  <div class=\"row\">\n    <div class=\"col-sm-3 col-sm-offset-8\">\n      <div class=\"panel panel-primary\" ng-repeat=\"instance in vm.selectedInstances\">\n        <div class=\"panel-heading\">\n          {{instance.humanReadableName}}\n        </div>\n          <ul class=\"list-group\">\n            <li class=\"list-group-item\">Backend Status: {{instance.backend_status}}</li>\n            <li class=\"list-group-item\">IP Address: {{instance.ip}}</li>\n          </ul>\n          <ul class=\"list-group\">\n            <li class=\"list-group-item\" ng-repeat=\"stat in instance.stats\">\n              <span class=\"badge\">{{stat.value}}</span>\n              {{stat.meter}}\n            </li>\n          </ul>\n        </div>\n      </div>  \n    </div>\n  </div>\n</div>");
$templateCache.put("templates/select-subscriber-modal.tpl.html","<div class=\"modal fade\" ng-class=\"{in: vm.open}\" tabindex=\"-1\" role=\"dialog\">\n  <div class=\"modal-dialog modal-sm\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <button ng-click=\"vm.close()\"  type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\"><span aria-hidden=\"true\">&times;</span></button>\n        <h4 class=\"modal-title\">Select a subscriber:</h4>\n      </div>\n      <div class=\"modal-body\">\n        <select class=\"form-control\" ng-options=\"s as s.humanReadableName for s in vm.subscribers\" ng-model=\"vm.selected\"></select>\n      </div>\n      <div class=\"modal-footer\">\n        <button ng-click=\"vm.close()\" type=\"button\" class=\"btn btn-default\" data-dismiss=\"modal\">Close</button>\n        <button ng-click=\"vm.select(vm.selected)\" type=\"button\" class=\"btn btn-primary\">Select</button>\n      </div>\n    </div><!-- /.modal-content -->\n  </div><!-- /.modal-dialog -->\n</div><!-- /.modal -->");
$templateCache.put("templates/subscriber-status-modal.tpl.html","<div class=\"modal fade\" ng-class=\"{in: vm.open}\" tabindex=\"-1\" role=\"dialog\">\n  <div class=\"modal-dialog modal-sm\">\n    <div class=\"modal-content\">\n      <div class=\"modal-header\">\n        <button ng-click=\"vm.close()\"  type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-label=\"Close\"><span aria-hidden=\"true\">&times;</span></button>\n        <h4 class=\"modal-title\">Manage subscriber:</h4>\n      </div>\n      <form name=\"vm.subscriber-detail\">\n        <div class=\"modal-body\">\n          <div class=\"row\">\n            <div class=\"col-xs-12\">\n              <label>Status</label>\n            </div>\n            <div class=\"col-xs-6\">\n              <a ng-click=\"vm.subscriber.status = \'enabled\'\"\n                class=\"btn btn-block\"\n                ng-class=\"{\'btn-primary\': vm.subscriber.status === \'enabled\' ,\'btn-default\': vm.subscriber.status !== \'enabled\'}\"\n                >Enabled</a>\n            </div>\n            <div class=\"col-xs-6\">\n              <a ng-click=\"vm.subscriber.status = \'suspended\'\"\n                class=\"btn btn-block\"\n                ng-class=\"{\'btn-primary\': vm.subscriber.status === \'suspended\' ,\'btn-default\': vm.subscriber.status !== \'suspended\'}\"\n                >Suspended</a>\n            </div>\n          </div>\n          <div class=\"row\">\n            <div class=\"col-xs-6\">\n              <a ng-click=\"vm.subscriber.status = \'delinquent\'\"\n                class=\"btn btn-block\"\n                ng-class=\"{\'btn-primary\': vm.subscriber.status === \'delinquent\' ,\'btn-default\': vm.subscriber.status !== \'delinquent\'}\"\n                >Delinquent <br> payment</a>\n            </div>\n            <div class=\"col-xs-6\">\n              <a ng-click=\"vm.subscriber.status = \'copyrightviolation\'\"\n                class=\"btn btn-block\"\n                ng-class=\"{\'btn-primary\': vm.subscriber.status === \'copyrightviolation\' ,\'btn-default\': vm.subscriber.status !== \'copyrightviolation\'}\"\n                >Copyright <br> violation</a>\n            </div>\n          </div>\n          <div class=\"row\">\n            <div class=\"col-xs-6\">\n              <label>Uplink Speed</label>\n              <input type=\"number\" class=\"form-control\" ng-model=\"vm.subscriber.uplink_speed\"/>\n            </div>\n            <div class=\"col-xs-6\">\n              <label>Downlink Speed</label>\n              <input type=\"number\" class=\"form-control\" ng-model=\"vm.subscriber.downlink_speed\"/>\n            </div>\n          </div>\n          <div class=\"row\">\n            <div class=\"col-xs-6\">\n              <label>Enable Uverse</label>\n            </div>\n            <div class=\"col-xs-6\">\n              <a \n                ng-click=\"vm.subscriber.enable_uverse = !vm.subscriber.enable_uverse\" \n                ng-class=\"{\'btn-success\': vm.subscriber.enable_uverse, \'btn-danger\': !vm.subscriber.enable_uverse}\"\n                class=\"btn btn-block\">\n                <span ng-show=\"vm.subscriber.enable_uverse === true\">Enabled</span>\n                <span ng-show=\"vm.subscriber.enable_uverse !== true\">Disabled</span>\n              </a>\n            </div>\n          </div>\n        </div>\n        <div class=\"modal-footer\" ng-show=\"vm.success || vm.formError\">\n          <div class=\"alert alert-success\" ng-show=\"vm.success\">\n            {{vm.success}}\n          </div>\n          <div class=\"alert alert-danger\" ng-show=\"vm.formError\">\n            {{vm.formError}}\n          </div>\n        </div>\n        <div class=\"modal-footer\">\n          <button ng-click=\"vm.close()\" type=\"button\" class=\"btn btn-default\" data-dismiss=\"modal\">Close</button>\n          <button ng-click=\"vm.updateSubscriber(vm.subscriber)\" type=\"button\" class=\"btn btn-primary\">Save</button>\n        </div>\n      </form>\n    </div><!-- /.modal-content -->\n  </div><!-- /.modal-dialog -->\n</div><!-- /.modal -->");}]);
'use strict';

(function () {
  'use strict';
  angular.module('xos.diagnostic').directive('selectSubscriberModal', function () {
    return {
      scope: {
        subscribers: '=',
        open: '='
      },
      bindToController: true,
      restrict: 'E',
      templateUrl: 'templates/select-subscriber-modal.tpl.html',
      controllerAs: 'vm',
      controller: ["$rootScope", function controller($rootScope) {
        var _this = this;

        this.close = function () {
          _this.open = false;
        };

        this.select = function (subscriber) {
          $rootScope.$emit('subscriber.selected', subscriber);
          _this.close();
        };
      }]
    };
  }).directive('subscriberStatusModal', function () {
    return {
      scope: {
        open: '=',
        subscriber: '='
      },
      bindToController: true,
      restrict: 'E',
      templateUrl: 'templates/subscriber-status-modal.tpl.html',
      controllerAs: 'vm',
      controller: ["$log", "$timeout", "$scope", "Subscribers", function controller($log, $timeout, $scope, Subscribers) {
        var _this2 = this;

        $scope.$watch(function () {
          return _this2.open;
        }, function () {
          _this2.success = null;
          _this2.formError = null;
        });

        this.close = function () {
          _this2.open = false;
        };

        this.updateSubscriber = function (subscriber) {

          Subscribers.update(subscriber).$promise.then(function () {
            _this2.success = 'Subscriber successfully updated!';
          })['catch'](function (e) {
            _this2.formError = e;
          })['finally'](function () {
            $timeout(function () {
              _this2.close();
            }, 1500);
          });
        };
      }]
    };
  });
})();
'use strict';

(function () {
  'use strict';

  angular.module('xos.diagnostic').service('ServiceTopologyHelper', ["$rootScope", "$window", "$log", "lodash", "ServiceRelation", "serviceTopologyConfig", "d3", function ($rootScope, $window, $log, lodash, ServiceRelation, serviceTopologyConfig, d3) {

    var _svg, _layout, _source, _el;

    var i = 0;

    // given a canvas, a layout and a data source, draw a tree layout
    var updateTree = function updateTree(svg, layout, source) {
      var el = arguments.length <= 3 || arguments[3] === undefined ? _el : arguments[3];

      if (el) {
        _el = el;
      }

      //cache data
      _svg = svg;
      _layout = layout;
      _source = source;

      var maxDepth = ServiceRelation.depthOf(source);

      var diagonal = d3.svg.diagonal().projection(function (d) {
        return [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
        var step = (_el.clientWidth - 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': function _class(d) {
          return 'node ' + d.type;
        },
        transform: function transform(d) {
          return d.x && d.y ? 'translate(' + d.y + ', ' + d.x + ')' : 'translate(' + source.y0 + ', ' + source.x0 + ')';
        }
      });

      var subscriberNodes = nodeEnter.filter('.subscriber');
      var internetNodes = nodeEnter.filter('.router');
      var serviceNodes = nodeEnter.filter('.service');

      subscriberNodes.append('rect').attr(serviceTopologyConfig.square)
      // add event listener to subscriber
      .on('click', function () {
        $rootScope.$emit('subscriber.modal.open');
      });

      internetNodes.append('rect').attr(serviceTopologyConfig.square);

      serviceNodes.append('circle').attr('r', 1e-6).style('fill', function (d) {
        return d._children ? 'lightsteelblue' : '#fff';
      }).on('click', serviceClick);

      nodeEnter.append('text').attr({
        x: function x(d) {
          return d.children ? -serviceTopologyConfig.circle.selectedRadius - 3 : serviceTopologyConfig.circle.selectedRadius + 3;
        },
        dy: '.35em',
        transform: function transform(d) {
          if (d.children && d.parent) {
            if (d.parent.x < d.x) {
              return 'rotate(-30)';
            }
            return 'rotate(30)';
          }
        },
        'text-anchor': function textAnchor(d) {
          return d.children ? 'end' : 'start';
        }
      }).text(function (d) {
        return d.name;
      }).style('fill-opacity', 1e-6);

      // Transition nodes to their new position.
      var nodeUpdate = node.transition().duration(serviceTopologyConfig.duration).attr({
        'transform': function transform(d) {
          return 'translate(' + d.y + ',' + d.x + ')';
        }
      });

      nodeUpdate.select('circle').attr('r', function (d) {
        return d.selected ? serviceTopologyConfig.circle.selectedRadius : serviceTopologyConfig.circle.radius;
      }).style('fill', function (d) {
        return 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', function (d) {
        return '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;
      });
    };

    var serviceClick = function serviceClick(d) {

      // if was selected
      if (d.selected) {
        d.selected = !d.selected;
        $rootScope.$emit('instance.detail.hide', {});
        return updateTree(_svg, _layout, _source);
      }

      $rootScope.$emit('instance.detail', { name: d.name, service: d.service, tenant: d.tenant });

      // unselect all
      _svg.selectAll('circle').each(function (d) {
        return d.selected = false;
      });

      // toggling selected status
      d.selected = !d.selected;

      updateTree(_svg, _layout, _source);
    };

    this.updateTree = updateTree;
  }]);
})();
'use strict';

(function () {
  'use strict';

  angular.module('xos.diagnostic').directive('serviceTopology', function () {
    return {
      restrict: 'E',
      scope: {
        serviceChain: '='
      },
      bindToController: true,
      controllerAs: 'vm',
      template: '',
      controller: ["$element", "$window", "$scope", "d3", "serviceTopologyConfig", "ServiceRelation", "Slice", "Instances", "Subscribers", "ServiceTopologyHelper", function controller($element, $window, $scope, d3, serviceTopologyConfig, ServiceRelation, Slice, Instances, Subscribers, ServiceTopologyHelper) {
        var _this = this;

        var el = $element[0];

        d3.select(window).on('resize', function () {
          draw(_this.serviceChain);
        });

        var root, svg;

        var draw = function draw(tree) {

          if (!tree) {
            console.error('Tree is missing');
            return;
          }

          // TODO update instead clear and redraw

          // clean
          d3.select($element[0]).select('svg').remove();

          var width = el.clientWidth - serviceTopologyConfig.widthMargin * 2;
          var height = el.clientHeight - serviceTopologyConfig.heightMargin * 2;

          var treeLayout = d3.layout.tree().size([height, width]);

          svg = d3.select($element[0]).append('svg').style('width', el.clientWidth + 'px').style('height', el.clientHeight + 'px');

          var treeContainer = svg.append('g').attr('transform', 'translate(' + serviceTopologyConfig.widthMargin * 4 + ',' + serviceTopologyConfig.heightMargin + ')');

          root = tree;
          root.x0 = height / 2;
          root.y0 = width / 2;

          // ServiceTopologyHelper.drawLegend(svg);
          ServiceTopologyHelper.updateTree(treeContainer, treeLayout, root, el);
        };

        $scope.$watch(function () {
          return _this.serviceChain;
        }, function (chain) {
          if (angular.isDefined(chain)) {
            draw(chain);
          }
        });
      }]
    };
  });
})();
'use strict';

(function () {
  'use strict';

  angular.module('xos.diagnostic').service('Services', ["$resource", function ($resource) {
    return $resource('/xos/services/:id', { id: '@id' });
  }]).service('Tenant', ["$resource", function ($resource) {
    return $resource('/xos/tenants', { id: '@id' }, {
      queryVsgInstances: {
        method: 'GET',
        isArray: true,
        interceptor: {
          response: function response(res) {

            // NOTE
            // Note that VCPETenant is now VSGTenant.

            var instances = [];

            angular.forEach(res.data, function (tenant) {
              var info = JSON.parse(tenant.service_specific_attribute);
              if (info && info.instance_id) {
                instances.push(info.instance_id);
              }
            });

            return instances;
          }
        }
      },
      getSubscriberTag: {
        method: 'GET',
        isArray: true,
        interceptor: {
          response: function response(res) {
            // NOTE we should receive only one vOLT tenant here
            return JSON.parse(res.data[0].service_specific_attribute);
          }
        }
      }
    });
  }]).service('Ceilometer', ["$http", "$q", "Instances", function ($http, $q, Instances) {
    var _this = this;

    /**
    * Get stats for a single instance
    */
    this.getInstanceStats = function (instanceUuid) {
      var deferred = $q.defer();

      $http.get('/xoslib/xos-instance-statistics', { params: { 'instance-uuid': instanceUuid } }).then(function (res) {
        deferred.resolve(res.data);
      })['catch'](function (e) {
        deferred.reject(e);
      });

      return deferred.promise;
    };

    /**
    * Collect stats for an array of instances
    */
    this.getInstancesStats = function (instances) {
      var deferred = $q.defer();
      var instancePromises = [];
      var instanceList = [];

      // retrieve instance details
      instances.forEach(function (instanceId) {
        instancePromises.push(Instances.get({ id: instanceId }).$promise);
      });

      // get all instance data
      $q.all(instancePromises).then(function (_instanceList) {
        instanceList = _instanceList;
        var promises = [];
        // foreach instance query stats
        instanceList.forEach(function (instance) {
          promises.push(_this.getInstanceStats(instance.instance_uuid));
        });
        return $q.all(promises);
      }).then(function (stats) {
        // augment instance with stats information
        instanceList.map(function (instance, i) {
          instance.stats = stats[i];
        });
        deferred.resolve(instanceList);
      })['catch'](deferred.reject);

      return deferred.promise;
    };

    this.getContainerStats = function (containerName) {
      var deferred = $q.defer();

      var res = {};

      $http.get('/xoslib/meterstatistics', { params: { 'resource': containerName } }).then(function (containerStats) {
        res.stats = containerStats.data;
        return $http.get('/xoslib/meterstatistics', { params: { 'resource': containerName + '-eth0' } });
      }).then(function (portStats) {
        res.port = {
          eth0: portStats.data
        };
        return $http.get('/xoslib/meterstatistics', { params: { 'resource': containerName + '-eth1' } });
      }).then(function (portStats) {
        res.port.eth1 = portStats.data;
        deferred.resolve(res);
      })['catch'](function (e) {
        deferred.reject(e);
      });

      return deferred.promise;
    };
  }]).service('Slice', ["$resource", function ($resource) {
    return $resource('/xos/slices', { id: '@id' });
  }]).service('Instances', ["$resource", function ($resource) {
    return $resource('/xos/instances/:id', { id: '@id' });
  }]).service('Node', ["$resource", "$q", "Instances", function ($resource, $q, Instances) {
    return $resource('/xos/nodes', { id: '@id' }, {
      queryWithInstances: {
        method: 'GET',
        isArray: true,
        interceptor: {
          response: function response(res) {

            // TODO update the API to include instances in nodes
            // http://stackoverflow.com/questions/14573102/how-do-i-include-related-model-fields-using-django-rest-framework

            var deferred = $q.defer();

            var requests = [];

            angular.forEach(res.data, function (node) {
              requests.push(Instances.query({ node: node.id }).$promise);
            });

            $q.all(requests).then(function (list) {
              res.data.map(function (node, i) {
                node.instances = list[i];
                return node;
              });
              deferred.resolve(res.data);
            });

            return deferred.promise;
          }
        }
      }
    });
  }]).service('Subscribers', ["$resource", "$q", "SubscriberDevice", function ($resource, $q, SubscriberDevice) {
    return $resource('/xoslib/cordsubscriber/:id', { id: '@id' }, {
      update: {
        method: 'PUT',
        isArray: false
      },
      queryWithDevices: {
        method: 'GET',
        isArray: true,
        interceptor: {
          response: function response(res) {

            /**
            * For each subscriber retrieve devices and append them
            */

            var deferred = $q.defer();

            var requests = [];

            angular.forEach(res.data, function (subscriber) {
              requests.push(SubscriberDevice.query({ id: subscriber.id }).$promise);
            });

            $q.all(requests).then(function (list) {

              // adding devices

              res.data.map(function (subscriber, i) {
                subscriber.devices = list[i];
                subscriber.type = 'subscriber';

                subscriber.devices.map(function (d) {
                  return d.type = 'device';
                });

                return subscriber;
              });

              // faking to have 2 subscriber
              // res.data.push(angular.copy(res.data[0]));

              deferred.resolve(res.data);
            });

            return deferred.promise;
          }
        }
      },
      getWithDevices: {
        method: 'GET',
        isArray: false,
        interceptor: {
          response: function response(res) {
            var d = $q.defer();

            SubscriberDevice.query({ id: res.data.id }).$promise.then(function (devices) {
              devices.map(function (d) {
                return d.type = 'device';
              });
              res.data.devices = devices;
              res.data.type = 'subscriber';
              d.resolve(res.data);
            })['catch'](function (err) {
              d.reject(err);
            });

            return d.promise;
          }
        }
      }
    });
  }]).service('SubscriberDevice', ["$resource", function ($resource) {
    return $resource('/xoslib/rs/subscriber/:id/users/', { id: '@id' });
  }]).service('ServiceRelation', ["$q", "lodash", "Services", "Tenant", "Slice", "Instances", function ($q, lodash, Services, Tenant, Slice, Instances) {

    // count the mas depth of an object
    var depthOf = function 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
    var findLevelRelation = function findLevelRelation(tenants, rootId) {
      return lodash.filter(tenants, function (service) {
        return service.subscriber_service === rootId;
      });
    };

    var findSpecificInformation = function findSpecificInformation(tenants, rootId) {
      var tenants = lodash.filter(tenants, function (service) {
        return service.provider_service === rootId && service.subscriber_tenant;
      });

      var info;

      tenants.forEach(function (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
    var findLevelServices = function findLevelServices(relations, services) {
      var levelServices = [];
      lodash.forEach(relations, function (tenant) {
        var service = lodash.find(services, { id: tenant.provider_service });
        levelServices.push(service);
      });
      return levelServices;
    };

    var buildLevel = function buildLevel(tenants, services, rootService, rootTenant) {
      var parentName = arguments.length <= 4 || arguments[4] === undefined ? null : arguments[4];

      // 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
      var levelRelation = findLevelRelation(tenants, rootService.id);
      // find all items related to rootElement
      var levelServices = findLevelServices(levelRelation, services);

      // remove this item from the list (performance
      unlinkedServices = lodash.difference(unlinkedServices, levelServices);

      rootService.service_specific_attribute = findSpecificInformation(tenants, rootService.id);

      var tree = {
        name: rootService.humanReadableName,
        parent: parentName,
        type: 'service',
        service: rootService,
        tenant: rootTenant,
        children: []
      };

      lodash.forEach(levelServices, function (service) {
        var tenant = lodash.find(tenants, { subscriber_tenant: rootTenant.id, provider_service: service.id });
        tree.children.push(buildLevel(tenants, unlinkedServices, service, tenant, rootService.humanReadableName));
      });

      // if it is the last element append internet
      if (tree.children.length === 0) {
        tree.children.push({
          name: 'Router',
          type: 'router',
          children: []
        });
      }

      return tree;
    };

    var buildSubscriberServiceTree = function buildSubscriberServiceTree(services, tenants) {
      var subscriber = arguments.length <= 2 || arguments[2] === undefined ? { id: 1, name: 'fakeSubs' } : arguments[2];

      // find the root service
      // it is the one attached to subsriber_root
      // as now we have only one root so this can work
      var rootTenant = lodash.find(tenants, { subscriber_root: subscriber.id });
      var rootService = lodash.find(services, { id: rootTenant.provider_service });

      var serviceTree = buildLevel(tenants, services, rootService, rootTenant);

      return {
        name: subscriber.name || subscriber.humanReadableName,
        parent: null,
        type: 'subscriber',
        children: [serviceTree]
      };
    };

    // applying domain knowledge to build the global service tree
    var buildServiceTree = function buildServiceTree(services, tenants) {

      // TODO refactor
      var buildChild = function buildChild(services, tenants, currentService) {

        var response = {
          type: 'service',
          name: currentService.humanReadableName,
          service: currentService
        };

        var tenant = lodash.find(tenants, { subscriber_service: currentService.id });
        if (tenant) {
          var next = lodash.find(services, { id: tenant.provider_service });
          response.children = [buildChild(services, tenants, next)];
        } else {
          response.children = [{
            name: 'Router',
            type: 'router',
            children: []
          }];
        }
        delete currentService.id; // conflict with d3
        return response;
      };

      var baseService = lodash.find(services, { id: 3 });

      if (!angular.isDefined(baseService)) {
        console.error('Missing Base service!');
        return;
      }

      var baseData = {
        name: 'Subscriber',
        type: 'subscriber',
        parent: null,
        children: [buildChild(services, tenants, baseService)]
      };
      return baseData;
    };

    var getBySubscriber = function getBySubscriber(subscriber) {
      var deferred = $q.defer();
      var services, tenants;
      Services.query().$promise.then(function (res) {
        services = res;
        return Tenant.query().$promise;
      }).then(function (res) {
        tenants = res;
        deferred.resolve(buildSubscriberServiceTree(services, tenants, subscriber));
      })['catch'](function (e) {
        throw new Error(e);
      });

      return deferred.promise;
    };

    var get = function get() {
      var deferred = $q.defer();
      var services, tenants;
      Services.query().$promise.then(function (res) {
        services = res;
        return Tenant.query({ kind: 'coarse' }).$promise;
      }).then(function (res) {
        tenants = res;
        deferred.resolve(buildServiceTree(services, tenants));
      })['catch'](function (e) {
        throw new Error(e);
      });

      return deferred.promise;
    };

    // export APIs
    return {
      get: get,
      buildServiceTree: buildServiceTree,
      getBySubscriber: getBySubscriber,
      buildLevel: buildLevel,
      buildSubscriberServiceTree: buildSubscriberServiceTree,
      findLevelRelation: findLevelRelation,
      findLevelServices: findLevelServices,
      depthOf: depthOf,
      findSpecificInformation: findSpecificInformation
    };
  }]);
})();
'use strict';

var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })();

(function () {
  angular.module('xos.diagnostic').service('RackHelper', ["serviceTopologyConfig", "lodash", function (serviceTopologyConfig, lodash) {
    var _this = this;

    this.getComputeNodeLabelSize = function () {
      return serviceTopologyConfig.computeNode.labelHeight + serviceTopologyConfig.instance.margin * 2;
    };

    /**
    * Given a list of instance should get the Compute Node size.
    * They are placed in rows of 2 with 5px margin on each side.
    */

    this.getComputeNodeSize = lodash.memoize(function (instances) {
      var width = serviceTopologyConfig.instance.margin * 3 + serviceTopologyConfig.instance.width * 2;

      var rows = Math.round(instances.length / 2);

      var labelSpace = _this.getComputeNodeLabelSize();

      var height = serviceTopologyConfig.instance.height * rows + serviceTopologyConfig.instance.margin * (rows + 1) + labelSpace;

      return [width, height];
    });

    /**
    * Give a list on Compute Node should calculate the Rack Size.
    * Compute nodes are placed in a single column with 5px margin on each side.
    */
    this.getRackSize = function (nodes) {

      var width = 0;
      var height = serviceTopologyConfig.computeNode.margin;

      lodash.forEach(nodes, function (node) {
        var _getComputeNodeSize = _this.getComputeNodeSize(node.instances);

        var _getComputeNodeSize2 = _slicedToArray(_getComputeNodeSize, 2);

        var nodeWidth = _getComputeNodeSize2[0];
        var nodeHeight = _getComputeNodeSize2[1];

        width = nodeWidth + serviceTopologyConfig.computeNode.margin * 2;
        height += nodeHeight + serviceTopologyConfig.computeNode.margin;
      });

      return [width, height];
    };

    /**
    * Given an instance index, return the coordinates
    */

    this.getInstancePosition = function (position) {
      var row = Math.floor(position / 2);
      var column = position % 2 ? 1 : 0;

      // add ComputeNode label size
      var labelSpace = _this.getComputeNodeLabelSize();

      // x = margin + (width * column) + ( maring * column)
      var x = serviceTopologyConfig.instance.margin + serviceTopologyConfig.instance.width * column + serviceTopologyConfig.instance.margin * column;

      // y = label + margin + (height * row) + ( maring * row)
      var y = labelSpace + serviceTopologyConfig.instance.margin + serviceTopologyConfig.instance.height * row + serviceTopologyConfig.instance.margin * row;
      return [x, y];
    };

    /**
    * Given an Compute Node index, return the coordinates
    */

    this.getComputeNodePosition = function (nodes, position) {

      var x = serviceTopologyConfig.computeNode.margin;

      var previousElEight = lodash.reduce(nodes.slice(0, position), function (val, node) {
        return val + _this.getComputeNodeSize(node.instances)[1];
      }, 0);

      var y = serviceTopologyConfig.computeNode.margin + serviceTopologyConfig.computeNode.margin * position + previousElEight;

      return [x, y];
    };
  }]);
})();
'use strict';

var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })();

(function () {
  'use strict';

  var shapes = {
    cloud: ' M 79.72 49.60 C 86.00 37.29 98.57 29.01 111.96 26.42 C 124.27 24.11 137.53 26.15 148.18 32.90 C 158.08 38.78 165.39 48.87 167.65 60.20 C 176.20 57.90 185.14 56.01 194.00 57.73 C 206.08 59.59 217.92 66.01 224.37 76.66 C 227.51 81.54 228.85 87.33 229.23 93.06 C 237.59 93.33 246.22 95.10 253.04 100.19 C 256.69 103.13 259.87 107.67 258.91 112.59 C 257.95 118.43 252.78 122.38 247.78 124.82 C 235.27 130.43 220.23 130.09 207.98 123.93 C 199.33 127.88 189.76 129.43 180.30 128.57 C 173.70 139.92 161.70 147.65 148.86 149.93 C 133.10 153.26 116.06 148.15 104.42 137.08 C 92.98 143.04 78.96 143.87 66.97 139.04 C 57.75 135.41 49.70 128.00 46.60 118.43 C 43.87 109.95 45.81 100.29 51.30 93.32 C 57.38 85.18 67.10 80.44 76.99 78.89 C 74.38 69.20 74.87 58.52 79.72 49.60 Z'
  };

  var computeNodeId = 0;
  var instanceId = 0;

  angular.module('xos.diagnostic').service('NodeDrawer', ["d3", "serviceTopologyConfig", "RackHelper", "lodash", function (d3, serviceTopologyConfig, RackHelper, lodash) {
    var _this2 = this;

    var _this = this;

    this.addNetworks = function (nodes) {
      nodes.append('path').attr({
        d: shapes.cloud,
        transform: 'translate(-63, -52), scale(0.5)',
        'class': 'cloud'
      });

      nodes.append('text').attr({
        'text-anchor': 'middle'
      }).text(function (d) {
        return d.name;
      });

      nodes.each(function (n) {
        var currentNode = d3.select(this);
        // cicle trouch node to add Tags and Public IP
        if (n.name === 'LAN' && angular.isDefined(n.subscriberTag)) {
          currentNode.append('text').attr({
            'text-anchor': 'middle',
            y: 40
          }).text(function () {
            return 'C-Tag: ' + n.subscriberTag.cTag;
          });

          currentNode.append('text').attr({
            'text-anchor': 'middle',
            y: 60
          }).text(function () {
            return 'S-Tag: ' + n.subscriberTag.sTag;
          });
        }

        if (n.name === 'WAN' && angular.isDefined(n.subscriberIP)) {
          currentNode.append('text').attr({
            'text-anchor': 'middle',
            y: 40
          }).text(function () {
            return 'Public IP: ' + n.subscriberIP;
          });
        }
      });
    };

    this.addRack = function (nodes) {

      // loop because of D3
      // rack will be only one
      nodes.each(function (d) {
        var _RackHelper$getRackSize = RackHelper.getRackSize(d.computeNodes);

        var _RackHelper$getRackSize2 = _slicedToArray(_RackHelper$getRackSize, 2);

        var w = _RackHelper$getRackSize2[0];
        var h = _RackHelper$getRackSize2[1];

        // TODO update instead of delete and redraw
        nodes.select('g').remove();

        var rack = nodes.append('g');

        rack.attr({
          transform: 'translate(0,0)'
        }).transition().duration(serviceTopologyConfig.duration).attr({
          transform: function transform() {
            return 'translate(' + -(w / 2) + ', ' + -(h / 2) + ')';
          }
        });

        rack.append('rect').attr({
          width: 0,
          height: 0
        }).transition().duration(serviceTopologyConfig.duration).attr({
          width: w,
          height: h
        });

        rack.append('text').attr({
          'text-anchor': 'middle',
          y: -10,
          x: w / 2,
          opacity: 0
        }).text(function (d) {
          return d.name;
        }).transition().duration(serviceTopologyConfig.duration).attr({
          opacity: 1
        });

        _this2.drawComputeNodes(rack, d.computeNodes);
      });
    };

    this.drawComputeNodes = function (container, nodes) {

      var elements = container.selectAll('.compute-nodes').data(nodes, function (d) {
        if (!angular.isString(d.d3Id)) {
          d.d3Id = 'compute-node-' + ++computeNodeId;
        }
        return d.d3Id;
      });

      var _container$node$getBoundingClientRect = container.node().getBoundingClientRect();

      var width = _container$node$getBoundingClientRect.width;
      var height = _container$node$getBoundingClientRect.height;

      var nodeContainer = elements.enter().append('g');

      nodeContainer.attr({
        transform: 'translate(' + width / 2 + ', ' + height / 2 + ')',
        'class': 'compute-node'
      }).transition().duration(serviceTopologyConfig.duration).attr({
        transform: function transform(d) {
          return 'translate(' + RackHelper.getComputeNodePosition(nodes, d.d3Id.replace('compute-node-', '') - 1) + ')';
        }
      });

      nodeContainer.append('rect').attr({
        width: 0,
        height: 0
      }).transition().duration(serviceTopologyConfig.duration).attr({
        width: function width(d) {
          return RackHelper.getComputeNodeSize(d.instances)[0];
        },
        height: function height(d) {
          return RackHelper.getComputeNodeSize(d.instances)[1];
        }
      });

      nodeContainer.append('text').attr({
        'text-anchor': 'start',
        y: 15, //FIXME
        x: 10, //FIXME
        opacity: 0
      }).text(function (d) {
        return d.humanReadableName.split('.')[0];
      }).transition().duration(serviceTopologyConfig.duration).attr({
        opacity: 1
      });

      // if there are Compute Nodes
      if (nodeContainer.length > 0) {
        // draw instances for each compute node
        nodeContainer.each(function (a) {
          _this.drawInstances(d3.select(this), a.instances);
        });
      }
    };

    // NOTE Stripping unuseful names to shorten labels.
    // This is not elegant
    var formatInstanceName = function formatInstanceName(name) {
      return name.replace('app_', '').replace('service_', '')
      // .replace('ovs_', '')
      .replace('mysite_', '').replace('_instance', '');
    };

    var getInstanceStatusColor = function getInstanceStatusColor(instance) {
      function startWith(val, string) {
        return string.substring(0, val.length) === val;
      }

      if (startWith('0 - ', instance.backend_status)) {
        return 'provisioning';
      }
      if (startWith('1 - ', instance.backend_status)) {
        return 'good';
      }
      if (startWith('2 - ', instance.backend_status)) {
        return 'bad';
      } else {
        return '';
      }
    };

    var drawContainer = function drawContainer(container, docker) {

      var containerBox = container.append('g').attr({
        'class': 'container',
        transform: 'translate(' + serviceTopologyConfig.instance.margin + ', 115)'
      });

      containerBox.append('rect').attr({
        width: 250 - serviceTopologyConfig.container.margin * 2,
        height: serviceTopologyConfig.container.height
      });

      containerBox.append('text').attr({
        y: 20,
        x: serviceTopologyConfig.instance.margin,
        'class': 'name'
      }).text(docker.name);

      // add stats
      var interestingMeters = ['memory', 'memory.usage', 'cpu_util'];

      interestingMeters.forEach(function (m, i) {
        var meter = lodash.find(docker.stats, { meter: m });
        // if there is no meter stats skip rendering
        if (!angular.isDefined(meter)) {
          return;
        }
        containerBox.append('text').attr({
          y: 40 + i * 15,
          x: serviceTopologyConfig.instance.margin,
          opacity: 0
        }).text(meter.description + ': ' + Math.round(meter.value) + ' ' + meter.unit).transition().duration(serviceTopologyConfig.duration).attr({
          opacity: 1
        });
      });

      // add port stats
      var ports = ['eth0', 'eth1'];
      var interestingPortMeters = [{
        meter: 'network.incoming.bytes.rate',
        label: 'Incoming'
      }, {
        meter: 'network.outgoing.bytes.rate',
        label: 'Outgoing'
      }];

      ports.forEach(function (p, j) {

        // if there are no port stats skip rendering
        if (docker.port[p].length === 0) {
          return;
        }

        containerBox.append('text').attr({
          y: 90,
          x: serviceTopologyConfig.instance.margin + 120 * j,
          'class': 'name'
        }).text(docker.name + '-' + p);

        interestingPortMeters.forEach(function (m, i) {

          var meter = lodash.find(docker.port[p], { meter: m.meter });
          // if there is no meter stats skip rendering
          if (!angular.isDefined(meter)) {
            return;
          }
          containerBox.append('text').attr({
            y: 105 + i * 15,
            x: serviceTopologyConfig.instance.margin + 120 * j,
            opacity: 0
          }).text(m.label + ': ' + Math.round(meter.value) + ' ' + meter.unit).transition().duration(serviceTopologyConfig.duration).attr({
            opacity: 1
          });
        });
      });
    };

    var showInstanceStats = function showInstanceStats(container, instance) {

      // NOTE this should be dinamically positioned
      // base on the number of element present
      var statsContainer = container.append('g').attr({
        transform: 'translate(200, -120)',
        'class': 'stats-container'
      });

      statsContainer.append('line').attr({
        x1: -160,
        y1: 120,
        x2: 0,
        y2: 50,
        stroke: 'black',
        opacity: 0
      }).transition().duration(serviceTopologyConfig.duration).attr({
        opacity: 1
      });

      // NOTE rect should be dinamically sized base on the presence of a container
      var statsHeight = 110;
      var statsWidth = 250;

      if (instance.container) {
        statsHeight += serviceTopologyConfig.container.height + serviceTopologyConfig.container.margin * 2;
      }

      statsContainer.append('rect').attr({
        width: statsWidth,
        height: statsHeight,
        opacity: 0
      }).transition().duration(serviceTopologyConfig.duration).attr({
        opacity: 1
      });

      // add instance info
      statsContainer.append('text').attr({
        y: 15,
        x: serviceTopologyConfig.instance.margin,
        'class': 'name',
        opacity: 0
      }).text(instance.humanReadableName).transition().duration(serviceTopologyConfig.duration).attr({
        opacity: 1
      });

      statsContainer.append('text').attr({
        y: 30,
        x: serviceTopologyConfig.instance.margin,
        'class': 'ip',
        opacity: 0
      }).text(instance.ip).transition().duration(serviceTopologyConfig.duration).attr({
        opacity: 1
      });

      // add stats
      var interestingMeters = ['memory', 'memory.usage', 'cpu', 'vcpus'];

      interestingMeters.forEach(function (m, i) {
        var meter = lodash.find(instance.stats, { meter: m });
        statsContainer.append('text').attr({
          y: 55 + i * 15,
          x: serviceTopologyConfig.instance.margin,
          opacity: 0
        }).text(meter.description + ': ' + Math.round(meter.value) + ' ' + meter.unit).transition().duration(serviceTopologyConfig.duration).attr({
          opacity: 1
        });
      });

      if (instance.container) {
        // draw container
        drawContainer(statsContainer, instance.container);
      }
    };

    this.drawInstances = function (container, instances) {

      // TODO check for stats field in instance and draw popup

      var _container$node$getBoundingClientRect2 = container.node().getBoundingClientRect();

      var width = _container$node$getBoundingClientRect2.width;
      var height = _container$node$getBoundingClientRect2.height;

      var elements = container.selectAll('.instances').data(instances, function (d) {
        return angular.isString(d.d3Id) ? d.d3Id : d.d3Id = 'instance-' + ++instanceId;
      });

      var instanceContainer = elements.enter().append('g');

      instanceContainer.attr({
        transform: 'translate(' + width / 2 + ', ' + height / 2 + ')',
        'class': function _class(d) {
          return 'instance ' + (d.selected ? 'active' : '') + ' ' + getInstanceStatusColor(d);
        }
      }).transition().duration(serviceTopologyConfig.duration).attr({
        transform: function transform(d, i) {
          return 'translate(' + RackHelper.getInstancePosition(i) + ')';
        }
      });

      instanceContainer.append('rect').attr({
        width: 0,
        height: 0
      }).transition().duration(serviceTopologyConfig.duration).attr({
        width: serviceTopologyConfig.instance.width,
        height: serviceTopologyConfig.instance.height
      });

      instanceContainer.append('text').attr({
        'text-anchor': 'middle',
        y: 23, //FIXME
        x: 40, //FIXME
        opacity: 0
      }).text(function (d) {
        return formatInstanceName(d.humanReadableName);
      }).transition().duration(serviceTopologyConfig.duration).attr({
        opacity: 1
      });

      // if stats are attached and instance is active,
      // draw stats
      instanceContainer.each(function (instance, i) {

        var container = d3.select(this);

        if (angular.isDefined(instance.stats) && instance.selected) {
          showInstanceStats(container, instance, i);
        }
      });

      instanceContainer.on('click', function (d) {
        console.log('Draw vignette with stats for instance: ' + d.name);
      });
    };

    this.addPhisical = function (nodes) {
      nodes.append('rect').attr(serviceTopologyConfig.square);

      nodes.append('text').attr({
        'text-anchor': 'middle',
        y: serviceTopologyConfig.square.y - 10
      }).text(function (d) {
        return d.name || d.humanReadableName;
      });
    };

    this.addDevice = function (nodes) {
      nodes.append('circle').attr(serviceTopologyConfig.circle);

      nodes.append('text').attr({
        'text-anchor': 'end',
        x: -serviceTopologyConfig.circle.r - 10,
        y: serviceTopologyConfig.circle.r / 2
      }).text(function (d) {
        return d.name || d.mac;
      });
    };
  }]);
})();
'use strict';

var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })();

(function () {
  'use strict';

  angular.module('xos.diagnostic').service('LogicTopologyHelper', ["$window", "$log", "$rootScope", "lodash", "serviceTopologyConfig", "NodeDrawer", "ChartData", function ($window, $log, $rootScope, lodash, serviceTopologyConfig, NodeDrawer, ChartData) {
    var _this = this;

    var diagonal,
        nodes,
        links,
        i = 0,
        svgWidth,
        svgHeight,
        layout;

    var baseData = ChartData.logicTopologyData;

    /**
     * Calculate the horizontal position for each element.
     * subsrcribers, devices and routers have the same fixed width 20
     * network have a fixed width 104
     * rack have a fixed width 105
     * build and array of 6 elements representing the position of each element in the svg
     * to equally space them
     */

    this.computeElementPosition = function (svgWidth) {

      var xPos = [];

      var totalElWidth = lodash.reduce(serviceTopologyConfig.elWidths, function (el, val) {
        return val + el;
      }, 0);

      var remainingSpace = svgWidth - totalElWidth - serviceTopologyConfig.widthMargin * 2;

      var step = remainingSpace / (serviceTopologyConfig.elWidths.length - 1);

      lodash.forEach(serviceTopologyConfig.elWidths, function (el, i) {

        // get half of the previous elements width
        var previousElWidth = 0;
        if (i !== 0) {
          previousElWidth = lodash.reduce(serviceTopologyConfig.elWidths.slice(0, i), function (el, val) {
            return val + el;
          }, 0);
        }

        var elPos = serviceTopologyConfig.widthMargin // right margin
         + step * i // space between elements
         + el / 2 // this el width
         + previousElWidth; // previous elements width

        xPos.push(svgWidth - elPos);
      });

      return xPos;
    };

    /**
    * from a nested data structure,
    * create nodes and links for a D3 Tree Layout
    */
    var computeLayout = function computeLayout(data) {
      var nodes = layout.nodes(data);

      // Normalize for fixed-depth.
      nodes.forEach(function (d) {
        // position the child node horizontally
        d.y = _this.computeElementPosition(svgWidth)[d.depth];
      });

      var links = layout.links(nodes);

      return [nodes, links];
    };

    /**
    * Draw the containing group for any node or update the existing one
    */
    var drawNodes = function drawNodes(svg, nodes) {
      // Update the nodes…
      var node = svg.selectAll('g.node').data(nodes, function (d) {
        if (!angular.isString(d.d3Id)) {
          d.d3Id = 'tree-' + ++i;
        }
        return d.d3Id;
      });

      // Enter any new nodes
      var nodeEnter = node.enter().append('g').attr({
        'class': function _class(d) {
          return 'node ' + d.type;
        },
        transform: 'translate(' + svgWidth / 2 + ', ' + svgHeight / 2 + ')'
      });

      // create Nodes
      NodeDrawer.addNetworks(node.filter('.network'));
      NodeDrawer.addRack(node.filter('.rack'));
      NodeDrawer.addPhisical(node.filter('.router'));
      NodeDrawer.addPhisical(node.filter('.subscriber'));
      NodeDrawer.addDevice(node.filter('.device'));

      // add event listener to subscriber
      node.filter('.subscriber').on('click', function () {
        $rootScope.$emit('subscriber.modal.open');
      });

      //update nodes
      // TODO if data change, only update them
      // NodeDrawer.updateRack(node.filter('.rack'));

      // Transition nodes to their new position.
      var nodeUpdate = node.transition().duration(serviceTopologyConfig.duration).attr({
        'transform': function transform(d) {
          return 'translate(' + d.y + ',' + d.x + ')';
        }
      });

      // TODO handle node remove
      var nodeExit = node.exit().remove();
    };

    /**
    * Handle links in the tree layout
    */
    var drawLinks = function drawLinks(svg, links) {

      diagonal = d3.svg.diagonal().projection(function (d) {
        return [d.y, d.x];
      });

      // Update the links…
      var link = svg.selectAll('path.link').data(links, function (d) {
        return d.target.d3Id;
      });

      // Enter any new links at the parent's previous position.
      link.enter().insert('path', 'g').attr('class', function (d) {
        return 'link ' + d.target.type;
      }).attr('d', function (d) {
        var o = { x: svgHeight / 2, y: svgWidth / 2 };
        return diagonal({ source: o, target: o });
      });

      // Transition links to their new position.
      link.transition().duration(serviceTopologyConfig.duration).attr('d', diagonal);

      link.exit().remove();
    };

    /**
    * Calculate the svg size and setup tree layout
    */
    this.setupTree = function (svg) {

      svgWidth = svg.node().getBoundingClientRect().width;
      svgHeight = svg.node().getBoundingClientRect().height;

      var width = svgWidth - serviceTopologyConfig.widthMargin * 2;
      var height = svgHeight - serviceTopologyConfig.heightMargin * 2;

      layout = d3.layout.tree().size([height, width]);
    };

    /**
    * Update the tree layout
    */

    this.updateTree = function (svg) {

      // console.log(baseData);

      var _computeLayout = computeLayout(baseData);

      // Compute the new tree layout.

      var _computeLayout2 = _slicedToArray(_computeLayout, 2);

      nodes = _computeLayout2[0];
      links = _computeLayout2[1];
      drawNodes(svg, nodes);
      drawLinks(svg, links);
    };
  }]);
})();
'use strict';

(function () {
  'use strict';
  angular.module('xos.diagnostic').directive('logicTopology', function () {
    return {
      restrict: 'E',
      scope: {
        subscribers: '=',
        selected: '='
      },
      bindToController: true,
      controllerAs: 'vm',
      templateUrl: 'templates/logicTopology.tpl.html',
      controller: ["$element", "$log", "$scope", "$rootScope", "$timeout", "d3", "LogicTopologyHelper", "Node", "Tenant", "Ceilometer", "serviceTopologyConfig", "ChartData", function controller($element, $log, $scope, $rootScope, $timeout, d3, LogicTopologyHelper, Node, Tenant, Ceilometer, serviceTopologyConfig, ChartData) {
        var _this = this;

        $log.info('Logic Plane');

        var svg;
        this.selectedInstances = [];
        this.hideInstanceStats = true;

        var handleSvg = function handleSvg(el) {

          svg = d3.select(el).append('svg').style('width', el.clientWidth + 'px').style('height', el.clientHeight + 'px');
        };

        ChartData.getLogicTree().then(function (tree) {
          LogicTopologyHelper.updateTree(svg);
        });

        $scope.$watch(function () {
          return _this.selected;
        }, function (selected) {
          if (selected) {
            ChartData.selectSubscriber(selected);
            LogicTopologyHelper.updateTree(svg);
          }
        });

        $rootScope.$on('instance.detail.hide', function () {
          _this.hideInstanceStats = true;
          $timeout(function () {
            _this.selectedInstances = [];
            ChartData.highlightInstances([]);
            LogicTopologyHelper.updateTree(svg);
          }, 500);
        });

        $rootScope.$on('instance.detail', function (evt, service) {
          ChartData.getInstanceStatus(service).then(function (instances) {
            LogicTopologyHelper.updateTree(svg);
          });
        });

        handleSvg($element[0]);
        LogicTopologyHelper.setupTree(svg);

        this.selectSubscriberModal = function () {
          _this.openSelectSubscriberModal = true;
          $scope.$apply();
        };

        this.subscriberStatusModal = function () {
          _this.openSubscriberStatusModal = true;
          $scope.$apply();
        };

        // listen for subscriber modal event
        $rootScope.$on('subscriber.modal.open', function () {

          if (ChartData.currentSubscriber) {
            _this.subscriberStatusModal();
          } else {
            _this.selectSubscriberModal();
          }
        });

        // listen for subscriber modal event
        $rootScope.$on('subscriber.modal.open', function () {

          if (ChartData.currentSubscriber) {
            _this.currentSubscriber = ChartData.currentSubscriber;
            _this.subscriberStatusModal();
          } else {
            _this.selectSubscriberModal();
          }
        });
      }]
    };
  });
})();
'use strict';

(function () {
  'use strict';
  angular.module('xos.diagnostic').directive('diagnosticContainer', function () {
    return {
      restrict: 'E',
      templateUrl: 'templates/diagnostic.tpl.html',
      controllerAs: 'vm',
      controller: ["ChartData", "Subscribers", "ServiceRelation", "$rootScope", "$log", function controller(ChartData, Subscribers, ServiceRelation, $rootScope, $log) {
        var _this = this;

        this.loader = true;
        this.error = false;
        Subscribers.query().$promise.then(function (subscribers) {
          _this.subscribers = subscribers;
          return ServiceRelation.get();
        }).then(function (serviceChain) {
          _this.serviceChain = serviceChain;
        })['catch'](function (e) {
          throw new Error(e);
          _this.error = e;
        })['finally'](function () {
          _this.loader = false;
        });

        $rootScope.$on('subscriber.selected', function (evt, subscriber) {
          ServiceRelation.getBySubscriber(subscriber).then(function (serviceChain) {
            _this.serviceChain = serviceChain;
            ChartData.currentServiceChain = serviceChain;
            return Subscribers.getWithDevices({ id: subscriber.id }).$promise;
          }).then(function (subscriber) {
            _this.selectedSubscriber = subscriber;
            ChartData.currentSubscriber = subscriber;
          });
        });
      }]
    };
  });
})();
'use strict';

(function () {
  'use strict';

  angular.module('xos.diagnostic').factory('d3', ["$window", function ($window) {
    return $window.d3;
  }]);
})();
'use strict';

(function () {
  'use strict';

  angular.module('xos.diagnostic').constant('serviceTopologyConfig', {
    widthMargin: 20,
    heightMargin: 30,
    duration: 750,
    elWidths: [20, 104, 105, 104, 20], //this is not true
    circle: {
      radius: 10,
      r: 10,
      selectedRadius: 15
    },
    square: {
      width: 20,
      height: 20,
      x: -10,
      y: -10
    },
    rack: {
      width: 105,
      height: 50,
      x: -30,
      y: -25
    },
    computeNode: {
      width: 50,
      height: 20,
      margin: 5,
      labelHeight: 10,
      x: -25,
      y: -10
    },
    instance: {
      width: 80,
      height: 36,
      margin: 5,
      x: -40,
      y: -18
    },
    container: {
      width: 60,
      height: 130,
      margin: 5,
      x: -30,
      y: -15
    }
  });
})();
'use strict';

(function () {
  'use strict';

  angular.module('xos.diagnostic').service('ChartData', ["$rootScope", "$q", "lodash", "Tenant", "Node", "serviceTopologyConfig", "Ceilometer", "Instances", function ($rootScope, $q, lodash, Tenant, Node, serviceTopologyConfig, Ceilometer, Instances) {
    var _this = this;

    this.currentSubscriber = null;
    this.currentServiceChain = null;

    this.logicTopologyData = {
      name: 'Router',
      type: 'router',
      children: [{
        name: 'WAN',
        type: 'network',
        children: [{
          name: 'Rack',
          type: 'rack',
          computeNodes: [],
          children: [{
            name: 'LAN',
            type: 'network',
            children: [{
              name: 'Subscriber',
              type: 'subscriber'
            }] //subscribers goes here
          }]
        }]
      }]
    };

    this.getLogicTree = function () {
      var deferred = $q.defer();

      Node.queryWithInstances().$promise.then(function (computeNodes) {
        _this.logicTopologyData.children[0].children[0].computeNodes = computeNodes;
        // LogicTopologyHelper.updateTree(svg);
        deferred.resolve(_this.logicTopologyData);
      });

      return deferred.promise;
    };

    /**
    * Add Subscriber tag to LAN Network
    */
    this.addSubscriberTag = function (tags) {
      _this.logicTopologyData.children[0].children[0].children[0].subscriberTag = {
        cTag: tags.cTag,
        sTag: tags.sTag
      };
    };

    /**
    * Add Subscribers to the tree
    */
    this.addSubscriber = function (subscriber) {
      subscriber.children = subscriber.devices;

      // add subscriber to data tree
      _this.logicTopologyData.children[0].children[0].children[0].children = [subscriber];
      return _this.logicTopologyData;
    };

    this.getSubscriberTag = function (subscriber) {
      var tags = {
        cTag: subscriber.c_tag,
        sTag: subscriber.s_tag
      };

      _this.addSubscriberTag(tags);
      // add tags info to current subscriber
      _this.currentSubscriber.tags = tags;
    };

    this.getSubscriberIP = function (subscriber) {
      // const ip = JSON.parse(this.currentServiceChain.children[0].children[0].tenant.service_specific_attribute).wan_container_ip;
      // const ip = this.currentServiceChain.children[0].children[0].tenant.wan_container_ip;
      _this.logicTopologyData.children[0].subscriberIP = subscriber.wan_container_ip;
    };

    this.selectSubscriber = function (subscriber) {
      // append the device with to config settings
      serviceTopologyConfig.elWidths.push(160);

      _this.addSubscriber(angular.copy(subscriber));

      //clean selected instances
      _this.highlightInstances([]);

      _this.getSubscriberTag(subscriber);
      _this.getSubscriberIP(subscriber);
    };

    this.highlightInstances = function (instances) {

      var computeNodes = _this.logicTopologyData.children[0].children[0].computeNodes;

      // unselect all
      computeNodes.map(function (node) {
        node.instances.map(function (instance) {
          instance.selected = false;
          return instance;
        });
      });

      lodash.forEach(instances, function (instance) {
        computeNodes.map(function (node) {
          node.instances.map(function (d3instance) {
            if (d3instance.id === instance.id) {
              // console.log(d3instance, instance);
              d3instance.selected = true;
              d3instance.stats = instance.stats; //add stats to d3 node
              d3instance.container = instance.container; // container info to d3 node
            }
            return d3instance;
          });
        });
      });
    };

    this.getInstanceStatus = function (service) {
      var deferred = $q.defer();

      var p = undefined;

      // subscriber specific
      if (_this.currentSubscriber) {

        var attr = undefined;
        try {
          attr = JSON.parse(service.tenant.service_specific_attribute);
        } catch (e) {
          attr = null;
        }

        // if no instances are associated to the subscriber
        if (!attr || !attr.instance_id) {
          var d = $q.defer();
          d.resolve([]);
          p = d.promise;
        }
        // if ther is an instance
        else {
            (function () {
              var instance = {};
              p = Instances.get({ id: attr.instance_id }).$promise.then(function (_instance) {
                instance = _instance;
                return Ceilometer.getInstanceStats(instance.instance_uuid);
              }).then(function (stats) {
                instance.stats = stats;
                var containerName = 'vcpe-' + _this.currentSubscriber.tags.sTag + '-' + _this.currentSubscriber.tags.cTag;
                // append containers
                instance.container = {
                  name: containerName
                };

                // TODO fetch container stats
                return Ceilometer.getContainerStats(containerName);
              }).then(function (containerStats) {
                instance.container.stats = containerStats.stats;
                instance.container.port = containerStats.port;
                return [instance];
              });
            })();
          }
      }
      // global scope
      else {
          var param = {
            'service_vsg': { kind: 'vCPE' },
            'service_vbng': { kind: 'vBNG' },
            'service_volt': { kind: 'vOLT' }
          };

          p = Tenant.queryVsgInstances(param[service.name]).$promise.then(function (instances) {

            return Ceilometer.getInstancesStats(instances);
          });
        }

      p.then(function (instances) {
        _this.highlightInstances(instances);
        deferred.resolve(instances);
      })['catch'](function (e) {
        deferred.reject(e);
      });

      return deferred.promise;
    };
  }]);
})();
angular.module('xos.diagnostic').run(function($location){
  $location.path('/')
});
angular.bootstrap(angular.element('#xosDiagnostic'), ['xos.diagnostic']);