| //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\">×</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\">×</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']); |