Merge branch 'master' into refactor

This commit is contained in:
Alexander Zobnin
2018-06-08 13:24:17 +03:00
29 changed files with 263 additions and 1266 deletions

View File

@@ -28,7 +28,7 @@ class ZabbixAPIDatasource {
this.basicAuth = instanceSettings.basicAuth;
this.withCredentials = instanceSettings.withCredentials;
const jsonData = instanceSettings.jsonData;
const jsonData = instanceSettings.jsonData || {};
// Zabbix API credentials
this.username = jsonData.username;
@@ -48,6 +48,9 @@ class ZabbixAPIDatasource {
this.addThresholds = jsonData.addThresholds;
this.alertingMinSeverity = jsonData.alertingMinSeverity || c.SEV_WARNING;
// Other options
this.disableReadOnlyUsersAck = jsonData.disableReadOnlyUsersAck;
// Direct DB Connection options
let dbConnectionOptions = jsonData.dbConnection || {};
this.enableDirectDBConnection = dbConnectionOptions.enable;
@@ -206,11 +209,7 @@ class ZabbixAPIDatasource {
return getHistoryPromise
.then(timeseries => this.applyDataProcessingFunctions(timeseries, target))
.then(timeseries => downsampleSeries(timeseries, options))
.catch(error => {
console.log(error);
return [];
});
.then(timeseries => downsampleSeries(timeseries, options));
}
getTrendValueType(target) {

View File

@@ -126,3 +126,11 @@
</div>
</div>
</div>
<div class="gf-form-group">
<h3 class="page-heading">Other</h3>
<gf-form-switch class="gf-form" label-class="width-20"
label="Disable acknowledges for read-only users"
checked="ctrl.current.jsonData.disableReadOnlyUsersAck">
</gf-form-switch>
</div>

View File

@@ -203,7 +203,7 @@ function buildSQLTrendsQuery(itemids, table, timeFrom, timeTill, intervalSec, ag
function buildMysqlHistoryQuery(itemids, table, timeFrom, timeTill, intervalSec, aggFunction) {
let time_expression = `clock DIV ${intervalSec} * ${intervalSec}`;
let query = `
SELECT itemid AS metric, ${time_expression} AS time_sec, ${aggFunction}(value) AS value
SELECT CAST(itemid AS CHAR) AS metric, ${time_expression} AS time_sec, ${aggFunction}(value) AS value
FROM ${table}
WHERE itemid IN (${itemids})
AND clock > ${timeFrom} AND clock < ${timeTill}
@@ -216,7 +216,7 @@ function buildMysqlHistoryQuery(itemids, table, timeFrom, timeTill, intervalSec,
function buildMysqlTrendsQuery(itemids, table, timeFrom, timeTill, intervalSec, aggFunction, valueColumn) {
let time_expression = `clock DIV ${intervalSec} * ${intervalSec}`;
let query = `
SELECT itemid AS metric, ${time_expression} AS time_sec, ${aggFunction}(${valueColumn}) AS value
SELECT CAST(itemid AS CHAR) AS metric, ${time_expression} AS time_sec, ${aggFunction}(${valueColumn}) AS value
FROM ${table}
WHERE itemid IN (${itemids})
AND clock > ${timeFrom} AND clock < ${timeTill}
@@ -226,7 +226,7 @@ function buildMysqlTrendsQuery(itemids, table, timeFrom, timeTill, intervalSec,
return query;
}
const TEST_MYSQL_QUERY = `SELECT itemid AS metric, clock AS time_sec, value_avg AS value FROM trends_uint LIMIT 1`;
const TEST_MYSQL_QUERY = `SELECT CAST(itemid AS CHAR) AS metric, clock AS time_sec, value_avg AS value FROM trends_uint LIMIT 1`;
////////////////
// PostgreSQL //

View File

@@ -49,13 +49,7 @@ class ZabbixAlertingService {
}
getPanelModels() {
return _.flatten(_.map(this.dashboardSrv.dash.rows, row => {
if (row.collapse) {
return [];
} else {
return row.panels;
}
}));
return _.filter(this.dashboardSrv.dash.panels, panel => panel.type !== 'row');
}
getPanelModel(panelId) {

View File

@@ -3,10 +3,12 @@
<section class="card-section card-list-layout-list">
<ol class="alert-rule-list">
<!-- Trigger list item -->
<li class="alert-rule-item zbx-trigger-card" ng-repeat="trigger in ctrl.currentTriggersPage">
<li class="alert-rule-item zbx-trigger-card" ng-repeat="trigger in ctrl.currentTriggersPage"
ng-class="{'zbx-trigger-highlighted': ctrl.panel.highlightBackground}"
ng-style="ctrl.panel.highlightBackground && {background: ctrl.getBackground(trigger)}">
<!-- Heart icon -->
<div class="alert-rule-item__icon" ng-style="{color: trigger.color}">
<div class="alert-rule-item__icon" ng-style="!ctrl.panel.highlightBackground && {color: trigger.color}">
<i class="icon-gf" ng-class="ctrl.getAlertIconClass(trigger)"></i>
</div>
@@ -32,11 +34,13 @@
</p>
<div class="alert-rule-item__text">
<span ng-if="ctrl.panel.statusField" ng-class="ctrl.getAlertStateClass(trigger)">
<span ng-if="ctrl.panel.statusField" class="zbx-trigger-state"
ng-class="ctrl.getAlertStateClass(trigger)">
{{ctrl.triggerStatusMap[trigger.value]}}
</span>
<span ng-if="ctrl.panel.severityField" ng-class="ctrl.getAlertStateClass(trigger)"
ng-style="{color: trigger.color}">
<span ng-if="ctrl.panel.severityField" class="zbx-trigger-severity"
ng-class="ctrl.getAlertStateClass(trigger)"
ng-style="!ctrl.panel.highlightBackground && {color: trigger.color}">
{{trigger.severity}}
</span>
<span class="alert-rule-item__time">

View File

@@ -116,6 +116,12 @@
ng-model="ctrl.panel.pageSize"
ng-model-onblur ng-change="ctrl.render()">
</div>
<gf-form-switch class="gf-form"
label-class="width-10"
label="Highlight background"
checked="ctrl.panel.highlightBackground"
on-change="ctrl.render()">
</gf-form-switch>
<gf-form-switch class="gf-form"
label-class="width-10"
label="Highlight new events"
@@ -157,7 +163,7 @@
<label class="gf-form-label width-3">{{ trigger.priority }}</label>
<label class="gf-form-label triggers-severity-config"
ng-style="{color: trigger.color}">
<i class="icon-gf" ng-class="ctrl.getAlertIconClass(trigger)"></i>
<i class="icon-gf" ng-class="ctrl.getAlertIconClassBySeverity(trigger)"></i>
</label>
<input type="text"
class="gf-form-input width-12"

View File

@@ -187,6 +187,26 @@ describe('TriggerPanelCtrl', () => {
});
});
});
describe('When formatting acknowledges', () => {
beforeEach(() => {
ctx.panelCtrl = createPanelCtrl();
});
it('should build proper user name', () => {
const ack = {
alias: 'alias', name: 'name', surname: 'surname'
};
const formatted = ctx.panelCtrl.formatAcknowledge(ack);
expect(formatted.user).toBe('alias (name surname)');
});
it('should return empty name if it is not defined', () => {
const formatted = ctx.panelCtrl.formatAcknowledge({});
expect(formatted.user).toBe('');
});
});
});
const defaultTrigger = {

View File

@@ -50,7 +50,8 @@ export const PANEL_DEFAULTS = {
// View options
fontSize: '100%',
pageSize: 10,
highlightNewEvents: true,
highlightBackground: false,
highlightNewEvents: false,
highlightNewerThan: '1h',
customLastChangeFormat: false,
lastChangeFormat: "",
@@ -242,16 +243,7 @@ export class TriggerPanelCtrl extends PanelCtrl {
});
if (event) {
trigger.acknowledges = _.map(event.acknowledges, ack => {
let timestamp = moment.unix(ack.clock);
if (this.panel.customLastChangeFormat) {
ack.time = timestamp.format(this.panel.lastChangeFormat);
} else {
ack.time = timestamp.format(this.defaultTimeFormat);
}
ack.user = ack.alias + ' (' + ack.name + ' ' + ack.surname + ')';
return ack;
});
trigger.acknowledges = _.map(event.acknowledges, this.formatAcknowledge.bind(this));
}
if (!trigger.lastEvent.eventid) {
@@ -263,6 +255,21 @@ export class TriggerPanelCtrl extends PanelCtrl {
});
}
formatAcknowledge(ack) {
let timestamp = moment.unix(ack.clock);
if (this.panel.customLastChangeFormat) {
ack.time = timestamp.format(this.panel.lastChangeFormat);
} else {
ack.time = timestamp.format(this.defaultTimeFormat);
}
ack.user = ack.alias || '';
if (ack.name || ack.surname) {
const fullName = `${ack.name || ''} ${ack.surname || ''}`;
ack.user += ` (${fullName})`;
}
return ack;
}
filterTriggersPre(triggerList, ds) {
// Filter triggers by description
let triggerFilter = this.panel.targets[ds].trigger.filter;
@@ -446,6 +453,10 @@ export class TriggerPanelCtrl extends PanelCtrl {
let ack_message = grafana_user + ' (Grafana): ' + message;
return this.datasourceSrv.get(trigger.datasource)
.then(datasource => {
const userIsEditor = this.contextSrv.isEditor || this.contextSrv.isGrafanaAdmin;
if (datasource.disableReadOnlyUsersAck && !userIsEditor) {
return Promise.reject({message: 'You have no permissions to acknowledge events.'});
}
if (eventid) {
return datasource.zabbix.zabbixAPI.acknowledgeEvent(eventid, ack_message);
} else {
@@ -490,9 +501,8 @@ export class TriggerPanelCtrl extends PanelCtrl {
}
getAlertIconClass(trigger) {
const triggerValue = Number(trigger.value);
let iconClass = '';
if (triggerValue || trigger.color) {
if (trigger.value === '1') {
if (trigger.priority >= 3) {
iconClass = 'icon-gf-critical';
} else {
@@ -508,6 +518,14 @@ export class TriggerPanelCtrl extends PanelCtrl {
return iconClass;
}
getAlertIconClassBySeverity(triggerSeverity) {
let iconClass = 'icon-gf-warning';
if (triggerSeverity.priority >= 3) {
iconClass = 'icon-gf-critical';
}
return iconClass;
}
getAlertStateClass(trigger) {
let statusClass = '';
@@ -524,6 +542,15 @@ export class TriggerPanelCtrl extends PanelCtrl {
return statusClass;
}
getBackground(trigger) {
const mainColor = trigger.color;
const secondColor = this.contextSrv.user.lightTheme ? '#dde4ed' : '#262628';
if (this.contextSrv.user.lightTheme) {
return `linear-gradient(135deg, ${secondColor}, ${mainColor})`;
}
return `linear-gradient(135deg, ${mainColor}, ${secondColor})`;
}
isNewTrigger(trigger) {
try {
const highlightIntervalMs = utils.parseInterval(this.panel.highlightNewerThan || PANEL_DEFAULTS.highlightNewerThan);

View File

@@ -26,8 +26,8 @@
{"name": "Metric Editor", "path": "img/screenshot-metric_editor.png"},
{"name": "Triggers", "path": "img/screenshot-triggers.png"}
],
"version": "3.8.1",
"updated": "2017-12-21"
"version": "3.9.1",
"updated": "2018-05-02"
},
"includes": [
@@ -49,7 +49,7 @@
],
"dependencies": {
"grafanaVersion": "3.x 4.x",
"grafanaVersion": "5.x",
"plugins": []
}
}

View File

@@ -38,10 +38,6 @@
}
}
.alert-list-footer {
justify-content: flex-start;
}
.trigger-info-block {
display: flex;
@@ -59,10 +55,6 @@
opacity: 0.4;
}
}
.card-item-wrapper {
padding-left: 4px;
}
}
.zbx-trigger-card {
@@ -81,12 +73,28 @@
color: $orange;
}
.zbx-trigger-highlighted {
color: $zbx-text-highlighted;
@keyframes zabbix-triggers-panel {
100% {
opacity: 1;
box-shadow: 0 0 10px 0px rgba($red, 1);
border-bottom-color: rgba($red, 0.25);
.alert-rule-item__body,
.alert-rule-item__header,
.alert-rule-item__time,
.zabbix-hostname,
.zbx-description {
color: $zbx-text-highlighted;
}
.alert-rule-item__text {
.zbx-trigger-state,
.zbx-trigger-severity {
color: $zbx-text-highlighted;
}
}
.zbx-trigger-lastchange .trigger-info-block.zbx-status-icons {
a, i {
color: $zbx-text-highlighted;
}
}
}
@@ -164,8 +172,9 @@
.triggers-severity-config {
&.gf-form-label {
padding-top: 4px;
padding-bottom: 4px;
padding-top: 5px;
padding-bottom: 5px;
border-radius: 0px;
}
.icon-gf {

View File

@@ -37,3 +37,4 @@ $tight-form-func-bg: #333;
$grafanaListAccent: lighten($dark-2, 2%);
$zbx-tag-color: $gray-5;
$zbx-text-highlighted: $white;

View File

@@ -36,3 +36,4 @@ $tight-form-func-bg: $gray-5;
$grafanaListAccent: $gray-5;
$zbx-tag-color: $gray-6;
$zbx-text-highlighted: $black;