vmanomaly: Add Download Config button

This commit is contained in:
Dzmitry Lazerka 2024-04-17 18:21:25 +03:00
parent a5f81f67fd
commit 7bac53379d
No known key found for this signature in database
GPG key ID: F710E0DC6F0204A3
2 changed files with 52 additions and 4 deletions

View file

@ -47,7 +47,7 @@ const AnomalyLayout: FC = () => {
className={classNames({ className={classNames({
"vm-container-body": true, "vm-container-body": true,
"vm-container-body_mobile": isMobile, "vm-container-body_mobile": isMobile,
"vm-container-body_app": appModeEnable "vm-container-body_app": appModeEnable,
})} })}
> >
<Outlet/> <Outlet/>

View file

@ -1,7 +1,7 @@
import React, { FC, useMemo, useRef, useState } from "preact/compat"; import React, { FC, useMemo, useRef, useState } from "preact/compat";
import classNames from "classnames"; import classNames from "classnames";
import useDeviceDetect from "../../hooks/useDeviceDetect"; import useDeviceDetect from "../../hooks/useDeviceDetect";
import { ForecastType } from "../../types"; import { ErrorTypes, ForecastType } from "../../types";
import { useSetQueryParams } from "../CustomPanel/hooks/useSetQueryParams"; import { useSetQueryParams } from "../CustomPanel/hooks/useSetQueryParams";
import QueryConfigurator from "../CustomPanel/QueryConfigurator/QueryConfigurator"; import QueryConfigurator from "../CustomPanel/QueryConfigurator/QueryConfigurator";
import "../CustomPanel/style.scss"; import "../CustomPanel/style.scss";
@ -15,6 +15,8 @@ import GraphTab from "../CustomPanel/CustomPanelTabs/GraphTab";
import { extractFields, isForecast } from "../../utils/uplot"; import { extractFields, isForecast } from "../../utils/uplot";
import { MetricResult } from "../../api/types"; import { MetricResult } from "../../api/types";
import { promValueToNumber } from "../../utils/metric"; import { promValueToNumber } from "../../utils/metric";
import Button from "../../components/Main/Button/Button";
import { useAppState } from "../../state/common/StateContext";
// Hardcoded to 1.0 for now; consider adding a UI slider for threshold adjustment in the future. // Hardcoded to 1.0 for now; consider adding a UI slider for threshold adjustment in the future.
const ANOMALY_SCORE_THRESHOLD = 1; const ANOMALY_SCORE_THRESHOLD = 1;
@ -22,6 +24,7 @@ const ANOMALY_SCORE_THRESHOLD = 1;
const ExploreAnomaly: FC = () => { const ExploreAnomaly: FC = () => {
useSetQueryParams(); useSetQueryParams();
const { isMobile } = useDeviceDetect(); const { isMobile } = useDeviceDetect();
const { serverUrl } = useAppState();
const { query } = useQueryState(); const { query } = useQueryState();
const { customStep } = useGraphState(); const { customStep } = useGraphState();
@ -44,7 +47,7 @@ const ExploreAnomaly: FC = () => {
visible: true, visible: true,
customStep, customStep,
hideQuery, hideQuery,
showAllSeries showAllSeries,
}); });
const data = useMemo(() => { const data = useMemo(() => {
@ -63,7 +66,7 @@ const ExploreAnomaly: FC = () => {
if (!anomalyScoreDataByLabels) return false; if (!anomalyScoreDataByLabels) return false;
const anomalyScore = anomalyScoreDataByLabels.values.find(([tMax]) => tMax === t) as [number, string]; const anomalyScore = anomalyScoreDataByLabels.values.find(([tMax]) => tMax === t) as [number, string];
return anomalyScore && promValueToNumber(anomalyScore[1]) > ANOMALY_SCORE_THRESHOLD; return anomalyScore && promValueToNumber(anomalyScore[1]) > ANOMALY_SCORE_THRESHOLD;
}) }),
}; };
}); });
const filterData = detectedData.filter(d => (d.value !== ForecastType.anomaly) && d.value) as MetricResult[]; const filterData = detectedData.filter(d => (d.value !== ForecastType.anomaly) && d.value) as MetricResult[];
@ -74,6 +77,30 @@ const ExploreAnomaly: FC = () => {
setHideError(false); setHideError(false);
}; };
const downloadLinkRef = React.createRef<HTMLAnchorElement>();
const downloadConfig = async () => {
const response = await fetch(`${serverUrl}/api/vmanomaly/config.yaml`, { });
if (!response.ok) {
// const errorType = resp.errorType ? `${resp.errorType}\r\n` : "";
// const msg = `${errorType}${resp?.error || resp?.message || "unknown error"}`;
const msg = `Cannot download config: ${response.status} ${response.statusText}`;
console.error(msg);
// TODO: show error to user
return;
}
const blob = await response.blob();
// const blob = new Blob([], { type: "application/yaml" });
const url = window.URL.createObjectURL(blob);
const a = downloadLinkRef.current;
if (a == null) {
throw Error("Hidden <a> is not present");
}
a.href = url;
a.download = "config.yaml";
a.click();
window.URL.revokeObjectURL(url);
};
return ( return (
<div <div
className={classNames({ className={classNames({
@ -98,6 +125,27 @@ const ExploreAnomaly: FC = () => {
onChange={setShowAllSeries} onChange={setShowAllSeries}
/> />
)} )}
<div
className={classNames({
"vm-custom-panel-body": true,
"vm-custom-panel-body_mobile": isMobile,
"vm-block": true,
"vm-block_mobile": isMobile,
})}
>
<Button
color="primary"
variant="contained"
onClick={downloadConfig}
>
Download Config
</Button>
{/*eslint-disable-next-line*/}
<a className="hidden" ref={downloadLinkRef} href="#"/>
{error && <Alert variant="error">{error}</Alert>}
</div>
<div <div
className={classNames({ className={classNames({
"vm-custom-panel-body": true, "vm-custom-panel-body": true,