From ed5936ede7c7dae12d742c18ff9bce57198613d7 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Tue, 31 Dec 2024 11:11:33 +0100 Subject: [PATCH 1/4] feat: add health endpoint for frontend app --- apps/dokploy/pages/api/health.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 apps/dokploy/pages/api/health.ts diff --git a/apps/dokploy/pages/api/health.ts b/apps/dokploy/pages/api/health.ts new file mode 100644 index 000000000..9dc8101e5 --- /dev/null +++ b/apps/dokploy/pages/api/health.ts @@ -0,0 +1,8 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + return res.status(200).json({ ok: true }); +} From 29a8cb63c056155bc1d04a233cd3b6a4ec8b4963 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Tue, 31 Dec 2024 11:13:58 +0100 Subject: [PATCH 2/4] refactor: pass update data and use updates modal in navbar --- apps/dokploy/components/layouts/navbar.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/dokploy/components/layouts/navbar.tsx b/apps/dokploy/components/layouts/navbar.tsx index 0e52d7019..bf733bd23 100644 --- a/apps/dokploy/components/layouts/navbar.tsx +++ b/apps/dokploy/components/layouts/navbar.tsx @@ -13,15 +13,19 @@ import { HeartIcon } from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/router"; import { useEffect, useRef, useState } from "react"; -import { UpdateWebServer } from "../dashboard/settings/web-server/update-webserver"; import { Logo } from "../shared/logo"; import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar"; import { buttonVariants } from "../ui/button"; +import UpdateServer from "../dashboard/settings/web-server/update-server"; +import type { IUpdateData } from "@dokploy/server/index"; const AUTO_CHECK_UPDATES_INTERVAL_MINUTES = 7; export const Navbar = () => { - const [isUpdateAvailable, setIsUpdateAvailable] = useState(false); + const [updateData, setUpdateData] = useState({ + latestVersion: null, + updateAvailable: false, + }); const router = useRouter(); const { data } = api.auth.get.useQuery(); const { data: isCloud } = api.settings.isCloud.useQuery(); @@ -62,12 +66,12 @@ export const Navbar = () => { return; } - const { updateAvailable } = await getUpdateData(); + const fetchedUpdateData = await getUpdateData(); - if (updateAvailable) { + if (fetchedUpdateData?.updateAvailable) { // Stop interval when update is available clearUpdatesInterval(); - setIsUpdateAvailable(true); + setUpdateData(fetchedUpdateData); } } catch (error) { console.error("Error auto-checking for updates:", error); @@ -101,9 +105,9 @@ export const Navbar = () => { - {isUpdateAvailable && ( + {updateData.updateAvailable && (
- +
)} Date: Tue, 31 Dec 2024 11:19:22 +0100 Subject: [PATCH 3/4] refactor: allow using already fetched updateData, remove ping icon --- .../settings/web-server/update-server.tsx | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx b/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx index 6c8475d30..dad04ab03 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/update-server.tsx @@ -22,16 +22,25 @@ import { useState } from "react"; import { toast } from "sonner"; import { ToggleAutoCheckUpdates } from "./toggle-auto-check-updates"; import { UpdateWebServer } from "./update-webserver"; +import type { IUpdateData } from "@dokploy/server/index"; -export const UpdateServer = () => { - const [hasCheckedUpdate, setHasCheckedUpdate] = useState(false); - const [isUpdateAvailable, setIsUpdateAvailable] = useState(false); +interface Props { + updateData?: IUpdateData; +} + +export const UpdateServer = ({ updateData }: Props) => { + const [hasCheckedUpdate, setHasCheckedUpdate] = useState(!!updateData); + const [isUpdateAvailable, setIsUpdateAvailable] = useState( + !!updateData?.updateAvailable, + ); const { mutateAsync: getUpdateData, isLoading } = api.settings.getUpdateData.useMutation(); const { data: dokployVersion } = api.settings.getDokployVersion.useQuery(); const { data: releaseTag } = api.settings.getReleaseTag.useQuery(); const [isOpen, setIsOpen] = useState(false); - const [latestVersion, setLatestVersion] = useState(""); + const [latestVersion, setLatestVersion] = useState( + updateData?.latestVersion ?? "", + ); const handleCheckUpdates = async () => { try { @@ -61,9 +70,24 @@ export const UpdateServer = () => { return ( - @@ -99,10 +123,6 @@ export const UpdateServer = () => {
- - - - New version available: From 187f051484ab3c51d6d19647713e49aa7e267adf Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Tue, 31 Dec 2024 11:20:59 +0100 Subject: [PATCH 4/4] fix: check health endpoint instead of awaiting service restart to fix update success status --- .../settings/web-server/update-webserver.tsx | 97 +++++++++++++------ apps/dokploy/server/api/routers/settings.ts | 4 +- 2 files changed, 71 insertions(+), 30 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/web-server/update-webserver.tsx b/apps/dokploy/components/dashboard/settings/web-server/update-webserver.tsx index c1e5de706..537a1adde 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/update-webserver.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/update-webserver.tsx @@ -11,30 +11,50 @@ import { } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { api } from "@/utils/api"; -import { HardDriveDownload } from "lucide-react"; +import { HardDriveDownload, Loader2 } from "lucide-react"; +import { useState } from "react"; import { toast } from "sonner"; -interface Props { - isNavbar?: boolean; -} +export const UpdateWebServer = () => { + const [updating, setUpdating] = useState(false); + const [open, setOpen] = useState(false); -export const UpdateWebServer = ({ isNavbar }: Props) => { - const { mutateAsync: updateServer, isLoading } = - api.settings.updateServer.useMutation(); + const { mutateAsync: updateServer } = api.settings.updateServer.useMutation(); - const buttonLabel = isNavbar ? "Update available" : "Update Server"; - - const handleConfirm = async () => { + const checkIsUpdateFinished = async () => { try { - await updateServer(); + const response = await fetch("/api/health"); + if (!response.ok) { + throw new Error("Health check failed"); + } + toast.success( "The server has been updated. The page will be reloaded to reflect the changes...", ); + setTimeout(() => { // Allow seeing the toast before reloading window.location.reload(); }, 2000); + } catch { + // Delay each request + await new Promise((resolve) => setTimeout(resolve, 2000)); + // Keep running until it returns 200 + void checkIsUpdateFinished(); + } + }; + + const handleConfirm = async () => { + try { + setUpdating(true); + await updateServer(); + + // Give some time for docker service restart before starting to check status + await new Promise((resolve) => setTimeout(resolve, 8000)); + + await checkIsUpdateFinished(); } catch (error) { + setUpdating(false); console.error("Error updating server:", error); toast.error( "An error occurred while updating the server, please try again.", @@ -43,35 +63,54 @@ export const UpdateWebServer = ({ isNavbar }: Props) => { }; return ( - + - Are you absolutely sure? + + {updating + ? "Server update in progress" + : "Are you absolutely sure?"} + - This action cannot be undone. This will update the web server to the - new version. The page will be reloaded once the update is finished. + {updating ? ( + + + The server is being updated, please wait... + + ) : ( + <> + This action cannot be undone. This will update the web server to + the new version. You will not be able to use the panel during + the update process. The page will be reloaded once the update is + finished. + + )} - - Cancel - Confirm - + {!updating && ( + + setOpen(false)}> + Cancel + + + Confirm + + + )} ); diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts index b40ee95bd..449a22333 100644 --- a/apps/dokploy/server/api/routers/settings.ts +++ b/apps/dokploy/server/api/routers/settings.ts @@ -359,7 +359,9 @@ export const settingsRouter = createTRPCRouter({ await pullLatestRelease(); - await spawnAsync("docker", [ + // This causes restart of dokploy, thus it will not finish executing properly, so don't await it + // Status after restart is checked via frontend /api/health endpoint + void spawnAsync("docker", [ "service", "update", "--force",