Compare commits

...

17 Commits

Author SHA1 Message Date
Mauricio Siu
8d56544c1d Merge pull request #3844 from Dokploy/fix/improve-loading-queries
Fix/improve loading queries
2026-02-28 23:06:59 -06:00
Mauricio Siu
ca527ab6ff test: add mock implementation for member.findMany in application command and real tests 2026-02-28 22:59:24 -06:00
Mauricio Siu
439fa17292 chore: bump version to v0.28.2 in package.json 2026-02-28 22:52:04 -06:00
Mauricio Siu
096c04486c refactor: increase cache TTL for trusted origins in admin service 2026-02-28 22:42:15 -06:00
Mauricio Siu
c9e1079076 refactor: remove console log from BreadcrumbSidebar component 2026-02-28 22:39:20 -06:00
Mauricio Siu
e29a86a85f refactor: optimize trusted origins retrieval and caching in auth and admin services 2026-02-28 22:33:31 -06:00
Mauricio Siu
1ba0eb0c2e Merge pull request #3835 from Dokploy/3799-bug-misleading-error---compose-file-not-found-when-no-domains-configured
refactor: simplify domain handling in Docker compose utility functions
2026-02-28 11:27:34 -06:00
Mauricio Siu
d7dc10993e refactor: simplify domain handling in Docker compose utility functions 2026-02-28 11:23:16 -06:00
Mauricio Siu
2a5d3975e8 chore: remove proprietary README.md and add database connection logic in index.ts 2026-02-28 11:14:12 -06:00
Mauricio Siu
9f3356ddb4 Merge pull request #3834 from Dokploy/3833-error-when-connecting-a-git-github-app---internal-server-error
fix: handle optional chaining for organization and user IDs in GitHub…
2026-02-28 11:07:27 -06:00
Mauricio Siu
f5674f5bf8 fix: handle optional chaining for organization and user IDs in GitHub provider setup 2026-02-28 11:06:45 -06:00
Mauricio Siu
17a617e585 refactor: standardize dialog component formatting in MariaDB, MySQL, and Redis update files 2026-02-27 14:01:44 -06:00
Mauricio Siu
f50eea9e05 Merge pull request #3826 from Dokploy/3822-renaming-redis-doesnt-close-the-update-dialog
feat: add state management for dialog visibility in MariaDB, MySQL, a…
2026-02-27 14:01:02 -06:00
Mauricio Siu
81ee8f653a feat: add state management for dialog visibility in MariaDB, MySQL, and Redis update components 2026-02-27 14:00:35 -06:00
Mauricio Siu
9507745cc0 Merge pull request #3824 from Dokploy/fix/breaking-change-api-rever4t
chore: update @dokploy/trpc-openapi to version 0.0.17 and adjust Open…
2026-02-27 13:55:48 -06:00
Mauricio Siu
d33e164876 chore: bump version to v0.28.1 in package.json 2026-02-27 13:55:18 -06:00
Mauricio Siu
7e6e815375 chore: update @dokploy/trpc-openapi to version 0.0.17 and adjust OpenAPI document URL in settings router 2026-02-27 13:54:58 -06:00
16 changed files with 127 additions and 71 deletions

View File

