2022-07-05 12:53:41 -05:00
|
|
|
import { DataFrame, dataFrameFromJSON, DataFrameJSON, getDisplayProcessor } from '@grafana/data';
|
|
|
|
import { config, getBackendSrv } from '@grafana/runtime';
|
2022-07-14 17:36:17 -05:00
|
|
|
import { backendSrv } from 'app/core/services/backend_srv';
|
|
|
|
import { DashboardDTO } from 'app/types';
|
2022-07-05 12:53:41 -05:00
|
|
|
|
2022-07-29 01:26:44 -05:00
|
|
|
import { UploadReponse, StorageInfo, ItemOptions, WriteValueRequest, WriteValueResponse } from './types';
|
2022-07-05 12:53:41 -05:00
|
|
|
|
|
|
|
// Likely should be built into the search interface!
|
|
|
|
export interface GrafanaStorage {
|
|
|
|
get: <T = any>(path: string) => Promise<T>;
|
|
|
|
list: (path: string) => Promise<DataFrame | undefined>;
|
2022-07-12 09:13:57 -05:00
|
|
|
upload: (folder: string, file: File, overwriteExistingFile: boolean) => Promise<UploadReponse>;
|
2022-07-08 13:23:16 -05:00
|
|
|
createFolder: (path: string) => Promise<{ error?: string }>;
|
|
|
|
delete: (path: { isFolder: boolean; path: string }) => Promise<{ error?: string }>;
|
2022-07-14 17:36:17 -05:00
|
|
|
|
2022-07-29 01:26:44 -05:00
|
|
|
/** Admin only */
|
|
|
|
getConfig: () => Promise<StorageInfo[]>;
|
|
|
|
|
|
|
|
/** Called before save */
|
|
|
|
getOptions: (path: string) => Promise<ItemOptions>;
|
|
|
|
|
2022-07-14 17:36:17 -05:00
|
|
|
/**
|
|
|
|
* Temporary shim that will return a DashboardDTO shape for files in storage
|
|
|
|
* Longer term, this will call an "Entity API" that is eventually backed by storage
|
|
|
|
*/
|
|
|
|
getDashboard: (path: string) => Promise<DashboardDTO>;
|
2022-07-29 01:26:44 -05:00
|
|
|
|
|
|
|
/** Saves dashbaords */
|
|
|
|
write: (path: string, options: WriteValueRequest) => Promise<WriteValueResponse>;
|
2022-07-05 12:53:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
class SimpleStorage implements GrafanaStorage {
|
|
|
|
constructor() {}
|
|
|
|
|
|
|
|
async get<T = any>(path: string): Promise<T> {
|
|
|
|
const storagePath = `api/storage/read/${path}`.replace('//', '/');
|
|
|
|
return getBackendSrv().get<T>(storagePath);
|
|
|
|
}
|
|
|
|
|
|
|
|
async list(path: string): Promise<DataFrame | undefined> {
|
|
|
|
let url = 'api/storage/list/';
|
|
|
|
if (path) {
|
|
|
|
url += path + '/';
|
|
|
|
}
|
|
|
|
const rsp = await getBackendSrv().get<DataFrameJSON>(url);
|
|
|
|
if (rsp?.data) {
|
|
|
|
const f = dataFrameFromJSON(rsp);
|
|
|
|
for (const field of f.fields) {
|
|
|
|
field.display = getDisplayProcessor({ field, theme: config.theme2 });
|
|
|
|
}
|
|
|
|
return f;
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2022-07-08 13:23:16 -05:00
|
|
|
async createFolder(path: string): Promise<{ error?: string }> {
|
|
|
|
const res = await getBackendSrv().post<{ success: boolean; message: string }>(
|
|
|
|
'/api/storage/createFolder',
|
|
|
|
JSON.stringify({ path })
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!res.success) {
|
|
|
|
return {
|
|
|
|
error: res.message ?? 'unknown error',
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
async deleteFolder(req: { path: string; force: boolean }): Promise<{ error?: string }> {
|
|
|
|
const res = await getBackendSrv().post<{ success: boolean; message: string }>(
|
|
|
|
`/api/storage/deleteFolder`,
|
|
|
|
JSON.stringify(req)
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!res.success) {
|
|
|
|
return {
|
|
|
|
error: res.message ?? 'unknown error',
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
async deleteFile(req: { path: string }): Promise<{ error?: string }> {
|
|
|
|
const res = await getBackendSrv().post<{ success: boolean; message: string }>(`/api/storage/delete/${req.path}`);
|
|
|
|
|
|
|
|
if (!res.success) {
|
|
|
|
return {
|
|
|
|
error: res.message ?? 'unknown error',
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
async delete(req: { isFolder: boolean; path: string }): Promise<{ error?: string }> {
|
|
|
|
return req.isFolder ? this.deleteFolder({ path: req.path, force: true }) : this.deleteFile({ path: req.path });
|
|
|
|
}
|
|
|
|
|
2022-07-12 09:13:57 -05:00
|
|
|
async upload(folder: string, file: File, overwriteExistingFile: boolean): Promise<UploadReponse> {
|
2022-07-05 12:53:41 -05:00
|
|
|
const formData = new FormData();
|
|
|
|
formData.append('folder', folder);
|
|
|
|
formData.append('file', file);
|
2022-07-12 09:13:57 -05:00
|
|
|
formData.append('overwriteExistingFile', String(overwriteExistingFile));
|
2022-07-05 12:53:41 -05:00
|
|
|
const res = await fetch('/api/storage/upload', {
|
|
|
|
method: 'POST',
|
|
|
|
body: formData,
|
|
|
|
});
|
|
|
|
|
|
|
|
let body = (await res.json()) as UploadReponse;
|
|
|
|
if (!body) {
|
|
|
|
body = {} as any;
|
|
|
|
}
|
|
|
|
body.status = res.status;
|
|
|
|
body.statusText = res.statusText;
|
|
|
|
if (res.status !== 200 && !body.err) {
|
|
|
|
body.err = true;
|
|
|
|
}
|
|
|
|
return body;
|
|
|
|
}
|
|
|
|
|
2022-07-14 17:36:17 -05:00
|
|
|
// Temporary shim that can be loaded into the existing dashboard page structure
|
|
|
|
async getDashboard(path: string): Promise<DashboardDTO> {
|
|
|
|
if (!config.featureToggles.dashboardsFromStorage) {
|
|
|
|
return Promise.reject('Dashboards from storage is not enabled');
|
|
|
|
}
|
2022-07-05 12:53:41 -05:00
|
|
|
|
2022-07-14 17:36:17 -05:00
|
|
|
if (!path.endsWith('.json')) {
|
|
|
|
path += '.json';
|
|
|
|
}
|
2022-07-19 13:41:27 -05:00
|
|
|
|
2022-09-12 17:34:46 -05:00
|
|
|
if (!path.startsWith('content/')) {
|
|
|
|
path = `content/${path}`;
|
|
|
|
}
|
|
|
|
|
2022-07-14 17:36:17 -05:00
|
|
|
const result = await backendSrv.get(`/api/storage/read/${path}`);
|
|
|
|
result.uid = path;
|
|
|
|
delete result.id; // Saved with the dev dashboards!
|
|
|
|
|
|
|
|
return {
|
|
|
|
meta: {
|
|
|
|
uid: path,
|
|
|
|
slug: path,
|
|
|
|
canEdit: true,
|
|
|
|
canSave: true,
|
|
|
|
canStar: false, // needs id
|
|
|
|
},
|
|
|
|
dashboard: result,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-07-29 01:26:44 -05:00
|
|
|
async write(path: string, options: WriteValueRequest): Promise<WriteValueResponse> {
|
|
|
|
return backendSrv.post<WriteValueResponse>(`/api/storage/write/${path}`, options);
|
|
|
|
}
|
2022-07-14 17:36:17 -05:00
|
|
|
|
2022-07-29 01:26:44 -05:00
|
|
|
async getConfig() {
|
|
|
|
return getBackendSrv().get<StorageInfo[]>('/api/storage/config');
|
|
|
|
}
|
2022-07-14 17:36:17 -05:00
|
|
|
|
2022-07-29 01:26:44 -05:00
|
|
|
async getOptions(path: string) {
|
|
|
|
return getBackendSrv().get<ItemOptions>(`/api/storage/options/${path}`);
|
2022-07-05 12:53:41 -05:00
|
|
|
}
|
|
|
|
}
|
2022-07-12 09:13:57 -05:00
|
|
|
|
|
|
|
export function filenameAlreadyExists(folderName: string, fileNames: string[]) {
|
|
|
|
const lowerCase = folderName.toLowerCase();
|
|
|
|
const trimmedLowerCase = lowerCase.trim();
|
|
|
|
const existingTrimmedLowerCaseNames = fileNames.map((f) => f.trim().toLowerCase());
|
|
|
|
|
|
|
|
return existingTrimmedLowerCaseNames.includes(trimmedLowerCase);
|
|
|
|
}
|
2022-07-14 17:36:17 -05:00
|
|
|
|
|
|
|
let storage: GrafanaStorage | undefined;
|
|
|
|
|
|
|
|
export function getGrafanaStorage() {
|
|
|
|
if (!storage) {
|
|
|
|
storage = new SimpleStorage();
|
|
|
|
}
|
|
|
|
return storage;
|
|
|
|
}
|