mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-12 12:46:23 +01:00
vmui: custom step (#1942)
* feat: add a label for the Query field * fix: change zoom position * fix: add description and error code to alerts * fix: correct logic query history * fix: correct update query history * feat: add custom step * update package-lock.json * docs: document that VMUI now supports overriding of `step` query arg, which is passed to `/api/v1/query_range` Co-authored-by: Aliaksandr Valialkin <valyala@victoriametrics.com>
This commit is contained in:
parent
28655e8eb8
commit
b3389ce26b
@ -1,19 +1,19 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.83d9ae2d.chunk.css",
|
||||
"main.js": "./static/js/main.33e45f18.chunk.js",
|
||||
"main.js": "./static/js/main.6651c49c.chunk.js",
|
||||
"runtime-main.js": "./static/js/runtime-main.c4b656b8.js",
|
||||
"static/css/2.77671664.chunk.css": "./static/css/2.77671664.chunk.css",
|
||||
"static/js/2.f1e36397.chunk.js": "./static/js/2.f1e36397.chunk.js",
|
||||
"static/js/2.ef1db8c8.chunk.js": "./static/js/2.ef1db8c8.chunk.js",
|
||||
"static/js/3.65648506.chunk.js": "./static/js/3.65648506.chunk.js",
|
||||
"index.html": "./index.html",
|
||||
"static/js/2.f1e36397.chunk.js.LICENSE.txt": "./static/js/2.f1e36397.chunk.js.LICENSE.txt"
|
||||
"static/js/2.ef1db8c8.chunk.js.LICENSE.txt": "./static/js/2.ef1db8c8.chunk.js.LICENSE.txt"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/js/runtime-main.c4b656b8.js",
|
||||
"static/css/2.77671664.chunk.css",
|
||||
"static/js/2.f1e36397.chunk.js",
|
||||
"static/js/2.ef1db8c8.chunk.js",
|
||||
"static/css/main.83d9ae2d.chunk.css",
|
||||
"static/js/main.33e45f18.chunk.js"
|
||||
"static/js/main.6651c49c.chunk.js"
|
||||
]
|
||||
}
|
@ -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="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><link href="./static/css/2.77671664.chunk.css" rel="stylesheet"><link href="./static/css/main.83d9ae2d.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],s=0,p=[];s<a.length;s++)i=a[s],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&p.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);p.length;)p.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var c=t[a];0!==o[c]&&(n=!1)}n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={1:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+"static/js/"+({}[e]||e)+"."+{3:"65648506"}[e]+".chunk.js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,function(r){return e[r]}.bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="./",i.oe=function(e){throw console.error(e),e};var a=this.webpackJsonpvmui=this.webpackJsonpvmui||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var l=0;l<a.length;l++)r(a[l]);var f=c;t()}([])</script><script src="./static/js/2.f1e36397.chunk.js"></script><script src="./static/js/main.33e45f18.chunk.js"></script></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="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><link href="./static/css/2.77671664.chunk.css" rel="stylesheet"><link href="./static/css/main.83d9ae2d.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],s=0,p=[];s<a.length;s++)i=a[s],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&p.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);p.length;)p.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var c=t[a];0!==o[c]&&(n=!1)}n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={1:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+"static/js/"+({}[e]||e)+"."+{3:"65648506"}[e]+".chunk.js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,function(r){return e[r]}.bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="./",i.oe=function(e){throw console.error(e),e};var a=this.webpackJsonpvmui=this.webpackJsonpvmui||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var l=0;l<a.length;l++)r(a[l]);var f=c;t()}([])</script><script src="./static/js/2.ef1db8c8.chunk.js"></script><script src="./static/js/main.6651c49c.chunk.js"></script></body></html>
|
2
app/vmselect/vmui/static/js/2.ef1db8c8.chunk.js
Normal file
2
app/vmselect/vmui/static/js/2.ef1db8c8.chunk.js
Normal file
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
1
app/vmselect/vmui/static/js/main.6651c49c.chunk.js
Normal file
1
app/vmselect/vmui/static/js/main.6651c49c.chunk.js
Normal file
File diff suppressed because one or more lines are too long
@ -24,7 +24,7 @@ const AxesLimitsConfigurator: FC = () => {
|
||||
control={<BasicSwitch checked={yaxis.limits.enable} onChange={onChangeYaxisLimits}/>}
|
||||
label="Fix the limits for y-axis"
|
||||
/>
|
||||
<Box display="grid" alignItems="center" gap={4}>
|
||||
<Box display="grid" alignItems="center" gap={2}>
|
||||
{axes.map(axis => <Box display="grid" gridTemplateColumns="120px 120px" gap={1} key={axis}>
|
||||
<TextField label={`Min ${axis}`} type="number" size="small" variant="outlined"
|
||||
disabled={!yaxis.limits.enable}
|
||||
|
@ -25,7 +25,9 @@ const useStyles = makeStyles({
|
||||
cursor: "move",
|
||||
},
|
||||
popoverBody: {
|
||||
padding: "0 14px"
|
||||
display: "grid",
|
||||
gridGap: "6px",
|
||||
padding: "0 14px",
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -3,6 +3,7 @@ import {Box, FormControlLabel} from "@mui/material";
|
||||
import {saveToStorage} from "../../../../utils/storage";
|
||||
import {useAppDispatch, useAppState} from "../../../../state/common/StateContext";
|
||||
import BasicSwitch from "../../../../theme/switch";
|
||||
import StepConfigurator from "./StepConfigurator";
|
||||
|
||||
const AdditionalSettings: FC = () => {
|
||||
|
||||
@ -30,6 +31,9 @@ const AdditionalSettings: FC = () => {
|
||||
control={<BasicSwitch checked={!nocache} onChange={onChangeCache}/>}
|
||||
/>
|
||||
</Box>
|
||||
<Box ml={2}>
|
||||
<StepConfigurator/>
|
||||
</Box>
|
||||
</Box>;
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,54 @@
|
||||
import React, {FC, useCallback, useEffect, useState} from "react";
|
||||
import {Box, FormControlLabel, TextField} from "@mui/material";
|
||||
import BasicSwitch from "../../../../theme/switch";
|
||||
import {useGraphDispatch, useGraphState} from "../../../../state/graph/GraphStateContext";
|
||||
import {useAppState} from "../../../../state/common/StateContext";
|
||||
import debounce from "lodash.debounce";
|
||||
|
||||
const StepConfigurator: FC = () => {
|
||||
const {customStep} = useGraphState();
|
||||
const graphDispatch = useGraphDispatch();
|
||||
const [error, setError] = useState(false);
|
||||
const {time: {period: {step}, duration}} = useAppState();
|
||||
|
||||
const onChangeStep = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const value = +e.target.value;
|
||||
if (value > 0) {
|
||||
graphDispatch({type: "SET_CUSTOM_STEP", payload: value});
|
||||
setError(false);
|
||||
} else {
|
||||
setError(true);
|
||||
}
|
||||
};
|
||||
|
||||
const debouncedOnChangeStep = useCallback(debounce(onChangeStep, 500), [customStep.value]);
|
||||
|
||||
const onChangeEnableStep = () => {
|
||||
setError(false);
|
||||
graphDispatch({type: "TOGGLE_CUSTOM_STEP"});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (customStep.enable) onChangeEnableStep();
|
||||
}, [duration]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!customStep.enable) graphDispatch({type: "SET_CUSTOM_STEP", payload: step || 1});
|
||||
}, [step]);
|
||||
|
||||
return <Box display="grid" gridTemplateColumns="auto 120px" alignItems="center">
|
||||
<FormControlLabel
|
||||
control={<BasicSwitch checked={customStep.enable} onChange={onChangeEnableStep}/>}
|
||||
label="Override step value"
|
||||
/>
|
||||
{customStep.enable &&
|
||||
<TextField label="Step value" type="number" size="small" variant="outlined"
|
||||
defaultValue={customStep.value}
|
||||
error={error}
|
||||
helperText={error ? "step is out of allowed range" : " "}
|
||||
onChange={debouncedOnChangeStep}/>
|
||||
}
|
||||
</Box>;
|
||||
};
|
||||
|
||||
export default StepConfigurator;
|
@ -5,6 +5,7 @@ import {InstantMetricResult, MetricBase, MetricResult} from "../../../../api/typ
|
||||
import {isValidHttpUrl} from "../../../../utils/url";
|
||||
import {useAuthState} from "../../../../state/auth/AuthStateContext";
|
||||
import {ErrorTypes, TimeParams} from "../../../../types";
|
||||
import {useGraphState} from "../../../../state/graph/GraphStateContext";
|
||||
|
||||
export const useFetchQuery = (): {
|
||||
fetchUrl?: string[],
|
||||
@ -16,6 +17,7 @@ export const useFetchQuery = (): {
|
||||
const {query, displayType, serverUrl, time: {period}, queryControls: {nocache}} = useAppState();
|
||||
|
||||
const {basicData, bearerData, authMethod} = useAuthState();
|
||||
const {customStep} = useGraphState();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [graphData, setGraphData] = useState<MetricResult[]>();
|
||||
@ -86,6 +88,7 @@ export const useFetchQuery = (): {
|
||||
} else if (isValidHttpUrl(serverUrl)) {
|
||||
const duration = (period.end - period.start) / 2;
|
||||
const bufferPeriod = {...period, start: period.start - duration, end: period.end + duration};
|
||||
if (customStep.enable) bufferPeriod.step = customStep.value;
|
||||
return query.filter(q => q.trim()).map(q => displayType === "chart"
|
||||
? getQueryRangeUrl(serverUrl, q, bufferPeriod, nocache)
|
||||
: getQueryUrl(serverUrl, q, period));
|
||||
@ -93,7 +96,7 @@ export const useFetchQuery = (): {
|
||||
setError(ErrorTypes.validServer);
|
||||
}
|
||||
},
|
||||
[serverUrl, period, displayType]);
|
||||
[serverUrl, period, displayType, customStep]);
|
||||
|
||||
useEffect(() => {
|
||||
setPrevPeriod(undefined);
|
||||
@ -103,7 +106,7 @@ export const useFetchQuery = (): {
|
||||
// Doing it on each query change - looks to be a bad idea. Probably can be done on blur
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [serverUrl, displayType]);
|
||||
}, [serverUrl, displayType, customStep]);
|
||||
|
||||
useEffect(() => {
|
||||
if (needUpdateData) {
|
||||
|
@ -3,21 +3,30 @@ export interface AxisRange {
|
||||
}
|
||||
|
||||
export interface YaxisState {
|
||||
limits: {
|
||||
enable: boolean,
|
||||
range: AxisRange
|
||||
}
|
||||
limits: {
|
||||
enable: boolean,
|
||||
range: AxisRange
|
||||
}
|
||||
}
|
||||
|
||||
export interface CustomStep {
|
||||
enable: boolean,
|
||||
value: number
|
||||
}
|
||||
|
||||
export interface GraphState {
|
||||
yaxis: YaxisState
|
||||
customStep: CustomStep
|
||||
yaxis: YaxisState
|
||||
}
|
||||
|
||||
export type GraphAction =
|
||||
| { type: "TOGGLE_ENABLE_YAXIS_LIMITS" }
|
||||
| { type: "SET_YAXIS_LIMITS", payload: {[key: string]: [number, number]} }
|
||||
| { type: "TOGGLE_ENABLE_YAXIS_LIMITS" }
|
||||
| { type: "SET_YAXIS_LIMITS", payload: { [key: string]: [number, number] } }
|
||||
| { type: "TOGGLE_CUSTOM_STEP" }
|
||||
| { type: "SET_CUSTOM_STEP", payload: number}
|
||||
|
||||
export const initialGraphState: GraphState = {
|
||||
customStep: {enable: false, value: 1},
|
||||
yaxis: {
|
||||
limits: {enable: false, range: {"1": [0, 0]}}
|
||||
}
|
||||
@ -36,6 +45,22 @@ export function reducer(state: GraphState, action: GraphAction): GraphState {
|
||||
}
|
||||
}
|
||||
};
|
||||
case "TOGGLE_CUSTOM_STEP":
|
||||
return {
|
||||
...state,
|
||||
customStep: {
|
||||
...state.customStep,
|
||||
enable: !state.customStep.enable
|
||||
}
|
||||
};
|
||||
case "SET_CUSTOM_STEP":
|
||||
return {
|
||||
...state,
|
||||
customStep: {
|
||||
...state.customStep,
|
||||
value: action.payload
|
||||
}
|
||||
};
|
||||
case "SET_YAXIS_LIMITS":
|
||||
return {
|
||||
...state,
|
||||
|
@ -13,6 +13,16 @@ const THEME = createTheme({
|
||||
}
|
||||
},
|
||||
components: {
|
||||
MuiFormHelperText: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
position: "absolute",
|
||||
top: "36px",
|
||||
left: "2px",
|
||||
margin: 0,
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiInputLabel: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
|
@ -13,6 +13,7 @@ sort: 15
|
||||
* FEATURE: vminsert: allow specifying `http` and `https` urls in `-relabelConfig` command-line flag.
|
||||
* FEATURE: vminsert: add `-maxLabelValueLen` command-line flag for the ability to configure the maximum length of label value. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1908).
|
||||
* FEATURE: preserve the order of time series passed to [limit_offset](https://docs.victoriametrics.com/MetricsQL.html#limit_offset) function. This allows implementing series paging via `limit_offset(limit, offset, sort_by_label(...))`. See [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1920) and [this](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/951) issues.
|
||||
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add ability to override the interval between returned datapoints. By default it is automatically calculated depending on the selected time range and horizontal resolution of the graph. Now it is possible to override it with custom values. This may be useful during data exploration and debugging.
|
||||
|
||||
* BUGFIX: fix `unaligned 64-bit atomic operation` panic on 32-bit architectures, which has been introduced in v1.70.0.
|
||||
* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): restore the ability to use `{{ $labels.alertname }}` in labels templating. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1921).
|
||||
|
@ -611,6 +611,8 @@ Query history can be navigated by holding `Ctrl` (or `Cmd` on MacOS) and pressin
|
||||
|
||||
When querying the [backfilled data](https://docs.victoriametrics.com/#backfilling), it may be useful disabling response cache by clicking `Enable cache` checkbox.
|
||||
|
||||
VMUI automatically adjusts the interval between datapoints on the graph depending on the horizontal resolution and on the selected time range. The step value can be customized by clickhing `Override step value` checkbox.
|
||||
|
||||
VMUI allows investigating correlations between two queries on the same graph. Just click `+Query` button, enter the second query in the newly appeared input field and press `Ctrl+Enter`. Results for both queries should be displayed simultaneously on the same graph. Every query has its own vertical scale, which is displayed on the left and the right side of the graph. Lines for the second query are dashed.
|
||||
|
||||
See the [example VMUI at VictoriaMetrics playground](https://play.victoriametrics.com/select/accounting/1/6a716b0f-38bc-4856-90ce-448fd713e3fe/prometheus/graph/?g0.expr=100%20*%20sum(rate(process_cpu_seconds_total))%20by%20(job)&g0.range_input=1d).
|
||||
|
@ -615,6 +615,8 @@ Query history can be navigated by holding `Ctrl` (or `Cmd` on MacOS) and pressin
|
||||
|
||||
When querying the [backfilled data](https://docs.victoriametrics.com/#backfilling), it may be useful disabling response cache by clicking `Enable cache` checkbox.
|
||||
|
||||
VMUI automatically adjusts the interval between datapoints on the graph depending on the horizontal resolution and on the selected time range. The step value can be customized by clickhing `Override step value` checkbox.
|
||||
|
||||
VMUI allows investigating correlations between two queries on the same graph. Just click `+Query` button, enter the second query in the newly appeared input field and press `Ctrl+Enter`. Results for both queries should be displayed simultaneously on the same graph. Every query has its own vertical scale, which is displayed on the left and the right side of the graph. Lines for the second query are dashed.
|
||||
|
||||
See the [example VMUI at VictoriaMetrics playground](https://play.victoriametrics.com/select/accounting/1/6a716b0f-38bc-4856-90ce-448fd713e3fe/prometheus/graph/?g0.expr=100%20*%20sum(rate(process_cpu_seconds_total))%20by%20(job)&g0.range_input=1d).
|
||||
|
Loading…
Reference in New Issue
Block a user