Files
tacticalrmm-web/src/ee/reporting/api/reporting.ts

630 lines
18 KiB
TypeScript

/*
Copyright (c) 2023-present Amidaware Inc.
This file is subject to the EE License Agreement.
For details, see: https://license.tacticalrmm.com/ee
*/
import axios from "axios";
import { ref, type Ref } from "vue";
import { router } from "@/router";
import type {
ReportFormat,
ReportDependencies,
ReportTemplate,
ReportHTMLTemplate,
ReportDataQuery,
UploadAssetsResponse,
RunReportPreviewRequest,
RunReportRequest,
VariableAnalysis,
SharedTemplate,
} from "../types/reporting";
import type { QTreeFileNode } from "@/types/filebrowser";
import { notifySuccess } from "@/utils/notify";
import { exportFile, Dialog } from "quasar";
import { until } from "@vueuse/shared";
import ReportDependencyPrompt from "../components/ReportDependencyPrompt.vue";
const baseUrl = "/reporting";
export interface useReportingTemplates {
reportTemplates: Ref<ReportTemplate[]>;
isLoading: Ref<boolean>;
isError: Ref<boolean>;
getReportTemplates: (dependsOn?: string[]) => void;
addReportTemplate: (payload: ReportTemplate) => void;
editReportTemplate: (
id: number,
payload: ReportTemplate,
options?: { dontNotify?: boolean },
) => void;
deleteReportTemplate: (id: number) => void;
renderedPreview: Ref<string>;
renderedVariables: Ref<string>;
runReportPreview: (payload: RunReportPreviewRequest) => void;
runReportPreviewDebug: (payload: RunReportPreviewRequest) => void;
reportData: Ref<string>;
runReport: (
id: number,
payload: RunReportRequest,
forDownload?: boolean,
) => void;
openReport: (
id: number,
format: ReportFormat,
dependsOn: string[],
dependencies?: ReportDependencies,
newWindow?: boolean,
) => void;
exportReport: (id: number) => void;
importReport: (payload: { overwrite: boolean; template: string }) => void;
downloadReport: (
template: ReportTemplate,
format: ReportFormat,
dependencies?: ReportDependencies,
) => void;
getSharedTemplates: () => void;
sharedTemplates: Ref<SharedTemplate[]>;
importSharedTemplates: (payload: {
templates: SharedTemplate[];
overwrite: boolean;
}) => void;
variableAnalysis: Ref<VariableAnalysis>;
getAllowedValues: (payload: {
variables: string;
dependencies: ReportDependencies;
}) => void;
}
// reporting endpoints
export function useReportTemplates(): useReportingTemplates {
const reportTemplates = ref<ReportTemplate[]>([]);
const isLoading = ref(false);
const isError = ref(false);
const renderedPreview = ref("");
const renderedVariables = ref("");
const reportData = ref("");
const variableAnalysis = ref<VariableAnalysis>({});
const sharedTemplates = ref<SharedTemplate[]>([]);
function getReportTemplates(dependsOn?: string[]) {
isLoading.value = true;
isError.value = false;
const query = {} as { dependsOn?: string[] };
if (dependsOn) {
query.dependsOn = dependsOn;
}
axios
.get(`${baseUrl}/templates/`, { params: query })
.then(({ data }) => {
reportTemplates.value = data;
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
function deleteReportTemplate(id: number) {
isLoading.value = true;
isError.value = false;
axios
.delete(`${baseUrl}/templates/${id}/`)
.then(() => {
reportTemplates.value = reportTemplates.value.filter(
(template) => template.id != id,
);
notifySuccess("The report template was successfully removed");
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
function addReportTemplate(payload: ReportTemplate) {
isLoading.value = true;
isError.value = false;
axios
.post(`${baseUrl}/templates/`, payload)
.then(({ data }: { data: ReportTemplate }) => {
reportTemplates.value.push(data);
notifySuccess("The report template was added successfully");
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
function editReportTemplate(
id: number,
payload: ReportTemplate,
options?: { dontNotify?: boolean },
) {
isLoading.value = true;
isError.value = false;
axios
.put(`${baseUrl}/templates/${id}/`, payload)
.then(({ data }: { data: ReportTemplate }) => {
const index = reportTemplates.value.findIndex(
(template) => template.id === id,
);
reportTemplates.value[index] = data;
options?.dontNotify ||
notifySuccess("The report template was edited successfully");
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
function runReportPreviewDebug(payload: RunReportPreviewRequest) {
isLoading.value = true;
isError.value = false;
renderedPreview.value = "";
renderedVariables.value = "";
axios
.post(`${baseUrl}/templates/preview/`, payload)
.then(({ data }) => {
if (payload.format === "html") renderedPreview.value = data.template;
else renderedPreview.value = `<pre>${data.template}</pre>`;
renderedVariables.value = JSON.stringify(data.variables, undefined, 4);
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
function runReportPreview(payload: RunReportPreviewRequest) {
isLoading.value = true;
isError.value = false;
renderedPreview.value = "";
axios
.post(`${baseUrl}/templates/preview/`, payload, {
responseType: payload.format !== "pdf" ? "json" : "blob",
})
.then(({ data }) => {
if (payload.format === "html") renderedPreview.value = data;
else if (payload.format === "pdf")
renderedPreview.value = URL.createObjectURL(data);
else renderedPreview.value = `<pre>${data}</pre>`;
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
function runReport(
id: number,
payload: RunReportRequest,
forDownload?: boolean,
): void {
isLoading.value = true;
isError.value = false;
axios
.post(`${baseUrl}/templates/${id}/run/`, payload, {
responseType: payload.format !== "pdf" ? "json" : "blob",
})
.then(({ data }) => {
if (payload.format === "html" || forDownload) reportData.value = data;
else if (payload.format === "pdf")
reportData.value = URL.createObjectURL(data);
else reportData.value = `<pre>${data}</pre>`;
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
function downloadReport(
template: ReportTemplate,
format: ReportFormat,
dependencies: ReportDependencies = {},
) {
isLoading.value = true;
isError.value = false;
reportData.value = "";
const needsPrompt =
template.depends_on?.filter((dep) => !dependencies[dep]) || [];
let extension;
if (format === "plaintext") extension = "csv";
else extension = format;
// get filename
Dialog.create({
title: "Confirm File Name",
prompt: {
model: `${template.name}.${extension}`,
isValid: (val) => !!val,
type: "text",
},
cancel: true,
persistent: true,
}).onOk(async (name: string) => {
// get dependencies
if (needsPrompt.length > 0) {
Dialog.create({
component: ReportDependencyPrompt,
componentProps: { dependsOn: needsPrompt },
})
.onOk((deps) => (dependencies = { ...dependencies, ...deps }))
.onDismiss(() => {
runReport(
template.id,
{
format: format,
dependencies: dependencies,
},
true,
);
});
} else {
// no dependencies run report
runReport(
template.id,
{
format: format,
dependencies: dependencies,
},
true,
);
}
await until(isLoading).not.toBeTruthy();
if (isError.value) return;
exportFile(name, reportData.value);
});
}
function openReport(
id: number,
format: ReportFormat,
dependsOn: string[],
dependencies?: ReportDependencies,
newWindow?: boolean,
) {
const dependencyString = JSON.stringify(dependencies) || "{}";
const dependsOnString =
dependsOn.length > 0 ? JSON.stringify(dependsOn) : null;
const params = dependsOnString
? `format=${format}&dependsOn=${dependsOnString}&dependencies=${dependencyString}`
: `format=${format}`;
const url = router.resolve(`/reports/${id}?${params}`).href;
if (newWindow === undefined || newWindow) {
window.open(url, "_blank");
} else {
router.push(url);
}
}
function exportReport(id: number) {
isLoading.value = true;
isError.value = false;
axios
.post(`${baseUrl}/templates/${id}/export/`)
.then(({ data }) => {
exportFile(
`${data.template.name}-export.json`,
JSON.stringify(data, null, 2),
);
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
function importReport(payload: { overwrite: boolean; template: string }) {
isLoading.value = true;
isError.value = false;
axios
.post(`${baseUrl}/templates/import/`, payload)
.then(({ data }: { data: ReportTemplate }) => {
const index = reportTemplates.value.findIndex(
(report) => report.id === data.id,
);
if (index !== -1) reportTemplates.value[index] = data;
else reportTemplates.value.push(data);
notifySuccess("Report Template was successfully imported.");
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
function getSharedTemplates() {
isLoading.value = true;
isError.value = false;
axios
.get(`${baseUrl}/templates/shared/`)
.then(({ data }: { data: SharedTemplate[] }) => {
sharedTemplates.value = data;
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
function importSharedTemplates(payload: {
templates: SharedTemplate[];
overwrite: boolean;
}) {
isLoading.value = true;
isError.value = false;
axios
.post(`${baseUrl}/templates/shared/`, payload)
.then(() => {
notifySuccess("Shared templates imported successfully");
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
function getAllowedValues(payload: {
variables: string;
dependencies: ReportDependencies;
}) {
isLoading.value = true;
isError.value = false;
axios
.post(`${baseUrl}/templates/preview/analysis/`, payload)
.then(({ data }: { data: VariableAnalysis }) => {
variableAnalysis.value = data;
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
return {
reportTemplates,
isLoading,
isError,
getReportTemplates,
addReportTemplate,
editReportTemplate,
deleteReportTemplate,
renderedPreview,
renderedVariables,
runReportPreview,
runReportPreviewDebug,
reportData,
runReport,
openReport,
exportReport,
importReport,
downloadReport,
getSharedTemplates,
sharedTemplates,
importSharedTemplates,
variableAnalysis,
getAllowedValues,
};
}
export const useSharedReportTemplates = useReportTemplates();
// reporting asset endpoints
export async function fetchReportAssets(
path?: string,
folderOnly?: boolean,
): Promise<QTreeFileNode[]> {
const params = {} as { path?: string; folders?: boolean };
if (path) params.path = path;
if (folderOnly) params.folders = true;
const { data } = await axios.get(`${baseUrl}/assets/`, { params: params });
return data;
}
export async function fetchAllReportAssets(
foldersOnly?: boolean,
): Promise<QTreeFileNode[]> {
const params = {} as { onlyFolders?: boolean };
if (foldersOnly) params.onlyFolders = true;
const { data } = await axios.get(`${baseUrl}/assets/all/`, {
params: params,
});
return data;
}
export async function renameReportAsset(
path: string,
newName: string,
): Promise<string> {
const payload = { path, newName };
const { data } = await axios.put(`${baseUrl}/assets/rename/`, payload);
return data;
}
export async function createAssetFolder(path: string): Promise<string> {
const payload = { path };
const { data } = await axios.post(`${baseUrl}/assets/newfolder/`, payload);
return data;
}
export async function deleteAssets(paths: string[]): Promise<undefined> {
const payload = { paths };
const { data } = await axios.post(`${baseUrl}/assets/delete/`, payload);
return data;
}
export async function downloadAsset(path: string): Promise<Blob> {
const params = path ? { path } : {};
const { data } = await axios.get(`${baseUrl}/assets/download/`, {
responseType: "blob",
params: params,
});
return data;
}
export async function uploadAssets(
form: FormData,
path = "",
): Promise<UploadAssetsResponse> {
form.append("parentPath", path);
const { data } = await axios.post(`${baseUrl}/assets/upload/`, form);
return data;
}
// reporting html templates endpoints
export interface useReportingHTMLTemplates {
reportHTMLTemplates: Ref<ReportHTMLTemplate[]>;
isLoading: Ref<boolean>;
isError: Ref<boolean>;
getReportHTMLTemplates: () => void;
addReportHTMLTemplate: (payload: ReportHTMLTemplate) => void;
editReportHTMLTemplate: (id: number, payload: ReportHTMLTemplate) => void;
deleteReportHTMLTemplate: (id: number) => void;
}
export function useReportingHTMLTemplates(): useReportingHTMLTemplates {
const reportHTMLTemplates = ref<ReportHTMLTemplate[]>([]);
const isLoading = ref(false);
const isError = ref(false);
function getReportHTMLTemplates() {
axios
.get(`${baseUrl}/htmltemplates/`)
.then(({ data }) => {
reportHTMLTemplates.value = data;
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
function addReportHTMLTemplate(payload: ReportHTMLTemplate) {
isLoading.value = true;
axios
.post(`${baseUrl}/htmltemplates/`, payload)
.then(({ data }: { data: ReportHTMLTemplate }) => {
reportHTMLTemplates.value.push(data);
notifySuccess("HTML Template was added successfully");
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
function editReportHTMLTemplate(id: number, payload: ReportHTMLTemplate) {
isLoading.value = true;
axios
.put(`${baseUrl}/htmltemplates/${id}/`, payload)
.then(({ data }: { data: ReportHTMLTemplate }) => {
const index = reportHTMLTemplates.value.findIndex(
(template) => template.id === id,
);
reportHTMLTemplates.value[index] = data;
notifySuccess("HTML Template was edited successfully");
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
function deleteReportHTMLTemplate(id: number) {
isLoading.value = true;
axios
.delete(`${baseUrl}/htmltemplates/${id}/`)
.then(() => {
reportHTMLTemplates.value = reportHTMLTemplates.value.filter(
(template) => template.id != id,
);
notifySuccess("The HTML template was successfully removed");
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
return {
reportHTMLTemplates,
isLoading,
isError,
getReportHTMLTemplates,
addReportHTMLTemplate,
editReportHTMLTemplate,
deleteReportHTMLTemplate,
};
}
// Use if you want the state to be consistent across components
export const useSharedReportHTMLTemplates = useReportingHTMLTemplates();
// reporting data query endpoints
export interface useReportingDataQueries {
reportDataQueries: Ref<ReportDataQuery[]>;
isLoading: Ref<boolean>;
isError: Ref<boolean>;
getReportDataQueries: () => void;
addReportDataQuery: (payload: ReportDataQuery) => void;
editReportDataQuery: (id: number, payload: ReportDataQuery) => void;
deleteReportDataQuery: (id: number) => void;
}
export function useReportingDataQueries(): useReportingDataQueries {
const reportDataQueries = ref<ReportDataQuery[]>([]);
const isLoading = ref(false);
const isError = ref(false);
function getReportDataQueries() {
axios
.get(`${baseUrl}/dataqueries/`)
.then(({ data }) => {
isLoading.value = true;
reportDataQueries.value = data;
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
function addReportDataQuery(payload: ReportDataQuery) {
axios
.post(`${baseUrl}/dataqueries/`, payload)
.then(({ data }: { data: ReportDataQuery }) => {
isLoading.value = true;
reportDataQueries.value.push(data);
notifySuccess("Data Query was added successfully");
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
function editReportDataQuery(id: number, payload: ReportDataQuery) {
axios
.put(`${baseUrl}/dataqueries/${id}/`, payload)
.then(({ data }: { data: ReportDataQuery }) => {
isLoading.value = true;
const index = reportDataQueries.value.findIndex(
(template) => template.id === id,
);
reportDataQueries.value[index] = data;
notifySuccess("Data Query was edited successfully");
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
function deleteReportDataQuery(id: number) {
axios
.delete(`${baseUrl}/dataqueries/${id}/`)
.then(() => {
reportDataQueries.value = reportDataQueries.value.filter(
(template) => template.id != id,
);
notifySuccess("The Data Query was successfully removed");
})
.catch(() => (isError.value = true))
.finally(() => (isLoading.value = false));
}
return {
reportDataQueries,
isLoading,
isError,
getReportDataQueries,
addReportDataQuery,
editReportDataQuery,
deleteReportDataQuery,
};
}
// Use if you want the state to be consistent across components
export const useSharedReportDataQueries = useReportingDataQueries();