Merge branch 'master' into refactor
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 //
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -37,3 +37,4 @@ $tight-form-func-bg: #333;
|
||||
$grafanaListAccent: lighten($dark-2, 2%);
|
||||
|
||||
$zbx-tag-color: $gray-5;
|
||||
$zbx-text-highlighted: $white;
|
||||
|
||||
@@ -36,3 +36,4 @@ $tight-form-func-bg: $gray-5;
|
||||
$grafanaListAccent: $gray-5;
|
||||
|
||||
$zbx-tag-color: $gray-6;
|
||||
$zbx-text-highlighted: $black;
|
||||
|
||||
Reference in New Issue
Block a user