From eb6def0695ac0dc00237630699b184a213a476d8 Mon Sep 17 00:00:00 2001 From: Yury Molodov Date: Tue, 23 Jan 2024 03:11:19 +0100 Subject: [PATCH] vmui: add flag for default timezone setting (#5611) * vmui: add flag for default timezone setting #5375 * vmui: validate timezone before client return * Update app/vmselect/vmui.go --------- Co-authored-by: Aliaksandr Valialkin --- README.md | 2 + app/vmselect/main.go | 8 +++ app/vmselect/vmui.go | 13 ++++ app/vmui/README.md | 37 ++++++++++++ .../GlobalSettings/GlobalSettings.tsx | 7 ++- .../ServerConfigurator/ServerConfigurator.tsx | 6 ++ .../GlobalSettings/Timezones/Timezones.tsx | 51 +++++++++++----- .../vmui/src/hooks/useFetchDefaultTimezone.ts | 59 +++++++++++++++++++ .../layouts/AnomalyLayout/AnomalyLayout.tsx | 4 +- .../src/layouts/LogsLayout/LogsLayout.tsx | 3 + .../src/layouts/MainLayout/MainLayout.tsx | 2 + .../packages/vmui/src/state/time/reducer.ts | 8 +++ app/vmui/packages/vmui/src/utils/storage.ts | 1 + docs/CHANGELOG.md | 2 + docs/Cluster-VictoriaMetrics.md | 2 + docs/README.md | 2 + docs/Single-server-VictoriaMetrics.md | 2 + 17 files changed, 191 insertions(+), 18 deletions(-) create mode 100644 app/vmui/packages/vmui/src/hooks/useFetchDefaultTimezone.ts diff --git a/README.md b/README.md index c41d693e63..1b06d67f07 100644 --- a/README.md +++ b/README.md @@ -3040,4 +3040,6 @@ Pass `-help` to VictoriaMetrics in order to see the list of supported command-li Optional URL for proxying requests to vmalert. For example, if -vmalert.proxyURL=http://vmalert:8880 , then alerting API requests such as /api/v1/rules from Grafana will be proxied to http://vmalert:8880/api/v1/rules -vmui.customDashboardsPath string Optional path to vmui dashboards. See https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui/packages/vmui/public/dashboards + -vmui.defaultTimezone string + The default timezone to be used in vmui. Timezone must be a valid IANA Time Zone. For example: America/New_York, Europe/Berlin, Etc/GMT+3 or Local. See https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui#timezone-configuration ``` diff --git a/app/vmselect/main.go b/app/vmselect/main.go index 9a7c187a26..e9d66d29ab 100644 --- a/app/vmselect/main.go +++ b/app/vmselect/main.go @@ -426,6 +426,14 @@ func handleStaticAndSimpleRequests(w http.ResponseWriter, r *http.Request, path } return true } + if path == "/vmui/timezone" { + httpserver.EnableCORS(w, r) + if err := handleVMUITimezone(w); err != nil { + httpserver.Errorf(w, r, "%s", err) + return true + } + return true + } if strings.HasPrefix(path, "/vmui/") { if strings.HasPrefix(path, "/vmui/static/") { // Allow clients caching static contents for long period of time, since it shouldn't change over time. diff --git a/app/vmselect/vmui.go b/app/vmselect/vmui.go index b25585ce9f..a3a176c85e 100644 --- a/app/vmselect/vmui.go +++ b/app/vmselect/vmui.go @@ -7,6 +7,7 @@ import ( "net/http" "os" "path/filepath" + "time" "github.com/VictoriaMetrics/VictoriaMetrics/lib/fs" ) @@ -14,6 +15,8 @@ import ( var ( vmuiCustomDashboardsPath = flag.String("vmui.customDashboardsPath", "", "Optional path to vmui dashboards. "+ "See https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui/packages/vmui/public/dashboards") + vmuiDefaultTimezone = flag.String("vmui.defaultTimezone", "", "The default timezone to be used in vmui."+ + "Timezone must be a valid IANA Time Zone. For example: America/New_York, Europe/Berlin, Etc/GMT+3 or Local") ) // dashboardSettings represents dashboard settings file struct. @@ -65,6 +68,16 @@ func handleVMUICustomDashboards(w http.ResponseWriter) error { return nil } +func handleVMUITimezone(w http.ResponseWriter) error { + tz, err := time.LoadLocation(*vmuiDefaultTimezone) + if err != nil { + return fmt.Errorf("cannot load timezone %q: %w", *vmuiDefaultTimezone, err) + } + response := fmt.Sprintf(`{"timezone": %q}`, tz) + writeSuccessResponse(w, []byte(response)) + return nil +} + func writeSuccessResponse(w http.ResponseWriter, data []byte) { w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") diff --git a/app/vmui/README.md b/app/vmui/README.md index 3730bc5303..abaf0a61d3 100644 --- a/app/vmui/README.md +++ b/app/vmui/README.md @@ -7,6 +7,7 @@ Web UI for VictoriaMetrics * [Updating vmui embedded into VictoriaMetrics](#updating-vmui-embedded-into-victoriametrics) * [Predefined dashboards](#predefined-dashboards) * [App mode config options](#app-mode-config-options) +* [Timezone configuration](#timezone-configuration) ---- @@ -246,3 +247,39 @@ vmui can be used to paste into other applications ```html
``` + +---- + +## Timezone configuration + +vmui's timezone setting offers flexibility in displaying time data. It can be set through a configuration flag and is adjustable within the vmui interface. This feature caters to various user preferences and time zones. + +### Default Timezone Setting + +#### Via Configuration Flag + +- Set the default timezone using the `--vmui.defaultTimezone` flag. +- Accepts a valid IANA Time Zone string (e.g., `America/New_York`, `Europe/Berlin`, `Etc/GMT+3`). +- If the flag is unset or invalid, vmui defaults to the browser's local timezone. + +#### User Interface Adjustments + +- Users can change the timezone in the vmui interface. +- Any changed setting in the interface overrides the flag's default, persisting for the user. +- The timezone specified in the `--vmui.defaultTimezone` flag is included in the vmui's timezone selection dropdown, aiding user choice. + +### Key Points + +- **Fallback to Browser's Local Timezone**: If the flag is not set or an invalid timezone is specified, vmui uses the local timezone of the user's browser. +- **User Preference Priority**: User-selected timezones in vmui take precedence over the default set by the flag. +- **Cluster Consistency**: Ensure uniform timezone settings across cluster nodes, but individual user interface selections will always override these defaults. + +### Examples + +Setting a default timezone, with user options to change: + +``` +./victoria-metrics --vmui.defaultTimezone="America/New_York" +``` + +In this scenario, if a user in Berlin accesses vmui without changing settings, it will default to their browser's local timezone (CET). If they select a different timezone in vmui, this choice will override the `"America/New_York"` setting for that user. 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 f23d58b394..fbfe09ce2e 100644 --- a/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/GlobalSettings.tsx +++ b/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/GlobalSettings.tsx @@ -29,7 +29,7 @@ const GlobalSettings: FC = () => { const appModeEnable = getAppModeEnable(); const { serverUrl: stateServerUrl, theme } = useAppState(); - const { timezone: stateTimezone } = useTimeState(); + const { timezone: stateTimezone, defaultTimezone } = useTimeState(); const { seriesLimits } = useCustomPanelState(); const dispatch = useAppDispatch(); @@ -78,6 +78,10 @@ const GlobalSettings: FC = () => { setServerUrl(stateServerUrl); }, [stateServerUrl]); + useEffect(() => { + setTimezone(stateTimezone); + }, [stateTimezone]); + const controls = [ { show: !appModeEnable && !isLogsApp, @@ -100,6 +104,7 @@ const GlobalSettings: FC = () => { show: true, component: }, 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 c3fa516f21..a16d3d256a 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 @@ -51,6 +51,12 @@ const ServerConfigurator: FC = ({ } }, [enabledStorage]); + useEffect(() => { + if (enabledStorage) { + saveToStorage("SERVER_URL", serverUrl); + } + }, [serverUrl]); + return (
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 6bb0372ea6..8d012dedbd 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 @@ -12,11 +12,16 @@ import useDeviceDetect from "../../../../hooks/useDeviceDetect"; import useBoolean from "../../../../hooks/useBoolean"; interface TimezonesProps { - timezoneState: string - onChange: (val: string) => void + timezoneState: string; + defaultTimezone?: string; + onChange: (val: string) => void; } -const Timezones: FC = ({ timezoneState, onChange }) => { +interface PinnedTimezone extends Timezone { + title: string +} + +const Timezones: FC = ({ timezoneState, defaultTimezone, onChange }) => { const { isMobile } = useDeviceDetect(); const timezones = getTimezoneList(); @@ -29,6 +34,24 @@ const Timezones: FC = ({ timezoneState, onChange }) => { setFalse: handleCloseList, } = useBoolean(false); + const pinnedTimezones = useMemo(() => [ + { + title: `Default time (${defaultTimezone})`, + region: defaultTimezone, + utc: defaultTimezone ? getUTCByTimezone(defaultTimezone) : "UTC" + }, + { + title: `Browser Time (${dayjs.tz.guess()})`, + region: dayjs.tz.guess(), + utc: getUTCByTimezone(dayjs.tz.guess()) + }, + { + title: "UTC (Coordinated Universal Time)", + region: "UTC", + utc: "UTC" + }, + ].filter(t => t.region) as PinnedTimezone[], [defaultTimezone]); + const searchTimezones = useMemo(() => { if (!search) return timezones; try { @@ -40,11 +63,6 @@ const Timezones: FC = ({ timezoneState, onChange }) => { const timezonesGroups = useMemo(() => Object.keys(searchTimezones), [searchTimezones]); - const localTimezone = useMemo(() => ({ - region: dayjs.tz.guess(), - utc: getUTCByTimezone(dayjs.tz.guess()) - }), []); - const activeTimezone = useMemo(() => ({ region: timezoneState, utc: getUTCByTimezone(timezoneState) @@ -108,13 +126,16 @@ const Timezones: FC = ({ timezoneState, onChange }) => { onChange={handleChangeSearch} />
-
-
Browser Time ({localTimezone.region})
-
{localTimezone.utc}
-
+ {pinnedTimezones.map((t, i) => t && ( +
+
{t.title}
+
{t.utc}
+
+ ))}
{timezonesGroups.map(t => (
{ + const { serverUrl } = useAppState(); + const timeDispatch = useTimeDispatch(); + + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(""); + + const setTimezone = (timezoneStr: string) => { + const timezone = timezoneStr.toLowerCase() === "local" ? dayjs.tz.guess() : timezoneStr; + try { + dayjs().tz(timezone).isValid(); + timeDispatch({ type: "SET_DEFAULT_TIMEZONE", payload: timezone }); + if (disabledDefaultTimezone) return; + timeDispatch({ type: "SET_TIMEZONE", payload: timezone }); + } catch (e) { + if (e instanceof Error) setError(`${e.name}: ${e.message}`); + } + }; + + const fetchDefaultTimezone = async () => { + if (!serverUrl || process.env.REACT_APP_TYPE) return; + setError(""); + setIsLoading(true); + + try { + const response = await fetch(`${serverUrl}/vmui/timezone`); + const resp = await response.json(); + + if (response.ok) { + setTimezone(resp.timezone); + setIsLoading(false); + } else { + setError(resp.error); + setIsLoading(false); + } + } catch (e) { + setIsLoading(false); + if (e instanceof Error) setError(`${e.name}: ${e.message}`); + } + }; + + useEffect(() => { + fetchDefaultTimezone(); + }, [serverUrl]); + + return { isLoading, error }; +}; + +export default useFetchDefaultTimezone; + diff --git a/app/vmui/packages/vmui/src/layouts/AnomalyLayout/AnomalyLayout.tsx b/app/vmui/packages/vmui/src/layouts/AnomalyLayout/AnomalyLayout.tsx index 686e6e8899..a5f516be2c 100644 --- a/app/vmui/packages/vmui/src/layouts/AnomalyLayout/AnomalyLayout.tsx +++ b/app/vmui/packages/vmui/src/layouts/AnomalyLayout/AnomalyLayout.tsx @@ -7,7 +7,7 @@ import { getAppModeEnable } from "../../utils/app-mode"; import classNames from "classnames"; import Footer from "../Footer/Footer"; import { routerOptions } from "../../router"; -import { useFetchDashboards } from "../../pages/PredefinedPanels/hooks/useFetchDashboards"; +import useFetchDefaultTimezone from "../../hooks/useFetchDefaultTimezone"; import useDeviceDetect from "../../hooks/useDeviceDetect"; import ControlsAnomalyLayout from "./ControlsAnomalyLayout"; @@ -17,7 +17,7 @@ const AnomalyLayout: FC = () => { const { pathname } = useLocation(); const [searchParams, setSearchParams] = useSearchParams(); - useFetchDashboards(); + useFetchDefaultTimezone(); const setDocumentTitle = () => { const defaultTitle = "vmui for vmanomaly"; diff --git a/app/vmui/packages/vmui/src/layouts/LogsLayout/LogsLayout.tsx b/app/vmui/packages/vmui/src/layouts/LogsLayout/LogsLayout.tsx index 4d8a26eb19..3d62849938 100644 --- a/app/vmui/packages/vmui/src/layouts/LogsLayout/LogsLayout.tsx +++ b/app/vmui/packages/vmui/src/layouts/LogsLayout/LogsLayout.tsx @@ -8,12 +8,15 @@ import Footer from "../Footer/Footer"; import router, { routerOptions } from "../../router"; import useDeviceDetect from "../../hooks/useDeviceDetect"; import ControlsLogsLayout from "./ControlsLogsLayout"; +import useFetchDefaultTimezone from "../../hooks/useFetchDefaultTimezone"; const LogsLayout: FC = () => { const appModeEnable = getAppModeEnable(); const { isMobile } = useDeviceDetect(); const { pathname } = useLocation(); + useFetchDefaultTimezone(); + const setDocumentTitle = () => { const defaultTitle = "vmui for VictoriaLogs"; const routeTitle = routerOptions[router.logs]?.title; diff --git a/app/vmui/packages/vmui/src/layouts/MainLayout/MainLayout.tsx b/app/vmui/packages/vmui/src/layouts/MainLayout/MainLayout.tsx index 6d5f5fdc7a..552b0b0726 100644 --- a/app/vmui/packages/vmui/src/layouts/MainLayout/MainLayout.tsx +++ b/app/vmui/packages/vmui/src/layouts/MainLayout/MainLayout.tsx @@ -10,6 +10,7 @@ import { routerOptions } from "../../router"; import { useFetchDashboards } from "../../pages/PredefinedPanels/hooks/useFetchDashboards"; import useDeviceDetect from "../../hooks/useDeviceDetect"; import ControlsMainLayout from "./ControlsMainLayout"; +import useFetchDefaultTimezone from "../../hooks/useFetchDefaultTimezone"; const MainLayout: FC = () => { const appModeEnable = getAppModeEnable(); @@ -18,6 +19,7 @@ const MainLayout: FC = () => { const [searchParams, setSearchParams] = useSearchParams(); useFetchDashboards(); + useFetchDefaultTimezone(); const setDocumentTitle = () => { const defaultTitle = "vmui"; diff --git a/app/vmui/packages/vmui/src/state/time/reducer.ts b/app/vmui/packages/vmui/src/state/time/reducer.ts index 6cc69ac40a..a1d8aed80e 100644 --- a/app/vmui/packages/vmui/src/state/time/reducer.ts +++ b/app/vmui/packages/vmui/src/state/time/reducer.ts @@ -17,6 +17,7 @@ export interface TimeState { period: TimeParams; relativeTime?: string; timezone: string; + defaultTimezone?: string; } export type TimeAction = @@ -26,6 +27,7 @@ export type TimeAction = | { type: "RUN_QUERY"} | { type: "RUN_QUERY_TO_NOW"} | { type: "SET_TIMEZONE", payload: string } + | { type: "SET_DEFAULT_TIMEZONE", payload: string } const timezone = getFromStorage("TIMEZONE") as string || dayjs.tz.guess(); setTimezone(timezone); @@ -90,10 +92,16 @@ export function reducer(state: TimeState, action: TimeAction): TimeState { case "SET_TIMEZONE": setTimezone(action.payload); saveToStorage("TIMEZONE", action.payload); + if (state.defaultTimezone) saveToStorage("DISABLED_DEFAULT_TIMEZONE", action.payload !== state.defaultTimezone); return { ...state, timezone: action.payload }; + case "SET_DEFAULT_TIMEZONE": + return { + ...state, + defaultTimezone: action.payload + }; default: throw new Error(); } diff --git a/app/vmui/packages/vmui/src/utils/storage.ts b/app/vmui/packages/vmui/src/utils/storage.ts index 17f9c991f5..995cf77725 100644 --- a/app/vmui/packages/vmui/src/utils/storage.ts +++ b/app/vmui/packages/vmui/src/utils/storage.ts @@ -4,6 +4,7 @@ export type StorageKeys = "AUTOCOMPLETE" | "SERIES_LIMITS" | "TABLE_COMPACT" | "TIMEZONE" + | "DISABLED_DEFAULT_TIMEZONE" | "THEME" | "LOGS_LIMIT" | "EXPLORE_METRICS_TIPS" diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 0ddfbbeac5..d3e06a0ed8 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -53,6 +53,8 @@ The sandbox cluster installation is running under the constant load generated by * FEATURE: [vmctl](https://docs.victoriametrics.com/vmctl.html): rename cmd-line flag `vm-native-disable-retries` to `vm-native-disable-per-metric-migration` to better reflect its meaning. * FEATURE: [vmctl](https://docs.victoriametrics.com/vmctl.html): add `-vm-native-src-insecure-skip-verify` and `-vm-native-dst-insecure-skip-verify` command-line flags for native protocol. It can be used for skipping TLS certificate verification when connecting to the source or destination addresses. * FEATURE: [Alerting rules for VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/deployment/docker#alerts): add `job` label to `DiskRunsOutOfSpace` alerting rule, so it is easier to understand to which installation the triggered instance belongs. +* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add `-vmui.defaultTimezone` flag to set a default timezone. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5375) and [these docs](https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui#timezone-configuration). +* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): include UTC in the timezone selection dropdown for standardized time referencing. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5375). * FEATURE: add [VictoriaMetrics datasource](https://github.com/VictoriaMetrics/grafana-datasource) to docker compose environment. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5363). * BUGFIX: properly return errors from [export APIs](https://docs.victoriametrics.com/#how-to-export-time-series). Previously these errors were silently suppressed. See [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5649). diff --git a/docs/Cluster-VictoriaMetrics.md b/docs/Cluster-VictoriaMetrics.md index 1d1efb05f2..134ff19010 100644 --- a/docs/Cluster-VictoriaMetrics.md +++ b/docs/Cluster-VictoriaMetrics.md @@ -1444,6 +1444,8 @@ Below is the output for `/path/to/vmselect -help`: Network timeout for RPC connections from vmselect to vmstorage (Linux only). Lower values reduce the maximum query durations when some vmstorage nodes become unavailable because of networking issues. Read more about TCP_USER_TIMEOUT at https://blog.cloudflare.com/when-tcp-sockets-refuse-to-die/ . See also -vmstorageDialTimeout (default 3s) -vmui.customDashboardsPath string Optional path to vmui dashboards. See https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui/packages/vmui/public/dashboards + -vmui.defaultTimezone string + The default timezone to be used in vmui. Timezone must be a valid IANA Time Zone. For example: America/New_York, Europe/Berlin, Etc/GMT+3 or Local. See https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui#timezone-configuration ``` ### List of command-line flags for vmstorage diff --git a/docs/README.md b/docs/README.md index 3325043fd7..95a08c1615 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3043,4 +3043,6 @@ Pass `-help` to VictoriaMetrics in order to see the list of supported command-li Optional URL for proxying requests to vmalert. For example, if -vmalert.proxyURL=http://vmalert:8880 , then alerting API requests such as /api/v1/rules from Grafana will be proxied to http://vmalert:8880/api/v1/rules -vmui.customDashboardsPath string Optional path to vmui dashboards. See https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui/packages/vmui/public/dashboards + -vmui.defaultTimezone string + The default timezone to be used in vmui. Timezone must be a valid IANA Time Zone. For example: America/New_York, Europe/Berlin, Etc/GMT+3 or Local. See https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui#timezone-configuration ``` diff --git a/docs/Single-server-VictoriaMetrics.md b/docs/Single-server-VictoriaMetrics.md index 1e42bf65d0..d5ad175e4f 100644 --- a/docs/Single-server-VictoriaMetrics.md +++ b/docs/Single-server-VictoriaMetrics.md @@ -3051,4 +3051,6 @@ Pass `-help` to VictoriaMetrics in order to see the list of supported command-li Optional URL for proxying requests to vmalert. For example, if -vmalert.proxyURL=http://vmalert:8880 , then alerting API requests such as /api/v1/rules from Grafana will be proxied to http://vmalert:8880/api/v1/rules -vmui.customDashboardsPath string Optional path to vmui dashboards. See https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui/packages/vmui/public/dashboards + -vmui.defaultTimezone string + The default timezone to be used in vmui. Timezone must be a valid IANA Time Zone. For example: America/New_York, Europe/Berlin, Etc/GMT+3 or Local. See https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui#timezone-configuration ```