diff --git a/docker-compose.yml b/docker-compose.yml index 1ad7bd0..e93fe9e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,7 @@ services: HASTIC_WEBHOOK_TYPE: ${HASTIC_WEBHOOK_TYPE} GRAFANA_URL: ${GRAFANA_URL} HASTIC_INSTANCE_NAME: ${HASTIC_INSTANCE_NAME} + HASTIC_WEBHOOK_IMAGE_ENABLED: ${HASTIC_WEBHOOK_IMAGE_ENABLED} ports: - ${HASTIC_PORT:-8000}:8000 volumes: diff --git a/server/src/config.ts b/server/src/config.ts index aa389d4..37c67b7 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -29,8 +29,10 @@ export const GRAFANA_URL = normalizeUrl(getConfigField('GRAFANA_URL', null)); // TODO: save orgId in analytic_units.db export const ORG_ID = getConfigField('ORG_ID', 1); export const HASTIC_WEBHOOK_URL = getConfigField('HASTIC_WEBHOOK_URL', null); -export const HASTIC_WEBHOOK_TYPE = getConfigField('HASTIC_WEBHOOK_TYPE', 'application/x-www-form-urlencoded'); +export const HASTIC_WEBHOOK_TYPE = getConfigField('HASTIC_WEBHOOK_TYPE', 'application/json'); export const HASTIC_WEBHOOK_SECRET = getConfigField('HASTIC_WEBHOOK_SECRET', null); +export const HASTIC_WEBHOOK_IMAGE_ENABLED = getConfigField('HASTIC_WEBHOOK_IMAGE', false); + export const ANLYTICS_PING_INTERVAL = 500; // ms export const PACKAGE_VERSION = getPackageVersion(); export const GIT_INFO = getGitInfo(); diff --git a/server/src/services/alert_service.ts b/server/src/services/alert_service.ts index 3159d78..7980f7b 100644 --- a/server/src/services/alert_service.ts +++ b/server/src/services/alert_service.ts @@ -1,10 +1,11 @@ import { sendNotification, InfoMeta, AnalyticMeta, WebhookType, Notification } from './notification_service'; - -import * as _ from 'lodash'; import * as AnalyticUnit from '../models/analytic_units'; import { Segment } from '../models/segment_model'; import { availableReporter } from '../utils/reporter'; -import { ORG_ID } from '../config'; +import { ORG_ID, HASTIC_API_KEY, HASTIC_WEBHOOK_IMAGE_ENABLED } from '../config'; + +import axios from 'axios'; +import * as _ from 'lodash'; export class Alert { @@ -12,21 +13,52 @@ export class Alert { constructor(protected analyticUnit: AnalyticUnit.AnalyticUnit) {}; public receive(segment: Segment) { if(this.enabled) { - sendNotification(this.makeNotification(segment)); + this.send(segment); } }; - protected makeNotification(segment: Segment): Notification { + protected async send(segment) { + const notification = await this.makeNotification(segment); + sendNotification(notification); + } + + protected async makeNotification(segment: Segment): Promise { const meta = this.makeMeta(segment); const message = this.makeMessage(meta); - return { meta, message }; + let result: Notification = { meta, message }; + if(HASTIC_WEBHOOK_IMAGE_ENABLED) { + try { + const image = await this.loadImage(); + result.image = image; + } catch(err) { + console.error(`Can't load alert image: ${err}. Check that API key has admin permissions`); + } + } + + return result; + } + + protected async loadImage() { + const headers = { Authorization: `Bearer ${HASTIC_API_KEY}` }; + const dashdoardId = this.analyticUnit.panelId.split('/')[0]; + const panelId = this.analyticUnit.panelId.split('/')[1]; + const dashboardApiURL = `${this.analyticUnit.grafanaUrl}/api/dashboards/uid/${dashdoardId}`; + const dashboardInfo: any = await axios.get(dashboardApiURL, { headers }); + const dashboardName = _.last(dashboardInfo.data.meta.url.split('/')); + const renderUrl = `${this.analyticUnit.grafanaUrl}/render/d-solo/${dashdoardId}/${dashboardName}?panelId=${panelId}&ordId=${ORG_ID}&api-rendering`; + const response = await axios.get(renderUrl, { + headers, + responseType: 'arraybuffer' + }); + return new Buffer(response.data, 'binary').toString('base64'); } protected makeMeta(segment: Segment): AnalyticMeta { - const datshdoardId = this.analyticUnit.panelId.split('/')[0]; + const dashdoardId = this.analyticUnit.panelId.split('/')[0]; const panelId = this.analyticUnit.panelId.split('/')[1]; - const grafanaUrl = `${this.analyticUnit.grafanaUrl}/d/${datshdoardId}?panelId=${panelId}&edit=true&fullscreen=true?orgId=${ORG_ID}`; - const alert: AnalyticMeta = { + const grafanaUrl = `${this.analyticUnit.grafanaUrl}/d/${dashdoardId}?panelId=${panelId}&edit=true&fullscreen=true?orgId=${ORG_ID}`; + + let alert: AnalyticMeta = { type: WebhookType.DETECT, analyticUnitType: this.analyticUnit.type, analyticUnitName: this.analyticUnit.name, @@ -61,7 +93,7 @@ class PatternAlert extends Alert { if(this.lastSentSegment === undefined || !segment.equals(this.lastSentSegment) ) { this.lastSentSegment = segment; if(this.enabled) { - sendNotification(this.makeNotification(segment)); + this.send(segment); } } } @@ -89,14 +121,14 @@ class ThresholdAlert extends Alert { if(this.lastOccurence === 0) { this.lastOccurence = segment.from; if(this.enabled) { - sendNotification(this.makeNotification(segment)); + this.send(segment); } } else { if(segment.from - this.lastOccurence > this.EXPIRE_PERIOD_MS) { if(this.enabled) { console.log(`time between threshold occurences ${segment.from - this.lastOccurence}ms, send alert`); - sendNotification(this.makeNotification(segment)); + this.send(segment); } } @@ -169,7 +201,7 @@ export class AlertService { public sendGrafanaAvailableWebhook() { this._grafanaAvailableReporter(true); } - + public sendGrafanaUnavailableWebhook() { this._grafanaAvailableReporter(false); } diff --git a/server/src/services/notification_service.ts b/server/src/services/notification_service.ts index 81a5267..b27191e 100644 --- a/server/src/services/notification_service.ts +++ b/server/src/services/notification_service.ts @@ -25,8 +25,7 @@ export declare type AnalyticMeta = { grafanaUrl: string, from: number, to: number - message?: any, - regionImage?: any + message?: any } export declare type InfoMeta = { @@ -38,7 +37,8 @@ export declare type InfoMeta = { export declare type Notification = { message: string, - meta: InfoMeta | AnalyticMeta + meta: InfoMeta | AnalyticMeta, + image?: any } export async function sendNotification(notification: Notification) {