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:
Alon Heller 2017-09-13 12:37:49 +03:00
parent 8be5a32a61
commit e3caa58f24
13 changed files with 544 additions and 340 deletions

View File

@ -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'):

View File

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

View File

@ -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.'));

View File

@ -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;
})
}
}
}
})();

View File

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

View File

@ -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') {

View File

@ -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 origs 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;
}
}
}

View File

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

View File

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

View File

@ -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);
}
};
}
}

View File

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

View File

@ -33,4 +33,9 @@
padding-left: 5px;
padding-top: 2px;
}
.heats-filter {
width: 100%;
padding-bottom: 10px;
}
}

View File

@ -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
};
}
}
})();