From d8cef11a252ec27cb81b097d9e103ce65c34b67c Mon Sep 17 00:00:00 2001 From: Carlos Valente Date: Sat, 21 Mar 2026 20:56:52 +0100 Subject: [PATCH] fix: server port settings has lifecycle --- apps/client/src/common/api/constants.ts | 1 + apps/client/src/common/api/settings.ts | 4 +- .../src/common/hooks-query/useServerPort.ts | 36 ++++++++++++++++ .../settings-panel/ServerPortSettings.tsx | 41 +++++++++---------- 4 files changed, 59 insertions(+), 23 deletions(-) create mode 100644 apps/client/src/common/hooks-query/useServerPort.ts diff --git a/apps/client/src/common/api/constants.ts b/apps/client/src/common/api/constants.ts index 3998e9800..00d9e6536 100644 --- a/apps/client/src/common/api/constants.ts +++ b/apps/client/src/common/api/constants.ts @@ -3,6 +3,7 @@ import { serverURL } from '../../externals'; // keys in tanstack store export const APP_INFO = ['appinfo']; export const APP_SETTINGS = ['appSettings']; +export const APP_SERVER_PORT = ['appServerPort']; export const APP_VERSION = ['appVersion']; export const AUTOMATION = ['automation']; export const CUSTOM_FIELDS = ['customFields']; diff --git a/apps/client/src/common/api/settings.ts b/apps/client/src/common/api/settings.ts index f3eb9c574..897a00759 100644 --- a/apps/client/src/common/api/settings.ts +++ b/apps/client/src/common/api/settings.ts @@ -31,8 +31,8 @@ export async function postShowWelcomeDialog(show: boolean) { /** * HTTP request to retrieve server port */ -export async function getServerPort(): Promise { - const res = await axios.get(`${settingsPath}/serverport`); +export async function getServerPort(options?: RequestOptions): Promise { + const res = await axios.get(`${settingsPath}/serverport`, { signal: options?.signal }); return res.data; } diff --git a/apps/client/src/common/hooks-query/useServerPort.ts b/apps/client/src/common/hooks-query/useServerPort.ts new file mode 100644 index 000000000..c93974c6b --- /dev/null +++ b/apps/client/src/common/hooks-query/useServerPort.ts @@ -0,0 +1,36 @@ +import { useMutation, useQuery } from '@tanstack/react-query'; +import { PortInfo } from 'ontime-types'; + +import { queryRefetchIntervalSlow } from '../../ontimeConfig'; +import { APP_SERVER_PORT } from '../api/constants'; +import { getServerPort, postServerPort } from '../api/settings'; +import { ontimeQueryClient } from '../queryClient'; + +const serverPortPlaceholder: PortInfo = { + port: 4001, + pendingRestart: false, +}; + +export default function useServerPort() { + const { data, status, isError, refetch } = useQuery({ + queryKey: APP_SERVER_PORT, + queryFn: ({ signal }) => getServerPort({ signal }), + placeholderData: (previousData) => previousData, + refetchInterval: queryRefetchIntervalSlow, + }); + + const { mutateAsync } = useMutation({ + mutationFn: async (serverPort: number) => { + const response = await postServerPort(serverPort); + return response.data; + }, + onMutate: () => { + ontimeQueryClient.cancelQueries({ queryKey: APP_SERVER_PORT }); + }, + onSuccess: (newData) => { + ontimeQueryClient.setQueryData(APP_SERVER_PORT, newData); + }, + }); + + return { data: data ?? serverPortPlaceholder, status, isError, refetch, mutateAsync }; +} diff --git a/apps/client/src/features/app-settings/panel/settings-panel/ServerPortSettings.tsx b/apps/client/src/features/app-settings/panel/settings-panel/ServerPortSettings.tsx index 9ba6efa0c..a2cd068a0 100644 --- a/apps/client/src/features/app-settings/panel/settings-panel/ServerPortSettings.tsx +++ b/apps/client/src/features/app-settings/panel/settings-panel/ServerPortSettings.tsx @@ -1,12 +1,11 @@ -import { PortInfo } from 'ontime-types'; -import { useCallback, useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { useForm } from 'react-hook-form'; -import { getServerPort, postServerPort } from '../../../../common/api/settings'; import { maybeAxiosError } from '../../../../common/api/utils'; import Button from '../../../../common/components/buttons/Button'; import Input from '../../../../common/components/input/input/Input'; import Tag from '../../../../common/components/tag/Tag'; +import useServerPort from '../../../../common/hooks-query/useServerPort'; import { preventEscape } from '../../../../common/utils/keyEvent'; import { isOnlyNumbers } from '../../../../common/utils/regex'; import * as Panel from '../../panel-utils/PanelUtils'; @@ -16,29 +15,22 @@ interface ServerPortForm { } export default function ServerPortSettings() { + const { data, status, isError, refetch, mutateAsync } = useServerPort(); const { handleSubmit, register, reset, setError, + clearErrors, formState: { isSubmitting, isDirty, isValid, errors }, } = useForm({ mode: 'onChange', defaultValues: { serverPort: 4001 }, }); - const [pendingRestart, setPendingRestart] = useState(false); - - const setPort = useCallback((info: PortInfo) => { - reset({ serverPort: info.port }); - setPendingRestart(info.pendingRestart); - }, []); - useEffect(() => { - getServerPort() - .then(setPort) - .catch(() => setError('root', { message: 'Failed to load server port' })); - }, [reset, setError, setPort]); + reset({ serverPort: data.port }); + }, [data.pendingRestart, data.port, reset]); const onSubmit = async (formData: ServerPortForm) => { if (formData.serverPort < 1024 || formData.serverPort > 65535) { @@ -46,21 +38,27 @@ export default function ServerPortSettings() { return; } try { - await postServerPort(formData.serverPort); - setPort(await getServerPort()); + clearErrors('root'); + await mutateAsync(formData.serverPort); } catch (error) { setError('root', { message: maybeAxiosError(error) }); } }; const onReset = async () => { - try { - setPort(await getServerPort()); - } catch (error) { + clearErrors('root'); + const result = await refetch(); + + if (result.isError) { setError('root', { message: 'Failed to load server port' }); + return; } + + reset({ serverPort: result.data?.port ?? data.port }); }; + const rootError = isError ? 'Failed to load server port' : errors.root?.message; + return ( Server port - {pendingRestart && A port change is pending and will happen on the next restart} + {data.pendingRestart && A port change is pending and will happen on the next restart} @@ -88,7 +86,8 @@ export default function ServerPortSettings() { - {errors.root && {errors.root.message}} + + {rootError && {rootError}}