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 @@