Browse Source

Hastic datasource #120 (#198)

master
rozetko 5 years ago committed by GitHub
parent
commit
134d48ce3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      build/webpack.base.conf.js
  2. 23
      src/config/config_ctrl.ts
  3. 13
      src/config/template.html
  4. 33
      src/datasource/config_ctrl.ts
  5. 37
      src/datasource/datasource.ts
  6. 29
      src/datasource/hastic_api.ts
  7. 10
      src/datasource/module.ts
  8. 87
      src/datasource/partials/config.html
  9. 0
      src/datasource/partials/query_ctrl.html
  10. 12
      src/datasource/plugin.json
  11. 12
      src/datasource/query_ctrl.ts
  12. 3
      src/panel/graph_panel/controllers/analytic_controller.ts
  13. 153
      src/panel/graph_panel/graph_ctrl.ts
  14. 4
      src/panel/graph_panel/models/info.ts
  15. 394
      src/panel/graph_panel/partials/tab_analytics.html
  16. 42
      src/panel/graph_panel/services/analytic_service.ts
  17. 3
      src/plugin.json
  18. 17
      src/utlis.ts

6
build/webpack.base.conf.js

@ -11,7 +11,8 @@ module.exports = {
context: resolve('src'),
entry: {
'./module': './module.ts',
'./panel/graph_panel/module': './panel/graph_panel/graph_ctrl.ts'
'./panel/graph_panel/module': './panel/graph_panel/graph_ctrl.ts',
'./datasource/module': './datasource/module.ts'
},
output: {
filename: '[name].js',
@ -35,7 +36,8 @@ module.exports = {
{ from: 'plugin.json' },
{ from: 'img/*' },
{ from: 'panel/graph_panel/plugin.json', to: 'panel/graph_panel/plugin.json' },
{ from: 'panel/graph_panel/partials/*' }
{ from: 'panel/graph_panel/partials/*' },
{ from: 'datasource/plugin.json', to: 'datasource/plugin.json' },
])
],
resolve: {

23
src/config/config_ctrl.ts

@ -1,36 +1,13 @@
import template from './template.html';
import { normalizeUrl } from '../utlis';
class ConfigCtrl {
static template = template;
appModel: any;
appEditCtrl: any;
constructor() {
if(this.appModel.jsonData === undefined) {
this.appModel.jsonData = {};
}
this.appEditCtrl.setPreUpdateHook(this.preUpdate.bind(this));
this.appEditCtrl.setPostUpdateHook(this.postUpdate.bind(this));
}
preUpdate() {
this.normalizeUrl();
return Promise.resolve();
}
postUpdate() {
// TODO: check whether hasticServerUrl is accessible
if(!this.appModel.enabled) {
return Promise.resolve();
}
return { message: 'Hastic app installed!' };
}
normalizeUrl() {
this.appModel.jsonData.hasticServerUrl = normalizeUrl(this.appModel.jsonData.hasticServerUrl);
}
}

13
src/config/template.html

@ -1,12 +1 @@
<h3 class="page-heading">Enter your Hastic config</h3>
<div class="gf-form-group">
<div class="gf-form">
<label class="gf-form-label width-10">Hastic server url</label>
<input
type="text"
class="gf-form-input max-width-20"
ng-model="ctrl.appModel.jsonData.hasticServerUrl"
ng-blur="ctrl.normalizeUrl"
/>
</div>
</div>

33
src/datasource/config_ctrl.ts

@ -0,0 +1,33 @@
import { normalizeUrl } from '../utlis';
import configTemplate from './partials/config.html';
export class HasticConfigCtrl {
public static template = configTemplate;
public ACCESS_OPTIONS = [
{ key: 'proxy', value: 'Server (Default)' },
{ key: 'direct', value: 'Browser' }
];
public showAccessHelp = false;
constructor(private $scope: any) {
if(this.$scope.current === undefined) {
this.$scope.current = {
url: '',
access: 'proxy'
};
}
}
normalizeUrl() {
if(this.$scope.current.url === '') {
return;
}
this.$scope.current.url = normalizeUrl(this.$scope.current.url);
}
toggleAccessHelp() {
this.showAccessHelp = !this.showAccessHelp;
}
}

37
src/datasource/datasource.ts

@ -0,0 +1,37 @@
import HasticAPI from './hastic_api';
import { BackendSrv } from 'grafana/app/core/services/backend_srv';
export class HasticDatasource {
private hastic: HasticAPI;
/** @ngInject */
constructor(instanceSettings: any, backendSrv: BackendSrv) {
this.hastic = new HasticAPI(instanceSettings, backendSrv);
}
async query(options: any) {
console.log(options);
}
async testDatasource() {
try {
await this.hastic.get('/');
// TODO: check if it is hastic
return {
status: 'success', title: 'Success',
message: 'Datasource is working'
};
} catch(err) {
console.error(err);
return {
status: 'error', title: 'Error',
message: 'Hastic connection error'
};
}
}
metricFindQuery(options: any) {
return [];
}
}

29
src/datasource/hastic_api.ts

@ -0,0 +1,29 @@
import { BackendSrv } from 'grafana/app/core/services/backend_srv';
export default class HasticAPI {
private url: string;
constructor(instanceSettings: any, private backendSrv: BackendSrv) {
this.url = instanceSettings.url;
}
get(url: string, params?: any) {
return this._query('GET', url, params);
}
private async _query(method: string, url: string, data?: any) {
method = method.toUpperCase();
let options: any = {
method,
url: this.url + url
};
if(method === 'GET' || method === 'DELETE') {
options.params = data;
} else {
options.data = data;
}
const response = await this.backendSrv.datasourceRequest(options);
return response.data;
}
}

10
src/datasource/module.ts

@ -0,0 +1,10 @@
import { HasticDatasource } from './datasource';
import { HasticQueryCtrl } from './query_ctrl';
import { HasticConfigCtrl } from './config_ctrl';
export {
HasticDatasource as Datasource,
HasticConfigCtrl as ConfigCtrl,
HasticQueryCtrl as QueryCtrl
};

87
src/datasource/partials/config.html

@ -0,0 +1,87 @@
<div class="gf-form-group">
<h3 class="page-heading">HTTP</h3>
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form max-width-30">
<span class="gf-form-label width-10">Hastic Server URL</span>
<input
class="gf-form-input" type="text"
placeholder="http://localhost:8000"
ng-model='ctrl.current.url'
min-length="0"
ng-pattern="/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/"
required
ng-blur="ctrl.normalizeUrl()"
/>
<info-popover mode="right-absolute">
<p>Specify a complete Hastic Server HTTP URL (for example http://your_hastic_server:8000)</p>
<span ng-show="ctrl.current.access === 'direct'">
Your access method is <em>Browser</em>, this means the URL
needs to be accessible from the browser.
</span>
<span ng-show="ctrl.current.access === 'proxy'">
Your access method is <em>Server</em>, this means the URL
needs to be accessible from the grafana backend/server.
</span>
</info-popover>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form max-width-30">
<span class="gf-form-label width-10">Access</span>
<div class="gf-form-select-wrapper max-width-24">
<select
class="gf-form-input"
ng-model="ctrl.current.access"
ng-options="f.key as f.value for f in ctrl.ACCESS_OPTIONS"
/>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword pointer" ng-click="ctrl.toggleAccessHelp()">
Help&nbsp;
<i class="fa fa-caret-down" ng-show="ctrl.showAccessHelp"></i>
<i class="fa fa-caret-right" ng-hide="ctrl.showAccessHelp">&nbsp;</i>
</label>
</div>
</div>
<div class="grafana-info-box m-t-2" ng-show="ctrl.showAccessHelp">
<p>
Access mode controls how requests to the data source will be handled.
<strong><i>Server</i></strong> access mode should be the preferred way if nothing else stated.
</p>
<div class="alert-title">Server access mode (Default):</div>
<p>
All requests will be made from the browser to Grafana backend/server which in turn will
forward the requests to the data source and by that circumvent possible
Cross-Origin Resource Sharing (CORS) requirements.
The URL needs to be accessible from the grafana backend/server if you select this access mode.
</p>
<div class="alert-title">Browser access mode:</div>
<p>
All requests will be made from the browser directly to the data source and may be subject to
Cross-Origin Resource Sharing (CORS) requirements. The URL needs to be accessible from the browser
if you select this access mode.
</p>
</div>
<div class="gf-form-inline" ng-if="ctrl.current.access=='proxy'">
<div class="gf-form">
<span class="gf-form-label width-10">Whitelisted Cookies</span>
<bootstrap-tagsinput
ng-model="ctrl.current.jsonData.keepCookies"
width-class="width-20" tagclass="label label-tag"
placeholder="Add Name"
/>
<info-popover mode="right-absolute">
Grafana Proxy deletes forwarded cookies by default. Specify cookies by name
that should be forwarded to the data source.
</info-popover>
</div>
</div>
</div>
</div>

0
src/datasource/partials/query_ctrl.html

12
src/datasource/plugin.json

@ -0,0 +1,12 @@
{
"type": "datasource",
"name": "Hastic Datasource",
"id": "corpglory-hastic-datasource",
"metrics": true,
"info": {
"logos": {
"small": "../corpglory-hastic-app/img/icn-graph-panel.png",
"large": "../corpglory-hastic-app/img/icn-graph-panel.png"
}
}
}

12
src/datasource/query_ctrl.ts

@ -0,0 +1,12 @@
import template from './partials/query_ctrl.html';
import { QueryCtrl } from 'grafana/app/plugins/sdk';
export class HasticQueryCtrl extends QueryCtrl {
static template = template;
/** @ngInject */
constructor($scope, $injector) {
super($scope, $injector);
}
}

3
src/panel/graph_panel/controllers/analytic_controller.ts

@ -430,6 +430,9 @@ export class AnalyticController {
);
for await (const data of statusGenerator) {
if(data === undefined) {
break;
}
let status = data.status;
let error = data.errorMessage;
if(analyticUnit.status !== status) {

153
src/panel/graph_panel/graph_ctrl.ts

@ -29,7 +29,6 @@ class GraphCtrl extends MetricsPanelCtrl {
seriesList: any = [];
dataList: any = [];
_backendUrl: string = undefined;
// annotations: any = [];
private _datasourceRequest: DatasourceRequest;
@ -51,10 +50,15 @@ class GraphCtrl extends MetricsPanelCtrl {
_panelInfo: PanelInfo;
private _analyticUnitTypes: any;
private _hasticDatasources: any[];
private $graphElem: any;
private $legendElem: any;
panelDefaults = {
// datasource name, null = default datasource
datasource: null,
hasticDatasource: null,
// sets client side (flot) or native graphite png renderer (png)
renderer: 'flot',
yaxes: [
@ -180,20 +184,16 @@ class GraphCtrl extends MetricsPanelCtrl {
this.rebindKeys();
}
async getBackendURL(): Promise<string> {
if(this._backendUrl !== undefined) {
return this._backendUrl;
}
var data = await this.backendSrv.get('api/plugins/corpglory-hastic-app/settings');
if(data.jsonData === undefined || data.jsonData === null) {
return undefined;
}
let val = data.jsonData.hasticServerUrl;
if(val === undefined || val === null) {
return undefined;
getBackendURL(): string {
const datasourceId = this.panel.hasticDatasource;
if(datasourceId !== undefined && datasourceId !== null) {
const datasource = _.find(this._hasticDatasources, { id: datasourceId });
if(datasource.access === 'proxy') {
return `/api/datasources/proxy/${datasource.id}`;
}
return datasource.url;
}
val = val.replace(/\/+$/, "");
return val;
return undefined;
}
async updateAnalyticUnitTypes() {
@ -210,53 +210,34 @@ class GraphCtrl extends MetricsPanelCtrl {
return _.keys(this._analyticUnitTypes);
}
private _checkBackendUrlOk(backendURL: string): boolean {
if(backendURL === null || backendURL === undefined || backendURL === '') {
appEvents.emit(
'alert-warning',
[
`hasticServerUrl is missing`,
`Please set it in config. More info: https://github.com/hastic/hastic-grafana-app/wiki/Getting-started`
]
);
return false;
}
return true;
}
async runBackendConnectivityCheck() {
let backendURL = await this.getBackendURL();
if(!this._checkBackendUrlOk(backendURL)) {
return;
const backendURL = this.getBackendURL();
try {
const connected = await this.analyticService.isBackendOk();
if(connected) {
this.updateAnalyticUnitTypes();
appEvents.emit(
'alert-success',
[
'Connected to Hastic server',
`Hastic server: "${backendURL}"`
]
);
}
}
let connected = await this.analyticService.isBackendOk();
if(connected) {
this.updateAnalyticUnitTypes();
appEvents.emit(
'alert-success',
[
'Connected to Hastic server',
`Hastic server: "${backendURL}"`
]
);
catch(err) {
console.error(err);
}
}
async link(scope, elem, attrs, ctrl) {
this._datasources = {};
this.processor = new DataProcessor(this.panel);
let backendURL = await this.getBackendURL();
if(!this._checkBackendUrlOk(backendURL)) {
return;
}
this.analyticService = new AnalyticService(backendURL, this.$http);
this.runBackendConnectivityCheck();
this.analyticsController = new AnalyticController(this.panel, this.analyticService, this.events);
this.$graphElem = $(elem[0]).find('#graphPanel');
this.$legendElem = $(elem[0]).find('#graphLegend');
this.onHasticDatasourceChange();
this.events.on('render', this.onRender.bind(this));
this.events.on('data-received', this.onDataReceived.bind(this));
@ -281,8 +262,6 @@ class GraphCtrl extends MetricsPanelCtrl {
this.$scope.$digest();
});
this._datasources = {};
appEvents.on('ds-request-response', data => {
let requestConfig = data.config;
@ -294,19 +273,6 @@ class GraphCtrl extends MetricsPanelCtrl {
type: undefined
};
});
this.analyticsController.fetchAnalyticUnitsStatuses();
var $graphElem = $(elem[0]).find('#graphPanel');
var $legendElem = $(elem[0]).find('#graphLegend');
this._graphRenderer = new GraphRenderer(
$graphElem, this.timeSrv, this.contextSrv, this.$scope
);
this._graphLegend = new GraphLegend($legendElem, this.popoverSrv, this.$scope);
this._updatePanelInfo();
this.analyticsController.updateServerInfo();
}
onInitEditMode() {
@ -329,6 +295,27 @@ class GraphCtrl extends MetricsPanelCtrl {
actions.push({ text: 'Toggle legend', click: 'ctrl.toggleLegend()' });
}
async onHasticDatasourceChange() {
this.processor = new DataProcessor(this.panel);
await this._fetchHasticDatasources();
const backendURL = this.getBackendURL();
this.analyticService = new AnalyticService(backendURL, this.$http);
this.runBackendConnectivityCheck();
this.analyticsController = new AnalyticController(this.panel, this.analyticService, this.events);
this.analyticsController.fetchAnalyticUnitsStatuses();
this._graphRenderer = new GraphRenderer(
this.$graphElem, this.timeSrv, this.contextSrv, this.$scope
);
this._graphLegend = new GraphLegend(this.$legendElem, this.popoverSrv, this.$scope);
this._updatePanelInfo();
this.analyticsController.updateServerInfo();
}
issueQueries(datasource) {
// this.annotationsPromise = this.annotationsSrv.getAnnotations({
// dashboard: this.dashboard,
@ -385,15 +372,17 @@ class GraphCtrl extends MetricsPanelCtrl {
}
}
var loadTasks = [
// this.annotationsPromise,
this.analyticsController.fetchAnalyticUnitsSegments(+this.range.from, +this.range.to)
];
if(this.analyticsController !== undefined) {
var loadTasks = [
// this.annotationsPromise,
this.analyticsController.fetchAnalyticUnitsSegments(+this.range.from, +this.range.to)
];
var results = await Promise.all(loadTasks);
this.loading = false;
// this.annotations = results[0].annotations;
this.render(this.seriesList);
await Promise.all(loadTasks);
this.loading = false;
// this.annotations = results[0].annotations;
this.render(this.seriesList);
}
}
@ -657,7 +646,7 @@ class GraphCtrl extends MetricsPanelCtrl {
private async _updatePanelInfo() {
const datasource = await this._getDatasourceByName(this.panel.datasource);
const backendUrl = await this.getBackendURL();
const backendUrl = this.getBackendURL();
let grafanaVersion = 'unknown';
if(_.has(window, 'grafanaBootData.settings.buildInfo.version')) {
@ -691,6 +680,16 @@ class GraphCtrl extends MetricsPanelCtrl {
}
}
private async _fetchHasticDatasources() {
this._hasticDatasources = await this.backendSrv.get('/api/datasources');
this._hasticDatasources = this._hasticDatasources.filter(ds => ds.type === 'corpglory-hastic-datasource');
this.$scope.$digest();
}
get hasticDatasources() {
return this._hasticDatasources;
}
get panelInfo() {
return this._panelInfo;
}

4
src/panel/graph_panel/models/info.ts

@ -2,9 +2,9 @@ export type ServerInfo = {
nodeVersion: string,
packageVersion: string,
npmUserAgent: string,
docker: boolean,
docker: string | boolean,
zmqConectionString: string,
serverPort: number,
serverPort: string | number,
gitBranch: string,
gitCommitHash: string
}

394
src/panel/graph_panel/partials/tab_analytics.html

@ -1,210 +1,224 @@
<div class="gf-form-button-row" ng-if="ctrl.analyticsController.serverStatus === false">
<h5>Hastic server at "{{ctrl.backendURL}}" is not available</h5>
<button class="btn btn-inverse" ng-click="ctrl.runBackendConnectivityCheck()">
<i class="fa fa-plug"></i>
Reconnect to Hastic server
</button>
<div class="gf-form-group">
<div class="gf-form">
<label class="gf-form-label">Select Hastic datasource</label>
<select class="gf-form-input max-width-15"
ng-model="ctrl.panel.hasticDatasource"
ng-options="ds.id as ds.name for ds in ctrl.hasticDatasources"
ng-change="ctrl.onHasticDatasourceChange()"
/>
</div>
</div>
<div ng-if="ctrl.analyticsController.serverStatus === true">
<h5> Analytic Units </h5>
<div class="editor-row">
<div class="gf-form" ng-repeat="analyticUnit in ctrl.analyticsController.analyticUnits">
<label class="gf-form-label width-5">
<i class="fa fa-info" bs-tooltip="'Analytic unit id: ' + analyticUnit.id"></i>
&nbsp; Name
</label>
<input
type="text" class="gf-form-input max-width-15"
ng-model="analyticUnit.name"
ng-change="ctrl.onAnalyticUnitNameChange(analyticUnit)"
>
<label class="gf-form-label width-4"> Type </label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input width-10"
ng-model="analyticUnit.type"
ng-options="type.value as type.name for type in ctrl.analyticUnitTypes[analyticUnit.detectorType]"
ng-disabled="true"
/>
</div>
<!--
<label class="gf-form-label width-6"> Confidence </label>
<input
type="number" class="gf-form-input width-5 ng-valid ng-scope ng-empty ng-dirty ng-valid-number ng-touched"
placeholder="auto" bs-tooltip="'Override automatic decimal precision for legend and tooltips'"
data-placement="right" ng-model="ctrl.panel.decimals" ng-change="ctrl.render()" ng-model-onblur="" data-original-title="" title=""
/>
-->
<label class="gf-form-label width-8"> Positive Color </label>
<span class="gf-form-label">
<color-picker
color="analyticUnit.labeledColor"
onChange="ctrl.onColorChange.bind(ctrl, analyticUnit.id, false)"
/>
</span>
<!-- Hack to avoid Grafana's .gf-form-label + .gf-form-label css. Fix for https://github.com/hastic/hastic-grafana-app/issues/192 -->
<div ng-if="analyticUnit.detectorType === 'pattern'"></div>
<label ng-if="analyticUnit.detectorType === 'pattern'" class="gf-form-label width-8"> Negative Color </label>
<span ng-if="analyticUnit.detectorType === 'pattern'" class="gf-form-label">
<color-picker
color="analyticUnit.deletedColor"
onChange="ctrl.onColorChange.bind(ctrl, analyticUnit.id, true)"
/>
</span>
<label
class="gf-form-label"
ng-if="analyticUnit.detectorType === 'pattern'"
ng-style="analyticUnit.status === 'LEARNING' && { 'cursor': 'not-allowed' }"
>
<a class="pointer" tabindex="1"
ng-click="ctrl.onToggleLabelingMode(analyticUnit.id)"
ng-disabled="analyticUnit.status === 'LEARNING'"
>
<i class="fa fa-bar-chart" ng-if="!analyticUnit.saving"></i>
<i class="fa fa-spinner fa-spin" ng-if="analyticUnit.saving"></i>
<b ng-if="analyticUnit.saving" ng-disabled="true"> saving... </b>
</a>
</label>
<select class="gf-form-input width-10"
ng-model="ctrl.analyticsController.labelingMode"
ng-options="type.value as type.name for type in [
{ name:'Label Positive', value: 'LABELING' },
{ name:'Label Negative', value: 'DELETING' },
{ name:'Unlabel', value: 'UNLABELING' }
]"
ng-if="analyticUnit.selected && !analyticUnit.saving"
ng-disabled="analyticUnit.status === 'LEARNING'"
/>
<select class="gf-form-input width-7"
ng-model="ctrl.analyticsController.getThreshold(analyticUnit.id).condition"
ng-options="type for type in ctrl.analyticsController.conditions"
ng-if="analyticUnit.detectorType === 'threshold'"
/>
<input
class="gf-form-input width-5"
type="number"
ng-model="ctrl.analyticsController.getThreshold(analyticUnit.id).value"
ng-if="
analyticUnit.detectorType === 'threshold' &&
ctrl.analyticsController.getThreshold(analyticUnit.id).condition != 'NO_DATA'
"
/>
<!-- TODO set .saving flag to thresholds, when learning is in progress -->
<button
class="btn btn-inverse"
ng-if="analyticUnit.detectorType === 'threshold'"
ng-click="ctrl.analyticsController.sendThresholdParamsToServer(analyticUnit.id)"
ng-disabled="analyticUnit.status === 'PENDING' || analyticUnit.status === 'LEARNING'"
>
Apply
</button>
<div class="gf-form">
<div class="gf-form-button-row" ng-if="ctrl.analyticsController.serverStatus === false">
<h5>Hastic server at "{{ctrl.backendURL}}" is not available</h5>
<button class="btn btn-inverse" ng-click="ctrl.runBackendConnectivityCheck()">
<i class="fa fa-plug"></i>
Reconnect to Hastic server
</button>
</div>
<label class="gf-form-label" ng-hide="analyticUnit.selected">
<a
ng-if="analyticUnit.visible"
bs-tooltip="'Hide. It`s visible now.'"
ng-click="ctrl.onToggleVisibility(analyticUnit.id)"
class="pointer"
>
<i class="fa fa-eye"></i>
</a>
<a
ng-if="!analyticUnit.visible"
bs-tooltip="'Show. It`s hidden now.'"
ng-click="ctrl.onToggleVisibility(analyticUnit.id)"
class="pointer"
<div ng-if="ctrl.analyticsController.serverStatus === true">
<h5> Analytic Units </h5>
<div class="editor-row">
<div class="gf-form" ng-repeat="analyticUnit in ctrl.analyticsController.analyticUnits">
<label class="gf-form-label width-5">
<i class="fa fa-info" bs-tooltip="'Analytic unit id: ' + analyticUnit.id"></i>
&nbsp; Name
</label>
<input
type="text" class="gf-form-input max-width-15"
ng-model="analyticUnit.name"
ng-change="ctrl.onAnalyticUnitNameChange(analyticUnit)"
>
<i class="fa fa-eye-slash"></i>
</a>
</label>
<label class="gf-form-label">
<a
ng-if="!analyticUnit.selected"
ng-click="ctrl.onRemove(analyticUnit.id)"
class="pointer"
>
<i class="fa fa-trash"></i>
</a>
<a
ng-if="analyticUnit.selected"
ng-click="ctrl.onCancelLabeling(analyticUnit.id)"
class="pointer"
<label class="gf-form-label width-4"> Type </label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input width-10"
ng-model="analyticUnit.type"
ng-options="type.value as type.name for type in ctrl.analyticUnitTypes[analyticUnit.detectorType]"
ng-disabled="true"
/>
</div>
<!--
<label class="gf-form-label width-6"> Confidence </label>
<input
type="number" class="gf-form-input width-5 ng-valid ng-scope ng-empty ng-dirty ng-valid-number ng-touched"
placeholder="auto" bs-tooltip="'Override automatic decimal precision for legend and tooltips'"
data-placement="right" ng-model="ctrl.panel.decimals" ng-change="ctrl.render()" ng-model-onblur="" data-original-title="" title=""
/>
-->
<label class="gf-form-label width-8"> Positive Color </label>
<span class="gf-form-label">
<color-picker
color="analyticUnit.labeledColor"
onChange="ctrl.onColorChange.bind(ctrl, analyticUnit.id, false)"
/>
</span>
<!-- Hack to avoid Grafana's .gf-form-label + .gf-form-label css. Fix for https://github.com/hastic/hastic-grafana-app/issues/192 -->
<div ng-if="analyticUnit.detectorType === 'pattern'"></div>
<label ng-if="analyticUnit.detectorType === 'pattern'" class="gf-form-label width-8"> Negative Color </label>
<span ng-if="analyticUnit.detectorType === 'pattern'" class="gf-form-label">
<color-picker
color="analyticUnit.deletedColor"
onChange="ctrl.onColorChange.bind(ctrl, analyticUnit.id, true)"
/>
</span>
<label
class="gf-form-label"
ng-if="analyticUnit.detectorType === 'pattern'"
ng-style="analyticUnit.status === 'LEARNING' && { 'cursor': 'not-allowed' }"
>
<i class="fa fa-ban"></i>
</a>
</label>
<label>
<i ng-if="analyticUnit.status === 'LEARNING'" class="grafana-tip fa fa-leanpub ng-scope" bs-tooltip="'Learning'"></i>
<i ng-if="analyticUnit.status === 'PENDING'" class="grafana-tip fa fa-list-ul ng-scope" bs-tooltip="'Pending'"></i>
<i ng-if="analyticUnit.status === 'FAILED'" class="grafana-tip fa fa-exclamation-circle ng-scope" bs-tooltip="'Error: ' + analyticUnit.error"></i>
</label>
<a class="pointer" tabindex="1"
ng-click="ctrl.onToggleLabelingMode(analyticUnit.id)"
ng-disabled="analyticUnit.status === 'LEARNING'"
>
<i class="fa fa-bar-chart" ng-if="!analyticUnit.saving"></i>
<i class="fa fa-spinner fa-spin" ng-if="analyticUnit.saving"></i>
<b ng-if="analyticUnit.saving" ng-disabled="true"> saving... </b>
</a>
</label>
</div>
</div>
<div class="editor-row" ng-if="ctrl.analyticsController.creatingNew">
<div class="gf-form">
<label class="gf-form-label width-4"> Name </label>
<input
type="text" class="gf-form-input max-width-15"
ng-model="ctrl.analyticsController.newAnalyticUnit.name"
>
<label class="gf-form-label width-8"> Detector Type </label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input width-10"
ng-model="ctrl.analyticsController.newAnalyticUnit.detectorType"
ng-options="analyticUnitDetectorType for analyticUnitDetectorType in ctrl.analyticUnitDetectorTypes"
ng-change="ctrl.analyticsController.onAnalyticUnitDetectorChange(ctrl.analyticUnitTypes);"
ng-model="ctrl.analyticsController.labelingMode"
ng-options="type.value as type.name for type in [
{ name:'Label Positive', value: 'LABELING' },
{ name:'Label Negative', value: 'DELETING' },
{ name:'Unlabel', value: 'UNLABELING' }
]"
ng-if="analyticUnit.selected && !analyticUnit.saving"
ng-disabled="analyticUnit.status === 'LEARNING'"
/>
</div>
<label class="gf-form-label width-8"> Type </label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input width-10"
ng-model="ctrl.analyticsController.newAnalyticUnit.type"
ng-options="
type.value as type.name
for type in ctrl.analyticUnitTypes[ctrl.analyticsController.newAnalyticUnit.detectorType]
<select class="gf-form-input width-7"
ng-model="ctrl.analyticsController.getThreshold(analyticUnit.id).condition"
ng-options="type for type in ctrl.analyticsController.conditions"
ng-if="analyticUnit.detectorType === 'threshold'"
/>
<input
class="gf-form-input width-5"
type="number"
ng-model="ctrl.analyticsController.getThreshold(analyticUnit.id).value"
ng-if="
analyticUnit.detectorType === 'threshold' &&
ctrl.analyticsController.getThreshold(analyticUnit.id).condition != 'NO_DATA'
"
/>
<!-- TODO set .saving flag to thresholds, when learning is in progress -->
<button
class="btn btn-inverse"
ng-if="analyticUnit.detectorType === 'threshold'"
ng-click="ctrl.analyticsController.sendThresholdParamsToServer(analyticUnit.id)"
ng-disabled="analyticUnit.status === 'PENDING' || analyticUnit.status === 'LEARNING'"
>
Apply
</button>
<label class="gf-form-label" ng-hide="analyticUnit.selected">
<a
ng-if="analyticUnit.visible"
bs-tooltip="'Hide. It`s visible now.'"
ng-click="ctrl.onToggleVisibility(analyticUnit.id)"
class="pointer"
>
<i class="fa fa-eye"></i>
</a>
<a
ng-if="!analyticUnit.visible"
bs-tooltip="'Show. It`s hidden now.'"
ng-click="ctrl.onToggleVisibility(analyticUnit.id)"
class="pointer"
>
<i class="fa fa-eye-slash"></i>
</a>
</label>
<label class="gf-form-label">
<a
ng-if="!analyticUnit.selected"
ng-click="ctrl.onRemove(analyticUnit.id)"
class="pointer"
>
<i class="fa fa-trash"></i>
</a>
<a
ng-if="analyticUnit.selected"
ng-click="ctrl.onCancelLabeling(analyticUnit.id)"
class="pointer"
>
<i class="fa fa-ban"></i>
</a>
</label>
<label>
<i ng-if="analyticUnit.status === 'LEARNING'" class="grafana-tip fa fa-leanpub ng-scope" bs-tooltip="'Learning'"></i>
<i ng-if="analyticUnit.status === 'PENDING'" class="grafana-tip fa fa-list-ul ng-scope" bs-tooltip="'Pending'"></i>
<i ng-if="analyticUnit.status === 'FAILED'" class="grafana-tip fa fa-exclamation-circle ng-scope" bs-tooltip="'Error: ' + analyticUnit.error"></i>
</label>
</div>
</div>
<div class="editor-row" ng-if="ctrl.analyticsController.creatingNew">
<div class="gf-form">
<label class="gf-form-label width-4"> Name </label>
<input
type="text" class="gf-form-input max-width-15"
ng-model="ctrl.analyticsController.newAnalyticUnit.name"
>
<label class="gf-form-label">
<a class="pointer" tabindex="1" ng-click="ctrl.saveNew()">
<b ng-if="!ctrl.analyticsController.saving"> create </b>
<b ng-if="ctrl.analyticsController.saving" ng-disabled="true"> saving... </b>
</a>
</label>
<label class="gf-form-label width-8"> Detector Type </label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input width-10"
ng-model="ctrl.analyticsController.newAnalyticUnit.detectorType"
ng-options="analyticUnitDetectorType for analyticUnitDetectorType in ctrl.analyticUnitDetectorTypes"
ng-change="ctrl.analyticsController.onAnalyticUnitDetectorChange(ctrl.analyticUnitTypes);"
/>
</div>
<label class="gf-form-label width-8"> Type </label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input width-10"
ng-model="ctrl.analyticsController.newAnalyticUnit.type"
ng-options="
type.value as type.name
for type in ctrl.analyticUnitTypes[ctrl.analyticsController.newAnalyticUnit.detectorType]
"
/>
</div>
<label class="gf-form-label">
<a class="pointer" tabindex="1" ng-click="ctrl.saveNew()">
<b ng-if="!ctrl.analyticsController.saving"> create </b>
<b ng-if="ctrl.analyticsController.saving" ng-disabled="true"> saving... </b>
</a>
</label>
</div>
</div>
</div>
<div class="gf-form-button-row" ng-if="!ctrl.analyticsController.creatingAnalyticUnit">
<button class="btn btn-inverse" ng-click="ctrl.createNew()">
<i class="fa fa-plus"></i>
Add Analytic Unit
</button>
</div>
<div class="gf-form-button-row">
<button class="gf-form-label width-10 pointer" ng-click="ctrl.showHelp = !ctrl.showHelp">
Show Help
<i class="fa fa-caret-down" ng-show="ctrl.showHelp"></i>
<i class="fa fa-caret-right" ng-hide="ctrl.showHelp"></i>
</button>
<div class="gf-form-button-row" ng-if="!ctrl.analyticsController.creatingAnalyticUnit">
<button class="btn btn-inverse" ng-click="ctrl.createNew()">
<i class="fa fa-plus"></i>
Add Analytic Unit
</button>
</div>
<div class="gf-form-button-row">
<button class="gf-form-label width-10 pointer" ng-click="ctrl.showHelp = !ctrl.showHelp">
Show Help
<i class="fa fa-caret-down" ng-show="ctrl.showHelp"></i>
<i class="fa fa-caret-right" ng-hide="ctrl.showHelp"></i>
</button>
</div>
</div>
</div>
<div class="gf-form" ng-show="ctrl.showHelp" ng-bind-html="ctrl.analyticsController.helpSectionText"></div>

42
src/panel/graph_panel/services/analytic_service.ts

@ -14,9 +14,7 @@ export class AnalyticService {
constructor(
private _backendURL: string,
private $http
) {
this.isBackendOk();
}
) { }
async getAnalyticUnitTypes() {
return await this.get('/analyticUnits/types');
@ -24,6 +22,9 @@ export class AnalyticService {
async getThresholds(ids: AnalyticUnitId[]) {
const resp = await this.get('/threshold', { ids: ids.join(',') });
if(resp === undefined) {
return [];
}
return resp.thresholds.filter(t => t !== null);
}
@ -61,6 +62,10 @@ export class AnalyticService {
}
async isBackendOk(): Promise<boolean> {
if(!this._checkBackendUrl()) {
this._isUp = false;
return false;
}
await this.get('/');
return this._isUp;
}
@ -135,7 +140,19 @@ export class AnalyticService {
}
async getServerInfo(): Promise<ServerInfo> {
let data = await this.get('/');
const data = await this.get('/');
if(data === undefined) {
return {
nodeVersion: 'unknown',
packageVersion: 'unknown',
npmUserAgent: 'unknown',
docker: 'unknown',
zmqConectionString: 'unknown',
serverPort: 'unknown',
gitBranch: 'unknown',
gitCommitHash: 'unknown'
};
}
return {
nodeVersion: data.nodeVersion,
packageVersion: data.packageVersion,
@ -184,8 +201,23 @@ export class AnalyticService {
} else {
this._isUp = true;
}
throw error;
// throw error;
}
}
private _checkBackendUrl(): boolean {
if(this._backendURL === null || this._backendURL === undefined || this._backendURL === '') {
appEvents.emit(
'alert-warning',
[
`Datasource (or URL in datasource) is missing`,
`Please set it in datasource config. More info: https://github.com/hastic/hastic-grafana-app/wiki/Getting-started`
]
);
return false;
}
return true;
}
private async get(url, params?) {

3
src/plugin.json

@ -14,7 +14,8 @@
"version": "0.3.0"
},
"includes": [
{ "type": "panel", "name": "Hastic Graph Panel" }
{ "type": "panel", "name": "Hastic Graph Panel" },
{ "type": "datasource", "name": "Hastic Datasource" }
],
"dependencies": {
"grafanaVersion": "5.4.x"

17
src/utlis.ts

@ -1,14 +1,15 @@
import url from 'url-parse';
export function normalizeUrl(grafanaUrl: string) {
if(!grafanaUrl) {
return grafanaUrl;
export function normalizeUrl(inputUrl: string) {
if(!inputUrl) {
return inputUrl;
}
let urlObj = new url(grafanaUrl, {});
let urlObj = new url(inputUrl, {});
if(urlObj.protocol !== 'http:' && urlObj.protocol !== 'https:') {
grafanaUrl = `http://${grafanaUrl}`;
urlObj = new url(grafanaUrl, {});
console.log('No protocol provided in GRAFANA_URL -> inserting "http://"');
inputUrl = `http://${inputUrl}`;
urlObj = new url(inputUrl, {});
console.log('No protocol provided in inputUrl -> inserting "http://"');
}
if(urlObj.slashes === false) {
urlObj = new url(`${urlObj.protocol}//${urlObj.pathname}`, {});
@ -16,7 +17,7 @@ export function normalizeUrl(grafanaUrl: string) {
}
if(urlObj.pathname.slice(-1) === '/') {
urlObj.pathname = urlObj.pathname.slice(0, -1);
console.log('Removing the slash at the end of GRAFANA_URL');
console.log('Removing the slash at the end of inputUrl');
}
let finalUrl = `${urlObj.protocol}//${urlObj.hostname}`;
if(urlObj.port !== '') {

Loading…
Cancel
Save