mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge 6642941f62 into 2a89be6efc
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -45,8 +46,14 @@ export const UpdateServer = ({
|
|||||||
const [isUpdateAvailable, setIsUpdateAvailable] = useState(
|
const [isUpdateAvailable, setIsUpdateAvailable] = useState(
|
||||||
!!updateData?.updateAvailable,
|
!!updateData?.updateAvailable,
|
||||||
);
|
);
|
||||||
|
const [customImageUrl, setCustomImageUrl] = useState("");
|
||||||
|
const [isCustomUpdate, setIsCustomUpdate] = useState(false);
|
||||||
const { mutateAsync: getUpdateData, isLoading } =
|
const { mutateAsync: getUpdateData, isLoading } =
|
||||||
api.settings.getUpdateData.useMutation();
|
api.settings.getUpdateData.useMutation();
|
||||||
|
const { mutateAsync: pullCustomImage, isLoading: isPullingCustom } =
|
||||||
|
api.settings.pullCustomImage.useMutation();
|
||||||
|
const { mutateAsync: updateServer, isLoading: isUpdatingServer } =
|
||||||
|
api.settings.updateServer.useMutation();
|
||||||
const { data: dokployVersion } = api.settings.getDokployVersion.useQuery();
|
const { data: dokployVersion } = api.settings.getDokployVersion.useQuery();
|
||||||
const { data: releaseTag } = api.settings.getReleaseTag.useQuery();
|
const { data: releaseTag } = api.settings.getReleaseTag.useQuery();
|
||||||
const [latestVersion, setLatestVersion] = useState(
|
const [latestVersion, setLatestVersion] = useState(
|
||||||
@@ -79,6 +86,41 @@ export const UpdateServer = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCustomUpdate = async () => {
|
||||||
|
if (!customImageUrl) {
|
||||||
|
toast.error("Please enter a valid Docker image URL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const success = await pullCustomImage({
|
||||||
|
imageUrl: customImageUrl,
|
||||||
|
});
|
||||||
|
if (success) {
|
||||||
|
toast.success("Custom image pulled successfully");
|
||||||
|
|
||||||
|
// Update the Docker service to use the new image
|
||||||
|
await updateServer({ imageUrl: customImageUrl });
|
||||||
|
toast.success("Server updated successfully");
|
||||||
|
onOpenChange?.(false);
|
||||||
|
} else {
|
||||||
|
toast.error("Failed to pull custom image");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating server:", error);
|
||||||
|
const errorMessage =
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
.split("manifest unknown: manifest unknown")[0]
|
||||||
|
?.trim() || "Failed to pull image"
|
||||||
|
: "An error occurred while updating the server";
|
||||||
|
|
||||||
|
toast.error(errorMessage, {
|
||||||
|
description: "Please check if the image URL is correct and accessible",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const isOpen = isOpenInternal || isOpenProp;
|
const isOpen = isOpenInternal || isOpenProp;
|
||||||
const onOpenChange = (open: boolean) => {
|
const onOpenChange = (open: boolean) => {
|
||||||
setIsOpenInternal(open);
|
setIsOpenInternal(open);
|
||||||
@@ -141,116 +183,161 @@ export const UpdateServer = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Initial state */}
|
<div className="mb-8">
|
||||||
{!hasCheckedUpdate && (
|
<div className="flex items-center gap-2 mb-4">
|
||||||
<div className="mb-8">
|
<Button
|
||||||
<p className="text text-muted-foreground">
|
variant={!isCustomUpdate ? "secondary" : "outline"}
|
||||||
Check for new releases and update Dokploy.
|
size="sm"
|
||||||
<br />
|
onClick={() => setIsCustomUpdate(false)}
|
||||||
<br />
|
>
|
||||||
We recommend checking for updates regularly to ensure you have the
|
Standard Update
|
||||||
latest features and security improvements.
|
</Button>
|
||||||
</p>
|
<Button
|
||||||
|
variant={isCustomUpdate ? "secondary" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setIsCustomUpdate(true)}
|
||||||
|
>
|
||||||
|
Custom Image
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Update available state */}
|
{isCustomUpdate ? (
|
||||||
{isUpdateAvailable && latestVersion && (
|
<div className="space-y-4">
|
||||||
<div className="mb-8">
|
<div className="space-y-2">
|
||||||
<div className="inline-flex items-center gap-2 rounded-lg px-3 py-2 border border-emerald-900 bg-emerald-900 dark:bg-emerald-900/40 mb-4 w-full">
|
<label
|
||||||
<div className="flex items-center gap-1.5">
|
htmlFor="custom-image-url"
|
||||||
<Download className="h-4 w-4 text-emerald-400" />
|
className="text-sm font-medium"
|
||||||
<span className="text font-medium text-emerald-400 ">
|
|
||||||
New version available:
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<span className="text font-semibold text-emerald-300">
|
|
||||||
{latestVersion}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-4 text-muted-foreground">
|
|
||||||
<p className="text">
|
|
||||||
A new version of the server software is available. Consider
|
|
||||||
updating if you:
|
|
||||||
</p>
|
|
||||||
<ul className="space-y-3">
|
|
||||||
<li className="flex items-start gap-2">
|
|
||||||
<Stars className="h-5 w-5 mt-0.5 text-[#5B9DFF]" />
|
|
||||||
<span className="text">
|
|
||||||
Want to access the latest features and improvements
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li className="flex items-start gap-2">
|
|
||||||
<Bug className="h-5 w-5 mt-0.5 text-[#5B9DFF]" />
|
|
||||||
<span className="text">
|
|
||||||
Are experiencing issues that may be resolved in the new
|
|
||||||
version
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Up to date state */}
|
|
||||||
{hasCheckedUpdate && !isUpdateAvailable && !isLoading && (
|
|
||||||
<div className="mb-8">
|
|
||||||
<div className="flex flex-col items-center gap-6 mb-6">
|
|
||||||
<div className="rounded-full p-4 bg-emerald-400/40">
|
|
||||||
<Sparkles className="h-8 w-8 text-emerald-400" />
|
|
||||||
</div>
|
|
||||||
<div className="text-center space-y-2">
|
|
||||||
<h3 className="text-lg font-medium">
|
|
||||||
You are using the latest version
|
|
||||||
</h3>
|
|
||||||
<p className="text text-muted-foreground">
|
|
||||||
Your server is up to date with all the latest features and
|
|
||||||
security improvements.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{hasCheckedUpdate && isLoading && (
|
|
||||||
<div className="mb-8">
|
|
||||||
<div className="flex flex-col items-center gap-6 mb-6">
|
|
||||||
<div className="rounded-full p-4 bg-[#5B9DFF]/40 text-foreground">
|
|
||||||
<RefreshCcw className="h-8 w-8 animate-spin" />
|
|
||||||
</div>
|
|
||||||
<div className="text-center space-y-2">
|
|
||||||
<h3 className="text-lg font-medium">Checking for updates...</h3>
|
|
||||||
<p className="text text-muted-foreground">
|
|
||||||
Please wait while we pull the latest version information from
|
|
||||||
Docker Hub.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isUpdateAvailable && (
|
|
||||||
<div className="rounded-lg bg-[#16254D] p-4 mb-8">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Info className="h-5 w-5 flex-shrink-0 text-[#5B9DFF]" />
|
|
||||||
<div className="text-[#5B9DFF]">
|
|
||||||
We recommend reviewing the{" "}
|
|
||||||
<Link
|
|
||||||
href="https://github.com/Dokploy/dokploy/releases"
|
|
||||||
target="_blank"
|
|
||||||
className="text-white underline hover:text-zinc-200"
|
|
||||||
>
|
>
|
||||||
release notes
|
Docker Image URL
|
||||||
</Link>{" "}
|
</label>
|
||||||
for any breaking changes before updating.
|
<Input
|
||||||
|
id="custom-image-url"
|
||||||
|
placeholder="e.g., dokploy/dokploy:v1.0.0 or custom-registry.com/image:tag"
|
||||||
|
value={customImageUrl}
|
||||||
|
onChange={(e) => setCustomImageUrl(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Enter the full Docker image URL you want to pull and update to.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
)}
|
<>
|
||||||
|
{/* Initial state */}
|
||||||
|
{!hasCheckedUpdate && (
|
||||||
|
<div className="mb-8">
|
||||||
|
<p className="text text-muted-foreground">
|
||||||
|
Check for new releases and update Dokploy.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
We recommend checking for updates regularly to ensure you
|
||||||
|
have the latest features and security improvements.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Update available state */}
|
||||||
|
{isUpdateAvailable && latestVersion && (
|
||||||
|
<div className="mb-8">
|
||||||
|
<div className="inline-flex items-center gap-2 rounded-lg px-3 py-2 border border-emerald-900 bg-emerald-900 dark:bg-emerald-900/40 mb-4 w-full">
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<Download className="h-4 w-4 text-emerald-400" />
|
||||||
|
<span className="text font-medium text-emerald-400 ">
|
||||||
|
New version available:
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span className="text font-semibold text-emerald-300">
|
||||||
|
{latestVersion}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4 text-muted-foreground">
|
||||||
|
<p className="text">
|
||||||
|
A new version of the server software is available.
|
||||||
|
Consider updating if you:
|
||||||
|
</p>
|
||||||
|
<ul className="space-y-3">
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<Stars className="h-5 w-5 mt-0.5 text-[#5B9DFF]" />
|
||||||
|
<span className="text">
|
||||||
|
Want to access the latest features and improvements
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<Bug className="h-5 w-5 mt-0.5 text-[#5B9DFF]" />
|
||||||
|
<span className="text">
|
||||||
|
Are experiencing issues that may be resolved in the
|
||||||
|
new version
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Up to date state */}
|
||||||
|
{hasCheckedUpdate && !isUpdateAvailable && !isLoading && (
|
||||||
|
<div className="mb-8">
|
||||||
|
<div className="flex flex-col items-center gap-6 mb-6">
|
||||||
|
<div className="rounded-full p-4 bg-emerald-400/40">
|
||||||
|
<Sparkles className="h-8 w-8 text-emerald-400" />
|
||||||
|
</div>
|
||||||
|
<div className="text-center space-y-2">
|
||||||
|
<h3 className="text-lg font-medium">
|
||||||
|
You are using the latest version
|
||||||
|
</h3>
|
||||||
|
<p className="text text-muted-foreground">
|
||||||
|
Your server is up to date with all the latest features
|
||||||
|
and security improvements.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasCheckedUpdate && isLoading && (
|
||||||
|
<div className="mb-8">
|
||||||
|
<div className="flex flex-col items-center gap-6 mb-6">
|
||||||
|
<div className="rounded-full p-4 bg-[#5B9DFF]/40 text-foreground">
|
||||||
|
<RefreshCcw className="h-8 w-8 animate-spin" />
|
||||||
|
</div>
|
||||||
|
<div className="text-center space-y-2">
|
||||||
|
<h3 className="text-lg font-medium">
|
||||||
|
Checking for updates...
|
||||||
|
</h3>
|
||||||
|
<p className="text text-muted-foreground">
|
||||||
|
Please wait while we pull the latest version information
|
||||||
|
from Docker Hub.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isUpdateAvailable && (
|
||||||
|
<div className="rounded-lg bg-[#16254D] p-4 mb-8">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Info className="h-5 w-5 flex-shrink-0 text-[#5B9DFF]" />
|
||||||
|
<div className="text-[#5B9DFF]">
|
||||||
|
We recommend reviewing the{" "}
|
||||||
|
<Link
|
||||||
|
href="https://github.com/Dokploy/dokploy/releases"
|
||||||
|
target="_blank"
|
||||||
|
className="text-white underline hover:text-zinc-200"
|
||||||
|
>
|
||||||
|
release notes
|
||||||
|
</Link>{" "}
|
||||||
|
for any breaking changes before updating.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between pt-2">
|
<div className="flex items-center justify-between pt-2">
|
||||||
<ToggleAutoCheckUpdates disabled={isLoading} />
|
<ToggleAutoCheckUpdates disabled={isLoading || isPullingCustom} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4 flex items-center justify-end">
|
<div className="space-y-4 flex items-center justify-end">
|
||||||
@@ -258,7 +345,29 @@ export const UpdateServer = ({
|
|||||||
<Button variant="outline" onClick={() => onOpenChange?.(false)}>
|
<Button variant="outline" onClick={() => onOpenChange?.(false)}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
{isUpdateAvailable ? (
|
{isCustomUpdate ? (
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={handleCustomUpdate}
|
||||||
|
disabled={
|
||||||
|
isPullingCustom || isUpdatingServer || !customImageUrl
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{isPullingCustom || isUpdatingServer ? (
|
||||||
|
<>
|
||||||
|
<RefreshCcw className="h-4 w-4 animate-spin" />
|
||||||
|
{isPullingCustom
|
||||||
|
? "Pulling Image..."
|
||||||
|
: "Updating Server..."}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Download className="h-4 w-4" />
|
||||||
|
Pull & Update Server
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
) : isUpdateAvailable ? (
|
||||||
<UpdateWebServer />
|
<UpdateWebServer />
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const UpdateWebServer = () => {
|
|||||||
const handleConfirm = async () => {
|
const handleConfirm = async () => {
|
||||||
try {
|
try {
|
||||||
setUpdating(true);
|
setUpdating(true);
|
||||||
await updateServer();
|
await updateServer({ imageUrl: "" });
|
||||||
|
|
||||||
// Give some time for docker service restart before starting to check status
|
// Give some time for docker service restart before starting to check status
|
||||||
await new Promise((resolve) => setTimeout(resolve, 8000));
|
await new Promise((resolve) => setTimeout(resolve, 8000));
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ import {
|
|||||||
paths,
|
paths,
|
||||||
prepareEnvironmentVariables,
|
prepareEnvironmentVariables,
|
||||||
processLogs,
|
processLogs,
|
||||||
pullLatestRelease,
|
pullCustomImage,
|
||||||
|
pullRelease,
|
||||||
readConfig,
|
readConfig,
|
||||||
readConfigInPath,
|
readConfigInPath,
|
||||||
readDirectory,
|
readDirectory,
|
||||||
@@ -376,26 +377,28 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return await getUpdateData();
|
return await getUpdateData();
|
||||||
}),
|
}),
|
||||||
updateServer: adminProcedure.mutation(async () => {
|
updateServer: adminProcedure
|
||||||
if (IS_CLOUD) {
|
.input(z.object({ imageUrl: z.string().optional() }))
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
if (IS_CLOUD) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await pullRelease(input.imageUrl);
|
||||||
|
|
||||||
|
// 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",
|
||||||
|
"--image",
|
||||||
|
getDokployImage(input.imageUrl),
|
||||||
|
"dokploy",
|
||||||
|
]);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}),
|
||||||
|
|
||||||
await pullLatestRelease();
|
|
||||||
|
|
||||||
// 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",
|
|
||||||
"--image",
|
|
||||||
getDokployImage(),
|
|
||||||
"dokploy",
|
|
||||||
]);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
|
|
||||||
getDokployVersion: protectedProcedure.query(() => {
|
getDokployVersion: protectedProcedure.query(() => {
|
||||||
return packageInfo.version;
|
return packageInfo.version;
|
||||||
@@ -837,6 +840,15 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
getLogCleanupStatus: adminProcedure.query(async () => {
|
getLogCleanupStatus: adminProcedure.query(async () => {
|
||||||
return getLogCleanupStatus();
|
return getLogCleanupStatus();
|
||||||
}),
|
}),
|
||||||
|
pullCustomImage: adminProcedure
|
||||||
|
.input(z.object({ imageUrl: z.string() }))
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
if (IS_CLOUD) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
console.log("pulling custom image", input.imageUrl);
|
||||||
|
return await pullCustomImage(input.imageUrl);
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getTraefikPorts = async (serverId?: string) => {
|
export const getTraefikPorts = async (serverId?: string) => {
|
||||||
|
|||||||
@@ -21,12 +21,13 @@ export const getDokployImageTag = () => {
|
|||||||
return process.env.RELEASE_TAG || "latest";
|
return process.env.RELEASE_TAG || "latest";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDokployImage = () => {
|
export const getDokployImage = (customImage?: string) => {
|
||||||
return `dokploy/dokploy:${getDokployImageTag()}`;
|
return customImage || `dokploy/dokploy:${getDokployImageTag()}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pullLatestRelease = async () => {
|
export const pullRelease = async (customImage?: string) => {
|
||||||
const stream = await docker.pull(getDokployImage());
|
const imageToPull = getDokployImage(customImage);
|
||||||
|
const stream = await docker.pull(imageToPull);
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
docker.modem.followProgress(stream, (err, res) =>
|
docker.modem.followProgress(stream, (err, res) =>
|
||||||
err ? reject(err) : resolve(res),
|
err ? reject(err) : resolve(res),
|
||||||
@@ -34,6 +35,17 @@ export const pullLatestRelease = async () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Pulls a custom Docker image and returns whether it was successful */
|
||||||
|
export const pullCustomImage = async (imageUrl: string): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
await pullRelease(imageUrl);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error pulling custom image:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/** Returns Dokploy docker service image digest */
|
/** Returns Dokploy docker service image digest */
|
||||||
export const getServiceImageDigest = async () => {
|
export const getServiceImageDigest = async () => {
|
||||||
const { stdout } = await execAsync(
|
const { stdout } = await execAsync(
|
||||||
|
|||||||
Reference in New Issue
Block a user