@@ -14,13 +14,18 @@ vi.mock("@dokploy/server/db", () => {
set: vi.fn(() => chain),
where: vi.fn(() => chain),
returning: vi.fn().mockResolvedValue([{}] as any),
from: vi.fn(() => chain),
innerJoin: vi.fn(() => chain),
then: (resolve: (v: any) => void) => {
resolve([]);
},
} as any;
return chain;
};
return {
db: {
select: vi.fn(),
select: vi.fn(() => createChainableMock()),
insert: vi.fn(),
update: vi.fn(() => createChainableMock()),
delete: vi.fn(),
@@ -31,6 +36,9 @@ vi.mock("@dokploy/server/db", () => {
patch: {
findMany: vi.fn().mockResolvedValue([]),
},
member: {
findMany: vi.fn().mockResolvedValue([]),
},
},
},
};

View File

@@ -15,13 +15,18 @@ vi.mock("@dokploy/server/db", () => {
set: vi.fn(() => chain),
where: vi.fn(() => chain),
returning: vi.fn().mockResolvedValue([{}]),
from: vi.fn(() => chain),
innerJoin: vi.fn(() => chain),
then: (resolve: (v: any) => void) => {
resolve([]);
},
};
return chain;
};
return {
db: {
select: vi.fn(),
select: vi.fn(() => createChainableMock()),
insert: vi.fn(),
update: vi.fn(() => createChainableMock()),
delete: vi.fn(),
@@ -32,6 +37,9 @@ vi.mock("@dokploy/server/db", () => {
patch: {
findMany: vi.fn().mockResolvedValue([]),
},
member: {
findMany: vi.fn().mockResolvedValue([]),
},
},
},
};

View File

@@ -12,7 +12,11 @@ vi.mock("@dokploy/server/db", () => {
chain.where = () => chain;
chain.values = () => chain;
chain.returning = () => Promise.resolve([{}]);
chain.then = undefined;
chain.from = () => chain;
chain.innerJoin = () => chain;
chain.then = (resolve: (value: unknown) => void) => {
resolve([]);
};
const tableMock = {
findFirst: vi.fn(() => Promise.resolve(undefined)),
@@ -21,7 +25,6 @@ vi.mock("@dokploy/server/db", () => {
update: vi.fn(() => chain),
delete: vi.fn(() => chain),
};
const createQueryMock = () => tableMock;
return {
db: {

View File

@@ -1,6 +1,6 @@
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
import { PenBoxIcon } from "lucide-react";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
@@ -41,6 +41,7 @@ interface Props {
}
export const UpdateMariadb = ({ mariadbId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const utils = api.useUtils();
const { mutateAsync, error, isError, isPending } =
api.mariadb.update.useMutation();
@@ -79,6 +80,7 @@ export const UpdateMariadb = ({ mariadbId }: Props) => {
utils.mariadb.one.invalidate({
mariadbId: mariadbId,
});
setIsOpen(false);
})
.catch(() => {
toast.error("Error updating the Mariadb");
@@ -87,7 +89,7 @@ export const UpdateMariadb = ({ mariadbId }: Props) => {
};
return (
<Dialog>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button
variant="ghost"

View File

@@ -1,6 +1,6 @@
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
import { PenBoxIcon } from "lucide-react";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
@@ -41,6 +41,7 @@ interface Props {
}
export const UpdateMysql = ({ mysqlId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const utils = api.useUtils();
const { mutateAsync, error, isError, isPending } =
api.mysql.update.useMutation();
@@ -79,6 +80,7 @@ export const UpdateMysql = ({ mysqlId }: Props) => {
utils.mysql.one.invalidate({
mysqlId: mysqlId,
});
setIsOpen(false);
})
.catch(() => {
toast.error("Error updating MySQL");
@@ -87,7 +89,7 @@ export const UpdateMysql = ({ mysqlId }: Props) => {
};
return (
<Dialog>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button
variant="ghost"

View File

@@ -1,6 +1,6 @@
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
import { PenBoxIcon } from "lucide-react";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
@@ -41,6 +41,7 @@ interface Props {
}
export const UpdateRedis = ({ redisId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const utils = api.useUtils();
const { mutateAsync, error, isError, isPending } =
api.redis.update.useMutation();
@@ -79,6 +80,7 @@ export const UpdateRedis = ({ redisId }: Props) => {
utils.redis.one.invalidate({
redisId: redisId,
});
setIsOpen(false);
})
.catch(() => {
toast.error("Error updating Redis");
@@ -87,7 +89,7 @@ export const UpdateRedis = ({ redisId }: Props) => {
};
return (
<Dialog>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button
variant="ghost"

View File

@@ -30,7 +30,7 @@ export const AddGithubProvider = () => {
const url = document.location.origin;
const manifest = JSON.stringify(
{
redirect_url: `${origin}/api/providers/github/setup?organizationId=${activeOrganization?.id}&userId=${session?.user?.id}`,
redirect_url: `${origin}/api/providers/github/setup?organizationId=${activeOrganization?.id ?? ""}&userId=${session?.user?.id ?? ""}`,
name: `Dokploy-${format(new Date(), "yyyy-MM-dd")}-${randomString()}`,
url: origin,
hook_attributes: {
@@ -52,7 +52,7 @@ export const AddGithubProvider = () => {
);
setManifest(manifest);
}, [data?.id]);
}, [data?.id, activeOrganization?.id, session?.user?.id]);
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
@@ -131,7 +131,11 @@ export const AddGithubProvider = () => {
Unsure if you already have an app?
</a>
<Button
disabled={isOrganization && organizationName.length < 1}
disabled={
(isOrganization && organizationName.length < 1) ||
!activeOrganization?.id ||
!session?.user?.id
}
type="submit"
className="self-end"
>

View File

@@ -32,7 +32,6 @@ interface Props {
}
export const BreadcrumbSidebar = ({ list }: Props) => {
console.log(list);
return (
<header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
<div className="flex items-center justify-between w-full">

View File

@@ -1,6 +1,6 @@
{
"name": "dokploy",
"version": "v0.28.0",
"version": "v0.28.2",
"private": true,
"license": "Apache-2.0",
"type": "module",
@@ -55,7 +55,7 @@
"@codemirror/legacy-modes": "6.4.0",
"@codemirror/view": "6.29.0",
"@dokploy/server": "workspace:*",
"@dokploy/trpc-openapi": "0.0.16",
"@dokploy/trpc-openapi": "0.0.17",
"@faker-js/faker": "^8.4.1",
"@hookform/resolvers": "^5.2.2",
"@octokit/auth-app": "^6.1.3",

View File

@@ -1,18 +0,0 @@
# Proprietary Features
This directory contains all proprietary functionality of Dokploy.
## Purpose
This folder will house all **paid features** and premium functionality that are not part of the open source code.
## License
The code in this directory is under Dokploy's proprietary license. See [LICENSE_PROPRIETARY.md](../../../LICENSE_PROPRIETARY.md) for more details.
## Contact
If you want to learn more about our paid features or have any questions, please contact us at:
- Email: [sales@dokploy.com](mailto:sales@dokploy.com)
- Contact Form: [https://dokploy.com/contact](https://dokploy.com/contact)

View File

@@ -530,12 +530,12 @@ export const settingsRouter = createTRPCRouter({
getOpenApiDocument: protectedProcedure.query(
async ({ ctx }): Promise<unknown> => {
const protocol = ctx.req.headers["x-forwarded-proto"];
const url = `${protocol}://${ctx.req.headers.host}/api/trpc`;
const url = `${protocol}://${ctx.req.headers.host}/api`;
const openApiDocument = generateOpenApiDocument(appRouter, {
title: "tRPC OpenAPI",
version: packageInfo.version,
baseUrl: url,
docsUrl: `${url}/trpc/settings.getOpenApiDocument`,
docsUrl: `${url}/settings.getOpenApiDocument`,
tags: [
"admin",
"docker",

View File

@@ -0,0 +1,38 @@
import { dbUrl } from "@dokploy/server/db/constants";
import * as schema from "@dokploy/server/db/schema";
import { and, eq } from "drizzle-orm";
import { drizzle, type PostgresJsDatabase } from "drizzle-orm/postgres-js";
import postgres from "postgres";
export { and, eq };
type Database = PostgresJsDatabase<typeof schema>;
/**
* Evita problemas de redeclaración global en monorepos.
* No usamos `declare global`.
*/
const globalForDb = globalThis as unknown as {
db?: Database;
};
let dbConnection: Database;
if (process.env.NODE_ENV === "production") {
// En producción no usamos global cache
dbConnection = drizzle(postgres(dbUrl), {
schema,
});
} else {
// En desarrollo reutilizamos conexión para evitar múltiples conexiones
if (!globalForDb.db) {
globalForDb.db = drizzle(postgres(dbUrl), {
schema,
});
}
dbConnection = globalForDb.db;
}
export const db: Database = dbConnection;
export { dbUrl };

View File

@@ -73,23 +73,25 @@ const { handler, api } = betterAuth({
disabled: process.env.NODE_ENV === "production",
},
async trustedOrigins() {
const trustedOrigins = await getTrustedOrigins();
if (IS_CLOUD) {
return trustedOrigins;
return getTrustedOrigins();
}
const settings = await getWebServerSettings();
if (!settings) {
return [];
}
return [
...(settings?.serverIp ? [`http://${settings?.serverIp}:3000`] : []),
...(settings?.host ? [`https://${settings?.host}`] : []),
...(process.env.NODE_ENV === "development"
const [trustedOrigins, settings] = await Promise.all([
getTrustedOrigins(),
getWebServerSettings(),
]);
if (!settings) return [];
const devOrigins =
process.env.NODE_ENV === "development"
? [
"http://localhost:3000",
"https://absolutely-handy-falcon.ngrok-free.app",
]
: []),
: [];
return [
...(settings?.serverIp ? [`http://${settings?.serverIp}:3000`] : []),
...(settings?.host ? [`https://${settings?.host}`] : []),
...devOrigins,
...trustedOrigins,
];
},

View File

@@ -117,23 +117,33 @@ export const getDokployUrl = async () => {
return `http://${settings?.serverIp}:${process.env.PORT}`;
};
export const getTrustedOrigins = async () => {
const members = await db.query.member.findMany({
where: eq(member.role, "owner"),
with: {
user: true,
},
});
const TRUSTED_ORIGINS_CACHE_TTL_MS = 30 * 60_000;
let trustedOriginsCache: { data: string[]; expiresAt: number } | null = null;
if (members.length === 0) {
return [];
export const getTrustedOrigins = async () => {
const runQuery = async () => {
const rows = await db
.select({ trustedOrigins: user.trustedOrigins })
.from(member)
.innerJoin(user, eq(member.userId, user.id))
.where(eq(member.role, "owner"));
return Array.from(new Set(rows.flatMap((r) => r.trustedOrigins ?? [])));
};
if (IS_CLOUD) {
const now = Date.now();
if (trustedOriginsCache && now < trustedOriginsCache.expiresAt) {
return trustedOriginsCache.data;
}
const trustedOrigins = await runQuery();
trustedOriginsCache = {
data: trustedOrigins,
expiresAt: now + TRUSTED_ORIGINS_CACHE_TTL_MS,
};
return trustedOrigins;
}
const trustedOrigins = members.flatMap(
(member) => member.user.trustedOrigins || [],
);
return Array.from(new Set(trustedOrigins));
return runQuery();
};
export const getTrustedProviders = async () => {

View File

@@ -106,10 +106,6 @@ export const writeDomainsToCompose = async (
compose: Compose,
domains: Domain[],
) => {
if (!domains.length) {
return "";
}
try {
const composeConverted = await addDomainToCompose(compose, domains);
const path = getComposePath(compose);
@@ -145,7 +141,7 @@ export const addDomainToCompose = async (
result = await loadDockerCompose(compose);
}
if (!result || domains.length === 0) {
if (!result) {
return null;
}

10
pnpm-lock.yaml generated
View File

@@ -138,8 +138,8 @@ importers:
specifier: workspace:*
version: link:../../packages/server
'@dokploy/trpc-openapi':
specifier: 0.0.16
version: 0.0.16(@trpc/server@11.10.0(typescript@5.9.3))(zod-openapi@5.4.6(zod@4.3.6))(zod@4.3.6)
specifier: 0.0.17
version: 0.0.17(@trpc/server@11.10.0(typescript@5.9.3))(zod-openapi@5.4.6(zod@4.3.6))(zod@4.3.6)
'@faker-js/faker':
specifier: ^8.4.1
version: 8.4.1
@@ -1291,8 +1291,8 @@ packages:
'@codemirror/view@6.39.15':
resolution: {integrity: sha512-aCWjgweIIXLBHh7bY6cACvXuyrZ0xGafjQ2VInjp4RM4gMfscK5uESiNdrH0pE+e1lZr2B4ONGsjchl2KsKZzg==}
'@dokploy/trpc-openapi@0.0.16':
resolution: {integrity: sha512-95pukFwMkSKLfnUB21OnYVwmR832NVD6ewI65ZgpxYxTrmdVGCUzvphqs85fiq4UKV3qcuUSq3nn47d3Sh09zg==}
'@dokploy/trpc-openapi@0.0.17':
resolution: {integrity: sha512-pXWbqx2W0MoWav/wehEqcXzORLgn7PhnmLsZza1v6+lOSo0Vwuu47PrITbRYKQ2zZcR1nTL18TrgPuMzXK23Iw==}
peerDependencies:
'@trpc/server': ^11.1.0
zod: ^4.3.6
@@ -8867,7 +8867,7 @@ snapshots:
style-mod: 4.1.3
w3c-keyname: 2.2.8
'@dokploy/trpc-openapi@0.0.16(@trpc/server@11.10.0(typescript@5.9.3))(zod-openapi@5.4.6(zod@4.3.6))(zod@4.3.6)':
'@dokploy/trpc-openapi@0.0.17(@trpc/server@11.10.0(typescript@5.9.3))(zod-openapi@5.4.6(zod@4.3.6))(zod@4.3.6)':
dependencies:
'@trpc/server': 11.10.0(typescript@5.9.3)
co-body: 6.2.0