vmui: add link to vmalert (#7088)

### Describe Your Changes

Add link to VMalert when proxy is enabled. The link is displayed when
the `-vmalert.proxyURL` flag is present.

#5924


![image](https://github.com/user-attachments/assets/c45ca884-8912-4bd9-a867-df5919f278a1)

### Checklist

The following checks are **mandatory**:

- [ ] My change adheres [VictoriaMetrics contributing
guidelines](https://docs.victoriametrics.com/contributing/).

---------

Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
Yury Molodov 2024-09-27 13:22:22 +02:00 committed by GitHub
parent 8657d03433
commit 25a9802ca4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 113 additions and 17 deletions

View file

@ -1,10 +1,16 @@
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 = {

View file

@ -0,0 +1,44 @@
import { useAppDispatch, useAppState } from "../state/common/StateContext";
import { useEffect, useState } from "preact/compat";
import { ErrorTypes } from "../types";
import { getUrlWithoutTenant } from "../utils/tenants";
const useFetchFlags = () => {
const { serverUrl } = useAppState();
const dispatch = useAppDispatch();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<ErrorTypes | string>("");
useEffect(() => {
const fetchFlags = async () => {
if (!serverUrl || process.env.REACT_APP_TYPE) return;
setError("");
setIsLoading(true);
try {
const url = getUrlWithoutTenant(serverUrl);
const response = await fetch(`${url}/flags`);
const data = await response.text();
const flags = data.split("\n").filter(flag => flag.trim() !== "")
.reduce((acc, flag) => {
const [keyRaw, valueRaw] = flag.split("=");
const key = keyRaw.trim().replace(/^-/, "");
acc[key.trim()] = valueRaw ? valueRaw.trim().replace(/^"(.*)"$/, "$1") : null;
return acc;
}, {} as Record<string, string|null>);
dispatch({ type: "SET_FLAGS", payload: flags });
} catch (e) {
setIsLoading(false);
if (e instanceof Error) setError(`${e.name}: ${e.message}`);
}
};
fetchFlags();
}, [serverUrl]);
return { isLoading, error };
};
export default useFetchFlags;

View file

@ -8,8 +8,9 @@ import "./style.scss";
import NavItem from "./NavItem";
import NavSubItem from "./NavSubItem";
import classNames from "classnames";
import { anomalyNavigation, defaultNavigation, logsNavigation } from "../../../constants/navigation";
import { anomalyNavigation, defaultNavigation, logsNavigation, NavigationItemType } from "../../../constants/navigation";
import { AppType } from "../../../types/appType";
import { useAppState } from "../../../state/common/StateContext";
interface HeaderNavProps {
color: string
@ -21,6 +22,7 @@ const HeaderNav: FC<HeaderNavProps> = ({ color, background, direction }) => {
const appModeEnable = getAppModeEnable();
const { dashboardsSettings } = useDashboardsState();
const { pathname } = useLocation();
const { serverUrl, flags } = useAppState();
const [activeMenu, setActiveMenu] = useState(pathname);
@ -37,7 +39,14 @@ const HeaderNav: FC<HeaderNavProps> = ({ color, background, direction }) => {
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]);
@ -74,6 +83,7 @@ const HeaderNav: FC<HeaderNavProps> = ({ color, background, direction }) => {
value={m.value || ""}
label={m.label || ""}
color={color}
type={m.type || NavigationItemType.internalLink}
/>
)
))}

View file

@ -1,30 +1,49 @@
import React, { FC } from "preact/compat";
import { NavLink } from "react-router-dom";
import classNames from "classnames";
import { NavigationItemType } from "../../../constants/navigation";
interface NavItemProps {
activeMenu: string,
label: string,
value: string,
color?: string
type: NavigationItemType,
color?: string,
}
const NavItem: FC<NavItemProps> = ({
activeMenu,
label,
value,
type,
color
}) => (
<NavLink
className={classNames({
"vm-header-nav-item": true,
"vm-header-nav-item_active": activeMenu === value // || m.submenu?.find(m => m.value === activeMenu)
})}
style={{ color }}
to={value}
>
{label}
</NavLink>
);
}) => {
if (type === NavigationItemType.externalLink) return (
<a
className={classNames({
"vm-header-nav-item": true,
"vm-header-nav-item_active": activeMenu === value
})}
style={{ color }}
href={value}
target={"_blank"}
rel="noreferrer"
>
{label}
</a>
);
return (
<NavLink
className={classNames({
"vm-header-nav-item": true,
"vm-header-nav-item_active": activeMenu === value // || m.submenu?.find(m => m.value === activeMenu)
})}
style={{ color }}
to={value}
>
{label}
</NavLink>
);
};
export default NavItem;

