From e3caa58f24e24f4de84d91baf195fc64d6109203 Mon Sep 17 00:00:00 2001 From: Alon Heller Date: Wed, 13 Sep 2017 12:37:49 +0300 Subject: [PATCH] Vitrage - entity graph fixes: ============================= 1. Circles on entity graph are bigger 2. Alarms fixed - now shown on graph 3. Heat Stack icon added 4. Captions in entities info panel fixed. 5. Info Panel style fixed 6. Heat stack filter added 7. Heat stack - Depth of graph added 8. Bug - data change was checked just by number of nodes. Now it also check names Change-Id: I4af0863a5e217ec3435dccce3b75d1a7e05ac4e0 --- vitrage_dashboard/api/vitrage.py | 26 +- vitrage_dashboard/api/vitrage_rest_api.py | 25 +- .../openstack-service-api/vitrage.service.js | 5 +- .../project/entities/entities.controller.js | 567 ++++++++++-------- .../dashboard/project/entities/entities.html | 8 +- .../graph/entities-graph.directive.js | 27 +- .../entities/info/entities-info.directive.js | 50 ++ .../project/entities/info/entities-info.html | 12 +- .../project/entities/info/entities-info.scss | 15 +- .../toolbox/entities-toolbox.directive.js | 9 +- .../entities/toolbox/entities-toolbox.html | 12 + .../entities/toolbox/entities-toolbox.scss | 5 + .../services/vitrage_topology.service.js | 123 ++-- 13 files changed, 544 insertions(+), 340 deletions(-) diff --git a/vitrage_dashboard/api/vitrage.py b/vitrage_dashboard/api/vitrage.py index f7fb424..7a2a53b 100644 --- a/vitrage_dashboard/api/vitrage.py +++ b/vitrage_dashboard/api/vitrage.py @@ -12,6 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +https://docs.openstack.org/horizon/latest/contributor/tutorials/plugin.html +""" + +""" This file will likely be necessary if creating a Django or Angular driven + plugin. This file is intended to act as a convenient location for + interacting with the new service this plugin is supporting. + While interactions with the service can be handled in the views.py, + isolating the logic is an established pattern in Horizon. +""" from horizon.utils.memoized import memoized # noqa from keystoneauth1.identity.generic.token import Token @@ -19,6 +29,9 @@ from keystoneauth1.session import Session from openstack_dashboard.api import base from vitrageclient import client as vitrage_client +import logging +LOG = logging.getLogger(__name__) + @memoized def vitrageclient(request, password=None): @@ -30,10 +43,19 @@ def vitrageclient(request, password=None): return vitrage_client.Client('1', session) -def topology(request, query=None, graph_type='tree', all_tenants='false'): +def topology(request, query=None, graph_type='tree', all_tenants='false', + root=None, limit=None): + LOG.info("--------- CALLING VITRAGE_CLIENT ---request %s", str(request)) + LOG.info("--------- CALLING VITRAGE_CLIENT ---query %s", str(query)) + LOG.info("------ CALLING VITRAGE_CLIENT --graph_type %s", str(graph_type)) + LOG.info("---- CALLING VITRAGE_CLIENT --all_tenants %s", str(all_tenants)) + LOG.info("--------- CALLING VITRAGE_CLIENT --------root %s", str(root)) + LOG.info("--------- CALLING VITRAGE_CLIENT --------limit %s", str(limit)) return vitrageclient(request).topology.get(query=query, graph_type=graph_type, - all_tenants=all_tenants) + all_tenants=all_tenants, + root=root, + limit=limit) def alarms(request, vitrage_id='all', all_tenants='false'): diff --git a/vitrage_dashboard/api/vitrage_rest_api.py b/vitrage_dashboard/api/vitrage_rest_api.py index b61cc33..0a3b0cf 100644 --- a/vitrage_dashboard/api/vitrage_rest_api.py +++ b/vitrage_dashboard/api/vitrage_rest_api.py @@ -12,16 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. - from django.views import generic - - -from openstack_dashboard.api.rest import utils as rest_utils - +import logging from openstack_dashboard.api.rest import urls - +from openstack_dashboard.api.rest import utils as rest_utils from vitrage_dashboard.api import vitrage +LOG = logging.getLogger(__name__) + @urls.register class Topolgy(generic.View): @@ -41,16 +39,27 @@ class Topolgy(generic.View): """ ''' original default is graph ''' + + LOG.info("--------- reques --------------- %s", str(request)) + graph_type = 'tree' all_tenants = 'false' + root = None + limit = None + if 'graph_type' in request.GET: graph_type = request.GET.get('graph_type') if 'all_tenants' in request.GET: all_tenants = request.GET.get('all_tenants') + if 'root' in request.GET: + root = request.GET.get('root') + if 'depth' in request.GET: + limit = int(request.GET.get('depth')) query = None if 'query' in request.GET: query = request.GET.get('query') + LOG.info("--A request QUERY -- %s", str(query)) elif graph_type == 'tree': ''' Default tree query - get computes, used by Sunburst''' query = '{"and": [{"==": {"vitrage_category": "RESOURCE"}},' \ @@ -64,7 +73,9 @@ class Topolgy(generic.View): return vitrage.topology(request=request, query=query, graph_type=graph_type, - all_tenants=all_tenants) + all_tenants=all_tenants, + root=root, + limit=limit) @urls.register diff --git a/vitrage_dashboard/dashboard/static/app/core/openstack-service-api/vitrage.service.js b/vitrage_dashboard/dashboard/static/app/core/openstack-service-api/vitrage.service.js index 34d040a..5c93501 100644 --- a/vitrage_dashboard/dashboard/static/app/core/openstack-service-api/vitrage.service.js +++ b/vitrage_dashboard/dashboard/static/app/core/openstack-service-api/vitrage.service.js @@ -30,12 +30,13 @@ config = config || {}; if (graph_type) { - config.params = {graph_type: graph_type}; + config.params = config.params || {}; + config.params.graph_type = graph_type; } if (admin){ (!config.params) ? config.params = {all_tenants: true} : config.params.all_tenants = true; } - + console.info('CONFIG in core - ', config) return apiService.get('/api/vitrage/topology/', config) .error(function () { toastService.add('error', gettext('Unable to fetch the Vitrage Topology service.')); diff --git a/vitrage_dashboard/dashboard/static/dashboard/project/entities/entities.controller.js b/vitrage_dashboard/dashboard/static/dashboard/project/entities/entities.controller.js index 85a7932..619c172 100644 --- a/vitrage_dashboard/dashboard/static/dashboard/project/entities/entities.controller.js +++ b/vitrage_dashboard/dashboard/static/dashboard/project/entities/entities.controller.js @@ -1,265 +1,342 @@ -(function() { - 'use strict'; +(function () { + 'use strict'; - angular - .module('horizon.dashboard.project.vitrage') - .controller('EntitiesController', EntitiesController); + angular + .module('horizon.dashboard.project.vitrage') + .controller('EntitiesController', EntitiesController); - EntitiesController.$inject = ['$scope', 'vitrageTopologySrv', '$interval', '$location']; + EntitiesController.$inject = ['$scope', 'vitrageTopologySrv', '$interval', '$location', '$timeout']; - function EntitiesController($scope, vitrageTopologySrv, $interval, $location) { - this.model = {selected: {}}; + function EntitiesController($scope, vitrageTopologySrv, $interval, $location, $timeout) { + this.model = {selected: {}}; - var _this = this, - loadTime = 5000, - errorCount = 0, - loadInterval; + var _this = this, + loadTime = 5000, + errorCount = 0, + loadInterval, + initialized = false, + timeoutSubscriber; - $scope.$watch('automaticRefresh', function(newData, oldData) { - if (newData !== undefined && newData != oldData) { - horizon.cookies.put('entitiesAutomaticRefresh', newData); - if (newData) { - nextLoad(); - } - } - }); - - $scope.$on('graphItemClicked', function(event, data) { - _this.selectedItem = data; - event.stopPropagation(); - $scope.$digest(); - }); - - this.setSelected = function(item) { - this.model.selected = item; - }; - - loadData(); - - function loadData() { - var url = $location.absUrl(); - var admin = false; - - if (url.indexOf('admin') != -1) admin = true; - - vitrageTopologySrv.getTopology('graph', null, admin) - .then(function(res) { - var nodes = res.data.nodes, - links = res.data.links; - - _.each(links, function(link) { - link.source = nodes[link.source]; - link.target = nodes[link.target]; - }); - - if (_this.graphData) { - mergeData(res.data); - } else { - _this.graphData = res.data; - _this.graphData.ts = Date.now(); - } - - errorCount = 0; - nextLoad(); - }) - .catch(function(res) { - nextLoad(++errorCount * 2 * loadTime); - }) - } - - function nextLoad(mill) { - mill = mill || loadTime; - cancelNextLoad(); - - if ($scope.automaticRefresh) { - loadInterval = $interval(loadData, mill); - } - - } - - function cancelNextLoad() { - $interval.cancel(loadInterval); - } - - function mergeData(data) { - var graphNodes = $scope.vm.graphData.nodes, - graphLinks = $scope.vm.graphData.links; - - if (graphNodes.length != data.nodes.length || graphLinks.length != data.links.length) { - graphNodes.splice(0, graphNodes.length); - graphLinks.splice(0, graphLinks.length); - - _.each(data.nodes, function(node) { - graphNodes.push(node); + $scope.$watch('automaticRefresh', function (newData, oldData) { + if (newData !== undefined && newData != oldData) { + horizon.cookies.put('entitiesAutomaticRefresh', newData); + if (newData) { + nextLoad(); + } + } }); - _.each(data.links, function(link) { - graphLinks.push(link); + $scope.$watch('selectedHeat', function (newData, oldData) { + if (newData !== undefined && newData != oldData) { + if (timeoutSubscriber) { + console.log('Canceling heat stack timeout'); + $timeout.cancel(timeoutSubscriber); + } + timeoutSubscriber = $timeout(loadData, 250); + } }); - $scope.vm.graphData.ts = Date.now(); - - /* temp stuff */ - d3.selectAll('.node .icon') - .attr('text-anchor', 'middle') - .attr('dominant-baseline', 'central') - .attr('transform', 'scale(1)') - .attr('class', function(d) { - var category = d.category, - cls = ''; - if (category && category.toLowerCase() === 'alarm') { - var severity = d.vitrage_operational_severity; - if (severity) { - switch (severity.toLowerCase()) { - case 'critical': - cls = 'red'; - break; - case 'severe': - cls = 'orange'; - break; - case 'warning': - cls = 'yellow'; - break; - case 'ok': - cls = 'green'; - break; - case 'n/a': - cls = 'gray'; - break; - default: //'DISABLED', 'UNKNOWN', 'UNDEFINED' - cls = 'gray'; - break; + $scope.$watch('depthRange', function (newData, oldData) { + if (newData !== undefined && newData != oldData) { + if (timeoutSubscriber) { + console.log('Canceling depth timeout'); + $timeout.cancel(timeoutSubscriber); } - } - } else { - var reald = _.find(graphNodes, function(n) { - return n.id == d.id; - }); + timeoutSubscriber = $timeout(loadData, 250); + } + }); - var state = reald.vitrage_operational_state; - if (state) { - switch (state.toLowerCase()) { - case 'error': - cls = 'red'; - break; - case 'suboptimal': - cls = 'yellow'; - break; - case 'n/a': - cls = 'gray'; - break; - } - } - } - return cls; - }) - .style('font-size', function(d) { - var category = d.category || 'no_category', - icon_size; + $scope.$on('graphItemClicked', function (event, data) { + _this.selectedItem = data; + event.stopPropagation(); + $scope.$digest(); + }); - if (category && category.toLowerCase() === 'alarm') { - icon_size = '18px'; - } else { - var type = d.vitrage_type || 'no_type'; - switch (type.toLowerCase()) { - case 'nova.instance': - case 'nova.host': - case 'nova.zone': - case 'neutron.port': - icon_size = '16px'; //fa-external-link-square - break; - case 'openstack.cluster': - icon_size = '18px'; //fa-cloud - break; - case 'cinder.volume': - icon_size = '22px'; - break; - case 'neutron.network': - default: - icon_size = '20px'; - break; - } - } - return icon_size; - }) - .style('stroke', function(d) { - var category = d.category; - if (category && category.toLowerCase() === 'alarm') { - return '18px' - } - return '20px' - }) - .classed('icon', true) - .classed('fill-only', function(d) { - var type = (d.vitrage_type || '').toLowerCase(); - if (type && type === 'nova.host' || type === 'cinder.volume') { - return true; - } - }) - .text(function(d) { - var category = d.category, - icon; - if (category && category.toLowerCase() === 'alarm') { - icon = '\uf0f3'; //\uf0a2'; //bell-o - } else { - var type = d.vitrage_type || 'no_type'; - switch (type.toLowerCase()) { - case 'nova.instance': - icon = '\uf108'; //fa-desktop - break; - case 'nova.host': - icon = '\uf233'; //fa-server - break; - case 'nova.zone': - icon = '\uf279'; //fa-map - break; - case 'neutron.network': - icon = '\uf0ac'; //fa-globe - break; - case 'neutron.port': - icon = '\uf14c'; //fa-external-link-square - break; - case 'cinder.volume': - icon = '\uf0a0'; //fa-hdd-o - break; - case 'openstack.cluster': - icon = '\uf0c2'; //fa-cloud - break; - default: - icon = '\uf013'; //fa-cog - break; - } - } - return icon - }); - } + this.setSelected = function (item) { + this.model.selected = item; + }; - // we need to updated the selected info - if (_this.selectedItem && _this.selectedItem.vitrage_id && _this.selectedItem.vitrage_id !== '') { - for (var i = 0; i < data.nodes.length; i++) { - var val = data.nodes[i]; - if (val && val.vitrage_id && val.vitrage_id == _this.selectedItem.vitrage_id) { - _this.selectedItem = val; - break; - } + loadData(); + + function loadData() { + var url = $location.absUrl(); + var admin = false; + if (url.indexOf('admin') != -1) admin = true; + + // get heat stacks + if (!_this.initialized) { + vitrageTopologySrv.getTopology('graph', null, admin) + .then(function (res) { + var heats = []; + + _.each(res.data.nodes, function (node) { + if (node.vitrage_type === 'heat.stack') { + heats.push({name: node.name, vitrageId: node.vitrage_id}); + } + }); + + $scope.heats = heats; + _this.initialized = true; + }); + } + + var config = {params: {query: '{"==": {"vitrage_is_deleted" : false} }'}}; + if ($scope.selectedHeat && $scope.selectedHeat !== 'all') { + config.params.depth = $scope.depthRange; + config.params.root = $scope.selectedHeat; + } + + console.log('Config: ', config); + + vitrageTopologySrv.getTopology('graph', config, admin) + .then(function (res) { + var nodes = res.data.nodes, + links = res.data.links; + + _.each(links, function (link) { + link.source = nodes[link.source]; + link.target = nodes[link.target]; + }); + + if (_this.graphData) { + mergeData(res.data); + } else { + _this.graphData = res.data; + _this.graphData.ts = Date.now(); + } + + errorCount = 0; + nextLoad(); + }) + .catch(function (res) { + nextLoad(++errorCount * 2 * loadTime); + }); } - } - } - /* utils */ - function rnd(min, max) { - return Math.round(Math.random() * (max - min)) + min; - } + function nextLoad(mill) { + mill = mill || loadTime; + cancelNextLoad(); - function onlyIn(a1, a2, prop) { - prop = prop || 'id'; - return a1.filter(function(o1) { - return a2.filter(function(o2) { - return o1[prop] === o2[prop]; - }).length === 0; - }) + if ($scope.automaticRefresh) { + loadInterval = $interval(loadData, mill); + } + + } + + function cancelNextLoad() { + $interval.cancel(loadInterval); + } + + function dataChanged(nodes1, nodes2, links1, links2) { + if (nodes1.length !== nodes2.length || links1.length !== links2.length) { + console.log('number of nodes or links changed'); + return true; + } + + // check for nodes change + for (var i = 0; i < nodes1.length; i++) { + var nodeFound = false; + for (var j = 0; j < nodes2.length; j++) { + if (nodes1[i].name === nodes2[j].name) { + nodeFound = true; + continue; + } + } + if (!nodeFound) { + console.log('name of nodes changed'); + return true; + } + } + + return false; + } + + function mergeData(data) { + var graphNodes = $scope.vm.graphData.nodes, + graphLinks = $scope.vm.graphData.links; + + if (dataChanged(graphNodes, data.nodes, graphLinks, data.links)) { + graphNodes.splice(0, graphNodes.length); + graphLinks.splice(0, graphLinks.length); + + _.each(data.nodes, function (node) { + graphNodes.push(node); + }); + + _.each(data.links, function (link) { + graphLinks.push(link); + }); + + $scope.vm.graphData.ts = Date.now(); + + /* temp stuff */ + d3.selectAll('.node .icon') + .attr('text-anchor', 'middle') + .attr('dominant-baseline', 'central') + .attr('transform', 'scale(1)') + .attr('class', function (d) { + var category = d.category, + cls = ''; + if (category && category.toLowerCase() === 'alarm') { + var severity = d.vitrage_operational_severity; + if (severity) { + switch (severity.toLowerCase()) { + case 'critical': + cls = 'red'; + break; + case 'severe': + cls = 'orange'; + break; + case 'warning': + cls = 'yellow'; + break; + case 'ok': + cls = 'green'; + break; + case 'n/a': + cls = 'gray'; + break; + default: //'DISABLED', 'UNKNOWN', 'UNDEFINED' + cls = 'gray'; + break; + } + } + } else { + var reald = _.find(graphNodes, function (n) { + return n.id == d.id; + }); + + if (reald) { + var state = reald.vitrage_operational_state; + if (state) { + switch (state.toLowerCase()) { + case 'error': + cls = 'red'; + break; + case 'suboptimal': + cls = 'yellow'; + break; + case 'n/a': + cls = 'gray'; + break; + } + } + } + } + return cls; + }) + .style('font-size', function (d) { + var category = d.category || 'no_category', + icon_size; + + if (category && category.toLowerCase() === 'alarm') { + icon_size = '18px'; + } else { + var type = d.vitrage_type || 'no_type'; + switch (type.toLowerCase()) { + case 'nova.instance': + case 'nova.host': + case 'nova.zone': + case 'neutron.port': + icon_size = '16px'; //fa-external-link-square + break; + case 'openstack.cluster': + icon_size = '18px'; //fa-cloud + break; + case 'cinder.volume': + icon_size = '22px'; + break; + case 'neutron.network': + default: + icon_size = '20px'; + break; + } + } + return icon_size; + }) + .style('stroke', function (d) { + var category = d.category; + if (category && category.toLowerCase() === 'alarm') { + return '18px'; + } + return '20px'; + }) + .classed('icon', true) + .classed('fill-only', function (d) { + var type = (d.vitrage_type || '').toLowerCase(); + if (type && type === 'nova.host' || type === 'cinder.volume') { + return true; + } + }) + .text(function (d) { + var category = d.category, + icon; + if (category && category.toLowerCase() === 'alarm') { + icon = '\uf0f3'; //\uf0a2'; //bell-o + } else { + var type = d.vitrage_type || 'no_type'; + + switch (type.toLowerCase()) { + case 'nova.instance': + icon = '\uf108'; //fa-desktop + break; + case 'nova.host': + icon = '\uf233'; //fa-server + break; + case 'nova.zone': + icon = '\uf279'; //fa-map + break; + case 'neutron.network': + icon = '\uf0ac'; //fa-globe + break; + case 'neutron.port': + icon = '\uf14c'; //fa-external-link-square + break; + case 'cinder.volume': + icon = '\uf0a0'; //fa-hdd-o + break; + case 'openstack.cluster': + icon = '\uf0c2'; //fa-cloud + break; + case 'heat.stack': + icon = '\uf1b3'; //fa-cubes + break; + default: + console.warn('Vitrage type not supported: ' + d.vitrage_type); + icon = '\uf1b3'; //fa-cog + break; + } + } + return icon; + }); + } + + // we need to updated the selected info + if (_this.selectedItem && _this.selectedItem.vitrage_id && _this.selectedItem.vitrage_id !== '') { + for (var i = 0; i < data.nodes.length; i++) { + var val = data.nodes[i]; + if (val && val.vitrage_id && val.vitrage_id == _this.selectedItem.vitrage_id) { + _this.selectedItem = val; + break; + } + } + } + } + + /* utils */ + function rnd(min, max) { + return Math.round(Math.random() * (max - min)) + min; + } + + function onlyIn(a1, a2, prop) { + prop = prop || 'id'; + return a1.filter(function (o1) { + return a2.filter(function (o2) { + return o1[prop] === o2[prop]; + }).length === 0; + }) + } } - } })(); \ No newline at end of file diff --git a/vitrage_dashboard/dashboard/static/dashboard/project/entities/entities.html b/vitrage_dashboard/dashboard/static/dashboard/project/entities/entities.html index 8e26f63..ef1d253 100644 --- a/vitrage_dashboard/dashboard/static/dashboard/project/entities/entities.html +++ b/vitrage_dashboard/dashboard/static/dashboard/project/entities/entities.html @@ -7,7 +7,11 @@
- +
- \ No newline at end of file + diff --git a/vitrage_dashboard/dashboard/static/dashboard/project/entities/graph/entities-graph.directive.js b/vitrage_dashboard/dashboard/static/dashboard/project/entities/graph/entities-graph.directive.js index e352888..ec2606e 100644 --- a/vitrage_dashboard/dashboard/static/dashboard/project/entities/graph/entities-graph.directive.js +++ b/vitrage_dashboard/dashboard/static/dashboard/project/entities/graph/entities-graph.directive.js @@ -23,7 +23,7 @@ function hzEntitiesGraph() { var minZoom = 0.3, maxZoom = 4, linkWidth = 1, - circleRadius = 14, + circleRadius = 18, circlePadding = 1, zoom = d3.behavior.zoom().scaleExtent([minZoom, maxZoom]), ellipsisWidth = 80, @@ -361,7 +361,7 @@ function hzEntitiesGraph() { .attr('dominant-baseline', 'central') .attr('transform', 'scale(1)') .attr('class', function(d) { - var category = d.category, + var category = d.vitrage_category, cls = ''; if (category && category.toLowerCase() === 'alarm') { @@ -407,7 +407,7 @@ function hzEntitiesGraph() { return cls; }) .style('font-size', function(d) { - var category = d.category || 'no_category', + var category = d.vitrage_category || 'no_category', icon_size; if (category && category.toLowerCase() === 'alarm') { @@ -437,11 +437,11 @@ function hzEntitiesGraph() { return icon_size; }) .style('stroke', function(d) { - var category = d.category; + var category = d.vitrage_category; if (category && category.toLowerCase() === 'alarm') { - return '18px' + return '18px'; } - return '20px' + return '20px'; }) .classed('icon', true) .classed('fill-only', function(d) { @@ -451,7 +451,7 @@ function hzEntitiesGraph() { } }) .text(function(d) { - var category = d.category, + var category = d.vitrage_category, icon; if (category && category.toLowerCase() === 'alarm') { @@ -480,12 +480,15 @@ function hzEntitiesGraph() { case 'openstack.cluster': icon = '\uf0c2'; //fa-cloud break; + case 'heat.stack': + icon = '\uf1b3'; //fa-cubes + break; default: icon = '\uf013'; //fa-cog break; } } - return icon + return icon; }); content.append('text') @@ -493,7 +496,7 @@ function hzEntitiesGraph() { .attr('dx', '-18px') .attr('dy', '-12px') .text('\uf08d') - .on('click', pinNode) + .on('click', pinNode); var textNode = content.append('text') .classed('.label', true) @@ -510,7 +513,7 @@ function hzEntitiesGraph() { } d.width = 2 * (circleRadius + circlePadding) + (d.bbox ? d.bbox.width * 2 : 0); d.height = 2 * (circleRadius + circlePadding); - }) + }); }) .append('title') .text(function(d) { return d.name; }); @@ -576,7 +579,7 @@ function hzEntitiesGraph() { svg_g.selectAll('.link').classed('selected', function(d) { return d.source.high && d.target.high; - }) + }); } function selectNone(d) { @@ -697,7 +700,7 @@ function hzEntitiesGraph() { findNodes(node, depth, allNodes, linksMap); } else if (depth <= -1) { //Always find 'depth' + alarms & (sdns + alarms) - if (node.category.toLowerCase() === 'alarm') { + if (node && node.vitrage_category && node.vitrage_category.toLowerCase() === 'alarm') { node.high = true; node.highDepth = 0; } else if (!node.high && node.vitrage_type && node.vitrage_type.toLowerCase() === 'sdn_controller') { diff --git a/vitrage_dashboard/dashboard/static/dashboard/project/entities/info/entities-info.directive.js b/vitrage_dashboard/dashboard/static/dashboard/project/entities/info/entities-info.directive.js index 40c08cd..75d6cd3 100644 --- a/vitrage_dashboard/dashboard/static/dashboard/project/entities/info/entities-info.directive.js +++ b/vitrage_dashboard/dashboard/static/dashboard/project/entities/info/entities-info.directive.js @@ -16,5 +16,55 @@ function hzEntitiesInfo() { function link(scope, element, attrs) { scope.blackList = ['name', 'vitrage_is_deleted', 'vitrage_is_placeholder', 'index', 'graph_index', 'fixed', 'weight', 'px', 'py', 'x', 'y', 'width', 'height', 'bbox', 'high', 'highDepth']; + scope.parseItem = {}; + + // TODO: Order info by this priority + var topPriority = ['Vitrage resource type', 'Vitrage operational severity', 'Vitrage category', 'vitrage_aggregated_severity', 'vitrage_type', 'vitrage_operational_state'] + + scope.$watch('item', function (newData, oldData) { + if (newData !== undefined && newData != oldData) { + var tmpItem = copyObject(scope.item); + var itemParsed = {}; + + // 1. Replace _ with spaces + // 2. First letter uppercase + for (var property in tmpItem) { + if (scope.blackList.indexOf(property) < 0) { + if (tmpItem.hasOwnProperty(property)) { + var parsedProperty = ''; + parsedProperty= property.split("_").join(" "); + parsedProperty = parsedProperty.charAt(0).toUpperCase() + parsedProperty.substr(1).toLowerCase(); + itemParsed[parsedProperty] = tmpItem[property]; + } + } + } + + scope.parseItem = itemParsed; + } + }); + + function copyObject(orig, deep) { + // 1. copy has same prototype as orig + var copy = Object.create(Object.getPrototypeOf(orig)); + + // 2. copy has all of orig’s properties + copyOwnPropertiesFrom(copy, orig, deep); + + return copy; + } + + function copyOwnPropertiesFrom(target, source, deep) { + Object.getOwnPropertyNames(source) + .forEach(function(propKey) { + var desc = Object.getOwnPropertyDescriptor(source, propKey); + Object.defineProperty(target, propKey, desc); + if (deep && typeof desc.value === 'object') { + target[propKey] = copyObject(source[propKey], deep); + } + }); + return target; + } } + + } diff --git a/vitrage_dashboard/dashboard/static/dashboard/project/entities/info/entities-info.html b/vitrage_dashboard/dashboard/static/dashboard/project/entities/info/entities-info.html index 039fb75..b4b8185 100644 --- a/vitrage_dashboard/dashboard/static/dashboard/project/entities/info/entities-info.html +++ b/vitrage_dashboard/dashboard/static/dashboard/project/entities/info/entities-info.html @@ -1,10 +1,10 @@
-
- {$ 'name' | translate $}: - {$ item.name $} +
+ {$ 'name' | translate $}: + {$ parseItem.name $}
-
- {$ key | translate $}: - {$ value $} +
+ {$ key | translate $}: + {$ value $}
\ No newline at end of file diff --git a/vitrage_dashboard/dashboard/static/dashboard/project/entities/info/entities-info.scss b/vitrage_dashboard/dashboard/static/dashboard/project/entities/info/entities-info.scss index 54d2f92..3a9002a 100644 --- a/vitrage_dashboard/dashboard/static/dashboard/project/entities/info/entities-info.scss +++ b/vitrage_dashboard/dashboard/static/dashboard/project/entities/info/entities-info.scss @@ -2,13 +2,18 @@ position: absolute; margin: 12px; padding:6px 10px; - width: 240px; + width: 446px; min-height: 120px; max-height: 400px; border: 1px solid #ddd; border-radius: 4px; overflow: hidden; background: rgba(255, 255, 255, 0.8); + opacity: 0.7; + + &:hover { + opacity: 1; + } .info { margin: 0 0 1px 0; @@ -27,4 +32,12 @@ } } + .keys-font { + color: #428bca; + font-style: italic; + } + + .font-title { + font-size: 12px; + } } \ No newline at end of file diff --git a/vitrage_dashboard/dashboard/static/dashboard/project/entities/toolbox/entities-toolbox.directive.js b/vitrage_dashboard/dashboard/static/dashboard/project/entities/toolbox/entities-toolbox.directive.js index 927c616..7c2eb09 100644 --- a/vitrage_dashboard/dashboard/static/dashboard/project/entities/toolbox/entities-toolbox.directive.js +++ b/vitrage_dashboard/dashboard/static/dashboard/project/entities/toolbox/entities-toolbox.directive.js @@ -12,12 +12,17 @@ function hzEntitiesToolbox($rootScope) { scope: { item: '=', searchText: '=', - autoRefresh: '=' + autoRefresh: '=', + heats: '=', + selectedHeat: '=', + depthRange: '=' } }; return directive; function link(scope, element, attrs) { + scope.selectedHeat = 'all'; + scope.depthRange = 2; scope.autoRefresh = !!horizon.cookies.get('entitiesAutomaticRefresh'); console.log('Getting autoRefresh cookie: ', scope.autoRefresh); @@ -25,6 +30,6 @@ function hzEntitiesToolbox($rootScope) { scope.broadcast = function(event) { console.log('click', event); $rootScope.$broadcast('toolbox-' + event); - } + }; } } diff --git a/vitrage_dashboard/dashboard/static/dashboard/project/entities/toolbox/entities-toolbox.html b/vitrage_dashboard/dashboard/static/dashboard/project/entities/toolbox/entities-toolbox.html index b858973..14e89b2 100644 --- a/vitrage_dashboard/dashboard/static/dashboard/project/entities/toolbox/entities-toolbox.html +++ b/vitrage_dashboard/dashboard/static/dashboard/project/entities/toolbox/entities-toolbox.html @@ -1,5 +1,17 @@
+
+ +
+ +
+ + +
+
diff --git a/vitrage_dashboard/dashboard/static/dashboard/project/entities/toolbox/entities-toolbox.scss b/vitrage_dashboard/dashboard/static/dashboard/project/entities/toolbox/entities-toolbox.scss index 06959cb..56a1fdd 100644 --- a/vitrage_dashboard/dashboard/static/dashboard/project/entities/toolbox/entities-toolbox.scss +++ b/vitrage_dashboard/dashboard/static/dashboard/project/entities/toolbox/entities-toolbox.scss @@ -33,4 +33,9 @@ padding-left: 5px; padding-top: 2px; } + + .heats-filter { + width: 100%; + padding-bottom: 10px; + } } \ No newline at end of file diff --git a/vitrage_dashboard/dashboard/static/dashboard/project/services/vitrage_topology.service.js b/vitrage_dashboard/dashboard/static/dashboard/project/services/vitrage_topology.service.js index c3a19fa..9fca372 100644 --- a/vitrage_dashboard/dashboard/static/dashboard/project/services/vitrage_topology.service.js +++ b/vitrage_dashboard/dashboard/static/dashboard/project/services/vitrage_topology.service.js @@ -1,80 +1,81 @@ -(function() { - 'use strict'; +(function () { + 'use strict'; - angular - .module('horizon.dashboard.project.vitrage') - .service('vitrageTopologySrv', VitrageTopologySrv); + angular + .module('horizon.dashboard.project.vitrage') + .service('vitrageTopologySrv', VitrageTopologySrv); - VitrageTopologySrv.$inject = ['$http', '$injector']; + VitrageTopologySrv.$inject = ['$http', '$injector']; - function VitrageTopologySrv($http, $injector) { - var vitrageAPI; + function VitrageTopologySrv($http, $injector) { + var vitrageAPI; - if ($injector.has('horizon.app.core.openstack-service-api.vitrage')) { - vitrageAPI = $injector.get('horizon.app.core.openstack-service-api.vitrage'); + if ($injector.has('horizon.app.core.openstack-service-api.vitrage')) { + vitrageAPI = $injector.get('horizon.app.core.openstack-service-api.vitrage'); - } - function getTopology(graph_type,config,admin) { + } - if (vitrageAPI) { - return vitrageAPI.getTopology(graph_type,config,admin) - .success(function(data) { - return data; - }) - .error(function(err) { - console.error(err); + function getTopology(graph_type, config, admin) { + + if (vitrageAPI) { + return vitrageAPI.getTopology(graph_type, config, admin) + .success(function (data) { + return data; + }) + .error(function (err) { + console.error(err); + } + ); } - ) - } - } + } - function getAlarms(vitrage_id,admin) { + function getAlarms(vitrage_id, admin) { - if (vitrageAPI) { - return vitrageAPI.getAlarms(vitrage_id,admin) - .success(function(data) { - return data; - }) - .error(function(err) { - console.error(err); + if (vitrageAPI) { + return vitrageAPI.getAlarms(vitrage_id, admin) + .success(function (data) { + return data; + }) + .error(function (err) { + console.error(err); + } + ); } - ) - } - } + } - function getTemplates(template_id) { + function getTemplates(template_id) { - if (vitrageAPI) { - return vitrageAPI.getTemplates(template_id) - .success(function(data) { - return data; - }) - .error(function(err) { - console.error(err); + if (vitrageAPI) { + return vitrageAPI.getTemplates(template_id) + .success(function (data) { + return data; + }) + .error(function (err) { + console.error(err); + } + ); } - ) - } - } + } - function getRootCauseAnalysis(alarm_id,adminState) { - if (vitrageAPI) { - return vitrageAPI.getRca(alarm_id,adminState) - .success(function(data) { - return data; - }) - .error(function(err) { - console.error(err); + function getRootCauseAnalysis(alarm_id, adminState) { + if (vitrageAPI) { + return vitrageAPI.getRca(alarm_id, adminState) + .success(function (data) { + return data; + }) + .error(function (err) { + console.error(err); + } + ); } - ) - } - } + } - return { - getTopology: getTopology, - getAlarms: getAlarms, - getRootCauseAnalysis: getRootCauseAnalysis, - getTemplates: getTemplates + return { + getTopology: getTopology, + getAlarms: getAlarms, + getRootCauseAnalysis: getRootCauseAnalysis, + getTemplates: getTemplates + }; } - } })();