Entities graph - added search to entities. Added checkbox to auto refresh
Change-Id: I81d5216a159991be632503e5fa0951437817382f
This commit is contained in:
parent
0fe376fa05
commit
b3c562b70e
@ -22,7 +22,7 @@ ADD_PANEL = 'vitrage_dashboard.dashboard.panel.TopologyAdminVitrage'
|
||||
|
||||
ADD_INSTALLED_APPS = ['vitrage_dashboard.admin_dashboard']
|
||||
|
||||
ADD_ANGULAR_MODULES = ['horizon.dashboard.project.admin_vitrage']
|
||||
ADD_ANGULAR_MODULES = ['horizon.dashboard.project.vitrage']
|
||||
|
||||
AUTO_DISCOVER_STATIC_FILES = True
|
||||
|
||||
|
@ -1,498 +1,243 @@
|
||||
(function () {
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
|
||||
|
||||
|
||||
angular
|
||||
|
||||
.module('horizon.dashboard.project.vitrage')
|
||||
|
||||
.controller('EntitiesController', EntitiesController);
|
||||
|
||||
EntitiesController.$inject = ['$scope', 'vitrageTopologySrv', '$interval', '$location'];
|
||||
|
||||
|
||||
|
||||
EntitiesController.$inject = ['$scope', 'vitrageTopologySrv', '$interval','$location'];
|
||||
|
||||
|
||||
|
||||
|
||||
function EntitiesController($scope, vitrageTopologySrv, $interval,$location) {
|
||||
|
||||
|
||||
|
||||
|
||||
//this.$interval = $interval;
|
||||
|
||||
function EntitiesController($scope, vitrageTopologySrv, $interval, $location) {
|
||||
this.model = {selected: {}};
|
||||
|
||||
|
||||
|
||||
|
||||
var _this = this,
|
||||
|
||||
loadTime = 1000,
|
||||
|
||||
loadTime = 5000,
|
||||
errorCount = 0,
|
||||
|
||||
loadInterval;
|
||||
|
||||
|
||||
|
||||
|
||||
$scope.$on('graphItemClicked',function (event, data){
|
||||
|
||||
_this.selectedItem = data;
|
||||
|
||||
event.stopPropagation();
|
||||
|
||||
$scope.$digest();
|
||||
|
||||
$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)
|
||||
|
||||
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();
|
||||
|
||||
loadInterval = $interval(loadData, mill);
|
||||
if ($scope.automaticRefresh) {
|
||||
loadInterval = $interval(loadData, mill);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function cancelNextLoad() {
|
||||
|
||||
$interval.cancel(loadInterval);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function mergeData(data) {
|
||||
|
||||
//temp mess with data
|
||||
|
||||
/*var nodeIndex = rnd(0, data.nodes.length - 1);
|
||||
|
||||
var nodeCount = (data.nodes.length - 1) - nodeIndex;
|
||||
|
||||
var linkIndex = rnd(0, data.nodes.length - 1);
|
||||
|
||||
var linkCount = (data.links.length - 1) - linkIndex;
|
||||
|
||||
data.nodes.splice(nodeIndex, nodeCount);
|
||||
|
||||
data.links.splice(linkIndex, linkCount);*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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);
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
_.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.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;
|
||||
|
||||
});
|
||||
|
||||
var state = reald.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.type || 'no_type';
|
||||
|
||||
switch(type.toLowerCase()) {
|
||||
|
||||
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.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.type || 'no_type';
|
||||
|
||||
switch(type.toLowerCase()) {
|
||||
|
||||
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
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// 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++) {
|
||||
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;
|
||||
@ -502,50 +247,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* utils */
|
||||
|
||||
|
||||
|
||||
|
||||
function rnd(min, max) {
|
||||
|
||||
return Math.round(Math.random() * (max- min)) + min;
|
||||
|
||||
return Math.round(Math.random() * (max - min)) + min;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//var old = [{a: 111}, {a: 222}, {a: 333}, {a: 444}];
|
||||
|
||||
//var newa = [{a:111}, {a: 444}, {a: 777}, {a: 999}];
|
||||
|
||||
//var onlyInOld = onlyIn(old, newa, 'a');
|
||||
|
||||
//var onlyInNew = onlyIn(newa, old, 'a');
|
||||
|
||||
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,8 +7,11 @@
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<hz-entities-info item="vm.selectedItem"></hz-entities-info>
|
||||
<hz-entities-toolbox></hz-entities-toolbox>
|
||||
<hz-entities-graph data="vm.graphData" selected="vm.model.selected" item-selected="vm.setSelected"></hz-entities-graph>
|
||||
<hz-entities-toolbox search-text="searchText" auto-refresh="automaticRefresh"></hz-entities-toolbox>
|
||||
<hz-entities-graph data="vm.graphData"
|
||||
selected="vm.model.selected"
|
||||
search-item="searchText"
|
||||
item-selected="vm.setSelected"></hz-entities-graph>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,7 +9,8 @@ function hzEntitiesGraph() {
|
||||
data: '=',
|
||||
selected: '=',
|
||||
itemSelected: '&',
|
||||
search: '='
|
||||
search: '=',
|
||||
searchItem: '='
|
||||
},
|
||||
templateUrl: STATIC_URL + 'dashboard/project/entities/graph/entities-graph.html',
|
||||
restrict: 'E'
|
||||
@ -51,6 +52,46 @@ function hzEntitiesGraph() {
|
||||
}
|
||||
})();
|
||||
|
||||
scope.$watch('searchItem', function(newData, oldData) {
|
||||
if (newData != oldData) {
|
||||
console.log('searching for node: ', newData);
|
||||
searchNode(newData);
|
||||
}
|
||||
});
|
||||
|
||||
function searchNode(value) {
|
||||
value = value.toLowerCase();
|
||||
var allNodes = d3.select("svg g").selectAll("g")
|
||||
.filter(function(d, i){ return this.classList.contains("node"); })
|
||||
.selectAll("circle");
|
||||
|
||||
//console.log('all nodes: ', allNodes.length);
|
||||
|
||||
allNodes
|
||||
.transition()
|
||||
.duration(200)
|
||||
.style("stroke", "darkgray")
|
||||
.style("stroke-width", "1")
|
||||
.style("fill", "#FFFFFF")
|
||||
.attr('r', function(item) { return 14; });
|
||||
|
||||
// Set special style to the found node
|
||||
if (value && value !== '') {
|
||||
var theNodes = d3.select("svg g").selectAll("g")
|
||||
.filter(function(d, i){ return this.classList.contains("node"); })
|
||||
.filter(function(d, i){ return d.name ? d.name.toLowerCase().indexOf(value) > -1 : ''; })
|
||||
.selectAll("circle");
|
||||
|
||||
theNodes
|
||||
.transition()
|
||||
.duration(400)
|
||||
.style("stroke", "#006600")
|
||||
.style("stroke-width", "3")
|
||||
.style("fill", "#51EFD2")
|
||||
.attr('r', function(item) { return 40; });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
scope.$watch('data.ts', function(newVal, oldVal) {
|
||||
if (newVal) {
|
||||
@ -282,6 +323,9 @@ function hzEntitiesGraph() {
|
||||
content = node
|
||||
.enter().append('g')
|
||||
.attr('class', 'node')
|
||||
.attr('name', function(d) {
|
||||
return d.name;
|
||||
})
|
||||
.classed('pinned', function(d) { return d.fixed; })
|
||||
.call(force.drag)
|
||||
.on('click', nodeClick)
|
||||
|
@ -10,12 +10,18 @@ function hzEntitiesToolbox($rootScope) {
|
||||
templateUrl: STATIC_URL + 'dashboard/project/entities/toolbox/entities-toolbox.html',
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
item: '='
|
||||
item: '=',
|
||||
searchText: '=',
|
||||
autoRefresh: '='
|
||||
}
|
||||
};
|
||||
return directive;
|
||||
|
||||
function link(scope, element, attrs) {
|
||||
|
||||
scope.autoRefresh = !!horizon.cookies.get('entitiesAutomaticRefresh');
|
||||
console.log('Getting autoRefresh cookie: ', scope.autoRefresh);
|
||||
|
||||
scope.broadcast = function(event) {
|
||||
console.log('click', event);
|
||||
$rootScope.$broadcast('toolbox-' + event);
|
||||
|
@ -1,12 +1,22 @@
|
||||
<div class="entities-toolbox">
|
||||
|
||||
<div class="input-group input-group-xs">
|
||||
<span class="input-group-addon search-icon">
|
||||
<span class="fa fa-search"></span>
|
||||
</span>
|
||||
<input type="text" ng-model="searchText" class="form-control search" placeholder="Search">
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-xs" role="group">
|
||||
<a href="#" class="btn btn-default" ng-click="broadcast('pin')"><span class="fa fa-thumb-tack"></span> Pin</a>
|
||||
<a href="#" class="btn btn-default" ng-click="broadcast('unpin')">Unpin</a>
|
||||
|
||||
<div class="themable-checkbox refreshBtn auto-refresh">
|
||||
<input type="checkbox" ng-model="autoRefresh" id="themable-checkbox">
|
||||
<label for="themable-checkbox" translate>Auto Refresh</label>
|
||||
</div>
|
||||
</div>
|
||||
<!--<div class="btn-group btn-group-xs" role="group">
|
||||
<a href="#" class="btn btn-default" ng-click="broadcast('zoom-to-fit')"><span class="fa fa-expand"></span> Zoom to fit</a>
|
||||
</div>
|
||||
<div class="btn-group btn-group-xs" role="group">
|
||||
<a href="#" class="btn btn-default" ng-click="broadcast('toggle-fullscreen')"><span class="fa fa-square-o"></span> Toggle fullscreen</a>
|
||||
</div>-->
|
||||
|
||||
|
||||
|
||||
</div>
|
@ -9,9 +9,28 @@
|
||||
|
||||
.btn-group {
|
||||
margin-right: 6px;
|
||||
margin-top: 10px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.search {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
.refreshBtn{
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.auto-refresh {
|
||||
float: right;
|
||||
padding-left: 5px;
|
||||
padding-top: 2px;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user