View file

@ -6,7 +6,7 @@ import Popper from "../../../components/Main/Popper/Popper";
import NavItem from "./NavItem";
import { useEffect } from "react";
import useBoolean from "../../../hooks/useBoolean";
import { NavigationItem } from "../../../constants/navigation";
import { NavigationItem, NavigationItemType } from "../../../constants/navigation";
interface NavItemProps {
activeMenu: string,
@ -64,6 +64,7 @@ const NavSubItem: FC<NavItemProps> = ({
activeMenu={activeMenu}
value={sm.value || ""}
label={sm.label || ""}
type={sm.type || NavigationItemType.internalLink}
/>
))}
</>
@ -106,6 +107,7 @@ const NavSubItem: FC<NavItemProps> = ({
value={sm.value || ""}
label={sm.label || ""}
color={color}
type={sm.type || NavigationItemType.internalLink}
/>
))}
</div>

View file

@ -11,6 +11,7 @@ import { useFetchDashboards } from "../../pages/PredefinedPanels/hooks/useFetchD
import useDeviceDetect from "../../hooks/useDeviceDetect";
import ControlsMainLayout from "./ControlsMainLayout";
import useFetchDefaultTimezone from "../../hooks/useFetchDefaultTimezone";
import useFetchFlags from "../../hooks/useFetchFlags";
const MainLayout: FC = () => {
const appModeEnable = getAppModeEnable();
@ -20,6 +21,7 @@ const MainLayout: FC = () => {
useFetchDashboards();
useFetchDefaultTimezone();
useFetchFlags();
const setDocumentTitle = () => {
const defaultTitle = "vmui";

View file

@ -10,12 +10,14 @@ export interface AppState {
tenantId: string;
theme: Theme;
isDarkTheme: boolean | null;
flags: Record<string, string | null>;
}
export type Action =
| { type: "SET_SERVER", payload: string }
| { type: "SET_THEME", payload: Theme }
| { type: "SET_TENANT_ID", payload: string }
| { type: "SET_FLAGS", payload: Record<string, string | null> }
| { type: "SET_DARK_THEME" }
const tenantId = getQueryStringValue("g0.tenantID", "") as string;
@ -24,7 +26,8 @@ export const initialState: AppState = {
serverUrl: removeTrailingSlash(getDefaultServer(tenantId)),
tenantId,
theme: (getFromStorage("THEME") || Theme.system) as Theme,
isDarkTheme: null
isDarkTheme: null,
flags: {},
};
export function reducer(state: AppState, action: Action): AppState {
@ -50,6 +53,11 @@ export function reducer(state: AppState, action: Action): AppState {
...state,
isDarkTheme: isDarkTheme(state.theme)
};
case "SET_FLAGS":
return {
...state,
flags: action.payload
};
default:
throw new Error();
}

View file

@ -7,3 +7,7 @@ export const replaceTenantId = (serverUrl: string, tenantId: string) => {
export const getTenantIdFromUrl = (url: string): string => {
return url.match(regexp)?.[2] || "";
};
export const getUrlWithoutTenant = (url: string): string => {
return url.replace(regexp, "");
};

View file

@ -26,6 +26,7 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/).
* FEATURE: [vmgateway](https://docs.victoriametrics.com/vmgateway/): support parsing `vm_access` claims in string format. This is useful for cases when identity provider does not support mapping claims to JSON format.
* FEATURE: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and `vmstorage` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): add new metrics for data ingestion: `vm_rows_received_by_storage_total`, `vm_rows_ignored_total{reason="nan_value"}`, `vm_rows_ignored_total{reason="invalid_raw_metric_name"}`, `vm_rows_ignored_total{reason="hourly_limit_exceeded"}`, `vm_rows_ignored_total{reason="daily_limit_exceeded"}`. See this [PR](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/6663) for details.
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): change request method for `/query_range` and `/query` calls from `GET` to `POST`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6288).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add link to vmalert when proxy is enabled. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5924).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): keep selected columns in table view on page reloads. Before, selected columns were reset on each update. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7016).
* FEATURE: [dashboards](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/dashboards) for VM single-node, cluster, vmalert, vmagent, VictoriaLogs: add `Go scheduling latency` panel to show the 99th quantile of Go goroutines scheduling. This panel should help identifying insufficient CPU resources for the service. It is especially useful if CPU gets throttled, which now should be visible on this panel.
* FEATURE: [alerts](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/alerts-health.yml): add alerting rule to track the Go scheduling latency for goroutines. It should notify users if VM component doesn't have enough CPU to run or gets throttled.