mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-05-17 08:36:55 +03:00
Compare commits
1 Commits
weakpointe
...
vmui/logs/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d36d8fdd8 |
@@ -1,4 +1,4 @@
|
||||
import React, { FC, useEffect, useMemo, useRef } from "preact/compat";
|
||||
import { FC, useEffect, useMemo, useRef } from "preact/compat";
|
||||
import { GraphOptions, GRAPH_STYLES } from "../types";
|
||||
import Switch from "../../../Main/Switch/Switch";
|
||||
import "./style.scss";
|
||||
@@ -61,15 +61,6 @@ const BarHitsOptions: FC<Props> = ({ onChange }) => {
|
||||
|
||||
return (
|
||||
<div className="vm-bar-hits-options">
|
||||
<Tooltip title={hideChart ? "Show chart and resume hits updates" : "Hide chart and pause hits updates"}>
|
||||
<Button
|
||||
variant="text"
|
||||
color="primary"
|
||||
startIcon={hideChart ? <VisibilityOffIcon/> : <VisibilityIcon/>}
|
||||
onClick={toggleHideChart}
|
||||
ariaLabel="settings"
|
||||
/>
|
||||
</Tooltip>
|
||||
<div ref={optionsButtonRef}>
|
||||
<Tooltip title="Graph settings">
|
||||
<Button
|
||||
@@ -81,6 +72,15 @@ const BarHitsOptions: FC<Props> = ({ onChange }) => {
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Tooltip title={hideChart ? "Show chart and resume hits updates" : "Hide chart and pause hits updates"}>
|
||||
<Button
|
||||
variant="text"
|
||||
color="primary"
|
||||
startIcon={hideChart ? <VisibilityOffIcon/> : <VisibilityIcon/>}
|
||||
onClick={toggleHideChart}
|
||||
ariaLabel="settings"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Popper
|
||||
open={openOptions}
|
||||
placement="bottom-right"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
z-index: 2;
|
||||
z-index: 9;
|
||||
overflow: hidden;
|
||||
|
||||
&__background {
|
||||
|
||||
@@ -23,6 +23,8 @@ import usePrevious from "../../hooks/usePrevious";
|
||||
const storageLimit = Number(getFromStorage("LOGS_LIMIT"));
|
||||
const defaultLimit = isNaN(storageLimit) ? LOGS_ENTRIES_LIMIT : storageLimit;
|
||||
|
||||
type FetchFlags = { logs: boolean; hits: boolean };
|
||||
|
||||
const ExploreLogs: FC = () => {
|
||||
const { serverUrl } = useAppState();
|
||||
const { queryHistory } = useQueryState();
|
||||
@@ -30,9 +32,13 @@ const ExploreLogs: FC = () => {
|
||||
const { duration, relativeTime, period: periodState } = useTimeState();
|
||||
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const hideChart = useMemo(() => searchParams.get("hide_chart"), [searchParams]);
|
||||
const prevHideChart = usePrevious(hideChart);
|
||||
|
||||
const hideLogs = useMemo(() => searchParams.get("hide_logs"), [searchParams]);
|
||||
const prevHideLogs = usePrevious(hideLogs);
|
||||
|
||||
const [limit, setLimit] = useStateSearchParams(defaultLimit, "limit");
|
||||
const [query, setQuery] = useStateSearchParams("*", "query");
|
||||
|
||||
@@ -54,10 +60,15 @@ const ExploreLogs: FC = () => {
|
||||
const { logs, isLoading, error, fetchLogs, abortController } = useFetchLogs(serverUrl, query, limit);
|
||||
const { fetchLogHits, ...dataLogHits } = useFetchLogHits(serverUrl, query);
|
||||
|
||||
const fetchData = (p: TimeParams, hits: boolean) => {
|
||||
fetchLogs(p).then((isSuccess) => {
|
||||
if (isSuccess && hits) fetchLogHits(p);
|
||||
}).catch(() => {/* error handled elsewhere */});
|
||||
const fetchData = async (p: TimeParams, flags: FetchFlags) => {
|
||||
if (flags.logs) {
|
||||
const isSuccess = await fetchLogs(p);
|
||||
if (!isSuccess) return;
|
||||
}
|
||||
|
||||
if (flags.hits) {
|
||||
await fetchLogHits(p);
|
||||
}
|
||||
};
|
||||
|
||||
const debouncedFetchLogs = useDebounceCallback(fetchData, 300);
|
||||
@@ -78,7 +89,7 @@ const ExploreLogs: FC = () => {
|
||||
|
||||
const newPeriod = getPeriod();
|
||||
setPeriod(newPeriod);
|
||||
debouncedFetchLogs(newPeriod, !hideChart);
|
||||
debouncedFetchLogs(newPeriod, { logs: !hideLogs, hits: !hideChart });
|
||||
setSearchParamsFromKeys({
|
||||
query,
|
||||
"g0.range_input": duration,
|
||||
@@ -125,6 +136,12 @@ const ExploreLogs: FC = () => {
|
||||
}
|
||||
}, [hideChart, prevHideChart, period]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hideLogs && prevHideLogs) {
|
||||
fetchLogs(period);
|
||||
}
|
||||
}, [hideLogs, prevHideLogs, period]);
|
||||
|
||||
return (
|
||||
<div className="vm-explore-logs">
|
||||
<ExploreLogsHeader
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { FC, useCallback, useMemo } from "preact/compat";
|
||||
import { FC, useCallback, useMemo } from "preact/compat";
|
||||
import "./style.scss";
|
||||
import useDeviceDetect from "../../../hooks/useDeviceDetect";
|
||||
import classNames from "classnames";
|
||||
@@ -69,6 +69,8 @@ const ExploreLogsBarChart: FC<Props> = ({ logHits, period, error, isLoading, onA
|
||||
}, [logHits]);
|
||||
|
||||
const noDataMessage: string = useMemo(() => {
|
||||
if (isLoading) return "";
|
||||
|
||||
const noData = data.every(d => d.length === 0);
|
||||
const noTimestamps = data[0].length === 0;
|
||||
const noValues = data[1].length === 0;
|
||||
@@ -81,7 +83,7 @@ const ExploreLogsBarChart: FC<Props> = ({ logHits, period, error, isLoading, onA
|
||||
} else if (noValues) {
|
||||
return "No value information available for the current queries and time range.";
|
||||
} return "";
|
||||
}, [data, hideChart]);
|
||||
}, [data, hideChart, isLoading]);
|
||||
|
||||
const setPeriod = ({ from, to }: {from: Date, to: Date}) => {
|
||||
timeDispatch({ type: "SET_PERIOD", payload: { from, to } });
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { FC, useRef } from "preact/compat";
|
||||
import { CodeIcon, ListIcon, TableIcon, PlayIcon } from "../../../components/Main/Icons";
|
||||
import {
|
||||
CodeIcon,
|
||||
ListIcon,
|
||||
TableIcon,
|
||||
PlayIcon,
|
||||
VisibilityOffIcon,
|
||||
VisibilityIcon
|
||||
} from "../../../components/Main/Icons";
|
||||
import Tabs from "../../../components/Main/Tabs/Tabs";
|
||||
import "./style.scss";
|
||||
import classNames from "classnames";
|
||||
@@ -12,6 +19,10 @@ import GroupView from "./views/GroupView/GroupView";
|
||||
import TableView from "./views/TableView/TableView";
|
||||
import JsonView from "./views/JsonView/JsonView";
|
||||
import LiveTailingView from "./views/LiveTailingView/LiveTailingView";
|
||||
import Tooltip from "../../../components/Main/Tooltip/Tooltip";
|
||||
import Button from "../../../components/Main/Button/Button";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import Alert from "../../../components/Main/Alert/Alert";
|
||||
|
||||
export interface ExploreLogBodyProps {
|
||||
data: Logs[];
|
||||
@@ -34,10 +45,22 @@ const tabs = [
|
||||
|
||||
const ExploreLogsBody: FC<ExploreLogBodyProps> = ({ data, isLoading }) => {
|
||||
const { isMobile } = useDeviceDetect();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const { setSearchParamsFromKeys } = useSearchParamsFromObject();
|
||||
const [activeTab, setActiveTab] = useStateSearchParams(DisplayType.group, "view");
|
||||
const settingsRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [hideLogs, setHideLogs] = useStateSearchParams(false, "hide_logs");
|
||||
|
||||
const toggleHideLogs = () => {
|
||||
setHideLogs(prev => {
|
||||
const newVal = !prev;
|
||||
newVal ? searchParams.set("hide_logs", "true") : searchParams.delete("hide_logs");
|
||||
setSearchParams(searchParams);
|
||||
return newVal;
|
||||
});
|
||||
};
|
||||
|
||||
const handleChangeTab = (view: string) => {
|
||||
setActiveTab(view as DisplayType);
|
||||
setSearchParamsFromKeys({ view });
|
||||
@@ -82,19 +105,33 @@ const ExploreLogsBody: FC<ExploreLogBodyProps> = ({ data, isLoading }) => {
|
||||
className="vm-explore-logs-body-header__settings"
|
||||
ref={settingsRef}
|
||||
/>
|
||||
<Tooltip title={hideLogs ? "Show Logs" : "Hide Logs"}>
|
||||
<Button
|
||||
variant="text"
|
||||
color="primary"
|
||||
startIcon={hideLogs ? <VisibilityOffIcon/> : <VisibilityIcon/>}
|
||||
onClick={toggleHideLogs}
|
||||
ariaLabel="settings"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={classNames({
|
||||
"vm-explore-logs-body__table": true,
|
||||
"vm-explore-logs-body__table_hide": hideLogs,
|
||||
"vm-explore-logs-body__table_mobile": isMobile,
|
||||
})}
|
||||
>
|
||||
{ActiveTabComponent &&
|
||||
<ActiveTabComponent
|
||||
data={data}
|
||||
settingsRef={settingsRef}
|
||||
/>
|
||||
{hideLogs && (
|
||||
<Alert variant="info">Logs are hidden. Updates paused.</Alert>
|
||||
)}
|
||||
|
||||
{!hideLogs && ActiveTabComponent &&
|
||||
<ActiveTabComponent
|
||||
data={data}
|
||||
settingsRef={settingsRef}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
position: relative;
|
||||
|
||||
&-header {
|
||||
grid-template-columns: 1fr auto auto;
|
||||
background-color: $color-background-block;
|
||||
z-index: 3;
|
||||
margin: -$padding-medium 0-$padding-medium 0;
|
||||
@@ -68,5 +69,11 @@
|
||||
.vm-table {
|
||||
min-width: 700px;
|
||||
}
|
||||
|
||||
&_hide {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,6 @@ const LiveTailingSettings: FC<LiveTailingSettingsProps> = ({
|
||||
<Button
|
||||
ref={settingButtonRef}
|
||||
variant="text"
|
||||
color="secondary"
|
||||
onClick={openSettings}
|
||||
startIcon={<SettingsIcon/>}
|
||||
ariaLabel={"Settings"}
|
||||
|
||||
@@ -5,6 +5,5 @@
|
||||
&__settings-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $padding-small;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,10 @@ const GroupLogs: FC<Props> = ({ logs, settingsRef }) => {
|
||||
const getLogs = useCallback(() => logs, [logs]);
|
||||
|
||||
useEffect(() => {
|
||||
setExpandGroups(new Array(groupData.length).fill(!isMobile));
|
||||
setExpandGroups(prev => {
|
||||
const keepClosed = (prev.every(v => !v) && prev.length) || isMobile;
|
||||
return new Array(groupData.length).fill(!keepClosed);
|
||||
});
|
||||
}, [groupData]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -7,9 +7,13 @@ import { LOGS_GROUP_BY, LOGS_LIMIT_HITS } from "../../../constants/logs";
|
||||
import { isEmptyObject } from "../../../utils/object";
|
||||
import { useEffect } from "react";
|
||||
import { useTenant } from "../../../hooks/useTenant";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
||||
export const useFetchLogHits = (server: string, query: string) => {
|
||||
const tenant = useTenant();
|
||||
const [searchParams] = useSearchParams();
|
||||
const hideChart = useMemo(() => searchParams.get("hide_chart"), [searchParams]);
|
||||
|
||||
const [logHits, setLogHits] = useState<LogHits[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<{[key: number]: boolean;}>([]);
|
||||
const [error, setError] = useState<ErrorTypes | string>();
|
||||
@@ -82,6 +86,13 @@ export const useFetchLogHits = (server: string, query: string) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (hideChart) {
|
||||
setLogHits([]);
|
||||
setError(undefined);
|
||||
}
|
||||
}, [hideChart]);
|
||||
|
||||
return {
|
||||
logHits,
|
||||
isLoading: Object.values(isLoading).some(s => s),
|
||||
|
||||
@@ -4,9 +4,12 @@ import { ErrorTypes, TimeParams } from "../../../types";
|
||||
import { Logs } from "../../../api/types";
|
||||
import dayjs from "dayjs";
|
||||
import { useTenant } from "../../../hooks/useTenant";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
||||
export const useFetchLogs = (server: string, query: string, limit: number) => {
|
||||
const tenant = useTenant();
|
||||
const [searchParams] = useSearchParams();
|
||||
const hideLogs = useMemo(() => searchParams.get("hide_logs"), [searchParams]);
|
||||
|
||||
const [logs, setLogs] = useState<Logs[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<{ [key: number]: boolean }>({});
|
||||
@@ -75,6 +78,13 @@ export const useFetchLogs = (server: string, query: string, limit: number) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (hideLogs) {
|
||||
setLogs([]);
|
||||
setError(undefined);
|
||||
}
|
||||
}, [hideLogs]);
|
||||
|
||||
return {
|
||||
logs,
|
||||
isLoading: Object.values(isLoading).some(s => s),
|
||||
|
||||
@@ -18,8 +18,11 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta
|
||||
|
||||
## tip
|
||||
|
||||
* FEATURE: [web UI](https://docs.victoriametrics.com/victorialogs/querying/#web-ui): add the ability to hide the logs panel to view only the graph. When the logs panel is hidden, the `/query` request is not executed.
|
||||
|
||||
* BUGFIX: [`rate_sum` stats function](https://docs.victoriametrics.com/victorialogs/logsql/#rate_sum-stats): fix inconsistent per-second rate calculation when time filters are specified via HTTP query parameters instead of LogsQL expression. This affects recording rule results. See [#9303](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9303).
|
||||
* BUGFIX: [web UI](https://docs.victoriametrics.com/victorialogs/querying/#web-ui): disabled opening of autocomplete popup on initial page load.
|
||||
* BUGFIX: [web UI](https://docs.victoriametrics.com/victorialogs/querying/#web-ui): prevent groups from automatically expanding on list updates if all groups were previously collapsed. See [#8076](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8076).
|
||||
|
||||
## [v1.24.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.24.0-victorialogs)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user