Skip to content

Commit 304454b

Browse files
authored
Merge pull request #3312 from Dokploy/canary
🚀 Release v0.26.3
2 parents 42c2076 + 9498fbe commit 304454b

70 files changed

Lines changed: 9010 additions & 696 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/dokploy/__test__/cluster/upload.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,38 @@ describe("getRegistryTag", () => {
206206
expect(result).toBe("docker.io/myuser/repo");
207207
});
208208
});
209+
210+
describe("special characters in username", () => {
211+
it("should handle Harbor robot account username with $ (e.g. robot$library+dokploy)", () => {
212+
const registry = createMockRegistry({
213+
username: "robot$library+dokploy",
214+
});
215+
const result = getRegistryTag(registry, "nginx");
216+
expect(result).toBe("docker.io/robot$library+dokploy/nginx");
217+
});
218+
219+
it("should handle username with $ and other special characters", () => {
220+
const registry = createMockRegistry({
221+
username: "robot$test+app",
222+
});
223+
const result = getRegistryTag(registry, "myapp:latest");
224+
expect(result).toBe("docker.io/robot$test+app/myapp:latest");
225+
});
226+
227+
it("should handle username with multiple $ symbols", () => {
228+
const registry = createMockRegistry({
229+
username: "user$name$test",
230+
});
231+
const result = getRegistryTag(registry, "app");
232+
expect(result).toBe("docker.io/user$name$test/app");
233+
});
234+
235+
it("should handle username with + and - symbols", () => {
236+
const registry = createMockRegistry({
237+
username: "robot+test-user",
238+
});
239+
const result = getRegistryTag(registry, "nginx:latest");
240+
expect(result).toBe("docker.io/robot+test-user/nginx:latest");
241+
});
242+
});
209243
});

apps/dokploy/__test__/compose/domain/host-rule-format.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Domain } from "@dokploy/server";
22
import { createDomainLabels } from "@dokploy/server";
3-
import { parse, stringify } from "yaml";
43
import { describe, expect, it } from "vitest";
4+
import { parse, stringify } from "yaml";
55

