Alarm history - client side implementation

Change-Id: Id60b01fbc2d33a984ee955089aebab0da01bdc60
Depends-On: I03a303e79ee12a399d32db3bfebad98eef50b52d
This commit is contained in:
Alon Heller 2018-08-06 16:32:09 +03:00 committed by Ifat Afek
parent ac002037a7
commit 385f8ba1ec
10 changed files with 504 additions and 136 deletions

View File

@ -6,5 +6,11 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0
# Horizon Core Requirements
django-compressor>=2.0 # MIT
iso8601>=0.1.11 # MIT
horizon>=14.0.0.0b1 # Apache-2.0
XStatic-Angular>=1.5.8.0 # MIT License
XStatic-Angular-Bootstrap>=2.2.0.0 # MIT License
XStatic-Bootstrap-Datepicker>=1.3.1.0 # Apache 2.0 License
XStatic-Bootstrap-SCSS>=3.3.7.1 # Apache 2.0 License
XStatic-Font-Awesome>=4.7.0.0 # SIL OFL 1.1 License, MIT License
XStatic-jQuery>=1.8.2.1 # MIT License
XStatic-smart-table>=1.4.13.2 # MIT License

View File

@ -62,9 +62,50 @@ def topology(request, query=None, graph_type='tree', all_tenants='false',
limit=limit)
def alarms(request, vitrage_id='all', all_tenants='false'):
def alarms(request, vitrage_id='all', all_tenants='false',
limit=1000,
sort_by=['start_timestamp', 'vitrage_id'],
sort_dirs=['asc', 'asc'],
filter_by=None,
filter_vals=None,
next_page=True,
marker=None
):
return vitrageclient(request).alarm.list(vitrage_id=vitrage_id,
all_tenants=all_tenants)
all_tenants=all_tenants,
limit=limit,
sort_by=sort_by,
sort_dirs=sort_dirs,
filter_by=filter_by,
filter_vals=filter_vals,
next_page=next_page,
marker=marker
)
def history(request, all_tenants='false',
start=None, end=None,
limit=1000,
sort_by=['start_timestamp', 'vitrage_id'],
sort_dirs=['asc', 'asc'],
filter_by=None,
filter_vals=None,
next_page=True,
marker=None
):
return vitrageclient(request).alarm.history(all_tenants=all_tenants,
start=start,
end=end,
limit=limit,
sort_by=sort_by,
sort_dirs=sort_dirs,
filter_by=filter_by,
filter_vals=filter_vals,
next_page=next_page,
marker=marker
)
def alarm_counts(request, all_tenants='false'):

View File

@ -83,11 +83,10 @@ class Topolgy(generic.View):
class Alarms(generic.View):
"""API for vitrage alarms."""
url_regex = r'vitrage/alarm/(?P<vitrage_id>.+|default)/' \
'(?P<all_tenants>.+|default)/$'
url_regex = r'vitrage/alarm/$'
@rest_utils.ajax()
def get(self, request, vitrage_id, all_tenants):
def get(self, request):
"""Get a single entity's alarm with the vitrage id.
The following get alarm may be passed in the GET
@ -96,8 +95,62 @@ class Alarms(generic.View):
The result is a alarms object.
"""
vitrage_id = request.GET.get('vitrage_id', 'all')
all_tenants = request.GET.get('all_tenants', False)
limit = request.GET.get('limit', 1000)
sort_by = request.GET.get('sort_by', ['start_timestamp', 'vitrage_id'])
sort_dirs = request.GET.get('sort_dirs', ['asc', 'asc'])
filter_by = request.GET.get('filter_by', None)
filter_vals = request.GET.get('filter_vals', None)
next_page = request.GET.get('next_page', True)
marker = request.GET.get('marker', None)
return vitrage.alarms(request, vitrage_id, all_tenants)
return vitrage.alarms(request, vitrage_id, all_tenants,
limit,
sort_by,
sort_dirs,
filter_by,
filter_vals,
next_page,
marker
)
@urls.register
class History(generic.View):
"""API for vitrage alarms history."""
url_regex = r'vitrage/history/$'
@rest_utils.ajax()
def get(self, request):
"""Get a list of alarms history based on the input parameters.
The following get alarm may be passed in the GET
The result is a alarms object.
"""
all_tenants = request.GET.get('all_tenants', False)
start = request.GET.get('start', None)
end = request.GET.get('end', None)
limit = request.GET.get('limit', 1000)
sort_by = request.GET.get('sort_by', ['start_timestamp', 'vitrage_id'])
sort_dirs = request.GET.get('sort_dirs', ['asc', 'asc'])
filter_by = request.GET.get('filter_by', None)
filter_vals = request.GET.get('filter_vals', None)
next_page = request.GET.get('next_page', True)
marker = request.GET.get('marker', None)
return vitrage.history(request, all_tenants,
start, end,
limit,
sort_by,
sort_dirs,
filter_by,
filter_vals,
next_page,
marker
)
@urls.register

