Browse Source

"Server is offline" error instead of "Unexpected error" #48 (#87)

master
sanke1 6 years ago committed by rozetko
parent
commit
ae597c4644
  1. 7
      src/controllers/analytic_controller.ts
  2. 17
      src/module.ts
  3. 280
      src/partials/tab_analytics.html
  4. 112
      src/services/analytic_service.ts

7
src/controllers/analytic_controller.ts

@ -132,7 +132,7 @@ export class AnalyticController {
}); });
this.labelingAnomaly.saving = false; this.labelingAnomaly.saving = false;
var anomaly = this.labelingAnomaly; var anomaly = this.labelingAnomaly;
this.dropLabeling(); this.dropLabeling();
this._runStatusWaiter(anomaly); this._runStatusWaiter(anomaly);
@ -256,7 +256,7 @@ export class AnalyticController {
} }
var rangeDist = +options.xaxis.max - +options.xaxis.min; var rangeDist = +options.xaxis.max - +options.xaxis.min;
let labeledSegmentBorderColor = tinycolor(LABELED_SEGMENT_BORDER_COLOR).toRgbString(); let labeledSegmentBorderColor = tinycolor(LABELED_SEGMENT_BORDER_COLOR).toRgbString();
labeledSegmentBorderColor = addAlphaToRGB(labeledSegmentBorderColor, REGION_STROKE_ALPHA); labeledSegmentBorderColor = addAlphaToRGB(labeledSegmentBorderColor, REGION_STROKE_ALPHA);
let deletedSegmentFillColor = tinycolor(DELETED_SEGMENT_FILL_COLOR).toRgbString(); let deletedSegmentFillColor = tinycolor(DELETED_SEGMENT_FILL_COLOR).toRgbString();
@ -402,6 +402,9 @@ export class AnalyticController {
return this._serverInfo; return this._serverInfo;
} }
public get serverStatus() {
return this._analyticService.isUp;
}
} }
function addAlphaToRGB(colorString: string, alpha: number): string { function addAlphaToRGB(colorString: string, alpha: number): string {

17
src/module.ts

@ -16,7 +16,6 @@ import { PanelInfo } from './models/info';
import { axesEditorComponent } from './axes_editor'; import { axesEditorComponent } from './axes_editor';
import { MetricsPanelCtrl, alertTab } from 'grafana/app/plugins/sdk'; import { MetricsPanelCtrl, alertTab } from 'grafana/app/plugins/sdk';
import { BackendSrv } from 'grafana/app/core/services/backend_srv';
import { appEvents } from 'grafana/app/core/core' import { appEvents } from 'grafana/app/core/core'
import config from 'grafana/app/core/config'; import config from 'grafana/app/core/config';
@ -28,6 +27,7 @@ const BACKEND_VARIABLE_NAME = 'HASTIC_SERVER_URL';
class GraphCtrl extends MetricsPanelCtrl { class GraphCtrl extends MetricsPanelCtrl {
static template = template; static template = template;
analyticService: AnalyticService;
hiddenSeries: any = {}; hiddenSeries: any = {};
seriesList: any = []; seriesList: any = [];
dataList: any = []; dataList: any = [];
@ -148,7 +148,7 @@ class GraphCtrl extends MetricsPanelCtrl {
/** @ngInject */ /** @ngInject */
constructor( constructor(
$scope, $injector, private annotationsSrv, $scope, $injector, $http, private annotationsSrv,
private keybindingSrv, private backendSrv, private keybindingSrv, private backendSrv,
private popoverSrv, private contextSrv, private popoverSrv, private contextSrv,
private alertSrv private alertSrv
@ -163,11 +163,11 @@ class GraphCtrl extends MetricsPanelCtrl {
this.processor = new DataProcessor(this.panel); this.processor = new DataProcessor(this.panel);
var anomalyService = new AnalyticService(this.backendURL, backendSrv as BackendSrv); this.analyticService = new AnalyticService(this.backendURL, $http, this.alertSrv);
this.runBackendConnectivityCheck(); this.runBackendConnectivityCheck();
this.analyticsController = new AnalyticController(this.panel, anomalyService, this.events); this.analyticsController = new AnalyticController(this.panel, this.analyticService, this.events);
this.anomalyTypes = this.panel.anomalyTypes; this.anomalyTypes = this.panel.anomalyTypes;
keybindingSrv.bind('d', this.onDKey.bind(this)); keybindingSrv.bind('d', this.onDKey.bind(this));
@ -224,11 +224,12 @@ class GraphCtrl extends MetricsPanelCtrl {
return; return;
} }
var as = new AnalyticService(this.backendURL, this.backendSrv); let connected = await this.analyticService.isBackendOk();
var isOK = await as.isBackendOk(); if(connected) {
if(!isOK) {
this.alertSrv.set( this.alertSrv.set(
'Can`t connect to Hastic server', `Hastic server: "${this.backendURL}"`, 'warning', 4000 'Connected to Hastic server',
`Hastic server: "${this.backendURL}"`,
'success', 4000
); );
} }
} }

280
src/partials/tab_analytics.html

@ -1,148 +1,160 @@
<h5> Analytic Units </h5> <div class="gf-form-button-row" ng-if="ctrl.analyticsController.serverStatus === false">
<div class="editor-row"> <h5>Hastic server at "{{ctrl.backendURL}}" is not available</h5>
<div class="gf-form" ng-repeat="analyticUnit in ctrl.analyticsController.analyticUnits"> <button class="btn btn-inverse" ng-click="ctrl.runBackendConnectivityCheck()">
<i class="fa fa-plug"></i>
<label class="gf-form-label width-4"> Name </label> Reconnect to Hastic server
<input </button>
type="text" class="gf-form-input max-width-15" </div>
ng-model="analyticUnit.name"
ng-disabled="true"
>
<label class="gf-form-label width-8"> Type </label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input width-12"
ng-model="analyticUnit.type"
ng-options="type.value as type.name for type in ctrl.panel.analyticUnitTypes"
ng-disabled="true"
/>
</div>
<!-- <div ng-if="ctrl.analyticsController.serverStatus === true">
<label class="gf-form-label width-6"> Confidence </label> <h5> Analytic Units </h5>
<input <div class="editor-row">
type="number" class="gf-form-input width-5 ng-valid ng-scope ng-empty ng-dirty ng-valid-number ng-touched" <div class="gf-form" ng-repeat="analyticUnit in ctrl.analyticsController.analyticUnits">
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-6"> Color </label>
<span class="gf-form-label">
<color-picker
color="analyticUnit.color"
onChange="ctrl.onColorChange.bind(ctrl, analyticUnit.id)"
/>
</span>
<label class="gf-form-label" ng-style="analyticUnit.status === 'LEARNING' && { 'cursor': 'not-allowed' }"> <label class="gf-form-label width-4"> Name </label>
<a class="pointer" tabindex="1" <input
ng-click="ctrl.onToggleLabelingMode(analyticUnit.id)" type="text" class="gf-form-input max-width-15"
ng-disabled="analyticUnit.status === 'LEARNING'" ng-model="analyticUnit.name"
> ng-disabled="true"
<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.selected && !analyticUnit.deleteMode && !analyticUnit.saving"> labeling </b>
<b ng-if="analyticUnit.selected && analyticUnit.deleteMode && !analyticUnit.saving"> deleting </b>
<b ng-if="analyticUnit.saving" ng-disabled="true"> saving... </b>
</a>
</label>
<!-- <label class="gf-form-label"> Alerts: </label>
<label
class="gf-form-label text-center"
style="width: 4rem"
ng-if="analyticUnit.alertEnabled === undefined"
bs-tooltip="'Alarting status isn`t available. Wait please.'"
>
<i class="fa fa-spinner fa-spin"></i>
</label> -->
<gf-form-switch
ng-if="analyticUnit.alertEnabled !== undefined"
on-change="ctrl.onAnomalyAlertChange(analyticUnit)"
checked="analyticUnit.alertEnabled"
style="height: 36px;"
/>
<label class="gf-form-label">
<a
ng-if="analyticUnit.visible"
ng-disabled="analyticUnit.selected"
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"
ng-disabled="analyticUnit.selected"
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 <label class="gf-form-label width-8"> Type </label>
ng-if="analyticUnit.selected" <div class="gf-form-select-wrapper">
ng-click="ctrl.onCancelLabeling(analyticUnit.id)" <select class="gf-form-input width-12"
class="pointer" ng-model="analyticUnit.type"
ng-options="type.value as type.name for type in ctrl.panel.analyticUnitTypes"
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-6"> Color </label>
<span class="gf-form-label">
<color-picker
color="analyticUnit.color"
onChange="ctrl.onColorChange.bind(ctrl, analyticUnit.id)"
/>
</span>
<label class="gf-form-label" 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.selected && !analyticUnit.deleteMode && !analyticUnit.saving"> labeling </b>
<b ng-if="analyticUnit.selected && analyticUnit.deleteMode && !analyticUnit.saving"> deleting </b>
<b ng-if="analyticUnit.saving" ng-disabled="true"> saving... </b>
</a>
</label>
<!-- <label class="gf-form-label"> Alerts: </label>
<label
class="gf-form-label text-center"
style="width: 4rem"
ng-if="analyticUnit.alertEnabled === undefined"
bs-tooltip="'Alarting status isn`t available. Wait please.'"
> >
<i class="fa fa-ban"></i> <i class="fa fa-spinner fa-spin"></i>
</a> </label> -->
</label>
<gf-form-switch
ng-if="analyticUnit.alertEnabled !== undefined"
on-change="ctrl.onAnomalyAlertChange(analyticUnit)"
checked="analyticUnit.alertEnabled"
style="height: 36px;"
/>
<label> <label class="gf-form-label">
<i ng-if="analyticUnit.status === 'LEARNING'" class="grafana-tip fa fa-leanpub ng-scope" bs-tooltip="'Learning'"></i> <a
<i ng-if="analyticUnit.status === 'PENDING'" class="grafana-tip fa fa-list-ul ng-scope" bs-tooltip="'Pending'"></i> ng-if="analyticUnit.visible"
<i ng-if="analyticUnit.status === 'FAILED'" class="grafana-tip fa fa-exclamation-circle ng-scope" bs-tooltip="'Error: ' + analyticUnit.error"></i> ng-disabled="analyticUnit.selected"
</label> 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"
ng-disabled="analyticUnit.selected"
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>
</div>
<div class="editor-row" ng-if="ctrl.analyticsController.creatingNew"> <div class="editor-row" ng-if="ctrl.analyticsController.creatingNew">
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-4"> Name </label> <label class="gf-form-label width-4"> Name </label>
<input <input
type="text" class="gf-form-input max-width-15" type="text" class="gf-form-input max-width-15"
ng-model="ctrl.analyticsController.newAnalyticUnit.name" ng-model="ctrl.analyticsController.newAnalyticUnit.name"
> >
<label class="gf-form-label width-8"> Type </label> <label class="gf-form-label width-8"> Type </label>
<div class="gf-form-select-wrapper"> <div class="gf-form-select-wrapper">
<select class="gf-form-input width-12" <select class="gf-form-input width-12"
ng-model="ctrl.analyticsController.newAnalyticUnit.type" ng-model="ctrl.analyticsController.newAnalyticUnit.type"
ng-options="type.value as type.name for type in ctrl.panel.analyticUnitTypes" ng-options="type.value as type.name for type in ctrl.panel.analyticUnitTypes"
/> />
</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>
<label class="gf-form-label"> <div class="gf-form-button-row" ng-if="!ctrl.analyticsController.creatingAnalyticUnit">
<a class="pointer" tabindex="1" ng-click="ctrl.saveNew()"> <button class="btn btn-inverse" ng-click="ctrl.createNew()">
<b ng-if="!ctrl.analyticsController.saving"> create </b> <i class="fa fa-plus"></i>
<b ng-if="ctrl.analyticsController.saving" ng-disabled="true"> saving... </b> Add Analytic Unit
</a> </button>
</label>
</div> </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>

112
src/services/analytic_service.ts

@ -5,46 +5,41 @@ import { SegmentsSet } from '../models/segment_set';
import { AnalyticUnitId, AnalyticUnit, AnalyticSegment } from '../models/analytic_unit'; import { AnalyticUnitId, AnalyticUnit, AnalyticSegment } from '../models/analytic_unit';
import { ServerInfo } from '../models/info'; import { ServerInfo } from '../models/info';
import { BackendSrv } from 'grafana/app/core/services/backend_srv';
export class AnalyticService { export class AnalyticService {
constructor(private _backendURL: string, private _backendSrv: BackendSrv) { private _isUp = false;
constructor(private _backendURL: string, private $http, private alertSrv) {
this.isBackendOk();
} }
async postNewItem( async postNewItem(
metric: MetricExpanded, datasourceRequest: DatasourceRequest, metric: MetricExpanded, datasourceRequest: DatasourceRequest,
newItem: AnalyticUnit, panelId: number newItem: AnalyticUnit, panelId: number
): Promise<AnalyticUnitId> { ): Promise<AnalyticUnitId> {
let datasource = await this._backendSrv.get(`/api/datasources/name/${metric.datasource}`); let datasource = await this.get(`/api/datasources/name/${metric.datasource}`);
datasourceRequest.type = datasource.type; datasourceRequest.type = datasource.type;
return this._backendSrv.post( const response = await this.post(this._backendURL + '/analyticUnits', {
this._backendURL + '/analyticUnits', panelUrl: window.location.origin + window.location.pathname + `?panelId=${panelId}&fullscreen`,
{ type: newItem.type,
panelUrl: window.location.origin + window.location.pathname + `?panelId=${panelId}&fullscreen`, name: newItem.name,
type: newItem.type, metric: metric.toJSON(),
name: newItem.name, datasource: datasourceRequest
metric: metric.toJSON(), });
datasource: datasourceRequest
} return response.id as AnalyticUnitId;
).then(res => res.id as AnalyticUnitId); }
};
async isBackendOk(): Promise<boolean> { async isBackendOk(): Promise<boolean> {
try { await this.get(this._backendURL);
var data = await this._backendSrv.get(this._backendURL);
// TODO: check version return this._isUp;
return true;
} catch(e) {
return false;
}
} }
async updateSegments( async updateSegments(
id: AnalyticUnitId, addedSegments: SegmentsSet<Segment>, removedSegments: SegmentsSet<Segment> id: AnalyticUnitId, addedSegments: SegmentsSet<Segment>, removedSegments: SegmentsSet<Segment>
): Promise<SegmentId[]> { ): Promise<SegmentId[]> {
const getJSONs = (segs: SegmentsSet<Segment>) => segs.getSegments().map(segment => ({ const getJSONs = (segs: SegmentsSet<Segment>) => segs.getSegments().map(segment => ({
from: segment.from, from: segment.from,
to: segment.to to: segment.to
@ -56,11 +51,10 @@ export class AnalyticService {
removedSegments: removedSegments.getSegments().map(s => s.id) removedSegments: removedSegments.getSegments().map(s => s.id)
}; };
var data = await this._backendSrv.patch(this._backendURL + '/segments', payload); var data = await this.patch(this._backendURL + '/segments', payload);
if(data.addedIds === undefined) { if(data.addedIds === undefined) {
throw new Error('Server didn`t send addedIds'); throw new Error('Server didn`t send addedIds');
} }
return data.addedIds as SegmentId[]; return data.addedIds as SegmentId[];
} }
@ -75,7 +69,7 @@ export class AnalyticService {
if(to !== undefined) { if(to !== undefined) {
payload['to'] = to; payload['to'] = to;
} }
var data = await this._backendSrv.get(this._backendURL + '/segments', payload); var data = await this.get(this._backendURL + '/segments', payload);
if(data.segments === undefined) { if(data.segments === undefined) {
throw new Error('Server didn`t return segments array'); throw new Error('Server didn`t return segments array');
} }
@ -88,9 +82,7 @@ export class AnalyticService {
throw new Error('id is undefined'); throw new Error('id is undefined');
} }
let statusCheck = async () => { let statusCheck = async () => {
var data = await this._backendSrv.get( var data = await this.get(this._backendURL + '/analyticUnits/status', { id });
this._backendURL + '/analyticUnits/status', { id }
);
return data; return data;
} }
@ -102,34 +94,29 @@ export class AnalyticService {
yield await statusCheck(); yield await statusCheck();
await timeout(); await timeout();
} }
} }
async getAlertEnabled(id: AnalyticUnitId): Promise<boolean> { async getAlertEnabled(id: AnalyticUnitId): Promise<boolean> {
if(id === undefined) { if(id === undefined) {
throw new Error('id is undefined'); throw new Error('id is undefined');
} }
var data = await this._backendSrv.get( var data = await this.get(this._backendURL + '/alerts', { id });
this._backendURL + '/alerts', { id }
);
if(data.enabled === undefined) { if(data.enabled === undefined) {
throw new Error('Server didn`t return "enabled"'); throw new Error('Server didn`t return "enabled"');
} }
return data.enabled as boolean; return data.enabled as boolean;
} }
async setAlertEnabled(id: AnalyticUnitId, enabled: boolean): Promise<void> { async setAlertEnabled(id: AnalyticUnitId, enabled: boolean): Promise<void> {
if(id === undefined) { if(id === undefined) {
throw new Error('id is undefined'); throw new Error('id is undefined');
} }
return this._backendSrv.post( return await this.post(this._backendURL + '/alerts', { id, enabled });
this._backendURL + '/alerts', { id, enabled }
);
} }
async getServerInfo(): Promise<ServerInfo> { async getServerInfo(): Promise<ServerInfo> {
let data = await this._backendSrv.get(this._backendURL); let data = await this.get(this._backendURL);
console.log(data);
return { return {
nodeVersion: data.nodeVersion, nodeVersion: data.nodeVersion,
packageVersion: data.packageVersion, packageVersion: data.packageVersion,
@ -142,4 +129,51 @@ export class AnalyticService {
}; };
} }
private async get(url, params?) {
try {
let response = await this.$http({ method: 'GET', url, params });
this._isUp = true;
return response.data;
} catch(error) {
this.displayConnectionAlert();
console.error(error);
this._isUp = false;
}
}
private async post(url, data) {
try {
let response = await this.$http({ method: 'POST', url, data });
this._isUp = true;
return response.data;
} catch(error) {
this.displayConnectionAlert();
console.error(error);
this._isUp = false;
}
}
private async patch(url, data) {
try {
let response = await this.$http({ method: 'PATCH', url, data });
this._isUp = true;
return response.data;
} catch(error) {
this.displayConnectionAlert();
console.error(error);
this._isUp = false;
}
}
private displayConnectionAlert() {
this.alertSrv.set(
'No connection to Hastic server',
`Hastic server: "${this._backendURL}"`,
'warning', 4000
);
}
public get isUp() {
return this._isUp;
}
} }

Loading…
Cancel
Save