From 385f8ba1eca3963eaf9e2302291d40a502c6d56b Mon Sep 17 00:00:00 2001 From: Alon Heller Date: Mon, 6 Aug 2018 16:32:09 +0300 Subject: [PATCH] Alarm history - client side implementation Change-Id: Id60b01fbc2d33a984ee955089aebab0da01bdc60 Depends-On: I03a303e79ee12a399d32db3bfebad98eef50b52d --- requirements.txt | 8 +- vitrage_dashboard/api/vitrage.py | 45 ++++- vitrage_dashboard/api/vitrage_rest_api.py | 61 ++++++- .../openstack-service-api/vitrage.service.js | 30 ++-- .../project/alarmList/alarmList.controller.js | 163 ++++++++++++++---- .../project/alarmList/alarmList.html | 134 +++++++++++--- .../project/alarmList/alarmList.scss | 69 +++++++- .../project/services/modal.service.js | 2 +- .../services/vitrage_topology.service.js | 127 ++++++++------ .../static/dashboard/project/vitrage.scss | 1 + 10 files changed, 504 insertions(+), 136 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8052758..14b13ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 \ No newline at end of file diff --git a/vitrage_dashboard/api/vitrage.py b/vitrage_dashboard/api/vitrage.py index 14a0f09..3cccdde 100644 --- a/vitrage_dashboard/api/vitrage.py +++ b/vitrage_dashboard/api/vitrage.py @@ -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'): diff --git a/vitrage_dashboard/api/vitrage_rest_api.py b/vitrage_dashboard/api/vitrage_rest_api.py index 38901b5..795e767 100644 --- a/vitrage_dashboard/api/vitrage_rest_api.py +++ b/vitrage_dashboard/api/vitrage_rest_api.py @@ -83,11 +83,10 @@ class Topolgy(generic.View): class Alarms(generic.View): """API for vitrage alarms.""" - url_regex = r'vitrage/alarm/(?P.+|default)/' \ - '(?P.+|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 diff --git a/vitrage_dashboard/dashboard/static/app/core/openstack-service-api/vitrage.service.js b/vitrage_dashboard/dashboard/static/app/core/openstack-service-api/vitrage.service.js index c5c6254..0775455 100644 --- a/vitrage_dashboard/dashboard/static/app/core/openstack-service-api/vitrage.service.js +++ b/vitrage_dashboard/dashboard/static/app/core/openstack-service-api/vitrage.service.js @@ -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.')); + }); } diff --git a/vitrage_dashboard/dashboard/static/dashboard/project/alarmList/alarmList.controller.js b/vitrage_dashboard/dashboard/static/dashboard/project/alarmList/alarmList.controller.js index 1c77cfb..01ea1fd 100644 --- a/vitrage_dashboard/dashboard/static/dashboard/project/alarmList/alarmList.controller.js +++ b/vitrage_dashboard/dashboard/static/dashboard/project/alarmList/alarmList.controller.js @@ -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){ + 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; + } + + + 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.onRcaClick = function(alarm) { + 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() { - return alarm; - }} + 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(''); + }; } })(); diff --git a/vitrage_dashboard/dashboard/static/dashboard/project/alarmList/alarmList.html b/vitrage_dashboard/dashboard/static/dashboard/project/alarmList/alarmList.html index 57600cf..65f5925 100644 --- a/vitrage_dashboard/dashboard/static/dashboard/project/alarmList/alarmList.html +++ b/vitrage_dashboard/dashboard/static/dashboard/project/alarmList/alarmList.html @@ -1,40 +1,126 @@
-
- - -
-
- +
+ +
+
+ + + +
+ +
+ + + + + + + + + +
+
+ +
+ + + + + + +
+
+ +
+ +
- - - - - - - - - - - + + + + + + + + + + - - + + + + + - - + +
{$ 'TimeStamp' | translate $}{$ 'Name' | translate $}{$ 'Resource Type' | translate $}{$ 'Resource ID' | translate $}{$ 'Severity' | translate $}{$ 'Type' | translate $}{$ 'RCA' | translate $}
- - + + {$ 'Severity' | translate $} {$ + 'Started' | + translate $} + {$ 'Ended' | + translate $} + {$ 'Name' | translate + $} + {$ + 'Resource Type' | translate $} + {$ + 'Resource ID' | translate $} + {$ 'Type' | + translate $} + {$ 'RCA' | translate $}
{$alarm.update_timestamp | date:"yyyy-MM-dd HH:mm:ss"$} + + {$alarm.vitrage_aggregated_severity | + lowercase$} + {$alarm.start_timestamp | date:"yyyy-MM-dd + HH:mm:ss"$} + {$alarm.end_timestamp | date:"yyyy-MM-dd + HH:mm:ss"$} + {$alarm.name$} {$alarm.vitrage_resource_type$} {$alarm.vitrage_resource_id$}{$alarm.vitrage_aggregated_severity | lowercase$} {$alarm.vitrage_type$} +
diff --git a/vitrage_dashboard/dashboard/static/dashboard/project/alarmList/alarmList.scss b/vitrage_dashboard/dashboard/static/dashboard/project/alarmList/alarmList.scss index f7ee220..cc3e00e 100644 --- a/vitrage_dashboard/dashboard/static/dashboard/project/alarmList/alarmList.scss +++ b/vitrage_dashboard/dashboard/static/dashboard/project/alarmList/alarmList.scss @@ -1,9 +1,72 @@ .alarm-list { - .refreshBtn{ + .refreshBtn { text-align: right; } - .first-column { - padding-left: 5px; + .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; } } diff --git a/vitrage_dashboard/dashboard/static/dashboard/project/services/modal.service.js b/vitrage_dashboard/dashboard/static/dashboard/project/services/modal.service.js index 503cc66..33494eb 100644 --- a/vitrage_dashboard/dashboard/static/dashboard/project/services/modal.service.js +++ b/vitrage_dashboard/dashboard/static/dashboard/project/services/modal.service.js @@ -35,6 +35,6 @@ show: show, close: close, dismiss: dismiss - } + }; } })(); diff --git a/vitrage_dashboard/dashboard/static/dashboard/project/services/vitrage_topology.service.js b/vitrage_dashboard/dashboard/static/dashboard/project/services/vitrage_topology.service.js index 20f671a..fcf9405 100644 --- a/vitrage_dashboard/dashboard/static/dashboard/project/services/vitrage_topology.service.js +++ b/vitrage_dashboard/dashboard/static/dashboard/project/services/vitrage_topology.service.js @@ -1,61 +1,79 @@ (function () { - 'use strict'; + '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) { + function getTopology(graph_type, config, admin) { - if (vitrageAPI) { - return vitrageAPI.getTopology(graph_type, config, admin) - .then(function (data) { - return data; - }) - .catch(function (err) { - console.error(err); - } - ); + if (vitrageAPI) { + return vitrageAPI.getTopology(graph_type, config, admin) + .then(function (data) { + return data; + }) + .catch(function (err) { + console.error(err); } - } + ); + } + } - function getAlarms(vitrage_id, admin) { + function getAlarms(config) { - if (vitrageAPI) { - return vitrageAPI.getAlarms(vitrage_id, admin) - .then(function (data) { - return data; - }) - .catch(function (err) { - console.error(err); - } - ); + config = {params: config}; + + if (vitrageAPI) { + return vitrageAPI.getAlarms(config) + .then(function (data) { + return data; + }) + .catch(function (err) { + console.error(err); } - } + ); + } + } - function getTemplates(template_id) { + function getHistoryAlarms(config) { - if (vitrageAPI) { - return vitrageAPI.getTemplates(template_id) - .then(function (data) { - return data; - }) - .catch(function (err) { - console.error(err); - } - ); + config = {params: config}; + + if (vitrageAPI) { + return vitrageAPI.getHistoryAlarms(config) + .then(function (data) { + return data; + }) + .catch(function (err) { + console.error(err); } - } + ); + } + } + + function getTemplates(template_id) { + + if (vitrageAPI) { + return vitrageAPI.getTemplates(template_id) + .then(function (data) { + return data; + }) + .catch(function (err) { + console.error(err); + } + ); + } + } function deleteTemplate(template_id) { @@ -86,22 +104,23 @@ } - function getRootCauseAnalysis(alarm_id, adminState) { - if (vitrageAPI) { - return vitrageAPI.getRca(alarm_id, adminState) - .then(function (data) { - return data; - }) - .catch(function (err) { - console.error(err); - } - ); + function getRootCauseAnalysis(alarm_id, adminState) { + if (vitrageAPI) { + return vitrageAPI.getRca(alarm_id, adminState) + .then(function (data) { + return data; + }) + .catch(function (err) { + console.error(err); } - } + ); + } + } return { getTopology: getTopology, getAlarms: getAlarms, + getHistoryAlarms: getHistoryAlarms, getRootCauseAnalysis: getRootCauseAnalysis, getTemplates: getTemplates, deleteTemplate: deleteTemplate, diff --git a/vitrage_dashboard/dashboard/static/dashboard/project/vitrage.scss b/vitrage_dashboard/dashboard/static/dashboard/project/vitrage.scss index 2f93d0d..1b97fc4 100755 --- a/vitrage_dashboard/dashboard/static/dashboard/project/vitrage.scss +++ b/vitrage_dashboard/dashboard/static/dashboard/project/vitrage.scss @@ -1,3 +1,4 @@ + @import 'layout/main/compute/compute.scss'; @import 'alarmList/alarmList.scss'; @import 'components/sunburst/sunburst.scss';