From 29bd120126a8f4e1c5fdc18082915c45ee417328 Mon Sep 17 00:00:00 2001 From: Yury Molodov Date: Mon, 13 May 2024 12:24:50 +0200 Subject: [PATCH 1/5] vmui/vmanomaly: fix default server url (#6178) This PR for ui vmanomaly eliminates URL parameters to automatically use the default server URL, simplifying URLs like: From http://localhost:3000/#/?g0.expr=vm_blocks... to http://localhost:3000 From http://localhost:3000/select/0/vmui/#/?g0.expr=vm_blocks... to http://localhost:3000/select/0/vmui/ etc. --- app/vmui/packages/vmui/src/utils/default-server-url.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/vmui/packages/vmui/src/utils/default-server-url.ts b/app/vmui/packages/vmui/src/utils/default-server-url.ts index c6f98e8b7..7a23b6443 100644 --- a/app/vmui/packages/vmui/src/utils/default-server-url.ts +++ b/app/vmui/packages/vmui/src/utils/default-server-url.ts @@ -8,7 +8,7 @@ export const getDefaultServer = (tenantId?: string): string => { const { serverURL } = getAppModeParams(); const storageURL = getFromStorage("SERVER_URL") as string; const logsURL = window.location.href.replace(/\/(select\/)?(vmui)\/.*/, ""); - const anomalyURL = window.location.href.replace(/(?:graph|vmui)\/.*/, ""); + const anomalyURL = `${window.location.origin}${window.location.pathname}`; const defaultURL = window.location.href.replace(/\/(?:prometheus\/)?(?:graph|vmui)\/.*/, "/prometheus"); const url = serverURL || storageURL || defaultURL; @@ -16,7 +16,7 @@ export const getDefaultServer = (tenantId?: string): string => { case AppType.logs: return logsURL; case AppType.anomaly: - return serverURL || storageURL || anomalyURL; + return storageURL || anomalyURL; default: return tenantId ? replaceTenantId(url, tenantId) : url; } From 37c22ee053789e2be5b5e11513c97f78b76e67cb Mon Sep 17 00:00:00 2001 From: Yury Molodov Date: Mon, 13 May 2024 12:25:31 +0200 Subject: [PATCH 2/5] vmui/vmanomaly: add download config button (#6231) This pull request adds a button to the vmanomaly ui that opens a modal window for viewing and downloading the config file. button error modal --- .../ExploreAnomaly/AnomalyConfig.tsx | 120 ++++++++++++++++++ .../src/components/ExploreAnomaly/style.scss | 61 +++++++++ .../src/components/Main/Spinner/Spinner.tsx | 7 +- .../QueryConfigurator/QueryConfigurator.tsx | 3 + .../pages/ExploreAnomaly/ExploreAnomaly.tsx | 2 +- 5 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 app/vmui/packages/vmui/src/components/ExploreAnomaly/AnomalyConfig.tsx create mode 100644 app/vmui/packages/vmui/src/components/ExploreAnomaly/style.scss diff --git a/app/vmui/packages/vmui/src/components/ExploreAnomaly/AnomalyConfig.tsx b/app/vmui/packages/vmui/src/components/ExploreAnomaly/AnomalyConfig.tsx new file mode 100644 index 000000000..ca9a90fc5 --- /dev/null +++ b/app/vmui/packages/vmui/src/components/ExploreAnomaly/AnomalyConfig.tsx @@ -0,0 +1,120 @@ +import React, { FC, useState } from "preact/compat"; +import Button from "../Main/Button/Button"; +import TextField from "../Main/TextField/TextField"; +import Modal from "../Main/Modal/Modal"; +import Spinner from "../Main/Spinner/Spinner"; +import { DownloadIcon, ErrorIcon } from "../Main/Icons"; +import useBoolean from "../../hooks/useBoolean"; +import useDeviceDetect from "../../hooks/useDeviceDetect"; +import { useAppState } from "../../state/common/StateContext"; +import classNames from "classnames"; +import "./style.scss"; + +const AnomalyConfig: FC = () => { + const { serverUrl } = useAppState(); + const { isMobile } = useDeviceDetect(); + + const { + value: isModalOpen, + setTrue: setOpenModal, + setFalse: setCloseModal, + } = useBoolean(false); + + const [isLoading, setIsLoading] = useState(false); + const [textConfig, setTextConfig] = useState(""); + const [downloadUrl, setDownloadUrl] = useState(""); + const [error, setError] = useState(""); + + const fetchConfig = async () => { + setIsLoading(true); + try { + const url = `${serverUrl}/api/vmanomaly/config.yaml`; + const response = await fetch(url); + if (!response.ok) { + setError(` ${response.status} ${response.statusText}`); + } else { + const blob = await response.blob(); + const yamlAsString = await blob.text(); + setTextConfig(yamlAsString); + setDownloadUrl(URL.createObjectURL(blob)); + } + } catch (error) { + console.error(error); + setError(String(error)); + } + setIsLoading(false); + }; + + const handleOpenModal = () => { + setOpenModal(); + setError(""); + URL.revokeObjectURL(downloadUrl); + setTextConfig(""); + setDownloadUrl(""); + fetchConfig(); + }; + + return ( + <> + + {isModalOpen && ( + +
+ {isLoading && ( + + )} + {!isLoading && error && ( +
+
+

Cannot download config

+

{error}

+
+ )} + {!isLoading && textConfig && ( + + )} +
+ {downloadUrl && ( + + + + )} +
+
+
+ )} + + ); +}; + +export default AnomalyConfig; diff --git a/app/vmui/packages/vmui/src/components/ExploreAnomaly/style.scss b/app/vmui/packages/vmui/src/components/ExploreAnomaly/style.scss new file mode 100644 index 000000000..72a43e314 --- /dev/null +++ b/app/vmui/packages/vmui/src/components/ExploreAnomaly/style.scss @@ -0,0 +1,61 @@ +@use "src/styles/variables" as *; + +.vm-anomaly-config { + display: grid; + grid-template-rows: calc(($vh * 70) - 78px - ($padding-medium*3)) auto; + gap: $padding-global; + min-width: 400px; + max-width: 80vw; + min-height: 300px; + + &_mobile { + width: 100%; + max-width: none; + min-height: 100%; + grid-template-rows: calc(($vh * 100) - 78px - ($padding-global*3)) auto; + } + + textarea { + overflow: auto; + width: 100%; + height: 100%; + max-height: 900px; + } + + &-error { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + gap: $padding-small; + text-align: center; + + &__icon { + display: flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + margin-bottom: $padding-small; + color: $color-error; + } + + &__title { + font-size: $font-size-medium; + font-weight: bold; + } + + &__text { + max-width: 700px; + line-height: 1.3; + } + } + + &-footer { + display: flex; + align-items: center; + justify-content: flex-end; + gap: $padding-small; + } +} diff --git a/app/vmui/packages/vmui/src/components/Main/Spinner/Spinner.tsx b/app/vmui/packages/vmui/src/components/Main/Spinner/Spinner.tsx index 05e997e36..e19549a17 100644 --- a/app/vmui/packages/vmui/src/components/Main/Spinner/Spinner.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Spinner/Spinner.tsx @@ -1,4 +1,5 @@ -import React, { CSSProperties, FC } from "preact/compat"; +import React, { FC } from "preact/compat"; +import { CSSProperties } from "react"; import "./style.scss"; import classNames from "classnames"; import { useAppState } from "../../../state/common/StateContext"; @@ -8,7 +9,7 @@ interface SpinnerProps { message?: string } -const Spinner: FC = ({ containerStyles = {}, message }) => { +const Spinner: FC = ({ containerStyles, message }) => { const { isDarkTheme } = useAppState(); return ( @@ -17,7 +18,7 @@ const Spinner: FC = ({ containerStyles = {}, message }) => { "vm-spinner": true, "vm-spinner_dark": isDarkTheme, })} - style={containerStyles && {}} + style={containerStyles} >
diff --git a/app/vmui/packages/vmui/src/pages/CustomPanel/QueryConfigurator/QueryConfigurator.tsx b/app/vmui/packages/vmui/src/pages/CustomPanel/QueryConfigurator/QueryConfigurator.tsx index bd687041e..6ca90a921 100644 --- a/app/vmui/packages/vmui/src/pages/CustomPanel/QueryConfigurator/QueryConfigurator.tsx +++ b/app/vmui/packages/vmui/src/pages/CustomPanel/QueryConfigurator/QueryConfigurator.tsx @@ -24,6 +24,7 @@ import useDeviceDetect from "../../../hooks/useDeviceDetect"; import { QueryStats } from "../../../api/types"; import { usePrettifyQuery } from "./hooks/usePrettifyQuery"; import QueryHistory from "../QueryHistory/QueryHistory"; +import AnomalyConfig from "../../../components/ExploreAnomaly/AnomalyConfig"; export interface QueryConfiguratorProps { queryErrors: string[]; @@ -37,6 +38,7 @@ export interface QueryConfiguratorProps { prettify?: boolean; autocomplete?: boolean; traceQuery?: boolean; + anomalyConfig?: boolean; } } @@ -253,6 +255,7 @@ const QueryConfigurator: FC = ({
+ {hideButtons?.anomalyConfig && } {!hideButtons?.addQuery && stateQuery.length < MAX_QUERY_FIELDS && (