mirror of
https://github.com/grafana/grafana.git
synced 2025-02-20 11:48:34 -06:00
378 lines
9.5 KiB
TypeScript
378 lines
9.5 KiB
TypeScript
///<reference path="../../headers/common.d.ts" />
|
|
|
|
import _ from "lodash";
|
|
import coreModule from "app/core/core_module";
|
|
import appEvents from "app/core/app_events";
|
|
import { DashboardModel } from "app/features/dashboard/dashboard_model";
|
|
|
|
export class BackendSrv {
|
|
private inFlightRequests = {};
|
|
private HTTP_REQUEST_CANCELLED = -1;
|
|
private noBackendCache: boolean;
|
|
|
|
/** @ngInject */
|
|
constructor(
|
|
private $http,
|
|
private alertSrv,
|
|
private $q,
|
|
private $timeout,
|
|
private contextSrv
|
|
) {}
|
|
|
|
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 });
|
|
}
|
|
|
|
withNoBackendCache(callback) {
|
|
this.noBackendCache = true;
|
|
return callback().finally(() => {
|
|
this.noBackendCache = false;
|
|
});
|
|
}
|
|
|
|
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) {
|
|
let description = "";
|
|
let message = data.message;
|
|
if (message.length > 80) {
|
|
description = message;
|
|
message = "Error";
|
|
}
|
|
this.alertSrv.set(message, description, data.severity, 10000);
|
|
}
|
|
|
|
throw data;
|
|
}
|
|
|
|
request(options) {
|
|
options.retry = options.retry || 0;
|
|
var requestIsLocal = !options.url.match(/^http/);
|
|
var firstAttempt = options.retry === 0;
|
|
|
|
if (requestIsLocal) {
|
|
if (this.contextSrv.user && this.contextSrv.user.orgId) {
|
|
options.headers = options.headers || {};
|
|
options.headers["X-Grafana-Org-Id"] = this.contextSrv.user.orgId;
|
|
}
|
|
|
|
if (options.url.indexOf("/") === 0) {
|
|
options.url = options.url.substring(1);
|
|
}
|
|
}
|
|
|
|
return this.$http(options).then(
|
|
results => {
|
|
if (options.method !== "GET") {
|
|
if (results && results.data.message) {
|
|
if (options.showSuccessAlert !== false) {
|
|
this.alertSrv.set(results.data.message, "", "success", 3000);
|
|
}
|
|
}
|
|
}
|
|
return results.data;
|
|
},
|
|
err => {
|
|
// handle unauthorized
|
|
if (
|
|
err.status === 401 &&
|
|
this.contextSrv.user.isSignedIn &&
|
|
firstAttempt
|
|
) {
|
|
return this.loginPing().then(() => {
|
|
options.retry = 1;
|
|
return this.request(options);
|
|
});
|
|
}
|
|
|
|
this.$timeout(this.requestErrorHandler.bind(this, err), 50);
|
|
throw err;
|
|
}
|
|
);
|
|
}
|
|
|
|
addCanceler(requestId, canceler) {
|
|
if (requestId in this.inFlightRequests) {
|
|
this.inFlightRequests[requestId].push(canceler);
|
|
} else {
|
|
this.inFlightRequests[requestId] = [canceler];
|
|
}
|
|
}
|
|
|
|
resolveCancelerIfExists(requestId) {
|
|
var cancelers = this.inFlightRequests[requestId];
|
|
if (!_.isUndefined(cancelers) && cancelers.length) {
|
|
cancelers[0].resolve();
|
|
}
|
|
}
|
|
|
|
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 requestId = options.requestId;
|
|
if (requestId) {
|
|
this.resolveCancelerIfExists(requestId);
|
|
// create new canceler
|
|
var canceler = this.$q.defer();
|
|
options.timeout = canceler.promise;
|
|
this.addCanceler(requestId, canceler);
|
|
}
|
|
|
|
var requestIsLocal = !options.url.match(/^http/);
|
|
var firstAttempt = options.retry === 0;
|
|
|
|
if (requestIsLocal) {
|
|
if (this.contextSrv.user && this.contextSrv.user.orgId) {
|
|
options.headers = options.headers || {};
|
|
options.headers["X-Grafana-Org-Id"] = this.contextSrv.user.orgId;
|
|
}
|
|
|
|
if (options.url.indexOf("/") === 0) {
|
|
options.url = options.url.substring(1);
|
|
}
|
|
|
|
if (options.headers && options.headers.Authorization) {
|
|
options.headers["X-DS-Authorization"] = options.headers.Authorization;
|
|
delete options.headers.Authorization;
|
|
}
|
|
|
|
if (this.noBackendCache) {
|
|
options.headers["X-Grafana-NoCache"] = "true";
|
|
}
|
|
}
|
|
|
|
return this.$http(options)
|
|
.then(response => {
|
|
appEvents.emit("ds-request-response", response);
|
|
return response;
|
|
})
|
|
.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,
|
|
response: err.data
|
|
};
|
|
}
|
|
|
|
// for Prometheus
|
|
if (err.data && !err.data.message && _.isString(err.data.error)) {
|
|
err.data.message = err.data.error;
|
|
}
|
|
|
|
appEvents.emit("ds-request-error", err);
|
|
throw err;
|
|
})
|
|
.finally(() => {
|
|
// clean up
|
|
if (options.requestId) {
|
|
this.inFlightRequests[options.requestId].shift();
|
|
}
|
|
});
|
|
}
|
|
|
|
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,
|
|
folderId: dash.folderId,
|
|
overwrite: options.overwrite === true,
|
|
message: options.message || ""
|
|
});
|
|
}
|
|
|
|
createDashboardFolder(name) {
|
|
const dash = {
|
|
schemaVersion: 16,
|
|
title: name.trim(),
|
|
editable: true,
|
|
panels: []
|
|
};
|
|
|
|
return this.post("/api/dashboards/db/", {
|
|
dashboard: dash,
|
|
isFolder: true,
|
|
overwrite: false
|
|
}).then(res => {
|
|
return this.getDashboard("db", res.slug);
|
|
});
|
|
}
|
|
|
|
deleteDashboard(slug) {
|
|
let deferred = this.$q.defer();
|
|
|
|
this.getDashboard("db", slug).then(fullDash => {
|
|
this.delete(`/api/dashboards/db/${slug}`)
|
|
.then(() => {
|
|
deferred.resolve(fullDash);
|
|
})
|
|
.catch(err => {
|
|
deferred.reject(err);
|
|
});
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
deleteDashboards(dashboardSlugs) {
|
|
const tasks = [];
|
|
|
|
for (let slug of dashboardSlugs) {
|
|
tasks.push(this.createTask(this.deleteDashboard.bind(this), true, slug));
|
|
}
|
|
|
|
return this.executeInOrder(tasks, []);
|
|
}
|
|
|
|
moveDashboards(dashboardSlugs, toFolder) {
|
|
const tasks = [];
|
|
|
|
for (let slug of dashboardSlugs) {
|
|
tasks.push(
|
|
this.createTask(this.moveDashboard.bind(this), true, slug, toFolder)
|
|
);
|
|
}
|
|
|
|
return this.executeInOrder(tasks, []).then(result => {
|
|
return {
|
|
totalCount: result.length,
|
|
successCount: _.filter(result, { succeeded: true }).length,
|
|
alreadyInFolderCount: _.filter(result, { alreadyInFolder: true }).length
|
|
};
|
|
});
|
|
}
|
|
|
|
private moveDashboard(slug, toFolder) {
|
|
let deferred = this.$q.defer();
|
|
|
|
this.getDashboard("db", slug).then(fullDash => {
|
|
const model = new DashboardModel(fullDash.dashboard, fullDash.meta);
|
|
|
|
if (
|
|
(!fullDash.meta.folderId && toFolder.id === 0) ||
|
|
fullDash.meta.folderId === toFolder.id
|
|
) {
|
|
deferred.resolve({ alreadyInFolder: true });
|
|
return;
|
|
}
|
|
|
|
model.folderId = toFolder.id;
|
|
model.meta.folderId = toFolder.id;
|
|
model.meta.folderTitle = toFolder.title;
|
|
const clone = model.getSaveModelClone();
|
|
|
|
this.saveDashboard(clone, {})
|
|
.then(() => {
|
|
deferred.resolve({ succeeded: true });
|
|
})
|
|
.catch(err => {
|
|
if (err.data && err.data.status === "plugin-dashboard") {
|
|
err.isHandled = true;
|
|
|
|
this.saveDashboard(clone, { overwrite: true })
|
|
.then(() => {
|
|
deferred.resolve({ succeeded: true });
|
|
})
|
|
.catch(err => {
|
|
deferred.resolve({ succeeded: false });
|
|
});
|
|
} else {
|
|
deferred.resolve({ succeeded: false });
|
|
}
|
|
});
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
private createTask(fn, ignoreRejections, ...args: any[]) {
|
|
return result => {
|
|
return fn
|
|
.apply(null, args)
|
|
.then(res => {
|
|
return Array.prototype.concat(result, [res]);
|
|
})
|
|
.catch(err => {
|
|
if (ignoreRejections) {
|
|
return result;
|
|
}
|
|
|
|
throw err;
|
|
});
|
|
};
|
|
}
|
|
|
|
private executeInOrder(tasks, initialValue) {
|
|
return tasks.reduce(this.$q.when, initialValue);
|
|
}
|
|
}
|
|
|
|
coreModule.service("backendSrv", BackendSrv);
|