Compound alarm expression
Commits provides ability to specify alarm expressions composed of multiple sub-expressions. Following features/changes are made: * responsive UI based on browser window size * rewrite expression widget to act as full-featured Angular directive * directive submits compiled expression into hidden input * any error in any of sub expressions form results in clearing the expression value and effectively prevents user from reaching next step * sub-expressions can be: ** added ** removed ** reorganized Change-Id: I1b4bd51e8887bc44dd3e2ffab4900d29fc77b77e
This commit is contained in:
parent
a181c1cc34
commit
58367e3e00
@ -60,7 +60,6 @@ class ExpressionWidget(forms.Widget):
|
|||||||
t = get_template(constants.TEMPLATE_PREFIX + 'expression_field.html')
|
t = get_template(constants.TEMPLATE_PREFIX + 'expression_field.html')
|
||||||
|
|
||||||
local_attrs = {
|
local_attrs = {
|
||||||
'service': '',
|
|
||||||
'func': ExpressionWidget.func,
|
'func': ExpressionWidget.func,
|
||||||
'comparators': ExpressionWidget.comparators,
|
'comparators': ExpressionWidget.comparators,
|
||||||
'operators': ExpressionWidget.operators,
|
'operators': ExpressionWidget.operators,
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.core import urlresolvers
|
from django.core import urlresolvers
|
||||||
|
from django.http import HttpResponse
|
||||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||||
|
|
||||||
from horizon import tables
|
from horizon import tables
|
||||||
@ -30,12 +31,11 @@ LOG = logging.getLogger(__name__)
|
|||||||
class CreateAlarm(tables.LinkAction):
|
class CreateAlarm(tables.LinkAction):
|
||||||
name = "create_alarm"
|
name = "create_alarm"
|
||||||
verbose_name = _("Create Alarm Definition")
|
verbose_name = _("Create Alarm Definition")
|
||||||
classes = ("ajax-modal",)
|
classes = ("ajax-modal", "btn-create")
|
||||||
icon = "plus"
|
icon = "plus"
|
||||||
policy_rules = (("alarm", "alarm:create"),)
|
policy_rules = (("alarm", "alarm:create"),)
|
||||||
ajax = True
|
ajax = True
|
||||||
|
|
||||||
|
|
||||||
def get_link_url(self):
|
def get_link_url(self):
|
||||||
return urlresolvers.reverse(constants.URL_PREFIX + 'alarm_create',
|
return urlresolvers.reverse(constants.URL_PREFIX + 'alarm_create',
|
||||||
args=())
|
args=())
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
{% block css %}
|
{% block css %}
|
||||||
{% include "_stylesheets.html" %}
|
{% include "_stylesheets.html" %}
|
||||||
<link href='{{ STATIC_URL }}monitoring/css/ng-tags-input.css' type="text/css" rel="stylesheet"/>
|
<link href='{{ STATIC_URL }}monitoring/css/ng-tags-input.css' type="text/css" rel="stylesheet"/>
|
||||||
<link href='{{ STATIC_URL }}monitoring/css/alarm-create.css' type="text/css" rel="stylesheet"/>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block page_header %}
|
{% block page_header %}
|
||||||
|
@ -1,95 +1,4 @@
|
|||||||
{% load i18n %}
|
<mon-alarm-expression metrics="{{ metrics|default:'[]' }}"
|
||||||
|
functions="{{ func }}"
|
||||||
{% block js %}{% spaceless %}
|
comparators="{{ comparators }}"
|
||||||
<script type="text/javascript">
|
operators="{{ operators }}"></mon-alarm-expression>
|
||||||
window._alarm_edit_ctrl_metrics = {{ metrics|safe|default:"[]" }}
|
|
||||||
</script>
|
|
||||||
{% endspaceless %}{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
<div class="alarm-expression">
|
|
||||||
|
|
||||||
<input type="hidden" name="{{ name }}" id="expression">
|
|
||||||
|
|
||||||
<div class="row expression-details">
|
|
||||||
<div class="col-md-2">
|
|
||||||
<select id="function"
|
|
||||||
class="form-control"
|
|
||||||
aria-label="{% trans 'Function' %}"
|
|
||||||
title="{% trans 'Function' %}"
|
|
||||||
ng-model="currentFunction"
|
|
||||||
ng-options="f[0] as f[1] for f in {{func}}"
|
|
||||||
ng-change="saveExpression()"></select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<select id="metric-chooser"
|
|
||||||
class="form-control"
|
|
||||||
aria-label="{% trans 'Metric' %}"
|
|
||||||
title="{% trans 'Metric' %}"
|
|
||||||
ng-model="currentMetric"
|
|
||||||
ng-options="metric for metric in metricNames"
|
|
||||||
ng-change="metricChanged()"></select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<select class="form-control"
|
|
||||||
aria-label="{% trans 'Comparator' %}"
|
|
||||||
title="{% trans 'Comparator' %}"
|
|
||||||
ng-model="currentComparator"
|
|
||||||
ng-options="f[0] as f[1] for f in {{comparators}}"
|
|
||||||
ng-change="saveExpression()"></select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<input type="number"
|
|
||||||
step="any"
|
|
||||||
class="form-control"
|
|
||||||
aria-label="{% trans 'Threshold' %}"
|
|
||||||
title="{% trans 'Threshold' %}"
|
|
||||||
ng-model="currentThreshold"
|
|
||||||
ng-change="saveExpression()"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row expression-details">
|
|
||||||
<div class="col-md-10">
|
|
||||||
<tags-input id="dimension-chooser"
|
|
||||||
ng-model="tags"
|
|
||||||
placeholder="{% trans 'Add a dimension' %}"
|
|
||||||
add-from-autocomplete-only="true"
|
|
||||||
on-tag-added="saveDimension()"
|
|
||||||
on-tag-removed="saveDimension()">
|
|
||||||
<auto-complete source="possibleDimensions($query)"
|
|
||||||
max-results-to-show="30"
|
|
||||||
min-length="1">
|
|
||||||
</auto-complete>
|
|
||||||
</tags-input>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<div class="form-group">
|
|
||||||
<label id="is-deterministic-expression"
|
|
||||||
class="btn expression-deterministic"
|
|
||||||
ng-class="{'btn-primary': currentIsDeterministic, 'btn-default': !currentIsDeterministic}"
|
|
||||||
ng-click="currentIsDeterministic = !currentIsDeterministic;saveExpression()"
|
|
||||||
ng-model="currentIsDeterministic">{% trans 'Deterministic' %}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row expression-details">
|
|
||||||
<div class="topologyBalloon" id="metrics" style="position:static;display: block;">
|
|
||||||
<div class="contentBody">
|
|
||||||
<table class="detailInfoTable">
|
|
||||||
<caption>Matching Metrics</caption>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th ng-repeat="name in dimnames">{$name$}</th>
|
|
||||||
</tr>
|
|
||||||
<tr ng-repeat="metric in matchingMetrics">
|
|
||||||
<td ng-repeat="dim in dimnames" style="white-space:normal">{$metric[dim] | spacedim $}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
<noscript><h3>{{ step }}</h3></noscript>
|
|
||||||
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
|
||||||
{{ step.get_help_text }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12 col-md-12 col-lg-12"
|
|
||||||
ng-controller="alarmEditController"
|
|
||||||
ng-init="init('{{ service }}')">
|
|
||||||
{% include "horizon/common/_form_fields.html" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,12 +1,15 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div>
|
<div ng-controller="alarmMatchByController as ctrl">
|
||||||
<input type="hidden" name="{{ name }}" id="id_{{ name }}"/>
|
<input type="hidden" name="{{ name }}" id="id_{{ name }}"/>
|
||||||
<tags-input id="dimkey-chooser" ng-model="matchByTags"
|
<tags-input id="dimkey-chooser"
|
||||||
|
ng-model="ctrl.matchByTags"
|
||||||
placeholder="{% trans 'Add a match by' %}"
|
placeholder="{% trans 'Add a match by' %}"
|
||||||
add-from-autocomplete-only="true"
|
add-from-autocomplete-only="true"
|
||||||
on-tag-added="saveDimKey()" on-tag-removed="saveDimKey()">
|
on-tag-added="ctrl.saveDimKey()"
|
||||||
<auto-complete source="possibleDimKeys($query)"
|
on-tag-removed="ctrl.saveDimKey()">
|
||||||
max-results-to-show="30" min-length="1">
|
<auto-complete source="ctrl.possibleDimKeys($query)"
|
||||||
|
max-results-to-show="30"
|
||||||
|
min-length="1">
|
||||||
</auto-complete>
|
</auto-complete>
|
||||||
</tags-input>
|
</tags-input>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,13 +2,14 @@
|
|||||||
|
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
<!-- hide if window gets very small to save some space -->
|
||||||
|
<div class="col-sm-12 col-md-12 col-lg-12 hidden-xs">
|
||||||
{{ step.get_help_text }}
|
{{ step.get_help_text }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||||
{% include "horizon/common/_form_fields.html" %}
|
{% include "horizon/common/_form_fields.html" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -82,7 +82,7 @@ class AlarmDefinitionsTest(helpers.TestCase):
|
|||||||
self.assertContains(res, '<select class="form-control" '
|
self.assertContains(res, '<select class="form-control" '
|
||||||
'id="id_severity"')
|
'id="id_severity"')
|
||||||
|
|
||||||
self.assertContains(res, '<input type="hidden" name="expression"')
|
self.assertContains(res, '<mon-alarm-expression')
|
||||||
|
|
||||||
self.assertContains(res, '<input type="hidden" name="alarm_actions"')
|
self.assertContains(res, '<input type="hidden" name="alarm_actions"')
|
||||||
self.assertContains(res, '<input type="hidden" name="ok_actions"')
|
self.assertContains(res, '<input type="hidden" name="ok_actions"')
|
||||||
|
@ -156,12 +156,13 @@ class SetAlarmDefinitionExpressionAction(workflows.Action):
|
|||||||
class SetDetailsStep(workflows.Step):
|
class SetDetailsStep(workflows.Step):
|
||||||
action_class = SetAlarmDefinitionAction
|
action_class = SetAlarmDefinitionAction
|
||||||
contributes = ('name', 'description', 'severity')
|
contributes = ('name', 'description', 'severity')
|
||||||
|
template_name = 'monitoring/alarmdefs/workflow_step.html'
|
||||||
|
|
||||||
|
|
||||||
class SetExpressionStep(workflows.Step):
|
class SetExpressionStep(workflows.Step):
|
||||||
action_class = SetAlarmDefinitionExpressionAction
|
action_class = SetAlarmDefinitionExpressionAction
|
||||||
contributes = ('expression', 'match_by')
|
contributes = ('expression', 'match_by')
|
||||||
template_name = 'monitoring/alarmdefs/expression_step.html'
|
template_name = 'monitoring/alarmdefs/workflow_step.html'
|
||||||
|
|
||||||
def contribute(self, data, context):
|
def contribute(self, data, context):
|
||||||
context = (super(SetExpressionStep, self)
|
context = (super(SetExpressionStep, self)
|
||||||
@ -181,7 +182,7 @@ class SetExpressionStep(workflows.Step):
|
|||||||
class SetNotificationsStep(workflows.Step):
|
class SetNotificationsStep(workflows.Step):
|
||||||
action_class = SetAlarmNotificationsAction
|
action_class = SetAlarmNotificationsAction
|
||||||
contributes = ('alarm_actions', 'ok_actions', 'undetermined_actions')
|
contributes = ('alarm_actions', 'ok_actions', 'undetermined_actions')
|
||||||
template_name = 'monitoring/alarmdefs/notification_step.html'
|
template_name = 'monitoring/alarmdefs/workflow_step.html'
|
||||||
|
|
||||||
|
|
||||||
class AlarmDefinitionWorkflow(workflows.Workflow):
|
class AlarmDefinitionWorkflow(workflows.Workflow):
|
||||||
|
@ -8,9 +8,15 @@ ADD_ANGULAR_MODULES = ['monitoringApp']
|
|||||||
|
|
||||||
# A list of javascript files to be included for all pages
|
# A list of javascript files to be included for all pages
|
||||||
ADD_JS_FILES = ['monitoring/js/app.js',
|
ADD_JS_FILES = ['monitoring/js/app.js',
|
||||||
|
'monitoring/js/filters.js',
|
||||||
'monitoring/js/controllers.js',
|
'monitoring/js/controllers.js',
|
||||||
|
'monitoring/js/directives.js',
|
||||||
|
'monitoring/js/services.js',
|
||||||
'monitoring/js/ng-tags-input.js']
|
'monitoring/js/ng-tags-input.js']
|
||||||
|
|
||||||
|
ADD_SCSS_FILES = [
|
||||||
|
'monitoring/css/alarm-create.scss']
|
||||||
|
|
||||||
from monascaclient import exc
|
from monascaclient import exc
|
||||||
# A dictionary of exception classes to be added to HORIZON['exceptions'].
|
# A dictionary of exception classes to be added to HORIZON['exceptions'].
|
||||||
ADD_EXCEPTIONS = {
|
ADD_EXCEPTIONS = {
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
.alarm-expression .row {
|
|
||||||
margin-left: -5px;
|
|
||||||
margin-right: -5px;
|
|
||||||
}
|
|
||||||
.alarm-expression .expression-details {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
.alarm-expression .expression-details > div {
|
|
||||||
padding-left: 2px;
|
|
||||||
padding-right: 2px;
|
|
||||||
}
|
|
||||||
|
|
60
monitoring/static/monitoring/css/alarm-create.scss
Normal file
60
monitoring/static/monitoring/css/alarm-create.scss
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
.alarm-expression {
|
||||||
|
|
||||||
|
.row {
|
||||||
|
margin-left: -5px;
|
||||||
|
margin-right: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre.expression-preview {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
white-space: -moz-pre-wrap;
|
||||||
|
white-space: -pre-wrap;
|
||||||
|
white-space: -o-pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre.expression-valid {
|
||||||
|
border-color: #00B700;
|
||||||
|
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
pre.expression-invalid {
|
||||||
|
border-color: #A94442;
|
||||||
|
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-sub-expression {
|
||||||
|
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.sub-expression-preview {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expression-details {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
|
||||||
|
div {
|
||||||
|
padding-left: 2px;
|
||||||
|
padding-right: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
|
||||||
|
.has-error {
|
||||||
|
border-color: #a94442;
|
||||||
|
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,19 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// Declare app level module which depends on filters, and services
|
// Declare app level module which depends on filters, and services
|
||||||
angular.module('monitoringApp', [
|
angular
|
||||||
'monitoring.controllers', 'ngTagsInput', 'monitoring.filters'
|
.module('monitoringApp', [
|
||||||
]);
|
'monitoring.controllers',
|
||||||
|
'monitoring.directives',
|
||||||
|
'monitoring.filters',
|
||||||
|
'monitoring.services',
|
||||||
|
'ngTagsInput'
|
||||||
|
])
|
||||||
|
.config(config);
|
||||||
|
|
||||||
|
config.$inject = ['$provide', '$windowProvider'];
|
||||||
|
|
||||||
|
function config($provide, $windowProvider) {
|
||||||
|
var path = $windowProvider.$get().STATIC_URL + 'monitoring/widgets/';
|
||||||
|
$provide.constant('monitoringApp.staticPath', path);
|
||||||
|
}
|
||||||
|
@ -127,173 +127,63 @@ angular.module('monitoring.controllers', [])
|
|||||||
};
|
};
|
||||||
|
|
||||||
}])
|
}])
|
||||||
.controller('alarmEditController', [
|
.controller('alarmNotificationFieldController',
|
||||||
"$window", "$scope", "$http", "$timeout", "$q",
|
['$rootScope', NotificationField]
|
||||||
function ($window, $scope, $http, $timeout, $q) {
|
)
|
||||||
|
.controller('alarmMatchByController',
|
||||||
|
['$q', '$rootScope', MatchByController]
|
||||||
|
);
|
||||||
|
|
||||||
$scope.metrics = [];
|
function MatchByController($q, $rootScope) {
|
||||||
$scope.metricNames = []
|
// model
|
||||||
$scope.currentMetric = undefined;
|
var vm = this;
|
||||||
|
|
||||||
$scope.currentFunction = "max";
|
vm.matchBy = [];
|
||||||
$scope.currentComparator = ">";
|
vm.matchByTags = [];
|
||||||
$scope.currentThreshold = 0;
|
|
||||||
$scope.currentIsDeterministic = false;
|
|
||||||
$scope.matchingMetrics = [];
|
|
||||||
$scope.tags = [];
|
|
||||||
$scope.matchByTags = [];
|
|
||||||
|
|
||||||
$scope.possibleDimensions = function(query) {
|
// api
|
||||||
return $q(function(resolve, reject) {
|
vm.saveDimKey = saveDimKey;
|
||||||
var dim = {}
|
vm.possibleDimKeys = possibleDimKeys;
|
||||||
var dimList = []
|
|
||||||
angular.forEach($scope.matchingMetrics, function(value, name) {
|
function possibleDimKeys(query) {
|
||||||
for (var key in value.dimensions) {
|
return $q(function(resolve, reject) {
|
||||||
if (value.dimensions.hasOwnProperty(key)) {
|
var dimList = [];
|
||||||
var dimStr = key + "=" + value.dimensions[key]
|
angular.forEach(vm.matchBy, function(value) {
|
||||||
if (dimStr.indexOf(query) === 0) {
|
if (value.indexOf(query) === 0) {
|
||||||
dim[dimStr] = dimStr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
angular.forEach(dim, function(value, name) {
|
|
||||||
dimList.push(value);
|
dimList.push(value);
|
||||||
});
|
|
||||||
resolve(dimList);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.possibleDimKeys = function(query) {
|
|
||||||
return $q(function(resolve, reject) {
|
|
||||||
var dimList = []
|
|
||||||
angular.forEach($scope.matchingMetrics, function(value, name) {
|
|
||||||
for (var key in value.dimensions) {
|
|
||||||
if (key.indexOf(query) === 0) {
|
|
||||||
if (dimList.indexOf(key) < 0) {
|
|
||||||
dimList.push(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
resolve(dimList);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.metricChanged = function() {
|
|
||||||
if ($scope.defaultTag.length > 0) {
|
|
||||||
$scope.tags = [{text: $scope.defaultTag}];
|
|
||||||
}
|
|
||||||
$scope.saveDimension();
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.saveExpression = function() {
|
|
||||||
$('#expression').val($scope.formatDimension());
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.saveDimension = function() {
|
|
||||||
$scope.saveExpression();
|
|
||||||
|
|
||||||
var mm = []
|
|
||||||
angular.forEach($scope.metrics, function(value, key) {
|
|
||||||
if (value.name === $scope.currentMetric) {
|
|
||||||
var match = true;
|
|
||||||
for (var i = 0; i < $scope.tags.length; i++) {
|
|
||||||
var vals = $scope.tags[i]['text'].split('=');
|
|
||||||
if (value.dimensions[vals[0]] !== vals[1]) {
|
|
||||||
match = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (match) {
|
|
||||||
mm.push(value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$scope.matchingMetrics = mm
|
resolve(dimList);
|
||||||
$scope.dimnames = ['name', 'dimensions'];
|
});
|
||||||
$('#match').val($scope.formatMatchBy());
|
}
|
||||||
|
|
||||||
|
function saveDimKey() {
|
||||||
|
var matchByTags = []
|
||||||
|
for (var i = 0; i < vm.matchByTags.length; i++) {
|
||||||
|
matchByTags.push(vm.matchByTags[i]['text'])
|
||||||
|
}
|
||||||
|
$('#id_match_by').val(matchByTags.join(','));
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
$rootScope.$on('$destroy', (function() {
|
||||||
|
|
||||||
|
var watcher = $rootScope.$on('mon_match_by_changed', onMatchByChange);
|
||||||
|
|
||||||
|
return function destroyer() {
|
||||||
|
watcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.saveDimKey = function() {
|
function onMatchByChange(event, matchBy) {
|
||||||
var matchByTags = []
|
// remove from tags those match by that do not match
|
||||||
for (var i = 0; i < $scope.matchByTags.length; i++) {
|
vm.matchByTags = vm.matchByTags.filter(function filter(tag){
|
||||||
matchByTags.push($scope.matchByTags[i]['text'])
|
return matchBy.indexOf(tag['text']) >= 0;
|
||||||
}
|
|
||||||
$('#id_match_by').val(matchByTags.join(','));
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.formatDimension = function() {
|
|
||||||
var dim = '';
|
|
||||||
angular.forEach($scope.tags, function(value, key) {
|
|
||||||
if (dim.length) {
|
|
||||||
dim += ',';
|
|
||||||
}
|
|
||||||
dim += value['text'];
|
|
||||||
})
|
|
||||||
return $scope.currentFunction
|
|
||||||
+ '('
|
|
||||||
+ $scope.currentMetric
|
|
||||||
+ '{' + dim + '}'
|
|
||||||
+ ($scope.currentIsDeterministic ? ',deterministic' : '')
|
|
||||||
+ ') '
|
|
||||||
+ $scope.currentComparator
|
|
||||||
+ ' '
|
|
||||||
+ $scope.currentThreshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.formatMatchBy = function() {
|
|
||||||
var dimNames = {}
|
|
||||||
for (var i = 0; i < $scope.matchingMetrics.length; i++) {
|
|
||||||
for (var attrname in $scope.matchingMetrics[i].dimensions) { dimNames[attrname] = true; }
|
|
||||||
}
|
|
||||||
var matches = [];
|
|
||||||
for (var attrname in dimNames) { matches.push(attrname); }
|
|
||||||
return matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.init = function(defaultTag) {
|
|
||||||
|
|
||||||
if (defaultTag.length > 0) {
|
|
||||||
$scope.tags = [{text: defaultTag}];
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.defaultTag = defaultTag;
|
|
||||||
|
|
||||||
metrics = $window._alarm_edit_ctrl_metrics
|
|
||||||
|
|
||||||
$scope.metrics = metrics && metrics.length ? metrics : [];
|
|
||||||
$scope.metricNames = uniqueNames($scope.metrics, 'name');
|
|
||||||
$scope.currentMetric = $scope.metricNames[0];
|
|
||||||
|
|
||||||
$scope.saveDimension();
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.$on('$destroy', (function() {
|
|
||||||
var detWatcher = $scope.$watch('currentIsDeterministic', function detWatcher(newValue, oldValue) {
|
|
||||||
if(newValue != oldValue){
|
|
||||||
$scope.$emit('mon_deterministic_changed', newValue);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return function() {
|
vm.matchBy = matchBy || [];
|
||||||
// destroy watchers
|
|
||||||
detWatcher();
|
|
||||||
}
|
|
||||||
}()));
|
|
||||||
|
|
||||||
function uniqueNames(input, key) {
|
|
||||||
var unique = {};
|
|
||||||
var uniqueList = [];
|
|
||||||
for(var i = 0; i < input.length; i++){
|
|
||||||
if(typeof unique[input[i][key]] == "undefined"){
|
|
||||||
unique[input[i][key]] = "";
|
|
||||||
uniqueList.push(input[i][key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return uniqueList.sort();
|
|
||||||
}
|
}
|
||||||
}])
|
|
||||||
.controller('alarmNotificationFieldController', NotificationField);
|
}()));
|
||||||
|
}
|
||||||
|
|
||||||
function NotificationField($rootScope) {
|
function NotificationField($rootScope) {
|
||||||
|
|
||||||
@ -316,11 +206,18 @@ function NotificationField($rootScope) {
|
|||||||
data.forEach(prepareNotify);
|
data.forEach(prepareNotify);
|
||||||
};
|
};
|
||||||
vm.add = function(){
|
vm.add = function(){
|
||||||
if(vm.select.model){
|
var opt;
|
||||||
vm.list.push(allOptions[vm.select.model]);
|
if (vm.select.model) {
|
||||||
|
opt = allOptions[vm.select.model];
|
||||||
|
|
||||||
|
oldUndetermined[opt.id] = opt.undetermined;
|
||||||
|
opt.undetermined = !vm.isDeterministic;
|
||||||
|
|
||||||
|
vm.list.push(opt);
|
||||||
|
|
||||||
removeFromSelect();
|
removeFromSelect();
|
||||||
vm.select.model = null;
|
vm.select.model = undefined;
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
vm.remove = function(id){
|
vm.remove = function(id){
|
||||||
@ -337,7 +234,7 @@ function NotificationField($rootScope) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$rootScope.$on('mon_deterministic_changed', onDeterministicChange)
|
$rootScope.$on('mon_deterministic_changed', onDeterministicChange);
|
||||||
|
|
||||||
function prepareNotify(item){
|
function prepareNotify(item){
|
||||||
var selected = item[7]
|
var selected = item[7]
|
||||||
@ -371,9 +268,7 @@ function NotificationField($rootScope) {
|
|||||||
|
|
||||||
function onDeterministicChange(event, isDeterministic) {
|
function onDeterministicChange(event, isDeterministic) {
|
||||||
|
|
||||||
if (!(vm.list && vm.list.length)) {
|
if (isDeterministic === vm.isDeterministic) {
|
||||||
return;
|
|
||||||
} else if (isDeterministic === vm.isDeterministic) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,14 +288,3 @@ function NotificationField($rootScope) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationField.$inject = ['$rootScope'];
|
|
||||||
|
|
||||||
angular.module('monitoring.filters', [])
|
|
||||||
.filter('spacedim', function () {
|
|
||||||
return function(text) {
|
|
||||||
if (typeof text == "string")
|
|
||||||
return text;
|
|
||||||
return JSON.stringify(text).split(',').join(', ');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
366
monitoring/static/monitoring/js/directives.js
Normal file
366
monitoring/static/monitoring/js/directives.js
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 FUJITSU LIMITED
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
* in compliance with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||||
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||||
|
* or implied. See the License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('monitoring.directives', [
|
||||||
|
'horizon.framework.widgets',
|
||||||
|
'monitoring.filters',
|
||||||
|
'monitoring.services',
|
||||||
|
'gettext'
|
||||||
|
])
|
||||||
|
.directive('monAlarmExpression',
|
||||||
|
['monitoringApp.staticPath', monAlarmExpressionsDirective]
|
||||||
|
)
|
||||||
|
.directive('monAlarmSubExpression',
|
||||||
|
['monitoringApp.staticPath', monAlarmSubExpressionDirective]
|
||||||
|
);
|
||||||
|
|
||||||
|
function monAlarmExpressionsDirective(staticPath){
|
||||||
|
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
scope: {
|
||||||
|
metrics: '=metrics',
|
||||||
|
functions: '=functions',
|
||||||
|
operators: '=operators',
|
||||||
|
comparators: '=comparators',
|
||||||
|
connectable: '=connectable'
|
||||||
|
},
|
||||||
|
templateUrl: staticPath + 'expression/expression.tpl.html',
|
||||||
|
controller: ['$q', '$scope', 'monExpressionBuilder', AlarmExpressionController],
|
||||||
|
controllerAs: 'vm',
|
||||||
|
bindToController: true
|
||||||
|
};
|
||||||
|
|
||||||
|
function AlarmExpressionController($q, $scope, monExpressionBuilder) {
|
||||||
|
// private
|
||||||
|
var vm = this,
|
||||||
|
deterministic = false,
|
||||||
|
matchBy = undefined;
|
||||||
|
|
||||||
|
// scope
|
||||||
|
vm.expression = '';
|
||||||
|
vm.subExpressions = undefined;
|
||||||
|
vm.expressionValid = true;
|
||||||
|
|
||||||
|
// api
|
||||||
|
vm.touch = touch;
|
||||||
|
vm.addExpression = addExpression;
|
||||||
|
vm.removeExpression = removeExpression;
|
||||||
|
vm.reorderExpression = reorderExpression;
|
||||||
|
|
||||||
|
// listen
|
||||||
|
$scope.$on('$destroy', destroy);
|
||||||
|
|
||||||
|
// init
|
||||||
|
$scope.$applyAsync(init);
|
||||||
|
|
||||||
|
function addExpression($event, $index) {
|
||||||
|
if ($event) {
|
||||||
|
$event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.subExpressions.splice($index, 0, {});
|
||||||
|
if ($index >= 1 && vm.subExpressions[$index - 1].$valid) {
|
||||||
|
// hide previous expression
|
||||||
|
// if it is valid
|
||||||
|
vm.subExpressions[$index -1]['preview'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyConnectable();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeExpression($event, index) {
|
||||||
|
if ($event) {
|
||||||
|
$event.preventDefault();
|
||||||
|
}
|
||||||
|
vm.subExpressions.splice(index, 1);
|
||||||
|
|
||||||
|
applyConnectable();
|
||||||
|
touch();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reorderExpression($event, which, where) {
|
||||||
|
$event.preventDefault();
|
||||||
|
vm.subExpressions[where]['op'] = [
|
||||||
|
vm.subExpressions[which]['op'],
|
||||||
|
vm.subExpressions[which]['op'] = vm.subExpressions[where]['op']
|
||||||
|
][0];
|
||||||
|
vm.subExpressions[where] = vm.subExpressions.splice(which, 1, vm.subExpressions[where])[0];
|
||||||
|
|
||||||
|
applyConnectable();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function touch() {
|
||||||
|
var hasInvalid = false;
|
||||||
|
expression = undefined;
|
||||||
|
|
||||||
|
matchBy = [];
|
||||||
|
deterministic = true;
|
||||||
|
|
||||||
|
angular.forEach(vm.subExpressions, subExpressionIt);
|
||||||
|
|
||||||
|
if (hasInvalid) {
|
||||||
|
expression = undefined;
|
||||||
|
} else {
|
||||||
|
$scope.$emit('mon_match_by_changed', matchBy.sort());
|
||||||
|
$scope.$emit('mon_deterministic_changed', deterministic);
|
||||||
|
|
||||||
|
expression = monExpressionBuilder.asString(vm.subExpressions, true);
|
||||||
|
|
||||||
|
// change preview only if valid
|
||||||
|
vm.expression = expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update that always, regardless if it's valid or not
|
||||||
|
// for invalid case that will reset input's value to empty
|
||||||
|
// disallowing form to be accepted by django
|
||||||
|
$('#expression').val(expression);
|
||||||
|
vm.expressionValid = !hasInvalid;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
function subExpressionIt(expr){
|
||||||
|
if (!expr.$valid) {
|
||||||
|
return !(hasInvalid = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.forEach(expr.matchBy || [], function it(mb){
|
||||||
|
if(matchBy.indexOf(mb) < 0){
|
||||||
|
matchBy.push(mb);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
deterministic = deterministic && (expr.deterministic || false);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
if(vm.metrics.length) {
|
||||||
|
vm.subExpressions = [];
|
||||||
|
vm.matchBy = [];
|
||||||
|
|
||||||
|
addExpression(undefined, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroy() {
|
||||||
|
delete vm.metrics;
|
||||||
|
delete vm.expression;
|
||||||
|
delete vm.subExpressions;
|
||||||
|
delete vm.deterministic;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyConnectable() {
|
||||||
|
var count = vm.subExpressions.length;
|
||||||
|
|
||||||
|
switch(count) {
|
||||||
|
case 1:
|
||||||
|
vm.subExpressions[0]['connectable'] = false;
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
angular.forEach(vm.subExpressions, function(expr, index) {
|
||||||
|
expr.connectable = index >= 1 && index < vm.subExpressions.length;
|
||||||
|
if (!expr.connectable) {
|
||||||
|
delete expr['op'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function monAlarmSubExpressionDirective(staticPath) {
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
require: '^monAlarmExpression',
|
||||||
|
scope: {
|
||||||
|
metrics: '=metrics',
|
||||||
|
functions: '=functions',
|
||||||
|
comparators: '=comparators',
|
||||||
|
operators: '=operators',
|
||||||
|
model: '=subExpression',
|
||||||
|
connectable: '=connectable',
|
||||||
|
preview: '=preview'
|
||||||
|
},
|
||||||
|
templateUrl: staticPath + 'expression/sub-expression.tpl.html',
|
||||||
|
link: linkFn,
|
||||||
|
controller: ['$q', '$scope', 'monExpressionBuilder', AlarmSubExpressionController],
|
||||||
|
controllerAs: 'vm',
|
||||||
|
bindToController: true
|
||||||
|
};
|
||||||
|
|
||||||
|
function linkFn(scope, el, attrs, monAlarmExpressions) {
|
||||||
|
el.on('$destroy', (function(){
|
||||||
|
|
||||||
|
var watcher = scope.$watch('vm.model', function(expr, oldExpr) {
|
||||||
|
if (expr !== oldExpr) {
|
||||||
|
monAlarmExpressions.touch();
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
return function destroyer() {
|
||||||
|
watcher();
|
||||||
|
};
|
||||||
|
|
||||||
|
}()));
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlarmSubExpressionController($q, $scope, monExpressionBuilder) {
|
||||||
|
var vm = this;
|
||||||
|
|
||||||
|
vm.tags = [];
|
||||||
|
vm.matchingMetrics = [];
|
||||||
|
|
||||||
|
// api
|
||||||
|
vm.possibleDimensions = possibleDimensions;
|
||||||
|
vm.onMetricChanged = onMetricChanged;
|
||||||
|
vm.onDimensionsUpdated = onDimensionsUpdated;
|
||||||
|
|
||||||
|
vm.updateExpression = updateExpression;
|
||||||
|
vm.resetExpression = resetExpression;
|
||||||
|
vm.updateExpression = updateExpression;
|
||||||
|
|
||||||
|
// init
|
||||||
|
$scope.$on('$destroy', destroyerFactory());
|
||||||
|
|
||||||
|
function opRemoverListener(nval) {
|
||||||
|
if (vm.model && 'op' in vm.model && !nval) {
|
||||||
|
delete vm.model['op'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroyerFactory() {
|
||||||
|
|
||||||
|
var watcher = $scope.$watch('vm.model.connectable', opRemoverListener, true);
|
||||||
|
|
||||||
|
return function destroyer() {
|
||||||
|
watcher();
|
||||||
|
|
||||||
|
delete vm.tags;
|
||||||
|
delete vm.matchingMetrics;
|
||||||
|
delete vm.model;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateExpression() {
|
||||||
|
var dim = [],
|
||||||
|
formController = $scope.$$childHead.subExpressionForm;
|
||||||
|
|
||||||
|
vm.model.$valid = !formController.$invalid;
|
||||||
|
|
||||||
|
if (vm.model.$valid) {
|
||||||
|
|
||||||
|
if (vm.tags.length > 0) {
|
||||||
|
angular.forEach(vm.tags, function(value, key) {
|
||||||
|
dim.push(value['text']);
|
||||||
|
});
|
||||||
|
vm.model.dimensions = dim;
|
||||||
|
} else {
|
||||||
|
vm.model.dimensions = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetExpression() {
|
||||||
|
vm.matchingMetrics = [];
|
||||||
|
vm.tags = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function possibleDimensions(query) {
|
||||||
|
return $q(function(resolve) {
|
||||||
|
var dim = {},
|
||||||
|
dimList = [];
|
||||||
|
|
||||||
|
angular.forEach(vm.matchingMetrics, function(value, name) {
|
||||||
|
for (var key in value.dimensions) {
|
||||||
|
if (value.dimensions.hasOwnProperty(key)) {
|
||||||
|
var dimStr = key + "=" + value.dimensions[key];
|
||||||
|
if (dimStr.indexOf(query) === 0) {
|
||||||
|
dim[dimStr] = dimStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
angular.forEach(dim, function(value, name) {
|
||||||
|
dimList.push(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve(dimList);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDimensionsUpdated() {
|
||||||
|
onMetricChanged(vm.model.metric);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMetricChanged(metric) {
|
||||||
|
handleMetricChanged(metric);
|
||||||
|
updateExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMetricChanged(metric) {
|
||||||
|
var mm = [],
|
||||||
|
matchBy = [],
|
||||||
|
tags = vm.tags || [];
|
||||||
|
|
||||||
|
angular.forEach(vm.metrics, function(value, key) {
|
||||||
|
if (value.name === metric.name) {
|
||||||
|
var match = true;
|
||||||
|
for (var i = 0; i < tags.length; i++) {
|
||||||
|
var vals = tags[i]['text'].split('=');
|
||||||
|
if (value.dimensions[vals[0]] !== vals[1]) {
|
||||||
|
match = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (match) {
|
||||||
|
mm.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
angular.forEach(mm, function(value, key){
|
||||||
|
angular.forEach(value.dimensions, function(value, key){
|
||||||
|
if(matchBy.indexOf(key) < 0){
|
||||||
|
matchBy.push(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
vm.matchingMetrics = mm;
|
||||||
|
vm.model.matchBy = matchBy.sort();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
49
monitoring/static/monitoring/js/filters.js
Normal file
49
monitoring/static/monitoring/js/filters.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 FUJITSU LIMITED
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
* in compliance with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||||
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||||
|
* or implied. See the License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('monitoring.filters', [
|
||||||
|
'monitoring.services'
|
||||||
|
])
|
||||||
|
.filter('monUniqueMetric', uniqueMetricFilterFactory)
|
||||||
|
.filter('monExpression', ['monExpressionBuilder', monExpressionFilterFactory]);
|
||||||
|
|
||||||
|
function monExpressionFilterFactory(monExpressionBuilder) {
|
||||||
|
|
||||||
|
return function monExpressionFilter(value, withOp) {
|
||||||
|
return monExpressionBuilder.asString([value], withOp);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function uniqueMetricFilterFactory() {
|
||||||
|
|
||||||
|
return function uniqueMetricFilter(arr) {
|
||||||
|
return uniqueNames(arr, 'name');
|
||||||
|
};
|
||||||
|
|
||||||
|
function uniqueNames(input, key) {
|
||||||
|
var unique = {};
|
||||||
|
var uniqueList = [];
|
||||||
|
for(var i = 0; i < input.length; i++){
|
||||||
|
if(typeof unique[input[i][key]] === 'undefined'){
|
||||||
|
unique[input[i][key]] = '';
|
||||||
|
uniqueList.push(input[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uniqueList;
|
||||||
|
}
|
||||||
|
}
|
72
monitoring/static/monitoring/js/services.js
Normal file
72
monitoring/static/monitoring/js/services.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 FUJITSU LIMITED
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
* in compliance with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||||
|
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||||
|
* or implied. See the License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('monitoring.services', [])
|
||||||
|
.factory('monExpressionBuilder', expressionBuilder);
|
||||||
|
|
||||||
|
function expressionBuilder() {
|
||||||
|
|
||||||
|
return {
|
||||||
|
asString: subExpressionToString
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function subExpressionToString(subExpressions, withOp) {
|
||||||
|
var tmp = [],
|
||||||
|
exprAsStr;
|
||||||
|
|
||||||
|
angular.forEach(subExpressions, function(expr) {
|
||||||
|
exprAsStr = [
|
||||||
|
withOp ? renderOp(expr) : '',
|
||||||
|
expr.fun || '',
|
||||||
|
expr.fun && '(',
|
||||||
|
expr.metric ? expr.metric.name : '', renderDimensions(expr),
|
||||||
|
(expr.deterministic ? ',deterministic': ''),
|
||||||
|
expr.fun && ')',
|
||||||
|
expr.cmp || '',
|
||||||
|
expr.threshold || ''
|
||||||
|
].join('');
|
||||||
|
tmp.push(exprAsStr);
|
||||||
|
});
|
||||||
|
|
||||||
|
return tmp.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDimensions(expr) {
|
||||||
|
var tmp = [];
|
||||||
|
|
||||||
|
if (angular.isUndefined(expr.dimensions) || !expr.dimensions.length){
|
||||||
|
return tmp.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp.push('{');
|
||||||
|
tmp.push(expr.dimensions.join(','));
|
||||||
|
tmp.push('}');
|
||||||
|
|
||||||
|
return tmp.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderOp(expr) {
|
||||||
|
var tmp = [];
|
||||||
|
if ('op' in expr) {
|
||||||
|
tmp.push(' ');
|
||||||
|
tmp.push(expr['op']);
|
||||||
|
tmp.push(' ');
|
||||||
|
}
|
||||||
|
return tmp.join('');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
<div class="alarm-expression">
|
||||||
|
|
||||||
|
<input id="expression"
|
||||||
|
name="expression"
|
||||||
|
type="hidden">
|
||||||
|
|
||||||
|
<div class="alert alert-warning"
|
||||||
|
role="alert"
|
||||||
|
ng-if="!vm.metrics.length"
|
||||||
|
translate>
|
||||||
|
No metric available
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container-fluid" ng-if="vm.metrics.length">
|
||||||
|
|
||||||
|
<div class="row center-block" ng-if="vm.expression">
|
||||||
|
<pre class="text-primary expression-preview"
|
||||||
|
ng-class="{'expression-valid': vm.expressionValid, 'expression-invalid': !vm.expressionValid}">{{ vm.expression }}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row"
|
||||||
|
ng-repeat="expr in vm.subExpressions track by $id(expr)" ng-cloak>
|
||||||
|
|
||||||
|
<div class="container-fluid"
|
||||||
|
style="padding-left:0px;padding-right:0px">
|
||||||
|
<div class="row">
|
||||||
|
<!-- :: one time binding -->
|
||||||
|
|
||||||
|
<mon-alarm-sub-expression metrics="vm.metrics"
|
||||||
|
functions="::vm.functions"
|
||||||
|
comparators="::vm.comparators"
|
||||||
|
operators="::vm.operators"
|
||||||
|
sub-expression="vm.subExpressions[$index]"
|
||||||
|
preview="expr.preview"
|
||||||
|
connectable="expr.connectable"
|
||||||
|
class="alarm-sub-expression"
|
||||||
|
ng-class="{'col-lg-11 col-md-11 col-sm-11 col-xs-11': !expr.preview, 'col-lg-9 col-md-9 col-sm-9 col-xs-9': expr.preview}"
|
||||||
|
ng-cloak></mon-alarm-sub-expression>
|
||||||
|
<div ng-class="{'col-lg-1 col-md-1 col-sm-1 col-xs-1': !expr.preview, 'col-lg-3 col-md-3 col-sm-3 col-xs-3': expr.preview}">
|
||||||
|
|
||||||
|
<button role="button"
|
||||||
|
title="{$ 'Edit'|translate $}"
|
||||||
|
class="btn btn-default btn-xs"
|
||||||
|
ng-class="{'btn-block': !expr.preview}"
|
||||||
|
ng-if="expr.preview"
|
||||||
|
ng-click="expr.preview = false">
|
||||||
|
<span class="fa fa-edit" aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
<button role="button"
|
||||||
|
title="{$ 'Submit'|translate $}"
|
||||||
|
class="btn btn-default btn-xs"
|
||||||
|
ng-class="{'btn-block': !expr.preview}"
|
||||||
|
ng-if="!expr.preview"
|
||||||
|
ng-disabled="!expr.$valid"
|
||||||
|
ng-click="expr.preview = true">
|
||||||
|
<span class="fa fa-check"
|
||||||
|
aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="btn btn-default btn-xs"
|
||||||
|
role="button"
|
||||||
|
title="{$ 'Up'|translate $}"
|
||||||
|
ng-class="{'btn-block': !expr.preview}"
|
||||||
|
ng-disabled="$index === 0 || !expr.$valid"
|
||||||
|
ng-click="vm.reorderExpression($event, $index, $index - 1)">
|
||||||
|
<span class="fa fa-sort-asc"
|
||||||
|
aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-default btn-xs"
|
||||||
|
role="button"
|
||||||
|
title="{$ 'Add'|translate $}"
|
||||||
|
ng-class="{'btn-block': !expr.preview}"
|
||||||
|
ng-disabled="!expr.$valid"
|
||||||
|
ng-click="vm.addExpression($event, $index + 1)">
|
||||||
|
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-default btn-xs"
|
||||||
|
role="button"
|
||||||
|
title="{$ 'Remove'|translate $}"
|
||||||
|
ng-class="{'btn-block': !expr.preview}"
|
||||||
|
ng-disabled="vm.subExpressions.length === 1"
|
||||||
|
ng-click="vm.removeExpression($event, $index)">
|
||||||
|
<span class="fa fa-minus"
|
||||||
|
aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-default btn-xs"
|
||||||
|
role="button"
|
||||||
|
title="{$ 'Down'|translate $}"
|
||||||
|
ng-class="{'btn-block': !expr.preview}"
|
||||||
|
ng-disabled="$index === vm.subExpressions.length - 1 || !expr.$valid"
|
||||||
|
ng-click="vm.reorderExpression($event, $index, $index + 1)">
|
||||||
|
<span class="fa fa-sort-desc"
|
||||||
|
aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
@ -0,0 +1,155 @@
|
|||||||
|
<div ng-if="vm.preview" class="sub-expression-preview">
|
||||||
|
<span class="text-muted">{{ vm.model | monExpression:false }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form name="subExpressionForm" ng-if="!vm.preview" novalidate>
|
||||||
|
|
||||||
|
<div class="row expression-details" ng-if="vm.connectable">
|
||||||
|
<div class="col-md-2 col-xs-6">
|
||||||
|
<label class="control-label" for="expressionOperator">
|
||||||
|
<span class="field-label">{{ 'Operator'|translate }}</span>
|
||||||
|
<span class="hz-icon-required fa fa-asterisk"></span>
|
||||||
|
</label>
|
||||||
|
<select id="expressionOperator"
|
||||||
|
name="operator"
|
||||||
|
class="form-control input-sm"
|
||||||
|
title="{$ 'Operators'|translate $}"
|
||||||
|
aria-label="{$ 'Operators'|translate $}"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
data-placement="right"
|
||||||
|
required
|
||||||
|
ng-change="vm.updateExpression()"
|
||||||
|
ng-model="vm.model.op"
|
||||||
|
ng-options="f[0] as f[1] for f in vm.operators"
|
||||||
|
ng-class="{'has-error': subExpressionForm.operator.$invalid && !subExpressionForm.$pristine}"
|
||||||
|
aria-describedby="helpBlock"></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row expression-details">
|
||||||
|
<div class="col-md-2 col-xs-6">
|
||||||
|
<label class="control-label" for="expressionFunction">
|
||||||
|
<span class="field-label">{{ 'Function'|translate }}</span>
|
||||||
|
<span class="hz-icon-required fa fa-asterisk"></span>
|
||||||
|
</label>
|
||||||
|
<select id="expressionFunction"
|
||||||
|
name="function"
|
||||||
|
class="form-control input-sm"
|
||||||
|
aria-label="{$ 'Function'|translate $}"
|
||||||
|
title="{$ 'Function'|translate $}"
|
||||||
|
required
|
||||||
|
ng-change="vm.updateExpression()"
|
||||||
|
ng-model="vm.model.fun"
|
||||||
|
ng-options="f[0] as f[1] for f in vm.functions"
|
||||||
|
ng-class="{'has-error': subExpressionForm.function.$invalid && !subExpressionForm.$pristine}"
|
||||||
|
aria-describedby="helpBlock"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-xs-6">
|
||||||
|
<label class="control-label" for="expressionFunction">
|
||||||
|
<span class="field-label">{{ 'Metric'|translate }}</span>
|
||||||
|
<span class="hz-icon-required fa fa-asterisk"></span>
|
||||||
|
</label>
|
||||||
|
<select id="expressionMetric"
|
||||||
|
name="metric"
|
||||||
|
class="form-control input-sm"
|
||||||
|
aria-label="{$ 'Metric'|translate $}"
|
||||||
|
title="{$ 'Metric'|translate $}"
|
||||||
|
required
|
||||||
|
ng-change="vm.resetExpression();vm.onMetricChanged(vm.model.metric)"
|
||||||
|
ng-model="vm.model.metric"
|
||||||
|
ng-options="metric.name for metric in vm.metrics|monUniqueMetric|orderBy:'name'"
|
||||||
|
ng-class="{'has-error': subExpressionForm.metric.$invalid && !subExpressionForm.$pristine}"
|
||||||
|
aria-describedby="helpBlock"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 col-xs-6">
|
||||||
|
<label class="control-label" for="expressionFunction">
|
||||||
|
<span class="field-label">{{ 'Comparator'|translate }}</span>
|
||||||
|
<span class="hz-icon-required fa fa-asterisk"></span>
|
||||||
|
</label>
|
||||||
|
<select id="expressionComparator"
|
||||||
|
name="comparator"
|
||||||
|
class="form-control input-sm"
|
||||||
|
title="{$ 'Comparator'|translate $}"
|
||||||
|
aria-label="{$ 'Comparator'|translate $}"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
data-placement="right"
|
||||||
|
required
|
||||||
|
ng-change="vm.updateExpression()"
|
||||||
|
ng-model="vm.model.cmp"
|
||||||
|
ng-options="f[0] as f[1] for f in vm.comparators"
|
||||||
|
ng-class="{'has-error': subExpressionForm.comparator.$invalid && !subExpressionForm.$pristine}"
|
||||||
|
aria-describedby="helpBlock"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 col-xs-6">
|
||||||
|
<label class="control-label" for="expressionFunction">
|
||||||
|
<span class="field-label">{{ 'Threshold'|translate }}</span>
|
||||||
|
<span class="hz-icon-required fa fa-asterisk"></span>
|
||||||
|
</label>
|
||||||
|
<input id="expressionThreshold"
|
||||||
|
name="threshold"
|
||||||
|
type="number"
|
||||||
|
step="any"
|
||||||
|
class="form-control input-sm"
|
||||||
|
aria-label="{$ 'Threshold'|translate $}"
|
||||||
|
title="{$ 'Threshold'|translate $}"
|
||||||
|
required
|
||||||
|
ng-change="vm.updateExpression()"
|
||||||
|
ng-model="vm.model.threshold"
|
||||||
|
ng-class="{'has-error': subExpressionForm.threshold.$invalid && !subExpressionForm.$pristine}"
|
||||||
|
aria-describedby="helpBlock"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row expression-details">
|
||||||
|
<div class="col-md-10 col-xs-10">
|
||||||
|
<tags-input id="dimension-chooser"
|
||||||
|
ng-model="vm.tags"
|
||||||
|
placeholder="{$ 'Add a dimension'|translate $}"
|
||||||
|
add-from-autocomplete-only="true"
|
||||||
|
on-tag-added="vm.onDimensionsUpdated()"
|
||||||
|
on-tag-removed="vm.onDimensionsUpdated()">
|
||||||
|
<auto-complete source="vm.possibleDimensions($query)"
|
||||||
|
max-results-to-show="30"
|
||||||
|
min-length="1">
|
||||||
|
</auto-complete>
|
||||||
|
</tags-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 col-xs-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="btn expression-deterministic"
|
||||||
|
ng-class="{'btn-primary active': vm.model.deterministic, 'btn-default': !vm.model.deterministic}"
|
||||||
|
ng-click="vm.model.deterministic = !vm.model.deterministic; vm.updateExpression()"
|
||||||
|
translate>Deterministic</label>
|
||||||
|
<input name="deterministic"
|
||||||
|
type="hidden"
|
||||||
|
ng-model="vm.model.deterministic">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- if window is small enough better hide this div to save some space -->
|
||||||
|
<div class="row expression-details hidden-sm hidden-xs"
|
||||||
|
ng-if="vm.matchingMetrics.length">
|
||||||
|
<div class="topologyBalloon" id="metrics"
|
||||||
|
style="position:static;display: block;">
|
||||||
|
<div class="contentBody">
|
||||||
|
<table class="detailInfoTable">
|
||||||
|
<caption translate>Matching Metrics</caption>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th translate>name</th>
|
||||||
|
<th translate>dimensions</th>
|
||||||
|
</tr>
|
||||||
|
<tr ng-repeat="metric in vm.matchingMetrics track by $id(metric)">
|
||||||
|
<td>{$ metric.name $}</td>
|
||||||
|
<td style="white-space:normal">{$ metric.dimensions |
|
||||||
|
json $}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
Loading…
Reference in New Issue
Block a user