diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index 871212adc6f..813a47f8fdf 100644 --- a/pkg/api/dataproxy.go +++ b/pkg/api/dataproxy.go @@ -63,6 +63,8 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string, targetUrl *url.URL) *ht req.Header.Add("Authorization", dsAuth) } + time.Sleep(time.Second * 5) + // clear cookie headers req.Header.Del("Cookie") req.Header.Del("Set-Cookie") diff --git a/public/app/core/services/backend_srv.js b/public/app/core/services/backend_srv.js deleted file mode 100644 index 3c96e3c69b4..00000000000 --- a/public/app/core/services/backend_srv.js +++ /dev/null @@ -1,175 +0,0 @@ -define([ - 'angular', - 'lodash', - '../core_module', - 'app/core/config', -], -function (angular, _, coreModule, config) { - 'use strict'; - - coreModule.default.service('backendSrv', function($http, alertSrv, $timeout, $q) { - var self = this; - - this.get = function(url, params) { - return this.request({ method: 'GET', url: url, params: params }); - }; - - this.delete = function(url) { - return this.request({ method: 'DELETE', url: url }); - }; - - this.post = function(url, data) { - return this.request({ method: 'POST', url: url, data: data }); - }; - - this.patch = function(url, data) { - return this.request({ method: 'PATCH', url: url, data: data }); - }; - - this.put = function(url, data) { - return this.request({ method: 'PUT', url: url, data: data }); - }; - - this._handleError = function(err) { - return function() { - if (err.isHandled) { - return; - } - - var data = err.data || { message: 'Unexpected error' }; - if (_.isString(data)) { - data = { message: data }; - } - - if (err.status === 422) { - alertSrv.set("Validation failed", data.message, "warning", 4000); - throw data; - } - - data.severity = 'error'; - - if (err.status < 500) { - data.severity = "warning"; - } - - if (data.message) { - alertSrv.set("Problem!", data.message, data.severity, 10000); - } - - throw data; - }; - }; - - this.request = function(options) { - options.retry = options.retry || 0; - var requestIsLocal = options.url.indexOf('/') === 0; - var firstAttempt = options.retry === 0; - - if (requestIsLocal && !options.hasSubUrl) { - options.url = config.appSubUrl + options.url; - options.hasSubUrl = true; - } - - return $http(options).then(function(results) { - if (options.method !== 'GET') { - if (results && results.data.message) { - alertSrv.set(results.data.message, '', 'success', 3000); - } - } - return results.data; - }, function(err) { - // handle unauthorized - if (err.status === 401 && firstAttempt) { - return self.loginPing().then(function() { - options.retry = 1; - return self.request(options); - }); - } - - $timeout(self._handleError(err), 50); - throw err; - }); - }; - - var datasourceInFlightRequests = {}; - var HTTP_REQUEST_ABORTED = -1; - this.datasourceRequest = function(options) { - options.retry = options.retry || 0; - - // A requestID is provided by the datasource as a unique identifier for a - // particular query. If the requestID exists, the promise it is keyed to - // is canceled, canceling the previous datasource request if it is still - // in-flight. - var canceler; - if (options.requestID) { - if (canceler = datasourceInFlightRequests[options.requestID]) { - canceler.resolve(); - } - canceler = $q.defer(); - options.timeout = canceler.promise; - datasourceInFlightRequests[options.requestID] = canceler; - } - - var requestIsLocal = options.url.indexOf('/') === 0; - var firstAttempt = options.retry === 0; - - if (requestIsLocal && options.headers && options.headers.Authorization) { - options.headers['X-DS-Authorization'] = options.headers.Authorization; - delete options.headers.Authorization; - } - - return $http(options).then(null, function(err) { - if (err.status === HTTP_REQUEST_ABORTED) { - // TODO: Hitting refresh before the original request returns cancels - // the "loading" animation on the panes, but it should continue to be - // visible. - err.statusText = "request aborted"; - return err; - } - - // handle unauthorized for backend requests - if (requestIsLocal && firstAttempt && err.status === 401) { - return self.loginPing().then(function() { - options.retry = 1; - if (canceler) { - canceler.resolve(); - } - return self.datasourceRequest(options); - }); - } - - //populate error obj on Internal Error - if (_.isString(err.data) && err.status === 500) { - err.data = { - error: err.statusText - }; - } - - // for Prometheus - if (!err.data.message && _.isString(err.data.error)) { - err.data.message = err.data.error; - } - - throw err; - }); - }; - - this.loginPing = function() { - return this.request({url: '/api/login/ping', method: 'GET', retry: 1 }); - }; - - this.search = function(query) { - return this.get('/api/search', query); - }; - - this.getDashboard = function(type, slug) { - return this.get('/api/dashboards/' + type + '/' + slug); - }; - - this.saveDashboard = function(dash, options) { - options = (options || {}); - return this.post('/api/dashboards/db/', {dashboard: dash, overwrite: options.overwrite === true}); - }; - - }); -}); diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts new file mode 100644 index 00000000000..412873f3677 --- /dev/null +++ b/public/app/core/services/backend_srv.ts @@ -0,0 +1,177 @@ +/// + +import angular from 'angular'; +import _ from 'lodash'; +import config from 'app/core/config'; +import coreModule from 'app/core/core_module'; + +export class BackendSrv { + inFlightRequests = {}; + HTTP_REQUEST_CANCELLED = -1; + + /** @ngInject */ + constructor(private $http, private alertSrv, private $rootScope, private $q, private $timeout) { + } + + get(url, params?) { + return this.request({ method: 'GET', url: url, params: params }); + } + + delete(url) { + return this.request({ method: 'DELETE', url: url }); + } + + post(url, data) { + return this.request({ method: 'POST', url: url, data: data }); + }; + + patch(url, data) { + return this.request({ method: 'PATCH', url: url, data: data }); + } + + put(url, data) { + return this.request({ method: 'PUT', url: url, data: data }); + } + + requestErrorHandler(err) { + if (err.isHandled) { + return; + } + + var data = err.data || { message: 'Unexpected error' }; + if (_.isString(data)) { + data = { message: data }; + } + + if (err.status === 422) { + this.alertSrv.set("Validation failed", data.message, "warning", 4000); + throw data; + } + + data.severity = 'error'; + + if (err.status < 500) { + data.severity = "warning"; + } + + if (data.message) { + this.alertSrv.set("Problem!", data.message, data.severity, 10000); + } + + throw data; + } + + request(options) { + options.retry = options.retry || 0; + var requestIsLocal = options.url.indexOf('/') === 0; + var firstAttempt = options.retry === 0; + + if (requestIsLocal && !options.hasSubUrl) { + options.url = config.appSubUrl + options.url; + options.hasSubUrl = true; + } + + return this.$http(options).then(results => { + if (options.method !== 'GET') { + if (results && results.data.message) { + this.alertSrv.set(results.data.message, '', 'success', 3000); + } + } + return results.data; + }, err => { + // handle unauthorized + if (err.status === 401 && firstAttempt) { + return this.loginPing().then(() => { + options.retry = 1; + return this.request(options); + }); + } + + this.$timeout(this.requestErrorHandler.bind(this), 50); + throw err; + }); + }; + + datasourceRequest(options) { + options.retry = options.retry || 0; + + // A requestID is provided by the datasource as a unique identifier for a + // particular query. If the requestID exists, the promise it is keyed to + // is canceled, canceling the previous datasource request if it is still + // in-flight. + var canceler; + if (options.requestId) { + canceler = this.inFlightRequests[options.requestId]; + if (canceler) { + canceler.resolve(); + } + // create new canceler + canceler = this.$q.defer(); + options.timeout = canceler.promise; + this.inFlightRequests[options.requestId] = canceler; + } + + var requestIsLocal = options.url.indexOf('/') === 0; + var firstAttempt = options.retry === 0; + + if (requestIsLocal && options.headers && options.headers.Authorization) { + options.headers['X-DS-Authorization'] = options.headers.Authorization; + delete options.headers.Authorization; + } + + return this.$http(options).catch(err => { + if (err.status === this.HTTP_REQUEST_CANCELLED) { + throw {err, cancelled: true}; + } + + // handle unauthorized for backend requests + if (requestIsLocal && firstAttempt && err.status === 401) { + return this.loginPing().then(() => { + options.retry = 1; + if (canceler) { + canceler.resolve(); + } + return this.datasourceRequest(options); + }); + } + + //populate error obj on Internal Error + if (_.isString(err.data) && err.status === 500) { + err.data = { + error: err.statusText + }; + } + + // for Prometheus + if (!err.data.message && _.isString(err.data.error)) { + err.data.message = err.data.error; + } + + throw err; + }).finally(() => { + // clean up + if (options.requestId) { + delete this.inFlightRequests[options.requestId]; + } + }); + }; + + loginPing() { + return this.request({url: '/api/login/ping', method: 'GET', retry: 1 }); + } + + search(query) { + return this.get('/api/search', query); + } + + getDashboard(type, slug) { + return this.get('/api/dashboards/' + type + '/' + slug); + } + + saveDashboard(dash, options) { + options = (options || {}); + return this.post('/api/dashboards/db/', {dashboard: dash, overwrite: options.overwrite === true}); + } +} + +coreModule.service('backendSrv', BackendSrv); diff --git a/public/app/features/panel/metrics_panel_ctrl.ts b/public/app/features/panel/metrics_panel_ctrl.ts index 4d0fddf58ec..269559bc22a 100644 --- a/public/app/features/panel/metrics_panel_ctrl.ts +++ b/public/app/features/panel/metrics_panel_ctrl.ts @@ -12,7 +12,7 @@ import * as dateMath from 'app/core/utils/datemath'; import {Subject} from 'vendor/npm/rxjs/Subject'; class MetricsPanelCtrl extends PanelCtrl { - error: boolean; + error: any; loading: boolean; datasource: any; datasourceName: any; @@ -86,8 +86,14 @@ class MetricsPanelCtrl extends PanelCtrl { .then(this.issueQueries.bind(this)) .then(this.handleQueryResult.bind(this)) .catch(err => { + // if cancelled keep loading set to true + if (err.cancelled) { + console.log('Panel request cancelled', err); + return; + } + this.loading = false; - this.error = err.message || "Timeseries data request error"; + this.error = err.message || "Request Error"; this.inspector = {error: err}; this.events.emit('data-error', err); console.log('Panel data error:', err); @@ -182,10 +188,6 @@ class MetricsPanelCtrl extends PanelCtrl { cacheTimeout: this.panel.cacheTimeout }; - metricsQuery.targets.forEach(function(target) { - target.exprID = target.refId + metricsQuery.panelId; - }); - return datasource.query(metricsQuery); } diff --git a/public/app/plugins/datasource/graphite/datasource.ts b/public/app/plugins/datasource/graphite/datasource.ts index 489d62f686f..efb107e3b16 100644 --- a/public/app/plugins/datasource/graphite/datasource.ts +++ b/public/app/plugins/datasource/graphite/datasource.ts @@ -30,13 +30,17 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv return $q.when({data: []}); } - var httpOptions: any = {method: this.render_method, url: '/render'}; + var httpOptions: any = { + method: 'POST', + url: '/render', + data: params.join('&'), + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + }; - if (httpOptions.method === 'GET') { - httpOptions.url = httpOptions.url + '?' + params.join('&'); - } else { - httpOptions.data = params.join('&'); - httpOptions.headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; + if (options.panelId) { + httpOptions.requestId = 'panel' + options.panelId; } return this.doGraphiteRequest(httpOptions).then(this.convertDataPointsToMs); @@ -177,17 +181,6 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv }); }; - this.listDashboards = function(query) { - return this.doGraphiteRequest({ method: 'GET', url: '/dashboard/find/', params: {query: query || ''} }) - .then(function(results) { - return results.data.dashboards; - }); - }; - - this.loadDashboard = function(dashName) { - return this.doGraphiteRequest({method: 'GET', url: '/dashboard/load/' + encodeURIComponent(dashName) }); - }; - this.doGraphiteRequest = function(options) { if (this.basicAuth || this.withCredentials) { options.withCredentials = true; @@ -198,7 +191,7 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv } options.url = this.url + options.url; - options.inspect = { type: 'graphite' }; + options.inspect = {type: 'graphite'}; return backendSrv.datasourceRequest(options); }; diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 3548b4fedb2..4284ce78e8f 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -21,11 +21,11 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS this.withCredentials = instanceSettings.withCredentials; this.lastErrors = {}; - this._request = function(method, url, requestID) { + this._request = function(method, url, requestId) { var options: any = { url: this.url + url, method: method, - requestID: requestID, + requestId: requestId, }; if (this.basicAuth || this.withCredentials) { @@ -77,7 +77,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS var query: any = {}; query.expr = templateSrv.replace(target.expr, options.scopedVars, self.interpolateQueryExpr); - query.requestID = target.exprID; + query.requestId = target.expr; var interval = target.interval || options.interval; var intervalFactor = target.intervalFactor || 1; @@ -103,14 +103,10 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS return this.performTimeSeriesQuery(query, start, end); }, this)); - return $q.all(allQueryPromise) - .then(function(allResponse) { + return $q.all(allQueryPromise).then(function(allResponse) { var result = []; _.each(allResponse, function(response, index) { - if (response.status === HTTP_REQUEST_ABORTED) { - return; - } if (response.status === 'error') { self.lastErrors.query = response.error; throw response.error; @@ -128,7 +124,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS this.performTimeSeriesQuery = function(query, start, end) { var url = '/api/v1/query_range?query=' + encodeURIComponent(query.expr) + '&start=' + start + '&end=' + end + '&step=' + query.step; - return this._request('GET', url, query.requestID); + return this._request('GET', url, query.requestId); }; this.performSuggestQuery = function(query) { @@ -175,9 +171,11 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS expr: interpolated, step: '60s' }; + var start = getPrometheusTime(options.range.from, false); var end = getPrometheusTime(options.range.to, true); var self = this; + return this.performTimeSeriesQuery(query, start, end).then(function(results) { var eventList = []; tagKeys = tagKeys.split(',');