diff --git a/README.md b/README.md index 8faf22a35..d60962cff 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,10 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
Hostinger LX Aer + + + +
diff --git a/apps/dokploy/__test__/templates/helpers.template.test.ts b/apps/dokploy/__test__/templates/helpers.template.test.ts index 1144b65fe..3ae92ae20 100644 --- a/apps/dokploy/__test__/templates/helpers.template.test.ts +++ b/apps/dokploy/__test__/templates/helpers.template.test.ts @@ -228,5 +228,58 @@ describe("helpers functions", () => { "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MzU2ODk2MDAsImV4cCI6MTczNTY5MzIwMCwiaXNzIjoidGVzdC1pc3N1ZXIiLCJjdXN0b21wcm9wIjoiY3VzdG9tdmFsdWUifQ.m42U7PZSUSCf7gBOJrxJir0rQmyPq4rA59Dydr_QahI", ); }); + + it("should handle JWT payload with newlines and whitespace by trimming them", () => { + const iat = Math.floor(new Date("2025-01-01T00:00:00Z").getTime() / 1000); + const expiry = iat + 3600; + const payloadWithNewlines = `{ + "role": "anon", + "iss": "supabase", + "exp": ${expiry} +} +`; + const jwt = processValue( + "${jwt:secret:payload}", + { + secret: "mysecret", + payload: payloadWithNewlines, + }, + mockSchema, + ); + expect(jwt).toMatch(jwtMatchExp); + const parts = jwt.split(".") as JWTParts; + jwtCheckHeader(parts[0]); + const decodedPayload = jwtBase64Decode(parts[1]); + expect(decodedPayload).toHaveProperty("role"); + expect(decodedPayload.role).toEqual("anon"); + expect(decodedPayload).toHaveProperty("iss"); + expect(decodedPayload.iss).toEqual("supabase"); + expect(decodedPayload).toHaveProperty("exp"); + expect(decodedPayload.exp).toEqual(expiry); + }); + + it("should handle JWT payload with leading and trailing whitespace", () => { + const iat = Math.floor(new Date("2025-01-01T00:00:00Z").getTime() / 1000); + const expiry = iat + 3600; + const payloadWithWhitespace = ` {"role": "service_role", "iss": "supabase", "exp": ${expiry}} `; + const jwt = processValue( + "${jwt:secret:payload}", + { + secret: "mysecret", + payload: payloadWithWhitespace, + }, + mockSchema, + ); + expect(jwt).toMatch(jwtMatchExp); + const parts = jwt.split(".") as JWTParts; + jwtCheckHeader(parts[0]); + const decodedPayload = jwtBase64Decode(parts[1]); + expect(decodedPayload).toHaveProperty("role"); + expect(decodedPayload.role).toEqual("service_role"); + expect(decodedPayload).toHaveProperty("iss"); + expect(decodedPayload.iss).toEqual("supabase"); + expect(decodedPayload).toHaveProperty("exp"); + expect(decodedPayload.exp).toEqual(expiry); + }); }); }); diff --git a/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx b/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx index 25040067b..3beedcdbc 100644 --- a/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx @@ -150,7 +150,10 @@ export const ShowResources = ({ id, type }: Props) => { render={({ field }) => { return ( -
+
e.preventDefault()} + > Memory Limit @@ -182,7 +185,10 @@ export const ShowResources = ({ id, type }: Props) => { name="memoryReservation" render={({ field }) => ( -
+
e.preventDefault()} + > Memory Reservation @@ -215,7 +221,10 @@ export const ShowResources = ({ id, type }: Props) => { render={({ field }) => { return ( -
+
e.preventDefault()} + > CPU Limit @@ -249,7 +258,10 @@ export const ShowResources = ({ id, type }: Props) => { render={({ field }) => { return ( -
+
e.preventDefault()} + > CPU Reservation diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx index 6f6db5dd1..1f54ddd58 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx @@ -150,7 +150,7 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => { enableSubmodules: data.enableSubmodules || false, }) .then(async () => { - toast.success("Service Provided Saved"); + toast.success("Service Provider Saved"); await refetch(); }) .catch(() => { diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx index 9a4b92ce1..80d6850ca 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx @@ -149,7 +149,7 @@ export const SaveGithubProvider = ({ applicationId }: Props) => { enableSubmodules: data.enableSubmodules, }) .then(async () => { - toast.success("Service Provided Saved"); + toast.success("Service Provider Saved"); await refetch(); }) .catch(() => { diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx index cb7209f8a..d6f65caf3 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx @@ -167,7 +167,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => { enableSubmodules: data.enableSubmodules, }) .then(async () => { - toast.success("Service Provided Saved"); + toast.success("Service Provider Saved"); await refetch(); }) .catch(() => { diff --git a/apps/dokploy/components/dashboard/application/schedules/show-schedules.tsx b/apps/dokploy/components/dashboard/application/schedules/show-schedules.tsx index 3209b6e03..da3c0b6ed 100644 --- a/apps/dokploy/components/dashboard/application/schedules/show-schedules.tsx +++ b/apps/dokploy/components/dashboard/application/schedules/show-schedules.tsx @@ -48,10 +48,8 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => { ); const utils = api.useUtils(); - const { mutateAsync: deleteSchedule, isLoading: isDeleting } = api.schedule.delete.useMutation(); - const { mutateAsync: runManually, isLoading } = api.schedule.runManually.useMutation(); @@ -67,7 +65,6 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => { Schedule tasks to run automatically at specified intervals.
- {schedules && schedules.length > 0 && ( )} @@ -75,7 +72,7 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => { {isLoadingSchedules ? ( -
+
Loading scheduled tasks... @@ -91,13 +88,13 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => { return (
-
+
-
+

{schedule.name} @@ -132,27 +129,25 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => { )}

{schedule.command && ( -
- - +
+ + {schedule.command}
)}
- -
+
- @@ -163,7 +158,6 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => { isLoading={isLoading} onClick={async () => { toast.success("Schedule run successfully"); - await runManually({ scheduleId: schedule.scheduleId, }) @@ -178,19 +172,17 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => { }); }} > - + Run Manual Schedule - - { + + +

Copy

+
+ + + + + + + + + +

Download

+
+
+
+
+
{backupCodes.map((code, index) => ( { > diff --git a/apps/dokploy/components/shared/alert-block.tsx b/apps/dokploy/components/shared/alert-block.tsx index 5a6d411a9..045391b55 100644 --- a/apps/dokploy/components/shared/alert-block.tsx +++ b/apps/dokploy/components/shared/alert-block.tsx @@ -39,13 +39,19 @@ export function AlertBlock({
- {icon || } - {children} +
+ {icon || } +
+
+ + {children} + +
); } diff --git a/apps/dokploy/components/ui/button.tsx b/apps/dokploy/components/ui/button.tsx index d2db30cd2..f504b4c2d 100644 --- a/apps/dokploy/components/ui/button.tsx +++ b/apps/dokploy/components/ui/button.tsx @@ -55,6 +55,8 @@ const Button = React.forwardRef( ref, ) => { const Comp = asChild ? Slot : "button"; + const type = props.type ?? undefined; + return ( <> ( ref={ref} {...props} disabled={isLoading || props.disabled} + type={type} > {isLoading && } {children} diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx index e3f75a852..0d4469330 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId].tsx @@ -14,10 +14,9 @@ import { PlusIcon, Search, ServerIcon, + SquareTerminal, Trash2, X, - Terminal, - SquareTerminal, } from "lucide-react"; import type { GetServerSidePropsContext, @@ -35,8 +34,8 @@ import { AddDatabase } from "@/components/dashboard/project/add-database"; import { AddTemplate } from "@/components/dashboard/project/add-template"; import { AdvancedEnvironmentSelector } from "@/components/dashboard/project/advanced-environment-selector"; import { DuplicateProject } from "@/components/dashboard/project/duplicate-project"; +import { EnvironmentVariables } from "@/components/dashboard/project/environment-variables"; import { ProjectEnvironment } from "@/components/dashboard/projects/project-environment"; - import { MariadbIcon, MongodbIcon, @@ -49,6 +48,7 @@ import { AlertBlock } from "@/components/shared/alert-block"; import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar"; import { DateTooltip } from "@/components/shared/date-tooltip"; import { DialogAction } from "@/components/shared/dialog-action"; +import { FocusShortcutInput } from "@/components/shared/focus-shortcut-input"; import { StatusTooltip } from "@/components/shared/status-tooltip"; import { Button } from "@/components/ui/button"; import { @@ -98,8 +98,6 @@ import { import { cn } from "@/lib/utils"; import { appRouter } from "@/server/api/root"; import { api } from "@/utils/api"; -import { FocusShortcutInput } from "@/components/shared/focus-shortcut-input"; -import { EnvironmentVariables } from "@/components/dashboard/project/environment-variables"; export type Services = { appName: string; @@ -781,7 +779,7 @@ const EnvironmentPage = ( currentEnvironmentId={environmentId} /> - + diff --git a/apps/dokploy/pages/register.tsx b/apps/dokploy/pages/register.tsx index 60a3d8de4..4452eb1ea 100644 --- a/apps/dokploy/pages/register.tsx +++ b/apps/dokploy/pages/register.tsx @@ -101,7 +101,7 @@ const Register = ({ isCloud }: Props) => { setIsError(true); setError(error.message || "An error occurred"); } else { - toast.success("User registered successfuly", { + toast.success("User registered successfully", { duration: 2000, }); if (!isCloud) { diff --git a/apps/dokploy/utils/gitea-utils.ts b/apps/dokploy/utils/gitea-utils.ts index ab7b82dcc..6099aaa45 100644 --- a/apps/dokploy/utils/gitea-utils.ts +++ b/apps/dokploy/utils/gitea-utils.ts @@ -22,7 +22,7 @@ export const getGiteaOAuthUrl = ( } const redirectUri = `${baseUrl}/api/providers/gitea/callback`; - const scopes = "repo repo:status read:user read:org"; + const scopes = "read:repository read:user read:organization"; return `${giteaUrl}/login/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent( redirectUri, diff --git a/packages/server/src/templates/processors.ts b/packages/server/src/templates/processors.ts index 5d9270aa1..9e73fb1f4 100644 --- a/packages/server/src/templates/processors.ts +++ b/packages/server/src/templates/processors.ts @@ -141,8 +141,8 @@ export function processValue( } if ( typeof payload === "string" && - payload.startsWith("{") && - payload.endsWith("}") + payload.trimStart().startsWith("{") && + payload.trimEnd().endsWith("}") ) { try { payload = JSON.parse(payload); diff --git a/packages/server/src/utils/traefik/middleware.ts b/packages/server/src/utils/traefik/middleware.ts index 907e65afd..681b0b831 100644 --- a/packages/server/src/utils/traefik/middleware.ts +++ b/packages/server/src/utils/traefik/middleware.ts @@ -46,8 +46,14 @@ export const deleteMiddleware = ( }; export const deleteAllMiddlewares = async (application: ApplicationNested) => { - const config = loadMiddlewares(); - const { security, appName, redirects } = application; + const { security, appName, redirects, serverId } = application; + let config: FileConfig; + + if (serverId) { + config = await loadRemoteMiddlewares(serverId); + } else { + config = loadMiddlewares(); + } if (config.http?.middlewares) { if (security.length > 0) { @@ -62,8 +68,8 @@ export const deleteAllMiddlewares = async (application: ApplicationNested) => { } } - if (application.serverId) { - await writeTraefikConfigRemote(config, "middlewares", application.serverId); + if (serverId) { + await writeTraefikConfigRemote(config, "middlewares", serverId); } else { writeMiddleware(config); }