diff --git a/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/GlobalSettings.tsx b/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/GlobalSettings.tsx index fbfe09ce2..816eff2ef 100644 --- a/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/GlobalSettings.tsx +++ b/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/GlobalSettings.tsx @@ -1,22 +1,17 @@ -import React, { FC, useEffect, useState } from "preact/compat"; +import React, { FC, useRef } from "preact/compat"; import ServerConfigurator from "./ServerConfigurator/ServerConfigurator"; -import { useAppDispatch, useAppState } from "../../../state/common/StateContext"; import { ArrowDownIcon, SettingsIcon } from "../../Main/Icons"; import Button from "../../Main/Button/Button"; import Modal from "../../Main/Modal/Modal"; import "./style.scss"; import Tooltip from "../../Main/Tooltip/Tooltip"; import LimitsConfigurator from "./LimitsConfigurator/LimitsConfigurator"; -import { Theme } from "../../../types"; -import { useCustomPanelDispatch, useCustomPanelState } from "../../../state/customPanel/CustomPanelStateContext"; import { getAppModeEnable } from "../../../utils/app-mode"; import classNames from "classnames"; import Timezones from "./Timezones/Timezones"; -import { useTimeDispatch, useTimeState } from "../../../state/time/TimeStateContext"; import ThemeControl from "../ThemeControl/ThemeControl"; import useDeviceDetect from "../../../hooks/useDeviceDetect"; import useBoolean from "../../../hooks/useBoolean"; -import { getTenantIdFromUrl } from "../../../utils/tenants"; import { AppType } from "../../../types/appType"; const title = "Settings"; @@ -24,27 +19,18 @@ const title = "Settings"; const { REACT_APP_TYPE } = process.env; const isLogsApp = REACT_APP_TYPE === AppType.logs; +export interface ChildComponentHandle { + handleApply: () => void; +} + const GlobalSettings: FC = () => { const { isMobile } = useDeviceDetect(); const appModeEnable = getAppModeEnable(); - const { serverUrl: stateServerUrl, theme } = useAppState(); - const { timezone: stateTimezone, defaultTimezone } = useTimeState(); - const { seriesLimits } = useCustomPanelState(); - const dispatch = useAppDispatch(); - const timeDispatch = useTimeDispatch(); - const customPanelDispatch = useCustomPanelDispatch(); - - const [serverUrl, setServerUrl] = useState(stateServerUrl); - const [limits, setLimits] = useState(seriesLimits); - const [timezone, setTimezone] = useState(stateTimezone); - - const setDefaultsValues = () => { - setServerUrl(stateServerUrl); - setLimits(seriesLimits); - setTimezone(stateTimezone); - }; + const serverSettingRef = useRef(null); + const limitsSettingRef = useRef(null); + const timezoneSettingRef = useRef(null); const { value: open, @@ -52,68 +38,35 @@ const GlobalSettings: FC = () => { setFalse: handleClose, } = useBoolean(false); - const handleCloseAndReset = () => { - handleClose(); - setDefaultsValues(); - }; - - const handleChangeTheme = (value: Theme) => { - dispatch({ type: "SET_THEME", payload: value }); - }; - - const handlerApply = () => { - const tenantIdFromUrl = getTenantIdFromUrl(serverUrl); - if (tenantIdFromUrl !== "") { - dispatch({ type: "SET_TENANT_ID", payload: tenantIdFromUrl }); - } - dispatch({ type: "SET_SERVER", payload: serverUrl }); - timeDispatch({ type: "SET_TIMEZONE", payload: timezone }); - customPanelDispatch({ type: "SET_SERIES_LIMITS", payload: limits }); + const handleApply = () => { + serverSettingRef.current && serverSettingRef.current.handleApply(); + limitsSettingRef.current && limitsSettingRef.current.handleApply(); + timezoneSettingRef.current && timezoneSettingRef.current.handleApply(); handleClose(); }; - useEffect(() => { - // the tenant selector can change the serverUrl - if (stateServerUrl === serverUrl) return; - setServerUrl(stateServerUrl); - }, [stateServerUrl]); - - useEffect(() => { - setTimezone(stateTimezone); - }, [stateTimezone]); - const controls = [ { show: !appModeEnable && !isLogsApp, component: }, { show: !isLogsApp, component: }, { show: true, - component: + component: }, { show: !appModeEnable, - component: + component: } ].filter(control => control.show); @@ -146,7 +99,7 @@ const GlobalSettings: FC = () => { {open && (
{ diff --git a/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/LimitsConfigurator/LimitsConfigurator.tsx b/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/LimitsConfigurator/LimitsConfigurator.tsx index 4581c7cb3..8c1afbf67 100644 --- a/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/LimitsConfigurator/LimitsConfigurator.tsx +++ b/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/LimitsConfigurator/LimitsConfigurator.tsx @@ -1,5 +1,5 @@ -import React, { FC, useState } from "preact/compat"; -import { DisplayType, ErrorTypes, SeriesLimits } from "../../../../types"; +import React, { forwardRef, useCallback, useImperativeHandle, useState } from "preact/compat"; +import { DisplayType, ErrorTypes } from "../../../../types"; import TextField from "../../../Main/TextField/TextField"; import Tooltip from "../../../Main/Tooltip/Tooltip"; import { InfoIcon, RestartIcon } from "../../../Main/Icons"; @@ -8,11 +8,11 @@ import { DEFAULT_MAX_SERIES } from "../../../../constants/graph"; import "./style.scss"; import classNames from "classnames"; import useDeviceDetect from "../../../../hooks/useDeviceDetect"; +import { ChildComponentHandle } from "../GlobalSettings"; +import { useCustomPanelDispatch, useCustomPanelState } from "../../../../state/customPanel/CustomPanelStateContext"; -export interface ServerConfiguratorProps { - limits: SeriesLimits - onChange: (limits: SeriesLimits) => void - onEnter: () => void +interface ServerConfiguratorProps { + onClose: () => void } const fields: {label: string, type: DisplayType}[] = [ @@ -21,31 +21,38 @@ const fields: {label: string, type: DisplayType}[] = [ { label: "Table", type: DisplayType.table } ]; -const LimitsConfigurator: FC = ({ limits, onChange , onEnter }) => { +const LimitsConfigurator = forwardRef(({ onClose }, ref) => { const { isMobile } = useDeviceDetect(); + const { seriesLimits } = useCustomPanelState(); + const customPanelDispatch = useCustomPanelDispatch(); + + const [limits, setLimits] = useState(seriesLimits); const [error, setError] = useState({ table: "", chart: "", code: "" }); - const handleChange = (val: string, type: DisplayType) => { + const handleReset = () => { + setLimits(DEFAULT_MAX_SERIES); + }; + + const createChangeHandler = (type: DisplayType) => (val: string) => { const value = val || ""; setError(prev => ({ ...prev, [type]: +value < 0 ? ErrorTypes.positiveNumber : "" })); - onChange({ + setLimits({ ...limits, [type]: !value ? Infinity : value }); }; - const handleReset = () => { - onChange(DEFAULT_MAX_SERIES); - }; + const handleApply = useCallback(() => { + customPanelDispatch({ type: "SET_SERIES_LIMITS", payload: limits }); + onClose(); + }, [limits]); - const createChangeHandler = (type: DisplayType) => (val: string) => { - handleChange(val, type); - }; + useImperativeHandle(ref, () => ({ handleApply }), [handleApply]); return (
@@ -84,7 +91,7 @@ const LimitsConfigurator: FC = ({ limits, onChange , on value={limits[f.type]} error={error[f.type]} onChange={createChangeHandler(f.type)} - onEnter={onEnter} + onEnter={handleApply} type="number" />
@@ -92,6 +99,6 @@ const LimitsConfigurator: FC = ({ limits, onChange , on
); -}; +}); export default LimitsConfigurator; diff --git a/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/ServerConfigurator/ServerConfigurator.tsx b/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/ServerConfigurator/ServerConfigurator.tsx index a16d3d256..c15fabc96 100644 --- a/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/ServerConfigurator/ServerConfigurator.tsx +++ b/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/ServerConfigurator/ServerConfigurator.tsx @@ -1,4 +1,4 @@ -import React, { FC, useEffect, useState } from "preact/compat"; +import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "preact/compat"; import { ErrorTypes } from "../../../../types"; import TextField from "../../../Main/TextField/TextField"; import { isValidHttpUrl } from "../../../../utils/url"; @@ -7,12 +7,12 @@ import { StorageIcon } from "../../../Main/Icons"; import Tooltip from "../../../Main/Tooltip/Tooltip"; import { getFromStorage, removeFromStorage, saveToStorage } from "../../../../utils/storage"; import useBoolean from "../../../../hooks/useBoolean"; +import { ChildComponentHandle } from "../GlobalSettings"; +import { useAppDispatch, useAppState } from "../../../../state/common/StateContext"; +import { getTenantIdFromUrl } from "../../../../utils/tenants"; -export interface ServerConfiguratorProps { - serverUrl: string - stateServerUrl: string - onChange: (url: string) => void - onEnter: () => void +interface ServerConfiguratorProps { + onClose: () => void; } const tooltipSave = { @@ -20,24 +20,33 @@ const tooltipSave = { disable: "Disable to stop saving the server URL to local storage, reverting to the default URL on page refresh." }; -const ServerConfigurator: FC = ({ - serverUrl, - stateServerUrl, - onChange , - onEnter -}) => { +const ServerConfigurator = forwardRef(({ onClose }, ref) => { + const { serverUrl: stateServerUrl } = useAppState(); + const dispatch = useAppDispatch(); + const { value: enabledStorage, toggle: handleToggleStorage, } = useBoolean(!!getFromStorage("SERVER_URL")); + + const [serverUrl, setServerUrl] = useState(stateServerUrl); const [error, setError] = useState(""); - const onChangeServer = (val: string) => { + const handleChange = (val: string) => { const value = val || ""; - onChange(value); + setServerUrl(value); setError(""); }; + const handleApply = useCallback(() => { + const tenantIdFromUrl = getTenantIdFromUrl(serverUrl); + if (tenantIdFromUrl !== "") { + dispatch({ type: "SET_TENANT_ID", payload: tenantIdFromUrl }); + } + dispatch({ type: "SET_SERVER", payload: serverUrl }); + onClose(); + }, [serverUrl]); + useEffect(() => { if (!stateServerUrl) setError(ErrorTypes.emptyServer); if (!isValidHttpUrl(stateServerUrl)) setError(ErrorTypes.validServer); @@ -57,6 +66,14 @@ const ServerConfigurator: FC = ({ } }, [serverUrl]); + useEffect(() => { + // the tenant selector can change the serverUrl + if (stateServerUrl === serverUrl) return; + setServerUrl(stateServerUrl); + }, [stateServerUrl]); + + useImperativeHandle(ref, () => ({ handleApply }), [handleApply]); + return (
@@ -67,8 +84,8 @@ const ServerConfigurator: FC = ({ autofocus value={serverUrl} error={error} - onChange={onChangeServer} - onEnter={onEnter} + onChange={handleChange} + onEnter={handleApply} inputmode="url" /> @@ -83,6 +100,6 @@ const ServerConfigurator: FC = ({
); -}; +}); export default ServerConfigurator; diff --git a/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/Timezones/Timezones.tsx b/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/Timezones/Timezones.tsx index 268eed76f..58cb24990 100644 --- a/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/Timezones/Timezones.tsx +++ b/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/Timezones/Timezones.tsx @@ -1,4 +1,4 @@ -import React, { FC, useMemo, useRef, useState } from "preact/compat"; +import React, { FC, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "preact/compat"; import { getBrowserTimezone, getTimezoneList, getUTCByTimezone } from "../../../../utils/time"; import { ArrowDropDownIcon } from "../../../Main/Icons"; import classNames from "classnames"; @@ -10,12 +10,7 @@ import "./style.scss"; import useDeviceDetect from "../../../../hooks/useDeviceDetect"; import useBoolean from "../../../../hooks/useBoolean"; import WarningTimezone from "./WarningTimezone"; - -interface TimezonesProps { - timezoneState: string; - defaultTimezone?: string; - onChange: (val: string) => void; -} +import { useTimeDispatch, useTimeState } from "../../../../state/time/TimeStateContext"; interface PinnedTimezone extends Timezone { title: string; @@ -24,10 +19,14 @@ interface PinnedTimezone extends Timezone { const browserTimezone = getBrowserTimezone(); -const Timezones: FC = ({ timezoneState, defaultTimezone, onChange }) => { +const Timezones: FC = forwardRef((props, ref) => { const { isMobile } = useDeviceDetect(); const timezones = getTimezoneList(); + const { timezone: stateTimezone, defaultTimezone } = useTimeState(); + const timeDispatch = useTimeDispatch(); + + const [timezone, setTimezone] = useState(stateTimezone); const [search, setSearch] = useState(""); const targetRef = useRef(null); @@ -68,16 +67,16 @@ const Timezones: FC = ({ timezoneState, defaultTimezone, onChang const timezonesGroups = useMemo(() => Object.keys(searchTimezones), [searchTimezones]); const activeTimezone = useMemo(() => ({ - region: timezoneState, - utc: getUTCByTimezone(timezoneState) - }), [timezoneState]); + region: timezone, + utc: getUTCByTimezone(timezone) + }), [timezone]); const handleChangeSearch = (val: string) => { setSearch(val); }; const handleSetTimezone = (val: Timezone) => { - onChange(val.region); + setTimezone(val.region); setSearch(""); handleCloseList(); }; @@ -86,6 +85,16 @@ const Timezones: FC = ({ timezoneState, defaultTimezone, onChang handleSetTimezone(val); }; + useEffect(() => { + setTimezone(stateTimezone); + }, [stateTimezone]); + + useImperativeHandle(ref, () => ({ + handleApply: () => { + timeDispatch({ type: "SET_TIMEZONE", payload: timezone }); + } + }), [timezone]); + return (
@@ -169,6 +178,6 @@ const Timezones: FC = ({ timezoneState, defaultTimezone, onChang
); -}; +}); export default Timezones; diff --git a/app/vmui/packages/vmui/src/components/Configurators/ThemeControl/ThemeControl.tsx b/app/vmui/packages/vmui/src/components/Configurators/ThemeControl/ThemeControl.tsx index 1d2885004..24c2aca6c 100644 --- a/app/vmui/packages/vmui/src/components/Configurators/ThemeControl/ThemeControl.tsx +++ b/app/vmui/packages/vmui/src/components/Configurators/ThemeControl/ThemeControl.tsx @@ -5,18 +5,17 @@ import Toggle from "../../Main/Toggle/Toggle"; import useDeviceDetect from "../../../hooks/useDeviceDetect"; import classNames from "classnames"; import { FC } from "preact/compat"; - -interface ThemeControlProps { - theme: Theme; - onChange: (val: Theme) => void -} +import { useAppDispatch, useAppState } from "../../../state/common/StateContext"; const options = Object.values(Theme).map(value => ({ title: value, value })); -const ThemeControl: FC = ({ theme, onChange }) => { +const ThemeControl: FC = () => { const { isMobile } = useDeviceDetect(); + const dispatch = useAppDispatch(); + + const { theme } = useAppState(); const handleClickItem = (value: string) => { - onChange(value as Theme); + dispatch({ type: "SET_THEME", payload: value as Theme }); }; return ( diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d3c74de08..36d8ec5c0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -32,7 +32,7 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/). * FEATURE: [dashboards](https://grafana.com/orgs/victoriametrics): add [Grafana dashboard](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/dashboards/vmauth.json) and [alerting rules](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/deployment/docker/alerts-vmauth.yml) for [vmauth](https://docs.victoriametrics.com/vmauth/) dashboard. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4313) for details. * BUGFIX: [docker-compose](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker#docker-compose-environment-for-victoriametrics): fix incorrect link to vmui from [VictoriaMetrics plugin in Grafana](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker#grafana). - +* BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix input cursor position reset in modal settings. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/6530). ## [v1.102.0-rc2](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.102.0-rc2)