View File

@ -16,6 +16,7 @@
var service = {
getTopology: getTopology,
getAlarms: getAlarms,
getHistoryAlarms: getHistoryAlarms,
getRca: getRca,
getTemplates: getTemplates,
deleteTemplate: deleteTemplate,
@ -26,7 +27,7 @@
///////////
// Topology
'/static/dashboard/project/topology/graph.sample.json'
///////////
function getTopology(graph_type, config,admin) {
config = config || {};
@ -45,20 +46,25 @@
});
}
function getAlarms(vitrage_id,adminState) {
if (vitrage_id == undefined){
vitrage_id = 'all';
}
var url = '/api/vitrage/alarm/' + vitrage_id;
if (adminState) {
url += '/true';
}else {
url += '/false';
}
return apiService.get(url)
function getAlarms(config) {
config = config || {};
var url = '/api/vitrage/alarm/';
return apiService.get(url, config)
.catch(function() {
toastService.add('error', gettext('Unable to fetch the Vitrage Alarms service.'));
});
}
function getHistoryAlarms(config) {
config = config || {};
var url = '/api/vitrage/history/';
return apiService.get(url, config)
.catch(function() {
toastService.add('error', gettext('Unable to fetch the Vitrage' +
' Alarms History service.'));
});
}

View File

@ -5,66 +5,159 @@
.module('horizon.dashboard.project.vitrage')
.controller('AlarmListController', AlarmListController);
AlarmListController.$inject = ['$scope', 'modalSrv', 'vitrageTopologySrv','$interval','$location'];
AlarmListController.$inject = ['$scope', 'modalSrv', 'vitrageTopologySrv', '$interval', '$location'];
function AlarmListController($scope, modalSrv, vitrageTopologySrv,$interval,$location) {
function AlarmListController($scope, modalSrv, vitrageTopologySrv, $interval, $location) {
var alarmList = this;
var LIMIT = horizon.cookies.get('API_RESULT_PAGE_SIZE') || 20;
var filterTimeout;
alarmList.alarms = [];
alarmList.ialarms = [];
alarmList.filterByField = {name: 'Filter By', filterValue: null};
alarmList.filterItems = [
{name: 'Filter By', filterValue: null},
{name: 'Severity', filterValue: 'vitrage_aggregated_severity'},
{name: 'Name', filterValue: 'name'},
{name: 'Resource Type', filterValue: 'vitrage_resource_type'},
{name: 'Resource ID', filterValue: 'vitrage_resource_id'},
{name: 'Alarm Type', filterValue: 'vitrage_type'}
];
alarmList.filterText = '';
alarmList.$interval = $interval;
alarmList.checkboxAutoRefresh = true;
$scope.STATIC_URL = STATIC_URL;
alarmList.alarms = [];
alarmList.alarmInterval;
alarmList.format = 'dd-MMMM-yyyy';
alarmList.dateOptions = {
dateDisabled: false,
formatYear: 'yy',
maxDate: new Date(),
startingDay: 1
};
alarmList.altInputFormats = ['M!/d!/yyyy'];
alarmList.fromDateTime = new Date();
alarmList.fromDateTime.setDate(alarmList.fromDateTime.getDate() - 30);
alarmList.fromDateTime.setMilliseconds(0);
getData();
startCollectData();
alarmList.sortByFieldName = '';
alarmList.sortOrder = '';
function startCollectData() {
if (angular.isDefined(alarmList.alarmInterval)) return;
alarmList.alarmInterval = alarmList.$interval(getData,10000);
}
alarmList.toDateTime = new Date();
alarmList.toDateTime.setMilliseconds(0);
function stopCollectData() {
if (angular.isDefined(alarmList.alarmInterval)) {
alarmList.$interval.cancel(alarmList.alarmInterval);
alarmList.alarmInterval = undefined;
}
}
$scope.$on('$destroy',function(){
alarmList.stopCollectData();
});
alarmList.nextEnabled = false;
alarmList.prevEnabled = false;
alarmList.autoRefreshChanged = function(){
if (alarmList.checkboxAutoRefresh){
getData();
startCollectData();
}else{
stopCollectData();
}
alarmList.radioModel = 'activeAlarms';
alarmList.open1 = function () {
alarmList.popup1.opened = true;
};
function getData() {
alarmList.popup1 = {
opened: false
};
alarmList.getHistoryData = function (nextPrev) {
if (nextPrev === 'next' && !alarmList.nextEnabled) {
return;
}
var url = $location.absUrl();
vitrageTopologySrv.getAlarms('all',url.indexOf('admin') != -1).then(function(result){
alarmList.alarms = result.data;
})
var config = {
vitrage_id: 'all',
admin: url.indexOf('admin') != -1,
limit: LIMIT
};
if (alarmList.radioModel === 'historyAlarms') {
config.start = alarmList.fromDateTime;
config.end = alarmList.toDateTime;
}
if (nextPrev !== '') {
if (nextPrev === 'next') {
config.next_page = true;
config.marker = alarmList.alarms.length > 0 ? alarmList.alarms[alarmList.alarms.length - 1].vitrage_id : 0;
} else if (nextPrev === 'prev') {
config.next_page = false;
config.marker = alarmList.alarms.length > 0 ? alarmList.alarms[0].vitrage_id : 0;
}
}
if (alarmList.sortByFieldName !== '') {
config.sort_by = [alarmList.sortByFieldName];
config.sort_dirs = [alarmList.sortOrder];
}
if (alarmList.filterByField && alarmList.filterByField.filterValue !== null && alarmList.filterText !== '') {
config.filter_by = [alarmList.filterByField.filterValue];
config.filter_vals = [alarmList.filterText];
} else {
config.filter_by = undefined;
config.filter_vals = undefined;
}
alarmList.onRcaClick = function(alarm) {
if (alarmList.radioModel === 'historyAlarms') {
vitrageTopologySrv.getHistoryAlarms(config).then(function (result) {
alarmList.alarms = result.data;
alarmList.nextEnabled = result.data.length === LIMIT;
alarmList.prevEnabled = true;
});
} else {
vitrageTopologySrv.getAlarms(config).then(function (result) {
alarmList.alarms = result.data;
alarmList.nextEnabled = result.data.length === LIMIT;
alarmList.prevEnabled = true;
});
}
};
alarmList.getHistoryData();
alarmList.sortBy = function (fieldName) {
if (fieldName === alarmList.sortByFieldName) {
alarmList.sortOrder = alarmList.sortOrder === 'asc' ? 'desc' : 'asc';
} else {
alarmList.sortOrder = 'asc';
}
alarmList.sortByFieldName = fieldName;
alarmList.getHistoryData();
};
alarmList.onRcaClick = function (alarm) {
var modalOptions = {
animation: true,
templateUrl: STATIC_URL + 'dashboard/project/components/rca/rcaContainer.html',
controller: 'RcaContainerController',
windowClass: 'app-modal-window',
resolve: {alarm: function() {
resolve: {
alarm: function () {
return alarm;
}}
}
}
};
modalSrv.show(modalOptions);
}
};
alarmList.onFilterChange = function() {
clearTimeout(filterTimeout);
var that = alarmList;
filterTimeout = setTimeout(function() {
that.getHistoryData('');
}, 500);
};
alarmList.onFilterFieldChange = function() {
alarmList.getHistoryData('');
};
}
})();

View File

@ -1,40 +1,126 @@
<div class="alarm-list" ng-controller="AlarmListController as alarmList">
<div class="themable-checkbox refreshBtn">
<input type="checkbox" ng-model="alarmList.checkboxAutoRefresh" id="themable-checkbox" ng-change="alarmList.autoRefreshChanged()">
<label for="themable-checkbox" translate>Auto Refresh</label>
</div>
<div class="panel panel-default" >
<table st-table='alarmList.ialarms' st-safe-src="alarmList.alarms" class="table-striped table-rsp table-detail modern" hz-table>
<div class="row">
<div class="col-lg-8">
<div class="btn-group">
<label class="btn btn-primary"
ng-model="alarmList.radioModel"
uib-btn-radio="'activeAlarms'"
ng-click="alarmList.getHistoryData('')">Active
Alarms</label>
<label class="btn btn-primary"
ng-model="alarmList.radioModel"
uib-btn-radio="'historyAlarms'">Alarm History</label>
</div>
<div ng-show="alarmList.radioModel === 'historyAlarms'" style="display: inline;">
<label>Started</label>
<input type="datetime-local" ng-model="alarmList.fromDateTime"/>
<label>Ended</label>
<input type="datetime-local" ng-model="alarmList.toDateTime"/>
<button class="btn btn-primary"
ng-click="alarmList.getHistoryData()">
Fetch Alarms
</button>
</div>
</div>
<div class="col-lg-4 prev-next-align">
<select class="form-control"
ng-options="item as item.name for item in alarmList.filterItems track by item.name"
ng-model="alarmList.filterByField"
ng-change="alarmList.onFilterFieldChange()">
</select>
<input type="text" ng-model="alarmList.filterText"
class="form-control"
ng-change="alarmList.onFilterChange()"
ng-disabled="alarmList.filterByField.filterValue === null">
<i title="Previous"
class="fa fa-angle-double-left prev-next-btn prev"
aria-hidden="true"
ng-click="alarmList.getHistoryData('prev')"
ng-show="alarmList.prevEnabled"></i>
<i title="Next" class="fa
fa-angle-double-right prev-next-btn"
aria-hidden="true"
ng-click="alarmList.getHistoryData('next')"></i>
</div>
</div>
<div class="panel panel-default">
<table st-table='alarmList.ialarms' st-safe-src="alarmList.alarms"
class="table-striped table-rsp table-detail modern alarm-table" hz-table>
<thead>
<tr>
<th st-sort="normalized_severity"></th>
<th st-sort="update_timestamp">{$ 'TimeStamp' | translate $}</th>
<th st-sort="name">{$ 'Name' | translate $}</th>
<th st-sort="resource_type">{$ 'Resource Type' | translate $}</th>
<th st-sort="vitrage_resource_id">{$ 'Resource ID' | translate $}</th>
<th st-sort="severity">{$ 'Severity' | translate $}</th>
<th st-sort="vitrage_type">{$ 'Type' | translate $}</th>
<th>{$ 'RCA' | translate $}</th>
</tr>
<tr>
<th colspan="7">
<hz-search-bar group-classes="input-group-sm"
icon-classes="fa-search">
</hz-search-bar>
<th class="column icon header"></th>
<th class="column header"
ng-click="alarmList.sortBy('vitrage_aggregated_severity')">
{$ 'Severity' | translate $}
</th>
<th class="column header"
ng-click="alarmList.sortBy('start_timestamp')">{$
'Started' |
translate $}
</th>
<th class="column header"
ng-click="alarmList.sortBy('end_timestamp')">{$ 'Ended' |
translate $}
</th>
<th class="column full-width header"
ng-click="alarmList.sortBy('name')">{$ 'Name' | translate
$}
</th>
<th class="column full-width header"
ng-click="alarmList.sortBy('vitrage_resource_type')">{$
'Resource Type' | translate $}
</th>
<th class="column header resource-column"
ng-click="alarmList.sortBy('vitrage_resource_id')">{$
'Resource ID' | translate $}
</th>
<th class="column header"
ng-click="alarmList.sortBy('vitrage_type')">{$ 'Type' |
translate $}
</th>
<th class="column icon header">{$ 'RCA' | translate $}</th>
<th class="column icon header"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="alarm in alarmList.ialarms track by $index">
<td title="{$ alarm.vitrage_aggregated_severity $}"><i class="fa first-column" ng-class="{'orange fa-exclamation-triangle': alarm.vitrage_operational_severity == 'SEVERE', 'yellow fa-exclamation-triangle': alarm.vitrage_operational_severity == 'WARNING', 'red fa-exclamation-circle': alarm.vitrage_operational_severity == 'CRITICAL', 'green fa-check': alarm.vitrage_operational_severity == 'OK', 'gray fa-circle-o-notch': alarm.vitrage_operational_severity == 'N/A'}"></i></td>
<td><i class="fa fa-clock-o"></i> {$alarm.update_timestamp | date:"yyyy-MM-dd HH:mm:ss"$} </td>
<td title="{$ alarm.vitrage_aggregated_severity $}"><i
class="fa first-column column icon"
ng-class="{'orange fa-exclamation-triangle': alarm.vitrage_operational_severity == 'SEVERE', 'yellow fa-exclamation-triangle': alarm.vitrage_operational_severity == 'WARNING', 'red fa-exclamation-circle': alarm.vitrage_operational_severity == 'CRITICAL', 'green fa-check': alarm.vitrage_operational_severity == 'OK', 'gray fa-circle-o-notch': alarm.vitrage_operational_severity == 'N/A'}"></i>
</td>
<td
ng-class="{'td-orange': alarm.vitrage_operational_severity == 'SEVERE', 'td-yellow': alarm.vitrage_operational_severity == 'WARNING', 'td-red': alarm.vitrage_operational_severity == 'CRITICAL', 'td-green': alarm.vitrage_operational_severity == 'OK', 'td-gray': alarm.vitrage_operational_severity == 'N/A'}">
{$alarm.vitrage_aggregated_severity |
lowercase$}
</td>
<td class="column">{$alarm.start_timestamp | date:"yyyy-MM-dd
HH:mm:ss"$}
</td>
<td class="column">{$alarm.end_timestamp | date:"yyyy-MM-dd
HH:mm:ss"$}
</td>
<td>{$alarm.name$}</td>
<td>{$alarm.vitrage_resource_type$}</td>
<td>{$alarm.vitrage_resource_id$}</td>
<td>{$alarm.vitrage_aggregated_severity | lowercase$}</td>
<td>{$alarm.vitrage_type$}</td>
<td ng-click="alarmList.onRcaClick(alarm)"><i class="fa fa-sitemap"></i></td>
<td><a class="btn btn-small btn-info" ng-click="alarmList.onRcaClick(alarm)">
<i class="fa fa-sitemap"></i></a></td>
<td></td>
</tr>
</tbody>
</table>

View File

@ -1,9 +1,72 @@
.alarm-list {
.refreshBtn{
.refreshBtn {
text-align: right;
}
.first-column {
.column {
width: 144px;
&.icon {
width: 24px;
padding-left: 5px;
}
&.resource-column {
width: 256px;
}
&.header {
background: #ebe6e6;
}
&.full-width {
width: unset;
}
}
.td-red {
background: #f67171 !important;
}
.td-orange {
background: #f6993c !important;
}
.td-yellow {
background: #f6f005 !important;
}
.td-green {
background: #3bf685 !important;
}
.td-gray {
background: rgba(243, 243, 243, 0.49) !important;
}
.prev-next-btn {
font-size: 24px;
cursor: pointer;
&.prev {
margin-right: 10px;
}
}
.prev-next-align {
text-align: right;
}
.form-control, .datepicker input {
width: 112px;
display: unset;
}
.panel-default {
margin-top: 10px;
}
.alarm-table tr {
line-height: 14px;
}
.btn-info {
padding: 2px 4px;
}
}

View File

@ -35,6 +35,6 @@
show: show,
close: close,
dismiss: dismiss
}
};
}
})();

View File

@ -29,10 +29,28 @@
}
}
function getAlarms(vitrage_id, admin) {
function getAlarms(config) {
config = {params: config};
if (vitrageAPI) {
return vitrageAPI.getAlarms(vitrage_id, admin)
return vitrageAPI.getAlarms(config)
.then(function (data) {
return data;
})
.catch(function (err) {
console.error(err);
}
);
}
}
function getHistoryAlarms(config) {
config = {params: config};
if (vitrageAPI) {
return vitrageAPI.getHistoryAlarms(config)
.then(function (data) {
return data;
})
@ -102,6 +120,7 @@
return {
getTopology: getTopology,
getAlarms: getAlarms,
getHistoryAlarms: getHistoryAlarms,
getRootCauseAnalysis: getRootCauseAnalysis,
getTemplates: getTemplates,
deleteTemplate: deleteTemplate,

View File

@ -1,3 +1,4 @@
@import 'layout/main/compute/compute.scss';
@import 'alarmList/alarmList.scss';
@import 'components/sunburst/sunburst.scss';