vmui: make the step input field global across all the tabs and views (#3644)

* feat: make the step input field global

* fix: correct get step from url

* fix: set minimumSignificantDigits to 1

* app/vmselect/vmui: `make vmui-update`

Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
Yury Molodov 2023-01-15 22:47:08 +01:00 committed by Aliaksandr Valialkin
parent 556484a52f
commit 060780af69
No known key found for this signature in database
GPG key ID: A72BEC6CD3D0DED1
23 changed files with 220 additions and 126 deletions

View file

@ -1,12 +1,12 @@
{ {
"files": { "files": {
"main.css": "./static/css/main.7672c15c.css", "main.css": "./static/css/main.01144815.css",
"main.js": "./static/js/main.84759f8d.js", "main.js": "./static/js/main.f9c0c050.js",
"static/js/27.c1ccfd29.chunk.js": "./static/js/27.c1ccfd29.chunk.js", "static/js/27.c1ccfd29.chunk.js": "./static/js/27.c1ccfd29.chunk.js",
"index.html": "./index.html" "index.html": "./index.html"
}, },
"entrypoints": [ "entrypoints": [
"static/css/main.7672c15c.css", "static/css/main.01144815.css",
"static/js/main.84759f8d.js" "static/js/main.f9c0c050.js"
] ]
} }

View file

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono&family=Lato:wght@300;400;700&display=swap" rel="stylesheet"><script src="./dashboards/index.js" type="module"></script><script defer="defer" src="./static/js/main.84759f8d.js"></script><link href="./static/css/main.7672c15c.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html> <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="VM-UI is a metric explorer for Victoria Metrics"/><link rel="apple-touch-icon" href="./apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"><link rel="manifest" href="./manifest.json"/><title>VM UI</title><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono&family=Lato:wght@300;400;700&display=swap" rel="stylesheet"><script src="./dashboards/index.js" type="module"></script><script defer="defer" src="./static/js/main.f9c0c050.js"></script><link href="./static/css/main.01144815.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -17,6 +17,17 @@
* @license MIT * @license MIT
*/ */
/**
* React Router DOM v6.4.5
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
/** /**
* React Router v6.4.5 * React Router v6.4.5
* *

View file

@ -1,20 +1,13 @@
import React, { FC, useEffect } from "preact/compat"; import React, { FC } from "preact/compat";
import StepConfigurator from "../StepConfigurator/StepConfigurator";
import { useGraphDispatch, useGraphState } from "../../../state/graph/GraphStateContext";
import { getAppModeParams } from "../../../utils/app-mode"; import { getAppModeParams } from "../../../utils/app-mode";
import TenantsConfiguration from "../TenantsConfiguration/TenantsConfiguration"; import TenantsConfiguration from "../TenantsConfiguration/TenantsConfiguration";
import { useCustomPanelDispatch, useCustomPanelState } from "../../../state/customPanel/CustomPanelStateContext"; import { useCustomPanelDispatch, useCustomPanelState } from "../../../state/customPanel/CustomPanelStateContext";
import { useTimeState } from "../../../state/time/TimeStateContext";
import { useQueryDispatch, useQueryState } from "../../../state/query/QueryStateContext"; import { useQueryDispatch, useQueryState } from "../../../state/query/QueryStateContext";
import "./style.scss"; import "./style.scss";
import Switch from "../../Main/Switch/Switch"; import Switch from "../../Main/Switch/Switch";
import usePrevious from "../../../hooks/usePrevious";
const AdditionalSettings: FC = () => { const AdditionalSettings: FC = () => {
const { customStep } = useGraphState();
const graphDispatch = useGraphDispatch();
const { inputTenantID } = getAppModeParams(); const { inputTenantID } = getAppModeParams();
const { autocomplete } = useQueryState(); const { autocomplete } = useQueryState();
@ -23,9 +16,6 @@ const AdditionalSettings: FC = () => {
const { nocache, isTracingEnabled } = useCustomPanelState(); const { nocache, isTracingEnabled } = useCustomPanelState();
const customPanelDispatch = useCustomPanelDispatch(); const customPanelDispatch = useCustomPanelDispatch();
const { period: { step }, duration } = useTimeState();
const prevDuration = usePrevious(duration);
const onChangeCache = () => { const onChangeCache = () => {
customPanelDispatch({ type: "TOGGLE_NO_CACHE" }); customPanelDispatch({ type: "TOGGLE_NO_CACHE" });
}; };
@ -38,19 +28,6 @@ const AdditionalSettings: FC = () => {
queryDispatch({ type: "TOGGLE_AUTOCOMPLETE" }); queryDispatch({ type: "TOGGLE_AUTOCOMPLETE" });
}; };
const onChangeStep = (value: string) => {
graphDispatch({ type: "SET_CUSTOM_STEP", payload: value });
};
useEffect(() => {
if (!customStep && step) onChangeStep(step);
}, [step]);
useEffect(() => {
if (duration === prevDuration || !prevDuration) return;
if (step) onChangeStep(step);
}, [duration, prevDuration]);
return <div className="vm-additional-settings"> return <div className="vm-additional-settings">
<Switch <Switch
label={"Autocomplete"} label={"Autocomplete"}
@ -67,13 +44,6 @@ const AdditionalSettings: FC = () => {
value={isTracingEnabled} value={isTracingEnabled}
onChange={onChangeQueryTracing} onChange={onChangeQueryTracing}
/> />
<div className="vm-additional-settings__input">
<StepConfigurator
defaultStep={step}
setStep={onChangeStep}
value={customStep}
/>
</div>
{!!inputTenantID && ( {!!inputTenantID && (
<div className="vm-additional-settings__input"> <div className="vm-additional-settings__input">
<TenantsConfiguration/> <TenantsConfiguration/>

View file

@ -1,26 +1,59 @@
import React, { FC, useEffect, useState } from "preact/compat"; import React, { FC, useEffect, useRef, useState } from "preact/compat";
import { RestartIcon } from "../../Main/Icons"; import { RestartIcon, TimelineIcon } from "../../Main/Icons";
import TextField from "../../Main/TextField/TextField"; import TextField from "../../Main/TextField/TextField";
import Button from "../../Main/Button/Button"; import Button from "../../Main/Button/Button";
import Tooltip from "../../Main/Tooltip/Tooltip"; import Tooltip from "../../Main/Tooltip/Tooltip";
import { ErrorTypes } from "../../../types"; import { ErrorTypes } from "../../../types";
import { supportedDurations } from "../../../utils/time"; import { supportedDurations } from "../../../utils/time";
import { useTimeState } from "../../../state/time/TimeStateContext";
import { useGraphDispatch, useGraphState } from "../../../state/graph/GraphStateContext";
import usePrevious from "../../../hooks/usePrevious";
import "./style.scss";
import { getAppModeEnable } from "../../../utils/app-mode";
import Popper from "../../Main/Popper/Popper";
interface StepConfiguratorProps { const StepConfigurator: FC = () => {
defaultStep?: string, const appModeEnable = getAppModeEnable();
value?: string,
setStep: (step: string) => void,
}
const StepConfigurator: FC<StepConfiguratorProps> = ({ value, defaultStep, setStep }) => { const { customStep: value } = useGraphState();
const { period: { step: defaultStep } } = useTimeState();
const graphDispatch = useGraphDispatch();
const { period: duration } = useTimeState();
const prevDuration = usePrevious(duration.end - duration.start);
const [openOptions, setOpenOptions] = useState(false);
const [customStep, setCustomStep] = useState(value || defaultStep); const [customStep, setCustomStep] = useState(value || defaultStep);
const [error, setError] = useState(""); const [error, setError] = useState("");
const buttonRef = useRef<HTMLDivElement>(null);
const toggleOpenOptions = () => {
setOpenOptions(prev => !prev);
};
const handleCloseOptions = () => {
setOpenOptions(false);
};
const handleApply = (value?: string) => { const handleApply = (value?: string) => {
const step = value || customStep || defaultStep || "1s"; const step = value || customStep || defaultStep || "1s";
const durations = step.match(/[a-zA-Z]+/g) || []; const durations = step.match(/[a-zA-Z]+/g) || [];
setStep(!durations.length ? `${step}s` : step); const stepDur = !durations.length ? `${step}s` : step;
graphDispatch({ type: "SET_CUSTOM_STEP", payload: stepDur });
setCustomStep(stepDur);
setError("");
};
const handleFocus = () => {
if (document.activeElement instanceof HTMLInputElement) {
document.activeElement.select();
}
};
const handleEnter = () => {
handleApply();
handleCloseOptions();
}; };
const handleChangeStep = (value: string) => { const handleChangeStep = (value: string) => {
@ -46,28 +79,94 @@ const StepConfigurator: FC<StepConfiguratorProps> = ({ value, defaultStep, setSt
}; };
useEffect(() => { useEffect(() => {
if (value) handleChangeStep(value); if (value) {
handleApply(value);
}
}, [value]); }, [value]);
useEffect(() => {
if (!value && defaultStep) {
handleApply(defaultStep);
}
}, [defaultStep]);
useEffect(() => {
const dur = duration.end - duration.start;
if (dur === prevDuration || !prevDuration) return;
if (defaultStep) {
handleApply(defaultStep);
}
}, [duration, prevDuration, defaultStep]);
return ( return (
<TextField <div
label="Step value" className="vm-step-control"
value={customStep} ref={buttonRef}
error={error} >
onChange={handleChangeStep} <Tooltip title="Query resolution step width">
onEnter={handleApply} <Button
onBlur={handleApply} className={appModeEnable ? "" : "vm-header-button"}
endIcon={( variant="contained"
<Tooltip title="Reset step to default"> color="primary"
<Button startIcon={<TimelineIcon/>}
variant={"text"} onClick={toggleOpenOptions}
size={"small"} >
startIcon={<RestartIcon/>} STEP {customStep}
onClick={handleReset} </Button>
</Tooltip>
<Popper
open={openOptions}
placement="bottom-right"
onClose={handleCloseOptions}
buttonRef={buttonRef}
>
<div className="vm-step-control-popper">
<TextField
autofocus
label="Step value"
value={customStep}
error={error}
onChange={handleChangeStep}
onEnter={handleEnter}
onFocus={handleFocus}
onBlur={handleApply}
endIcon={(
<Tooltip title={`Set default step value: ${defaultStep}`}>
<Button
size="small"
variant="text"
color="primary"
startIcon={<RestartIcon/>}
onClick={handleReset}
/>
</Tooltip>
)}
/> />
</Tooltip> <div className="vm-step-control-popper-info">
)} <code>step</code> - the <a
/> className="vm-link vm-link_colored"
href="https://prometheus.io/docs/prometheus/latest/querying/basics/#time-durations"
target="_blank"
rel="noreferrer"
>
interval
</a>
between datapoints, which must be returned from the range query.
The <code>query</code> is executed at
<code>start</code>, <code>start+step</code>, <code>start+2*step</code>, , <code>end</code> timestamps.
<a
className="vm-link vm-link_colored"
href="https://docs.victoriametrics.com/keyConcepts.html#range-query"
target="_blank"
rel="noreferrer"
>
Read more about Range query
</a>
</div>
</div>
</Popper>
</div>
); );
}; };

View file

@ -0,0 +1,37 @@
@use "src/styles/variables" as *;
@use "src/components/Main/Button/style" as *;
.vm-step-control {
display: inline-flex;
button {
text-transform: none;
}
&-popper {
display: grid;
gap: $padding-small;
max-width: 300px;
max-height: 208px;
overflow: auto;
padding: $padding-global;
font-size: $font-size;
&-info {
font-size: $font-size-small;
line-height: 1.6;
a {
margin: 0 0.2em;
}
code {
padding: 0.2em 0.4em;
margin: 0 0.2em;
font-size: 85%;
background-color: rgba($color-black, 0.05);
border-radius: 6px;
}
}
}
}

View file

@ -1,10 +1,6 @@
import React, { FC, useEffect, useMemo } from "preact/compat"; import React, { FC, useMemo } from "preact/compat";
import Select from "../../Main/Select/Select"; import Select from "../../Main/Select/Select";
import StepConfigurator from "../../Configurators/StepConfigurator/StepConfigurator";
import "./style.scss"; import "./style.scss";
import { useTimeState } from "../../../state/time/TimeStateContext";
import { useGraphDispatch, useGraphState } from "../../../state/graph/GraphStateContext";
import usePrevious from "../../../hooks/usePrevious";
import { GRAPH_SIZES } from "../../../constants/graph"; import { GRAPH_SIZES } from "../../../constants/graph";
interface ExploreMetricsHeaderProps { interface ExploreMetricsHeaderProps {
@ -36,28 +32,9 @@ const ExploreMetricsHeader: FC<ExploreMetricsHeaderProps> = ({
onToggleMetric, onToggleMetric,
onChangeSize onChangeSize
}) => { }) => {
const { period: { step }, duration } = useTimeState();
const { customStep } = useGraphState();
const graphDispatch = useGraphDispatch();
const prevDuration = usePrevious(duration);
const noInstanceText = useMemo(() => job ? "" : "No instances. Please select job", [job]); const noInstanceText = useMemo(() => job ? "" : "No instances. Please select job", [job]);
const noMetricsText = useMemo(() => job ? "" : "No metric names. Please select job", [job]); const noMetricsText = useMemo(() => job ? "" : "No metric names. Please select job", [job]);
const handleChangeStep = (value: string) => {
graphDispatch({ type: "SET_CUSTOM_STEP", payload: value });
};
useEffect(() => {
if (duration === prevDuration || !prevDuration) return;
if (customStep) handleChangeStep(step || "1s");
}, [duration, prevDuration]);
useEffect(() => {
if (!customStep && step) handleChangeStep(step);
}, [step]);
return ( return (
<div className="vm-explore-metrics-header vm-block"> <div className="vm-explore-metrics-header vm-block">
<div className="vm-explore-metrics-header__job"> <div className="vm-explore-metrics-header__job">
@ -81,13 +58,6 @@ const ExploreMetricsHeader: FC<ExploreMetricsHeaderProps> = ({
clearable clearable
/> />
</div> </div>
<div className="vm-explore-metrics-header__step">
<StepConfigurator
defaultStep={step}
setStep={handleChangeStep}
value={customStep}
/>
</div>
<div className="vm-explore-metrics-header__size"> <div className="vm-explore-metrics-header__size">
<Select <Select
label="Size graphs" label="Size graphs"

View file

@ -8,7 +8,7 @@ const Footer: FC = () => {
return <footer className="vm-footer"> return <footer className="vm-footer">
<a <a
className="vm__link vm-footer__website" className="vm-link vm-footer__website"
target="_blank" target="_blank"
href="https://victoriametrics.com/" href="https://victoriametrics.com/"
rel="noreferrer" rel="noreferrer"
@ -17,7 +17,7 @@ const Footer: FC = () => {
victoriametrics.com victoriametrics.com
</a> </a>
<a <a
className="vm__link" className="vm-link"
target="_blank" target="_blank"
href="https://github.com/VictoriaMetrics/VictoriaMetrics/issues/new/choose" href="https://github.com/VictoriaMetrics/VictoriaMetrics/issues/new/choose"
rel="noreferrer" rel="noreferrer"

View file

@ -15,6 +15,7 @@ import Tabs from "../../Main/Tabs/Tabs";
import "./style.scss"; import "./style.scss";
import classNames from "classnames"; import classNames from "classnames";
import { useDashboardsState } from "../../../state/dashboards/DashboardsStateContext"; import { useDashboardsState } from "../../../state/dashboards/DashboardsStateContext";
import StepConfigurator from "../../Configurators/StepConfigurator/StepConfigurator";
const Header: FC = () => { const Header: FC = () => {
const primaryColor = getCssVariable("color-primary"); const primaryColor = getCssVariable("color-primary");
@ -101,6 +102,7 @@ const Header: FC = () => {
/> />
</div> </div>
<div className="vm-header__settings"> <div className="vm-header__settings">
{headerSetup?.stepControl && <StepConfigurator/>}
{headerSetup?.timeSelector && <TimeSelector/>} {headerSetup?.timeSelector && <TimeSelector/>}
{headerSetup?.cardinalityDatePicker && <CardinalityDatePicker/>} {headerSetup?.cardinalityDatePicker && <CardinalityDatePicker/>}
{headerSetup?.executionControls && <ExecutionControls/>} {headerSetup?.executionControls && <ExecutionControls/>}

View file

@ -333,3 +333,14 @@ export const ResizeIcon = () => (
<path d="M21 11V3h-8l3.29 3.29-10 10L3 13v8h8l-3.29-3.29 10-10z"></path> <path d="M21 11V3h-8l3.29 3.29-10 10L3 13v8h8l-3.29-3.29 10-10z"></path>
</svg> </svg>
); );
export const TimelineIcon = () => (
<svg
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M23 8c0 1.1-.9 2-2 2-.18 0-.35-.02-.51-.07l-3.56 3.55c.05.16.07.34.07.52 0 1.1-.9 2-2 2s-2-.9-2-2c0-.18.02-.36.07-.52l-2.55-2.55c-.16.05-.34.07-.52.07s-.36-.02-.52-.07l-4.55 4.56c.05.16.07.33.07.51 0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2c.18 0 .35.02.51.07l4.56-4.55C8.02 9.36 8 9.18 8 9c0-1.1.9-2 2-2s2 .9 2 2c0 .18-.02.36-.07.52l2.55 2.55c.16-.05.34-.07.52-.07s.36.02.52.07l3.55-3.56C19.02 8.35 19 8.18 19 8c0-1.1.9-2 2-2s2 .9 2 2z"
></path>
</svg>
);

View file

@ -2,6 +2,7 @@ import { useEffect } from "react";
import { compactObject } from "../../../utils/object"; import { compactObject } from "../../../utils/object";
import { useTimeState } from "../../../state/time/TimeStateContext"; import { useTimeState } from "../../../state/time/TimeStateContext";
import { setQueryStringWithoutPageReload } from "../../../utils/query-string"; import { setQueryStringWithoutPageReload } from "../../../utils/query-string";
import { useGraphState } from "../../../state/graph/GraphStateContext";
interface queryProps { interface queryProps {
job: string job: string
@ -11,13 +12,14 @@ interface queryProps {
} }
export const useSetQueryParams = ({ job, instance, metrics, size }: queryProps) => { export const useSetQueryParams = ({ job, instance, metrics, size }: queryProps) => {
const { duration, relativeTime, period: { date, step } } = useTimeState(); const { duration, relativeTime, period: { date } } = useTimeState();
const { customStep } = useGraphState();
const setSearchParamsFromState = () => { const setSearchParamsFromState = () => {
const params = compactObject({ const params = compactObject({
["g0.range_input"]: duration, ["g0.range_input"]: duration,
["g0.end_input"]: date, ["g0.end_input"]: date,
["g0.step_input"]: step, ["g0.step_input"]: customStep,
["g0.relative_time"]: relativeTime, ["g0.relative_time"]: relativeTime,
size, size,
job, job,
@ -28,6 +30,6 @@ export const useSetQueryParams = ({ job, instance, metrics, size }: queryProps)
setQueryStringWithoutPageReload(params); setQueryStringWithoutPageReload(params);
}; };
useEffect(setSearchParamsFromState, [duration, relativeTime, date, step, job, instance, metrics, size]); useEffect(setSearchParamsFromState, [duration, relativeTime, date, customStep, job, instance, metrics, size]);
useEffect(setSearchParamsFromState, []); useEffect(setSearchParamsFromState, []);
}; };

View file

@ -4,7 +4,6 @@ import { AxisRange, YaxisState } from "../../../state/graph/reducer";
import GraphView from "../../../components/Views/GraphView/GraphView"; import GraphView from "../../../components/Views/GraphView/GraphView";
import { useFetchQuery } from "../../../hooks/useFetchQuery"; import { useFetchQuery } from "../../../hooks/useFetchQuery";
import Spinner from "../../../components/Main/Spinner/Spinner"; import Spinner from "../../../components/Main/Spinner/Spinner";
import StepConfigurator from "../../../components/Configurators/StepConfigurator/StepConfigurator";
import GraphSettings from "../../../components/Configurators/GraphSettings/GraphSettings"; import GraphSettings from "../../../components/Configurators/GraphSettings/GraphSettings";
import { marked } from "marked"; import { marked } from "marked";
import { useTimeDispatch, useTimeState } from "../../../state/time/TimeStateContext"; import { useTimeDispatch, useTimeState } from "../../../state/time/TimeStateContext";
@ -12,7 +11,7 @@ import { InfoIcon } from "../../../components/Main/Icons";
import "./style.scss"; import "./style.scss";
import Alert from "../../../components/Main/Alert/Alert"; import Alert from "../../../components/Main/Alert/Alert";
import Tooltip from "../../../components/Main/Tooltip/Tooltip"; import Tooltip from "../../../components/Main/Tooltip/Tooltip";
import usePrevious from "../../../hooks/usePrevious"; import { useGraphState } from "../../../state/graph/GraphStateContext";
export interface PredefinedPanelsProps extends PanelSettings { export interface PredefinedPanelsProps extends PanelSettings {
filename: string; filename: string;
@ -28,13 +27,12 @@ const PredefinedPanel: FC<PredefinedPanelsProps> = ({
alias alias
}) => { }) => {
const { period, duration } = useTimeState(); const { period } = useTimeState();
const { customStep } = useGraphState();
const dispatch = useTimeDispatch(); const dispatch = useTimeDispatch();
const prevDuration = usePrevious(duration);
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const [visible, setVisible] = useState(true); const [visible, setVisible] = useState(true);
const [customStep, setCustomStep] = useState(period.step || "1s");
const [yaxis, setYaxis] = useState<YaxisState>({ const [yaxis, setYaxis] = useState<YaxisState>({
limits: { limits: {
enable: false, enable: false,
@ -77,11 +75,6 @@ const PredefinedPanel: FC<PredefinedPanelsProps> = ({
}; };
}, []); }, []);
useEffect(() => {
if (duration === prevDuration || !prevDuration) return;
if (customStep) setCustomStep(period.step || "1s");
}, [duration, prevDuration]);
if (!validExpr) return ( if (!validExpr) return (
<Alert variant="error"> <Alert variant="error">
<code>&quot;expr&quot;</code> not found. Check the configuration file <b>{filename}</b>. <code>&quot;expr&quot;</code> not found. Check the configuration file <b>{filename}</b>.
@ -123,13 +116,6 @@ const PredefinedPanel: FC<PredefinedPanelsProps> = ({
<h3 className="vm-predefined-panel-header__title"> <h3 className="vm-predefined-panel-header__title">
{title || ""} {title || ""}
</h3> </h3>
<div className="vm-predefined-panel-header__step">
<StepConfigurator
defaultStep={period.step}
value={customStep}
setStep={setCustomStep}
/>
</div>
<GraphSettings <GraphSettings
yaxis={yaxis} yaxis={yaxis}
setYaxisLimits={setYaxisLimits} setYaxisLimits={setYaxisLimits}

View file

@ -4,7 +4,7 @@
&-header { &-header {
display: grid; display: grid;
grid-template-columns: auto 1fr 160px auto; grid-template-columns: auto 1fr auto;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
gap: $padding-small; gap: $padding-small;

View file

@ -2,21 +2,23 @@ import { useEffect } from "react";
import { compactObject } from "../../../utils/object"; import { compactObject } from "../../../utils/object";
import { useTimeState } from "../../../state/time/TimeStateContext"; import { useTimeState } from "../../../state/time/TimeStateContext";
import { setQueryStringWithoutPageReload } from "../../../utils/query-string"; import { setQueryStringWithoutPageReload } from "../../../utils/query-string";
import { useGraphState } from "../../../state/graph/GraphStateContext";
export const useSetQueryParams = () => { export const useSetQueryParams = () => {
const { duration, relativeTime, period: { date, step } } = useTimeState(); const { duration, relativeTime, period: { date } } = useTimeState();
const { customStep } = useGraphState();
const setSearchParamsFromState = () => { const setSearchParamsFromState = () => {
const params = compactObject({ const params = compactObject({
["g0.range_input"]: duration, ["g0.range_input"]: duration,
["g0.end_input"]: date, ["g0.end_input"]: date,
["g0.step_input"]: step, ["g0.step_input"]: customStep,
["g0.relative_time"]: relativeTime ["g0.relative_time"]: relativeTime
}); });
setQueryStringWithoutPageReload(params); setQueryStringWithoutPageReload(params);
}; };
useEffect(setSearchParamsFromState, [duration, relativeTime, date, step]); useEffect(setSearchParamsFromState, [duration, relativeTime, date, customStep]);
useEffect(setSearchParamsFromState, []); useEffect(setSearchParamsFromState, []);
}; };

View file

@ -40,7 +40,7 @@ const Index: FC = () => {
const getQueryStatsTitle = (key: keyof TopQueryStats) => { const getQueryStatsTitle = (key: keyof TopQueryStats) => {
if (!data) return key; if (!data) return key;
const value = data[key]; const value = data[key];
if (typeof value === "number") return formatPrettyNumber(value); if (typeof value === "number") return formatPrettyNumber(value, 0, value);
return value || key; return value || key;
}; };

View file

@ -148,7 +148,7 @@ const TracePage: FC = () => {
{"\n"} {"\n"}
In order to use tracing please refer to the doc:&nbsp; In order to use tracing please refer to the doc:&nbsp;
<a <a
className="vm__link vm__link_colored" className="vm-link vm-link_colored"
href="https://docs.victoriametrics.com/#query-tracing" href="https://docs.victoriametrics.com/#query-tracing"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"

View file

@ -11,6 +11,7 @@ const router = {
export interface RouterOptions { export interface RouterOptions {
title?: string, title?: string,
header: { header: {
stepControl?: boolean,
timeSelector?: boolean, timeSelector?: boolean,
executionControls?: boolean, executionControls?: boolean,
globalSettings?: boolean, globalSettings?: boolean,
@ -20,6 +21,7 @@ export interface RouterOptions {
const routerOptionsDefault = { const routerOptionsDefault = {
header: { header: {
stepControl: true,
timeSelector: true, timeSelector: true,
executionControls: true, executionControls: true,
} }
@ -33,6 +35,7 @@ export const routerOptions: {[key: string]: RouterOptions} = {
[router.metrics]: { [router.metrics]: {
title: "Explore metrics", title: "Explore metrics",
header: { header: {
stepControl: true,
timeSelector: true, timeSelector: true,
} }
}, },

View file

@ -1,6 +1,6 @@
@use "src/styles/variables" as *; @use "src/styles/variables" as *;
.vm__link { .vm-link {
transition: color 200ms ease; transition: color 200ms ease;
cursor: pointer; cursor: pointer;

View file

@ -16,6 +16,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
## tip ## tip
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add ability to show custom dashboards at vmui by specifying a path to a directory with dashboard config files via `-vmui.customDashboardsPath` command-line flag. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3322) and [these docs](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui/packages/vmui/public/dashboards). * FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add ability to show custom dashboards at vmui by specifying a path to a directory with dashboard config files via `-vmui.customDashboardsPath` command-line flag. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3322) and [these docs](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui/packages/vmui/public/dashboards).
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): apply the `step` globally to all the displayed graphs. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3574).
* BUGFIX: reduce the increased CPU usage at `vmselect` to v1.85.3 level when processing heavy queries. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3641). * BUGFIX: reduce the increased CPU usage at `vmselect` to v1.85.3 level when processing heavy queries. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3641).
* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): [dockerswarm_sd_configs](https://docs.victoriametrics.com/sd_configs.html#dockerswarm_sd_configs): apply `filters` only to objects of the specified `role`. Previously filters were applied to all the objects, which could cause errors when different types of objects were used with filters that were not compatible with them. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3579). * BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): [dockerswarm_sd_configs](https://docs.victoriametrics.com/sd_configs.html#dockerswarm_sd_configs): apply `filters` only to objects of the specified `role`. Previously filters were applied to all the objects, which could cause errors when different types of objects were used with filters that were not compatible with them. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3579).