66
/**
77
* Regression tests for Traefik Host rule label format.

apps/dokploy/__test__/templates/helpers.template.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,50 @@ describe("helpers functions", () => {
161161
});
162162
});
163163

164+
describe("Empty string variables", () => {
165+
it("should replace variables with empty string values correctly", () => {
166+
const variables = {
167+
smtp_username: "",
168+
smtp_password: "",
169+
non_empty: "value",
170+
};
171+
172+
const result1 = processValue("${smtp_username}", variables, mockSchema);
173+
expect(result1).toBe("");
174+
175+
const result2 = processValue("${smtp_password}", variables, mockSchema);
176+
expect(result2).toBe("");
177+
178+
const result3 = processValue("${non_empty}", variables, mockSchema);
179+
expect(result3).toBe("value");
180+
});
181+
182+
it("should not replace undefined variables", () => {
183+
const variables = {
184+
defined_var: "",
185+
};
186+
187+
const result = processValue("${undefined_var}", variables, mockSchema);
188+
expect(result).toBe("${undefined_var}");
189+
});
190+
191+
it("should handle mixed empty and non-empty variables in template", () => {
192+
const variables = {
193+
smtp_address: "smtp.example.com",
194+
smtp_port: "2525",
195+
smtp_username: "",
196+
smtp_password: "",
197+
};
198+
199+
const template =
200+
"SMTP_ADDRESS=${smtp_address} SMTP_PORT=${smtp_port} SMTP_USERNAME=${smtp_username} SMTP_PASSWORD=${smtp_password}";
201+
const result = processValue(template, variables, mockSchema);
202+
expect(result).toBe(
203+
"SMTP_ADDRESS=smtp.example.com SMTP_PORT=2525 SMTP_USERNAME= SMTP_PASSWORD=",
204+
);
205+
});
206+
});
207+
164208
describe("${jwt}", () => {
165209
it("should generate a JWT string", () => {
166210
const jwt = processValue("${jwt}", {}, mockSchema);

apps/dokploy/__test__/traefik/server/update-server-config.test.ts

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,27 @@ vi.mock("node:fs", () => ({
55
default: fs,
66
}));
77

8-
import type { FileConfig, User } from "@dokploy/server";
8+
import type { FileConfig } from "@dokploy/server";
99
import {
1010
createDefaultServerTraefikConfig,
1111
loadOrCreateConfig,
1212
updateServerTraefik,
1313
} from "@dokploy/server";
14+
import type { webServerSettings } from "@dokploy/server/db/schema";
1415
import { beforeEach, expect, test, vi } from "vitest";
1516

16-
const baseAdmin: User = {
17+
type WebServerSettings = typeof webServerSettings.$inferSelect;
18+
19+
const baseSettings: WebServerSettings = {
20+
id: "",
1721
https: false,
18-
enablePaidFeatures: false,
19-
allowImpersonation: false,
20-
role: "user",
21-
firstName: "",
22-
lastName: "",
22+
certificateType: "none",
23+
host: null,
24+
serverIp: null,
25+
letsEncryptEmail: null,
26+
sshPrivateKey: null,
27+
enableDockerCleanup: false,
28+
logCleanupCron: null,
2329
metricsConfig: {
2430
containers: {
2531
refreshRate: 20,
@@ -45,29 +51,8 @@ const baseAdmin: User = {
4551
cleanupCacheApplications: false,
4652
cleanupCacheOnCompose: false,
4753
cleanupCacheOnPreviews: false,
48-
createdAt: new Date(),
49-
serverIp: null,
50-
certificateType: "none",
51-
host: null,
52-
letsEncryptEmail: null,
53-
sshPrivateKey: null,
54-
enableDockerCleanup: false,
55-
logCleanupCron: null,
56-
serversQuantity: 0,
57-
stripeCustomerId: "",
58-
stripeSubscriptionId: "",
59-
banExpires: new Date(),
60-
banned: true,
61-
banReason: "",
62-
email: "",
63-
expirationDate: "",
64-
id: "",
65-
isRegistered: false,
66-
createdAt2: new Date().toISOString(),
67-
emailVerified: false,
68-
image: "",
54+
createdAt: null,
6955
updatedAt: new Date(),
70-
twoFactorEnabled: false,
7156
};
7257

7358
beforeEach(() => {
@@ -85,7 +70,7 @@ test("Should read the configuration file", () => {
8570
test("Should apply redirect-to-https", () => {
8671
updateServerTraefik(
8772
{
88-
...baseAdmin,
73+
...baseSettings,
8974
https: true,
9075
certificateType: "letsencrypt",
9176
},
@@ -100,7 +85,7 @@ test("Should apply redirect-to-https", () => {
10085
});
10186

10287
test("Should change only host when no certificate", () => {
103-
updateServerTraefik(baseAdmin, "example.com");
88+
updateServerTraefik(baseSettings, "example.com");
10489

10590
const config: FileConfig = loadOrCreateConfig("dokploy");
10691

@@ -110,7 +95,7 @@ test("Should change only host when no certificate", () => {
11095
test("Should not touch config without host", () => {
11196
const originalConfig: FileConfig = loadOrCreateConfig("dokploy");
11297

113-
updateServerTraefik(baseAdmin, null);
98+
updateServerTraefik(baseSettings, null);
11499

115100
const config: FileConfig = loadOrCreateConfig("dokploy");
116101

@@ -119,11 +104,14 @@ test("Should not touch config without host", () => {
119104

120105
test("Should remove websecure if https rollback to http", () => {
121106
updateServerTraefik(
122-
{ ...baseAdmin, certificateType: "letsencrypt" },
107+
{ ...baseSettings, certificateType: "letsencrypt" },
123108
"example.com",
124109
);
125110

126-
updateServerTraefik({ ...baseAdmin, certificateType: "none" }, "example.com");
111+
updateServerTraefik(
112+
{ ...baseSettings, certificateType: "none" },
113+
"example.com",
114+
);
127115

128116
const config: FileConfig = loadOrCreateConfig("dokploy");
129117

apps/dokploy/components/dashboard/application/advanced/show-resources.tsx

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ import {
2121
FormLabel,
2222
FormMessage,
2323
} from "@/components/ui/form";
24-
import { Input } from "@/components/ui/input";
24+
import {
25+
createConverter,
26+
NumberInputWithSteps,
27+
} from "@/components/ui/number-input";
2528
import {
2629
Tooltip,
2730
TooltipContent,
@@ -30,6 +33,23 @@ import {
3033
} from "@/components/ui/tooltip";
3134
import { api } from "@/utils/api";
3235

36+
const CPU_STEP = 0.25;
37+
const MEMORY_STEP_MB = 256;
38+
39+
const formatNumber = (value: number, decimals = 2): string =>
40+
Number.isInteger(value) ? String(value) : value.toFixed(decimals);
41+
42+
const cpuConverter = createConverter(1_000_000_000, (cpu) =>
43+
cpu <= 0 ? "" : `${formatNumber(cpu)} CPU`,
44+
);
45+
46+
const memoryConverter = createConverter(1024 * 1024, (mb) => {
47+
if (mb <= 0) return "";
48+
return mb >= 1024
49+
? `${formatNumber(mb / 1024)} GB`
50+
: `${formatNumber(mb)} MB`;
51+
});
52+
3353
const addResourcesSchema = z.object({
3454
memoryReservation: z.string().optional(),
3555
cpuLimit: z.string().optional(),
@@ -51,6 +71,7 @@ interface Props {
5171
}
5272

5373
type AddResources = z.infer<typeof addResourcesSchema>;
74+
5475
export const ShowResources = ({ id, type }: Props) => {
5576
const queryMap = {
5677
postgres: () =>
@@ -163,16 +184,20 @@ export const ShowResources = ({ id, type }: Props) => {
163184
<TooltipContent>
164185
<p>
165186
Memory hard limit in bytes. Example: 1GB =
166-
1073741824 bytes
187+
1073741824 bytes. Use +/- buttons to adjust by
188+
256 MB.
167189
</p>
168190
</TooltipContent>
169191
</Tooltip>
170192
</TooltipProvider>
171193
</div>
172194
<FormControl>
173-
<Input
195+
<NumberInputWithSteps
196+
value={field.value}
197+
onChange={field.onChange}
174198
placeholder="1073741824 (1GB in bytes)"
175-
{...field}
199+
step={MEMORY_STEP_MB}
200+
converter={memoryConverter}
176201
/>
177202
</FormControl>
178203
<FormMessage />
@@ -198,16 +223,20 @@ export const ShowResources = ({ id, type }: Props) => {
198223
<TooltipContent>
199224
<p>
200225
Memory soft limit in bytes. Example: 256MB =
201-
268435456 bytes
226+
268435456 bytes. Use +/- buttons to adjust by 256
227+
MB.
202228
</p>
203229
</TooltipContent>
204230
</Tooltip>
205231
</TooltipProvider>
206232
</div>
207233
<FormControl>
208-
<Input
234+
<NumberInputWithSteps
235+
value={field.value}
236+
onChange={field.onChange}
209237
placeholder="268435456 (256MB in bytes)"
210-
{...field}
238+
step={MEMORY_STEP_MB}
239+
converter={memoryConverter}
211240
/>
212241
</FormControl>
213242
<FormMessage />
@@ -234,17 +263,20 @@ export const ShowResources = ({ id, type }: Props) => {
234263
<TooltipContent>
235264
<p>
236265
CPU quota in units of 10^-9 CPUs. Example: 2
237-
CPUs = 2000000000
266+
CPUs = 2000000000. Use +/- buttons to adjust by
267+
0.25 CPU.
238268
</p>
239269
</TooltipContent>
240270
</Tooltip>
241271
</TooltipProvider>
242272
</div>
243273
<FormControl>
244-
<Input
274+
<NumberInputWithSteps
275+
value={field.value}
276+
onChange={field.onChange}
245277
placeholder="2000000000 (2 CPUs)"
246-
{...field}
247-
value={field.value?.toString() || ""}
278+
step={CPU_STEP}
279+
converter={cpuConverter}
248280
/>
249281
</FormControl>
250282
<FormMessage />
@@ -271,14 +303,21 @@ export const ShowResources = ({ id, type }: Props) => {
271303
<TooltipContent>
272304
<p>
273305
CPU shares (relative weight). Example: 1 CPU =
274-
1000000000
306+
1000000000. Use +/- buttons to adjust by 0.25
307+
CPU.
275308
</p>
276309
</TooltipContent>
277310
</Tooltip>
278311
</TooltipProvider>
279312
</div>
280313
<FormControl>
281-
<Input placeholder="1000000000 (1 CPU)" {...field} />
314+
<NumberInputWithSteps
315+
value={field.value}
316+
onChange={field.onChange}
317+
placeholder="1000000000 (1 CPU)"
318+
step={CPU_STEP}
319+
converter={cpuConverter}
320+
/>
282321
</FormControl>
283322
<FormMessage />
284323
</FormItem>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
123123
previewCertificateType: data.previewCertificateType || "none",
124124
previewCustomCertResolver: data.previewCustomCertResolver || "",
125125
previewRequireCollaboratorPermissions:
126-
data.previewRequireCollaboratorPermissions || true,
126+
data.previewRequireCollaboratorPermissions ?? true,
127127
});
128128
}
129129
}, [data]);

0 commit comments

Comments
 (0)