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:
commit
ad3f6213c0
@ -5,6 +5,7 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@ -45,8 +46,14 @@ export const UpdateServer = ({
|
||||
const [isUpdateAvailable, setIsUpdateAvailable] = useState(
|
||||
!!updateData?.updateAvailable,
|
||||
);
|
||||
const [customImageUrl, setCustomImageUrl] = useState("");
|
||||
const [isCustomUpdate, setIsCustomUpdate] = useState(false);
|
||||
const { mutateAsync: getUpdateData, isLoading } =
|
||||
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: releaseTag } = api.settings.getReleaseTag.useQuery();
|
||||
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 onOpenChange = (open: boolean) => {
|
||||
setIsOpenInternal(open);
|
||||
@ -141,116 +183,161 @@ export const UpdateServer = ({
|
||||
)}
|
||||
</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 className="mb-8">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Button
|
||||
variant={!isCustomUpdate ? "secondary" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setIsCustomUpdate(false)}
|
||||
>
|
||||
Standard Update
|
||||
</Button>
|
||||
<Button
|
||||
variant={isCustomUpdate ? "secondary" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => setIsCustomUpdate(true)}
|
||||
>
|
||||
Custom Image
|
||||
</Button>
|
||||
</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"
|
||||
{isCustomUpdate ? (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label
|
||||
htmlFor="custom-image-url"
|
||||
className="text-sm font-medium"
|
||||
>
|
||||
release notes
|
||||
</Link>{" "}
|
||||
for any breaking changes before updating.
|
||||
Docker Image URL
|
||||
</label>
|
||||
<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>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Enter the full Docker image URL you want to pull and update to.
|
||||
</p>
|
||||
</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">
|
||||
<ToggleAutoCheckUpdates disabled={isLoading} />
|
||||
<ToggleAutoCheckUpdates disabled={isLoading || isPullingCustom} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 flex items-center justify-end">
|
||||
@ -258,7 +345,29 @@ export const UpdateServer = ({
|
||||
<Button variant="outline" onClick={() => onOpenChange?.(false)}>
|
||||
Cancel
|
||||
</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 />
|
||||
) : (
|
||||
<Button
|
||||
|
@ -47,7 +47,7 @@ export const UpdateWebServer = () => {
|
||||
const handleConfirm = async () => {
|
||||
try {
|
||||
setUpdating(true);
|
||||
await updateServer();
|
||||
await updateServer({ imageUrl: "" });
|
||||
|
||||
// Give some time for docker service restart before starting to check status
|
||||
await new Promise((resolve) => setTimeout(resolve, 8000));
|
||||
|
@ -33,7 +33,8 @@ import {
|
||||
paths,
|
||||
prepareEnvironmentVariables,
|
||||
processLogs,
|
||||
pullLatestRelease,
|
||||
pullCustomImage,
|
||||
pullRelease,
|
||||
readConfig,
|
||||
readConfigInPath,
|
||||
readDirectory,
|
||||
@ -376,26 +377,28 @@ export const settingsRouter = createTRPCRouter({
|
||||
|
||||
return await getUpdateData();
|
||||
}),
|
||||
updateServer: adminProcedure.mutation(async () => {
|
||||
if (IS_CLOUD) {
|
||||
updateServer: adminProcedure
|
||||
.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;
|
||||
}
|
||||
|
||||
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(() => {
|
||||
return packageInfo.version;
|
||||
@ -837,6 +840,15 @@ export const settingsRouter = createTRPCRouter({
|
||||
getLogCleanupStatus: adminProcedure.query(async () => {
|
||||
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) => {
|
||||
|
@ -21,12 +21,13 @@ export const getDokployImageTag = () => {
|
||||
return process.env.RELEASE_TAG || "latest";
|
||||
};
|
||||
|
||||
export const getDokployImage = () => {
|
||||
return `dokploy/dokploy:${getDokployImageTag()}`;
|
||||
export const getDokployImage = (customImage?: string) => {
|
||||
return customImage || `dokploy/dokploy:${getDokployImageTag()}`;
|
||||
};
|
||||
|
||||
export const pullLatestRelease = async () => {
|
||||
const stream = await docker.pull(getDokployImage());
|
||||
export const pullRelease = async (customImage?: string) => {
|
||||
const imageToPull = getDokployImage(customImage);
|
||||
const stream = await docker.pull(imageToPull);
|
||||
await new Promise((resolve, reject) => {
|
||||
docker.modem.followProgress(stream, (err, 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 */
|
||||
export const getServiceImageDigest = async () => {
|
||||
const { stdout } = await execAsync(
|
||||
|
Loading…
Reference in New Issue
Block a user