mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
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:
parent
8657d03433
commit
25a9802ca4
9 changed files with 113 additions and 17 deletions
|
@ -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 = {
|
||||
|
|
44
app/vmui/packages/vmui/src/hooks/useFetchFlags.ts
Normal file
44
app/vmui/packages/vmui/src/hooks/useFetchFlags.ts
Normal 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;
|
||||
|
|
@ -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}
|
||||
/>
|
||||
)
|
||||
))}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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, "");
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue