From 91d63652759ec4915552502d5841c93f3a4ba13e Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Fri, 6 Feb 2026 23:24:38 -0600 Subject: [PATCH 1/2] feat(health-check): implement health check hook for post-mutation validation - Introduced `useHealthCheckAfterMutation` hook to perform health checks after mutations, ensuring services are ready before proceeding. - Updated `ShowTraefikActions`, `EditTraefikEnv`, and `ManageTraefikPorts` components to utilize the new hook, enhancing user feedback and error handling during updates. - Improved loading states by incorporating health check execution status into button states. --- .../servers/actions/show-traefik-actions.tsx | 55 +++++++---- .../settings/web-server/edit-traefik-env.tsx | 31 +++--- .../web-server/manage-traefik-ports.tsx | 22 +++-- .../hooks/use-health-check-after-mutation.ts | 98 +++++++++++++++++++ 4 files changed, 168 insertions(+), 38 deletions(-) create mode 100644 apps/dokploy/hooks/use-health-check-after-mutation.ts diff --git a/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx b/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx index aebba8877..36761973c 100644 --- a/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/actions/show-traefik-actions.tsx @@ -12,6 +12,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { useHealthCheckAfterMutation } from "@/hooks/use-health-check-after-mutation"; import { api } from "@/utils/api"; import { EditTraefikEnv } from "../../web-server/edit-traefik-env"; import { ManageTraefikPorts } from "../../web-server/manage-traefik-ports"; @@ -33,14 +34,33 @@ export const ShowTraefikActions = ({ serverId }: Props) => { serverId, }); + const { + execute: executeWithHealthCheck, + isExecuting: isHealthCheckExecuting, + } = useHealthCheckAfterMutation({ + initialDelay: 5000, + successMessage: "Traefik dashboard updated successfully", + onSuccess: () => { + refetchDashboard(); + }, + }); + return ( diff --git a/apps/dokploy/hooks/use-health-check-after-mutation.ts b/apps/dokploy/hooks/use-health-check-after-mutation.ts new file mode 100644 index 000000000..0fc03025c --- /dev/null +++ b/apps/dokploy/hooks/use-health-check-after-mutation.ts @@ -0,0 +1,98 @@ +import { useCallback, useState } from "react"; +import { toast } from "sonner"; + +const HEALTH_CHECK_URL = "/api/health"; + +export interface UseHealthCheckAfterMutationOptions { + /** + * Delay in ms before starting to poll the health endpoint. + * Gives time for the service (e.g. Traefik) to restart. + * @default 5000 + */ + initialDelay?: number; + /** + * Delay in ms between each health check poll. + * @default 2000 + */ + pollInterval?: number; + /** + * Message shown in toast when the operation completes successfully. + */ + successMessage: string; + /** + * Callback when health check passes. Use for refetching data. + */ + onSuccess?: () => void | Promise; + /** + * If true, reloads the page when health check passes (e.g. for server update). + * @default false + */ + reloadOnSuccess?: boolean; +} + +export const useHealthCheckAfterMutation = ({ + initialDelay = 5000, + pollInterval = 2000, + successMessage, + onSuccess, + reloadOnSuccess = false, +}: UseHealthCheckAfterMutationOptions) => { + const [isExecuting, setIsExecuting] = useState(false); + + const checkHealth = useCallback(async (): Promise => { + try { + const response = await fetch(HEALTH_CHECK_URL); + return response.ok; + } catch { + return false; + } + }, []); + + const pollUntilHealthy = useCallback(async (): Promise => { + const isHealthy = await checkHealth(); + + if (isHealthy) { + toast.success(successMessage); + + if (reloadOnSuccess) { + setTimeout(() => { + window.location.reload(); + }, 2000); + } else { + await onSuccess?.(); + } + return; + } + + await new Promise((resolve) => setTimeout(resolve, pollInterval)); + await pollUntilHealthy(); + }, [ + checkHealth, + successMessage, + reloadOnSuccess, + onSuccess, + pollInterval, + ]); + + const execute = useCallback( + async (mutationFn: () => Promise): Promise => { + setIsExecuting(true); + + try { + const result = await mutationFn(); + + // Give time for the service to restart before polling + await new Promise((resolve) => setTimeout(resolve, initialDelay)); + + await pollUntilHealthy(); + + return result; + } finally { + setIsExecuting(false); + } + }, + [initialDelay, pollUntilHealthy], + ); + + return { execute, isExecuting }; +}; From c68525aa597242c8259b93e9bcda27bd1ef5304e Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sat, 7 Feb 2026 05:25:34 +0000 Subject: [PATCH 2/2] [autofix.ci] apply automated fixes --- .../settings/web-server/edit-traefik-env.tsx | 12 ++++++----- .../web-server/manage-traefik-ports.tsx | 20 ++++++++++--------- .../hooks/use-health-check-after-mutation.ts | 8 +------- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx b/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx index 0fd136ea4..482b98579 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx @@ -47,11 +47,13 @@ export const EditTraefikEnv = ({ children, serverId }: Props) => { const { mutateAsync, isLoading, error, isError } = api.settings.writeTraefikEnv.useMutation(); - const { execute: executeWithHealthCheck, isExecuting: isHealthCheckExecuting } = - useHealthCheckAfterMutation({ - initialDelay: 5000, - successMessage: "Traefik Env Updated", - }); + const { + execute: executeWithHealthCheck, + isExecuting: isHealthCheckExecuting, + } = useHealthCheckAfterMutation({ + initialDelay: 5000, + successMessage: "Traefik Env Updated", + }); const form = useForm({ defaultValues: { diff --git a/apps/dokploy/components/dashboard/settings/web-server/manage-traefik-ports.tsx b/apps/dokploy/components/dashboard/settings/web-server/manage-traefik-ports.tsx index e38be72d6..73973ef06 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/manage-traefik-ports.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/manage-traefik-ports.tsx @@ -79,15 +79,17 @@ export const ManageTraefikPorts = ({ children, serverId }: Props) => { const { mutateAsync: updatePorts, isLoading } = api.settings.updateTraefikPorts.useMutation(); - const { execute: executeWithHealthCheck, isExecuting: isHealthCheckExecuting } = - useHealthCheckAfterMutation({ - initialDelay: 5000, - successMessage: t("settings.server.webServer.traefik.portsUpdated"), - onSuccess: () => { - refetchPorts(); - setOpen(false); - }, - }); + const { + execute: executeWithHealthCheck, + isExecuting: isHealthCheckExecuting, + } = useHealthCheckAfterMutation({ + initialDelay: 5000, + successMessage: t("settings.server.webServer.traefik.portsUpdated"), + onSuccess: () => { + refetchPorts(); + setOpen(false); + }, + }); useEffect(() => { if (currentPorts) { diff --git a/apps/dokploy/hooks/use-health-check-after-mutation.ts b/apps/dokploy/hooks/use-health-check-after-mutation.ts index 0fc03025c..e2df72720 100644 --- a/apps/dokploy/hooks/use-health-check-after-mutation.ts +++ b/apps/dokploy/hooks/use-health-check-after-mutation.ts @@ -66,13 +66,7 @@ export const useHealthCheckAfterMutation = ({ await new Promise((resolve) => setTimeout(resolve, pollInterval)); await pollUntilHealthy(); - }, [ - checkHealth, - successMessage, - reloadOnSuccess, - onSuccess, - pollInterval, - ]); + }, [checkHealth, successMessage, reloadOnSuccess, onSuccess, pollInterval]); const execute = useCallback( async (mutationFn: () => Promise): Promise => {