Merge branch 'panel-triggers' into grafana-3.0

This commit is contained in:
Alexander Zobnin
2016-02-03 15:45:50 +03:00
4 changed files with 364 additions and 184 deletions

View File

@@ -28,6 +28,19 @@ function (angular, _, utils) {
} }
}; };
/**
* Build trigger query in asynchronous manner
*/
this.buildTriggerQuery = function (groupFilter, hostFilter, appFilter) {
if (this.cache._initialized) {
return $q.when(self.buildTriggerQueryFromCache(groupFilter, hostFilter, appFilter));
} else {
return this.cache.refresh().then(function() {
return self.buildTriggerQueryFromCache(groupFilter, hostFilter, appFilter);
});
}
};
this.filterGroups = function(groupFilter) { this.filterGroups = function(groupFilter) {
return self.cache.getGroups().then(function(groupList) { return self.cache.getGroups().then(function(groupList) {
return groupList; return groupList;
@@ -309,6 +322,60 @@ function (angular, _, utils) {
}); });
}; };
/**
* Build query - convert target filters to array of Zabbix items
*/
this.buildTriggerQueryFromCache = function (groupFilter, hostFilter, appFilter) {
var promises = [
this.filterGroups(groupFilter).then(function(groups) {
return _.filter(groups, function(group) {
if (utils.isRegex(groupFilter)) {
return utils.buildRegex(groupFilter).test(group.name);
} else {
return group.name === groupFilter;
}
});
}),
this.filterHosts(groupFilter).then(function(hosts) {
return _.filter(hosts, function(host) {
if (utils.isRegex(hostFilter)) {
return utils.buildRegex(hostFilter).test(host.name);
} else {
return host.name === hostFilter;
}
});
}),
this.filterApplications(groupFilter, hostFilter).then(function(apps) {
return _.filter(apps, function(app) {
if (utils.isRegex(appFilter)) {
return utils.buildRegex(appFilter).test(app.name);
} else {
return app.name === appFilter;
}
});
})
];
return $q.all(promises).then(function(results) {
var filteredGroups = results[0];
var filteredHosts = results[1];
var filteredApps = results[2];
var query = {};
if (appFilter) {
query.applicationids = _.flatten(_.map(filteredApps, 'applicationids'));
}
if (hostFilter) {
query.hostids = _.map(filteredHosts, 'hostid');
}
if (groupFilter) {
query.groupids = _.map(filteredGroups, 'groupid');
}
return query;
});
};
/** /**
* Convert Zabbix API history.get response to Grafana format * Convert Zabbix API history.get response to Grafana format
* *

View File

@@ -296,49 +296,44 @@ function (angular, _) {
return this.request('service.getsla', params); return this.request('service.getsla', params);
}; };
p.getTriggers = function(limit, sortfield, groupids, hostids, applicationids, name) { p.getTriggers = function(groupids, hostids, applicationids, showEvents) {
var params = { var params = {
output: 'extend', output: 'extend',
expandDescription: true,
expandData: true,
monitored: true,
//only_true: true,
filter: {
value: 1
},
search : {
description: name
},
searchWildcardsEnabled: false,
groupids: groupids, groupids: groupids,
hostids: hostids, hostids: hostids,
applicationids: applicationids, applicationids: applicationids,
limit: limit, expandDescription: true,
sortfield: 'lastchange', expandData: true,
sortorder: 'DESC' monitored: true,
skipDependent: true,
//only_true: true,
filter: {
value: showEvents
},
selectGroups: ['name'],
selectHosts: ['name'],
selectItems: ['name', 'key_', 'lastvalue'],
selectLastEvent: 'extend'
}; };
if (sortfield) {
params.sortfield = sortfield;
}
return this.request('trigger.get', params); return this.request('trigger.get', params);
}; };
p.getAcknowledges = function(triggerids, from) { p.getAcknowledges = function(eventids) {
var params = { var params = {
output: 'extend', output: 'extend',
objectids: triggerids, eventids: eventids,
acknowledged: true, preservekeys: true,
select_acknowledges: 'extend', select_acknowledges: 'extend',
sortfield: 'clock', sortfield: 'clock',
sortorder: 'DESC', sortorder: 'DESC'
time_from: from
}; };
return this.request('event.get', params) return this.request('event.get', params)
.then(function (events) { .then(function (events) {
return _.flatten(_.map(events, 'acknowledges')); return _.filter(events, function(event) {
return event.acknowledges.length;
});
}); });
}; };

View File

@@ -7,21 +7,29 @@
Group Group
</li> </li>
<li> <li>
<select class="tight-form-input input-large" <input type="text"
ng-model="panel.triggers.group" ng-model="panel.triggers.group.filter"
ng-options="g.name for g in metric.groupList track by g.name" bs-typeahead="getGroupNames"
ng-change="groupChanged()"> ng-change="onTargetPartChange(panel.triggers.group)"
</select> ng-blur="parseTarget()"
data-min-length=0
data-items=100
class="input-large tight-form-input"
ng-style="panel.triggers.group.style">
</li> </li>
<li class="tight-form-item" style="width: 50px"> <li class="tight-form-item" style="width: 50px">
Host Host
</li> </li>
<li> <li>
<select class="tight-form-input input-large last" <input type="text"
ng-model="panel.triggers.host" ng-model="panel.triggers.host.filter"
ng-options="h.name for h in metric.hostList track by h.name" bs-typeahead="getHostNames"
ng-change="hostChanged()"> ng-change="onTargetPartChange(panel.triggers.host)"
</select> ng-blur="parseTarget()"
data-min-length=0
data-items=100
class="input-large tight-form-input last"
ng-style="panel.triggers.host.style">
</li> </li>
</ul> </ul>
<div class="clearfix"></div> <div class="clearfix"></div>
@@ -32,22 +40,28 @@
Application Application
</li> </li>
<li> <li>
<select class="tight-form-input input-large" <input type="text"
ng-model="panel.triggers.application" ng-model="panel.triggers.application.filter"
ng-options="app.name for app in metric.applicationList track by app.name" bs-typeahead="getApplicationNames"
ng-change="appChanged()"> ng-change="onTargetPartChange(panel.triggers.application)"
</select> ng-blur="parseTarget()"
data-min-length=0
data-items=100
class="input-large tight-form-input"
ng-style="panel.triggers.application.style">
</li> </li>
<li class="tight-form-item" style="width: 50px"> <li class="tight-form-item" style="width: 50px">
Trigger Trigger
</li> </li>
<li> <li>
<input type="text" <input type="text"
class="input-large tight-form-input last" ng-model="panel.triggers.trigger.filter"
ng-change="onTargetPartChange(panel.triggers.trigger)"
ng-blur="parseTarget()"
placeholder="trigger name" placeholder="trigger name"
ng-model="panel.triggers.name" class="input-large tight-form-input last"
empty-to-null ng-style="panel.triggers.trigger.style"
ng-blur="get_data()"> empty-to-null">
</li> </li>
</ul> </ul>
<div class="clearfix"></div> <div class="clearfix"></div>
@@ -91,7 +105,7 @@
<strong>Limit triggers number to</strong> <strong>Limit triggers number to</strong>
</li> </li>
<li> <li>
<input class="input-small tight-form-input last" <input class="input-small tight-form-input"
type="number" type="number"
ng-model="panel.limit" ng-model="panel.limit"
ng-model-onblur ng-model-onblur
@@ -116,7 +130,7 @@
<strong>Show events</strong> <strong>Show events</strong>
</li> </li>
<li> <li>
<select class="input-medium tight-form-input" <select class="tight-form-input input-medium last"
ng-model="panel.showEvents" ng-model="panel.showEvents"
ng-options="f.text for f in showEventsFields track by f.value" ng-options="f.text for f in showEventsFields track by f.value"
ng-change="get_data()"> ng-change="get_data()">
@@ -184,7 +198,6 @@
<h5>Customize triggers severity and colors</h5> <h5>Customize triggers severity and colors</h5>
<div class="tight-form" <div class="tight-form"
ng-repeat="trigger in panel.triggerSeverity" ng-repeat="trigger in panel.triggerSeverity"
ng-class="{last: $last}"
ng-style="{background:trigger.color}"> ng-style="{background:trigger.color}">
<ul class="tight-form-list"> <ul class="tight-form-list">
<li class="tight-form-item" style="width: 10px; color: #101010"> <li class="tight-form-item" style="width: 10px; color: #101010">
@@ -227,5 +240,28 @@
</ul> </ul>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
<div class="tight-form last"
ng-style="{background:panel.okEventColor}">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 160px; color: #101010">
<span style="padding-left: 25px"> OK event color </span>
</li>
<li data-trigger-index="6">
&nbsp;
<i class="pointer fa fa-eyedropper trigger-color"
style="color: #101010"
ng-click="openOkEventColorSelector($event)">&nbsp;</i>
<input type="text"
class="tight-form-input input-small"
style="color: #101010"
empty-to-null
ng-model="panel.okEventColor"
ng-style="{background:panel.okEventColor}"
ng-model-onblur
ng-change="get_data()">
</li>
</ul>
<div class="clearfix"></div>
</div>
</div> </div>
</div> </div>

View File

@@ -17,8 +17,7 @@ define([
'lodash', 'lodash',
'jquery', 'jquery',
'app/core/config', 'app/core/config',
'app/features/panel/panel_meta', 'app/features/panel/panel_meta'
'../zabbix/helperFunctions',
], ],
function (angular, app, _, $, config, PanelMeta) { function (angular, app, _, $, config, PanelMeta) {
'use strict'; 'use strict';
@@ -27,7 +26,7 @@ function (angular, app, _, $, config, PanelMeta) {
app.useModule(module); app.useModule(module);
/** @ngInject */ /** @ngInject */
function TriggerPanelCtrl($q, $scope, $element, datasourceSrv, panelSrv, templateSrv, zabbixHelperSrv, popoverSrv) { function TriggerPanelCtrl($q, $scope, $element, datasourceSrv, panelSrv, templateSrv, popoverSrv) {
$scope.panelMeta = new PanelMeta({ $scope.panelMeta = new PanelMeta({
panelName: 'Zabbix triggers', panelName: 'Zabbix triggers',
@@ -66,9 +65,10 @@ function (angular, app, _, $, config, PanelMeta) {
var panelDefaults = { var panelDefaults = {
datasource: null, datasource: null,
triggers: { triggers: {
group: {name: 'All', groupid: null}, group: {filter: ""},
host: {name: 'All', hostid: null}, host: {filter: ""},
application: {name: 'All', value: null} application: {filter: ""},
trigger: {filter: ""}
}, },
hostField: true, hostField: true,
severityField: false, severityField: false,
@@ -79,7 +79,8 @@ function (angular, app, _, $, config, PanelMeta) {
showTriggers: 'all triggers', showTriggers: 'all triggers',
sortTriggersBy: { text: 'last change', value: 'lastchange' }, sortTriggersBy: { text: 'last change', value: 'lastchange' },
showEvents: { text: 'Problem events', value: '1' }, showEvents: { text: 'Problem events', value: '1' },
triggerSeverity: grafanaDefaultSeverity triggerSeverity: grafanaDefaultSeverity,
okEventColor: '#890F02'
}; };
_.defaults($scope.panel, panelDefaults); _.defaults($scope.panel, panelDefaults);
@@ -91,13 +92,13 @@ function (angular, app, _, $, config, PanelMeta) {
$scope.panel.title = "Zabbix Triggers"; $scope.panel.title = "Zabbix Triggers";
} }
if (!$scope.metric) { // Load scope defaults
$scope.metric = { var scopeDefaults = {
groupList: [{name: 'All', groupid: null}], metric: {},
hostList: [{name: 'All', hostid: null}], inputStyles: {},
applicationList: [{name: 'All', applicationid: null}] oldTarget: _.cloneDeep($scope.panel.triggers)
}; };
} _.defaults($scope, scopeDefaults);
// Get zabbix data sources // Get zabbix data sources
var datasources = _.filter(datasourceSrv.getMetricSources(), function(datasource) { var datasources = _.filter(datasourceSrv.getMetricSources(), function(datasource) {
@@ -109,11 +110,11 @@ function (angular, app, _, $, config, PanelMeta) {
if (!$scope.panel.datasource) { if (!$scope.panel.datasource) {
$scope.panel.datasource = $scope.datasources[0]; $scope.panel.datasource = $scope.datasources[0];
} }
// Load datasource
// Update lists of groups, hosts and applications datasourceSrv.get($scope.panel.datasource).then(function (datasource) {
$scope.updateGroups() $scope.datasource = datasource;
.then($scope.updateHosts) $scope.initFilters();
.then($scope.updateApplications); });
}; };
$scope.refreshData = function() { $scope.refreshData = function() {
@@ -121,116 +122,186 @@ function (angular, app, _, $, config, PanelMeta) {
// Load datasource // Load datasource
return datasourceSrv.get($scope.panel.datasource).then(function (datasource) { return datasourceSrv.get($scope.panel.datasource).then(function (datasource) {
var zabbix = datasource.zabbixAPI; var zabbix = datasource.zabbixAPI;
var queryProcessor = datasource.queryProcessor;
var groupid = $scope.panel.triggers.group.groupid; var triggerFilter = $scope.panel.triggers;
var hostid = $scope.panel.triggers.host.hostid; var showEvents = $scope.panel.showEvents.value;
var applicationids = $scope.panel.triggers.application.value; var buildQuery = queryProcessor.buildTriggerQuery(triggerFilter.group.filter,
triggerFilter.host.filter,
// Get triggers triggerFilter.application.filter);
return zabbix.getTriggers(null, return buildQuery.then(function(query) {
$scope.panel.sortTriggersBy.value, return zabbix.getTriggers(query.groupids,
groupid, query.hostids,
hostid, query.applicationids,
applicationids, showEvents)
$scope.panel.triggers.name,
$scope.panel.showEvents.value)
.then(function(triggers) { .then(function(triggers) {
var promises = _.map(triggers, function (trigger) { return _.map(triggers, function (trigger) {
var lastchange = new Date(trigger.lastchange * 1000); var lastchange = new Date(trigger.lastchange * 1000);
var lastchangeUnix = trigger.lastchange; var lastchangeUnix = trigger.lastchange;
var now = new Date(); var now = new Date();
// Consider local time offset // Consider local time offset
var ageUnix = now - lastchange + now.getTimezoneOffset() * 60000; var ageUnix = now - lastchange + now.getTimezoneOffset() * 60000;
var age = zabbixHelperSrv.toZabbixAgeFormat(ageUnix); var age = toZabbixAgeFormat(ageUnix);
var triggerObj = trigger; var triggerObj = trigger;
triggerObj.lastchangeUnix = lastchangeUnix; triggerObj.lastchangeUnix = lastchangeUnix;
triggerObj.lastchange = lastchange.toLocaleString(); triggerObj.lastchange = lastchange.toLocaleString();
triggerObj.age = age.toLocaleString(); triggerObj.age = age.toLocaleString();
// Set color
if (trigger.value === '1') {
triggerObj.color = $scope.panel.triggerSeverity[trigger.priority].color; triggerObj.color = $scope.panel.triggerSeverity[trigger.priority].color;
} else {
triggerObj.color = $scope.panel.okEventColor;
}
triggerObj.severity = $scope.panel.triggerSeverity[trigger.priority].severity; triggerObj.severity = $scope.panel.triggerSeverity[trigger.priority].severity;
return triggerObj;
});
})
.then(function (triggerList) {
// Request acknowledges for trigger // Request acknowledges for trigger
return zabbix.getAcknowledges(trigger.triggerid, lastchangeUnix) var eventids = _.map(triggerList, function(trigger) {
.then(function (acknowledges) { return trigger.lastEvent.eventid;
if (acknowledges.length) { });
triggerObj.acknowledges = _.map(acknowledges, function (ack) { return zabbix.getAcknowledges(eventids)
.then(function (events) {
// Map events to triggers
_.each(triggerList, function(trigger) {
var event = _.find(events, function(event) {
return event.eventid === trigger.lastEvent.eventid;
});
if (event) {
trigger.acknowledges = _.map(event.acknowledges, function (ack) {
var time = new Date(+ack.clock * 1000); var time = new Date(+ack.clock * 1000);
ack.time = time.toLocaleString(); ack.time = time.toLocaleString();
ack.user = ack.alias + ' (' + ack.name + ' ' + ack.surname + ')'; ack.user = ack.alias + ' (' + ack.name + ' ' + ack.surname + ')';
return ack; return ack;
}); });
} }
return triggerObj;
}); });
});
return $q.all(promises).then(function (triggerList) { // Filter triggers by description
var triggerFilter = $scope.panel.triggers.trigger.filter;
if (triggerFilter) {
triggerList = filterTriggers(triggerList, triggerFilter);
}
// Filter acknowledged triggers // Filter acknowledged triggers
if ($scope.panel.showTriggers === 'unacknowledged') { if ($scope.panel.showTriggers === 'unacknowledged') {
$scope.triggerList = _.filter(triggerList, function (trigger) { triggerList = _.filter(triggerList, function (trigger) {
return !trigger.acknowledges; return !trigger.acknowledges;
}); });
} else if ($scope.panel.showTriggers === 'acknowledged') { } else if ($scope.panel.showTriggers === 'acknowledged') {
$scope.triggerList = _.filter(triggerList, 'acknowledges'); triggerList = _.filter(triggerList, 'acknowledges');
} else { } else {
$scope.triggerList = triggerList; triggerList = triggerList;
} }
// Filter triggers by severity // Filter triggers by severity
$scope.triggerList = _.filter($scope.triggerList, function (trigger) { triggerList = _.filter(triggerList, function (trigger) {
return $scope.panel.triggerSeverity[trigger.priority].show; return $scope.panel.triggerSeverity[trigger.priority].show;
}); });
// Sort triggers
if ($scope.panel.sortTriggersBy.value === 'priority') {
triggerList = _.sortBy(triggerList, 'priority').reverse();
} else {
triggerList = _.sortBy(triggerList, 'lastchangeUnix').reverse();
}
// Limit triggers number // Limit triggers number
$scope.triggerList = _.first($scope.triggerList, $scope.panel.limit); $scope.triggerList = _.first(triggerList, $scope.panel.limit);
$scope.panelRenderingComplete(); $scope.panelRenderingComplete();
}); });
}); });
}); });
};
$scope.groupChanged = function() {
return $scope.updateHosts()
.then($scope.updateApplications)
.then($scope.refreshData);
};
$scope.hostChanged = function() {
return $scope.updateApplications()
.then($scope.refreshData);
};
$scope.appChanged = function() {
var app = $scope.panel.triggers.application.name;
return datasourceSrv.get($scope.panel.datasource).then(function (datasource) {
return datasource.zabbixAPI.getAppByName(app).then(function (applications) {
var appids = _.map(applications, 'applicationid');
$scope.panel.triggers.application.value = appids.length ? appids : null;
});
}).then($scope.refreshData);
};
$scope.updateGroups = function() {
return datasourceSrv.get($scope.panel.datasource).then(function (datasource) {
return $scope.updateGroupList(datasource);
}); });
}; };
$scope.updateHosts = function() { $scope.initFilters = function () {
return datasourceSrv.get($scope.panel.datasource).then(function (datasource) { $scope.filterGroups();
return $scope.updateHostList(datasource); $scope.filterHosts();
$scope.filterApplications();
};
// Get list of metric names for bs-typeahead directive
function getMetricNames(scope, metricList) {
return _.uniq(_.map(scope.metric[metricList], 'name'));
}
// Map functions for bs-typeahead
$scope.getGroupNames = _.partial(getMetricNames, $scope, 'groupList');
$scope.getHostNames = _.partial(getMetricNames, $scope, 'filteredHosts');
$scope.getApplicationNames = _.partial(getMetricNames, $scope, 'filteredApplications');
$scope.getItemNames = _.partial(getMetricNames, $scope, 'filteredItems');
$scope.filterGroups = function() {
$scope.datasource.queryProcessor.filterGroups().then(function(groups) {
$scope.metric.groupList = groups;
}); });
}; };
$scope.updateApplications = function() { $scope.filterHosts = function() {
return datasourceSrv.get($scope.panel.datasource).then(function (datasource) { var groupFilter = templateSrv.replace($scope.panel.triggers.group.filter);
return $scope.updateAppList(datasource); $scope.datasource.queryProcessor.filterHosts(groupFilter).then(function(hosts) {
$scope.metric.filteredHosts = hosts;
}); });
}; };
$scope.filterApplications = function() {
var groupFilter = templateSrv.replace($scope.panel.triggers.group.filter);
var hostFilter = templateSrv.replace($scope.panel.triggers.host.filter);
$scope.datasource.queryProcessor.filterApplications(groupFilter, hostFilter)
.then(function(apps) {
$scope.metric.filteredApplications = apps;
});
};
function filterTriggers(triggers, triggerFilter) {
if (isRegex(triggerFilter)) {
return _.filter(triggers, function(trigger) {
return buildRegex(triggerFilter).test(trigger.description);
});
} else {
return _.filter(triggers, function(trigger) {
return trigger.description === triggerFilter;
});
}
}
$scope.onTargetPartChange = function(targetPart) {
var regexStyle = {'color': '#CCA300'};
targetPart.isRegex = isRegex(targetPart.filter);
targetPart.style = targetPart.isRegex ? regexStyle : {};
};
function isRegex(str) {
// Pattern for testing regex
var regexPattern = /^\/(.*)\/([gmi]*)$/m;
return regexPattern.test(str);
}
function buildRegex(str) {
var regexPattern = /^\/(.*)\/([gmi]*)$/m;
var matches = str.match(regexPattern);
var pattern = matches[1];
var flags = matches[2] !== "" ? matches[2] : undefined;
return new RegExp(pattern, flags);
}
$scope.parseTarget = function() {
$scope.initFilters();
var newTarget = _.cloneDeep($scope.panel.triggers);
if (!_.isEqual($scope.oldTarget, $scope.panel.triggers)) {
$scope.oldTarget = newTarget;
$scope.get_data();
}
};
$scope.refreshTriggerSeverity = function() { $scope.refreshTriggerSeverity = function() {
_.each($scope.triggerList, function(trigger) { _.each($scope.triggerList, function(trigger) {
trigger.color = $scope.panel.triggerSeverity[trigger.priority].color; trigger.color = $scope.panel.triggerSeverity[trigger.priority].color;
@@ -266,38 +337,49 @@ function (angular, app, _, $, config, PanelMeta) {
}); });
}; };
$scope.updateGroupList = function (datasource) { $scope.openOkEventColorSelector = function(event) {
datasource.zabbixAPI.performHostGroupSuggestQuery().then(function (groups) { var el = $(event.currentTarget);
$scope.metric.groupList = $scope.metric.groupList.concat(groups); var popoverScope = $scope.$new();
popoverScope.trigger = {color: $scope.panel.okEventColor};
popoverScope.changeTriggerSeverityColor = function(trigger, color) {
$scope.panel.okEventColor = color;
$scope.refreshTriggerSeverity();
};
popoverSrv.show({
element: el,
placement: 'top',
templateUrl: 'public/plugins/triggers/trigger.colorpicker.html',
scope: popoverScope
}); });
}; };
$scope.updateHostList = function (datasource) { /**
var groups = $scope.panel.triggers.group.groupid ? $scope.panel.triggers.group.name : '*'; * Convert event age from Unix format (milliseconds sins 1970)
if (groups) { * to Zabbix format (like at Last 20 issues panel).
datasource.zabbixAPI.hostFindQuery(groups).then(function (hosts) { * @param {Date} AgeUnix time in Unix format
$scope.metric.hostList = [{name: 'All', hostid: null}]; * @return {string} Formatted time
$scope.metric.hostList = $scope.metric.hostList.concat(hosts); */
}); function toZabbixAgeFormat(ageUnix) {
var age = new Date(+ageUnix);
var ageZabbix = age.getSeconds() + 's';
if (age.getMinutes()) {
ageZabbix = age.getMinutes() + 'm ' + ageZabbix;
} }
}; if (age.getHours()) {
ageZabbix = age.getHours() + 'h ' + ageZabbix;
$scope.updateAppList = function (datasource) { }
var groups = $scope.panel.triggers.group.groupid ? $scope.panel.triggers.group.name : '*'; if (age.getDate() - 1) {
var hosts = $scope.panel.triggers.host.hostid ? $scope.panel.triggers.host.name : '*'; ageZabbix = age.getDate() - 1 + 'd ' + ageZabbix;
if (groups && hosts) { }
datasource.zabbixAPI.appFindQuery(hosts, groups).then(function (apps) { if (age.getMonth()) {
apps = _.map(_.uniq(_.map(apps, 'name')), function (appname) { ageZabbix = age.getMonth() + 'M ' + ageZabbix;
return { }
name: appname, if (age.getYear() - 70) {
value: appname ageZabbix = age.getYear() -70 + 'y ' + ageZabbix;
}; }
}); return ageZabbix;
$scope.metric.applicationList = [{name: 'All', value: null}];
$scope.metric.applicationList = $scope.metric.applicationList.concat(apps);
});
} }
};
$scope.init(); $scope.init();
} }