Entities graph - added search to entities. Added checkbox to auto refresh

Change-Id: I81d5216a159991be632503e5fa0951437817382f
This commit is contained in:
Alon Heller 2017-04-27 13:47:45 +03:00
parent 0fe376fa05
commit b3c562b70e
7 changed files with 120 additions and 324 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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