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
This commit is contained in:
parent
8be5a32a61
commit
e3caa58f24
@ -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'):
|
||||
|
@ -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
|
||||
|
@ -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.'));
|
||||
|
@ -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;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
@ -7,7 +7,11 @@
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<hz-entities-info item="vm.selectedItem"></hz-entities-info>
|
||||
<hz-entities-toolbox search-text="searchText" auto-refresh="automaticRefresh"></hz-entities-toolbox>
|
||||
<hz-entities-toolbox search-text="searchText"
|
||||
auto-refresh="automaticRefresh"
|
||||
heats="heats"
|
||||
selected-heat="selectedHeat"
|
||||
depth-range="depthRange"></hz-entities-toolbox>
|
||||
<hz-entities-graph data="vm.graphData"
|
||||
selected="vm.model.selected"
|
||||
search-item="searchText"
|
||||
|
@ -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') {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
<div class="entities-info" ng-show="item">
|
||||
<div class="info clearfix" ng-if="item.name">
|
||||
<span class="key pull-left ellipsis" title="{$ 'name' | translate $}">{$ 'name' | translate $}: </span>
|
||||
<span class="value name pull-left ellipsis" title="{$ item.name $}"> {$ item.name $}</span>
|
||||
<div class="info clearfix" ng-if="parseItem.name">
|
||||
<span class="key pull-left ellipsis font-title" title="{$ 'name' | translate $}">{$ 'name' | translate $}: </span>
|
||||
<span class="value name pull-left ellipsis" title="{$ parseItem.name $}"> {$ parseItem.name $}</span>
|
||||
</div>
|
||||
<div class="info clearfix" ng-if="blackList.indexOf(key) < 0" ng-repeat="(key, value) in item">
|
||||
<span class="key pull-left ellipsis" title="{$ key | translate $}">{$ key | translate $}: </span>
|
||||
<span class="value pull-left ellipsis" title="{$ value $}"> {$ value $}</span>
|
||||
<div class="info clearfix" ng-repeat="(key, value) in parseItem">
|
||||
<span class="key pull-left ellipsis keys-font font-title" title="{$ key | translate $}">{$ key | translate $}: </span>
|
||||
<span class="value pull-left ellipsis font-title" title="{$ value $}"> {$ value $}</span>
|
||||
</div>
|
||||
</div>
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,17 @@
|
||||
<div class="entities-toolbox">
|
||||
|
||||
<div class="input-group input-group-xs heats-filter">
|
||||
<select class="form-control" ng-model="selectedHeat">
|
||||
<option value="all">All Heat Stacks</option>
|
||||
<option ng-repeat="heat in heats track by $index" ng-value="heat.vitrageId">{{heat.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-xs heats-filter" ng-show="selectedHeat !== 'all'">
|
||||
<label translate>Detail Level</label>
|
||||
<input type="range" min="1" max="4" ng-value="4" ng-model="depthRange" class="slider">
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-xs">
|
||||
<span class="input-group-addon search-icon">
|
||||
<span class="fa fa-search"></span>
|
||||
|
@ -33,4 +33,9 @@
|
||||
padding-left: 5px;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.heats-filter {
|
||||
width: 100%;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
Loading…
Reference in New Issue
Block a user