mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
vmui/logs: fix the update of the relative time range (#6517)
### Describe Your Changes - Fixed the update of the relative time range when `Execute Query` is clicked - Optimized server requests: now, if an error occurs in the `/query` request, the `/hits` request will not be executed. #6345 (duplicates: #6440, #6312)
This commit is contained in:
parent
6652fb630f
commit
43342745ac
6 changed files with 67 additions and 51 deletions
|
@ -1,4 +1,4 @@
|
||||||
import React, { FC, useEffect } from "preact/compat";
|
import React, { FC, useCallback, useEffect } from "preact/compat";
|
||||||
import ExploreLogsBody from "./ExploreLogsBody/ExploreLogsBody";
|
import ExploreLogsBody from "./ExploreLogsBody/ExploreLogsBody";
|
||||||
import useStateSearchParams from "../../hooks/useStateSearchParams";
|
import useStateSearchParams from "../../hooks/useStateSearchParams";
|
||||||
import useSearchParamsFromObject from "../../hooks/useSearchParamsFromObject";
|
import useSearchParamsFromObject from "../../hooks/useSearchParamsFromObject";
|
||||||
|
@ -8,45 +8,54 @@ import Spinner from "../../components/Main/Spinner/Spinner";
|
||||||
import Alert from "../../components/Main/Alert/Alert";
|
import Alert from "../../components/Main/Alert/Alert";
|
||||||
import ExploreLogsHeader from "./ExploreLogsHeader/ExploreLogsHeader";
|
import ExploreLogsHeader from "./ExploreLogsHeader/ExploreLogsHeader";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
import { ErrorTypes } from "../../types";
|
import { ErrorTypes, TimeParams } from "../../types";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTimeState } from "../../state/time/TimeStateContext";
|
import { useTimeState } from "../../state/time/TimeStateContext";
|
||||||
import { getFromStorage, saveToStorage } from "../../utils/storage";
|
import { getFromStorage, saveToStorage } from "../../utils/storage";
|
||||||
import ExploreLogsBarChart from "./ExploreLogsBarChart/ExploreLogsBarChart";
|
import ExploreLogsBarChart from "./ExploreLogsBarChart/ExploreLogsBarChart";
|
||||||
import { useFetchLogHits } from "./hooks/useFetchLogHits";
|
import { useFetchLogHits } from "./hooks/useFetchLogHits";
|
||||||
import { LOGS_ENTRIES_LIMIT } from "../../constants/logs";
|
import { LOGS_ENTRIES_LIMIT } from "../../constants/logs";
|
||||||
|
import { getTimeperiodForDuration, relativeTimeOptions } from "../../utils/time";
|
||||||
|
|
||||||
const storageLimit = Number(getFromStorage("LOGS_LIMIT"));
|
const storageLimit = Number(getFromStorage("LOGS_LIMIT"));
|
||||||
const defaultLimit = isNaN(storageLimit) ? LOGS_ENTRIES_LIMIT : storageLimit;
|
const defaultLimit = isNaN(storageLimit) ? LOGS_ENTRIES_LIMIT : storageLimit;
|
||||||
|
|
||||||
const ExploreLogs: FC = () => {
|
const ExploreLogs: FC = () => {
|
||||||
const { serverUrl } = useAppState();
|
const { serverUrl } = useAppState();
|
||||||
const { duration, relativeTime, period } = useTimeState();
|
const { duration, relativeTime, period: periodState } = useTimeState();
|
||||||
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
|
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
|
||||||
|
|
||||||
const [limit, setLimit] = useStateSearchParams(defaultLimit, "limit");
|
const [limit, setLimit] = useStateSearchParams(defaultLimit, "limit");
|
||||||
const [query, setQuery] = useStateSearchParams("*", "query");
|
const [query, setQuery] = useStateSearchParams("*", "query");
|
||||||
|
const [period, setPeriod] = useState<TimeParams>(periodState);
|
||||||
const { logs, isLoading, error, fetchLogs } = useFetchLogs(serverUrl, query, limit);
|
const { logs, isLoading, error, fetchLogs } = useFetchLogs(serverUrl, query, limit);
|
||||||
const { fetchLogHits, ...dataLogHits } = useFetchLogHits(serverUrl, query);
|
const { fetchLogHits, ...dataLogHits } = useFetchLogHits(serverUrl, query);
|
||||||
const [queryError, setQueryError] = useState<ErrorTypes | string>("");
|
const [queryError, setQueryError] = useState<ErrorTypes | string>("");
|
||||||
const [loaded, isLoaded] = useState(false);
|
|
||||||
const [markdownParsing, setMarkdownParsing] = useState(getFromStorage("LOGS_MARKDOWN") === "true");
|
const [markdownParsing, setMarkdownParsing] = useState(getFromStorage("LOGS_MARKDOWN") === "true");
|
||||||
|
|
||||||
|
const getPeriod = useCallback(() => {
|
||||||
|
const relativeTimeOpts = relativeTimeOptions.find(d => d.id === relativeTime);
|
||||||
|
if (!relativeTimeOpts) return periodState;
|
||||||
|
const { duration, until } = relativeTimeOpts;
|
||||||
|
return getTimeperiodForDuration(duration, until());
|
||||||
|
}, [periodState, relativeTime]);
|
||||||
|
|
||||||
const handleRunQuery = () => {
|
const handleRunQuery = () => {
|
||||||
if (!query) {
|
if (!query) {
|
||||||
setQueryError(ErrorTypes.validQuery);
|
setQueryError(ErrorTypes.validQuery);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchLogs().then(() => {
|
const newPeriod = getPeriod();
|
||||||
isLoaded(true);
|
setPeriod(newPeriod);
|
||||||
});
|
fetchLogs(newPeriod).then(() => {
|
||||||
fetchLogHits();
|
fetchLogHits(newPeriod);
|
||||||
|
}).catch(e => e);
|
||||||
|
|
||||||
setSearchParamsFromKeys( {
|
setSearchParamsFromKeys( {
|
||||||
query,
|
query,
|
||||||
"g0.range_input": duration,
|
"g0.range_input": duration,
|
||||||
"g0.end_input": period.date,
|
"g0.end_input": newPeriod.date,
|
||||||
"g0.relative_time": relativeTime || "none",
|
"g0.relative_time": relativeTime || "none",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -64,7 +73,7 @@ const ExploreLogs: FC = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (query) handleRunQuery();
|
if (query) handleRunQuery();
|
||||||
}, [period]);
|
}, [periodState]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setQueryError("");
|
setQueryError("");
|
||||||
|
@ -84,14 +93,15 @@ const ExploreLogs: FC = () => {
|
||||||
/>
|
/>
|
||||||
{isLoading && <Spinner />}
|
{isLoading && <Spinner />}
|
||||||
{error && <Alert variant="error">{error}</Alert>}
|
{error && <Alert variant="error">{error}</Alert>}
|
||||||
|
{!error && (
|
||||||
<ExploreLogsBarChart
|
<ExploreLogsBarChart
|
||||||
query={query}
|
query={query}
|
||||||
loaded={loaded}
|
period={period}
|
||||||
{...dataLogHits}
|
{...dataLogHits}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
<ExploreLogsBody
|
<ExploreLogsBody
|
||||||
data={logs}
|
data={logs}
|
||||||
loaded={loaded}
|
|
||||||
markdownParsing={markdownParsing}
|
markdownParsing={markdownParsing}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,22 +4,22 @@ import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { LogHits } from "../../../api/types";
|
import { LogHits } from "../../../api/types";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useTimeDispatch, useTimeState } from "../../../state/time/TimeStateContext";
|
import { useTimeDispatch } from "../../../state/time/TimeStateContext";
|
||||||
import { AlignedData } from "uplot";
|
import { AlignedData } from "uplot";
|
||||||
import BarHitsChart from "../../../components/Chart/BarHitsChart/BarHitsChart";
|
import BarHitsChart from "../../../components/Chart/BarHitsChart/BarHitsChart";
|
||||||
import Alert from "../../../components/Main/Alert/Alert";
|
import Alert from "../../../components/Main/Alert/Alert";
|
||||||
|
import { TimeParams } from "../../../types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
query: string;
|
query: string;
|
||||||
logHits: LogHits[];
|
logHits: LogHits[];
|
||||||
|
period: TimeParams;
|
||||||
error?: string;
|
error?: string;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
loaded: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExploreLogsBarChart: FC<Props> = ({ logHits, error, loaded }) => {
|
const ExploreLogsBarChart: FC<Props> = ({ logHits, period, error }) => {
|
||||||
const { isMobile } = useDeviceDetect();
|
const { isMobile } = useDeviceDetect();
|
||||||
const { period } = useTimeState();
|
|
||||||
const timeDispatch = useTimeDispatch();
|
const timeDispatch = useTimeDispatch();
|
||||||
|
|
||||||
const data = useMemo(() => {
|
const data = useMemo(() => {
|
||||||
|
@ -56,13 +56,13 @@ const ExploreLogsBarChart: FC<Props> = ({ logHits, error, loaded }) => {
|
||||||
"vm-block_mobile": isMobile,
|
"vm-block_mobile": isMobile,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{!error && loaded && noDataMessage && (
|
{!error && noDataMessage && (
|
||||||
<div className="vm-explore-logs-chart__empty">
|
<div className="vm-explore-logs-chart__empty">
|
||||||
<Alert variant="info">{noDataMessage}</Alert>
|
<Alert variant="info">{noDataMessage}</Alert>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{error && loaded && noDataMessage && (
|
{error && noDataMessage && (
|
||||||
<div className="vm-explore-logs-chart__empty">
|
<div className="vm-explore-logs-chart__empty">
|
||||||
<Alert variant="error">{error}</Alert>
|
<Alert variant="error">{error}</Alert>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,7 +19,6 @@ import { marked } from "marked";
|
||||||
|
|
||||||
export interface ExploreLogBodyProps {
|
export interface ExploreLogBodyProps {
|
||||||
data: Logs[];
|
data: Logs[];
|
||||||
loaded?: boolean;
|
|
||||||
markdownParsing: boolean;
|
markdownParsing: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +34,7 @@ const tabs = [
|
||||||
{ label: "JSON", value: DisplayType.json, icon: <CodeIcon/> },
|
{ label: "JSON", value: DisplayType.json, icon: <CodeIcon/> },
|
||||||
];
|
];
|
||||||
|
|
||||||
const ExploreLogsBody: FC<ExploreLogBodyProps> = ({ data, loaded, markdownParsing }) => {
|
const ExploreLogsBody: FC<ExploreLogBodyProps> = ({ data, markdownParsing }) => {
|
||||||
const { isMobile } = useDeviceDetect();
|
const { isMobile } = useDeviceDetect();
|
||||||
const { timezone } = useTimeState();
|
const { timezone } = useTimeState();
|
||||||
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
|
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
|
||||||
|
@ -109,11 +108,7 @@ const ExploreLogsBody: FC<ExploreLogBodyProps> = ({ data, loaded, markdownParsin
|
||||||
"vm-explore-logs-body__table_mobile": isMobile,
|
"vm-explore-logs-body__table_mobile": isMobile,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{!data.length && (
|
{!data.length && <div className="vm-explore-logs-body__empty">No logs found</div>}
|
||||||
<div className="vm-explore-logs-body__empty">
|
|
||||||
{loaded ? "No logs found" : "Run query to see logs"}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!!data.length && (
|
{!!data.length && (
|
||||||
<>
|
<>
|
||||||
{activeTab === DisplayType.table && (
|
{activeTab === DisplayType.table && (
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import { useCallback, useMemo, useRef, useState } from "preact/compat";
|
import { useCallback, useMemo, useRef, useState } from "preact/compat";
|
||||||
import { getLogHitsUrl } from "../../../api/logs";
|
import { getLogHitsUrl } from "../../../api/logs";
|
||||||
import { ErrorTypes } from "../../../types";
|
import { ErrorTypes, TimeParams } from "../../../types";
|
||||||
import { LogHits } from "../../../api/types";
|
import { LogHits } from "../../../api/types";
|
||||||
import { useTimeState } from "../../../state/time/TimeStateContext";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { LOGS_BARS_VIEW } from "../../../constants/logs";
|
import { LOGS_BARS_VIEW } from "../../../constants/logs";
|
||||||
|
|
||||||
export const useFetchLogHits = (server: string, query: string) => {
|
export const useFetchLogHits = (server: string, query: string) => {
|
||||||
const { period } = useTimeState();
|
|
||||||
const [logHits, setLogHits] = useState<LogHits[]>([]);
|
const [logHits, setLogHits] = useState<LogHits[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [error, setError] = useState<ErrorTypes | string>();
|
const [error, setError] = useState<ErrorTypes | string>();
|
||||||
|
@ -15,13 +13,14 @@ export const useFetchLogHits = (server: string, query: string) => {
|
||||||
|
|
||||||
const url = useMemo(() => getLogHitsUrl(server), [server]);
|
const url = useMemo(() => getLogHitsUrl(server), [server]);
|
||||||
|
|
||||||
const options = useMemo(() => {
|
const getOptions = (query: string, period: TimeParams, signal: AbortSignal) => {
|
||||||
const start = dayjs(period.start * 1000);
|
const start = dayjs(period.start * 1000);
|
||||||
const end = dayjs(period.end * 1000);
|
const end = dayjs(period.end * 1000);
|
||||||
const totalSeconds = end.diff(start, "milliseconds");
|
const totalSeconds = end.diff(start, "milliseconds");
|
||||||
const step = Math.ceil(totalSeconds / LOGS_BARS_VIEW) || 1;
|
const step = Math.ceil(totalSeconds / LOGS_BARS_VIEW) || 1;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
signal,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: new URLSearchParams({
|
body: new URLSearchParams({
|
||||||
query: query.trim(),
|
query: query.trim(),
|
||||||
|
@ -30,30 +29,34 @@ export const useFetchLogHits = (server: string, query: string) => {
|
||||||
end: end.toISOString(),
|
end: end.toISOString(),
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}, [query, period]);
|
};
|
||||||
|
|
||||||
const fetchLogHits = useCallback(async () => {
|
const fetchLogHits = useCallback(async (period: TimeParams) => {
|
||||||
abortControllerRef.current.abort();
|
abortControllerRef.current.abort();
|
||||||
abortControllerRef.current = new AbortController();
|
abortControllerRef.current = new AbortController();
|
||||||
const { signal } = abortControllerRef.current;
|
const { signal } = abortControllerRef.current;
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, { ...options, signal });
|
const options = getOptions(query, period, signal);
|
||||||
|
const response = await fetch(url, options);
|
||||||
|
|
||||||
if (!response.ok || !response.body) {
|
if (!response.ok || !response.body) {
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
setError(text);
|
setError(text);
|
||||||
setLogHits([]);
|
setLogHits([]);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
return;
|
return Promise.reject(new Error(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const hits = data?.hits as LogHits[];
|
const hits = data?.hits as LogHits[];
|
||||||
if (!hits) {
|
if (!hits) {
|
||||||
setError("Error: No 'hits' field in response");
|
const error = "Error: No 'hits' field in response";
|
||||||
return;
|
setError(error);
|
||||||
|
return Promise.reject(new Error(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
setLogHits(hits);
|
setLogHits(hits);
|
||||||
|
@ -63,9 +66,11 @@ export const useFetchLogHits = (server: string, query: string) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
setLogHits([]);
|
setLogHits([]);
|
||||||
}
|
}
|
||||||
|
return Promise.reject(e);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
// setIsLoading(false);
|
}, [url, query]);
|
||||||
}, [url, options]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
logHits,
|
logHits,
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import { useCallback, useMemo, useRef, useState } from "preact/compat";
|
import { useCallback, useMemo, useRef, useState } from "preact/compat";
|
||||||
import { getLogsUrl } from "../../../api/logs";
|
import { getLogsUrl } from "../../../api/logs";
|
||||||
import { ErrorTypes } from "../../../types";
|
import { ErrorTypes, TimeParams } from "../../../types";
|
||||||
import { Logs } from "../../../api/types";
|
import { Logs } from "../../../api/types";
|
||||||
import { useTimeState } from "../../../state/time/TimeStateContext";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
export const useFetchLogs = (server: string, query: string, limit: number) => {
|
export const useFetchLogs = (server: string, query: string, limit: number) => {
|
||||||
const { period } = useTimeState();
|
|
||||||
const [logs, setLogs] = useState<Logs[]>([]);
|
const [logs, setLogs] = useState<Logs[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [error, setError] = useState<ErrorTypes | string>();
|
const [error, setError] = useState<ErrorTypes | string>();
|
||||||
|
@ -14,7 +12,8 @@ export const useFetchLogs = (server: string, query: string, limit: number) => {
|
||||||
|
|
||||||
const url = useMemo(() => getLogsUrl(server), [server]);
|
const url = useMemo(() => getLogsUrl(server), [server]);
|
||||||
|
|
||||||
const options = useMemo(() => ({
|
const getOptions = (query: string, period: TimeParams, limit: number, signal: AbortSignal) => ({
|
||||||
|
signal,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Accept": "application/stream+json",
|
"Accept": "application/stream+json",
|
||||||
|
@ -25,7 +24,7 @@ export const useFetchLogs = (server: string, query: string, limit: number) => {
|
||||||
start: dayjs(period.start * 1000).tz().toISOString(),
|
start: dayjs(period.start * 1000).tz().toISOString(),
|
||||||
end: dayjs(period.end * 1000).tz().toISOString()
|
end: dayjs(period.end * 1000).tz().toISOString()
|
||||||
})
|
})
|
||||||
}), [query, limit, period]);
|
});
|
||||||
|
|
||||||
const parseLineToJSON = (line: string): Logs | null => {
|
const parseLineToJSON = (line: string): Logs | null => {
|
||||||
try {
|
try {
|
||||||
|
@ -35,22 +34,24 @@ export const useFetchLogs = (server: string, query: string, limit: number) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchLogs = useCallback(async () => {
|
const fetchLogs = useCallback(async (period: TimeParams) => {
|
||||||
abortControllerRef.current.abort();
|
abortControllerRef.current.abort();
|
||||||
abortControllerRef.current = new AbortController();
|
abortControllerRef.current = new AbortController();
|
||||||
const { signal } = abortControllerRef.current;
|
const { signal } = abortControllerRef.current;
|
||||||
const limit = Number(options.body.get("limit"));
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, { ...options, signal });
|
const options = getOptions(query, period, limit, signal);
|
||||||
|
const response = await fetch(url, options);
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
|
|
||||||
if (!response.ok || !response.body) {
|
if (!response.ok || !response.body) {
|
||||||
setError(text);
|
setError(text);
|
||||||
setLogs([]);
|
setLogs([]);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
return;
|
return Promise.reject(new Error(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines = text.split("\n").filter(line => line).slice(0, limit);
|
const lines = text.split("\n").filter(line => line).slice(0, limit);
|
||||||
|
@ -62,9 +63,12 @@ export const useFetchLogs = (server: string, query: string, limit: number) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
setLogs([]);
|
setLogs([]);
|
||||||
}
|
}
|
||||||
|
return Promise.reject(e);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}, [url, options]);
|
}, [url, query, limit]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
logs,
|
logs,
|
||||||
|
|
|
@ -21,6 +21,8 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta
|
||||||
|
|
||||||
* FEATURE: add `-retention.maxDiskSpaceUsageBytes` command-line flag, which allows limiting disk space usage for [VictoriaLogs data](https://docs.victoriametrics.com/victorialogs/#storage) by automatic dropping the oldest per-day partitions if the storage disk space usage becomes bigger than the `-retention.maxDiskSpaceUsageBytes`. See [these docs](https://docs.victoriametrics.com/victorialogs/#retention-by-disk-space-usage).
|
* FEATURE: add `-retention.maxDiskSpaceUsageBytes` command-line flag, which allows limiting disk space usage for [VictoriaLogs data](https://docs.victoriametrics.com/victorialogs/#storage) by automatic dropping the oldest per-day partitions if the storage disk space usage becomes bigger than the `-retention.maxDiskSpaceUsageBytes`. See [these docs](https://docs.victoriametrics.com/victorialogs/#retention-by-disk-space-usage).
|
||||||
|
|
||||||
|
* BUGFIX: [web UI](https://docs.victoriametrics.com/victorialogs/querying/#web-ui): fix the update of the relative time range when `Execute Query` is clicked. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6345).
|
||||||
|
|
||||||
## [v0.23.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v0.23.0-victorialogs)
|
## [v0.23.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v0.23.0-victorialogs)
|
||||||
|
|
||||||
Released at 2024-06-25
|
Released at 2024-06-25
|
||||||
|
|
Loading…
Reference in a new issue