diff --git a/app/vmui/packages/vmui/public/config.json b/app/vmui/packages/vmui/public/config.json new file mode 100644 index 000000000..4bb6a1501 --- /dev/null +++ b/app/vmui/packages/vmui/public/config.json @@ -0,0 +1,5 @@ +{ + "license": { + "type": "opensource" + } +} diff --git a/app/vmui/packages/vmui/src/App.tsx b/app/vmui/packages/vmui/src/App.tsx index f2807d7bf..1ffafe55f 100644 --- a/app/vmui/packages/vmui/src/App.tsx +++ b/app/vmui/packages/vmui/src/App.tsx @@ -15,6 +15,8 @@ import WithTemplate from "./pages/WithTemplate"; import Relabel from "./pages/Relabel"; import ActiveQueries from "./pages/ActiveQueries"; import QueryAnalyzer from "./pages/QueryAnalyzer"; +import DownsamplingFilters from "./pages/DownsamplingFilters"; +import RetentionFilters from "./pages/RetentionFilters"; const App: FC = () => { const [loadedTheme, setLoadedTheme] = useState(false); @@ -74,6 +76,14 @@ const App: FC = () => { path={router.icons} element={} /> + } + /> + } + /> )} diff --git a/app/vmui/packages/vmui/src/api/downsampling-filters-debug.ts b/app/vmui/packages/vmui/src/api/downsampling-filters-debug.ts new file mode 100644 index 000000000..4e7147729 --- /dev/null +++ b/app/vmui/packages/vmui/src/api/downsampling-filters-debug.ts @@ -0,0 +1,7 @@ +export const getDownsamplingFiltersDebug = (server: string, flags: string, metrics: string): string => { + const params = [ + `flags=${encodeURIComponent(flags)}`, + `metrics=${encodeURIComponent(metrics)}` + ]; + return `${server}/downsampling-filters-debug?${params.join("&")}`; +}; diff --git a/app/vmui/packages/vmui/src/api/retention-filters-debug.ts b/app/vmui/packages/vmui/src/api/retention-filters-debug.ts new file mode 100644 index 000000000..2bef0bedd --- /dev/null +++ b/app/vmui/packages/vmui/src/api/retention-filters-debug.ts @@ -0,0 +1,7 @@ +export const getRetentionFiltersDebug = (server: string, flags: string, metrics: string): string => { + const params = [ + `flags=${encodeURIComponent(flags)}`, + `metrics=${encodeURIComponent(metrics)}` + ]; + return `${server}/retention-filters-debug?${params.join("&")}`; +}; diff --git a/app/vmui/packages/vmui/src/constants/navigation.ts b/app/vmui/packages/vmui/src/constants/navigation.ts deleted file mode 100644 index 0df6c64d7..000000000 --- a/app/vmui/packages/vmui/src/constants/navigation.ts +++ /dev/null @@ -1,81 +0,0 @@ -import router, { routerOptions } from "../router"; - -export enum NavigationItemType { - internalLink, - externalLink, -} - -export interface NavigationItem { - label?: string, - value?: string, - hide?: boolean - submenu?: NavigationItem[], - type?: NavigationItemType, -} - -const explore = { - label: "Explore", - submenu: [ - { - label: routerOptions[router.metrics].title, - value: router.metrics, - }, - { - label: routerOptions[router.cardinality].title, - value: router.cardinality, - }, - { - label: routerOptions[router.topQueries].title, - value: router.topQueries, - }, - { - label: routerOptions[router.activeQueries].title, - value: router.activeQueries, - }, - ] -}; - -const tools = { - label: "Tools", - submenu: [ - { - label: routerOptions[router.trace].title, - value: router.trace, - }, - { - label: routerOptions[router.queryAnalyzer].title, - value: router.queryAnalyzer, - }, - { - label: routerOptions[router.withTemplate].title, - value: router.withTemplate, - }, - { - label: routerOptions[router.relabel].title, - value: router.relabel, - }, - ] -}; - -export const logsNavigation: NavigationItem[] = [ - { - label: routerOptions[router.logs].title, - value: router.home, - }, -]; - -export const anomalyNavigation: NavigationItem[] = [ - { - label: routerOptions[router.anomaly].title, - value: router.home, - } -]; - -export const defaultNavigation: NavigationItem[] = [ - { - label: routerOptions[router.home].title, - value: router.home, - }, - explore, - tools, -]; diff --git a/app/vmui/packages/vmui/src/hooks/useFetchAppConfig.ts b/app/vmui/packages/vmui/src/hooks/useFetchAppConfig.ts new file mode 100644 index 000000000..ac0bbffdd --- /dev/null +++ b/app/vmui/packages/vmui/src/hooks/useFetchAppConfig.ts @@ -0,0 +1,34 @@ +import { useAppDispatch } from "../state/common/StateContext"; +import { useEffect, useState } from "preact/compat"; +import { ErrorTypes } from "../types"; + +const useFetchFlags = () => { + const dispatch = useAppDispatch(); + + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(""); + + useEffect(() => { + const fetchAppConfig = async () => { + if (process.env.REACT_APP_TYPE) return; + setError(""); + setIsLoading(true); + + try { + const data = await fetch("./config.json"); + const config = await data.json(); + dispatch({ type: "SET_APP_CONFIG", payload: config || {} }); + } catch (e) { + setIsLoading(false); + if (e instanceof Error) setError(`${e.name}: ${e.message}`); + } + }; + + fetchAppConfig(); + }, []); + + return { isLoading, error }; +}; + +export default useFetchFlags; + diff --git a/app/vmui/packages/vmui/src/layouts/Header/HeaderNav/HeaderNav.tsx b/app/vmui/packages/vmui/src/layouts/Header/HeaderNav/HeaderNav.tsx index 88c8bc19d..4f009cf36 100644 --- a/app/vmui/packages/vmui/src/layouts/Header/HeaderNav/HeaderNav.tsx +++ b/app/vmui/packages/vmui/src/layouts/Header/HeaderNav/HeaderNav.tsx @@ -1,16 +1,12 @@ -import React, { FC, useMemo, useState } from "preact/compat"; -import router, { routerOptions } from "../../../router"; -import { getAppModeEnable } from "../../../utils/app-mode"; +import React, { FC, useState } from "preact/compat"; import { useLocation } from "react-router-dom"; -import { useDashboardsState } from "../../../state/dashboards/DashboardsStateContext"; import { useEffect } from "react"; import "./style.scss"; import NavItem from "./NavItem"; import NavSubItem from "./NavSubItem"; import classNames from "classnames"; -import { anomalyNavigation, defaultNavigation, logsNavigation, NavigationItemType } from "../../../constants/navigation"; -import { AppType } from "../../../types/appType"; -import { useAppState } from "../../../state/common/StateContext"; +import useNavigationMenu from "../../../router/useNavigationMenu"; +import { NavigationItemType } from "../../../router/navigation"; interface HeaderNavProps { color: string @@ -19,43 +15,14 @@ interface HeaderNavProps { } const HeaderNav: FC = ({ color, background, direction }) => { - const appModeEnable = getAppModeEnable(); - const { dashboardsSettings } = useDashboardsState(); const { pathname } = useLocation(); - const { serverUrl, flags } = useAppState(); - const [activeMenu, setActiveMenu] = useState(pathname); - - const menu = useMemo(() => { - switch (process.env.REACT_APP_TYPE) { - case AppType.logs: - return logsNavigation; - case AppType.anomaly: - return anomalyNavigation; - default: - return ([ - ...defaultNavigation, - { - label: routerOptions[router.dashboards].title, - value: router.dashboards, - hide: appModeEnable || !dashboardsSettings.length, - }, - { - // see more https://docs.victoriametrics.com/cluster-victoriametrics/?highlight=vmalertproxyurl#vmalert - label: "Alerts", - value: `${serverUrl}/vmalert`, - type: NavigationItemType.externalLink, - hide: !Object.keys(flags).includes("vmalert.proxyURL"), - }, - ].filter(r => !r.hide)); - } - }, [appModeEnable, dashboardsSettings]); + const menu = useNavigationMenu(); useEffect(() => { setActiveMenu(pathname); }, [pathname]); - return ( { const appModeEnable = getAppModeEnable(); @@ -21,6 +22,7 @@ const MainLayout: FC = () => { useFetchDashboards(); useFetchDefaultTimezone(); + useFetchAppConfig(); useFetchFlags(); const setDocumentTitle = () => { diff --git a/app/vmui/packages/vmui/src/pages/DownsamplingFilters/hooks/useDebugDownsamplingFilters.ts b/app/vmui/packages/vmui/src/pages/DownsamplingFilters/hooks/useDebugDownsamplingFilters.ts new file mode 100644 index 000000000..3c0a0a469 --- /dev/null +++ b/app/vmui/packages/vmui/src/pages/DownsamplingFilters/hooks/useDebugDownsamplingFilters.ts @@ -0,0 +1,53 @@ +import { useAppState } from "../../../state/common/StateContext"; +import { useState } from "react"; +import { ErrorTypes } from "../../../types"; +import { useSearchParams } from "react-router-dom"; +import { getDownsamplingFiltersDebug } from "../../../api/downsampling-filters-debug"; +import { useCallback } from "preact/compat"; + +export const useDebugDownsamplingFilters = () => { + const { serverUrl } = useAppState(); + const [searchParams, setSearchParams] = useSearchParams(); + + const [data, setData] = useState>(new Map()); + const [loading, setLoading] = useState(false); + const [metricsError, setMetricsError] = useState(); + const [flagsError, setFlagsError] = useState(); + const [error, setError] = useState(); + + const fetchData = useCallback(async (flags: string, metrics: string) => { + metrics ? setMetricsError("") : setMetricsError("metrics are required"); + flags ? setFlagsError("") : setFlagsError("flags are required"); + if (!metrics || !flags) return; + + searchParams.set("flags", flags); + searchParams.set("metrics", metrics); + setSearchParams(searchParams); + const fetchUrl = getDownsamplingFiltersDebug(serverUrl, flags, metrics); + setLoading(true); + try { + const response = await fetch(fetchUrl); + + const resp = await response.json(); + setData(new Map(Object.entries(resp.result || {}))); + setMetricsError(resp.error?.metrics || ""); + setFlagsError(resp.error?.flags || ""); + setError(""); + + } catch (e) { + if (e instanceof Error && e.name !== "AbortError") { + setError(`${e.name}: ${e.message}`); + } + } + setLoading(false); + }, [serverUrl]); + + return { + data, + error: error, + metricsError: metricsError, + flagsError: flagsError, + loading, + applyFilters: fetchData + }; +}; diff --git a/app/vmui/packages/vmui/src/pages/DownsamplingFilters/index.tsx b/app/vmui/packages/vmui/src/pages/DownsamplingFilters/index.tsx new file mode 100644 index 000000000..022a72e8d --- /dev/null +++ b/app/vmui/packages/vmui/src/pages/DownsamplingFilters/index.tsx @@ -0,0 +1,137 @@ +import React, { FC, useEffect } from "preact/compat"; +import "./style.scss"; +import TextField from "../../components/Main/TextField/TextField"; +import { useCallback, useState } from "react"; +import Button from "../../components/Main/Button/Button"; +import { PlayIcon, WikiIcon } from "../../components/Main/Icons"; +import { useDebugDownsamplingFilters } from "./hooks/useDebugDownsamplingFilters"; +import Spinner from "../../components/Main/Spinner/Spinner"; +import { useSearchParams } from "react-router-dom"; + +const example = { + flags: `-downsampling.period={env="dev"}:7d:5m,{env="dev"}:30d:30m +-downsampling.period=30d:1m +-downsampling.period=60d:5m +`, + metrics: `up +up{env="dev"} +up{env="prod"}`, +}; + +const DownsamplingFilters: FC = () => { + const [searchParams] = useSearchParams(); + + const { data, loading, error, metricsError, flagsError, applyFilters } = useDebugDownsamplingFilters(); + const [metrics, setMetrics] = useState(searchParams.get("metrics") || ""); + const [flags, setFlags] = useState(searchParams.get("flags") || ""); + + const handleMetricsChangeInput = useCallback((val: string) => { + setMetrics(val); + }, [setMetrics]); + + const handleFlagsChangeInput = useCallback((val: string) => { + setFlags(val); + }, [setFlags]); + + const handleApplyFilters = useCallback(() => { + applyFilters(flags, metrics); + }, [applyFilters, flags, metrics]); + + const handleRunExample = useCallback(() => { + const { flags, metrics } = example; + setFlags(flags); + setMetrics(metrics); + applyFilters(flags, metrics); + searchParams.set("flags", flags); + searchParams.set("metrics", metrics); + }, [example, setFlags, setMetrics, searchParams]); + + useEffect(() => { + if (flags && metrics) handleApplyFilters(); + }, []); + + const rows = []; + for (const [key, value] of data) { + rows.push( + {key} + {value.join(" ")} + ); + } + return ( + + {loading && } + + + + + Provide a list of flags for downsampling configuration. Note that + only -downsampling.period and -dedup.minScrapeInterval flags are supported + + + + + + Provide a list of metrics to check downsampling configuration. + + + + + + + + Metric + Applied downsampling rules + + + + {rows} + + + + + + + Documentation + + + Try example + + } + > + Apply + + + + + ); +}; + +export default DownsamplingFilters; diff --git a/app/vmui/packages/vmui/src/pages/DownsamplingFilters/style.scss b/app/vmui/packages/vmui/src/pages/DownsamplingFilters/style.scss new file mode 100644 index 000000000..2b718ce7a --- /dev/null +++ b/app/vmui/packages/vmui/src/pages/DownsamplingFilters/style.scss @@ -0,0 +1,46 @@ +@use "src/styles/variables" as *; + +.vm-downsampling-filters { + display: grid; + gap: $padding-medium; + + &-body { + display: grid; + gap: $padding-global; + align-items: flex-start; + width: 100%; + + &__title { + margin-bottom: $padding-medium; + } + + &-top { + display: flex; + gap: $padding-small; + align-items: center; + justify-content: flex-end; + } + + &__expr textarea { + min-height: 200px; + } + + &__result textarea { + min-height: 60px; + } + + code { + background-color: var(--color-hover-black); + border-radius: 6px; + font-size: 85%; + padding: .2em .4em; + } + + textarea { + font-family: $font-family-monospace; + overflow: auto; + width: 100%; + height: 100%; + } + } +} diff --git a/app/vmui/packages/vmui/src/pages/RetentionFilters/hooks/useDebugRetentionFilters.ts b/app/vmui/packages/vmui/src/pages/RetentionFilters/hooks/useDebugRetentionFilters.ts new file mode 100644 index 000000000..480e3e27e --- /dev/null +++ b/app/vmui/packages/vmui/src/pages/RetentionFilters/hooks/useDebugRetentionFilters.ts @@ -0,0 +1,53 @@ +import { useAppState } from "../../../state/common/StateContext"; +import { useState } from "react"; +import { ErrorTypes } from "../../../types"; +import { useSearchParams } from "react-router-dom"; +import { getRetentionFiltersDebug } from "../../../api/retention-filters-debug"; +import { useCallback } from "preact/compat"; + +export const useDebugRetentionFilters = () => { + const { serverUrl } = useAppState(); + const [searchParams, setSearchParams] = useSearchParams(); + + const [data, setData] = useState>(new Map()); + const [loading, setLoading] = useState(false); + const [metricsError, setMetricsError] = useState(); + const [flagsError, setFlagsError] = useState(); + const [error, setError] = useState(); + + const fetchData = useCallback(async (flags: string, metrics: string) => { + metrics ? setMetricsError("") : setMetricsError("metrics are required"); + flags ? setFlagsError("") : setFlagsError("flags are required"); + if (!metrics || !flags) return; + + searchParams.set("flags", flags); + searchParams.set("metrics", metrics); + setSearchParams(searchParams); + const fetchUrl = getRetentionFiltersDebug(serverUrl, flags, metrics); + setLoading(true); + try { + const response = await fetch(fetchUrl); + + const resp = await response.json(); + setData(new Map(Object.entries(resp.result || {}))); + setMetricsError(resp.error?.metrics || ""); + setFlagsError(resp.error?.flags || ""); + setError(""); + + } catch (e) { + if (e instanceof Error && e.name !== "AbortError") { + setError(`${e.name}: ${e.message}`); + } + } + setLoading(false); + }, [serverUrl]); + + return { + data, + error: error, + metricsError: metricsError, + flagsError: flagsError, + loading, + applyFilters: fetchData + }; +}; diff --git a/app/vmui/packages/vmui/src/pages/RetentionFilters/index.tsx b/app/vmui/packages/vmui/src/pages/RetentionFilters/index.tsx new file mode 100644 index 000000000..e0bfadfc2 --- /dev/null +++ b/app/vmui/packages/vmui/src/pages/RetentionFilters/index.tsx @@ -0,0 +1,137 @@ +import React, { FC, useEffect } from "preact/compat"; +import "./style.scss"; +import TextField from "../../components/Main/TextField/TextField"; +import { useCallback, useState } from "react"; +import Button from "../../components/Main/Button/Button"; +import { PlayIcon, WikiIcon } from "../../components/Main/Icons"; +import { useDebugRetentionFilters } from "./hooks/useDebugRetentionFilters"; +import Spinner from "../../components/Main/Spinner/Spinner"; +import { useSearchParams } from "react-router-dom"; + +const example = { + flags: `-retentionPeriod=1y +-retentionFilters={env!="prod"}:2w +`, + metrics: `up +up{env="dev"} +up{env="prod"}`, +}; + +const RetentionFilters: FC = () => { + const [searchParams] = useSearchParams(); + + const { data, loading, error, metricsError, flagsError, applyFilters } = useDebugRetentionFilters(); + const [metrics, setMetrics] = useState(searchParams.get("metrics") || ""); + const [flags, setFlags] = useState(searchParams.get("flags") || ""); + + const handleMetricsChangeInput = useCallback((val: string) => { + setMetrics(val); + }, [setMetrics]); + + const handleFlagsChangeInput = useCallback((val: string) => { + setFlags(val); + }, [setFlags]); + + const handleApplyFilters = useCallback(() => { + applyFilters(flags, metrics); + }, [applyFilters, flags, metrics]); + + const handleRunExample = useCallback(() => { + const { flags, metrics } = example; + setFlags(flags); + setMetrics(metrics); + applyFilters(flags, metrics); + searchParams.set("flags", flags); + searchParams.set("metrics", metrics); + }, [example, setFlags, setMetrics, searchParams]); + + useEffect(() => { + if (flags && metrics) handleApplyFilters(); + }, []); + + const rows = []; + for (const [key, value] of data) { + rows.push( + {key} + {value} + ); + } + return ( + + {loading && } + + + + + Provide a list of flags for retention configuration. Note that + only -retentionPeriod and -retentionFilters flags are + supported. + + + + + + Provide a list of metrics to check retention configuration. + + + + + + + + Metric + Applied retention + + + + {rows} + + + + + + + Documentation + + + Try example + + } + > + Apply + + + + + ); +}; + +export default RetentionFilters; diff --git a/app/vmui/packages/vmui/src/pages/RetentionFilters/style.scss b/app/vmui/packages/vmui/src/pages/RetentionFilters/style.scss new file mode 100644 index 000000000..69cecdb29 --- /dev/null +++ b/app/vmui/packages/vmui/src/pages/RetentionFilters/style.scss @@ -0,0 +1,46 @@ +@use "src/styles/variables" as *; + +.vm-retention-filters { + display: grid; + gap: $padding-medium; + + &-body { + display: grid; + gap: $padding-global; + align-items: flex-start; + width: 100%; + + &__title { + margin-bottom: $padding-medium; + } + + &-top { + display: flex; + gap: $padding-small; + align-items: center; + justify-content: flex-end; + } + + &__expr textarea { + min-height: 200px; + } + + &__result textarea { + min-height: 60px; + } + + code { + background-color: var(--color-hover-black); + border-radius: 6px; + font-size: 85%; + padding: .2em .4em; + } + + textarea { + font-family: $font-family-monospace; + overflow: auto; + width: 100%; + height: 100%; + } + } +} diff --git a/app/vmui/packages/vmui/src/router/index.ts b/app/vmui/packages/vmui/src/router/index.ts index 46856e849..231634d9d 100644 --- a/app/vmui/packages/vmui/src/router/index.ts +++ b/app/vmui/packages/vmui/src/router/index.ts @@ -15,6 +15,8 @@ const router = { icons: "/icons", anomaly: "/anomaly", query: "/query", + downsamplingDebug: "/downsampling-filters-debug", + retentionDebug: "/retention-filters-debug", }; export interface RouterOptionsHeader { @@ -108,6 +110,14 @@ export const routerOptions: {[key: string]: RouterOptions} = { [router.query]: { title: "Query", ...routerOptionsDefault + }, + [router.downsamplingDebug]: { + title: "Downsampling filters debug", + header: {} + }, + [router.retentionDebug]: { + title: "Retention filters debug", + header: {} } }; diff --git a/app/vmui/packages/vmui/src/router/navigation.ts b/app/vmui/packages/vmui/src/router/navigation.ts new file mode 100644 index 000000000..bf96205fe --- /dev/null +++ b/app/vmui/packages/vmui/src/router/navigation.ts @@ -0,0 +1,92 @@ +import router, { routerOptions } from "./index"; + +export enum NavigationItemType { + internalLink, + externalLink, +} + +export interface NavigationItem { + label?: string, + value?: string, + hide?: boolean + submenu?: NavigationItem[], + type?: NavigationItemType, +} + +interface NavigationConfig { + serverUrl: string, + isEnterpriseLicense: boolean, + showPredefinedDashboards: boolean, + showAlertLink: boolean, +} + +/** + * Special case for alert link + */ +const getAlertLink = (url: string, showAlertLink: boolean) => { + // see more https://docs.victoriametrics.com/cluster-victoriametrics/?highlight=vmalertproxyurl#vmalert + return { + label: "Alerts", + value: `${url}/vmalert`, + type: NavigationItemType.externalLink, + hide: !showAlertLink, + }; +}; + +/** + * Submenu for Tools tab + */ +const getToolsNav = (isEnterpriseLicense: boolean) => [ + { value: router.trace }, + { value: router.queryAnalyzer }, + { value: router.withTemplate }, + { value: router.relabel }, + { value: router.downsamplingDebug, hide: !isEnterpriseLicense }, + { value: router.retentionDebug, hide: !isEnterpriseLicense }, +]; + +/** + * Submenu for Explore tab + */ +const getExploreNav = () => [ + { value: router.metrics }, + { value: router.cardinality }, + { value: router.topQueries }, + { value: router.activeQueries }, +]; + +/** + * Default navigation menu + */ +export const getDefaultNavigation = ({ + serverUrl, + isEnterpriseLicense, + showPredefinedDashboards, + showAlertLink, +}: NavigationConfig): NavigationItem[] => [ + { value: router.home }, + { label: "Explore", submenu: getExploreNav() }, + { label: "Tools", submenu: getToolsNav(isEnterpriseLicense) }, + { value: router.dashboards, hide: !showPredefinedDashboards }, + getAlertLink(serverUrl, showAlertLink), +]; + +/** + * VictoriaLogs navigation menu + */ +export const getLogsNavigation = (): NavigationItem[] => [ + { + label: routerOptions[router.logs].title, + value: router.home, + }, +]; + +/** + * vmanomaly navigation menu + */ +export const getAnomalyNavigation = (): NavigationItem[] => [ + { + label: routerOptions[router.anomaly].title, + value: router.home, + }, +]; diff --git a/app/vmui/packages/vmui/src/router/useNavigationMenu.ts b/app/vmui/packages/vmui/src/router/useNavigationMenu.ts new file mode 100644 index 000000000..5f99c8816 --- /dev/null +++ b/app/vmui/packages/vmui/src/router/useNavigationMenu.ts @@ -0,0 +1,43 @@ +import { getAppModeEnable } from "../utils/app-mode"; +import { useDashboardsState } from "../state/dashboards/DashboardsStateContext"; +import { useAppState } from "../state/common/StateContext"; +import { useMemo } from "preact/compat"; +import { AppType } from "../types/appType"; +import { processNavigationItems } from "./utils"; +import { getAnomalyNavigation, getDefaultNavigation, getLogsNavigation } from "./navigation"; + +const appType = process.env.REACT_APP_TYPE; + +const useNavigationMenu = () => { + const appModeEnable = getAppModeEnable(); + const { dashboardsSettings } = useDashboardsState(); + const { serverUrl, flags, appConfig } = useAppState(); + const isEnterpriseLicense = appConfig.license?.type === "enterprise"; + const showAlertLink = Boolean(flags["vmalert.proxyURL"]); + const showPredefinedDashboards = Boolean(!appModeEnable && dashboardsSettings.length); + + const navigationConfig = useMemo(() => ({ + serverUrl, + isEnterpriseLicense, + showAlertLink, + showPredefinedDashboards + }), [serverUrl, isEnterpriseLicense, showAlertLink, showPredefinedDashboards]); + + + const menu = useMemo(() => { + switch (appType) { + case AppType.logs: + return getLogsNavigation(); + case AppType.anomaly: + return getAnomalyNavigation(); + default: + return getDefaultNavigation(navigationConfig); + } + }, [navigationConfig]); + + return processNavigationItems(menu); +}; + +export default useNavigationMenu; + + diff --git a/app/vmui/packages/vmui/src/router/utils.ts b/app/vmui/packages/vmui/src/router/utils.ts new file mode 100644 index 000000000..362f4c66c --- /dev/null +++ b/app/vmui/packages/vmui/src/router/utils.ts @@ -0,0 +1,30 @@ +import { routerOptions } from "./index"; +import { NavigationItem } from "./navigation"; + +const routePathToTitle = (path: string): string => { + try { + return path + .replace(/^\/+/, "") // Remove leading slashes + .replace(/-/g, " ") // Replace hyphens with spaces + .trim() // Trim whitespace from both ends + .replace(/^\w/, (c) => c.toUpperCase()); // Capitalize the first character + } catch (e) { + return path; + } +}; + +export const processNavigationItems = (items: NavigationItem[]): NavigationItem[] => { + return items.filter((item) => !item.hide).map((item) => { + const newItem: NavigationItem = { ...item }; + + if (newItem.value && !newItem.label) { + newItem.label = routerOptions[newItem.value]?.title || routePathToTitle(newItem.value); + } + + if (newItem.submenu && newItem.submenu.length > 0) { + newItem.submenu = processNavigationItems(newItem.submenu); + } + + return newItem; + }); +}; diff --git a/app/vmui/packages/vmui/src/state/common/reducer.ts b/app/vmui/packages/vmui/src/state/common/reducer.ts index 5c5986fb3..94e6cdcd8 100644 --- a/app/vmui/packages/vmui/src/state/common/reducer.ts +++ b/app/vmui/packages/vmui/src/state/common/reducer.ts @@ -1,7 +1,7 @@ import { getDefaultServer } from "../../utils/default-server-url"; import { getQueryStringValue } from "../../utils/query-string"; import { getFromStorage, saveToStorage } from "../../utils/storage"; -import { Theme } from "../../types"; +import { AppConfig, Theme } from "../../types"; import { isDarkTheme } from "../../utils/theme"; import { removeTrailingSlash } from "../../utils/url"; @@ -11,6 +11,7 @@ export interface AppState { theme: Theme; isDarkTheme: boolean | null; flags: Record; + appConfig: AppConfig } export type Action = @@ -18,6 +19,7 @@ export type Action = | { type: "SET_THEME", payload: Theme } | { type: "SET_TENANT_ID", payload: string } | { type: "SET_FLAGS", payload: Record } + | { type: "SET_APP_CONFIG", payload: AppConfig } | { type: "SET_DARK_THEME" } const tenantId = getQueryStringValue("g0.tenantID", "") as string; @@ -28,6 +30,7 @@ export const initialState: AppState = { theme: (getFromStorage("THEME") || Theme.system) as Theme, isDarkTheme: null, flags: {}, + appConfig: {} }; export function reducer(state: AppState, action: Action): AppState { @@ -58,6 +61,11 @@ export function reducer(state: AppState, action: Action): AppState { ...state, flags: action.payload }; + case "SET_APP_CONFIG": + return { + ...state, + appConfig: action.payload + }; default: throw new Error(); } diff --git a/app/vmui/packages/vmui/src/types/index.ts b/app/vmui/packages/vmui/src/types/index.ts index 6d71c6b20..d8a1e1b6d 100644 --- a/app/vmui/packages/vmui/src/types/index.ts +++ b/app/vmui/packages/vmui/src/types/index.ts @@ -165,3 +165,9 @@ export enum QueryContextType { label = "label", labelValue = "labelValue", } + +export interface AppConfig { + license?: { + type?: "enterprise" | "opensource"; + } +}
Provide a list of flags for downsampling configuration. Note that + only -downsampling.period and -dedup.minScrapeInterval flags are supported
-downsampling.period
-dedup.minScrapeInterval
Provide a list of metrics to check downsampling configuration.
Provide a list of flags for retention configuration. Note that + only -retentionPeriod and -retentionFilters flags are + supported.
-retentionPeriod
-retentionFilters
Provide a list of metrics to check retention configuration.