Skip to content

Commit 99cb807

Browse files
authored
Merge pull request #3152 from Dokploy/feat/improve-rollbacks
Feat/improve rollbacks
2 parents 35612b2 + 7467ada commit 99cb807

File tree

15 files changed

+7109
-114
lines changed

15 files changed

+7109
-114
lines changed

apps/dokploy/__test__/deploy/application.command.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ describe("deployApplication - Command Generation Tests", () => {
189189

190190
it("should verify nixpacks command is called with correct app", async () => {
191191
const mockNixpacksCommand = "nixpacks build /path/to/app --name test-app";
192-
vi.mocked(builders.getBuildCommand).mockReturnValue(mockNixpacksCommand);
192+
vi.mocked(builders.getBuildCommand).mockResolvedValue(mockNixpacksCommand);
193193

194194
await deployApplication({
195195
applicationId: "test-app-id",
@@ -220,7 +220,7 @@ describe("deployApplication - Command Generation Tests", () => {
220220
);
221221

222222
const mockRailpackCommand = "railpack prepare /path/to/app";
223-
vi.mocked(builders.getBuildCommand).mockReturnValue(mockRailpackCommand);
223+
vi.mocked(builders.getBuildCommand).mockResolvedValue(mockRailpackCommand);
224224

225225
await deployApplication({
226226
applicationId: "test-app-id",
@@ -241,7 +241,7 @@ describe("deployApplication - Command Generation Tests", () => {
241241

242242
it("should execute commands in correct order", async () => {
243243
const mockNixpacksCommand = "nixpacks build";
244-
vi.mocked(builders.getBuildCommand).mockReturnValue(mockNixpacksCommand);
244+
vi.mocked(builders.getBuildCommand).mockResolvedValue(mockNixpacksCommand);
245245

246246
await deployApplication({
247247
applicationId: "test-app-id",
@@ -260,7 +260,7 @@ describe("deployApplication - Command Generation Tests", () => {
260260

261261
it("should include log redirection in command", async () => {
262262
const mockCommand = "nixpacks build";
263-
vi.mocked(builders.getBuildCommand).mockReturnValue(mockCommand);
263+
vi.mocked(builders.getBuildCommand).mockResolvedValue(mockCommand);
264264

265265
await deployApplication({
266266
applicationId: "test-app-id",

apps/dokploy/__test__/drop/drop.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ const baseApp: ApplicationNested = {
4141
giteaRepository: "",
4242
cleanCache: false,
4343
watchPaths: [],
44+
rollbackRegistryId: "",
45+
rollbackRegistry: null,
46+
deployments: [],
4447
enableSubmodules: false,
4548
applicationStatus: "done",
4649
triggerType: "push",

apps/dokploy/__test__/traefik/traefik.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ const baseApp: ApplicationNested = {
1717
giteaBuildPath: "",
1818
giteaId: "",
1919
args: [],
20+
rollbackRegistryId: "",
21+
rollbackRegistry: null,
22+
deployments: [],
2023
cleanCache: false,
2124
applicationStatus: "done",
2225
endpointSpecSwarm: null,

apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,19 @@ export const ShowDeployments = ({
373373
type === "application" && (
374374
<DialogAction
375375
title="Rollback to this deployment"
376-
description="Are you sure you want to rollback to this deployment?"
376+
description={
377+
<div className="flex flex-col gap-3">
378+
<p>
379+
Are you sure you want to rollback to this
380+
deployment?
381+
</p>
382+
<AlertBlock type="info" className="text-sm">
383+
Please wait a few seconds while the image is
384+
pulled from the registry. Your application
385+
should be running shortly.
386+
</AlertBlock>
387+
</div>
388+
}
377389
type="default"
378390
onClick={async () => {
379391
await rollback({

apps/dokploy/components/dashboard/application/rollbacks/show-rollback-settings.tsx

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { zodResolver } from "@hookform/resolvers/zod";
2-
import { useState } from "react";
2+
import Link from "next/link";
3+
import { useEffect, useState } from "react";
34
import { useForm } from "react-hook-form";
45
import { toast } from "sonner";
56
import { z } from "zod";
@@ -20,13 +21,37 @@ import {
2021
FormField,
2122
FormItem,
2223
FormLabel,
24+
FormMessage,
2325
} from "@/components/ui/form";
26+
import {
27+
Select,
28+
SelectContent,
29+
SelectGroup,
30+
SelectItem,
31+
SelectLabel,
32+
SelectTrigger,
33+
SelectValue,
34+
} from "@/components/ui/select";
2435
import { Switch } from "@/components/ui/switch";
2536
import { api } from "@/utils/api";
2637

27-
const formSchema = z.object({
28-
rollbackActive: z.boolean(),
29-
});
38+
const formSchema = z
39+
.object({
40+
rollbackActive: z.boolean(),
41+
rollbackRegistryId: z.string().optional(),
42+
})
43+
.superRefine((values, ctx) => {
44+
if (
45+
values.rollbackActive &&
46+
(!values.rollbackRegistryId || values.rollbackRegistryId === "none")
47+
) {
48+
ctx.addIssue({
49+
code: z.ZodIssueCode.custom,
50+
path: ["rollbackRegistryId"],
51+
message: "Registry is required when rollbacks are enabled",
52+
});
53+
}
54+
});
3055

3156
type FormValues = z.infer<typeof formSchema>;
3257

@@ -49,17 +74,33 @@ export const ShowRollbackSettings = ({ applicationId, children }: Props) => {
4974
const { mutateAsync: updateApplication, isLoading } =
5075
api.application.update.useMutation();
5176

77+
const { data: registries } = api.registry.all.useQuery();
78+
5279
const form = useForm<FormValues>({
5380
resolver: zodResolver(formSchema),
5481
defaultValues: {
5582
rollbackActive: application?.rollbackActive ?? false,
83+
rollbackRegistryId: application?.rollbackRegistryId || "",
5684
},
5785
});
5886

87+
useEffect(() => {
88+
if (application) {
89+
form.reset({
90+
rollbackActive: application.rollbackActive ?? false,
91+
rollbackRegistryId: application.rollbackRegistryId || "",
92+
});
93+
}
94+
}, [application, form]);
95+
5996
const onSubmit = async (data: FormValues) => {
6097
await updateApplication({
6198
applicationId,
6299
rollbackActive: data.rollbackActive,
100+
rollbackRegistryId:
101+
data.rollbackRegistryId === "none" || !data.rollbackRegistryId
102+
? null
103+
: data.rollbackRegistryId,
63104
})
64105
.then(() => {
65106
toast.success("Rollback settings updated");
@@ -112,6 +153,65 @@ export const ShowRollbackSettings = ({ applicationId, children }: Props) => {
112153
)}
113154
/>
114155

156+
{form.watch("rollbackActive") && (
157+
<FormField
158+
control={form.control}
159+
name="rollbackRegistryId"
160+
render={({ field }) => (
161+
<FormItem>
162+
<FormLabel>Rollback Registry</FormLabel>
163+
<Select
164+
onValueChange={field.onChange}
165+
value={field.value || "none"}
166+
>
167+
<FormControl>
168+
<SelectTrigger>
169+
<SelectValue placeholder="Select a registry" />
170+
</SelectTrigger>
171+
</FormControl>
172+
<SelectContent>
173+
<SelectGroup>
174+
<SelectItem value="none">
175+
<span className="flex items-center gap-2">
176+
<span>None</span>
177+
</span>
178+
</SelectItem>
179+
{registries?.map((registry) => (
180+
<SelectItem
181+
key={registry.registryId}
182+
value={registry.registryId}
183+
>
184+
{registry.registryName}
185+
</SelectItem>
186+
))}
187+
<SelectLabel>
188+
Registries ({registries?.length || 0})
189+
</SelectLabel>
190+
</SelectGroup>
191+
</SelectContent>
192+
</Select>
193+
{!registries || registries.length === 0 ? (
194+
<FormDescription className="text-amber-600 dark:text-amber-500">
195+
No registries available. Please{" "}
196+
<Link
197+
href="/dashboard/settings/registry"
198+
className="underline font-medium hover:text-amber-700 dark:hover:text-amber-400"
199+
>
200+
configure a registry
201+
</Link>{" "}
202+
first to enable rollbacks.
203+
</FormDescription>
204+
) : (
205+
<FormDescription>
206+
Select a registry where rollback images will be stored.
207+
</FormDescription>
208+
)}
209+
<FormMessage />
210+
</FormItem>
211+
)}
212+
/>
213+
)}
214+
115215
<Button type="submit" className="w-full" isLoading={isLoading}>
116216
Save Settings
117217
</Button>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE "application" ADD COLUMN "rollbackRegistryId" text;--> statement-breakpoint
2+
ALTER TABLE "application" ADD CONSTRAINT "application_rollbackRegistryId_registry_registryId_fk" FOREIGN KEY ("rollbackRegistryId") REFERENCES "public"."registry"("registryId") ON DELETE set null ON UPDATE no action;

0 commit comments

Comments
 (0)