fix: server port settings has lifecycle

This commit is contained in:
Carlos Valente
2026-03-21 20:56:52 +01:00
committed by Carlos Valente
parent ef5c0a371e
commit d8cef11a25
4 changed files with 59 additions and 23 deletions
+1
View File
@@ -3,6 +3,7 @@ import { serverURL } from '../../externals';
// keys in tanstack store // keys in tanstack store
export const APP_INFO = ['appinfo']; export const APP_INFO = ['appinfo'];
export const APP_SETTINGS = ['appSettings']; export const APP_SETTINGS = ['appSettings'];
export const APP_SERVER_PORT = ['appServerPort'];
export const APP_VERSION = ['appVersion']; export const APP_VERSION = ['appVersion'];
export const AUTOMATION = ['automation']; export const AUTOMATION = ['automation'];
export const CUSTOM_FIELDS = ['customFields']; export const CUSTOM_FIELDS = ['customFields'];
+2 -2
View File
@@ -31,8 +31,8 @@ export async function postShowWelcomeDialog(show: boolean) {
/** /**
* HTTP request to retrieve server port * HTTP request to retrieve server port
*/ */
export async function getServerPort(): Promise<PortInfo> { export async function getServerPort(options?: RequestOptions): Promise<PortInfo> {
const res = await axios.get(`${settingsPath}/serverport`); const res = await axios.get(`${settingsPath}/serverport`, { signal: options?.signal });
return res.data; return res.data;
} }
@@ -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<PortInfo>({
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 };
}
@@ -1,12 +1,11 @@
import { PortInfo } from 'ontime-types'; import { useEffect } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { getServerPort, postServerPort } from '../../../../common/api/settings';
import { maybeAxiosError } from '../../../../common/api/utils'; import { maybeAxiosError } from '../../../../common/api/utils';
import Button from '../../../../common/components/buttons/Button'; import Button from '../../../../common/components/buttons/Button';
import Input from '../../../../common/components/input/input/Input'; import Input from '../../../../common/components/input/input/Input';
import Tag from '../../../../common/components/tag/Tag'; import Tag from '../../../../common/components/tag/Tag';
import useServerPort from '../../../../common/hooks-query/useServerPort';
import { preventEscape } from '../../../../common/utils/keyEvent'; import { preventEscape } from '../../../../common/utils/keyEvent';
import { isOnlyNumbers } from '../../../../common/utils/regex'; import { isOnlyNumbers } from '../../../../common/utils/regex';
import * as Panel from '../../panel-utils/PanelUtils'; import * as Panel from '../../panel-utils/PanelUtils';
@@ -16,29 +15,22 @@ interface ServerPortForm {
} }
export default function ServerPortSettings() { export default function ServerPortSettings() {
const { data, status, isError, refetch, mutateAsync } = useServerPort();
const { const {
handleSubmit, handleSubmit,
register, register,
reset, reset,
setError, setError,
clearErrors,
formState: { isSubmitting, isDirty, isValid, errors }, formState: { isSubmitting, isDirty, isValid, errors },
} = useForm<ServerPortForm>({ } = useForm<ServerPortForm>({
mode: 'onChange', mode: 'onChange',
defaultValues: { serverPort: 4001 }, defaultValues: { serverPort: 4001 },
}); });
const [pendingRestart, setPendingRestart] = useState<boolean>(false);
const setPort = useCallback((info: PortInfo) => {
reset({ serverPort: info.port });
setPendingRestart(info.pendingRestart);
}, []);
useEffect(() => { useEffect(() => {
getServerPort() reset({ serverPort: data.port });
.then(setPort) }, [data.pendingRestart, data.port, reset]);
.catch(() => setError('root', { message: 'Failed to load server port' }));
}, [reset, setError, setPort]);
const onSubmit = async (formData: ServerPortForm) => { const onSubmit = async (formData: ServerPortForm) => {
if (formData.serverPort < 1024 || formData.serverPort > 65535) { if (formData.serverPort < 1024 || formData.serverPort > 65535) {
@@ -46,21 +38,27 @@ export default function ServerPortSettings() {
return; return;
} }
try { try {
await postServerPort(formData.serverPort); clearErrors('root');
setPort(await getServerPort()); await mutateAsync(formData.serverPort);
} catch (error) { } catch (error) {
setError('root', { message: maybeAxiosError(error) }); setError('root', { message: maybeAxiosError(error) });
} }
}; };
const onReset = async () => { const onReset = async () => {
try { clearErrors('root');
setPort(await getServerPort()); const result = await refetch();
} catch (error) {
if (result.isError) {
setError('root', { message: 'Failed to load server port' }); 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 ( return (
<Panel.Section <Panel.Section
as='form' as='form'
@@ -72,7 +70,7 @@ export default function ServerPortSettings() {
<Panel.SubHeader> <Panel.SubHeader>
Server port Server port
<Panel.InlineElements> <Panel.InlineElements>
{pendingRestart && <Tag>A port change is pending and will happen on the next restart</Tag>} {data.pendingRestart && <Tag>A port change is pending and will happen on the next restart</Tag>}
<Button disabled={!isDirty || isSubmitting} variant='ghosted' onClick={onReset}> <Button disabled={!isDirty || isSubmitting} variant='ghosted' onClick={onReset}>
Revert to saved Revert to saved
</Button> </Button>
@@ -88,7 +86,8 @@ export default function ServerPortSettings() {
</Button> </Button>
</Panel.InlineElements> </Panel.InlineElements>
</Panel.SubHeader> </Panel.SubHeader>
{errors.root && <Panel.Error>{errors.root.message}</Panel.Error>} <Panel.Loader isLoading={status === 'pending'} />
{rootError && <Panel.Error>{rootError}</Panel.Error>}
<Panel.Divider /> <Panel.Divider />
<Panel.Section> <Panel.Section>
<Panel.ListGroup> <Panel.ListGroup>