diff --git a/package.json b/package.json index 6404022..e9f9d04 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,10 @@ }, "devDependencies": { "@types/classnames": "^2.2.6", + "@grafana/data": "^6.4.3", + "@grafana/runtime": "^6.4.3", + "@grafana/toolkit": "^6.4.3", + "@grafana/ui": "^6.4.3", "@types/grafana": "github:CorpGlory/types-grafana", "@types/jest": "^23.1.1", "@types/jquery": "^3.3.0", diff --git a/pkg/datasource.go b/pkg/datasource.go index 794735d..91a03b9 100644 --- a/pkg/datasource.go +++ b/pkg/datasource.go @@ -37,6 +37,8 @@ func (b *ZabbixBackend) Query(ctx context.Context, tsdbReq *datasource.Datasourc switch queryType { case "zabbixAPI": return zabbixDs.ZabbixAPIQuery(ctx, tsdbReq) + case "zabbixConnectionTest": + return zabbixDs.TestConnection(ctx, tsdbReq) default: return nil, errors.New("Query is not implemented yet") } diff --git a/pkg/models.go b/pkg/models.go index 06ab7d0..f2a01d3 100644 --- a/pkg/models.go +++ b/pkg/models.go @@ -1 +1,11 @@ package main + +type connectionTestResponse struct { + ZabbixVersion string `json:"zabbixVersion"` + DbConnectorStatus dbConnectionStatus `json:"dbConnectorStatus"` +} + +type dbConnectionStatus struct { + dsType string + dsName string +} diff --git a/pkg/zabbix_api.go b/pkg/zabbix_api.go index bf68370..bdeef9b 100644 --- a/pkg/zabbix_api.go +++ b/pkg/zabbix_api.go @@ -79,7 +79,7 @@ func (ds *ZabbixDatasource) ZabbixAPIQuery(ctx context.Context, tsdbReq *datasou for _, query := range tsdbReq.Queries { json, err := simplejson.NewJson([]byte(query.ModelJson)) apiMethod := json.GetPath("target", "method").MustString() - apiParams := json.GetPath("target", "params") + apiParams := json.GetPath("target", "params").MustMap() if err != nil { return nil, err @@ -96,10 +96,11 @@ func (ds *ZabbixDatasource) ZabbixAPIQuery(ctx context.Context, tsdbReq *datasou jsonQuery := jsonQueries[0].Get("target") apiMethod := jsonQuery.Get("method").MustString() - apiParams := jsonQuery.Get("params") + apiParams := jsonQuery.Get("params").MustMap() - result, err := ds.ZabbixRequest(ctx, dsInfo, apiMethod, apiParams) - ds.queryCache.Set(HashString(tsdbReq.String()), result) + response, err := ds.ZabbixRequest(ctx, dsInfo, apiMethod, apiParams) + ds.queryCache.Set(HashString(tsdbReq.String()), response) + result = response if err != nil { ds.logger.Debug("ZabbixAPIQuery", "error", err) return nil, errors.New("ZabbixAPIQuery is not implemented yet") @@ -112,6 +113,41 @@ func (ds *ZabbixDatasource) ZabbixAPIQuery(ctx context.Context, tsdbReq *datasou return ds.BuildResponse(result.(*simplejson.Json)) } +// TestConnection checks authentication and version of the Zabbix API and returns that info +func (ds *ZabbixDatasource) TestConnection(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) { + dsInfo := tsdbReq.GetDatasource() + + auth, err := ds.loginWithDs(ctx, dsInfo) + if err != nil { + return nil, err + } + ds.authToken = auth + + response, err := ds.zabbixAPIRequest(ctx, dsInfo.GetUrl(), "apiinfo.version", map[string]interface{}{}, "") + if err != nil { + ds.logger.Debug("TestConnection", "error", err) + return nil, err + } + + resultByte, _ := response.MarshalJSON() + ds.logger.Debug("TestConnection", "result", string(resultByte)) + + testResponse := connectionTestResponse{ + ZabbixVersion: response.MustString(), + } + responseJSON, err := json.Marshal(testResponse) + if err != nil { + return nil, err + } + + responseSimpleJSON, err := simplejson.NewJson(responseJSON) + if err != nil { + return nil, err + } + + return ds.BuildResponse(responseSimpleJSON) +} + // BuildResponse transforms a Zabbix API response to a DatasourceResponse func (ds *ZabbixDatasource) BuildResponse(result *simplejson.Json) (*datasource.DatasourceResponse, error) { resultByte, err := result.MarshalJSON() @@ -130,7 +166,7 @@ func (ds *ZabbixDatasource) BuildResponse(result *simplejson.Json) (*datasource. } // ZabbixRequest checks authentication and makes a request to the Zabbix API -func (ds *ZabbixDatasource) ZabbixRequest(ctx context.Context, dsInfo *datasource.DatasourceInfo, method string, params *simplejson.Json) (*simplejson.Json, error) { +func (ds *ZabbixDatasource) ZabbixRequest(ctx context.Context, dsInfo *datasource.DatasourceInfo, method string, params map[string]interface{}) (*simplejson.Json, error) { zabbixURL := dsInfo.GetUrl() // Authenticate first @@ -170,17 +206,12 @@ func (ds *ZabbixDatasource) loginWithDs(ctx context.Context, dsInfo *datasource. return auth, nil } -func (ds *ZabbixDatasource) login(ctx context.Context, apiUrl string, username string, password string) (string, error) { +func (ds *ZabbixDatasource) login(ctx context.Context, apiURL string, username string, password string) (string, error) { params := map[string]interface{}{ "user": username, "password": password, } - paramsJson, err := json.Marshal(params) - if err != nil { - return "", err - } - data, _ := simplejson.NewJson(paramsJson) - auth, err := ds.zabbixAPIRequest(ctx, apiUrl, "user.login", data, "") + auth, err := ds.zabbixAPIRequest(ctx, apiURL, "user.login", params, "") if err != nil { return "", err } @@ -188,8 +219,8 @@ func (ds *ZabbixDatasource) login(ctx context.Context, apiUrl string, username s return auth.MustString(), nil } -func (ds *ZabbixDatasource) zabbixAPIRequest(ctx context.Context, apiUrl string, method string, params *simplejson.Json, auth string) (*simplejson.Json, error) { - zabbixUrl, err := url.Parse(apiUrl) +func (ds *ZabbixDatasource) zabbixAPIRequest(ctx context.Context, apiURL string, method string, params map[string]interface{}, auth string) (*simplejson.Json, error) { + zabbixURL, err := url.Parse(apiURL) // TODO: inject auth token (obtain from 'user.login' first) apiRequest := map[string]interface{}{ @@ -217,7 +248,7 @@ func (ds *ZabbixDatasource) zabbixAPIRequest(ctx context.Context, apiUrl string, req := &http.Request{ Method: "POST", - URL: zabbixUrl, + URL: zabbixURL, Header: map[string][]string{ "Content-Type": {"application/json"}, }, diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 18205ca..b7a3de3 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -9,13 +9,35 @@ import dataProcessor from './dataProcessor'; import responseHandler from './responseHandler'; import { Zabbix } from './zabbix/zabbix'; import { ZabbixAPIError } from './zabbix/connectors/zabbix_api/zabbixAPICore'; +import { + PluginMeta, + DataSourceApi, + DataSourceInstanceSettings, + DataQueryError, + DataQueryRequest, + DataQueryResponse, + AnnotationQueryRequest, + MetricFindValue, +} from '@grafana/ui'; +import { BackendSrv, DataSourceSrv } from '@grafana/runtime'; +import { ZabbixAlertingService } from './zabbixAlerting.service' +import { ZabbixConnectionTestQuery, ZabbixMetricsQuery, ZabbixConnectionInfo, TemplateSrv } from './types' -const DEFAULT_ZABBIX_VERSION = 3; +const DEFAULT_ZABBIX_VERSION = 3 -export class ZabbixDatasource { +export class ZabbixDatasource extends DataSourceApi { - /** @ngInject */ + /** + * @ngInject + * @param {DataSourceInstanceSettings} instanceSettings + * @param {TemplateSrv} templateSrv + * @param {BackendSrv} backendSrv + * @param {DataSourceSrv} datasourceSrv + * @param {ZabbixAlertingService} zabbixAlertingSrv + */ constructor(instanceSettings, templateSrv, backendSrv, datasourceSrv, zabbixAlertingSrv) { + super(instanceSettings) + this.type = 'zabbix' this.templateSrv = templateSrv; this.backendSrv = backendSrv; this.zabbixAlertingSrv = zabbixAlertingSrv; @@ -185,11 +207,26 @@ export class ZabbixDatasource { tsdbRequestData.to = options.range.to.valueOf().toString(); } - return this.backendSrv.datasourceRequest({ - url: '/api/tsdb/query', - method: 'POST', - data: tsdbRequestData - }); + return this.backendSrv.post('/api/tsdb/query', tsdbRequestData); + } + + /** + * @returns {Promise} + */ + doTSDBConnectionTest() { + /** + * @type {{ queries: ZabbixConnectionTestQuery[] }} + */ + const tsdbRequestData = { + queries: [ + { + datasourceId: this.datasourceId, + queryType: 'connectionTest' + } + ] + }; + + return this.backendSrv.post('/api/tsdb/query', tsdbRequestData); } /** @@ -386,8 +423,7 @@ export class ZabbixDatasource { * Test connection to Zabbix API and external history DB. */ testDatasource() { - return this.zabbix.testDataSource() - .then(result => { + return this.doTSDBConnectionTest().then(result => { const { zabbixVersion, dbConnectorStatus } = result; let message = `Zabbix API version: ${zabbixVersion}`; if (dbConnectorStatus) { @@ -398,8 +434,7 @@ export class ZabbixDatasource { title: "Success", message: message }; - }) - .catch(error => { + }).catch(error => { if (error instanceof ZabbixAPIError) { return { status: "error", diff --git a/src/datasource-zabbix/migrations.ts b/src/datasource-zabbix/migrations.ts index a4f60cc..01417d3 100644 --- a/src/datasource-zabbix/migrations.ts +++ b/src/datasource-zabbix/migrations.ts @@ -1,4 +1,5 @@ import _ from 'lodash'; +import { ZabbixMetricsQuery } from './types'; /** * Query format migration. @@ -19,7 +20,7 @@ export function isGrafana2target(target) { } } -export function migrateFrom2To3version(target) { +export function migrateFrom2To3version(target: ZabbixMetricsQuery) { target.group.filter = target.group.name === "*" ? "/.*/" : target.group.name; target.host.filter = target.host.name === "*" ? convertToRegex(target.hostFilter) : target.host.name; target.application.filter = target.application.name === "*" ? "" : target.application.name; diff --git a/src/datasource-zabbix/module.js b/src/datasource-zabbix/module.js index edddcdf..c1a8b76 100644 --- a/src/datasource-zabbix/module.js +++ b/src/datasource-zabbix/module.js @@ -1,4 +1,4 @@ -import { loadPluginCss } from 'grafana/app/plugins/sdk'; +import { loadPluginCss } from '@grafana/runtime'; import { ZabbixDatasource } from './datasource'; import { ZabbixQueryController } from './query.controller'; import { ZabbixDSConfigController } from './config.controller'; diff --git a/src/datasource-zabbix/types.ts b/src/datasource-zabbix/types.ts new file mode 100644 index 0000000..b4f59dc --- /dev/null +++ b/src/datasource-zabbix/types.ts @@ -0,0 +1,35 @@ +import { DataQuery } from '@grafana/ui'; + +export interface ZabbixConnectionInfo { + zabbixVersion: string; + dbConnectorStatus: { + dsType: string; + dsName: string; + }; +} + +export interface ZabbixConnectionTestQuery { + datasourceId: number; + queryType: string; +} + +export interface ZabbixMetricsQuery extends DataQuery { + triggers: { minSeverity: string; acknowledged: boolean; count: number; }; + queryType: string; + datasourceId: number; + functions: { name: string; params: any; def: { name: string; params: any; } }[]; + options: any; + textFilter: string; + mode: number; + itemids: number[]; + useCaptureGroups: boolean; + group: { filter: string; name: string; }; + host: { filter: string; name: string; }; + hostFilter: string; + application: { filter: string; name: string; }; + item: { filter: string; name: string; }; + itemFilter: string; +} + +export { TemplateSrv } from 'grafana/app/features/templating/template_srv'; +export { DashboardSrv } from 'grafana/app/features/dashboard/dashboard_srv'; diff --git a/src/test-setup/jest-setup.js b/src/test-setup/jest-setup.js index dedf7a2..5b2a000 100644 --- a/src/test-setup/jest-setup.js +++ b/src/test-setup/jest-setup.js @@ -18,6 +18,14 @@ jest.mock('angular', () => { }; }, {virtual: true}); +jest.mock('grafana/app/features/templating/template_srv', () => { + return {}; +}, {virtual: true}); + +jest.mock('grafana/app/features/dashboard/dashboard_srv', () => { + return {}; +}, {virtual: true}); + jest.mock('grafana/app/core/core_module', () => { return { directive: function() {}, @@ -28,7 +36,6 @@ let mockPanelCtrl = PanelCtrl; jest.mock('grafana/app/plugins/sdk', () => { return { QueryCtrl: null, - loadPluginCss: () => {}, PanelCtrl: mockPanelCtrl }; }, {virtual: true}); @@ -74,9 +81,9 @@ jest.mock('grafana/app/core/config', () => { jest.mock('jquery', () => 'module not found', {virtual: true}); -jest.mock('@grafana/ui', () => { - return {}; -}, {virtual: true}); +jest.mock('@grafana/ui'); + +jest.mock('@grafana/runtime'); // Required for loading angularjs let dom = new JSDOM('');