Support add template with parameters in Vitrage UI

Change-Id: I626fd15ba36538f1a2194041ac739b1d6cc3ecf5
Story: 2004056
Task: 29571
This commit is contained in:
NoorYameen 2019-03-03 12:48:11 +00:00 committed by Noor Yameen
parent 5f61f8dcad
commit 94ead80eb5
13 changed files with 477 additions and 266 deletions

View File

@ -72,7 +72,6 @@ def alarms(request, vitrage_id='all', all_tenants='false',
next_page=True,
marker=None
):
return vitrageclient(request).alarm.list(vitrage_id=vitrage_id,
all_tenants=all_tenants,
limit=limit,
@ -95,7 +94,6 @@ def history(request, all_tenants='false',
next_page=True,
marker=None
):
return vitrageclient(request).alarm.history(all_tenants=all_tenants,
start=start,
end=end,
@ -133,9 +131,24 @@ def template_delete(request, template_id):
def template_add(request):
template = json.loads(request.body)
type = template.get('type')
params = template.get('params')
with tempfile.NamedTemporaryFile(suffix='.yaml') as temp:
temp.write(template.get('template'))
temp.flush()
temp.seek(0)
response = vitrageclient(request).template.add(temp.name, type)
response = vitrageclient(request).template.add(temp.name, type, params)
return response
def template_validate(request):
template = json.loads(request.body)
type = template.get('type')
params = template.get('params')
with tempfile.NamedTemporaryFile(suffix='.yaml') as temp:
temp.write(template.get('template'))
temp.flush()
temp.seek(0)
response = vitrageclient(request).template.validate(temp.name,
type,
params)
return response

View File

@ -13,6 +13,7 @@
# limitations under the License.
from django.views import generic
import json
import logging
from openstack_dashboard.api.rest import urls
from openstack_dashboard.api.rest import utils as rest_utils
@ -39,9 +40,6 @@ class Topolgy(generic.View):
"""
''' original default is graph '''
LOG.info("--------- reques --------------- %s", str(request))
graph_type = 'tree'
all_tenants = 'false'
root = None
@ -174,12 +172,14 @@ class Rca(generic.View):
@urls.register
class Templates(generic.View):
"""API for vitrage templates."""
url_regex = r'vitrage/template/(?P<template_id>.+|default)/$'
@rest_utils.ajax()
def get(self, request, template_id):
"""Get a single template with the vitrage id.
The following get template may be passed in the GET
@ -200,12 +200,15 @@ class Templates(generic.View):
@rest_utils.ajax()
def post(self, request, **kwargs):
"""Add a single template.
"""Add or validate a single template.
request.body holds template in format:
{template: template_string
type: template_type}
type: template_type
params: params}
"""
json_request = json.loads(request.body)
if json_request["method"] == 'validate':
return vitrage.template_validate(request)
return vitrage.template_add(request)

View File

@ -1,102 +1,111 @@
(function () {
'use strict';
'use strict';
angular
.module('horizon.app.core.openstack-service-api')
.factory('horizon.app.core.openstack-service-api.vitrage', vitrageAPI);
angular
.module('horizon.app.core.openstack-service-api')
.factory('horizon.app.core.openstack-service-api.vitrage', vitrageAPI);
vitrageAPI.$inject = [
'horizon.framework.util.http.service',
'horizon.framework.widgets.toast.service'
vitrageAPI.$inject = [
'horizon.framework.util.http.service',
'horizon.framework.widgets.toast.service'
];
];
function vitrageAPI(apiService, toastService) {
function vitrageAPI(apiService, toastService) {
var service = {
getTopology: getTopology,
getAlarms: getAlarms,
getHistoryAlarms: getHistoryAlarms,
getRca: getRca,
getTemplates: getTemplates,
deleteTemplate: deleteTemplate,
addTemplate: addTemplate
};
var service = {
getTopology: getTopology,
getAlarms: getAlarms,
getHistoryAlarms: getHistoryAlarms,
getRca: getRca,
getTemplates: getTemplates,
deleteTemplate: deleteTemplate,
validateTemplate: validateTemplate,
addTemplate: addTemplate
};
return service;
return service;
///////////
// Topology
///////////
///////////
// Topology
///////////
function getTopology(graph_type, config,admin) {
config = config || {};
function getTopology(graph_type, config, admin) {
config = config || {};
if (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.'));
});
}
if (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.'));
});
}
function getAlarms(config) {
config = config || {};
var url = '/api/vitrage/alarm/';
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.'));
});
}
return apiService.get(url, config)
.catch(function () {
toastService.add('error', gettext('Unable to fetch the Vitrage Alarms service.'));
});
}
function getHistoryAlarms(config) {
config = config || {};
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.'));
});
var url = '/api/vitrage/history/';
return apiService.get(url, config)
.catch(function () {
toastService.add('error', gettext('Unable to fetch the Vitrage'+
' Alarms History service.'));
});
}
function getRca(alarm_id, adminState) {
return apiService.get('/api/vitrage/rca/'+alarm_id+"/"+adminState)
.catch(function () {
toastService.add('error', gettext('Unable to fetch the Vitrage RCA service.'));
});
}
function getTemplates(template_id) {
return apiService.get('/api/vitrage/template/'+template_id)
.catch(function () {
toastService.add('error', gettext('Unable to fetch the Vitrage Templates service.'));
});
}
function deleteTemplate(template_id) {
return apiService.delete('/api/vitrage/template/'+template_id)
.catch(function () {
toastService.add('error', gettext('Unable to fetch the Vitrage Templates service.'));
});
}
function addTemplate(template, type, parameters) {
var temp = {'template': template, 'type': type, "params": parameters, "method": 'add'};
return apiService.post('/api/vitrage/template/default/', temp)
.catch(function () {
toastService.add('error', gettext('Unable to fetch the Vitrage Templates service.'));
});
}
function validateTemplate(template, type, parameters) {
var temp = {'template': template, 'type': type, "params": parameters, "method": 'validate'};
return apiService.post('/api/vitrage/template/default/', temp)
.catch(function () {
toastService.add('error', gettext('Unable to validate the Vitrage Templates service.'));
});
}
}
function getRca(alarm_id,adminState) {
return apiService.get('/api/vitrage/rca/'+alarm_id+"/"+adminState)
.catch(function () {
toastService.add('error', gettext('Unable to fetch the Vitrage RCA service.'));
});
}
function getTemplates(template_id) {
return apiService.get('/api/vitrage/template/'+template_id)
.catch(function () {
toastService.add('error', gettext('Unable to fetch the Vitrage Templates service.'));
});
}
function deleteTemplate(template_id) {
return apiService.delete('/api/vitrage/template/'+template_id)
.catch(function () {
toastService.add('error', gettext('Unable to fetch the Vitrage Templates service.'));
});
}
function addTemplate(template, type) {
var temp = { 'template' : template, 'type': type }
return apiService.post('/api/vitrage/template/default/', temp)
.catch(function () {
toastService.add('error', gettext('Unable to fetch the Vitrage Templates service.'));
});
}
}
}());

View File

@ -0,0 +1,143 @@
(function () {
'use strict';
angular
.module('horizon.dashboard.project.vitrage')
.controller('AddTemplateContainerController', AddTemplateContainerController);
AddTemplateContainerController.$inject = ['$scope', 'modalSrv', 'vitrageTopologySrv', '$rootScope'];
function AddTemplateContainerController($scope, modalSrv, vitrageTopologySrv, $rootScope) {
$scope.STATIC_URL = STATIC_URL;
$scope.status = {
isopen: false
};
$scope.version = "";
$scope.templateType = "";
$scope.parameters = [];
$scope.loading = false;
$scope.templateContent = null;
$scope.withParameters = false;
$scope.uploading = false;
$scope.addNewParameter = function () {
$scope.parameters.push({key: '', value: ''});
};
$scope.removeParameter = function (element) {
var index = $scope.parameters.indexOf(element);
if (index > -1) {
$scope.parameters.splice(index, 1);
}
};
$scope.submitModal = function () {
modalSrv.close();
};
$scope.closeModal = function () {
modalSrv.close();
};
$scope.uploadFile = function (file, errFile) {
if (file) {
var ending = file.name.split('.').pop();
if (ending !== 'yml' && ending !== 'yaml') {
horizon.toast.add("error", gettext("Invalid file type. Templates should be YAML files"));
delete file.name;
return;
}
$scope.file = file;
var r = new FileReader();
r.onload = function (e) {
$scope.uploading = true;
var tempVersion = JSON.stringify(e.target.result).match(/version: (\d+)/i);
$scope.withParameters = !!JSON.stringify(e.target.result).match("parameters:");
$scope.version = tempVersion ? tempVersion[1] : "1";
$scope.templateContent = e.target.result;
};
r.catch = function () {
$scope.uploading = false;
horizon.toast.add("error", gettext("Unable to read file"));
delete file.name;
}
try {
$scope.uploading = false;
r.readAsText(file);
} catch (error) {
$scope.uploading = false;
horizon.toast.add("error", gettext("Unable to read file"));
delete file.name;
return;
}
}
}
$scope.submit = function () {
$scope.loading = true;
var file = $scope.file;
var finalParameters = {};
if ($scope.version === '1') {
var e = document.getElementById("typeSelect");
var type = e.options[e.selectedIndex].text.toLowerCase();
if (type !== "standard" && type !== "definition" && type !== "equivalence") {
horizon.toast.add("error", gettext("Invalid type entered. Type is one of: standard, definition, equivalence"));
delete file.name;
$scope.loading = false;
$scope.closeModal();
return;
}
}
$scope.parameters.forEach(function (parameter) {
finalParameters[parameter.key] = parameter.value;
}
);
vitrageTopologySrv.validateTemplate($scope.templateContent, type, finalParameters).then(function (result) {
if (result.data.results[0]['status code'] !== 0) {
horizon.toast.add("error", gettext(result.data.results[0].message));
} else {
vitrageTopologySrv.addTemplate($scope.templateContent, type, finalParameters).then(function (result) {
if (result.data[0].status === 'ERROR') {
horizon.toast.add("error", gettext(result.data[0]['status details']));
} else {
$scope.loading = false;
$rootScope.$broadcast('autoRefresh');
}
})
.catch(function () {
$scope.loading = false;
horizon.toast.add("error", gettext("Unable to add template"));
return;
});
}
}).catch(function (reason) {
horizon.toast.add("error", gettext(reason));
})
$scope.closeModal();
}
}
})();

View File

@ -0,0 +1,66 @@
<div class="modal-content">
<div class="modal-header">
<a href="#" class="close" data-dismiss="modal" ng-click="closeModal()">
<span class="fa fa-times"></span>
</a>
<h3 class="modal-title" id="modal-title">Add New Template</h3>
</div>
<div ng-hide="loading" class="modal-body clearfix">
<div class="form-group">
<div>
<label class="testClass">Template File:</label>
<input class="btn btn-default "
type="file"
id="add-template"
ngf-select="uploadFile($file, $invalidFiles)"
ngf-max-height="1000"
accept=".yaml,.yml"
data-toggle="tooltip"
title="File type: YAML Max Size: 2MB"
ngf-max-size="1MB"/>
</div>
</div>
<div class="form-group" ng-if="version==='1'">
<label>Type:</label>
<select class="type-select" id="typeSelect">
<option selected="selected">Standard</option>
<option>Definition</option>
<option>Equivalence</option>
</select>
</div>
<div class="form-group" ng-if="version==='3'||version==='2'">
<label>Parameters:</label>
<button ng-click="addNewParameter()"><i class="fa fa-plus"></i></button>
<ul>
<li ng-repeat="parameter in parameters">
<label>Key :</label>
<input type="text" ng-model="parameter.key" ng-value="parameter.key">
<label>Value :</label>
<input type="text" ng-model="parameter.value" ng-value="parameter.value">
<button ng-click="removeParameter(parameter)"><i class="fa fa-minus"></i></button>
</li>
</ul>
</div>
</div>
<img ng-show="loading" id="spinner" ng-src="{$STATIC_URL$}dashboard/project/assets/spinner.gif"
class="template-spinner">
<div ng-hide="loading" class="modal-footer">
<button ng-disabled="!uploading" class="btn btn-primary" type="button" ng-click="submit()">Done</button>
</div>
</div>

View File

@ -1,14 +1,5 @@
/* ------ GLOBAL ------ */
.app-modal-window .modal-dialog{
height: 90%;
width: 90%;
}
.app-modal-window .modal-content{
height: 100%;
width: 100%;
opacity: 0.9;
}
/* ------ GLOBAL ------ */

View File

@ -4,7 +4,7 @@
background: #ffffff;
opacity: 0.8;
ul{
ul {
padding-left: 0;
list-style-type: none;
}
@ -21,16 +21,16 @@
.controls {
padding: 15px;
float: right;
font-size:30px;
font-size: 30px;
cursor: pointer;
}
.fa{
.fa {
display: inline;
}
.tabbed {
padding-left:1em;
padding-left: 1em;
}
.definition-block {
@ -51,6 +51,12 @@
pre {
color: #000;
}
}
.template-spinner {
margin-left: 45%;
margin-bottom: 5%;
}

View File

@ -1,6 +1,6 @@
<div class="template-container">
<div class="controls">
<i title="Close" class="fa fa-times" ng-click="templateList.closeModal()"></i>
<i title="Close" class="fa fa-times" ng-click="addTemplate.closeModal()"></i>
</div>
<div style="margin:15px">
<h2>Select template type:</h2>
@ -14,7 +14,7 @@
</select>
</div>
<div style="margin: 15px">
<button type="button" class="btn btn-primary" ng-click="templateList.submitModal()">
<button type="button" class="btn btn-primary" ng-click="addTemplate.submitModal()">
Submit
</button>
</div>

View File

@ -89,10 +89,10 @@
}
}
function addTemplate(template, type) {
function addTemplate(template, type, parameters) {
if (vitrageAPI) {
return vitrageAPI.addTemplate(template, type)
return vitrageAPI.addTemplate(template, type, parameters)
.then(function (data) {
return data;
})
@ -102,6 +102,19 @@
);
}
}
function validateTemplate(template, type, parameters) {
if (vitrageAPI) {
return vitrageAPI.validateTemplate(template, type, parameters)
.then(function (data) {
return data;
})
.catch(function (err) {
console.error(err);
}
);
}
}
function getRootCauseAnalysis(alarm_id, adminState) {
@ -124,6 +137,7 @@
getRootCauseAnalysis: getRootCauseAnalysis,
getTemplates: getTemplates,
deleteTemplate: deleteTemplate,
validateTemplate: validateTemplate,
addTemplate: addTemplate
};
}

View File

@ -1,157 +1,118 @@
(function () {
'use strict';
'use strict';
angular
.module('horizon.dashboard.project.vitrage')
.controller('TemplateListController', TemplateListController);
angular
.module('horizon.dashboard.project.vitrage')
.controller('TemplateListController', TemplateListController);
TemplateListController.$inject = ['$scope', '$interval', 'modalSrv', 'timeSrv', 'vitrageTopologySrv'];
TemplateListController.$inject = ['$scope', '$interval', 'modalSrv', 'timeSrv', 'vitrageTopologySrv'];
function TemplateListController($scope, $interval, modalSrv, timeSrv, vitrageTopologySrv)
{
var templateList = this;
templateList.templates = [];
templateList.itemplates = [];
$scope.STATIC_URL = STATIC_URL;
templateList.templates = [];
templateList.$interval = $interval;
templateList.checkboxAutoRefresh = true;
templateList.templateInterval;
templateList.timezone = timeSrv.getHorizonTimezone();
templateList.dateFormat = timeSrv.longDateFormat;
function TemplateListController($scope, $interval, modalSrv, timeSrv, vitrageTopologySrv) {
var templateList = this;
templateList.templates = [];
templateList.itemplates = [];
$scope.STATIC_URL = STATIC_URL;
templateList.templates = [];
templateList.$interval = $interval;
templateList.checkboxAutoRefresh = true;
templateList.templateInterval;
templateList.timezone = timeSrv.getHorizonTimezone();
templateList.dateFormat = timeSrv.longDateFormat;
var path = document.location.pathname;
$scope.admin = path.indexOf('admin') > 0;
getData();
startCollectData();
function startCollectData() {
if (angular.isDefined(templateList.templateInterval)) return;
templateList.templateInterval = templateList.$interval(getData,5000);
}
function stopCollectData() {
if (angular.isDefined(templateList.templateInterval)) {
templateList.$interval.cancel(templateList.templateInterval);
templateList.templateInterval = undefined;
}
}
$scope.$on('$destroy',function(){
templateList.stopCollectData();
});
templateList.autoRefreshChanged = function(){
if (templateList.checkboxAutoRefresh){
getData();
startCollectData();
}else{
stopCollectData();
}
};
templateList.refreshTemplates = function() {
getData();
}
templateList.closeModal = function() {
if(templateList.file){
delete templateList.file.name;
}
modalSrv.dismiss();
}
templateList.submitModal = function() {
modalSrv.close();
}
templateList.uploadFile = function(file, errFile) {
if (file) {
var ending = file.name.split('.').pop();
if(ending !== 'yml' && ending !== 'yaml') {
horizon.toast.add("error", gettext("Invalid file type. Templates should be YAML files"));
delete file.name;
return;
function startCollectData() {
if (angular.isDefined(templateList.templateInterval)) return;
templateList.templateInterval = templateList.$interval(getData, 5000);
}
var modalOptions = {
animation: true,
templateUrl: STATIC_URL + 'dashboard/project/components/templateAdd/templateAddOptions.html',
controller: 'TemplateListController',
controllerAs: 'templateList',
windowClass: 'modal-dialog-metadata',
resolve: {file: function() {
return file;
}}
};
templateList.file = file;
modalSrv.show(modalOptions).result.then(() => templateList.chooseType())
}
}
templateList.chooseType = function() {
var e = document.getElementById("typeSelect");
var type = e.options[e.selectedIndex].text.toLowerCase();
var file = templateList.file;
templateList.type = type;
if (type !== "standard" && type !== "definition" && type !== "equivalence") {
horizon.toast.add("error", gettext("Invalid type entered. Type is one of: standard, definition, equivalence"));
delete file.name;
return;
function stopCollectData() {
if (angular.isDefined(templateList.templateInterval)) {
templateList.$interval.cancel(templateList.templateInterval);
templateList.templateInterval = undefined;
}
}
var r = new FileReader();
r.onload = function(e) {
var content = e.target.result;
vitrageTopologySrv.addTemplate(content, type).then(function(result){
getData();
})
.catch(function(){
horizon.toast.add("error",gettext("Unable to add template"));
return;
});
}
r.catch = function() {
horizon.toast.add("error",gettext("Unable to read file"));
delete file.name;
}
try{
r.readAsText(file);
}
catch(error){
horizon.toast.add("error",gettext("Unable to read file"));
delete file.name;
return;
}
}
function getData() {
vitrageTopologySrv.getTemplates('all').then(function(result){
templateList.templates = result.data;
});
templateList.onShowClick = function(template) {
var modalOptions = {
animation: true,
templateUrl: STATIC_URL + 'dashboard/project/components/template/templateContainer.html',
controller: 'TemplateContainerController',
windowClass: 'app-modal-window',
resolve: {template: function() {
return template;
}}
};
modalSrv.show(modalOptions);
}
templateList.onDeleteClick = function(template) {
vitrageTopologySrv.deleteTemplate(template.uuid).then(function(result){
getData();
})
.catch(function(){
horizon.toast.add("error", gettext("Unable to delete template"));
$scope.$on('$destroy', function () {
templateList.stopCollectData();
});
}
templateList.autoRefreshChanged = function () {
if (templateList.checkboxAutoRefresh) {
getData();
startCollectData();
} else {
stopCollectData();
}
};
templateList.refreshTemplates = function () {
getData();
}
templateList.closeModal = function () {
if (templateList.file) {
delete templateList.file.name;
}
modalSrv.dismiss();
}
templateList.submitModal = function () {
modalSrv.close();
}
function getData() {
vitrageTopologySrv.getTemplates('all').then(function (result) {
templateList.templates = result.data;
});
templateList.onShowClick = function (template) {
var modalOptions = {
animation: true,
templateUrl: STATIC_URL + 'dashboard/project/components/template/templateContainer.html',
controller: 'TemplateContainerController',
windowClass: 'app-modal-window',
resolve: {
template: function () {
return template;
}
}
};
modalSrv.show(modalOptions);
}
templateList.onAddClick = function () {
var modalOptions = {
animation: true,
templateUrl: STATIC_URL + 'dashboard/project/components/addTemplate/addTemplateContainer.html',
controller: 'AddTemplateContainerController',
windowClass: 'app-modal-window'
};
modalSrv.show(modalOptions);
}
templateList.onDeleteClick = function (template) {
vitrageTopologySrv.deleteTemplate(template.uuid).then(function (result) {
getData();
})
.catch(function () {
horizon.toast.add("error", gettext("Unable to delete template"));
});
}
}
$scope.$on('autoRefresh', function () {
templateList.autoRefreshChanged();
console.log('inside ON');
}
);
}
}
})();

View File

@ -1,14 +1,10 @@
<div class="template-list" ng-controller="TemplateListController as templateList">
<div class="add-btn" >
<div class="add-btn">
<button class="btn btn-primary"
type="file"
id="add-template"
ngf-select="templateList.uploadFile($file, $invalidFiles)"
ngf-max-height="1000"
accept=".yaml,.yml"
data-toggle="tooltip"
title="File type: YAML Max Size: 2MB"
ngf-max-size="1MB">
ng-click="templateList.onAddClick()"
ng-if="admin"
>
Add Template
</button>
@ -17,11 +13,12 @@
ngf-max-height="1000"
data-toggle="tooltip"
title="Refresh template list"
>
>
</button>
<div class="themable-checkbox btn" style="float: right">
<input type="checkbox" ng-model="templateList.checkboxAutoRefresh" id="themable-checkbox" ng-change="alarmList.autoRefreshChanged()">
<input type="checkbox" ng-model="templateList.checkboxAutoRefresh" id="themable-checkbox"
ng-change="alarmList.autoRefreshChanged()">
<label for="themable-checkbox" translate>Auto Refresh</label>
</div>
@ -29,7 +26,8 @@
<div class="panel panel-default">
<table st-table='templateList.itemplates' st-safe-src="templateList.templates" class="table-striped table-rsp table-detail modern"
<table st-table='templateList.itemplates' st-safe-src="templateList.templates"
class="table-striped table-rsp table-detail modern"
hz-table>
<thead>
<tr>
@ -39,7 +37,7 @@
<th st-sort="details">{$ 'Details' | translate $}</th>
<th st-sort="timestamp">{$ 'Timestamp' | translate $}</th>
<th>{$ 'Show' | translate $}</th>
<th>{$ 'Delete' | translate $}</th>
<th ng-if="admin">{$ 'Delete' | translate $}</th>
</tr>
<tr>
<th colspan="5">
@ -55,9 +53,11 @@
<td>{$template.status$}</td>
<td>{$template.type$}</td>
<td>{$template["status details"]$}</td>
<td><i class="fa fa-clock-o"></i> {$template.date | vitrageDate:templateList.dateFormat:templateList.timezone$}</td>
<td><i class="fa fa-clock-o"></i> {$template.date |
vitrageDate:templateList.dateFormat:templateList.timezone$}
</td>
<td ng-click="templateList.onShowClick(template)"><i class="fa fa-list"></i></td>
<td ng-click="templateList.onDeleteClick(template)"><i class="fa fa-trash"></i></td>
<td ng-if="admin" ng-click="templateList.onDeleteClick(template)"><i class="fa fa-trash"></i></td>
</tr>
</tbody>
</table>

View File

@ -12,3 +12,8 @@
}
}
.modal-dialog {
top: 20%;
}